Make the CLI point users at the real install source

The next repo-local backlog item was ROADMAP #70: users could
mistake third-party pages or the deprecated `cargo install
claw-code` path for the official install route. The CLI now
surfaces the source of truth directly in `claw doctor` and
`claw --help`, and the roadmap closeout records the change.

Constraint: Keep the fix inside repo-local Rust CLI surfaces instead of relying on docs alone
Rejected: Close #70 with README-only wording | the bug was user-facing CLI ambiguity, so the warning needed to appear in runtime help/doctor output
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If install guidance changes later, update both the doctor check payload and the help-text warning together
Tested: cargo fmt --all --check; cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace; architect review APPROVE
Not-tested: Third-party websites outside this repo that may still present stale install instructions
This commit is contained in:
Yeachan-Heo
2026-04-12 04:50:03 +00:00
parent 26b89e583f
commit 3b806702e7
3 changed files with 69 additions and 3 deletions

View File

@@ -512,4 +512,4 @@ Model name prefix now wins unconditionally over env-var presence. Regression tes
69. **Lane stop summaries have no minimum quality floor****done (verified 2026-04-12):** completed lane persistence in `rust/crates/tools/src/lib.rs` now normalizes vague/control-only stop summaries into a contextual fallback that includes the lane target and status, while preserving structured metadata about whether the quality floor fired (`qualityFloorApplied`, `rawSummary`, `reasons`, `wordCount`). Regression coverage locks both the pass-through path for good summaries and the fallback path for mushy summaries like `commit push everyting, keep sweeping $ralph`. **Original filing below.** 69. **Lane stop summaries have no minimum quality floor****done (verified 2026-04-12):** completed lane persistence in `rust/crates/tools/src/lib.rs` now normalizes vague/control-only stop summaries into a contextual fallback that includes the lane target and status, while preserving structured metadata about whether the quality floor fired (`qualityFloorApplied`, `rawSummary`, `reasons`, `wordCount`). Regression coverage locks both the pass-through path for good summaries and the fallback path for mushy summaries like `commit push everyting, keep sweeping $ralph`. **Original filing below.**
70. **Install-source ambiguity misleads real users**community observation 2026-04-12. User treated `claw-code.io` as official, then hit `clawcode` vs deprecated `claw-code` naming collision and concluded install story was inconsistent. Source-of-truth is not obvious when website/repo/crates naming diverges. **Fix shape:** canonical repo docs should explicitly state which site is official; installation guidance should visibly warn against deprecated `claw-code` crate and ambiguous third-party pages. Blocker: none. Source: gaebal-gajae community watch 2026-04-12. 70. **Install-source ambiguity misleads real users****done (verified 2026-04-12):** repo-local Rust guidance now makes the source of truth explicit in `claw doctor` and `claw --help`, naming `ultraworkers/claw-code` as the canonical repo and warning that `cargo install claw-code` installs a deprecated stub rather than the `claw` binary. Regression coverage locks both the new doctor JSON check and the help-text warning. **Original filing below.**

View File

@@ -78,6 +78,9 @@ const INTERNAL_PROGRESS_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(3);
const POST_TOOL_STALL_TIMEOUT: Duration = Duration::from_secs(10); const POST_TOOL_STALL_TIMEOUT: Duration = Duration::from_secs(10);
const PRIMARY_SESSION_EXTENSION: &str = "jsonl"; const PRIMARY_SESSION_EXTENSION: &str = "jsonl";
const LEGACY_SESSION_EXTENSION: &str = "json"; const LEGACY_SESSION_EXTENSION: &str = "json";
const OFFICIAL_REPO_URL: &str = "https://github.com/ultraworkers/claw-code";
const OFFICIAL_REPO_SLUG: &str = "ultraworkers/claw-code";
const DEPRECATED_INSTALL_COMMAND: &str = "cargo install claw-code";
const LATEST_SESSION_REFERENCE: &str = "latest"; const LATEST_SESSION_REFERENCE: &str = "latest";
const SESSION_REFERENCE_ALIASES: &[&str] = &[LATEST_SESSION_REFERENCE, "last", "recent"]; const SESSION_REFERENCE_ALIASES: &[&str] = &[LATEST_SESSION_REFERENCE, "last", "recent"];
const CLI_OPTION_SUGGESTIONS: &[&str] = &[ const CLI_OPTION_SUGGESTIONS: &[&str] = &[
@@ -1477,6 +1480,7 @@ fn render_doctor_report() -> Result<DoctorReport, Box<dyn std::error::Error>> {
checks: vec![ checks: vec![
check_auth_health(), check_auth_health(),
check_config_health(&config_loader, config.as_ref()), check_config_health(&config_loader, config.as_ref()),
check_install_source_health(),
check_workspace_health(&context), check_workspace_health(&context),
check_sandbox_health(&context.sandbox_status), check_sandbox_health(&context.sandbox_status),
check_system_health(&cwd, config.as_ref().ok()), check_system_health(&cwd, config.as_ref().ok()),
@@ -1764,6 +1768,36 @@ fn check_config_health(
} }
} }
fn check_install_source_health() -> DiagnosticCheck {
DiagnosticCheck::new(
"Install source",
DiagnosticLevel::Ok,
format!(
"official source of truth is {OFFICIAL_REPO_SLUG}; avoid `{DEPRECATED_INSTALL_COMMAND}`"
),
)
.with_details(vec![
format!("Official repo {OFFICIAL_REPO_URL}"),
"Recommended path build from this repo or use the upstream binary documented in README.md"
.to_string(),
format!(
"Deprecated crate `{DEPRECATED_INSTALL_COMMAND}` installs a deprecated stub and does not provide the `claw` binary"
)
.to_string(),
])
.with_data(Map::from_iter([
("official_repo".to_string(), json!(OFFICIAL_REPO_URL)),
(
"deprecated_install".to_string(),
json!(DEPRECATED_INSTALL_COMMAND),
),
(
"recommended_install".to_string(),
json!("build from source or follow the upstream binary instructions in README.md"),
),
]))
}
fn check_workspace_health(context: &StatusContext) -> DiagnosticCheck { fn check_workspace_health(context: &StatusContext) -> DiagnosticCheck {
let in_repo = context.project_root.is_some(); let in_repo = context.project_root.is_some();
DiagnosticCheck::new( DiagnosticCheck::new(
@@ -8111,6 +8145,11 @@ fn print_help_to(out: &mut impl Write) -> io::Result<()> {
out, out,
" Diagnose local auth, config, workspace, and sandbox health" " Diagnose local auth, config, workspace, and sandbox health"
)?; )?;
writeln!(out, " Source of truth: {OFFICIAL_REPO_SLUG}")?;
writeln!(
out,
" Warning: do not `{DEPRECATED_INSTALL_COMMAND}` (deprecated stub)"
)?;
writeln!(out, " claw dump-manifests [--manifests-dir PATH]")?; writeln!(out, " claw dump-manifests [--manifests-dir PATH]")?;
writeln!(out, " claw bootstrap-plan")?; writeln!(out, " claw bootstrap-plan")?;
writeln!(out, " claw agents")?; writeln!(out, " claw agents")?;
@@ -8200,6 +8239,11 @@ fn print_help_to(out: &mut impl Write) -> io::Result<()> {
writeln!(out, " claw mcp show my-server")?; writeln!(out, " claw mcp show my-server")?;
writeln!(out, " claw /skills")?; writeln!(out, " claw /skills")?;
writeln!(out, " claw doctor")?; writeln!(out, " claw doctor")?;
writeln!(out, " source of truth: {OFFICIAL_REPO_URL}")?;
writeln!(
out,
" do not run `{DEPRECATED_INSTALL_COMMAND}` — it installs a deprecated stub"
)?;
writeln!(out, " claw init")?; writeln!(out, " claw init")?;
writeln!(out, " claw export")?; writeln!(out, " claw export")?;
writeln!(out, " claw export conversation.md")?; writeln!(out, " claw export conversation.md")?;
@@ -10082,6 +10126,8 @@ mod tests {
assert!(help.contains("claw mcp")); assert!(help.contains("claw mcp"));
assert!(help.contains("claw skills")); assert!(help.contains("claw skills"));
assert!(help.contains("claw /skills")); assert!(help.contains("claw /skills"));
assert!(help.contains("ultraworkers/claw-code"));
assert!(help.contains("cargo install claw-code"));
assert!(!help.contains("claw login")); assert!(!help.contains("claw login"));
assert!(!help.contains("claw logout")); assert!(!help.contains("claw logout"));
} }

View File

@@ -209,7 +209,7 @@ fn doctor_and_resume_status_emit_json_when_requested() {
assert!(summary["failures"].as_u64().is_some()); assert!(summary["failures"].as_u64().is_some());
let checks = doctor["checks"].as_array().expect("doctor checks"); let checks = doctor["checks"].as_array().expect("doctor checks");
assert_eq!(checks.len(), 5); assert_eq!(checks.len(), 6);
let check_names = checks let check_names = checks
.iter() .iter()
.map(|check| { .map(|check| {
@@ -221,7 +221,27 @@ fn doctor_and_resume_status_emit_json_when_requested() {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!( assert_eq!(
check_names, check_names,
vec!["auth", "config", "workspace", "sandbox", "system"] vec![
"auth",
"config",
"install source",
"workspace",
"sandbox",
"system"
]
);
let install_source = checks
.iter()
.find(|check| check["name"] == "install source")
.expect("install source check");
assert_eq!(
install_source["official_repo"],
"https://github.com/ultraworkers/claw-code"
);
assert_eq!(
install_source["deprecated_install"],
"cargo install claw-code"
); );
let workspace = checks let workspace = checks