Compare commits

..

1 Commits

Author SHA1 Message Date
Yeachan-Heo
c65126a339 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)
2026-04-05 17:27:46 +00:00
4 changed files with 148 additions and 127 deletions

View File

@@ -1,68 +0,0 @@
name: Release binaries
on:
push:
tags:
- 'v*'
workflow_dispatch:
permissions:
contents: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
env:
CARGO_TERM_COLOR: always
jobs:
build:
name: build-${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: linux-x64
os: ubuntu-latest
bin: claw
artifact_name: claw-linux-x64
- name: macos-arm64
os: macos-14
bin: claw
artifact_name: claw-macos-arm64
defaults:
run:
working-directory: rust
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: rust -> target
- name: Build release binary
run: cargo build --release -p rusty-claude-cli
- name: Package artifact
shell: bash
run: |
mkdir -p dist
cp "target/release/${{ matrix.bin }}" "dist/${{ matrix.artifact_name }}"
chmod +x "dist/${{ matrix.artifact_name }}"
- name: Upload workflow artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: rust/dist/${{ matrix.artifact_name }}
- name: Upload release asset
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: rust/dist/${{ matrix.artifact_name }}
fail_on_unmatched_files: true

View File

@@ -1954,25 +1954,49 @@ pub struct PluginsCommandResult {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum DefinitionSource { enum DefinitionSource {
ProjectClaw,
ProjectCodex, ProjectCodex,
ProjectClaude, ProjectClaude,
UserClawConfigHome,
UserCodexHome, UserCodexHome,
UserClaw,
UserCodex, UserCodex,
UserClaude, UserClaude,
} }
impl DefinitionSource { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum DefinitionScope {
Project,
UserConfigHome,
UserHome,
}
impl DefinitionScope {
fn label(self) -> &'static str { fn label(self) -> &'static str {
match self { match self {
Self::ProjectCodex => "Project (.codex)", Self::Project => "Project (.claw)",
Self::ProjectClaude => "Project (.claude)", Self::UserConfigHome => "User ($CLAW_CONFIG_HOME)",
Self::UserCodexHome => "User ($CODEX_HOME)", Self::UserHome => "User (~/.claw)",
Self::UserCodex => "User (~/.codex)",
Self::UserClaude => "User (~/.claude)",
} }
} }
} }
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)] #[derive(Debug, Clone, PartialEq, Eq)]
struct AgentSummary { struct AgentSummary {
name: String, name: String,
@@ -2302,6 +2326,11 @@ fn discover_definition_roots(cwd: &Path, leaf: &str) -> Vec<(DefinitionSource, P
let mut roots = Vec::new(); let mut roots = Vec::new();
for ancestor in cwd.ancestors() { for ancestor in cwd.ancestors() {
push_unique_root(
&mut roots,
DefinitionSource::ProjectClaw,
ancestor.join(".claw").join(leaf),
);
push_unique_root( push_unique_root(
&mut roots, &mut roots,
DefinitionSource::ProjectCodex, 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") { if let Ok(codex_home) = env::var("CODEX_HOME") {
push_unique_root( push_unique_root(
&mut roots, &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") { if let Some(home) = env::var_os("HOME") {
let home = PathBuf::from(home); let home = PathBuf::from(home);
push_unique_root(
&mut roots,
DefinitionSource::UserClaw,
home.join(".claw").join(leaf),
);
push_unique_root( push_unique_root(
&mut roots, &mut roots,
DefinitionSource::UserCodex, DefinitionSource::UserCodex,
@@ -2343,6 +2385,12 @@ fn discover_skill_roots(cwd: &Path) -> Vec<SkillRoot> {
let mut roots = Vec::new(); let mut roots = Vec::new();
for ancestor in cwd.ancestors() { for ancestor in cwd.ancestors() {
push_unique_skill_root(
&mut roots,
DefinitionSource::ProjectClaw,
ancestor.join(".claw").join("skills"),
SkillOrigin::SkillsDir,
);
push_unique_skill_root( push_unique_skill_root(
&mut roots, &mut roots,
DefinitionSource::ProjectCodex, DefinitionSource::ProjectCodex,
@@ -2355,6 +2403,12 @@ fn discover_skill_roots(cwd: &Path) -> Vec<SkillRoot> {
ancestor.join(".claude").join("skills"), ancestor.join(".claude").join("skills"),
SkillOrigin::SkillsDir, SkillOrigin::SkillsDir,
); );
push_unique_skill_root(
&mut roots,
DefinitionSource::ProjectClaw,
ancestor.join(".claw").join("commands"),
SkillOrigin::LegacyCommandsDir,
);
push_unique_skill_root( push_unique_skill_root(
&mut roots, &mut roots,
DefinitionSource::ProjectCodex, DefinitionSource::ProjectCodex,
@@ -2369,6 +2423,22 @@ fn discover_skill_roots(cwd: &Path) -> Vec<SkillRoot> {
); );
} }
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") { if let Ok(codex_home) = env::var("CODEX_HOME") {
let codex_home = PathBuf::from(codex_home); let codex_home = PathBuf::from(codex_home);
push_unique_skill_root( push_unique_skill_root(
@@ -2387,6 +2457,18 @@ fn discover_skill_roots(cwd: &Path) -> Vec<SkillRoot> {
if let Some(home) = env::var_os("HOME") { if let Some(home) = env::var_os("HOME") {
let home = PathBuf::from(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( push_unique_skill_root(
&mut roots, &mut roots,
DefinitionSource::UserCodex, DefinitionSource::UserCodex,
@@ -2467,15 +2549,18 @@ fn install_skill_into(
} }
fn default_skill_install_root() -> std::io::Result<PathBuf> { fn default_skill_install_root() -> std::io::Result<PathBuf> {
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") { if let Ok(codex_home) = env::var("CODEX_HOME") {
return Ok(PathBuf::from(codex_home).join("skills")); return Ok(PathBuf::from(codex_home).join("skills"));
} }
if let Some(home) = env::var_os("HOME") { 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( Err(std::io::Error::new(
std::io::ErrorKind::NotFound, 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(), String::new(),
]; ];
for source in [ for scope in [
DefinitionSource::ProjectCodex, DefinitionScope::Project,
DefinitionSource::ProjectClaude, DefinitionScope::UserConfigHome,
DefinitionSource::UserCodexHome, DefinitionScope::UserHome,
DefinitionSource::UserCodex,
DefinitionSource::UserClaude,
] { ] {
let group = agents let group = agents
.iter() .iter()
.filter(|agent| agent.source == source) .filter(|agent| agent.source.report_scope() == scope)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if group.is_empty() { if group.is_empty() {
continue; continue;
} }
lines.push(format!("{}:", source.label())); lines.push(format!("{}:", scope.label()));
for agent in group { for agent in group {
let detail = agent_detail(agent); let detail = agent_detail(agent);
match agent.shadowed_by { match agent.shadowed_by {
@@ -2899,22 +2982,20 @@ fn render_skills_report(skills: &[SkillSummary]) -> String {
String::new(), String::new(),
]; ];
for source in [ for scope in [
DefinitionSource::ProjectCodex, DefinitionScope::Project,
DefinitionSource::ProjectClaude, DefinitionScope::UserConfigHome,
DefinitionSource::UserCodexHome, DefinitionScope::UserHome,
DefinitionSource::UserCodex,
DefinitionSource::UserClaude,
] { ] {
let group = skills let group = skills
.iter() .iter()
.filter(|skill| skill.source == source) .filter(|skill| skill.source.report_scope() == scope)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if group.is_empty() { if group.is_empty() {
continue; continue;
} }
lines.push(format!("{}:", source.label())); lines.push(format!("{}:", scope.label()));
for skill in group { for skill in group {
let mut parts = vec![skill.name.clone()]; let mut parts = vec![skill.name.clone()];
if let Some(description) = &skill.description { if let Some(description) = &skill.description {
@@ -3080,7 +3161,7 @@ fn render_agents_usage(unexpected: Option<&str>) -> String {
"Agents".to_string(), "Agents".to_string(),
" Usage /agents [list|help]".to_string(), " Usage /agents [list|help]".to_string(),
" Direct CLI claw agents".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 { if let Some(args) = unexpected {
lines.push(format!(" Unexpected {args}")); lines.push(format!(" Unexpected {args}"));
@@ -3093,8 +3174,8 @@ fn render_skills_usage(unexpected: Option<&str>) -> String {
"Skills".to_string(), "Skills".to_string(),
" Usage /skills [list|install <path>|help]".to_string(), " Usage /skills [list|install <path>|help]".to_string(),
" Direct CLI claw skills [list|install <path>|help]".to_string(), " Direct CLI claw skills [list|install <path>|help]".to_string(),
" Install root $CODEX_HOME/skills or ~/.codex/skills".to_string(), " Install root $CLAW_CONFIG_HOME/skills or ~/.claw/skills".to_string(),
" Sources .codex/skills, .claude/skills, legacy /commands".to_string(), " Sources .claw/skills, ~/.claw/skills, legacy /commands".to_string(),
]; ];
if let Some(args) = unexpected { if let Some(args) = unexpected {
lines.push(format!(" Unexpected {args}")); lines.push(format!(" Unexpected {args}"));
@@ -3933,7 +4014,7 @@ mod tests {
let workspace = temp_dir("agents-workspace"); let workspace = temp_dir("agents-workspace");
let project_agents = workspace.join(".codex").join("agents"); let project_agents = workspace.join(".codex").join("agents");
let user_home = temp_dir("agents-home"); 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( write_agent(
&project_agents, &project_agents,
@@ -3966,10 +4047,10 @@ mod tests {
assert!(report.contains("Agents")); assert!(report.contains("Agents"));
assert!(report.contains("2 active 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("planner · Project planner · gpt-5.4 · medium"));
assert!(report.contains("User (~/.codex):")); assert!(report.contains("User (~/.claw):"));
assert!(report.contains("(shadowed by Project (.codex)) planner · User planner")); assert!(report.contains("(shadowed by Project (.claw)) planner · User planner"));
assert!(report.contains("verifier · Verification agent · gpt-5.4-mini · high")); assert!(report.contains("verifier · Verification agent · gpt-5.4-mini · high"));
let _ = fs::remove_dir_all(workspace); let _ = fs::remove_dir_all(workspace);
@@ -4011,12 +4092,11 @@ mod tests {
assert!(report.contains("Skills")); assert!(report.contains("Skills"));
assert!(report.contains("3 available 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("plan · Project planning guidance"));
assert!(report.contains("Project (.claude):"));
assert!(report.contains("deploy · Legacy deployment guidance · legacy /commands")); assert!(report.contains("deploy · Legacy deployment guidance · legacy /commands"));
assert!(report.contains("User (~/.codex):")); assert!(report.contains("User (~/.claw):"));
assert!(report.contains("(shadowed by Project (.codex)) plan · User planning guidance")); assert!(report.contains("(shadowed by Project (.claw)) plan · User planning guidance"));
assert!(report.contains("help · Help guidance")); assert!(report.contains("help · Help guidance"));
let _ = fs::remove_dir_all(workspace); let _ = fs::remove_dir_all(workspace);
@@ -4031,6 +4111,8 @@ mod tests {
super::handle_agents_slash_command(Some("help"), &cwd).expect("agents help"); super::handle_agents_slash_command(Some("help"), &cwd).expect("agents help");
assert!(agents_help.contains("Usage /agents [list|help]")); assert!(agents_help.contains("Usage /agents [list|help]"));
assert!(agents_help.contains("Direct CLI claw agents")); assert!(agents_help.contains("Direct CLI claw agents"));
assert!(agents_help
.contains("Sources .claw/agents, ~/.claw/agents, $CLAW_CONFIG_HOME/agents"));
let agents_unexpected = let agents_unexpected =
super::handle_agents_slash_command(Some("show planner"), &cwd).expect("agents usage"); super::handle_agents_slash_command(Some("show planner"), &cwd).expect("agents usage");
@@ -4039,7 +4121,7 @@ mod tests {
let skills_help = let skills_help =
super::handle_skills_slash_command(Some("--help"), &cwd).expect("skills help"); super::handle_skills_slash_command(Some("--help"), &cwd).expect("skills help");
assert!(skills_help.contains("Usage /skills [list|install <path>|help]")); assert!(skills_help.contains("Usage /skills [list|install <path>|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")); assert!(skills_help.contains("legacy /commands"));
let skills_unexpected = let skills_unexpected =
@@ -4213,7 +4295,7 @@ mod tests {
let listed = render_skills_report( let listed = render_skills_report(
&load_skills_from_roots(&roots).expect("installed skills should load"), &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")); assert!(listed.contains("help · Helpful skill"));
let _ = fs::remove_dir_all(workspace); let _ = fs::remove_dir_all(workspace);

View File

@@ -1,7 +1,7 @@
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
const STARTER_CLAUDE_JSON: &str = concat!( const STARTER_CLAW_JSON: &str = concat!(
"{\n", "{\n",
" \"permissions\": {\n", " \"permissions\": {\n",
" \"defaultMode\": \"dontAsk\"\n", " \"defaultMode\": \"dontAsk\"\n",
@@ -9,7 +9,7 @@ const STARTER_CLAUDE_JSON: &str = concat!(
"}\n", "}\n",
); );
const GITIGNORE_COMMENT: &str = "# Claw Code local artifacts"; 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum InitStatus { pub(crate) enum InitStatus {
@@ -80,16 +80,16 @@ struct RepoDetection {
pub(crate) fn initialize_repo(cwd: &Path) -> Result<InitReport, Box<dyn std::error::Error>> { pub(crate) fn initialize_repo(cwd: &Path) -> Result<InitReport, Box<dyn std::error::Error>> {
let mut artifacts = Vec::new(); let mut artifacts = Vec::new();
let claude_dir = cwd.join(".claude"); let claw_dir = cwd.join(".claw");
artifacts.push(InitArtifact { artifacts.push(InitArtifact {
name: ".claude/", name: ".claw/",
status: ensure_dir(&claude_dir)?, status: ensure_dir(&claw_dir)?,
}); });
let claude_json = cwd.join(".claude.json"); let claw_json = cwd.join(".claw.json");
artifacts.push(InitArtifact { artifacts.push(InitArtifact {
name: ".claude.json", name: ".claw.json",
status: write_file_if_missing(&claude_json, STARTER_CLAUDE_JSON)?, status: write_file_if_missing(&claw_json, STARTER_CLAW_JSON)?,
}); });
let gitignore = cwd.join(".gitignore"); 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("## Working agreement".to_string());
lines.push("- Prefer small, reviewable changes and keep generated bootstrap files aligned with actual repo workflows.".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("- Do not overwrite existing `CLAUDE.md` content automatically; update it intentionally when repo workflows change.".to_string());
lines.push(String::new()); lines.push(String::new());
@@ -354,15 +354,16 @@ mod tests {
let report = initialize_repo(&root).expect("init should succeed"); let report = initialize_repo(&root).expect("init should succeed");
let rendered = report.render(); let rendered = report.render();
assert!(rendered.contains(".claude/ created")); assert!(rendered.contains(".claw/"));
assert!(rendered.contains(".claude.json created")); assert!(rendered.contains(".claw.json"));
assert!(rendered.contains("created"));
assert!(rendered.contains(".gitignore created")); assert!(rendered.contains(".gitignore created"));
assert!(rendered.contains("CLAUDE.md created")); assert!(rendered.contains("CLAUDE.md created"));
assert!(root.join(".claude").is_dir()); assert!(root.join(".claw").is_dir());
assert!(root.join(".claude.json").is_file()); assert!(root.join(".claw.json").is_file());
assert!(root.join("CLAUDE.md").is_file()); assert!(root.join("CLAUDE.md").is_file());
assert_eq!( 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!( concat!(
"{\n", "{\n",
" \"permissions\": {\n", " \"permissions\": {\n",
@@ -372,8 +373,8 @@ mod tests {
) )
); );
let gitignore = fs::read_to_string(root.join(".gitignore")).expect("read gitignore"); let gitignore = fs::read_to_string(root.join(".gitignore")).expect("read gitignore");
assert!(gitignore.contains(".claude/settings.local.json")); assert!(gitignore.contains(".claw/settings.local.json"));
assert!(gitignore.contains(".claude/sessions/")); assert!(gitignore.contains(".claw/sessions/"));
let claude_md = fs::read_to_string(root.join("CLAUDE.md")).expect("read claude md"); 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("Languages: Rust."));
assert!(claude_md.contains("cargo clippy --workspace --all-targets -- -D warnings")); assert!(claude_md.contains("cargo clippy --workspace --all-targets -- -D warnings"));
@@ -386,8 +387,7 @@ mod tests {
let root = temp_dir(); let root = temp_dir();
fs::create_dir_all(&root).expect("create root"); 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("CLAUDE.md"), "custom guidance\n").expect("write existing claude md");
fs::write(root.join(".gitignore"), ".claude/settings.local.json\n") fs::write(root.join(".gitignore"), ".claw/settings.local.json\n").expect("write gitignore");
.expect("write gitignore");
let first = initialize_repo(&root).expect("first init should succeed"); let first = initialize_repo(&root).expect("first init should succeed");
assert!(first assert!(first
@@ -395,8 +395,9 @@ mod tests {
.contains("CLAUDE.md skipped (already exists)")); .contains("CLAUDE.md skipped (already exists)"));
let second = initialize_repo(&root).expect("second init should succeed"); let second = initialize_repo(&root).expect("second init should succeed");
let second_rendered = second.render(); let second_rendered = second.render();
assert!(second_rendered.contains(".claude/ skipped (already exists)")); assert!(second_rendered.contains(".claw/"));
assert!(second_rendered.contains(".claude.json skipped (already exists)")); 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(".gitignore skipped (already exists)"));
assert!(second_rendered.contains("CLAUDE.md skipped (already exists)")); assert!(second_rendered.contains("CLAUDE.md skipped (already exists)"));
assert_eq!( assert_eq!(
@@ -404,8 +405,8 @@ mod tests {
"custom guidance\n" "custom guidance\n"
); );
let gitignore = fs::read_to_string(root.join(".gitignore")).expect("read gitignore"); 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(".claw/settings.local.json").count(), 1);
assert_eq!(gitignore.matches(".claude/sessions/").count(), 1); assert_eq!(gitignore.matches(".claw/sessions/").count(), 1);
fs::remove_dir_all(root).expect("cleanup temp dir"); fs::remove_dir_all(root).expect("cleanup temp dir");
} }

View File

@@ -2976,15 +2976,21 @@ fn resolve_skill_path(skill: &str) -> Result<std::path::PathBuf, String> {
} }
let mut candidates = Vec::new(); 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") { if let Ok(codex_home) = std::env::var("CODEX_HOME") {
candidates.push(std::path::PathBuf::from(codex_home).join("skills")); candidates.push(std::path::PathBuf::from(codex_home).join("skills"));
} }
if let Ok(home) = std::env::var("HOME") { if let Ok(home) = std::env::var("HOME") {
let home = std::path::PathBuf::from(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(".agents").join("skills"));
candidates.push(home.join(".config").join("opencode").join("skills")); candidates.push(home.join(".config").join("opencode").join("skills"));
candidates.push(home.join(".codex").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")); candidates.push(std::path::PathBuf::from("/home/bellman/.codex/skills"));
for root in candidates { for root in candidates {