mirror of
https://github.com/instructkr/claw-code.git
synced 2026-04-06 16:14:49 +08:00
Compare commits
1 Commits
fix/p011-c
...
fix/p06-br
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac9005bc95 |
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1 +1,3 @@
|
||||
github: instructkr
|
||||
github:
|
||||
- ultraworkers
|
||||
- Yeachan-Heo
|
||||
|
||||
196
README.md
196
README.md
@@ -1,7 +1,17 @@
|
||||
# Rewriting Project Claw Code
|
||||
# Claw Code
|
||||
|
||||
<p align="center">
|
||||
<strong>⭐ The fastest repo in history to surpass 50K stars, reaching the milestone in just 2 hours after publication ⭐</strong>
|
||||
<a href="https://github.com/ultraworkers/claw-code">ultraworkers/claw-code</a>
|
||||
·
|
||||
<a href="./USAGE.md">Usage</a>
|
||||
·
|
||||
<a href="./rust/README.md">Rust workspace</a>
|
||||
·
|
||||
<a href="./PARITY.md">Parity</a>
|
||||
·
|
||||
<a href="./ROADMAP.md">Roadmap</a>
|
||||
·
|
||||
<a href="https://discord.gg/5TUQKqFWd">UltraWorkers Discord</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -9,177 +19,75 @@
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ultraworkers/claw-code&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ultraworkers/claw-code&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ultraworkers/claw-code&type=Date" width="600" />
|
||||
<img alt="Star history for ultraworkers/claw-code" src="https://api.star-history.com/svg?repos=ultraworkers/claw-code&type=Date" width="600" />
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/clawd-hero.jpeg" alt="Claw" width="300" />
|
||||
<img src="assets/claw-hero.jpeg" alt="Claw Code" width="300" />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<strong>Autonomously maintained by lobsters/claws — not by human hands</strong>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Yeachan-Heo/clawhip">clawhip</a> ·
|
||||
<a href="https://github.com/code-yeongyu/oh-my-openagent">oh-my-openagent</a> ·
|
||||
<a href="https://github.com/Yeachan-Heo/oh-my-claudecode">oh-my-claudecode</a> ·
|
||||
<a href="https://github.com/Yeachan-Heo/oh-my-codex">oh-my-codex</a> ·
|
||||
<a href="https://discord.gg/6ztZB9jvWq">UltraWorkers Discord</a>
|
||||
</p>
|
||||
Claw Code is the public Rust implementation of the `claw` CLI agent harness.
|
||||
The canonical implementation lives in [`rust/`](./rust), and the current source of truth for this repository is **ultraworkers/claw-code**.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The active Rust workspace now lives in [`rust/`](./rust). Start with [`USAGE.md`](./USAGE.md) for build, auth, CLI, session, and parity-harness workflows, then use [`rust/README.md`](./rust/README.md) for crate-level details.
|
||||
> Start with [`USAGE.md`](./USAGE.md) for build, auth, CLI, session, and parity-harness workflows. Use [`rust/README.md`](./rust/README.md) for crate-level details and [`PARITY.md`](./PARITY.md) for the current Rust-port checkpoint.
|
||||
|
||||
> Want the bigger idea behind this repo? Read [`PHILOSOPHY.md`](./PHILOSOPHY.md) and Sigrid Jin's public explanation: https://x.com/realsigridjin/status/2039472968624185713
|
||||
## Current repository shape
|
||||
|
||||
> Shout-out to the UltraWorkers ecosystem powering this repo: [clawhip](https://github.com/Yeachan-Heo/clawhip), [oh-my-openagent](https://github.com/code-yeongyu/oh-my-openagent), [oh-my-claudecode](https://github.com/Yeachan-Heo/oh-my-claudecode), [oh-my-codex](https://github.com/Yeachan-Heo/oh-my-codex), and the [UltraWorkers Discord](https://discord.gg/6ztZB9jvWq).
|
||||
- **`rust/`** — canonical Rust workspace and the `claw` CLI binary
|
||||
- **`USAGE.md`** — task-oriented usage guide for the current product surface
|
||||
- **`PARITY.md`** — Rust-port parity status and migration notes
|
||||
- **`ROADMAP.md`** — active roadmap and cleanup backlog
|
||||
- **`PHILOSOPHY.md`** — project intent and system-design framing
|
||||
- **`src/` + `tests/`** — companion Python/reference workspace and audit helpers; not the primary runtime surface
|
||||
|
||||
---
|
||||
|
||||
## Backstory
|
||||
|
||||
This repo is maintained by **lobsters/claws**, not by a conventional human-only dev team.
|
||||
|
||||
The people behind the system are [Bellman / Yeachan Heo](https://github.com/Yeachan-Heo) and friends like [Yeongyu](https://github.com/code-yeongyu), but the repo itself is being pushed forward by autonomous claw workflows: parallel coding sessions, event-driven orchestration, recovery loops, and machine-readable lane state.
|
||||
|
||||
In practice, that means this project is not just *about* coding agents — it is being **actively built by them**. Features, tests, telemetry, docs, and workflow hardening are landed through claw-driven loops using [clawhip](https://github.com/Yeachan-Heo/clawhip), [oh-my-openagent](https://github.com/code-yeongyu/oh-my-openagent), [oh-my-claudecode](https://github.com/Yeachan-Heo/oh-my-claudecode), and [oh-my-codex](https://github.com/Yeachan-Heo/oh-my-codex).
|
||||
|
||||
This repository exists to prove that an open coding harness can be built **autonomously, in public, and at high velocity** — with humans setting direction and claws doing the grinding.
|
||||
|
||||
See the public build story here:
|
||||
|
||||
https://x.com/realsigridjin/status/2039472968624185713
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Porting Status
|
||||
|
||||
The main source tree is now Python-first.
|
||||
|
||||
- `src/` contains the active Python porting workspace
|
||||
- `tests/` verifies the current Python workspace
|
||||
- the exposed snapshot is no longer part of the tracked repository state
|
||||
|
||||
The current Python workspace is not yet a complete one-to-one replacement for the original system, but the primary implementation surface is now Python.
|
||||
|
||||
## Why this rewrite exists
|
||||
|
||||
I originally studied the exposed codebase to understand its harness, tool wiring, and agent workflow. After spending more time with the legal and ethical questions—and after reading the essay linked below—I did not want the exposed snapshot itself to remain the main tracked source tree.
|
||||
|
||||
This repository now focuses on Python porting work instead.
|
||||
|
||||
## Repository Layout
|
||||
|
||||
```text
|
||||
.
|
||||
├── src/ # Python porting workspace
|
||||
│ ├── __init__.py
|
||||
│ ├── commands.py
|
||||
│ ├── main.py
|
||||
│ ├── models.py
|
||||
│ ├── port_manifest.py
|
||||
│ ├── query_engine.py
|
||||
│ ├── task.py
|
||||
│ └── tools.py
|
||||
├── tests/ # Python verification
|
||||
├── assets/omx/ # OmX workflow screenshots
|
||||
├── 2026-03-09-is-legal-the-same-as-legitimate-ai-reimplementation-and-the-erosion-of-copyleft.md
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Python Workspace Overview
|
||||
|
||||
The new Python `src/` tree currently provides:
|
||||
|
||||
- **`port_manifest.py`** — summarizes the current Python workspace structure
|
||||
- **`models.py`** — dataclasses for subsystems, modules, and backlog state
|
||||
- **`commands.py`** — Python-side command port metadata
|
||||
- **`tools.py`** — Python-side tool port metadata
|
||||
- **`query_engine.py`** — renders a Python porting summary from the active workspace
|
||||
- **`main.py`** — a CLI entrypoint for manifest and summary output
|
||||
|
||||
## Quickstart
|
||||
|
||||
Render the Python porting summary:
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
python3 -m src.main summary
|
||||
cd rust
|
||||
cargo build --workspace
|
||||
./target/debug/claw --help
|
||||
./target/debug/claw prompt "summarize this repository"
|
||||
```
|
||||
|
||||
Print the current Python workspace manifest:
|
||||
Authenticate with either an API key or the built-in OAuth flow:
|
||||
|
||||
```bash
|
||||
python3 -m src.main manifest
|
||||
export ANTHROPIC_API_KEY="sk-ant-..."
|
||||
# or
|
||||
cd rust
|
||||
./target/debug/claw login
|
||||
```
|
||||
|
||||
List the current Python modules:
|
||||
Run the workspace test suite:
|
||||
|
||||
```bash
|
||||
python3 -m src.main subsystems --limit 16
|
||||
cd rust
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
Run verification:
|
||||
## Documentation map
|
||||
|
||||
```bash
|
||||
python3 -m unittest discover -s tests -v
|
||||
```
|
||||
- [`USAGE.md`](./USAGE.md) — quick commands, auth, sessions, config, parity harness
|
||||
- [`rust/README.md`](./rust/README.md) — crate map, CLI surface, features, workspace layout
|
||||
- [`PARITY.md`](./PARITY.md) — parity status for the Rust port
|
||||
- [`rust/MOCK_PARITY_HARNESS.md`](./rust/MOCK_PARITY_HARNESS.md) — deterministic mock-service harness details
|
||||
- [`ROADMAP.md`](./ROADMAP.md) — active roadmap and open cleanup work
|
||||
- [`PHILOSOPHY.md`](./PHILOSOPHY.md) — why the project exists and how it is operated
|
||||
|
||||
Run the parity audit against the local ignored archive (when present):
|
||||
## Ecosystem
|
||||
|
||||
```bash
|
||||
python3 -m src.main parity-audit
|
||||
```
|
||||
Claw Code is built in the open alongside the broader UltraWorkers toolchain:
|
||||
|
||||
Inspect mirrored command/tool inventories:
|
||||
- [clawhip](https://github.com/Yeachan-Heo/clawhip)
|
||||
- [oh-my-openagent](https://github.com/code-yeongyu/oh-my-openagent)
|
||||
- [oh-my-claudecode](https://github.com/Yeachan-Heo/oh-my-claudecode)
|
||||
- [oh-my-codex](https://github.com/Yeachan-Heo/oh-my-codex)
|
||||
- [UltraWorkers Discord](https://discord.gg/5TUQKqFWd)
|
||||
|
||||
```bash
|
||||
python3 -m src.main commands --limit 10
|
||||
python3 -m src.main tools --limit 10
|
||||
```
|
||||
|
||||
## Current Parity Checkpoint
|
||||
|
||||
The port now mirrors the archived root-entry file surface, top-level subsystem names, and command/tool inventories much more closely than before. However, it is **not yet** a full runtime-equivalent replacement for the original TypeScript system; the Python tree still contains fewer executable runtime slices than the archived source.
|
||||
|
||||
|
||||
## Built with `oh-my-codex`
|
||||
|
||||
The restructuring and documentation work on this repository was AI-assisted and orchestrated with Yeachan Heo's [oh-my-codex (OmX)](https://github.com/Yeachan-Heo/oh-my-codex), layered on top of Codex.
|
||||
|
||||
- **`$team` mode:** used for coordinated parallel review and architectural feedback
|
||||
- **`$ralph` mode:** used for persistent execution, verification, and completion discipline
|
||||
- **Codex-driven workflow:** used to turn the main `src/` tree into a Python-first porting workspace
|
||||
|
||||
### OmX workflow screenshots
|
||||
|
||||

|
||||
|
||||
*Ralph/team orchestration view while the README and essay context were being reviewed in terminal panes.*
|
||||
|
||||

|
||||
|
||||
*Split-pane review and verification flow during the final README wording pass.*
|
||||
|
||||
## Community
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/6ztZB9jvWq"><img src="https://img.shields.io/badge/UltraWorkers-Discord-5865F2?logo=discord&style=for-the-badge" alt="UltraWorkers Discord" /></a>
|
||||
</p>
|
||||
|
||||
Join the [**UltraWorkers Discord**](https://discord.gg/6ztZB9jvWq) — the community around clawhip, oh-my-openagent, oh-my-claudecode, oh-my-codex, and claw-code. Come chat about LLMs, harness engineering, agent workflows, and autonomous software development.
|
||||
|
||||
[](https://discord.gg/6ztZB9jvWq)
|
||||
|
||||
## Star History
|
||||
|
||||
See the chart at the top of this README.
|
||||
|
||||
## Ownership / Affiliation Disclaimer
|
||||
## Ownership / affiliation disclaimer
|
||||
|
||||
- This repository does **not** claim ownership of the original Claude Code source material.
|
||||
- This repository is **not affiliated with, endorsed by, or maintained by Anthropic**.
|
||||
|
||||
15
ROADMAP.md
15
ROADMAP.md
@@ -276,14 +276,13 @@ Priority order: P0 = blocks CI/green state, P1 = blocks integration wiring, P2 =
|
||||
3. Add release-grade binary workflow — repo has a Rust CLI and release intent, but no GitHub Actions path that builds tagged artifacts / checks release packaging before a publish step
|
||||
4. Add container-first test/run docs — runtime detects Docker/Podman/container state, but docs do not show a canonical container workflow for `cargo test --workspace`, binary execution, or bind-mounted repo usage
|
||||
5. Surface `doctor` / preflight diagnostics in onboarding docs and help — the CLI already has setup-diagnosis commands and branch preflight machinery, but they are not prominent enough in README/USAGE, so new users still ask manual setup questions instead of running a built-in health check first
|
||||
6. Add branding/source-of-truth residue checks for docs — after repo migration, old org names can survive in badges, star-history URLs, and copied snippets; docs need a consistency pass or CI lint to catch stale branding automatically
|
||||
7. Reconcile README product narrative with current repo reality — top-level docs now say the active workspace is Rust, but later sections still describe the repo as Python-first; users should not have to infer which implementation is canonical
|
||||
8. Eliminate warning spam from first-run help/build path — `cargo run -p rusty-claude-cli -- --help` currently prints a wall of compile warnings before the actual help text, which pollutes the first-touch UX and hides the product surface behind unrelated noise
|
||||
9. Promote `doctor` from slash-only to top-level CLI entrypoint — users naturally try `claw doctor`, but today it errors and tells them to enter a REPL or resume path first; healthcheck flows should be callable directly from the shell
|
||||
10. Make machine-readable status commands actually machine-readable — `status` and `sandbox` accept the global `--output-format json` flag path, but currently still render prose tables, which breaks shell automation and agent-friendly health polling
|
||||
11. Unify legacy config/skill namespaces in user-facing output — `skills` currently surfaces mixed project roots like `.codex` and `.claude`, which leaks historical layers into the current product and makes it unclear which config namespace is canonical
|
||||
12. Honor JSON output on inventory commands like `skills` and `mcp` — these are exactly the commands agents and shell scripts want to inspect programmatically, but `--output-format json` still yields prose, forcing text scraping where structured inventory should exist
|
||||
13. Audit `--output-format` contract across the whole CLI surface — current behavior is inconsistent by subcommand, so agents cannot trust the global flag without command-by-command probing; the format contract itself needs to become deterministic
|
||||
6. Automate branding/source-of-truth residue checks in CI — the manual doc pass is done, but badges, Discord invites, copied repo URLs, and org-name drift can regress unless a cheap lint/check keeps README/docs aligned with `ultraworkers/claw-code`
|
||||
7. Eliminate warning spam from first-run help/build path — `cargo run -p rusty-claude-cli -- --help` currently prints a wall of compile warnings before the actual help text, which pollutes the first-touch UX and hides the product surface behind unrelated noise
|
||||
8. Promote `doctor` from slash-only to top-level CLI entrypoint — users naturally try `claw doctor`, but today it errors and tells them to enter a REPL or resume path first; healthcheck flows should be callable directly from the shell
|
||||
9. Make machine-readable status commands actually machine-readable — `status` and `sandbox` accept the global `--output-format json` flag path, but currently still render prose tables, which breaks shell automation and agent-friendly health polling
|
||||
10. Unify legacy config/skill namespaces in user-facing output — `skills` currently surfaces mixed project roots like `.codex` and `.claude`, which leaks historical layers into the current product and makes it unclear which config namespace is canonical
|
||||
11. Honor JSON output on inventory commands like `skills` and `mcp` — these are exactly the commands agents and shell scripts want to inspect programmatically, but `--output-format json` still yields prose, forcing text scraping where structured inventory should exist
|
||||
12. Audit `--output-format` contract across the whole CLI surface — current behavior is inconsistent by subcommand, so agents cannot trust the global flag without command-by-command probing; the format contract itself needs to become deterministic
|
||||
|
||||
**P1 — Next (integration wiring, unblocks verification)**
|
||||
2. Add cross-module integration tests — **done**: 12 integration tests covering worker→recovery→policy, stale_branch→policy, green_contract→policy, reconciliation flows
|
||||
|
||||
|
Before Width: | Height: | Size: 233 KiB After Width: | Height: | Size: 233 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.8 KiB |
@@ -1954,46 +1954,22 @@ pub struct PluginsCommandResult {
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum DefinitionSource {
|
||||
ProjectClaw,
|
||||
ProjectCodex,
|
||||
ProjectClaude,
|
||||
UserClawConfigHome,
|
||||
UserCodexHome,
|
||||
UserClaw,
|
||||
UserCodex,
|
||||
UserClaude,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum DefinitionScope {
|
||||
Project,
|
||||
UserConfigHome,
|
||||
UserHome,
|
||||
}
|
||||
|
||||
impl DefinitionScope {
|
||||
fn label(self) -> &'static str {
|
||||
match self {
|
||||
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()
|
||||
match self {
|
||||
Self::ProjectCodex => "Project (.codex)",
|
||||
Self::ProjectClaude => "Project (.claude)",
|
||||
Self::UserCodexHome => "User ($CODEX_HOME)",
|
||||
Self::UserCodex => "User (~/.codex)",
|
||||
Self::UserClaude => "User (~/.claude)",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2326,11 +2302,6 @@ 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,
|
||||
@@ -2343,14 +2314,6 @@ 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,
|
||||
@@ -2361,11 +2324,6 @@ 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,
|
||||
@@ -2385,12 +2343,6 @@ fn discover_skill_roots(cwd: &Path) -> Vec<SkillRoot> {
|
||||
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,
|
||||
@@ -2403,12 +2355,6 @@ fn discover_skill_roots(cwd: &Path) -> Vec<SkillRoot> {
|
||||
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,
|
||||
@@ -2423,22 +2369,6 @@ 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") {
|
||||
let codex_home = PathBuf::from(codex_home);
|
||||
push_unique_skill_root(
|
||||
@@ -2457,18 +2387,6 @@ fn discover_skill_roots(cwd: &Path) -> Vec<SkillRoot> {
|
||||
|
||||
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,
|
||||
@@ -2549,18 +2467,15 @@ fn install_skill_into(
|
||||
}
|
||||
|
||||
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") {
|
||||
return Ok(PathBuf::from(codex_home).join("skills"));
|
||||
}
|
||||
if let Some(home) = env::var_os("HOME") {
|
||||
return Ok(PathBuf::from(home).join(".claw").join("skills"));
|
||||
return Ok(PathBuf::from(home).join(".codex").join("skills"));
|
||||
}
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"unable to resolve a skills install root; set CLAW_CONFIG_HOME or HOME",
|
||||
"unable to resolve a skills install root; set CODEX_HOME or HOME",
|
||||
))
|
||||
}
|
||||
|
||||
@@ -2926,20 +2841,22 @@ fn render_agents_report(agents: &[AgentSummary]) -> String {
|
||||
String::new(),
|
||||
];
|
||||
|
||||
for scope in [
|
||||
DefinitionScope::Project,
|
||||
DefinitionScope::UserConfigHome,
|
||||
DefinitionScope::UserHome,
|
||||
for source in [
|
||||
DefinitionSource::ProjectCodex,
|
||||
DefinitionSource::ProjectClaude,
|
||||
DefinitionSource::UserCodexHome,
|
||||
DefinitionSource::UserCodex,
|
||||
DefinitionSource::UserClaude,
|
||||
] {
|
||||
let group = agents
|
||||
.iter()
|
||||
.filter(|agent| agent.source.report_scope() == scope)
|
||||
.filter(|agent| agent.source == source)
|
||||
.collect::<Vec<_>>();
|
||||
if group.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
lines.push(format!("{}:", scope.label()));
|
||||
lines.push(format!("{}:", source.label()));
|
||||
for agent in group {
|
||||
let detail = agent_detail(agent);
|
||||
match agent.shadowed_by {
|
||||
@@ -2982,20 +2899,22 @@ fn render_skills_report(skills: &[SkillSummary]) -> String {
|
||||
String::new(),
|
||||
];
|
||||
|
||||
for scope in [
|
||||
DefinitionScope::Project,
|
||||
DefinitionScope::UserConfigHome,
|
||||
DefinitionScope::UserHome,
|
||||
for source in [
|
||||
DefinitionSource::ProjectCodex,
|
||||
DefinitionSource::ProjectClaude,
|
||||
DefinitionSource::UserCodexHome,
|
||||
DefinitionSource::UserCodex,
|
||||
DefinitionSource::UserClaude,
|
||||
] {
|
||||
let group = skills
|
||||
.iter()
|
||||
.filter(|skill| skill.source.report_scope() == scope)
|
||||
.filter(|skill| skill.source == source)
|
||||
.collect::<Vec<_>>();
|
||||
if group.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
lines.push(format!("{}:", scope.label()));
|
||||
lines.push(format!("{}:", source.label()));
|
||||
for skill in group {
|
||||
let mut parts = vec![skill.name.clone()];
|
||||
if let Some(description) = &skill.description {
|
||||
@@ -3161,7 +3080,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 .claw/agents, ~/.claw/agents, $CLAW_CONFIG_HOME/agents".to_string(),
|
||||
" Sources .codex/agents, .claude/agents, $CODEX_HOME/agents".to_string(),
|
||||
];
|
||||
if let Some(args) = unexpected {
|
||||
lines.push(format!(" Unexpected {args}"));
|
||||
@@ -3174,8 +3093,8 @@ fn render_skills_usage(unexpected: Option<&str>) -> String {
|
||||
"Skills".to_string(),
|
||||
" Usage /skills [list|install <path>|help]".to_string(),
|
||||
" Direct CLI claw skills [list|install <path>|help]".to_string(),
|
||||
" Install root $CLAW_CONFIG_HOME/skills or ~/.claw/skills".to_string(),
|
||||
" Sources .claw/skills, ~/.claw/skills, legacy /commands".to_string(),
|
||||
" Install root $CODEX_HOME/skills or ~/.codex/skills".to_string(),
|
||||
" Sources .codex/skills, .claude/skills, legacy /commands".to_string(),
|
||||
];
|
||||
if let Some(args) = unexpected {
|
||||
lines.push(format!(" Unexpected {args}"));
|
||||
@@ -4014,7 +3933,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(".claude").join("agents");
|
||||
let user_agents = user_home.join(".codex").join("agents");
|
||||
|
||||
write_agent(
|
||||
&project_agents,
|
||||
@@ -4047,10 +3966,10 @@ mod tests {
|
||||
|
||||
assert!(report.contains("Agents"));
|
||||
assert!(report.contains("2 active agents"));
|
||||
assert!(report.contains("Project (.claw):"));
|
||||
assert!(report.contains("Project (.codex):"));
|
||||
assert!(report.contains("planner · Project planner · gpt-5.4 · medium"));
|
||||
assert!(report.contains("User (~/.claw):"));
|
||||
assert!(report.contains("(shadowed by Project (.claw)) planner · User planner"));
|
||||
assert!(report.contains("User (~/.codex):"));
|
||||
assert!(report.contains("(shadowed by Project (.codex)) planner · User planner"));
|
||||
assert!(report.contains("verifier · Verification agent · gpt-5.4-mini · high"));
|
||||
|
||||
let _ = fs::remove_dir_all(workspace);
|
||||
@@ -4092,11 +4011,12 @@ mod tests {
|
||||
|
||||
assert!(report.contains("Skills"));
|
||||
assert!(report.contains("3 available skills"));
|
||||
assert!(report.contains("Project (.claw):"));
|
||||
assert!(report.contains("Project (.codex):"));
|
||||
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 (~/.claw):"));
|
||||
assert!(report.contains("(shadowed by Project (.claw)) plan · User planning guidance"));
|
||||
assert!(report.contains("User (~/.codex):"));
|
||||
assert!(report.contains("(shadowed by Project (.codex)) plan · User planning guidance"));
|
||||
assert!(report.contains("help · Help guidance"));
|
||||
|
||||
let _ = fs::remove_dir_all(workspace);
|
||||
@@ -4111,8 +4031,6 @@ 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");
|
||||
@@ -4121,7 +4039,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 <path>|help]"));
|
||||
assert!(skills_help.contains("Install root $CLAW_CONFIG_HOME/skills or ~/.claw/skills"));
|
||||
assert!(skills_help.contains("Install root $CODEX_HOME/skills or ~/.codex/skills"));
|
||||
assert!(skills_help.contains("legacy /commands"));
|
||||
|
||||
let skills_unexpected =
|
||||
@@ -4295,7 +4213,7 @@ mod tests {
|
||||
let listed = render_skills_report(
|
||||
&load_skills_from_roots(&roots).expect("installed skills should load"),
|
||||
);
|
||||
assert!(listed.contains("User ($CLAW_CONFIG_HOME):"));
|
||||
assert!(listed.contains("User ($CODEX_HOME):"));
|
||||
assert!(listed.contains("help · Helpful skill"));
|
||||
|
||||
let _ = fs::remove_dir_all(workspace);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
const STARTER_CLAW_JSON: &str = concat!(
|
||||
const STARTER_CLAUDE_JSON: &str = concat!(
|
||||
"{\n",
|
||||
" \"permissions\": {\n",
|
||||
" \"defaultMode\": \"dontAsk\"\n",
|
||||
@@ -9,7 +9,7 @@ const STARTER_CLAW_JSON: &str = concat!(
|
||||
"}\n",
|
||||
);
|
||||
const GITIGNORE_COMMENT: &str = "# Claw Code local artifacts";
|
||||
const GITIGNORE_ENTRIES: [&str; 2] = [".claw/settings.local.json", ".claw/sessions/"];
|
||||
const GITIGNORE_ENTRIES: [&str; 2] = [".claude/settings.local.json", ".claude/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<InitReport, Box<dyn std::error::Error>> {
|
||||
let mut artifacts = Vec::new();
|
||||
|
||||
let claw_dir = cwd.join(".claw");
|
||||
let claude_dir = cwd.join(".claude");
|
||||
artifacts.push(InitArtifact {
|
||||
name: ".claw/",
|
||||
status: ensure_dir(&claw_dir)?,
|
||||
name: ".claude/",
|
||||
status: ensure_dir(&claude_dir)?,
|
||||
});
|
||||
|
||||
let claw_json = cwd.join(".claw.json");
|
||||
let claude_json = cwd.join(".claude.json");
|
||||
artifacts.push(InitArtifact {
|
||||
name: ".claw.json",
|
||||
status: write_file_if_missing(&claw_json, STARTER_CLAW_JSON)?,
|
||||
name: ".claude.json",
|
||||
status: write_file_if_missing(&claude_json, STARTER_CLAUDE_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 `.claw.json`; reserve `.claw/settings.local.json` for machine-local overrides.".to_string());
|
||||
lines.push("- Keep shared defaults in `.claude.json`; reserve `.claude/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,16 +354,15 @@ mod tests {
|
||||
|
||||
let report = initialize_repo(&root).expect("init should succeed");
|
||||
let rendered = report.render();
|
||||
assert!(rendered.contains(".claw/"));
|
||||
assert!(rendered.contains(".claw.json"));
|
||||
assert!(rendered.contains("created"));
|
||||
assert!(rendered.contains(".claude/ created"));
|
||||
assert!(rendered.contains(".claude.json created"));
|
||||
assert!(rendered.contains(".gitignore created"));
|
||||
assert!(rendered.contains("CLAUDE.md created"));
|
||||
assert!(root.join(".claw").is_dir());
|
||||
assert!(root.join(".claw.json").is_file());
|
||||
assert!(root.join(".claude").is_dir());
|
||||
assert!(root.join(".claude.json").is_file());
|
||||
assert!(root.join("CLAUDE.md").is_file());
|
||||
assert_eq!(
|
||||
fs::read_to_string(root.join(".claw.json")).expect("read claw json"),
|
||||
fs::read_to_string(root.join(".claude.json")).expect("read claude json"),
|
||||
concat!(
|
||||
"{\n",
|
||||
" \"permissions\": {\n",
|
||||
@@ -373,8 +372,8 @@ mod tests {
|
||||
)
|
||||
);
|
||||
let gitignore = fs::read_to_string(root.join(".gitignore")).expect("read gitignore");
|
||||
assert!(gitignore.contains(".claw/settings.local.json"));
|
||||
assert!(gitignore.contains(".claw/sessions/"));
|
||||
assert!(gitignore.contains(".claude/settings.local.json"));
|
||||
assert!(gitignore.contains(".claude/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"));
|
||||
@@ -387,7 +386,8 @@ 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"), ".claw/settings.local.json\n").expect("write gitignore");
|
||||
fs::write(root.join(".gitignore"), ".claude/settings.local.json\n")
|
||||
.expect("write gitignore");
|
||||
|
||||
let first = initialize_repo(&root).expect("first init should succeed");
|
||||
assert!(first
|
||||
@@ -395,9 +395,8 @@ 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(".claw/"));
|
||||
assert!(second_rendered.contains(".claw.json"));
|
||||
assert!(second_rendered.contains("skipped (already exists)"));
|
||||
assert!(second_rendered.contains(".claude/ skipped (already exists)"));
|
||||
assert!(second_rendered.contains(".claude.json skipped (already exists)"));
|
||||
assert!(second_rendered.contains(".gitignore skipped (already exists)"));
|
||||
assert!(second_rendered.contains("CLAUDE.md skipped (already exists)"));
|
||||
assert_eq!(
|
||||
@@ -405,8 +404,8 @@ mod tests {
|
||||
"custom guidance\n"
|
||||
);
|
||||
let gitignore = fs::read_to_string(root.join(".gitignore")).expect("read gitignore");
|
||||
assert_eq!(gitignore.matches(".claw/settings.local.json").count(), 1);
|
||||
assert_eq!(gitignore.matches(".claw/sessions/").count(), 1);
|
||||
assert_eq!(gitignore.matches(".claude/settings.local.json").count(), 1);
|
||||
assert_eq!(gitignore.matches(".claude/sessions/").count(), 1);
|
||||
|
||||
fs::remove_dir_all(root).expect("cleanup temp dir");
|
||||
}
|
||||
|
||||
@@ -2976,21 +2976,15 @@ fn resolve_skill_path(skill: &str) -> Result<std::path::PathBuf, String> {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user