diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 17cd6796..4c1d847a 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -1941,9 +1941,7 @@ fn parse_args(args: &[String]) -> Result { // Only reject for known top-level subcommands that don't use compact. let first = rest[0].as_str(); if is_known_top_level_subcommand(first) && first != "prompt" { - return Err(format!( - "invalid_flag_value: --compact is only supported with prompt mode.\nUsage: claw --compact \"\" or echo \"\" | claw --compact" - )); + return Err("invalid_flag_value: --compact is only supported with prompt mode.\nUsage: claw --compact \"\" or echo \"\" | claw --compact".to_string()); } } @@ -3147,10 +3145,11 @@ fn print_model_validation_warning_status( usage, permission_mode, context, - None, - None, - allowed_tools, - Some(&format_selection), + &StatusJsonExtras { + allowed_tools, + format_selection: Some(&format_selection), + ..StatusJsonExtras::default() + }, ); let object = value .as_object_mut() @@ -3226,9 +3225,7 @@ fn parse_system_prompt_args( })?; // #99: validate --date is a plausible date string (no newlines, reasonable length) if value.contains('\n') || value.contains('\r') { - return Err(format!( - "invalid_flag_value: --date value contains invalid characters.\nUsage: --date " - )); + return Err("invalid_flag_value: --date value contains invalid characters.\nUsage: --date ".to_string()); } if value.len() > 20 { return Err(format!( @@ -3465,11 +3462,7 @@ impl DiagnosticCheck { fn json_value(&self) -> Value { // Derive a stable snake_case id from the check name for machine-readable keying (#704). - let id = self - .name - .to_ascii_lowercase() - .replace(' ', "_") - .replace('-', "_"); + let id = self.name.to_ascii_lowercase().replace([' ', '-'], "_"); let mut value = Map::from_iter([ ("id".to_string(), Value::String(id.clone())), ( @@ -6650,10 +6643,8 @@ fn run_resume_command( }, default_permission_mode().as_str(), &context, - None, // #148: resumed sessions don't have flag provenance - None, - None, - None, + // #148: resumed sessions don't have flag provenance + &StatusJsonExtras::default(), )), }) } @@ -6791,16 +6782,15 @@ fn run_resume_command( } SlashCommand::Plugins { action, target } => { // Only list is supported in resume mode (no runtime to reload) - match action.as_deref() { - Some(action @ ("install" | "uninstall" | "enable" | "disable" | "update")) => { - // #777: use interactive_only: prefix + \n hint so #776's classify/split - // emits error_kind:interactive_only + non-null hint instead of unknown+null. - // Orchestrators can now detect this and switch to a live REPL instead of retrying. - return Err(format!( - "interactive_only: /plugins {action} requires a live session to reload the plugin runtime.\nStart `claw` and run `/plugins {action}` inside the REPL, or use `claw plugins {action}` as a direct CLI command." - ).into()); - } - _ => {} + if let Some(action @ ("install" | "uninstall" | "enable" | "disable" | "update")) = + action.as_deref() + { + // #777: use interactive_only: prefix + \n hint so #776's classify/split + // emits error_kind:interactive_only + non-null hint instead of unknown+null. + // Orchestrators can now detect this and switch to a live REPL instead of retrying. + return Err(format!( + "interactive_only: /plugins {action} requires a live session to reload the plugin runtime.\nStart `claw` and run `/plugins {action}` inside the REPL, or use `claw plugins {action}` as a direct CLI command." + ).into()); } let cwd = env::current_dir()?; let payload = plugins_command_payload_for( @@ -7940,8 +7930,7 @@ impl LiveCli { let max_compact_rounds = 4; let preserve_schedule = [4, 2, 1, 0]; - for round in 0..max_compact_rounds { - let preserve = preserve_schedule[round]; + for (round, &preserve) in preserve_schedule.iter().enumerate() { println!( " Auto-compacting session (round {}/{}, preserving {} recent messages)...", round + 1, @@ -8709,8 +8698,8 @@ impl LiveCli { let cwd = env::current_dir()?; // #803: reject flag-shaped tokens in list filter for BOTH text and JSON modes. // Previously the guard was JSON-only (#793); text mode silently returned empty success. - if action.as_deref() == Some("list") { - if let Some(filter) = target.as_deref() { + if action == Some("list") { + if let Some(filter) = target { if filter.starts_with('-') { if matches!(output_format, CliOutputFormat::Json) { // ROADMAP #817: this is a handled local inventory parse error. @@ -9670,31 +9659,49 @@ fn print_status_snapshot( usage, permission_mode.mode.as_str(), &context, - Some(&provenance), - Some(&permission_mode), - allowed_tools, - Some(&format_selection), + &StatusJsonExtras { + provenance: Some(&provenance), + permission_provenance: Some(&permission_mode), + allowed_tools, + format_selection: Some(&format_selection), + }, ))? ), } Ok(()) } +/// Optional provenance and selection inputs for [`status_json_value`]. +/// +/// Grouped into a struct rather than trailing positional parameters: they are all +/// `Option<&_>` and most callers pass `None` for most of them, which makes a +/// positional tail easy to transpose silently. +/// +/// `provenance` (#148) drives the `model_source` field ("flag" | "env" | "config" | +/// "default") and `model_raw` (user input before alias resolution, or null when the +/// source is "default"). Callers without provenance (legacy resume paths) leave it +/// `None`, in which case both fields are omitted. +#[derive(Default)] +struct StatusJsonExtras<'a> { + provenance: Option<&'a ModelProvenance>, + permission_provenance: Option<&'a PermissionModeProvenance>, + allowed_tools: Option<&'a AllowedToolSet>, + format_selection: Option<&'a OutputFormatSelection>, +} + fn status_json_value( model: Option<&str>, usage: StatusUsage, permission_mode: &str, context: &StatusContext, - // #148: optional provenance for `model` field. Surfaces `model_source` - // ("flag" | "env" | "config" | "default") and `model_raw` (user input - // before alias resolution, or null when source is "default"). Callers - // that don't have provenance (legacy resume paths) pass None, in which - // case both new fields are omitted. - provenance: Option<&ModelProvenance>, - permission_provenance: Option<&PermissionModeProvenance>, - allowed_tools: Option<&AllowedToolSet>, - format_selection: Option<&OutputFormatSelection>, + extras: &StatusJsonExtras<'_>, ) -> serde_json::Value { + let StatusJsonExtras { + provenance, + permission_provenance, + allowed_tools, + format_selection, + } = *extras; // #143: top-level `status` marker so claws can distinguish // a clean run from a degraded run (config parse failed but other fields // are still populated). `config_load_error` carries the parse-error string @@ -10176,9 +10183,7 @@ fn sandbox_json_value(status: &runtime::SandboxStatus) -> serde_json::Value { // (#731: "not supported on macOS" is a degraded state, not a hard error; // filesystem_active:true means partial containment is working) // error = enabled but unsupported AND no filesystem sandbox either (nothing active) - let top_status = if !status.enabled { - "ok" - } else if status.active { + let top_status = if !status.enabled || status.active { "ok" } else if status.supported { "warn" @@ -10553,18 +10558,20 @@ fn render_doctor_help_json() -> serde_json::Value { } /// #683-#692: extract structured metadata from help prose -fn extract_help_metadata( - topic: LocalHelpTopic, -) -> ( - Option, // usage - Option, // purpose - Option, // output description - Option>, // formats - Option>, // related - Option>, // aliases - bool, // local_only - bool, // requires_credentials -) { +/// Parsed fields of a help topic: usage, purpose, output description, formats, +/// related topics, aliases, local-only, requires-credentials. +type HelpMetadata = ( + Option, + Option, + Option, + Option>, + Option>, + Option>, + bool, + bool, +); + +fn extract_help_metadata(topic: LocalHelpTopic) -> HelpMetadata { let text = render_help_topic(topic); let mut usage = None; let mut purpose = None; @@ -13253,9 +13260,7 @@ fn format_context_window_blocked_error(session_id: &str, error: &api::ApiError) } api::ApiError::RetriesExhausted { last_error, .. } => { let detail = match last_error.as_ref() { - api::ApiError::Api(details) => { - details.message.as_deref().unwrap_or(&details.body) - } + api::ApiError::Api(details) => details.message.as_deref().unwrap_or(&details.body), other => return format_context_window_blocked_error(session_id, other), } .trim(); @@ -14273,39 +14278,37 @@ fn convert_messages(messages: &[ConversationMessage]) -> Vec { let content = message .blocks .iter() - .filter_map(|block| match block { - ContentBlock::Text { text } => { - Some(InputContentBlock::Text { text: text.clone() }) - } + .map(|block| match block { + ContentBlock::Text { text } => InputContentBlock::Text { text: text.clone() }, ContentBlock::Thinking { thinking, signature, } => { // 保留 Thinking 块:OpenAI 兼容协议会把它转成 reasoning_content 字段 // 回传给 DeepSeek V4(避免 400 "reasoning_content must be passed back" 错误) - Some(InputContentBlock::Thinking { + InputContentBlock::Thinking { thinking: thinking.clone(), signature: signature.clone(), - }) + } } - ContentBlock::ToolUse { id, name, input } => Some(InputContentBlock::ToolUse { + ContentBlock::ToolUse { id, name, input } => InputContentBlock::ToolUse { id: id.clone(), name: name.clone(), input: serde_json::from_str(input) .unwrap_or_else(|_| serde_json::json!({ "raw": input })), - }), + }, ContentBlock::ToolResult { tool_use_id, output, is_error, .. - } => Some(InputContentBlock::ToolResult { + } => InputContentBlock::ToolResult { tool_use_id: tool_use_id.clone(), content: vec![ToolResultContentBlock::Text { text: output.clone(), }], is_error: *is_error, - }), + }, }) .collect::>(); (!content.is_empty()).then(|| InputMessage { @@ -16178,10 +16181,7 @@ mod tests { usage, "workspace-write", &context, - None, - None, - None, - None, + &super::StatusJsonExtras::default(), ); assert_eq!( json.get("status").and_then(|v| v.as_str()), @@ -16253,10 +16253,10 @@ mod tests { usage, "workspace-write", &context, - None, - None, - Some(&allowed), - None, + &super::StatusJsonExtras { + allowed_tools: Some(&allowed), + ..super::StatusJsonExtras::default() + }, ); assert_eq!( restricted_json @@ -16286,10 +16286,7 @@ mod tests { usage, "workspace-write", &clean_context, - None, - None, - None, - None, + &super::StatusJsonExtras::default(), ); assert_eq!( clean_json.get("status").and_then(|v| v.as_str()), @@ -17237,7 +17234,7 @@ mod tests { for action in ["remove", "uninstall", "delete"] { assert_eq!( parse_args(&["skills".to_string(), action.to_string()]) - .expect(&format!("skills {action} should parse")), + .unwrap_or_else(|_| panic!("skills {action} should parse")), CliAction::Skills { args: Some(action.to_string()), output_format: CliOutputFormat::Text, @@ -18191,10 +18188,7 @@ mod tests { }, "workspace-write", &context, - None, - None, - None, - None, + &super::StatusJsonExtras::default(), ); assert_eq!( diff --git a/rust/crates/rusty-claude-cli/src/setup_wizard.rs b/rust/crates/rusty-claude-cli/src/setup_wizard.rs index 4cffbda3..aa315dd6 100644 --- a/rust/crates/rusty-claude-cli/src/setup_wizard.rs +++ b/rust/crates/rusty-claude-cli/src/setup_wizard.rs @@ -2,7 +2,6 @@ use std::io::{self, IsTerminal, Write}; use runtime::{save_user_provider_settings, ConfigLoader, RuntimeProviderConfig}; - const PROVIDERS: &[(&str, &str, &str)] = &[ ("1", "Anthropic", "anthropic"), ("2", "xAI / Grok", "xai"), diff --git a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs index 42ad37ec..02a8eccb 100644 --- a/rust/crates/rusty-claude-cli/tests/output_format_contract.rs +++ b/rust/crates/rusty-claude-cli/tests/output_format_contract.rs @@ -1007,13 +1007,13 @@ fn inventory_commands_emit_structured_json_when_requested() { assert!( !plugins .as_object() - .map_or(false, |o| o.contains_key("reload_runtime")), + .is_some_and(|o| o.contains_key("reload_runtime")), "plugins list should not include reload_runtime" ); assert!( !plugins .as_object() - .map_or(false, |o| o.contains_key("target")), + .is_some_and(|o| o.contains_key("target")), "plugins list should not include target" ); // #703: structured summary replaces prose message @@ -1707,13 +1707,13 @@ fn resumed_inventory_commands_emit_structured_json_when_requested() { assert!( !plugins .as_object() - .map_or(false, |o| o.contains_key("reload_runtime")), + .is_some_and(|o| o.contains_key("reload_runtime")), "plugins list should not include reload_runtime" ); assert!( !plugins .as_object() - .map_or(false, |o| o.contains_key("target")), + .is_some_and(|o| o.contains_key("target")), "plugins list should not include target" ); assert!( @@ -2946,7 +2946,7 @@ fn prompt_empty_arg_json_stdout_missing_prompt_823() { "claw prompt empty arg must retain abort action (#823); got: {parsed}" ); assert!( - parsed["hint"].as_str().map_or(false, |h| !h.is_empty()), + parsed["hint"].as_str().is_some_and(|h| !h.is_empty()), "claw prompt empty arg missing_prompt hint must be non-empty (#823)" ); } @@ -2984,9 +2984,9 @@ fn flag_value_errors_have_error_kind_and_hint_756() { "invalid --reasoning-effort must be invalid_flag_value (#756): {parsed}" ); assert!( - parsed["hint"].as_str().map_or(false, |h| h.contains("low") - || h.contains("medium") - || h.contains("high")), + parsed["hint"] + .as_str() + .is_some_and(|h| h.contains("low") || h.contains("medium") || h.contains("high")), "hint must mention valid values (#756): {parsed}" ); @@ -3012,7 +3012,7 @@ fn flag_value_errors_have_error_kind_and_hint_756() { "missing --model value must be missing_flag_value (#756): {parsed2}" ); assert!( - parsed2["hint"].as_str().map_or(false, |h| !h.is_empty()), + parsed2["hint"].as_str().is_some_and(|h| !h.is_empty()), "missing --model hint must be non-empty (#756): {parsed2}" ); } @@ -3256,7 +3256,7 @@ fn short_p_flag_swallows_no_flags_755() { "flag-like token after -p must be rejected as missing_prompt (#755): {parsed2}" ); assert!( - parsed2["hint"].as_str().map_or(false, |h| !h.is_empty()), + parsed2["hint"].as_str().is_some_and(|h| !h.is_empty()), "missing_prompt hint must be non-empty (#755)" ); } @@ -3398,7 +3398,7 @@ fn config_unsupported_section_json_hint_741() { assert!( parsed["supported_sections"] .as_array() - .map_or(false, |a| !a.is_empty()), + .is_some_and(|a| !a.is_empty()), "config {section} JSON must include supported_sections (#741)" ); } @@ -3722,8 +3722,8 @@ fn resume_non_slash_trailing_arg_has_typed_error_kind_and_hint_768() { !output.status.success(), "claw --resume latest compact should exit non-zero" ); - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); + let _stdout = String::from_utf8_lossy(&output.stdout); + let _stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let json_line = stdout .lines() @@ -3762,8 +3762,8 @@ fn session_with_unknown_subcommand_returns_interactive_only_not_credentials_767( !output.status.success(), "claw session {sub} should exit non-zero" ); - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); + let _stdout = String::from_utf8_lossy(&output.stdout); + let _stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let json_line = stdout .lines() @@ -3816,7 +3816,7 @@ fn slash_only_verbs_with_args_return_interactive_only_not_credentials_770() { "claw {} should exit non-zero", args.join(" ") ); - let stdout = String::from_utf8_lossy(&output.stdout); + let _stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let json_line = stdout @@ -3858,8 +3858,8 @@ fn agents_plugins_mcp_unknown_subcommand_have_hint_774() { { let output = run_claw(&root, &["--output-format", "json", "agents", "bogus"], &[]); assert!(!output.status.success(), "agents bogus should fail"); - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); + let _stdout = String::from_utf8_lossy(&output.stdout); + let _stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let json_line = stdout .lines() @@ -3882,8 +3882,8 @@ fn agents_plugins_mcp_unknown_subcommand_have_hint_774() { { let output = run_claw(&root, &["--output-format", "json", "plugins", "bogus"], &[]); assert!(!output.status.success(), "plugins bogus should fail"); - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); + let _stdout = String::from_utf8_lossy(&output.stdout); + let _stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let json_line = stdout .lines() @@ -3902,7 +3902,7 @@ fn agents_plugins_mcp_unknown_subcommand_have_hint_774() { { let output = run_claw(&root, &["--output-format", "json", "mcp", "bogus"], &[]); assert!(!output.status.success(), "mcp bogus should fail"); - let stdout = String::from_utf8_lossy(&output.stdout); + let _stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let json_str = if stdout.trim().starts_with('{') { @@ -4017,7 +4017,7 @@ fn interactive_only_guard_batch_769_to_771() { "claw {} should exit non-zero", args.join(" ") ); - let stdout = String::from_utf8_lossy(&output.stdout); + let _stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let json_line = stdout @@ -4081,7 +4081,7 @@ fn resume_plugin_mutations_are_typed_interactive_only_777() { !output.status.success(), "/plugins {mutation} in resume mode should exit non-zero" ); - let stdout = String::from_utf8_lossy(&output.stdout); + let _stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let json_line = stdout @@ -4138,7 +4138,7 @@ fn resume_skills_invocation_is_typed_interactive_only_779() { !output.status.success(), "/skills in resume mode should exit non-zero" ); - let stdout = String::from_utf8_lossy(&output.stdout); + let _stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let json_line = stdout @@ -4179,8 +4179,8 @@ fn acp_unsupported_invocation_has_hint_782() { let output = run_claw(&root, &["--output-format", "json", "acp", "start"], &[]); assert!(!output.status.success(), "acp start should fail"); - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); + let _stdout = String::from_utf8_lossy(&output.stdout); + let _stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let json_line = stdout .lines() @@ -4217,7 +4217,7 @@ fn init_json_envelope_has_hint_and_already_initialized_783() { // Fresh init — already_initialized should be false, hint should mention CLAUDE.md let output = run_claw(&root, &["--output-format", "json", "init"], &[]); assert!(output.status.success(), "init should succeed"); - let stdout = String::from_utf8_lossy(&output.stdout); + let _stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let raw = if stdout.trim_start().starts_with('{') { @@ -4286,7 +4286,7 @@ fn init_json_envelope_has_hint_and_already_initialized_783() { // Idempotent re-init — already_initialized should be true let output2 = run_claw(&root, &["--output-format", "json", "init"], &[]); assert!(output2.status.success(), "re-init should succeed"); - let stdout2 = String::from_utf8_lossy(&output2.stdout); + let _stdout2 = String::from_utf8_lossy(&output2.stdout); let stderr2 = String::from_utf8_lossy(&output2.stderr); let stdout2 = String::from_utf8_lossy(&output2.stdout); let raw2 = if stdout2.trim_start().starts_with('{') { @@ -4367,7 +4367,7 @@ fn export_arg_errors_have_typed_kind_and_hint_784() { &[], ); assert!(!out1.status.success(), "--output with no value should fail"); - let stderr1 = String::from_utf8_lossy(&out1.stderr); + let _stderr1 = String::from_utf8_lossy(&out1.stderr); let stdout1 = String::from_utf8_lossy(&out1.stdout); let j1: serde_json::Value = stdout1 .lines() @@ -4394,7 +4394,7 @@ fn export_arg_errors_have_typed_kind_and_hint_784() { &[], ); assert!(!out2.status.success(), "extra positional should fail"); - let stderr2 = String::from_utf8_lossy(&out2.stderr); + let _stderr2 = String::from_utf8_lossy(&out2.stderr); let stdout2 = String::from_utf8_lossy(&out2.stdout); let j2: serde_json::Value = stdout2 .lines() @@ -4431,7 +4431,7 @@ fn unknown_subcommand_returns_typed_kind_785() { // "dump" is close enough to "dump-manifests" to trigger the typo suggestion path let output = run_claw(&root, &["--output-format", "json", "dump"], &[]); assert!(!output.status.success(), "unknown subcommand should fail"); - let stderr = String::from_utf8_lossy(&output.stderr); + let _stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let j: serde_json::Value = stdout .lines() @@ -4479,7 +4479,7 @@ fn dump_manifests_missing_dir_has_typed_kind_and_hint_786() { &[], ); assert!(!out1.status.success()); - let stderr1 = String::from_utf8_lossy(&out1.stderr); + let _stderr1 = String::from_utf8_lossy(&out1.stderr); let stdout1 = String::from_utf8_lossy(&out1.stdout); let j1: serde_json::Value = stdout1 .lines() @@ -4511,7 +4511,7 @@ fn dump_manifests_missing_dir_has_typed_kind_and_hint_786() { &[], ); assert!(!out2.status.success()); - let stderr2 = String::from_utf8_lossy(&out2.stderr); + let _stderr2 = String::from_utf8_lossy(&out2.stderr); let stdout2 = String::from_utf8_lossy(&out2.stdout); let j2: serde_json::Value = stdout2 .lines() @@ -4561,7 +4561,7 @@ fn resume_directory_path_returns_typed_kind_and_hint_787() { !output.status.success(), "resume with directory should fail" ); - let stderr = String::from_utf8_lossy(&output.stderr); + let _stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let j: serde_json::Value = stdout .lines() @@ -4611,7 +4611,7 @@ fn skills_show_not_found_emits_single_json_object_788() { assert!(!output.status.success(), "skills show unknown should fail"); // Skills handler emits JSON to stdout; the duplicate was on stderr from the main error path. // After fix: stdout has 1 JSON object, stderr has none (no duplicate). - let stdout = String::from_utf8_lossy(&output.stdout); + let _stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); @@ -4773,7 +4773,7 @@ fn system_prompt_unknown_option_returns_typed_kind_790() { &[], ); assert!(!out1.status.success()); - let stderr1 = String::from_utf8_lossy(&out1.stderr); + let _stderr1 = String::from_utf8_lossy(&out1.stderr); let stdout1 = String::from_utf8_lossy(&out1.stdout); let j1: serde_json::Value = stdout1 .lines() @@ -4800,7 +4800,7 @@ fn system_prompt_unknown_option_returns_typed_kind_790() { &[], ); assert!(!out2.status.success()); - let stderr2 = String::from_utf8_lossy(&out2.stderr); + let _stderr2 = String::from_utf8_lossy(&out2.stderr); let stdout2 = String::from_utf8_lossy(&out2.stdout); let j2: serde_json::Value = stdout2 .lines() @@ -4838,7 +4838,7 @@ fn config_extra_args_have_non_null_hint_791() { &[], ); assert!(!out1.status.success()); - let stderr1 = String::from_utf8_lossy(&out1.stderr); + let _stderr1 = String::from_utf8_lossy(&out1.stderr); let stdout1 = String::from_utf8_lossy(&out1.stdout); let j1: serde_json::Value = stdout1 .lines() @@ -4872,7 +4872,7 @@ fn config_extra_args_have_non_null_hint_791() { &[], ); assert!(!out2.status.success()); - let stderr2 = String::from_utf8_lossy(&out2.stderr); + let _stderr2 = String::from_utf8_lossy(&out2.stderr); let stdout2 = String::from_utf8_lossy(&out2.stdout); let j2: serde_json::Value = stdout2 .lines() @@ -5058,7 +5058,7 @@ fn plugins_uninstall_not_found_has_hint_793() { "plugins uninstall not-found must exit non-zero (#793)" ); // Error envelope goes to stderr (propagated via ? to main error handler) - let stderr = String::from_utf8_lossy(&output.stderr); + let _stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let j: serde_json::Value = stdout .lines() @@ -5103,7 +5103,7 @@ fn plugins_install_not_found_path_returns_typed_kind_794() { !output.status.success(), "plugins install not-found-path must exit non-zero (#794)" ); - let stderr = String::from_utf8_lossy(&output.stderr); + let _stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let j: serde_json::Value = stdout .lines() @@ -5367,7 +5367,7 @@ fn agents_create_scaffolds_toml_and_lists_locally_431() { .iter() .any(|agent| { agent["name"] == "my-agent" - && PathBuf::from(agent["path"].as_str().expect("listed agent path")) + && Path::new(agent["path"].as_str().expect("listed agent path")) == fs::canonicalize(&agent_path).expect("canonical listed agent path") })); } @@ -5492,7 +5492,7 @@ fn plugins_extra_args_have_non_null_hint_797() { !output.status.success(), "plugins show with extra arg must exit non-zero (#797)" ); - let stderr = String::from_utf8_lossy(&output.stderr); + let _stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let j: serde_json::Value = stdout .lines() @@ -5558,7 +5558,7 @@ fn plugins_list_trailing_dash_text_error_stays_on_stderr_817() { String::from_utf8_lossy(&output.stdout) ); let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); + let _stdout = String::from_utf8_lossy(&output.stdout); assert!(stderr.contains("[error-kind: cli_parse]"), "{stderr}"); assert!( stderr.contains("unknown option for `claw plugins list`: --"), @@ -5583,7 +5583,7 @@ fn empty_prompt_has_non_null_hint_798() { !output.status.success(), "empty prompt must exit non-zero (#798)" ); - let stderr = String::from_utf8_lossy(&output.stderr); + let _stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); let j: serde_json::Value = stdout .lines()