mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-06 16:14:49 +08:00
Compare commits
1 Commits
fix/p03-re
...
fix/p011-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c65126a339 |
68
.github/workflows/release.yml
vendored
68
.github/workflows/release.yml
vendored
@@ -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
|
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user