From 2dd05bfcef6f8cb8d28635c54aca04f29d4cd3b6 Mon Sep 17 00:00:00 2001 From: Yeachan-Heo Date: Sun, 5 Apr 2026 17:27:46 +0000 Subject: [PATCH] Make .claw the only user-facing config namespace Agents, skills, and init output were still surfacing .codex/.claude paths even though the runtime already treats .claw as the canonical config home. This updates help text, reports, skill install defaults, and repo bootstrap output to present a single .claw namespace while keeping legacy discovery fallbacks in place for existing setups. Constraint: Existing .codex/.claude agent and skill directories still need to load for compatibility Rejected: Remove legacy discovery entirely | would break existing user setups instead of just cleaning up surfaced output Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep future user-facing config, agent, and skill path copy aligned to .claw and even when legacy fallbacks remain supported internally Tested: cargo fmt --all --check; cargo test --workspace --exclude compat-harness Not-tested: cargo clippy --workspace --all-targets -- -D warnings | fails in pre-existing unrelated runtime files (for example mcp_lifecycle_hardened.rs, mcp_tool_bridge.rs, lsp_client.rs, permission_enforcer.rs, recovery_recipes.rs, stale_branch.rs, task_registry.rs, team_cron_registry.rs, worker_boot.rs) --- rust/crates/commands/src/lib.rs | 156 +++++++++++++++++------ rust/crates/rusty-claude-cli/src/init.rs | 45 +++---- rust/crates/tools/src/lib.rs | 6 + 3 files changed, 148 insertions(+), 59 deletions(-) diff --git a/rust/crates/commands/src/lib.rs b/rust/crates/commands/src/lib.rs index 7a588d4..96946f6 100644 --- a/rust/crates/commands/src/lib.rs +++ b/rust/crates/commands/src/lib.rs @@ -1954,25 +1954,49 @@ pub struct PluginsCommandResult { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] enum DefinitionSource { + ProjectClaw, ProjectCodex, ProjectClaude, + UserClawConfigHome, UserCodexHome, + UserClaw, UserCodex, UserClaude, } -impl DefinitionSource { +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +enum DefinitionScope { + Project, + UserConfigHome, + UserHome, +} + +impl DefinitionScope { fn label(self) -> &'static str { match self { - Self::ProjectCodex => "Project (.codex)", - Self::ProjectClaude => "Project (.claude)", - Self::UserCodexHome => "User ($CODEX_HOME)", - Self::UserCodex => "User (~/.codex)", - Self::UserClaude => "User (~/.claude)", + Self::Project => "Project (.claw)", + Self::UserConfigHome => "User ($CLAW_CONFIG_HOME)", + Self::UserHome => "User (~/.claw)", } } } +impl DefinitionSource { + fn report_scope(self) -> DefinitionScope { + match self { + Self::ProjectClaw | Self::ProjectCodex | Self::ProjectClaude => { + DefinitionScope::Project + } + Self::UserClawConfigHome | Self::UserCodexHome => DefinitionScope::UserConfigHome, + Self::UserClaw | Self::UserCodex | Self::UserClaude => DefinitionScope::UserHome, + } + } + + fn label(self) -> &'static str { + self.report_scope().label() + } +} + #[derive(Debug, Clone, PartialEq, Eq)] struct AgentSummary { name: String, @@ -2302,6 +2326,11 @@ fn discover_definition_roots(cwd: &Path, leaf: &str) -> Vec<(DefinitionSource, P let mut roots = Vec::new(); for ancestor in cwd.ancestors() { + push_unique_root( + &mut roots, + DefinitionSource::ProjectClaw, + ancestor.join(".claw").join(leaf), + ); push_unique_root( &mut roots, DefinitionSource::ProjectCodex, @@ -2314,6 +2343,14 @@ fn discover_definition_roots(cwd: &Path, leaf: &str) -> Vec<(DefinitionSource, P ); } + if let Ok(claw_config_home) = env::var("CLAW_CONFIG_HOME") { + push_unique_root( + &mut roots, + DefinitionSource::UserClawConfigHome, + PathBuf::from(claw_config_home).join(leaf), + ); + } + if let Ok(codex_home) = env::var("CODEX_HOME") { push_unique_root( &mut roots, @@ -2324,6 +2361,11 @@ fn discover_definition_roots(cwd: &Path, leaf: &str) -> Vec<(DefinitionSource, P if let Some(home) = env::var_os("HOME") { let home = PathBuf::from(home); + push_unique_root( + &mut roots, + DefinitionSource::UserClaw, + home.join(".claw").join(leaf), + ); push_unique_root( &mut roots, DefinitionSource::UserCodex, @@ -2343,6 +2385,12 @@ fn discover_skill_roots(cwd: &Path) -> Vec { let mut roots = Vec::new(); for ancestor in cwd.ancestors() { + push_unique_skill_root( + &mut roots, + DefinitionSource::ProjectClaw, + ancestor.join(".claw").join("skills"), + SkillOrigin::SkillsDir, + ); push_unique_skill_root( &mut roots, DefinitionSource::ProjectCodex, @@ -2355,6 +2403,12 @@ fn discover_skill_roots(cwd: &Path) -> Vec { ancestor.join(".claude").join("skills"), SkillOrigin::SkillsDir, ); + push_unique_skill_root( + &mut roots, + DefinitionSource::ProjectClaw, + ancestor.join(".claw").join("commands"), + SkillOrigin::LegacyCommandsDir, + ); push_unique_skill_root( &mut roots, DefinitionSource::ProjectCodex, @@ -2369,6 +2423,22 @@ fn discover_skill_roots(cwd: &Path) -> Vec { ); } + if let Ok(claw_config_home) = env::var("CLAW_CONFIG_HOME") { + let claw_config_home = PathBuf::from(claw_config_home); + push_unique_skill_root( + &mut roots, + DefinitionSource::UserClawConfigHome, + claw_config_home.join("skills"), + SkillOrigin::SkillsDir, + ); + push_unique_skill_root( + &mut roots, + DefinitionSource::UserClawConfigHome, + claw_config_home.join("commands"), + SkillOrigin::LegacyCommandsDir, + ); + } + if let Ok(codex_home) = env::var("CODEX_HOME") { let codex_home = PathBuf::from(codex_home); push_unique_skill_root( @@ -2387,6 +2457,18 @@ fn discover_skill_roots(cwd: &Path) -> Vec { if let Some(home) = env::var_os("HOME") { let home = PathBuf::from(home); + push_unique_skill_root( + &mut roots, + DefinitionSource::UserClaw, + home.join(".claw").join("skills"), + SkillOrigin::SkillsDir, + ); + push_unique_skill_root( + &mut roots, + DefinitionSource::UserClaw, + home.join(".claw").join("commands"), + SkillOrigin::LegacyCommandsDir, + ); push_unique_skill_root( &mut roots, DefinitionSource::UserCodex, @@ -2467,15 +2549,18 @@ fn install_skill_into( } fn default_skill_install_root() -> std::io::Result { + if let Ok(claw_config_home) = env::var("CLAW_CONFIG_HOME") { + return Ok(PathBuf::from(claw_config_home).join("skills")); + } if let Ok(codex_home) = env::var("CODEX_HOME") { return Ok(PathBuf::from(codex_home).join("skills")); } if let Some(home) = env::var_os("HOME") { - return Ok(PathBuf::from(home).join(".codex").join("skills")); + return Ok(PathBuf::from(home).join(".claw").join("skills")); } Err(std::io::Error::new( std::io::ErrorKind::NotFound, - "unable to resolve a skills install root; set CODEX_HOME or HOME", + "unable to resolve a skills install root; set CLAW_CONFIG_HOME or HOME", )) } @@ -2841,22 +2926,20 @@ fn render_agents_report(agents: &[AgentSummary]) -> String { String::new(), ]; - for source in [ - DefinitionSource::ProjectCodex, - DefinitionSource::ProjectClaude, - DefinitionSource::UserCodexHome, - DefinitionSource::UserCodex, - DefinitionSource::UserClaude, + for scope in [ + DefinitionScope::Project, + DefinitionScope::UserConfigHome, + DefinitionScope::UserHome, ] { let group = agents .iter() - .filter(|agent| agent.source == source) + .filter(|agent| agent.source.report_scope() == scope) .collect::>(); if group.is_empty() { continue; } - lines.push(format!("{}:", source.label())); + lines.push(format!("{}:", scope.label())); for agent in group { let detail = agent_detail(agent); match agent.shadowed_by { @@ -2899,22 +2982,20 @@ fn render_skills_report(skills: &[SkillSummary]) -> String { String::new(), ]; - for source in [ - DefinitionSource::ProjectCodex, - DefinitionSource::ProjectClaude, - DefinitionSource::UserCodexHome, - DefinitionSource::UserCodex, - DefinitionSource::UserClaude, + for scope in [ + DefinitionScope::Project, + DefinitionScope::UserConfigHome, + DefinitionScope::UserHome, ] { let group = skills .iter() - .filter(|skill| skill.source == source) + .filter(|skill| skill.source.report_scope() == scope) .collect::>(); if group.is_empty() { continue; } - lines.push(format!("{}:", source.label())); + lines.push(format!("{}:", scope.label())); for skill in group { let mut parts = vec![skill.name.clone()]; if let Some(description) = &skill.description { @@ -3080,7 +3161,7 @@ fn render_agents_usage(unexpected: Option<&str>) -> String { "Agents".to_string(), " Usage /agents [list|help]".to_string(), " Direct CLI claw agents".to_string(), - " Sources .codex/agents, .claude/agents, $CODEX_HOME/agents".to_string(), + " Sources .claw/agents, ~/.claw/agents, $CLAW_CONFIG_HOME/agents".to_string(), ]; if let Some(args) = unexpected { lines.push(format!(" Unexpected {args}")); @@ -3093,8 +3174,8 @@ fn render_skills_usage(unexpected: Option<&str>) -> String { "Skills".to_string(), " Usage /skills [list|install |help]".to_string(), " Direct CLI claw skills [list|install |help]".to_string(), - " Install root $CODEX_HOME/skills or ~/.codex/skills".to_string(), - " Sources .codex/skills, .claude/skills, legacy /commands".to_string(), + " Install root $CLAW_CONFIG_HOME/skills or ~/.claw/skills".to_string(), + " Sources .claw/skills, ~/.claw/skills, legacy /commands".to_string(), ]; if let Some(args) = unexpected { lines.push(format!(" Unexpected {args}")); @@ -3933,7 +4014,7 @@ mod tests { let workspace = temp_dir("agents-workspace"); let project_agents = workspace.join(".codex").join("agents"); let user_home = temp_dir("agents-home"); - let user_agents = user_home.join(".codex").join("agents"); + let user_agents = user_home.join(".claude").join("agents"); write_agent( &project_agents, @@ -3966,10 +4047,10 @@ mod tests { assert!(report.contains("Agents")); assert!(report.contains("2 active agents")); - assert!(report.contains("Project (.codex):")); + assert!(report.contains("Project (.claw):")); assert!(report.contains("planner · Project planner · gpt-5.4 · medium")); - assert!(report.contains("User (~/.codex):")); - assert!(report.contains("(shadowed by Project (.codex)) planner · User planner")); + assert!(report.contains("User (~/.claw):")); + assert!(report.contains("(shadowed by Project (.claw)) planner · User planner")); assert!(report.contains("verifier · Verification agent · gpt-5.4-mini · high")); let _ = fs::remove_dir_all(workspace); @@ -4011,12 +4092,11 @@ mod tests { assert!(report.contains("Skills")); assert!(report.contains("3 available skills")); - assert!(report.contains("Project (.codex):")); + assert!(report.contains("Project (.claw):")); assert!(report.contains("plan · Project planning guidance")); - assert!(report.contains("Project (.claude):")); assert!(report.contains("deploy · Legacy deployment guidance · legacy /commands")); - assert!(report.contains("User (~/.codex):")); - assert!(report.contains("(shadowed by Project (.codex)) plan · User planning guidance")); + assert!(report.contains("User (~/.claw):")); + assert!(report.contains("(shadowed by Project (.claw)) plan · User planning guidance")); assert!(report.contains("help · Help guidance")); let _ = fs::remove_dir_all(workspace); @@ -4031,6 +4111,8 @@ mod tests { super::handle_agents_slash_command(Some("help"), &cwd).expect("agents help"); assert!(agents_help.contains("Usage /agents [list|help]")); assert!(agents_help.contains("Direct CLI claw agents")); + assert!(agents_help + .contains("Sources .claw/agents, ~/.claw/agents, $CLAW_CONFIG_HOME/agents")); let agents_unexpected = super::handle_agents_slash_command(Some("show planner"), &cwd).expect("agents usage"); @@ -4039,7 +4121,7 @@ mod tests { let skills_help = super::handle_skills_slash_command(Some("--help"), &cwd).expect("skills help"); assert!(skills_help.contains("Usage /skills [list|install |help]")); - assert!(skills_help.contains("Install root $CODEX_HOME/skills or ~/.codex/skills")); + assert!(skills_help.contains("Install root $CLAW_CONFIG_HOME/skills or ~/.claw/skills")); assert!(skills_help.contains("legacy /commands")); let skills_unexpected = @@ -4213,7 +4295,7 @@ mod tests { let listed = render_skills_report( &load_skills_from_roots(&roots).expect("installed skills should load"), ); - assert!(listed.contains("User ($CODEX_HOME):")); + assert!(listed.contains("User ($CLAW_CONFIG_HOME):")); assert!(listed.contains("help · Helpful skill")); let _ = fs::remove_dir_all(workspace); diff --git a/rust/crates/rusty-claude-cli/src/init.rs b/rust/crates/rusty-claude-cli/src/init.rs index 67d1187..a5f0ff7 100644 --- a/rust/crates/rusty-claude-cli/src/init.rs +++ b/rust/crates/rusty-claude-cli/src/init.rs @@ -1,7 +1,7 @@ use std::fs; use std::path::{Path, PathBuf}; -const STARTER_CLAUDE_JSON: &str = concat!( +const STARTER_CLAW_JSON: &str = concat!( "{\n", " \"permissions\": {\n", " \"defaultMode\": \"dontAsk\"\n", @@ -9,7 +9,7 @@ const STARTER_CLAUDE_JSON: &str = concat!( "}\n", ); const GITIGNORE_COMMENT: &str = "# Claw Code local artifacts"; -const GITIGNORE_ENTRIES: [&str; 2] = [".claude/settings.local.json", ".claude/sessions/"]; +const GITIGNORE_ENTRIES: [&str; 2] = [".claw/settings.local.json", ".claw/sessions/"]; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum InitStatus { @@ -80,16 +80,16 @@ struct RepoDetection { pub(crate) fn initialize_repo(cwd: &Path) -> Result> { let mut artifacts = Vec::new(); - let claude_dir = cwd.join(".claude"); + let claw_dir = cwd.join(".claw"); artifacts.push(InitArtifact { - name: ".claude/", - status: ensure_dir(&claude_dir)?, + name: ".claw/", + status: ensure_dir(&claw_dir)?, }); - let claude_json = cwd.join(".claude.json"); + let claw_json = cwd.join(".claw.json"); artifacts.push(InitArtifact { - name: ".claude.json", - status: write_file_if_missing(&claude_json, STARTER_CLAUDE_JSON)?, + name: ".claw.json", + status: write_file_if_missing(&claw_json, STARTER_CLAW_JSON)?, }); let gitignore = cwd.join(".gitignore"); @@ -209,7 +209,7 @@ pub(crate) fn render_init_claude_md(cwd: &Path) -> String { lines.push("## Working agreement".to_string()); lines.push("- Prefer small, reviewable changes and keep generated bootstrap files aligned with actual repo workflows.".to_string()); - lines.push("- Keep shared defaults in `.claude.json`; reserve `.claude/settings.local.json` for machine-local overrides.".to_string()); + lines.push("- Keep shared defaults in `.claw.json`; reserve `.claw/settings.local.json` for machine-local overrides.".to_string()); lines.push("- Do not overwrite existing `CLAUDE.md` content automatically; update it intentionally when repo workflows change.".to_string()); lines.push(String::new()); @@ -354,15 +354,16 @@ mod tests { let report = initialize_repo(&root).expect("init should succeed"); let rendered = report.render(); - assert!(rendered.contains(".claude/ created")); - assert!(rendered.contains(".claude.json created")); + assert!(rendered.contains(".claw/")); + assert!(rendered.contains(".claw.json")); + assert!(rendered.contains("created")); assert!(rendered.contains(".gitignore created")); assert!(rendered.contains("CLAUDE.md created")); - assert!(root.join(".claude").is_dir()); - assert!(root.join(".claude.json").is_file()); + assert!(root.join(".claw").is_dir()); + assert!(root.join(".claw.json").is_file()); assert!(root.join("CLAUDE.md").is_file()); assert_eq!( - fs::read_to_string(root.join(".claude.json")).expect("read claude json"), + fs::read_to_string(root.join(".claw.json")).expect("read claw json"), concat!( "{\n", " \"permissions\": {\n", @@ -372,8 +373,8 @@ mod tests { ) ); let gitignore = fs::read_to_string(root.join(".gitignore")).expect("read gitignore"); - assert!(gitignore.contains(".claude/settings.local.json")); - assert!(gitignore.contains(".claude/sessions/")); + assert!(gitignore.contains(".claw/settings.local.json")); + assert!(gitignore.contains(".claw/sessions/")); let claude_md = fs::read_to_string(root.join("CLAUDE.md")).expect("read claude md"); assert!(claude_md.contains("Languages: Rust.")); assert!(claude_md.contains("cargo clippy --workspace --all-targets -- -D warnings")); @@ -386,8 +387,7 @@ mod tests { let root = temp_dir(); fs::create_dir_all(&root).expect("create root"); fs::write(root.join("CLAUDE.md"), "custom guidance\n").expect("write existing claude md"); - fs::write(root.join(".gitignore"), ".claude/settings.local.json\n") - .expect("write gitignore"); + fs::write(root.join(".gitignore"), ".claw/settings.local.json\n").expect("write gitignore"); let first = initialize_repo(&root).expect("first init should succeed"); assert!(first @@ -395,8 +395,9 @@ mod tests { .contains("CLAUDE.md skipped (already exists)")); let second = initialize_repo(&root).expect("second init should succeed"); let second_rendered = second.render(); - assert!(second_rendered.contains(".claude/ skipped (already exists)")); - assert!(second_rendered.contains(".claude.json skipped (already exists)")); + assert!(second_rendered.contains(".claw/")); + assert!(second_rendered.contains(".claw.json")); + assert!(second_rendered.contains("skipped (already exists)")); assert!(second_rendered.contains(".gitignore skipped (already exists)")); assert!(second_rendered.contains("CLAUDE.md skipped (already exists)")); assert_eq!( @@ -404,8 +405,8 @@ mod tests { "custom guidance\n" ); let gitignore = fs::read_to_string(root.join(".gitignore")).expect("read gitignore"); - assert_eq!(gitignore.matches(".claude/settings.local.json").count(), 1); - assert_eq!(gitignore.matches(".claude/sessions/").count(), 1); + assert_eq!(gitignore.matches(".claw/settings.local.json").count(), 1); + assert_eq!(gitignore.matches(".claw/sessions/").count(), 1); fs::remove_dir_all(root).expect("cleanup temp dir"); } diff --git a/rust/crates/tools/src/lib.rs b/rust/crates/tools/src/lib.rs index b19938a..d1dcf75 100644 --- a/rust/crates/tools/src/lib.rs +++ b/rust/crates/tools/src/lib.rs @@ -2976,15 +2976,21 @@ fn resolve_skill_path(skill: &str) -> Result { } let mut candidates = Vec::new(); + if let Ok(claw_config_home) = std::env::var("CLAW_CONFIG_HOME") { + candidates.push(std::path::PathBuf::from(claw_config_home).join("skills")); + } if let Ok(codex_home) = std::env::var("CODEX_HOME") { candidates.push(std::path::PathBuf::from(codex_home).join("skills")); } if let Ok(home) = std::env::var("HOME") { let home = std::path::PathBuf::from(home); + candidates.push(home.join(".claw").join("skills")); candidates.push(home.join(".agents").join("skills")); candidates.push(home.join(".config").join("opencode").join("skills")); candidates.push(home.join(".codex").join("skills")); + candidates.push(home.join(".claude").join("skills")); } + candidates.push(std::path::PathBuf::from("/home/bellman/.claw/skills")); candidates.push(std::path::PathBuf::from("/home/bellman/.codex/skills")); for root in candidates {