Sfoglia il codice sorgente

Keep Rust PRs green with a minimal CI gate

Add a focused GitHub Actions workflow for pull requests into main plus
manual dispatch. The workflow checks workspace formatting and runs the
rusty-claude-cli crate tests so we get a real signal on the active Rust
surface without widening scope into a full matrix.

Because the workspace was not rustfmt-clean, include the formatting-only
updates needed for the new fmt gate to pass immediately.

Constraint: Keep scope to a fast, low-noise Rust PR gate
Constraint: CI should validate formatting and rusty-claude-cli without expanding to full workspace coverage
Rejected: Full workspace test or clippy matrix | too broad for the one-hour shipping window
Rejected: Add fmt CI without reformatting the workspace | the new gate would fail on arrival
Confidence: high
Scope-risk: narrow
Directive: Keep this workflow focused unless release requirements justify broader coverage
Tested: cargo fmt --all -- --check
Tested: cargo test -p rusty-claude-cli
Tested: YAML parse of .github/workflows/rust-ci.yml via python3 + PyYAML
Not-tested: End-to-end execution on GitHub-hosted runners
Yeachan-Heo 2 mesi fa
parent
commit
aea6b9162f

+ 48 - 0
.github/workflows/rust-ci.yml

@@ -0,0 +1,48 @@
+name: Rust CI
+
+on:
+  pull_request:
+    branches:
+      - main
+    paths:
+      - .github/workflows/rust-ci.yml
+      - rust/**
+  workflow_dispatch:
+
+concurrency:
+  group: rust-ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+  cancel-in-progress: true
+
+defaults:
+  run:
+    working-directory: rust
+
+env:
+  CARGO_TERM_COLOR: always
+
+jobs:
+  fmt:
+    name: cargo fmt
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: dtolnay/rust-toolchain@stable
+        with:
+          components: rustfmt
+      - uses: Swatinem/rust-cache@v2
+        with:
+          workspaces: rust -> target
+      - name: Check formatting
+        run: cargo fmt --all -- --check
+
+  test-rusty-claude-cli:
+    name: cargo test -p rusty-claude-cli
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: dtolnay/rust-toolchain@stable
+      - uses: Swatinem/rust-cache@v2
+        with:
+          workspaces: rust -> target
+      - name: Run crate tests
+        run: cargo test -p rusty-claude-cli

+ 4 - 1
rust/crates/api/src/providers/anthropic.rs

@@ -322,7 +322,10 @@ impl AnthropicClient {
                     .with_property(
                         "estimated_cost_usd",
                         Value::String(format_usd(
-                            response.usage.estimated_cost_usd(&response.model).total_cost_usd(),
+                            response
+                                .usage
+                                .estimated_cost_usd(&response.model)
+                                .total_cost_usd(),
                         )),
                     ),
             );

+ 6 - 6
rust/crates/runtime/src/lib.rs

@@ -24,17 +24,17 @@ pub use compact::{
     get_compact_continuation_message, should_compact, CompactionConfig, CompactionResult,
 };
 pub use config::{
-    ConfigEntry, ConfigError, ConfigLoader, ConfigSource, McpManagedProxyServerConfig,
-    McpConfigCollection, McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig,
+    ConfigEntry, ConfigError, ConfigLoader, ConfigSource, McpConfigCollection,
+    McpManagedProxyServerConfig, McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig,
     McpServerConfig, McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig,
     ResolvedPermissionMode, RuntimeConfig, RuntimeFeatureConfig, RuntimeHookConfig,
     RuntimePermissionRuleConfig, RuntimePluginConfig, ScopedMcpServerConfig,
     CLAW_SETTINGS_SCHEMA_NAME,
 };
 pub use conversation::{
-    auto_compaction_threshold_from_env, ApiClient, ApiRequest, AssistantEvent,
-    AutoCompactionEvent, ConversationRuntime, PromptCacheEvent, RuntimeError,
-    StaticToolExecutor, ToolError, ToolExecutor, TurnSummary,
+    auto_compaction_threshold_from_env, ApiClient, ApiRequest, AssistantEvent, AutoCompactionEvent,
+    ConversationRuntime, PromptCacheEvent, RuntimeError, StaticToolExecutor, ToolError,
+    ToolExecutor, TurnSummary,
 };
 pub use file_ops::{
     edit_file, glob_search, grep_search, read_file, write_file, EditFileOutput, GlobSearchOutput,
@@ -49,7 +49,7 @@ pub use mcp::{
     scoped_mcp_config_hash, unwrap_ccr_proxy_url,
 };
 pub use mcp_client::{
-    McpManagedProxyTransport, McpClientAuth, McpClientBootstrap, McpClientTransport,
+    McpClientAuth, McpClientBootstrap, McpClientTransport, McpManagedProxyTransport,
     McpRemoteTransport, McpSdkTransport, McpStdioTransport,
 };
 pub use mcp_stdio::{

+ 4 - 6
rust/crates/runtime/src/mcp_client.rs

@@ -97,12 +97,10 @@ impl McpClientTransport {
             McpServerConfig::Sdk(config) => Self::Sdk(McpSdkTransport {
                 name: config.name.clone(),
             }),
-            McpServerConfig::ManagedProxy(config) => {
-                Self::ManagedProxy(McpManagedProxyTransport {
-                    url: config.url.clone(),
-                    id: config.id.clone(),
-                })
-            }
+            McpServerConfig::ManagedProxy(config) => Self::ManagedProxy(McpManagedProxyTransport {
+                url: config.url.clone(),
+                id: config.id.clone(),
+            }),
         }
     }
 }

+ 25 - 20
rust/crates/rusty-claude-cli/src/main.rs

@@ -30,12 +30,11 @@ use plugins::{PluginManager, PluginManagerConfig};
 use render::{MarkdownStreamState, Spinner, TerminalRenderer};
 use runtime::{
     clear_oauth_credentials, generate_pkce_pair, generate_state, load_system_prompt,
-    parse_oauth_callback_request_target, resolve_sandbox_status, save_oauth_credentials,
-    ApiClient, ApiRequest, AssistantEvent, CompactionConfig, ConfigLoader, ConfigSource,
-    ContentBlock, ConversationMessage, ConversationRuntime, MessageRole, PromptCacheEvent,
-    OAuthAuthorizationRequest, OAuthConfig,
-    OAuthTokenExchangeRequest, PermissionMode, PermissionPolicy, ProjectContext, RuntimeError,
-    Session, TokenUsage, ToolError, ToolExecutor, UsageTracker,
+    parse_oauth_callback_request_target, resolve_sandbox_status, save_oauth_credentials, ApiClient,
+    ApiRequest, AssistantEvent, CompactionConfig, ConfigLoader, ConfigSource, ContentBlock,
+    ConversationMessage, ConversationRuntime, MessageRole, OAuthAuthorizationRequest, OAuthConfig,
+    OAuthTokenExchangeRequest, PermissionMode, PermissionPolicy, ProjectContext, PromptCacheEvent,
+    RuntimeError, Session, TokenUsage, ToolError, ToolExecutor, UsageTracker,
 };
 use serde_json::json;
 use tools::GlobalToolRegistry;
@@ -3146,7 +3145,8 @@ fn build_runtime(
     allowed_tools: Option<AllowedToolSet>,
     permission_mode: PermissionMode,
     progress_reporter: Option<InternalPromptProgressReporter>,
-) -> Result<ConversationRuntime<AnthropicRuntimeClient, CliToolExecutor>, Box<dyn std::error::Error>> {
+) -> Result<ConversationRuntime<AnthropicRuntimeClient, CliToolExecutor>, Box<dyn std::error::Error>>
+{
     let (feature_config, tool_registry) = build_runtime_plugin_state()?;
     let mut runtime = ConversationRuntime::new_with_features(
         session,
@@ -3286,7 +3286,6 @@ impl AnthropicRuntimeClient {
             progress_reporter,
         })
     }
-
 }
 
 fn resolve_cli_auth_source() -> Result<AuthSource, Box<dyn std::error::Error>> {
@@ -4023,7 +4022,9 @@ fn push_prompt_cache_record(client: &AnthropicClient, events: &mut Vec<Assistant
     }
 }
 
-fn prompt_cache_record_to_runtime_event(record: api::PromptCacheRecord) -> Option<PromptCacheEvent> {
+fn prompt_cache_record_to_runtime_event(
+    record: api::PromptCacheRecord,
+) -> Option<PromptCacheEvent> {
     let cache_break = record.cache_break?;
     Some(PromptCacheEvent {
         unexpected: cache_break.unexpected,
@@ -4245,18 +4246,17 @@ fn print_help() {
 #[cfg(test)]
 mod tests {
     use super::{
-        describe_tool_progress, filter_tool_specs, format_compact_report, format_cost_report,
-        format_internal_prompt_progress_line, format_model_report, format_model_switch_report,
-        format_permissions_report,
+        create_managed_session_handle, describe_tool_progress, filter_tool_specs,
+        format_compact_report, format_cost_report, format_internal_prompt_progress_line,
+        format_model_report, format_model_switch_report, format_permissions_report,
         format_permissions_switch_report, format_resume_report, format_status_report,
         format_tool_call_start, format_tool_result, normalize_permission_mode, parse_args,
-        parse_git_status_branch, parse_git_status_metadata_for, permission_policy,
-        print_help_to, push_output_block, render_config_report, render_diff_report,
-        render_memory_report, render_repl_help, resolve_model_alias, response_to_events,
+        parse_git_status_branch, parse_git_status_metadata_for, permission_policy, print_help_to,
+        push_output_block, render_config_report, render_diff_report, render_memory_report,
+        render_repl_help, resolve_model_alias, resolve_session_reference, response_to_events,
         resume_supported_slash_commands, run_resume_command, status_context, CliAction,
-        CliOutputFormat, InternalPromptProgressEvent,
-        InternalPromptProgressState, SlashCommand, StatusUsage, DEFAULT_MODEL,
-        create_managed_session_handle, resolve_session_reference,
+        CliOutputFormat, InternalPromptProgressEvent, InternalPromptProgressState, SlashCommand,
+        StatusUsage, DEFAULT_MODEL,
     };
     use api::{MessageResponse, OutputContentBlock, Usage};
     use plugins::{PluginTool, PluginToolDefinition, PluginToolPermission};
@@ -5051,8 +5051,13 @@ mod tests {
 
         let resolved = resolve_session_reference("legacy").expect("legacy session should resolve");
         assert_eq!(
-            resolved.path.canonicalize().expect("resolved path should exist"),
-            legacy_path.canonicalize().expect("legacy path should exist")
+            resolved
+                .path
+                .canonicalize()
+                .expect("resolved path should exist"),
+            legacy_path
+                .canonicalize()
+                .expect("legacy path should exist")
         );
 
         std::env::set_current_dir(previous).expect("restore cwd");

+ 16 - 5
rust/crates/tools/src/lib.rs

@@ -91,7 +91,10 @@ impl GlobalToolRegistry {
         Ok(Self { plugin_tools })
     }
 
-    pub fn normalize_allowed_tools(&self, values: &[String]) -> Result<Option<BTreeSet<String>>, String> {
+    pub fn normalize_allowed_tools(
+        &self,
+        values: &[String],
+    ) -> Result<Option<BTreeSet<String>>, String> {
         if values.is_empty() {
             return Ok(None);
         }
@@ -100,7 +103,11 @@ impl GlobalToolRegistry {
         let canonical_names = builtin_specs
             .iter()
             .map(|spec| spec.name.to_string())
-            .chain(self.plugin_tools.iter().map(|tool| tool.definition().name.clone()))
+            .chain(
+                self.plugin_tools
+                    .iter()
+                    .map(|tool| tool.definition().name.clone()),
+            )
             .collect::<Vec<_>>();
         let mut name_map = canonical_names
             .iter()
@@ -151,7 +158,8 @@ impl GlobalToolRegistry {
             .plugin_tools
             .iter()
             .filter(|tool| {
-                allowed_tools.is_none_or(|allowed| allowed.contains(tool.definition().name.as_str()))
+                allowed_tools
+                    .is_none_or(|allowed| allowed.contains(tool.definition().name.as_str()))
             })
             .map(|tool| ToolDefinition {
                 name: tool.definition().name.clone(),
@@ -174,7 +182,8 @@ impl GlobalToolRegistry {
             .plugin_tools
             .iter()
             .filter(|tool| {
-                allowed_tools.is_none_or(|allowed| allowed.contains(tool.definition().name.as_str()))
+                allowed_tools
+                    .is_none_or(|allowed| allowed.contains(tool.definition().name.as_str()))
             })
             .map(|tool| {
                 (
@@ -2057,7 +2066,9 @@ fn push_prompt_cache_record(client: &ProviderClient, events: &mut Vec<AssistantE
     }
 }
 
-fn prompt_cache_record_to_runtime_event(record: api::PromptCacheRecord) -> Option<PromptCacheEvent> {
+fn prompt_cache_record_to_runtime_event(
+    record: api::PromptCacheRecord,
+) -> Option<PromptCacheEvent> {
     let cache_break = record.cache_break?;
     Some(PromptCacheEvent {
         unexpected: cache_break.unexpected,