mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-08 09:04:49 +08:00
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
This commit is contained in:
48
.github/workflows/rust-ci.yml
vendored
Normal file
48
.github/workflows/rust-ci.yml
vendored
Normal file
@@ -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
|
||||||
@@ -322,7 +322,10 @@ impl AnthropicClient {
|
|||||||
.with_property(
|
.with_property(
|
||||||
"estimated_cost_usd",
|
"estimated_cost_usd",
|
||||||
Value::String(format_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(),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,17 +24,17 @@ pub use compact::{
|
|||||||
get_compact_continuation_message, should_compact, CompactionConfig, CompactionResult,
|
get_compact_continuation_message, should_compact, CompactionConfig, CompactionResult,
|
||||||
};
|
};
|
||||||
pub use config::{
|
pub use config::{
|
||||||
ConfigEntry, ConfigError, ConfigLoader, ConfigSource, McpManagedProxyServerConfig,
|
ConfigEntry, ConfigError, ConfigLoader, ConfigSource, McpConfigCollection,
|
||||||
McpConfigCollection, McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig,
|
McpManagedProxyServerConfig, McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig,
|
||||||
McpServerConfig, McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig,
|
McpServerConfig, McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig,
|
||||||
ResolvedPermissionMode, RuntimeConfig, RuntimeFeatureConfig, RuntimeHookConfig,
|
ResolvedPermissionMode, RuntimeConfig, RuntimeFeatureConfig, RuntimeHookConfig,
|
||||||
RuntimePermissionRuleConfig, RuntimePluginConfig, ScopedMcpServerConfig,
|
RuntimePermissionRuleConfig, RuntimePluginConfig, ScopedMcpServerConfig,
|
||||||
CLAW_SETTINGS_SCHEMA_NAME,
|
CLAW_SETTINGS_SCHEMA_NAME,
|
||||||
};
|
};
|
||||||
pub use conversation::{
|
pub use conversation::{
|
||||||
auto_compaction_threshold_from_env, ApiClient, ApiRequest, AssistantEvent,
|
auto_compaction_threshold_from_env, ApiClient, ApiRequest, AssistantEvent, AutoCompactionEvent,
|
||||||
AutoCompactionEvent, ConversationRuntime, PromptCacheEvent, RuntimeError,
|
ConversationRuntime, PromptCacheEvent, RuntimeError, StaticToolExecutor, ToolError,
|
||||||
StaticToolExecutor, ToolError, ToolExecutor, TurnSummary,
|
ToolExecutor, TurnSummary,
|
||||||
};
|
};
|
||||||
pub use file_ops::{
|
pub use file_ops::{
|
||||||
edit_file, glob_search, grep_search, read_file, write_file, EditFileOutput, GlobSearchOutput,
|
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,
|
scoped_mcp_config_hash, unwrap_ccr_proxy_url,
|
||||||
};
|
};
|
||||||
pub use mcp_client::{
|
pub use mcp_client::{
|
||||||
McpManagedProxyTransport, McpClientAuth, McpClientBootstrap, McpClientTransport,
|
McpClientAuth, McpClientBootstrap, McpClientTransport, McpManagedProxyTransport,
|
||||||
McpRemoteTransport, McpSdkTransport, McpStdioTransport,
|
McpRemoteTransport, McpSdkTransport, McpStdioTransport,
|
||||||
};
|
};
|
||||||
pub use mcp_stdio::{
|
pub use mcp_stdio::{
|
||||||
|
|||||||
@@ -97,12 +97,10 @@ impl McpClientTransport {
|
|||||||
McpServerConfig::Sdk(config) => Self::Sdk(McpSdkTransport {
|
McpServerConfig::Sdk(config) => Self::Sdk(McpSdkTransport {
|
||||||
name: config.name.clone(),
|
name: config.name.clone(),
|
||||||
}),
|
}),
|
||||||
McpServerConfig::ManagedProxy(config) => {
|
McpServerConfig::ManagedProxy(config) => Self::ManagedProxy(McpManagedProxyTransport {
|
||||||
Self::ManagedProxy(McpManagedProxyTransport {
|
url: config.url.clone(),
|
||||||
url: config.url.clone(),
|
id: config.id.clone(),
|
||||||
id: config.id.clone(),
|
}),
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,12 +30,11 @@ use plugins::{PluginManager, PluginManagerConfig};
|
|||||||
use render::{MarkdownStreamState, Spinner, TerminalRenderer};
|
use render::{MarkdownStreamState, Spinner, TerminalRenderer};
|
||||||
use runtime::{
|
use runtime::{
|
||||||
clear_oauth_credentials, generate_pkce_pair, generate_state, load_system_prompt,
|
clear_oauth_credentials, generate_pkce_pair, generate_state, load_system_prompt,
|
||||||
parse_oauth_callback_request_target, resolve_sandbox_status, save_oauth_credentials,
|
parse_oauth_callback_request_target, resolve_sandbox_status, save_oauth_credentials, ApiClient,
|
||||||
ApiClient, ApiRequest, AssistantEvent, CompactionConfig, ConfigLoader, ConfigSource,
|
ApiRequest, AssistantEvent, CompactionConfig, ConfigLoader, ConfigSource, ContentBlock,
|
||||||
ContentBlock, ConversationMessage, ConversationRuntime, MessageRole, PromptCacheEvent,
|
ConversationMessage, ConversationRuntime, MessageRole, OAuthAuthorizationRequest, OAuthConfig,
|
||||||
OAuthAuthorizationRequest, OAuthConfig,
|
OAuthTokenExchangeRequest, PermissionMode, PermissionPolicy, ProjectContext, PromptCacheEvent,
|
||||||
OAuthTokenExchangeRequest, PermissionMode, PermissionPolicy, ProjectContext, RuntimeError,
|
RuntimeError, Session, TokenUsage, ToolError, ToolExecutor, UsageTracker,
|
||||||
Session, TokenUsage, ToolError, ToolExecutor, UsageTracker,
|
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tools::GlobalToolRegistry;
|
use tools::GlobalToolRegistry;
|
||||||
@@ -3146,7 +3145,8 @@ fn build_runtime(
|
|||||||
allowed_tools: Option<AllowedToolSet>,
|
allowed_tools: Option<AllowedToolSet>,
|
||||||
permission_mode: PermissionMode,
|
permission_mode: PermissionMode,
|
||||||
progress_reporter: Option<InternalPromptProgressReporter>,
|
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 (feature_config, tool_registry) = build_runtime_plugin_state()?;
|
||||||
let mut runtime = ConversationRuntime::new_with_features(
|
let mut runtime = ConversationRuntime::new_with_features(
|
||||||
session,
|
session,
|
||||||
@@ -3286,7 +3286,6 @@ impl AnthropicRuntimeClient {
|
|||||||
progress_reporter,
|
progress_reporter,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_cli_auth_source() -> Result<AuthSource, Box<dyn std::error::Error>> {
|
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?;
|
let cache_break = record.cache_break?;
|
||||||
Some(PromptCacheEvent {
|
Some(PromptCacheEvent {
|
||||||
unexpected: cache_break.unexpected,
|
unexpected: cache_break.unexpected,
|
||||||
@@ -4245,18 +4246,17 @@ fn print_help() {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
describe_tool_progress, filter_tool_specs, format_compact_report, format_cost_report,
|
create_managed_session_handle, describe_tool_progress, filter_tool_specs,
|
||||||
format_internal_prompt_progress_line, format_model_report, format_model_switch_report,
|
format_compact_report, format_cost_report, format_internal_prompt_progress_line,
|
||||||
format_permissions_report,
|
format_model_report, format_model_switch_report, format_permissions_report,
|
||||||
format_permissions_switch_report, format_resume_report, format_status_report,
|
format_permissions_switch_report, format_resume_report, format_status_report,
|
||||||
format_tool_call_start, format_tool_result, normalize_permission_mode, parse_args,
|
format_tool_call_start, format_tool_result, normalize_permission_mode, parse_args,
|
||||||
parse_git_status_branch, parse_git_status_metadata_for, permission_policy,
|
parse_git_status_branch, parse_git_status_metadata_for, permission_policy, print_help_to,
|
||||||
print_help_to, push_output_block, render_config_report, render_diff_report,
|
push_output_block, render_config_report, render_diff_report, render_memory_report,
|
||||||
render_memory_report, render_repl_help, resolve_model_alias, response_to_events,
|
render_repl_help, resolve_model_alias, resolve_session_reference, response_to_events,
|
||||||
resume_supported_slash_commands, run_resume_command, status_context, CliAction,
|
resume_supported_slash_commands, run_resume_command, status_context, CliAction,
|
||||||
CliOutputFormat, InternalPromptProgressEvent,
|
CliOutputFormat, InternalPromptProgressEvent, InternalPromptProgressState, SlashCommand,
|
||||||
InternalPromptProgressState, SlashCommand, StatusUsage, DEFAULT_MODEL,
|
StatusUsage, DEFAULT_MODEL,
|
||||||
create_managed_session_handle, resolve_session_reference,
|
|
||||||
};
|
};
|
||||||
use api::{MessageResponse, OutputContentBlock, Usage};
|
use api::{MessageResponse, OutputContentBlock, Usage};
|
||||||
use plugins::{PluginTool, PluginToolDefinition, PluginToolPermission};
|
use plugins::{PluginTool, PluginToolDefinition, PluginToolPermission};
|
||||||
@@ -5051,8 +5051,13 @@ mod tests {
|
|||||||
|
|
||||||
let resolved = resolve_session_reference("legacy").expect("legacy session should resolve");
|
let resolved = resolve_session_reference("legacy").expect("legacy session should resolve");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolved.path.canonicalize().expect("resolved path should exist"),
|
resolved
|
||||||
legacy_path.canonicalize().expect("legacy path should exist")
|
.path
|
||||||
|
.canonicalize()
|
||||||
|
.expect("resolved path should exist"),
|
||||||
|
legacy_path
|
||||||
|
.canonicalize()
|
||||||
|
.expect("legacy path should exist")
|
||||||
);
|
);
|
||||||
|
|
||||||
std::env::set_current_dir(previous).expect("restore cwd");
|
std::env::set_current_dir(previous).expect("restore cwd");
|
||||||
|
|||||||
@@ -91,7 +91,10 @@ impl GlobalToolRegistry {
|
|||||||
Ok(Self { plugin_tools })
|
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() {
|
if values.is_empty() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
@@ -100,7 +103,11 @@ impl GlobalToolRegistry {
|
|||||||
let canonical_names = builtin_specs
|
let canonical_names = builtin_specs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|spec| spec.name.to_string())
|
.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<_>>();
|
.collect::<Vec<_>>();
|
||||||
let mut name_map = canonical_names
|
let mut name_map = canonical_names
|
||||||
.iter()
|
.iter()
|
||||||
@@ -151,7 +158,8 @@ impl GlobalToolRegistry {
|
|||||||
.plugin_tools
|
.plugin_tools
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|tool| {
|
.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 {
|
.map(|tool| ToolDefinition {
|
||||||
name: tool.definition().name.clone(),
|
name: tool.definition().name.clone(),
|
||||||
@@ -174,7 +182,8 @@ impl GlobalToolRegistry {
|
|||||||
.plugin_tools
|
.plugin_tools
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|tool| {
|
.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| {
|
.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?;
|
let cache_break = record.cache_break?;
|
||||||
Some(PromptCacheEvent {
|
Some(PromptCacheEvent {
|
||||||
unexpected: cache_break.unexpected,
|
unexpected: cache_break.unexpected,
|
||||||
|
|||||||
Reference in New Issue
Block a user