diff --git a/PARITY.md b/PARITY.md index d67389f..5d81d2b 100644 --- a/PARITY.md +++ b/PARITY.md @@ -1,13 +1,14 @@ # Parity Status — claw-code Rust Port -Last updated: 2026-04-03 +Last updated: 2026-04-23 ## Summary - Canonical document: this top-level `PARITY.md` is the file consumed by `rust/scripts/run_mock_parity_diff.py`. - Requested 9-lane checkpoint: **All 9 lanes merged on `main`.** -- Current `main` HEAD: `ee31e00` (stub implementations replaced with real AskUserQuestion + RemoteTrigger). -- Repository stats at this checkpoint: **292 commits on `main` / 293 across all branches**, **9 crates**, **48,599 tracked Rust LOC**, **2,568 test LOC**, **3 authors**, date range **2026-03-31 → 2026-04-03**. +- Current `main` HEAD: `ad1cf92` (doctrine loop canonical example). +- Repository stats at this checkpoint: **979 commits on `main`**, **9 crates**, **80,789 tracked Rust LOC**, **4,533 test LOC**, **3 authors**, date **2026-04-23**. +- **Growth since last PARITY update (2026-04-03):** Rust LOC +66% (48,599 → 80,789), Test LOC +76% (2,568 → 4,533), Commits +235% (292 → 979). Current phase: 13 branches awaiting review/integration. - Mock parity harness stats: **10 scripted scenarios**, **19 captured `/v1/messages` requests** in `rust/crates/rusty-claude-cli/tests/mock_parity_harness.rs`. ## Mock parity harness — milestone 1 diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 52915f0..8f89769 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -1098,6 +1098,19 @@ fn parse_single_word_command_alias( return Some(Err(msg)); } + // #160: reserved-semantic verbs (resume, compact, memory, commit, pr, issue) + // that have positional args should NOT fall through to Prompt dispatch. + // These verbs have CLI-reserved meanings and cannot reasonably be prompt text. + // Emit slash-command guidance instead. + if rest.len() > 1 { + if is_reserved_semantic_verb(&rest[0]) { + // Treat as slash-command verb; emit guidance instead of falling through to Prompt + if let Some(guidance) = bare_slash_command_guidance(&rest[0]) { + return Some(Err(guidance)); + } + } + } + if rest.len() != 1 { return None; } @@ -1123,6 +1136,16 @@ fn parse_single_word_command_alias( } } +fn is_reserved_semantic_verb(verb: &str) -> bool { + // #160: Verbs with CLI-reserved positional-arg semantics that should NOT + // fall through to Prompt dispatch when given args. These verbs have specific + // meaning (session ID, code target, etc.) and cannot be prompt text. + matches!( + verb, + "resume" | "compact" | "memory" | "commit" | "pr" | "issue" | "bughunter" + ) +} + fn bare_slash_command_guidance(command_name: &str) -> Option { if matches!( command_name, @@ -2308,21 +2331,35 @@ fn check_install_source_health() -> DiagnosticCheck { fn check_workspace_health(context: &StatusContext) -> DiagnosticCheck { let in_repo = context.project_root.is_some(); - DiagnosticCheck::new( - "Workspace", - if in_repo { - DiagnosticLevel::Ok - } else { - DiagnosticLevel::Warn - }, - if in_repo { + // #122b: detect broad cwd (home dir, filesystem root) — runtime commands + // (Prompt/REPL) refuse to run here without --allow-broad-cwd, but doctor + // previously reported "ok" regardless. Diagnostic must be at least as + // strict as runtime: downgrade to Warn and surface the condition. + let broad_cwd = detect_broad_cwd(); + let (level, summary) = match (in_repo, &broad_cwd) { + (_, Some(path)) => ( + DiagnosticLevel::Warn, + format!( + "current directory is a broad path ({}); Prompt/REPL will refuse to run here without --allow-broad-cwd", + path.display() + ), + ), + (true, None) => ( + DiagnosticLevel::Ok, format!( "project root detected on branch {}", context.git_branch.as_deref().unwrap_or("unknown") - ) - } else { - "current directory is not inside a git project".to_string() - }, + ), + ), + (false, None) => ( + DiagnosticLevel::Warn, + "current directory is not inside a git project".to_string(), + ), + }; + DiagnosticCheck::new( + "Workspace", + level, + summary, ) .with_details(vec![ format!("Cwd {}", context.cwd.display()), @@ -13144,3 +13181,64 @@ mod dump_manifests_tests { let _ = fs::remove_dir_all(&root); } } + +#[cfg(test)] +mod doctor_broad_cwd_tests { + //! #122b regression tests: doctor's workspace check must surface broad-cwd + //! as a warning, matching runtime (Prompt/REPL) refuse-to-run behavior. + //! Without these, `claw doctor` in ~/ or / reports "ok" while `claw prompt` + //! in the same dir errors out — diagnostic deception. + + use super::{check_workspace_health, render_diagnostic_check, StatusContext}; + use std::path::PathBuf; + + fn make_ctx(cwd: PathBuf, project_root: Option) -> StatusContext { + use runtime::SandboxStatus; + StatusContext { + cwd, + session_path: None, + loaded_config_files: 0, + discovered_config_files: 0, + memory_file_count: 0, + project_root, + git_branch: None, + git_summary: super::parse_git_workspace_summary(None), + sandbox_status: SandboxStatus::default(), + config_load_error: None, + } + } + + #[test] + fn workspace_check_in_project_dir_reports_ok() { + // #122b non-regression: non-broad project dir should stay OK. + let ctx = make_ctx( + PathBuf::from("/tmp/my-project"), + Some(PathBuf::from("/tmp/my-project")), + ); + let check = check_workspace_health(&ctx); + // Use rendered output as the contract surface. + let rendered = render_diagnostic_check(&check); + assert!(rendered.contains("Status ok"), + "project dir should be OK; got:\n{rendered}"); + } + + #[test] + fn workspace_check_outside_project_reports_warn() { + // #122b non-regression: non-broad, non-git dir stays as Warn with the + // "not inside a git project" summary. + let ctx = make_ctx( + PathBuf::from("/tmp/random-dir-not-project"), + None, + ); + let check = check_workspace_health(&ctx); + let rendered = render_diagnostic_check(&check); + assert!( + rendered.contains("Status warn"), + "non-git dir should warn; got:\n{rendered}" + ); + assert!( + rendered.contains("not inside a git project"), + "should report not-in-project; got:\n{rendered}" + ); + } +}