Rebrand claw to Brew Code; honor OPENAI_BASE_URL from .env
- Rename binary claw -> brewcode, display name -> Brew Code - Rename config paths .claw/ -> .brewcode/, CLAW_CONFIG_HOME -> BREWCODE_CONFIG_HOME - Resolve OPENAI_BASE_URL via the .env-aware lookup used for the API key
This commit is contained in:
parent
69c45a9b76
commit
422b46d8f0
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"plugins": {
|
||||
"example-bundled@bundled": {
|
||||
"kind": "bundled",
|
||||
"id": "example-bundled@bundled",
|
||||
"name": "example-bundled",
|
||||
"version": "0.1.0",
|
||||
"description": "Example bundled plugin scaffold for the Rust plugin system",
|
||||
"install_path": ".brewcode\\plugins\\installed\\example-bundled-bundled",
|
||||
"source": {
|
||||
"type": "local_path",
|
||||
"path": "C:\\Users\\dcsam\\claw-code-dcv1.1\\rust\\crates\\plugins\\bundled\\example-bundled"
|
||||
},
|
||||
"installed_at_unix_ms": 1779111214235,
|
||||
"updated_at_unix_ms": 1779111214235
|
||||
},
|
||||
"sample-hooks@bundled": {
|
||||
"kind": "bundled",
|
||||
"id": "sample-hooks@bundled",
|
||||
"name": "sample-hooks",
|
||||
"version": "0.1.0",
|
||||
"description": "Bundled sample plugin scaffold for hook integration tests.",
|
||||
"install_path": ".brewcode\\plugins\\installed\\sample-hooks-bundled",
|
||||
"source": {
|
||||
"type": "local_path",
|
||||
"path": "C:\\Users\\dcsam\\claw-code-dcv1.1\\rust\\crates\\plugins\\bundled\\sample-hooks"
|
||||
},
|
||||
"installed_at_unix_ms": 1779111214245,
|
||||
"updated_at_unix_ms": 1779111214245
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "example-bundled",
|
||||
"version": "0.1.0",
|
||||
"description": "Example bundled plugin scaffold for the Rust plugin system",
|
||||
"defaultEnabled": false,
|
||||
"hooks": {
|
||||
"PreToolUse": ["./hooks/pre.sh"],
|
||||
"PostToolUse": ["./hooks/post.sh"]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
printf '%s\n' 'example bundled post hook'
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
printf '%s\n' 'example bundled pre hook'
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "sample-hooks",
|
||||
"version": "0.1.0",
|
||||
"description": "Bundled sample plugin scaffold for hook integration tests.",
|
||||
"defaultEnabled": false,
|
||||
"hooks": {
|
||||
"PreToolUse": ["./hooks/pre.sh"],
|
||||
"PostToolUse": ["./hooks/post.sh"]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
printf 'sample bundled post hook'
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
printf 'sample bundled pre hook'
|
||||
|
|
@ -17,12 +17,12 @@ FILES = [
|
|||
FILES.extend(sorted((ROOT / 'docs').rglob('*.md')) if (ROOT / 'docs').exists() else [])
|
||||
|
||||
FORBIDDEN = {
|
||||
r'github\.com/Yeachan-Heo/claw-code(?!-parity)': 'replace old claw-code GitHub links with ultraworkers/claw-code',
|
||||
r'github\.com/code-yeongyu/claw-code': 'replace stale alternate claw-code GitHub links with ultraworkers/claw-code',
|
||||
r'github\.com/Yeachan-Heo/brew-code(?!-parity)': 'replace old brew-code GitHub links with ultraworkers/claw-code',
|
||||
r'github\.com/code-yeongyu/brew-code': 'replace stale alternate brew-code GitHub links with ultraworkers/claw-code',
|
||||
r'discord\.gg/6ztZB9jvWq': 'replace the stale UltraWorkers Discord invite with the current invite',
|
||||
r'api\.star-history\.com/svg\?repos=Yeachan-Heo/claw-code': 'update star-history embeds to ultraworkers/claw-code',
|
||||
r'star-history\.com/#Yeachan-Heo/claw-code': 'update star-history links to ultraworkers/claw-code',
|
||||
r'assets/clawd-hero\.jpeg': 'rename stale hero asset references to assets/claw-hero.jpeg',
|
||||
r'api\.star-history\.com/svg\?repos=Yeachan-Heo/brew-code': 'update star-history embeds to ultraworkers/claw-code',
|
||||
r'star-history\.com/#Yeachan-Heo/brew-code': 'update star-history links to ultraworkers/claw-code',
|
||||
r'assets/brewcoded-hero\.jpeg': 'rename stale hero asset references to assets/brewcode-hero.jpeg',
|
||||
r'assets/instructkr\.png': 'remove stale instructkr image references',
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Windows CI, and minimal release jobs. It validates:
|
|||
* required repository policy files exist;
|
||||
* local Markdown links and image targets resolve;
|
||||
* local heading anchors referenced from Markdown resolve; and
|
||||
* command examples do not present the deprecated `cargo install claw-code`
|
||||
* command examples do not present the deprecated `cargo install brew-code`
|
||||
package as an executable install path.
|
||||
"""
|
||||
|
||||
|
|
@ -142,11 +142,11 @@ def validate_command_examples(errors: list[str]) -> None:
|
|||
stripped = line.strip()
|
||||
if not stripped or stripped.startswith(("#", ">")):
|
||||
continue
|
||||
if re.search(r"\bcargo\s+install\s+claw-code\b", stripped):
|
||||
if re.search(r"\bcargo\s+install\s+brew-code\b", stripped):
|
||||
line_no = text.count("\n", 0, match.start()) + offset + 1
|
||||
errors.append(
|
||||
f"{path.relative_to(ROOT)}:{line_no}: deprecated "
|
||||
"`cargo install claw-code` appears in an executable "
|
||||
"`cargo install brew-code` appears in an executable "
|
||||
"command block; use build-from-source docs instead"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
__pycache__/
|
||||
archive/
|
||||
.omx/
|
||||
.clawd-agents/
|
||||
.brewcoded-agents/
|
||||
# Claude Code local artifacts
|
||||
.claude/settings.local.json
|
||||
.claude/sessions/
|
||||
# Claw Code local artifacts
|
||||
.claw/settings.local.json
|
||||
.claw/sessions/
|
||||
.clawhip/
|
||||
rust/.claude/sessions/
|
||||
# Brew Code local artifacts
|
||||
.brewcode/settings.local.json
|
||||
.brewcode/sessions/
|
||||
.brewcodehip/
|
||||
status-help.txt
|
||||
# Legacy Python port session scratch artifacts
|
||||
.port_sessions/
|
||||
# Local secrets — never commit
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Our pledge
|
||||
|
||||
We aim to make Claw Code a practical, respectful, and evidence-oriented
|
||||
We aim to make Brew Code a practical, respectful, and evidence-oriented
|
||||
community. Contributors and maintainers are expected to communicate with
|
||||
patience, assume good intent, and focus critique on the work rather than the
|
||||
person.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Contributing to Claw Code
|
||||
# Contributing to Brew Code
|
||||
|
||||
Thanks for helping improve Claw Code. This repository is a Rust-first CLI
|
||||
Thanks for helping improve Brew Code. This repository is a Rust-first CLI
|
||||
workspace with supporting docs and compatibility fixtures.
|
||||
|
||||
## Ground rules
|
||||
|
|
@ -19,7 +19,7 @@ workspace with supporting docs and compatibility fixtures.
|
|||
|
||||
```bash
|
||||
git clone https://github.com/ultraworkers/claw-code
|
||||
cd claw-code/rust
|
||||
cd brew-code/rust
|
||||
cargo build --workspace
|
||||
cargo test --workspace
|
||||
```
|
||||
|
|
@ -28,9 +28,9 @@ On Windows PowerShell, build from the same `rust` workspace and run the binary
|
|||
with the `.exe` suffix:
|
||||
|
||||
```powershell
|
||||
cd claw-code\rust
|
||||
cd brew-code\rust
|
||||
cargo build --workspace
|
||||
.\target\debug\claw.exe --help
|
||||
.\target\debug\brewcode.exe --help
|
||||
```
|
||||
|
||||
## Checks before opening a pull request
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Parity Status — claw-code Rust Port
|
||||
# Parity Status — brew-code Rust Port
|
||||
|
||||
Last updated: 2026-04-03
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
# Claw Code Philosophy
|
||||
# Brew Code Philosophy
|
||||
|
||||
## Stop Staring at the Files
|
||||
|
||||
If you only look at the generated files in this repository, you are looking at the wrong layer.
|
||||
|
||||
The Python rewrite was a byproduct. The Rust rewrite was also a byproduct. The real thing worth studying is the **system that produced them**: a clawhip-based coordination loop where humans give direction and autonomous claws execute the work.
|
||||
The Python rewrite was a byproduct. The Rust rewrite was also a byproduct. The real thing worth studying is the **system that produced them**: a brewcodehip-based coordination loop where humans give direction and autonomous brewcodes execute the work.
|
||||
|
||||
Claw Code is not just a codebase. It is a public demonstration of what happens when:
|
||||
Brew Code is not just a codebase. It is a public demonstration of what happens when:
|
||||
|
||||
- a human provides clear direction,
|
||||
- multiple coding agents coordinate in parallel,
|
||||
|
|
@ -20,9 +20,9 @@ The important interface here is not tmux, Vim, SSH, or a terminal multiplexer.
|
|||
|
||||
The real human interface is a Discord channel.
|
||||
|
||||
A person can type a sentence from a phone, walk away, sleep, or do something else. The claws read the directive, break it into tasks, assign roles, write code, run tests, argue over failures, recover, and push when the work passes.
|
||||
A person can type a sentence from a phone, walk away, sleep, or do something else. The brewcodes read the directive, break it into tasks, assign roles, write code, run tests, argue over failures, recover, and push when the work passes.
|
||||
|
||||
That is the philosophy: **humans set direction; claws perform the labor.**
|
||||
That is the philosophy: **humans set direction; brewcodes perform the labor.**
|
||||
|
||||
## The Three-Part System
|
||||
|
||||
|
|
@ -37,8 +37,8 @@ It turns short directives into structured execution:
|
|||
|
||||
This is the layer that converts a sentence into a repeatable work protocol.
|
||||
|
||||
### 2. clawhip
|
||||
[clawhip](https://github.com/Yeachan-Heo/clawhip) is the event and notification router.
|
||||
### 2. brewcodehip
|
||||
[brewcodehip](https://github.com/Yeachan-Heo/brewcodehip) is the event and notification router.
|
||||
|
||||
It watches:
|
||||
- git commits
|
||||
|
|
@ -70,12 +70,12 @@ When agent systems can rebuild a codebase in hours, the scarce resource becomes:
|
|||
|
||||
A fast agent team does not remove the need for thinking. It makes clear thinking even more valuable.
|
||||
|
||||
## What Claw Code Demonstrates
|
||||
## What Brew Code Demonstrates
|
||||
|
||||
Claw Code demonstrates that a repository can be:
|
||||
Brew Code demonstrates that a repository can be:
|
||||
|
||||
- **autonomously built in public**
|
||||
- coordinated by claws/lobsters rather than human pair-programming alone
|
||||
- coordinated by brewcodes/lobsters rather than human pair-programming alone
|
||||
- operated through a chat interface
|
||||
- continuously improved by structured planning/execution/review loops
|
||||
- maintained as a showcase of the coordination layer, not just the output files
|
||||
|
|
@ -100,10 +100,10 @@ The job of the human is to decide what deserves to exist.
|
|||
|
||||
## Short Version
|
||||
|
||||
**Claw Code is a demo of autonomous software development.**
|
||||
**Brew Code is a demo of autonomous software development.**
|
||||
|
||||
Humans provide direction.
|
||||
Claws coordinate, build, test, recover, and push.
|
||||
Brewcodes coordinate, build, test, recover, and push.
|
||||
The repository is the artifact.
|
||||
The philosophy is the system behind it.
|
||||
|
||||
|
|
|
|||
84
README.md
84
README.md
|
|
@ -1,4 +1,4 @@
|
|||
# Claw Code
|
||||
# Brew Code
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/ultraworkers/claw-code">ultraworkers/claw-code</a>
|
||||
|
|
@ -29,20 +29,20 @@
|
|||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/claw-hero.jpeg" alt="Claw Code" width="300" />
|
||||
<img src="assets/brewcode-hero.jpeg" alt="Brew Code" width="300" />
|
||||
</p>
|
||||
|
||||
Claw Code is the public Rust implementation of the `claw` CLI agent harness.
|
||||
Brew Code is the public Rust implementation of the `brewcode` CLI agent harness.
|
||||
The canonical implementation lives in [`rust/`](./rust), and the current source of truth for this repository is **ultraworkers/claw-code**.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Start with [`USAGE.md`](./USAGE.md) for build, auth, CLI, session, and parity-harness workflows. For file submission/navigation questions, see [Navigation and file context](./docs/navigation-file-context.md). For local OpenAI-compatible models and offline skill installs, see [Local OpenAI-compatible providers and skills setup](./docs/local-openai-compatible-providers.md). Windows users can jump to the PowerShell-first [Windows install and release quickstart](./docs/windows-install-release.md). Make `claw doctor` your first health check after building, use [`rust/README.md`](./rust/README.md) for crate-level details, read [`PARITY.md`](./PARITY.md) for the current Rust-port checkpoint, and see [`docs/container.md`](./docs/container.md) for the container-first workflow.
|
||||
> Start with [`USAGE.md`](./USAGE.md) for build, auth, CLI, session, and parity-harness workflows. For file submission/navigation questions, see [Navigation and file context](./docs/navigation-file-context.md). For local OpenAI-compatible models and offline skill installs, see [Local OpenAI-compatible providers and skills setup](./docs/local-openai-compatible-providers.md). Windows users can jump to the PowerShell-first [Windows install and release quickstart](./docs/windows-install-release.md). Make `brewcode doctor` your first health check after building, use [`rust/README.md`](./rust/README.md) for crate-level details, read [`PARITY.md`](./PARITY.md) for the current Rust-port checkpoint, and see [`docs/container.md`](./docs/container.md) for the container-first workflow.
|
||||
>
|
||||
> **ACP / Zed status:** `claw-code` does not ship an ACP/Zed daemon or JSON-RPC entrypoint yet. Run `claw acp` (or `claw --acp`) for the current status instead of guessing from source layout; `claw acp serve` is currently a discoverability alias only, returns status with exit code 0, and real ACP support remains tracked separately in `ROADMAP.md`. For the public JSON contract, see [`docs/g011-acp-json-rpc-status-contract.md`](./docs/g011-acp-json-rpc-status-contract.md).
|
||||
> **ACP / Zed status:** `brew-code` does not ship an ACP/Zed daemon or JSON-RPC entrypoint yet. Run `brewcode acp` (or `brewcode --acp`) for the current status instead of guessing from source layout; `brewcode acp serve` is currently a discoverability alias only, returns status with exit code 0, and real ACP support remains tracked separately in `ROADMAP.md`. For the public JSON contract, see [`docs/g011-acp-json-rpc-status-contract.md`](./docs/g011-acp-json-rpc-status-contract.md).
|
||||
|
||||
## Current repository shape
|
||||
|
||||
- **`rust/`** — canonical Rust workspace and the `claw` CLI binary
|
||||
- **`rust/`** — canonical Rust workspace and the `brewcode` 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
|
||||
|
|
@ -53,7 +53,7 @@ The canonical implementation lives in [`rust/`](./rust), and the current source
|
|||
|
||||
> [!NOTE]
|
||||
> [!WARNING]
|
||||
> **`cargo install claw-code` installs the wrong thing.** The `claw-code` crate on crates.io is a deprecated stub that places `claw-code-deprecated.exe` — not `claw`. Running it only prints `"claw-code has been renamed to agent-code"`. **Do not use `cargo install claw-code`.** Either build from source (this repo) or install the upstream binary:
|
||||
> **`cargo install brew-code` installs the wrong thing.** The `brew-code` crate on crates.io is a deprecated stub that places `brew-code-deprecated.exe` — not `brewcode`. Running it only prints `"brew-code has been renamed to agent-code"`. **Do not use `cargo install brew-code`.** Either build from source (this repo) or install the upstream binary:
|
||||
> ```bash
|
||||
> cargo install agent-code # upstream binary — installs 'agent.exe' (Windows) / 'agent' (Unix), NOT 'agent-code'
|
||||
> ```
|
||||
|
|
@ -62,21 +62,21 @@ The canonical implementation lives in [`rust/`](./rust), and the current source
|
|||
```bash
|
||||
# 1. Clone and build
|
||||
git clone https://github.com/ultraworkers/claw-code
|
||||
cd claw-code/rust
|
||||
cd brew-code/rust
|
||||
cargo build --workspace
|
||||
|
||||
# 2. Set your API key (Anthropic API key — not a Claude subscription)
|
||||
export ANTHROPIC_API_KEY="sk-ant-..."
|
||||
|
||||
# 3. Verify everything is wired correctly
|
||||
./target/debug/claw doctor
|
||||
./target/debug/brewcode doctor
|
||||
|
||||
# 4. Run a prompt
|
||||
./target/debug/claw prompt "say hello"
|
||||
./target/debug/brewcode prompt "say hello"
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> **Windows (PowerShell):** the binary is `claw.exe`, not `claw`. Use `.\target\debug\claw.exe` or run `cargo run -- prompt "say hello"` to skip the path lookup.
|
||||
> **Windows (PowerShell):** the binary is `brewcode.exe`, not `brewcode`. Use `.\target\debug\brewcode.exe` or run `cargo run -- prompt "say hello"` to skip the path lookup.
|
||||
|
||||
### Windows setup
|
||||
|
||||
|
|
@ -91,13 +91,13 @@ export ANTHROPIC_API_KEY="sk-ant-..."
|
|||
3. **Clone and build** (works in PowerShell, Git Bash, or WSL):
|
||||
```powershell
|
||||
git clone https://github.com/ultraworkers/claw-code
|
||||
cd claw-code/rust
|
||||
cd brew-code/rust
|
||||
cargo build --workspace
|
||||
```
|
||||
4. **Run** (PowerShell — note `.exe` and backslash):
|
||||
```powershell
|
||||
$env:ANTHROPIC_API_KEY = "sk-ant-..."
|
||||
.\target\debug\claw.exe prompt "say hello"
|
||||
.\target\debug\brewcode.exe prompt "say hello"
|
||||
```
|
||||
|
||||
For release ZIPs, PATH setup, provider switching, and notification smoke checks, see [`docs/windows-install-release.md`](./docs/windows-install-release.md).
|
||||
|
|
@ -106,19 +106,19 @@ For release ZIPs, PATH setup, provider switching, and notification smoke checks,
|
|||
|
||||
## Post-build: locate the binary and verify
|
||||
|
||||
After running `cargo build --workspace`, the `claw` binary is built but **not** automatically installed to your system. Here's where to find it and how to verify the build succeeded.
|
||||
After running `cargo build --workspace`, the `brewcode` binary is built but **not** automatically installed to your system. Here's where to find it and how to verify the build succeeded.
|
||||
|
||||
### Binary location
|
||||
|
||||
After `cargo build --workspace` in `claw-code/rust/`:
|
||||
After `cargo build --workspace` in `brew-code/rust/`:
|
||||
|
||||
**Debug build (default, faster compile):**
|
||||
- **macOS/Linux:** `rust/target/debug/claw`
|
||||
- **Windows:** `rust/target/debug/claw.exe`
|
||||
- **macOS/Linux:** `rust/target/debug/brewcode`
|
||||
- **Windows:** `rust/target/debug/brewcode.exe`
|
||||
|
||||
**Release build (optimized, slower compile):**
|
||||
- **macOS/Linux:** `rust/target/release/claw`
|
||||
- **Windows:** `rust/target/release/claw.exe`
|
||||
- **macOS/Linux:** `rust/target/release/brewcode`
|
||||
- **Windows:** `rust/target/release/brewcode.exe`
|
||||
|
||||
If you ran `cargo build` without `--release`, the binary is in the `debug/` folder.
|
||||
|
||||
|
|
@ -128,50 +128,50 @@ Test the binary directly using its path:
|
|||
|
||||
```bash
|
||||
# macOS/Linux (debug build)
|
||||
./rust/target/debug/claw --help
|
||||
./rust/target/debug/claw doctor
|
||||
./rust/target/debug/brewcode --help
|
||||
./rust/target/debug/brewcode doctor
|
||||
|
||||
# Windows PowerShell (debug build)
|
||||
.\rust\target\debug\claw.exe --help
|
||||
.\rust\target\debug\claw.exe doctor
|
||||
.\rust\target\debug\brewcode.exe --help
|
||||
.\rust\target\debug\brewcode.exe doctor
|
||||
```
|
||||
|
||||
PowerShell smoke commands that do not require live credentials:
|
||||
|
||||
```powershell
|
||||
$env:CLAW_CONFIG_HOME = Join-Path $env:TEMP "claw config home"
|
||||
New-Item -ItemType Directory -Force -Path $env:CLAW_CONFIG_HOME | Out-Null
|
||||
$env:BREWCODE_CONFIG_HOME = Join-Path $env:TEMP "brewcode config home"
|
||||
New-Item -ItemType Directory -Force -Path $env:BREWCODE_CONFIG_HOME | Out-Null
|
||||
Remove-Item Env:\ANTHROPIC_API_KEY, Env:\ANTHROPIC_AUTH_TOKEN, Env:\OPENAI_API_KEY -ErrorAction SilentlyContinue
|
||||
.\rust\target\debug\claw.exe help
|
||||
.\rust\target\debug\claw.exe status
|
||||
.\rust\target\debug\claw.exe config env
|
||||
.\rust\target\debug\claw.exe doctor
|
||||
.\rust\target\debug\brewcode.exe help
|
||||
.\rust\target\debug\brewcode.exe status
|
||||
.\rust\target\debug\brewcode.exe config env
|
||||
.\rust\target\debug\brewcode.exe doctor
|
||||
```
|
||||
|
||||
If these commands succeed, the build is working. `claw doctor` is your first health check — it validates your API key, model access, and tool configuration.
|
||||
If these commands succeed, the build is working. `brewcode doctor` is your first health check — it validates your API key, model access, and tool configuration.
|
||||
|
||||
### Optional: Add to PATH
|
||||
|
||||
If you want to run `claw` from any directory without the full path, choose one of these approaches:
|
||||
If you want to run `brewcode` from any directory without the full path, choose one of these approaches:
|
||||
|
||||
**Option 1: Symlink (macOS/Linux)**
|
||||
```bash
|
||||
ln -s $(pwd)/rust/target/debug/claw /usr/local/bin/claw
|
||||
ln -s $(pwd)/rust/target/debug/brewcode /usr/local/bin/brewcode
|
||||
```
|
||||
Then reload your shell and test:
|
||||
```bash
|
||||
claw --help
|
||||
brewcode --help
|
||||
```
|
||||
|
||||
**Option 2: Use `cargo install` (all platforms)**
|
||||
|
||||
Build and install to Cargo's default location (`~/.cargo/bin/`, which is usually on PATH):
|
||||
```bash
|
||||
# From the claw-code/rust/ directory
|
||||
# From the brew-code/rust/ directory
|
||||
cargo install --path . --force
|
||||
|
||||
# Then from anywhere
|
||||
claw --help
|
||||
brewcode --help
|
||||
```
|
||||
|
||||
**Option 3: Update shell profile (bash/zsh)**
|
||||
|
|
@ -184,17 +184,17 @@ export PATH="$(pwd)/rust/target/debug:$PATH"
|
|||
Reload your shell:
|
||||
```bash
|
||||
source ~/.bashrc # or source ~/.zshrc
|
||||
claw --help
|
||||
brewcode --help
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
- **"command not found: claw"** — The binary is in `rust/target/debug/claw`, but it's not on your PATH. Use the full path `./rust/target/debug/claw` or symlink/install as above.
|
||||
- **"permission denied"** — On macOS/Linux, you may need `chmod +x rust/target/debug/claw` if the executable bit isn't set (rare).
|
||||
- **"command not found: brewcode"** — The binary is in `rust/target/debug/brewcode`, but it's not on your PATH. Use the full path `./rust/target/debug/brewcode` or symlink/install as above.
|
||||
- **"permission denied"** — On macOS/Linux, you may need `chmod +x rust/target/debug/brewcode` if the executable bit isn't set (rare).
|
||||
- **Debug vs. release** — If the build is slow, you're in debug mode (default). Add `--release` to `cargo build` for faster runtime, but the build itself will take 5–10 minutes.
|
||||
|
||||
> [!NOTE]
|
||||
> **Auth:** claw requires an **API key** (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.) — Claude subscription login is not a supported auth path.
|
||||
> **Auth:** brewcode requires an **API key** (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.) — Claude subscription login is not a supported auth path.
|
||||
|
||||
Run the workspace test suite after verifying the binary works:
|
||||
|
||||
|
|
@ -207,7 +207,7 @@ cargo test --workspace
|
|||
|
||||
- [`USAGE.md`](./USAGE.md) — quick commands, auth, sessions, config, parity harness
|
||||
- [`docs/navigation-file-context.md`](./docs/navigation-file-context.md) — terminal navigation, scrollback, `@path` file context, attachments, and secret-safety guidance
|
||||
- [`docs/local-openai-compatible-providers.md`](./docs/local-openai-compatible-providers.md) — Ollama/llama.cpp/vLLM setup, Claw multi-provider positioning, and local skills install checks
|
||||
- [`docs/local-openai-compatible-providers.md`](./docs/local-openai-compatible-providers.md) — Ollama/llama.cpp/vLLM setup, Brewcode multi-provider positioning, and local skills install checks
|
||||
- [`docs/windows-install-release.md`](./docs/windows-install-release.md) — PowerShell-first install, release artifact, provider switching, and Windows/WSL notification smoke paths
|
||||
- [`rust/README.md`](./rust/README.md) — crate map, CLI surface, features, workspace layout
|
||||
- [`PARITY.md`](./PARITY.md) — parity status for the Rust port
|
||||
|
|
@ -220,9 +220,9 @@ cargo test --workspace
|
|||
|
||||
## Ecosystem
|
||||
|
||||
Claw Code is built in the open alongside the broader UltraWorkers toolchain:
|
||||
Brew Code is built in the open alongside the broader UltraWorkers toolchain:
|
||||
|
||||
- [clawhip](https://github.com/Yeachan-Heo/clawhip)
|
||||
- [brewcodehip](https://github.com/Yeachan-Heo/brewcodehip)
|
||||
- [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)
|
||||
|
|
|
|||
2716
ROADMAP.md
2716
ROADMAP.md
File diff suppressed because one or more lines are too long
|
|
@ -39,7 +39,7 @@ Out of scope:
|
|||
- social engineering;
|
||||
- denial-of-service without a practical security impact;
|
||||
- issues that require already-compromised local developer credentials;
|
||||
- reports against third-party providers or upstream tools without a Claw Code
|
||||
- reports against third-party providers or upstream tools without a Brew Code
|
||||
integration issue.
|
||||
|
||||
## Handling expectations
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ Use the lightest support path that fits the request:
|
|||
When asking for help, include:
|
||||
|
||||
```text
|
||||
claw --version
|
||||
claw doctor
|
||||
brewcode --version
|
||||
brewcode doctor
|
||||
operating system and shell
|
||||
command you ran
|
||||
```
|
||||
|
|
|
|||
146
USAGE.md
146
USAGE.md
|
|
@ -1,6 +1,6 @@
|
|||
# Claw Code Usage
|
||||
# Brew Code Usage
|
||||
|
||||
This guide covers the current Rust workspace under `rust/` and the `claw` CLI binary. If you are brand new, make the doctor health check your first run: start `claw`, then run `/doctor`.
|
||||
This guide covers the current Rust workspace under `rust/` and the `brewcode` CLI binary. If you are brand new, make the doctor health check your first run: start `brewcode`, then run `/doctor`.
|
||||
|
||||
## Quick-start health check
|
||||
|
||||
|
|
@ -9,12 +9,12 @@ Run this before prompts, sessions, or automation:
|
|||
```bash
|
||||
cd rust
|
||||
cargo build --workspace
|
||||
./target/debug/claw
|
||||
./target/debug/brewcode
|
||||
# first command inside the REPL
|
||||
/doctor
|
||||
```
|
||||
|
||||
`/doctor` is the built-in setup and preflight diagnostic. Once you have a saved session, you can rerun it with `./target/debug/claw --resume latest /doctor`.
|
||||
`/doctor` is the built-in setup and preflight diagnostic. Once you have a saved session, you can rerun it with `./target/debug/brewcode --resume latest /doctor`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ cd rust
|
|||
cargo build --workspace
|
||||
```
|
||||
|
||||
The CLI binary is available at `rust/target/debug/claw` after a debug build (`rust\target\debug\claw.exe` on Windows). Make the doctor check above your first post-build step. For PowerShell-first install, release ZIP, PATH, provider-switching, and Windows/WSL notification examples, see [`docs/windows-install-release.md`](./docs/windows-install-release.md).
|
||||
The CLI binary is available at `rust/target/debug/brewcode` after a debug build (`rust\target\debug\brewcode.exe` on Windows). Make the doctor check above your first post-build step. For PowerShell-first install, release ZIP, PATH, provider-switching, and Windows/WSL notification examples, see [`docs/windows-install-release.md`](./docs/windows-install-release.md).
|
||||
|
||||
## Quick start
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ The CLI binary is available at `rust/target/debug/claw` after a debug build (`ru
|
|||
|
||||
```bash
|
||||
cd rust
|
||||
./target/debug/claw
|
||||
./target/debug/brewcode
|
||||
/doctor
|
||||
```
|
||||
|
||||
|
|
@ -47,87 +47,87 @@ Or run doctor directly with JSON output for scripting:
|
|||
|
||||
```bash
|
||||
cd rust
|
||||
./target/debug/claw doctor --output-format json
|
||||
./target/debug/brewcode doctor --output-format json
|
||||
```
|
||||
|
||||
**Note:** Diagnostic verbs (`doctor`, `status`, `sandbox`, `version`) support `--output-format json` for machine-readable output. Invalid suffix arguments (e.g., `--json`) are now rejected at parse time rather than falling through to prompt dispatch.
|
||||
|
||||
### Initialize a repository
|
||||
|
||||
Set up a new repository with `.claw` config, `.claw.json`, `.gitignore` entries, and a `CLAUDE.md` guidance file:
|
||||
Set up a new repository with `.brewcode` config, `.brewcode.json`, `.gitignore` entries, and a `CLAUDE.md` guidance file:
|
||||
|
||||
```bash
|
||||
cd /path/to/your/repo
|
||||
./target/debug/claw init
|
||||
./target/debug/brewcode init
|
||||
```
|
||||
|
||||
Text mode (human-readable) shows artifact creation summary with project path and next steps. Idempotent — running multiple times in the same repo marks already-created files as "skipped".
|
||||
|
||||
JSON mode for scripting:
|
||||
```bash
|
||||
./target/debug/claw init --output-format json
|
||||
./target/debug/brewcode init --output-format json
|
||||
```
|
||||
|
||||
Returns structured output with `project_path`, `created[]`, `updated[]`, `skipped[]` arrays (one per artifact), and `artifacts[]` carrying each file's `name` and machine-stable `status` tag. The legacy `message` field preserves backward compatibility.
|
||||
|
||||
**Why structured fields matter:** Claws can detect per-artifact state (`created` vs `updated` vs `skipped`) without substring-matching human prose. Use the `created[]`, `updated[]`, and `skipped[]` arrays for conditional follow-up logic (e.g., only commit if files were actually created, not just updated).
|
||||
**Why structured fields matter:** Brewcodes can detect per-artifact state (`created` vs `updated` vs `skipped`) without substring-matching human prose. Use the `created[]`, `updated[]`, and `skipped[]` arrays for conditional follow-up logic (e.g., only commit if files were actually created, not just updated).
|
||||
|
||||
### Interactive REPL
|
||||
|
||||
```bash
|
||||
cd rust
|
||||
./target/debug/claw
|
||||
./target/debug/brewcode
|
||||
```
|
||||
|
||||
### One-shot prompt
|
||||
|
||||
```bash
|
||||
cd rust
|
||||
./target/debug/claw prompt "summarize this repository"
|
||||
./target/debug/brewcode prompt "summarize this repository"
|
||||
```
|
||||
|
||||
### Shorthand prompt mode
|
||||
|
||||
```bash
|
||||
cd rust
|
||||
./target/debug/claw "explain rust/crates/runtime/src/lib.rs"
|
||||
./target/debug/brewcode "explain rust/crates/runtime/src/lib.rs"
|
||||
```
|
||||
|
||||
### JSON output for scripting
|
||||
|
||||
```bash
|
||||
cd rust
|
||||
./target/debug/claw --output-format json prompt "status"
|
||||
./target/debug/brewcode --output-format json prompt "status"
|
||||
```
|
||||
|
||||
### Inspect worker state
|
||||
|
||||
The `claw state` command reads `.claw/worker-state.json`, which is written by the interactive REPL or a one-shot prompt when a worker executes a task. This file contains the worker ID, session reference, model, and permission mode.
|
||||
The `brewcode state` command reads `.brewcode/worker-state.json`, which is written by the interactive REPL or a one-shot prompt when a worker executes a task. This file contains the worker ID, session reference, model, and permission mode.
|
||||
|
||||
Prerequisite: You must run `claw` (interactive REPL) or `claw prompt <text>` at least once in the repository to produce the worker state file.
|
||||
Prerequisite: You must run `brewcode` (interactive REPL) or `brewcode prompt <text>` at least once in the repository to produce the worker state file.
|
||||
|
||||
```bash
|
||||
cd rust
|
||||
./target/debug/claw state
|
||||
./target/debug/brewcode state
|
||||
```
|
||||
|
||||
JSON mode:
|
||||
```bash
|
||||
./target/debug/claw state --output-format json
|
||||
./target/debug/brewcode state --output-format json
|
||||
```
|
||||
|
||||
If you run `claw state` before any worker has executed, you will see a helpful error:
|
||||
If you run `brewcode state` before any worker has executed, you will see a helpful error:
|
||||
```
|
||||
error: no worker state file found at .claw/worker-state.json
|
||||
error: no worker state file found at .brewcode/worker-state.json
|
||||
Hint: worker state is written by the interactive REPL or a non-interactive prompt.
|
||||
Run: claw # start the REPL (writes state on first turn)
|
||||
Or: claw prompt <text> # run one non-interactive turn
|
||||
Then rerun: claw state [--output-format json]
|
||||
Run: brewcode # start the REPL (writes state on first turn)
|
||||
Or: brewcode prompt <text> # run one non-interactive turn
|
||||
Then rerun: brewcode state [--output-format json]
|
||||
```
|
||||
|
||||
## Advanced slash commands (Interactive REPL only)
|
||||
|
||||
These commands are available inside the interactive REPL (`claw` with no args). They extend the assistant with workspace analysis, planning, and navigation features.
|
||||
These commands are available inside the interactive REPL (`brewcode` with no args). They extend the assistant with workspace analysis, planning, and navigation features.
|
||||
|
||||
### `/ultraplan` — Deep planning with multi-step reasoning
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ These commands are available inside the interactive REPL (`claw` with no args).
|
|||
|
||||
```bash
|
||||
# Start the REPL
|
||||
claw
|
||||
brewcode
|
||||
|
||||
# Inside the REPL
|
||||
/ultraplan refactor the auth module to use async/await
|
||||
|
|
@ -183,10 +183,10 @@ Output: A list of suspicious patterns with explanations (e.g., "unchecked unwrap
|
|||
|
||||
```bash
|
||||
cd rust
|
||||
./target/debug/claw --model sonnet prompt "review this diff"
|
||||
./target/debug/claw --permission-mode read-only prompt "summarize Cargo.toml"
|
||||
./target/debug/claw --permission-mode workspace-write prompt "update README.md"
|
||||
./target/debug/claw --allowedTools read,glob "inspect the runtime crate"
|
||||
./target/debug/brewcode --model sonnet prompt "review this diff"
|
||||
./target/debug/brewcode --permission-mode read-only prompt "summarize Cargo.toml"
|
||||
./target/debug/brewcode --permission-mode workspace-write prompt "update README.md"
|
||||
./target/debug/brewcode --allowedTools read,glob "inspect the runtime crate"
|
||||
```
|
||||
|
||||
Supported permission modes:
|
||||
|
|
@ -218,7 +218,7 @@ export ANTHROPIC_AUTH_TOKEN="anthropic-oauth-or-proxy-bearer-token"
|
|||
|
||||
### Which env var goes where
|
||||
|
||||
`claw` accepts two Anthropic credential env vars and they are **not interchangeable** — the HTTP header Anthropic expects differs per credential shape. Putting the wrong value in the wrong slot is the most common 401 we see.
|
||||
`brewcode` accepts two Anthropic credential env vars and they are **not interchangeable** — the HTTP header Anthropic expects differs per credential shape. Putting the wrong value in the wrong slot is the most common 401 we see.
|
||||
|
||||
| Credential shape | Env var | HTTP header | Typical source |
|
||||
|---|---|---|---|
|
||||
|
|
@ -226,41 +226,41 @@ export ANTHROPIC_AUTH_TOKEN="anthropic-oauth-or-proxy-bearer-token"
|
|||
| OAuth access token (opaque) | `ANTHROPIC_AUTH_TOKEN` | `Authorization: Bearer ...` | an Anthropic-compatible proxy or OAuth flow that mints bearer tokens |
|
||||
| OpenRouter key (`sk-or-v1-*`) | `OPENAI_API_KEY` + `OPENAI_BASE_URL=https://openrouter.ai/api/v1` | `Authorization: Bearer ...` | [openrouter.ai/keys](https://openrouter.ai/keys) |
|
||||
|
||||
**Why this matters:** if you paste an `sk-ant-*` key into `ANTHROPIC_AUTH_TOKEN`, Anthropic's API will return `401 Invalid bearer token` because `sk-ant-*` keys are rejected over the Bearer header. The fix is a one-line env var swap — move the key to `ANTHROPIC_API_KEY`. Recent `claw` builds detect this exact shape (401 + `sk-ant-*` in the Bearer slot) and append a hint to the error message pointing at the fix.
|
||||
**Why this matters:** if you paste an `sk-ant-*` key into `ANTHROPIC_AUTH_TOKEN`, Anthropic's API will return `401 Invalid bearer token` because `sk-ant-*` keys are rejected over the Bearer header. The fix is a one-line env var swap — move the key to `ANTHROPIC_API_KEY`. Recent `brewcode` builds detect this exact shape (401 + `sk-ant-*` in the Bearer slot) and append a hint to the error message pointing at the fix.
|
||||
|
||||
**If you meant a different provider:** if `claw` reports missing Anthropic credentials but you already have `OPENAI_API_KEY`, `XAI_API_KEY`, or `DASHSCOPE_API_KEY` exported, you most likely forgot to prefix the model name with the provider's routing prefix. Use `--model openai/gpt-4.1-mini` (OpenAI-compat / OpenRouter / Ollama), `--model grok` (xAI), or `--model qwen-plus` (DashScope) and the prefix router will select the right backend regardless of the ambient credentials. The error message now includes a hint that names the detected env var.
|
||||
**If you meant a different provider:** if `brewcode` reports missing Anthropic credentials but you already have `OPENAI_API_KEY`, `XAI_API_KEY`, or `DASHSCOPE_API_KEY` exported, you most likely forgot to prefix the model name with the provider's routing prefix. Use `--model openai/gpt-4.1-mini` (OpenAI-compat / OpenRouter / Ollama), `--model grok` (xAI), or `--model qwen-plus` (DashScope) and the prefix router will select the right backend regardless of the ambient credentials. The error message now includes a hint that names the detected env var.
|
||||
|
||||
|
||||
### Windows PowerShell provider switching
|
||||
|
||||
The same provider rules work in PowerShell. Use placeholder values in docs and tests; put real keys only in your private environment. Remove unrelated provider env vars when validating a switch so failures are easy to diagnose.
|
||||
|
||||
`CLAUDE_CODE_PROVIDER` is not required for normal Claw routing; prefer explicit model prefixes such as `openai/` and provider-specific env vars so PowerShell examples stay portable.
|
||||
`CLAUDE_CODE_PROVIDER` is not required for normal Brewcode routing; prefer explicit model prefixes such as `openai/` and provider-specific env vars so PowerShell examples stay portable.
|
||||
|
||||
```powershell
|
||||
# Anthropic direct
|
||||
$env:ANTHROPIC_API_KEY = "sk-ant-REPLACE_ME"
|
||||
Remove-Item Env:\OPENAI_BASE_URL -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:\OPENAI_API_KEY -ErrorAction SilentlyContinue
|
||||
.\target\debug\claw.exe --model "sonnet" prompt "reply with ready"
|
||||
.\target\debug\brewcode.exe --model "sonnet" prompt "reply with ready"
|
||||
|
||||
# OpenAI-compatible gateway / OpenRouter
|
||||
Remove-Item Env:\ANTHROPIC_API_KEY -ErrorAction SilentlyContinue
|
||||
$env:OPENAI_BASE_URL = "https://openrouter.ai/api/v1"
|
||||
$env:OPENAI_API_KEY = "sk-or-v1-REPLACE_ME"
|
||||
.\target\debug\claw.exe --model "openai/gpt-4.1-mini" prompt "reply with ready"
|
||||
.\target\debug\brewcode.exe --model "openai/gpt-4.1-mini" prompt "reply with ready"
|
||||
|
||||
# Local OpenAI-compatible server
|
||||
$env:OPENAI_BASE_URL = "http://127.0.0.1:11434/v1"
|
||||
Remove-Item Env:\OPENAI_API_KEY -ErrorAction SilentlyContinue
|
||||
.\target\debug\claw.exe --model "llama3.2" prompt "reply with ready"
|
||||
.\target\debug\brewcode.exe --model "llama3.2" prompt "reply with ready"
|
||||
```
|
||||
|
||||
See the full [Windows install and release quickstart](./docs/windows-install-release.md) for release artifact setup, persistent `setx` usage, and WSL notes.
|
||||
|
||||
## Local Models
|
||||
|
||||
`claw` can talk to local servers and provider gateways through either Anthropic-compatible or OpenAI-compatible endpoints. Use `ANTHROPIC_BASE_URL` with `ANTHROPIC_AUTH_TOKEN` for Anthropic-compatible services, or `OPENAI_BASE_URL` with `OPENAI_API_KEY` for OpenAI-compatible services. For copyable Ollama, llama.cpp, vLLM, raw `/v1/chat/completions`, and local skills install examples, see [`docs/local-openai-compatible-providers.md`](./docs/local-openai-compatible-providers.md).
|
||||
`brewcode` can talk to local servers and provider gateways through either Anthropic-compatible or OpenAI-compatible endpoints. Use `ANTHROPIC_BASE_URL` with `ANTHROPIC_AUTH_TOKEN` for Anthropic-compatible services, or `OPENAI_BASE_URL` with `OPENAI_API_KEY` for OpenAI-compatible services. For copyable Ollama, llama.cpp, vLLM, raw `/v1/chat/completions`, and local skills install examples, see [`docs/local-openai-compatible-providers.md`](./docs/local-openai-compatible-providers.md).
|
||||
|
||||
### Anthropic-compatible endpoint
|
||||
|
||||
|
|
@ -269,7 +269,7 @@ export ANTHROPIC_BASE_URL="http://127.0.0.1:8080"
|
|||
export ANTHROPIC_AUTH_TOKEN="local-dev-token"
|
||||
|
||||
cd rust
|
||||
./target/debug/claw --model "claude-sonnet-4-6" prompt "reply with the word ready"
|
||||
./target/debug/brewcode --model "claude-sonnet-4-6" prompt "reply with the word ready"
|
||||
```
|
||||
|
||||
### OpenAI-compatible endpoint
|
||||
|
|
@ -279,7 +279,7 @@ export OPENAI_BASE_URL="http://127.0.0.1:8000/v1"
|
|||
export OPENAI_API_KEY="local-dev-token"
|
||||
|
||||
cd rust
|
||||
./target/debug/claw --model "qwen2.5-coder" prompt "reply with the word ready"
|
||||
./target/debug/brewcode --model "qwen2.5-coder" prompt "reply with the word ready"
|
||||
```
|
||||
|
||||
### Ollama
|
||||
|
|
@ -289,7 +289,7 @@ export OPENAI_BASE_URL="http://127.0.0.1:11434/v1"
|
|||
unset OPENAI_API_KEY
|
||||
|
||||
cd rust
|
||||
./target/debug/claw --model "llama3.2" prompt "summarize this repository in one sentence"
|
||||
./target/debug/brewcode --model "llama3.2" prompt "summarize this repository in one sentence"
|
||||
```
|
||||
|
||||
### OpenRouter
|
||||
|
|
@ -299,7 +299,7 @@ export OPENAI_BASE_URL="https://openrouter.ai/api/v1"
|
|||
export OPENAI_API_KEY="sk-or-v1-..."
|
||||
|
||||
cd rust
|
||||
./target/debug/claw --model "openai/gpt-4.1-mini" prompt "summarize this repository in one sentence"
|
||||
./target/debug/brewcode --model "openai/gpt-4.1-mini" prompt "summarize this repository in one sentence"
|
||||
```
|
||||
|
||||
### Alibaba DashScope (Qwen)
|
||||
|
|
@ -310,9 +310,9 @@ For Qwen models via Alibaba's native DashScope API (higher rate limits than Open
|
|||
export DASHSCOPE_API_KEY="sk-..."
|
||||
|
||||
cd rust
|
||||
./target/debug/claw --model "qwen/qwen-max" prompt "hello"
|
||||
./target/debug/brewcode --model "qwen/qwen-max" prompt "hello"
|
||||
# or bare:
|
||||
./target/debug/claw --model "qwen-plus" prompt "hello"
|
||||
./target/debug/brewcode --model "qwen-plus" prompt "hello"
|
||||
```
|
||||
|
||||
Model names starting with `qwen/` or `qwen-` are automatically routed to the DashScope compatible-mode endpoint (`https://dashscope.aliyuncs.com/compatible-mode/v1`). You do **not** need to set `OPENAI_BASE_URL` or unset `ANTHROPIC_API_KEY` — the model prefix wins over the ambient credential sniffer.
|
||||
|
|
@ -321,7 +321,7 @@ Reasoning variants (`qwen-qwq-*`, `qwq-*`, `*-thinking`) automatically strip `te
|
|||
|
||||
## Supported Providers & Models
|
||||
|
||||
`claw` has three built-in provider backends. The provider is selected automatically based on the model name, falling back to whichever credential is present in the environment.
|
||||
`brewcode` has three built-in provider backends. The provider is selected automatically based on the model name, falling back to whichever credential is present in the environment.
|
||||
|
||||
### Provider matrix
|
||||
|
||||
|
|
@ -356,7 +356,7 @@ Any model name that does not match an alias is passed through verbatim after pro
|
|||
|
||||
### User-defined aliases
|
||||
|
||||
You can add custom aliases in any settings file (`~/.claw/settings.json`, `.claw/settings.json`, or `.claw/settings.local.json`):
|
||||
You can add custom aliases in any settings file (`~/.brewcode/settings.json`, `.brewcode/settings.json`, or `.brewcode/settings.local.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -377,7 +377,7 @@ Local project settings override user-level settings. Aliases resolve through the
|
|||
3. If it starts with `openai/` or `gpt-` → OpenAI-compatible.
|
||||
4. If it starts with `qwen/`, `qwen-`, `kimi/`, or `kimi-` → DashScope-compatible OpenAI wire format.
|
||||
5. If `OPENAI_BASE_URL` and `OPENAI_API_KEY` are set, unknown model names route to the OpenAI-compatible client for local/gateway servers.
|
||||
6. Otherwise, `claw` checks which credential is set: Anthropic first, then OpenAI, then xAI. If only `OPENAI_BASE_URL` is set, it still routes to OpenAI-compatible for authless local servers.
|
||||
6. Otherwise, `brewcode` checks which credential is set: Anthropic first, then OpenAI, then xAI. If only `OPENAI_BASE_URL` is set, it still routes to OpenAI-compatible for authless local servers.
|
||||
7. If nothing matches, it defaults to Anthropic.
|
||||
|
||||
|
||||
|
|
@ -389,27 +389,27 @@ For gateway features that are not first-class request fields yet, `MessageReques
|
|||
|
||||
## File context and navigation
|
||||
|
||||
Use `@path/to/file` in prompts to submit repository files as context, for example `Read @src/app.ts and explain the bug`, `Compare @old.md and @new.md`, or `Use @logs/error.txt as context and suggest a fix`. Prompt history, `Ctrl-r`, and long-output scrolling come from your shell, terminal, or tmux rather than from Claw itself. See [`docs/navigation-file-context.md`](./docs/navigation-file-context.md) for scrollback, attachment, and secret-redaction guidance.
|
||||
Use `@path/to/file` in prompts to submit repository files as context, for example `Read @src/app.ts and explain the bug`, `Compare @old.md and @new.md`, or `Use @logs/error.txt as context and suggest a fix`. Prompt history, `Ctrl-r`, and long-output scrolling come from your shell, terminal, or tmux rather than from Brewcode itself. See [`docs/navigation-file-context.md`](./docs/navigation-file-context.md) for scrollback, attachment, and secret-redaction guidance.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Is Claw Code Claude-only?
|
||||
### Is Brew Code Claude-only?
|
||||
|
||||
No. Claw Code is a Claude-Code-shaped workflow/runtime, not a Claude-only product. It can target Anthropic and OpenAI-compatible/provider-routed/local models depending on config. Non-Claude providers may require stricter response-shape and tool-call compatibility, so some workflows can be rougher than first-party Anthropic/OpenAI paths; provider-specific identity leaks are bugs, not product intent. See [`docs/local-openai-compatible-providers.md`](./docs/local-openai-compatible-providers.md) for local provider examples.
|
||||
No. Brew Code is a Claude-Code-shaped workflow/runtime, not a Claude-only product. It can target Anthropic and OpenAI-compatible/provider-routed/local models depending on config. Non-Claude providers may require stricter response-shape and tool-call compatibility, so some workflows can be rougher than first-party Anthropic/OpenAI paths; provider-specific identity leaks are bugs, not product intent. See [`docs/local-openai-compatible-providers.md`](./docs/local-openai-compatible-providers.md) for local provider examples.
|
||||
|
||||
### What about Codex?
|
||||
|
||||
The name "codex" appears in the Claw Code ecosystem but it does **not** refer to OpenAI Codex (the code-generation model). Here is what it means in this project:
|
||||
The name "codex" appears in the Brew Code ecosystem but it does **not** refer to OpenAI Codex (the code-generation model). Here is what it means in this project:
|
||||
|
||||
- **`oh-my-codex` (OmX)** is the workflow and plugin layer that sits on top of `claw`. It provides planning modes, parallel multi-agent execution, notification routing, and other automation features. See [PHILOSOPHY.md](./PHILOSOPHY.md) and the [oh-my-codex repo](https://github.com/Yeachan-Heo/oh-my-codex).
|
||||
- **`.codex/` directories** (e.g. `.codex/skills`, `.codex/agents`, `.codex/commands`) are legacy lookup paths that `claw` still scans alongside the primary `.claw/` directories.
|
||||
- **`oh-my-codex` (OmX)** is the workflow and plugin layer that sits on top of `brewcode`. It provides planning modes, parallel multi-agent execution, notification routing, and other automation features. See [PHILOSOPHY.md](./PHILOSOPHY.md) and the [oh-my-codex repo](https://github.com/Yeachan-Heo/oh-my-codex).
|
||||
- **`.codex/` directories** (e.g. `.codex/skills`, `.codex/agents`, `.codex/commands`) are legacy lookup paths that `brewcode` still scans alongside the primary `.brewcode/` directories.
|
||||
- **`CODEX_HOME`** is an optional environment variable that points to a custom root for user-level skill and command lookups.
|
||||
|
||||
`claw` does **not** support OpenAI Codex sessions, the Codex CLI, or Codex session import/export. If you need to use OpenAI models (like GPT-4.1), configure the OpenAI-compatible provider as shown above in the [OpenAI-compatible endpoint](#openai-compatible-endpoint) and [OpenRouter](#openrouter) sections.
|
||||
`brewcode` does **not** support OpenAI Codex sessions, the Codex CLI, or Codex session import/export. If you need to use OpenAI models (like GPT-4.1), configure the OpenAI-compatible provider as shown above in the [OpenAI-compatible endpoint](#openai-compatible-endpoint) and [OpenRouter](#openrouter) sections.
|
||||
|
||||
## HTTP proxy support
|
||||
|
||||
`claw` honours the standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables (both upper- and lower-case spellings are accepted) when issuing outbound requests to Anthropic, OpenAI-, and xAI-compatible endpoints. Set them before launching the CLI and the underlying `reqwest` client will be configured automatically.
|
||||
`brewcode` honours the standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables (both upper- and lower-case spellings are accepted) when issuing outbound requests to Anthropic, OpenAI-, and xAI-compatible endpoints. Set them before launching the CLI and the underlying `reqwest` client will be configured automatically.
|
||||
|
||||
### Environment variables
|
||||
|
||||
|
|
@ -419,7 +419,7 @@ export HTTP_PROXY="http://proxy.corp.example:3128"
|
|||
export NO_PROXY="localhost,127.0.0.1,.corp.example"
|
||||
|
||||
cd rust
|
||||
./target/debug/claw prompt "hello via the corporate proxy"
|
||||
./target/debug/brewcode prompt "hello via the corporate proxy"
|
||||
```
|
||||
|
||||
### Programmatic `proxy_url` config option
|
||||
|
|
@ -448,11 +448,11 @@ let client = build_http_client_with(&config).expect("proxy client");
|
|||
- `proxy_url` is a unified alternative: when set, it applies to both `http://` and `https://` destinations, overriding the per-scheme fields.
|
||||
- `NO_PROXY` accepts a comma-separated list of host suffixes (for example `.corp.example`) and IP literals.
|
||||
- Empty values are treated as unset, so leaving `HTTPS_PROXY=""` in your shell will not enable a proxy.
|
||||
- If a proxy URL cannot be parsed, `claw` falls back to a direct (no-proxy) client so existing workflows keep working; double-check the URL if you expected the request to be tunnelled.
|
||||
- If a proxy URL cannot be parsed, `brewcode` falls back to a direct (no-proxy) client so existing workflows keep working; double-check the URL if you expected the request to be tunnelled.
|
||||
|
||||
## Skills
|
||||
|
||||
Use `/skills list` in the interactive REPL or `claw skills --output-format json` from the direct CLI to inspect installed skills. For offline/local installs, install the directory that contains `SKILL.md`, then verify the discovered name before invoking it:
|
||||
Use `/skills list` in the interactive REPL or `brewcode skills --output-format json` from the direct CLI to inspect installed skills. For offline/local installs, install the directory that contains `SKILL.md`, then verify the discovered name before invoking it:
|
||||
|
||||
```text
|
||||
/skills install /absolute/path/to/my-skill
|
||||
|
|
@ -460,28 +460,28 @@ Use `/skills list` in the interactive REPL or `claw skills --output-format json`
|
|||
/skills my-skill
|
||||
```
|
||||
|
||||
If install succeeds but invocation fails with a provider HTTP error, treat provider setup separately: run `claw doctor` and a one-shot prompt smoke test before reinstalling the skill. See [`docs/local-openai-compatible-providers.md`](./docs/local-openai-compatible-providers.md#local-skills-install-from-disk) for the full checklist.
|
||||
If install succeeds but invocation fails with a provider HTTP error, treat provider setup separately: run `brewcode doctor` and a one-shot prompt smoke test before reinstalling the skill. See [`docs/local-openai-compatible-providers.md`](./docs/local-openai-compatible-providers.md#local-skills-install-from-disk) for the full checklist.
|
||||
|
||||
## Common operational commands
|
||||
|
||||
```bash
|
||||
cd rust
|
||||
./target/debug/claw status
|
||||
./target/debug/claw sandbox
|
||||
./target/debug/claw agents
|
||||
./target/debug/claw mcp
|
||||
./target/debug/claw skills
|
||||
./target/debug/claw system-prompt --cwd .. --date 2026-04-04
|
||||
./target/debug/brewcode status
|
||||
./target/debug/brewcode sandbox
|
||||
./target/debug/brewcode agents
|
||||
./target/debug/brewcode mcp
|
||||
./target/debug/brewcode skills
|
||||
./target/debug/brewcode system-prompt --cwd .. --date 2026-04-04
|
||||
```
|
||||
|
||||
## Session management
|
||||
|
||||
REPL turns are persisted under `.claw/sessions/` in the current workspace.
|
||||
REPL turns are persisted under `.brewcode/sessions/` in the current workspace.
|
||||
|
||||
```bash
|
||||
cd rust
|
||||
./target/debug/claw --resume latest
|
||||
./target/debug/claw --resume latest /status /diff
|
||||
./target/debug/brewcode --resume latest
|
||||
./target/debug/brewcode --resume latest /status /diff
|
||||
```
|
||||
|
||||
Useful interactive commands include `/help`, `/status`, `/cost`, `/config`, `/session`, `/model`, `/permissions`, and `/export`.
|
||||
|
|
@ -490,11 +490,11 @@ Useful interactive commands include `/help`, `/status`, `/cost`, `/config`, `/se
|
|||
|
||||
Runtime config is loaded in this order, with later entries overriding earlier ones:
|
||||
|
||||
1. `~/.claw.json`
|
||||
2. `~/.config/claw/settings.json`
|
||||
3. `<repo>/.claw.json`
|
||||
4. `<repo>/.claw/settings.json`
|
||||
5. `<repo>/.claw/settings.local.json`
|
||||
1. `~/.brewcode.json`
|
||||
2. `~/.config/brewcode/settings.json`
|
||||
3. `<repo>/.brewcode.json`
|
||||
4. `<repo>/.brewcode/settings.json`
|
||||
5. `<repo>/.brewcode/settings.local.json`
|
||||
|
||||
## Mock parity harness
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Use this checklist before spending engineering time on low-signal issues, genera
|
|||
| --- | --- | --- | --- |
|
||||
| `actionable-bug` | The report has a reproducible product failure. | Repro steps, failing test, logs with secrets removed, or matching roadmap item. | Fix, assign, or link to an existing fix. |
|
||||
| `actionable-docs` | The report identifies missing, stale, or confusing documentation. | Current doc path plus desired corrected source of truth. | Patch docs or link to the owning docs lane. |
|
||||
| `actionable-feature` | The request matches Claw Code direction and has a concrete acceptance shape. | Issue/PR link plus roadmap or maintainer rationale. | Defer to planning or implement if already scoped. |
|
||||
| `actionable-feature` | The request matches Brew Code direction and has a concrete acceptance shape. | Issue/PR link plus roadmap or maintainer rationale. | Defer to planning or implement if already scoped. |
|
||||
| `duplicate` | Another issue/PR already covers the same user-visible outcome. | Link the canonical issue/PR and note any extra evidence worth preserving. | Cross-link; close only with maintainer/owner policy. |
|
||||
| `spam-or-promotion` | The content is promotional, irrelevant, or abusive. | URL/title/body excerpt summary, not a full repost. | Label/close per repository policy. |
|
||||
| `generated-slop-or-hallucinated` | The change is broad, mechanically generated, unreviewable, or names APIs/files that do not exist. | Diff/path examples, missing symbols, or unverifiable claims. | Request a narrow repro or reject/defer with rationale. |
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# Container-first claw-code workflows
|
||||
# Container-first brew-code workflows
|
||||
|
||||
This repo already had **container detection** in the Rust runtime before this document was added:
|
||||
|
||||
- `rust/crates/runtime/src/sandbox.rs` detects Docker/Podman/container markers such as `/.dockerenv`, `/run/.containerenv`, matching env vars, and `/proc/1/cgroup` hints.
|
||||
- `rust/crates/rusty-claude-cli/src/main.rs` exposes that state through the `claw sandbox` / `cargo run -p rusty-claude-cli -- sandbox` report.
|
||||
- `rust/crates/rusty-claude-cli/src/main.rs` exposes that state through the `brewcode sandbox` / `cargo run -p rusty-claude-cli -- sandbox` report.
|
||||
- `.github/workflows/rust-ci.yml` runs on `ubuntu-latest`, but it does **not** define a Docker or Podman container job.
|
||||
- Before this change, the repo did **not** have a checked-in `Dockerfile`, `Containerfile`, or `.devcontainer/` config.
|
||||
|
||||
|
|
@ -22,13 +22,13 @@ From the repository root:
|
|||
### Docker
|
||||
|
||||
```bash
|
||||
docker build -t claw-code-dev -f Containerfile .
|
||||
docker build -t brew-code-dev -f Containerfile .
|
||||
```
|
||||
|
||||
### Podman
|
||||
|
||||
```bash
|
||||
podman build -t claw-code-dev -f Containerfile .
|
||||
podman build -t brew-code-dev -f Containerfile .
|
||||
```
|
||||
|
||||
## Run `cargo test --workspace` in the container
|
||||
|
|
@ -40,9 +40,9 @@ These commands mount the repo, keep Cargo build artifacts out of the working tre
|
|||
```bash
|
||||
docker run --rm -it \
|
||||
-v "$PWD":/workspace \
|
||||
-e CARGO_TARGET_DIR=/tmp/claw-target \
|
||||
-e CARGO_TARGET_DIR=/tmp/brewcode-target \
|
||||
-w /workspace/rust \
|
||||
claw-code-dev \
|
||||
brew-code-dev \
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
|
|
@ -51,9 +51,9 @@ docker run --rm -it \
|
|||
```bash
|
||||
podman run --rm -it \
|
||||
-v "$PWD":/workspace:Z \
|
||||
-e CARGO_TARGET_DIR=/tmp/claw-target \
|
||||
-e CARGO_TARGET_DIR=/tmp/brewcode-target \
|
||||
-w /workspace/rust \
|
||||
claw-code-dev \
|
||||
brew-code-dev \
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
|
|
@ -66,9 +66,9 @@ If you want a fully clean rebuild, add `cargo clean &&` before `cargo test --wor
|
|||
```bash
|
||||
docker run --rm -it \
|
||||
-v "$PWD":/workspace \
|
||||
-e CARGO_TARGET_DIR=/tmp/claw-target \
|
||||
-e CARGO_TARGET_DIR=/tmp/brewcode-target \
|
||||
-w /workspace/rust \
|
||||
claw-code-dev
|
||||
brew-code-dev
|
||||
```
|
||||
|
||||
### Podman
|
||||
|
|
@ -76,9 +76,9 @@ docker run --rm -it \
|
|||
```bash
|
||||
podman run --rm -it \
|
||||
-v "$PWD":/workspace:Z \
|
||||
-e CARGO_TARGET_DIR=/tmp/claw-target \
|
||||
-e CARGO_TARGET_DIR=/tmp/brewcode-target \
|
||||
-w /workspace/rust \
|
||||
claw-code-dev
|
||||
brew-code-dev
|
||||
```
|
||||
|
||||
Inside the shell:
|
||||
|
|
@ -94,7 +94,7 @@ The `sandbox` command is a useful sanity check: inside Docker or Podman it shoul
|
|||
|
||||
## Bind-mount this repo and another repo at the same time
|
||||
|
||||
If you want to run `claw` against a second checkout while keeping `claw-code` itself mounted read-write:
|
||||
If you want to run `brewcode` against a second checkout while keeping `brew-code` itself mounted read-write:
|
||||
|
||||
### Docker
|
||||
|
||||
|
|
@ -102,9 +102,9 @@ If you want to run `claw` against a second checkout while keeping `claw-code` it
|
|||
docker run --rm -it \
|
||||
-v "$PWD":/workspace \
|
||||
-v "$HOME/src/other-repo":/repo \
|
||||
-e CARGO_TARGET_DIR=/tmp/claw-target \
|
||||
-e CARGO_TARGET_DIR=/tmp/brewcode-target \
|
||||
-w /workspace/rust \
|
||||
claw-code-dev
|
||||
brew-code-dev
|
||||
```
|
||||
|
||||
### Podman
|
||||
|
|
@ -113,9 +113,9 @@ docker run --rm -it \
|
|||
podman run --rm -it \
|
||||
-v "$PWD":/workspace:Z \
|
||||
-v "$HOME/src/other-repo":/repo:Z \
|
||||
-e CARGO_TARGET_DIR=/tmp/claw-target \
|
||||
-e CARGO_TARGET_DIR=/tmp/brewcode-target \
|
||||
-w /workspace/rust \
|
||||
claw-code-dev
|
||||
brew-code-dev
|
||||
```
|
||||
|
||||
Then, for example:
|
||||
|
|
@ -128,5 +128,5 @@ cargo run -p rusty-claude-cli -- prompt "summarize /repo"
|
|||
|
||||
- Docker and Podman use the same checked-in `Containerfile`.
|
||||
- The `:Z` suffix in the Podman examples is for SELinux relabeling; keep it on Fedora/RHEL-class hosts.
|
||||
- Running with `CARGO_TARGET_DIR=/tmp/claw-target` avoids leaving container-owned `target/` artifacts in your bind-mounted checkout.
|
||||
- Running with `CARGO_TARGET_DIR=/tmp/brewcode-target` avoids leaving container-owned `target/` artifacts in your bind-mounted checkout.
|
||||
- For non-container local development, keep using [`../USAGE.md`](../USAGE.md) and [`../rust/README.md`](../rust/README.md).
|
||||
|
|
|
|||
|
|
@ -180,6 +180,6 @@ FAIL / integration blockers observed on this worktree:
|
|||
- `rust/crates/runtime/src/policy_engine.rs:5`: `clippy::duration-suboptimal-units`.
|
||||
- `rust/crates/runtime/src/sandbox.rs:295-302`: `clippy::map_unwrap_or`.
|
||||
- `(cd rust && cargo test --workspace)` failed after broad success in API/commands/plugins/runtime tests because `rusty-claude-cli` unit test `tests::session_lifecycle_prefers_running_process_over_idle_shell` asserted `RunningProcess` but observed `IdleShell`.
|
||||
- Rerun of the specific failing test confirmed deterministic failure: `(cd rust && cargo test -p rusty-claude-cli --bin claw tests::session_lifecycle_prefers_running_process_over_idle_shell -- --exact --nocapture)` → 0 passed, 1 failed with the same `IdleShell` vs `RunningProcess` assertion.
|
||||
- Rerun of the specific failing test confirmed deterministic failure: `(cd rust && cargo test -p rusty-claude-cli --bin brewcode tests::session_lifecycle_prefers_running_process_over_idle_shell -- --exact --nocapture)` → 0 passed, 1 failed with the same `IdleShell` vs `RunningProcess` assertion.
|
||||
|
||||
Recommended owner for failures: not `worker-4` unless re-scoped. These failures are outside the docs/report artifact and touch shared runtime/CLI implementation files.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ Generated by `worker-1` for OMX team task 2 on 2026-05-14.
|
|||
- Lifecycle states currently covered in code: `spawning`, `trust_required`, `tool_permission_required`, `ready_for_prompt`, `running`, `finished`, and `failed`.
|
||||
- Prompt delivery semantics currently use `Running` events and fields `prompt_in_flight`, `last_prompt`, `expected_receipt`, `replay_prompt`, and `prompt_delivery_attempts`.
|
||||
- Startup-no-evidence surface: `observe_startup_timeout` builds `StartupEvidenceBundle` and classifies trust, tool permission, prompt acceptance timeout, prompt misdelivery, transport death, worker crash, or unknown.
|
||||
- File observability surface: `emit_state_file` writes `.claw/worker-state.json` with status, readiness, trust state, prompt-in-flight flag, last event, and update age.
|
||||
- File observability surface: `emit_state_file` writes `.brewcode/worker-state.json` with status, readiness, trust state, prompt-in-flight flag, last event, and update age.
|
||||
|
||||
- `rust/crates/tools/src/lib.rs`
|
||||
- Tool APIs expose the worker control plane through `WorkerCreate`, `WorkerGet`, `WorkerObserve`, `WorkerResolveTrust`, `WorkerAwaitReady`, `WorkerSendPrompt`, `WorkerRestart`, `WorkerTerminate`, and `WorkerObserveCompletion`.
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Goal: `G006-task-policy-board` — Stream 4 task packets, executable policy engi
|
|||
| Policy decisions explainable and typed-event logged/emittable | `PolicyDecisionEvent` serializable typed event with `rule_name`, `priority`, `kind`, `explanation`, `approval_token_id`; `evaluate_with_events` emits event per flattened action. |
|
||||
| Active lane board/dashboard/status JSON over canonical state | `rust/crates/runtime/src/task_registry.rs` adds `LaneBoard`, `LaneBoardEntry`, `LaneFreshness`, `lane_board_at`, and `lane_status_json_at`; CLI status JSON advertises lane board contract in `rust/crates/rusty-claude-cli/src/main.rs`. |
|
||||
| Heartbeats independent of terminal rendering with healthy/stalled/transport-dead cases | `rust/crates/runtime/src/session.rs` adds `SessionHeartbeat`/`SessionLiveness` from persisted session health state; `task_registry.rs` heartbeat freshness is computed from canonical heartbeat timestamps and transport state. |
|
||||
| Task/lane status JSON shows active/blocked/finished lanes with heartbeat freshness | `task_registry::tests::lane_board_groups_active_blocked_finished_and_reports_freshness`; `status_json_surfaces_session_lifecycle_for_clawhip`/status JSON surfaces lane board metadata. |
|
||||
| Task/lane status JSON shows active/blocked/finished lanes with heartbeat freshness | `task_registry::tests::lane_board_groups_active_blocked_finished_and_reports_freshness`; `status_json_surfaces_session_lifecycle_for_brewcodehip`/status JSON surfaces lane board metadata. |
|
||||
| Leader-owned ultragoal audit remains separate from workers | No worker changed `.omx/ultragoal`; leader will checkpoint with fresh `get_goal` only after terminal verification. |
|
||||
|
||||
## Verification run
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Scope: worker-2 follow-up map for W4 mock integration and regression verificatio
|
|||
| Area | Artifact/evidence |
|
||||
| --- | --- |
|
||||
| Deterministic model server | `rust/crates/mock-anthropic-service/src/lib.rs` implements the Anthropic-compatible mock server and scenario router used by CLI parity tests. |
|
||||
| End-to-end CLI mock harness | `rust/crates/rusty-claude-cli/tests/mock_parity_harness.rs` starts the mock server, runs clean-environment `claw` commands, asserts JSON output, and optionally writes a machine-readable report via `MOCK_PARITY_REPORT_PATH`. |
|
||||
| End-to-end CLI mock harness | `rust/crates/rusty-claude-cli/tests/mock_parity_harness.rs` starts the mock server, runs clean-environment `brewcode` commands, asserts JSON output, and optionally writes a machine-readable report via `MOCK_PARITY_REPORT_PATH`. |
|
||||
| Scenario manifest / docs parity guard | `rust/mock_parity_scenarios.json` is required to stay ordered with harness cases; `rust/scripts/run_mock_parity_diff.py --no-run` verifies every manifest `parity_refs[]` string exists in `PARITY.md`. |
|
||||
| Convenience runner | `rust/scripts/run_mock_parity_harness.sh` runs `cargo test -p rusty-claude-cli --test mock_parity_harness -- --nocapture`. |
|
||||
| Plugin-path regression | `plugin_tool_roundtrip` loads an external plugin fixture from isolated settings and executes `plugin_echo` through the runtime tool registry. |
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Scope and source
|
||||
|
||||
This map ties the Stream 8 acceptance target from `.omx/plans/claw-code-2-0-adaptive-plan.md` to repository artifacts and local verification. It is the worker-1 integration lane artifact; it does not mutate `.omx/ultragoal` and avoids duplicating peer implementation lanes for Windows CI, install/provider docs, and policy/link work.
|
||||
This map ties the Stream 8 acceptance target from `.omx/plans/brew-code-2-0-adaptive-plan.md` to repository artifacts and local verification. It is the worker-1 integration lane artifact; it does not mutate `.omx/ultragoal` and avoids duplicating peer implementation lanes for Windows CI, install/provider docs, and policy/link work.
|
||||
|
||||
Stream 8 source requirement summary:
|
||||
|
||||
|
|
@ -17,9 +17,9 @@ Stream 8 source requirement summary:
|
|||
|
||||
| Acceptance area | Repository artifact(s) | Verification command(s) | Notes |
|
||||
|---|---|---|---|
|
||||
| PowerShell-first Windows install/run path | `README.md` (`Windows setup`, post-build binary location, PowerShell `.exe` examples); `install.sh` (Unix/WSL installer guard) | `python3 .github/scripts/check_doc_source_of_truth.py`; `cargo run -p rusty-claude-cli -- --help` | Current docs explicitly present Windows as a supported PowerShell path for source builds and `claw.exe`; `install.sh` is Linux/macOS/WSL-oriented, so native PowerShell binary usage and WSL installer usage must stay clearly separated. |
|
||||
| PowerShell-first Windows install/run path | `README.md` (`Windows setup`, post-build binary location, PowerShell `.exe` examples); `install.sh` (Unix/WSL installer guard) | `python3 .github/scripts/check_doc_source_of_truth.py`; `cargo run -p rusty-claude-cli -- --help` | Current docs explicitly present Windows as a supported PowerShell path for source builds and `brewcode.exe`; `install.sh` is Linux/macOS/WSL-oriented, so native PowerShell binary usage and WSL installer usage must stay clearly separated. |
|
||||
| Safe provider switching examples | `USAGE.md` (`Auth`, `Local Models`, `Supported Providers & Models`); `docs/MODEL_COMPATIBILITY.md` | `cargo test -p api providers::`; `cargo test -p rusty-claude-cli --test output_format_contract provider_diagnostics_explain_openai_compatible_capabilities -- --nocapture` | Provider docs cover Anthropic API-key vs bearer-token shape, OpenAI-compatible routing, Ollama/OpenRouter/DashScope examples, and prefix routing to avoid ambient credential misrouting. |
|
||||
| Release artifact quickstart and staged packaging path | `README.md` (`Quick start`, `Post-build: locate the binary and verify`); `.github/workflows/release.yml`; `docs/windows-install-release.md` | `cargo build --release -p rusty-claude-cli`; `cargo run -p rusty-claude-cli -- version --output-format json`; `python3 .github/scripts/check_release_readiness.py (release-readiness gate)` | Release workflow packages Linux, macOS, and `claw-windows-x64.exe` assets with `.sha256` checksum files. README remains source-build-first, and the Windows quickstart names the checksum verification path. |
|
||||
| Release artifact quickstart and staged packaging path | `README.md` (`Quick start`, `Post-build: locate the binary and verify`); `.github/workflows/release.yml`; `docs/windows-install-release.md` | `cargo build --release -p rusty-claude-cli`; `cargo run -p rusty-claude-cli -- version --output-format json`; `python3 .github/scripts/check_release_readiness.py (release-readiness gate)` | Release workflow packages Linux, macOS, and `brewcode-windows-x64.exe` assets with `.sha256` checksum files. README remains source-build-first, and the Windows quickstart names the checksum verification path. |
|
||||
| Windows smoke CI without live credentials | `.github/workflows/rust-ci.yml`; CLI local-only surfaces in `rust/crates/rusty-claude-cli/src/main.rs` (`help`, `doctor`, resumed `/config`, `status`) | `cargo run -p rusty-claude-cli -- --help`; `cargo run -p rusty-claude-cli -- doctor --output-format json`; `cargo run -p rusty-claude-cli -- status --output-format json`; `cargo run -p rusty-claude-cli -- config --output-format json` | The smoke target is local-only command execution with isolated config and no real provider credentials. If the Windows CI lane is not present in a branch, this map is the integration checklist for that lane. |
|
||||
| License metadata | `rust/Cargo.toml` (`workspace.package.license = "MIT"`) | `grep -n '^license = "MIT"' rust/Cargo.toml` | Cargo metadata declares MIT. A root `LICENSE` file remains the user-facing policy artifact to add if not already present in the policy lane. |
|
||||
| Contribution/security/support policies | Expected root policy docs: `CONTRIBUTING.md`, `SECURITY.md`, `SUPPORT.md`; existing support links in `README.md` | `test -f CONTRIBUTING.md`; `test -f SECURITY.md`; `test -f SUPPORT.md`; `python3 .github/scripts/check_doc_source_of_truth.py` | These files are policy-lane outputs. This map records the exact release gate so missing files fail visibly instead of being inferred from README links. |
|
||||
|
|
@ -31,7 +31,7 @@ Use isolated config and no live credentials. These commands must not require `AN
|
|||
|
||||
```powershell
|
||||
# From repository root on Windows PowerShell
|
||||
$env:CLAW_CONFIG_HOME = Join-Path $env:TEMP "claw-smoke-config"
|
||||
$env:BREWCODE_CONFIG_HOME = Join-Path $env:TEMP "brewcode-smoke-config"
|
||||
Remove-Item Env:\ANTHROPIC_API_KEY -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:\ANTHROPIC_AUTH_TOKEN -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:\OPENAI_API_KEY -ErrorAction SilentlyContinue
|
||||
|
|
@ -48,7 +48,7 @@ Equivalent Unix smoke used by this worker:
|
|||
|
||||
```bash
|
||||
env -u ANTHROPIC_API_KEY -u ANTHROPIC_AUTH_TOKEN -u OPENAI_API_KEY -u XAI_API_KEY -u DASHSCOPE_API_KEY \
|
||||
CLAW_CONFIG_HOME="$(mktemp -d)" cargo run -p rusty-claude-cli -- --help
|
||||
BREWCODE_CONFIG_HOME="$(mktemp -d)" cargo run -p rusty-claude-cli -- --help
|
||||
```
|
||||
|
||||
## Offline Markdown reference check
|
||||
|
|
@ -81,9 +81,9 @@ PY
|
|||
|
||||
A Stream 8 release candidate is ready when all of the following are true:
|
||||
|
||||
1. PowerShell examples in `README.md` build and run `claw.exe` from a clean Windows checkout.
|
||||
1. PowerShell examples in `README.md` build and run `brewcode.exe` from a clean Windows checkout.
|
||||
2. Provider examples in `USAGE.md` show session-local/shell-local switching, include cleanup for conflicting ambient credentials (`unset` / `Remove-Item Env:`), and never instruct users to paste secrets into persistent config by default.
|
||||
3. Windows smoke CI runs help/doctor/config/status without live credentials, separates native PowerShell `claw.exe` smoke from WSL `install.sh` smoke, and archives JSON output on failure.
|
||||
3. Windows smoke CI runs help/doctor/config/status without live credentials, separates native PowerShell `brewcode.exe` smoke from WSL `install.sh` smoke, and archives JSON output on failure.
|
||||
4. Release artifacts include the documented platform matrix or the docs clearly state source-only alpha status.
|
||||
5. `LICENSE`, `CONTRIBUTING.md`, `SECURITY.md`, and `SUPPORT.md` exist or the policy lane records an explicit release-blocking exception.
|
||||
6. Doc source-of-truth and offline relative-link validation pass.
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ Scope: worker-2 task 5 for `G010-session-hygiene` / Stream 9 session hygiene, lo
|
|||
|
||||
## Contract summary
|
||||
|
||||
Claw session state is intentionally scoped to the current workspace clone/worktree. Operators and automation should treat the **session partition**, not a bare session id or the flat `.claw/sessions/` directory, as the identity boundary.
|
||||
Brewcode session state is intentionally scoped to the current workspace clone/worktree. Operators and automation should treat the **session partition**, not a bare session id or the flat `.brewcode/sessions/` directory, as the identity boundary.
|
||||
|
||||
Required metadata and behaviors:
|
||||
|
||||
- **Workspace-bound partition**: managed sessions live under `.claw/sessions/<workspace_fingerprint>/`, where the fingerprint is a stable 16-character FNV-1a digest of the canonical workspace path.
|
||||
- **Workspace-bound partition**: managed sessions live under `.brewcode/sessions/<workspace_fingerprint>/`, where the fingerprint is a stable 16-character FNV-1a digest of the canonical workspace path.
|
||||
- **Canonical path input**: `SessionStore::from_cwd` and `SessionStore::from_data_dir` canonicalize their workspace path before computing the partition, preventing `/tmp/foo` vs `/private/tmp/foo` and relative-vs-absolute spelling from creating two stores for the same clone.
|
||||
- **Clone/worktree isolation**: two distinct clones or worktrees must get different session partitions, even if session ids collide.
|
||||
- **Legacy safety**: flat legacy sessions under `.claw/sessions/` remain readable only when they are bound to the same workspace or are unbound but physically inside the current workspace; sessions whose persisted `workspace_root` points at another clone are rejected as `WorkspaceMismatch`.
|
||||
- **Legacy safety**: flat legacy sessions under `.brewcode/sessions/` remain readable only when they are bound to the same workspace or are unbound but physically inside the current workspace; sessions whose persisted `workspace_root` points at another clone are rejected as `WorkspaceMismatch`.
|
||||
- **Fork lineage stays local**: `/session fork` / managed session forking keeps the forked session in the same workspace partition and records parent id plus optional branch name.
|
||||
- **User-facing disambiguation**: empty-session copy names the actual fingerprint directory and explains that sessions from other CWDs are intentionally invisible.
|
||||
|
||||
|
|
@ -19,11 +19,11 @@ Required metadata and behaviors:
|
|||
|
||||
| Contract area | Repo anchor | Evidence role |
|
||||
| --- | --- | --- |
|
||||
| Partition layout and canonical workspace root | `rust/crates/runtime/src/session_control.rs:10-18`, `:32-47`, `:54-71` | Documents and implements `.claw/sessions/<workspace_hash>/` for `from_cwd` and explicit data-dir stores. |
|
||||
| Partition layout and canonical workspace root | `rust/crates/runtime/src/session_control.rs:10-18`, `:32-47`, `:54-71` | Documents and implements `.brewcode/sessions/<workspace_hash>/` for `from_cwd` and explicit data-dir stores. |
|
||||
| Fingerprint algorithm | `rust/crates/runtime/src/session_control.rs:300-312` | Defines the 16-character FNV-1a workspace fingerprint used as the clone disambiguator. |
|
||||
| Managed create/resolve/list/load/fork APIs | `rust/crates/runtime/src/session_control.rs:86-204` | Ensures handles, `latest`, load, and fork resolve inside the active partition. |
|
||||
| Legacy/cross-workspace guard | `rust/crates/runtime/src/session_control.rs:213-233`, `:557-567` | Rejects mismatched persisted `workspace_root` and allows only same-workspace legacy files. |
|
||||
| Empty partition copy | `rust/crates/runtime/src/session_control.rs:535-543` | Reports `.claw/sessions/<fingerprint>/` plus the workspace-partition note. |
|
||||
| Empty partition copy | `rust/crates/runtime/src/session_control.rs:535-543` | Reports `.brewcode/sessions/<fingerprint>/` plus the workspace-partition note. |
|
||||
| CLI wrapper | `rust/crates/rusty-claude-cli/src/main.rs:5952-6040` | Routes session CLI helpers through `current_session_store()`, so CLI list/latest/load uses the same partition. |
|
||||
| CLI session-list lifecycle context | `rust/crates/rusty-claude-cli/src/main.rs:5991-6027`, `:12960-12990` | Renders saved-only/dirty/abandoned lifecycle context for the current partition. |
|
||||
| CLI session resolution regression | `rust/crates/rusty-claude-cli/src/main.rs:13470-13579` | Covers JSONL default, legacy flat resolution, latest selection, and workspace mismatch rejection from CLI wrappers. |
|
||||
|
|
@ -31,7 +31,7 @@ Required metadata and behaviors:
|
|||
## Covered roadmap and dogfood anchors
|
||||
|
||||
- `ROADMAP.md:1125-1129` — session files are namespaced by workspace fingerprint, and wrong-workspace session access is rejected.
|
||||
- `ROADMAP.md:1419-1441` — empty/missing session messages must expose the fingerprint directory instead of implying a flat `.claw/sessions/` search.
|
||||
- `ROADMAP.md:1419-1441` — empty/missing session messages must expose the fingerprint directory instead of implying a flat `.brewcode/sessions/` search.
|
||||
- `ROADMAP.md:1453-1476` — the session partition boundary must be visible or shared deliberately; current contract is visible CWD/workspace partitioning.
|
||||
- `ROADMAP.md:5797-5902` — canonicalization closes the symlink/path-equivalence split in workspace fingerprints.
|
||||
- `ROADMAP.md:6342-6366` and `ROADMAP.md:6384-6411` — remaining Stream 9 risks around reported CWD form, failed-resume filesystem side effects, and broad-CWD resume guards are related UX/recovery lanes, not clone identity itself.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Stream 9 session hygiene is implemented in the Rust runtime/CLI as workspace-sco
|
|||
|
||||
| Acceptance area | Code surface | Evidence |
|
||||
| --- | --- | --- |
|
||||
| Generated session files are not accidentally committed | `.gitignore`, `rust/.gitignore` ignore `.claw/sessions/` and `.claude/sessions/` | `git check-ignore .claw/sessions/example.jsonl rust/.claw/sessions/example.jsonl .claude/sessions/example.json` |
|
||||
| Generated session files are not accidentally committed | `.gitignore`, `rust/.gitignore` ignore `.brewcode/sessions/` and `.claude/sessions/` | `git check-ignore .brewcode/sessions/example.jsonl rust/.brewcode/sessions/example.jsonl .claude/sessions/example.json` |
|
||||
| Per-worktree session isolation | `rust/crates/runtime/src/session_control.rs` (`SessionStore`, `workspace_fingerprint`, workspace validation) | `cargo test -p runtime session_store_from_cwd_isolates_sessions_by_workspace` |
|
||||
| List/resume/delete/exists contracts | `rust/crates/commands/src/lib.rs` parses `/session list`, `/session exists`, `/session delete`, `/resume`; `rust/crates/rusty-claude-cli/src/main.rs` renders text/JSON resume-safe session commands | `cargo test -p rusty-claude-cli session_exists_resume_command_reports_json_contract`; `cargo test -p rusty-claude-cli resume_report_uses_sectioned_layout` |
|
||||
| Compact and provider context-window recovery | `rust/crates/runtime/src/compact.rs`; `rust/crates/rusty-claude-cli/src/main.rs` context-window error recovery guidance and resumed `/compact` | `cargo test -p rusty-claude-cli provider_context_window_errors_are_reframed_with_same_guidance`; `cargo test -p commands compacts_sessions_via_slash_command` |
|
||||
|
|
@ -17,5 +17,5 @@ Stream 9 session hygiene is implemented in the Rust runtime/CLI as workspace-sco
|
|||
## Notes for leader audit
|
||||
|
||||
- Workers did not mutate `.omx/ultragoal`; this file is a repo-local verification map for team evidence only.
|
||||
- Runtime-owned session state remains under ignored `.claw/sessions/<workspace-fingerprint>/` paths.
|
||||
- Resume-safe JSON output uses stable `kind` fields (`restored`, `compact`, `session_list`, `session_exists`, etc.) so claws can route without scraping text.
|
||||
- Runtime-owned session state remains under ignored `.brewcode/sessions/<workspace-fingerprint>/` paths.
|
||||
- Resume-safe JSON output uses stable `kind` fields (`restored`, `compact`, `session_list`, `session_exists`, etc.) so brewcodes can route without scraping text.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# G011 ACP/Zed and JSON-RPC status contract
|
||||
|
||||
Claw Code 2.0 keeps ACP/Zed and JSON-RPC serving behind the stable task,
|
||||
Brew Code 2.0 keeps ACP/Zed and JSON-RPC serving behind the stable task,
|
||||
session-control, and event/report contracts from the roadmap. The current public
|
||||
surface is therefore a **truthful unsupported status**, not a hidden daemon.
|
||||
|
||||
|
|
@ -9,12 +9,12 @@ surface is therefore a **truthful unsupported status**, not a hidden daemon.
|
|||
The following commands are status queries and exit with code `0`:
|
||||
|
||||
```bash
|
||||
claw acp
|
||||
claw acp serve
|
||||
claw --acp
|
||||
claw -acp
|
||||
claw acp --output-format json
|
||||
claw acp serve --output-format json
|
||||
brewcode acp
|
||||
brewcode acp serve
|
||||
brewcode --acp
|
||||
brewcode -acp
|
||||
brewcode acp --output-format json
|
||||
brewcode acp serve --output-format json
|
||||
```
|
||||
|
||||
`serve` is deliberately an alias for status today. It does not bind a socket,
|
||||
|
|
@ -22,7 +22,7 @@ start a daemon, or expose a JSON-RPC endpoint.
|
|||
|
||||
## JSON envelope
|
||||
|
||||
`claw acp --output-format json` returns a stable envelope for editor probes and
|
||||
`brewcode acp --output-format json` returns a stable envelope for editor probes and
|
||||
CI checks:
|
||||
|
||||
```json
|
||||
|
|
@ -49,7 +49,7 @@ Consumers should check `kind == "acp"`, `supported == false`, and
|
|||
|
||||
## Unsupported invocations
|
||||
|
||||
Malformed ACP invocations, such as `claw acp start`, exit with code `1`. With
|
||||
Malformed ACP invocations, such as `brewcode acp start`, exit with code `1`. With
|
||||
`--output-format json`, stderr uses the normal CLI error envelope and sets:
|
||||
|
||||
```json
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ until their product contracts are stable.
|
|||
|
||||
| Lane | Owned surface | Regression evidence | Gate / gap |
|
||||
| --- | --- | --- | --- |
|
||||
| ACP/Zed status and JSON contracts | `rust/crates/rusty-claude-cli/src/main.rs` parses `claw acp`, `claw acp serve`, `--acp`, and `-acp`; `README.md` and `rust/README.md` document discoverability-only status | `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli --test output_format_contract acp_guidance_emits_json_when_requested -- --nocapture`; `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli local_command_help_flags_stay_on_the_local_parser_path -- --nocapture` | Real ACP/Zed daemon support remains deferred; status output must not imply a running protocol endpoint. |
|
||||
| Plugin/marketplace local routing | `rust/crates/rusty-claude-cli/src/main.rs` routes `claw plugins`, `claw plugin`, and `claw marketplace` to local plugin handling; `rust/crates/commands/src/lib.rs` keeps `/plugin` aliases in shared slash-command help | `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli removed_login_and_logout_subcommands_error_helpfully -- --nocapture`; `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli direct_slash_commands_surface_shared_validation_errors -- --nocapture`; `python3 -m unittest tests.test_porting_workspace.PortingWorkspaceTests.test_plugin_command_filter_excludes_plugin_sources tests.test_porting_workspace.PortingWorkspaceTests.test_plugin_command_aliases_execute_as_local_commands tests.test_porting_workspace.PortingWorkspaceTests.test_route_plugin_slash_commands_match_commands tests.test_porting_workspace.PortingWorkspaceTests.test_plugin_command_stream_emits_command_match tests.test_porting_workspace.PortingWorkspaceTests.test_turn_loop_plugin_commands_are_not_prompt_only` | Marketplace is an alias to local plugin management only; no remote marketplace browsing/install contract is claimed. |
|
||||
| ACP/Zed status and JSON contracts | `rust/crates/rusty-claude-cli/src/main.rs` parses `brewcode acp`, `brewcode acp serve`, `--acp`, and `-acp`; `README.md` and `rust/README.md` document discoverability-only status | `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli --test output_format_contract acp_guidance_emits_json_when_requested -- --nocapture`; `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli local_command_help_flags_stay_on_the_local_parser_path -- --nocapture` | Real ACP/Zed daemon support remains deferred; status output must not imply a running protocol endpoint. |
|
||||
| Plugin/marketplace local routing | `rust/crates/rusty-claude-cli/src/main.rs` routes `brewcode plugins`, `brewcode plugin`, and `brewcode marketplace` to local plugin handling; `rust/crates/commands/src/lib.rs` keeps `/plugin` aliases in shared slash-command help | `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli removed_login_and_logout_subcommands_error_helpfully -- --nocapture`; `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli direct_slash_commands_surface_shared_validation_errors -- --nocapture`; `python3 -m unittest tests.test_porting_workspace.PortingWorkspaceTests.test_plugin_command_filter_excludes_plugin_sources tests.test_porting_workspace.PortingWorkspaceTests.test_plugin_command_aliases_execute_as_local_commands tests.test_porting_workspace.PortingWorkspaceTests.test_route_plugin_slash_commands_match_commands tests.test_porting_workspace.PortingWorkspaceTests.test_plugin_command_stream_emits_command_match tests.test_porting_workspace.PortingWorkspaceTests.test_turn_loop_plugin_commands_are_not_prompt_only` | Marketplace is an alias to local plugin management only; no remote marketplace browsing/install contract is claimed. |
|
||||
| TUI/copy/paste/clickable path UX | `rust/crates/commands/src/lib.rs` advertises `/copy`, `/paste`, `/desktop`, and path-oriented commands; `rust/crates/rusty-claude-cli/src/main.rs` renders compact file/tool paths for terminal readability | `cargo test --manifest-path rust/Cargo.toml -p commands renders_help_with_grouped_categories_and_keyboard_shortcuts -- --nocapture`; `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli tool_rendering_helpers_compact_output -- --nocapture`; `cargo test --manifest-path rust/Cargo.toml -p rusty-claude-cli tool_rendering_truncates_large_read_output_for_display_only -- --nocapture` | Clipboard integration, full-screen TUI mode, and clickable terminal hyperlinks are not stable product contracts yet; keep them as roadmap/UX follow-ups unless a targeted implementation lands. |
|
||||
| Desktop integration deferral | `rust/crates/commands/src/lib.rs` includes `/desktop`; `rust/crates/rusty-claude-cli/src/main.rs` treats it as not implemented in the current build | `cargo test --manifest-path rust/Cargo.toml -p commands renders_help_from_shared_specs -- --nocapture`; `cargo test --manifest-path rust/Cargo.toml -p commands renders_per_command_help_detail -- --nocapture` | `/desktop` must stay discoverable but non-committal until a desktop launch/API contract exists. |
|
||||
| Navigation/file-context/local-provider docs | `README.md`, `USAGE.md`, `rust/README.md`, `docs/MODEL_COMPATIBILITY.md`, and worker-2 docs updates | `python3 .github/scripts/check_doc_source_of_truth.py`; `python3 .github/scripts/check_release_readiness.py`; `git diff --check` | Re-run after docs integrations; this lane should not alter Rust behavior unless docs expose a code contract gap. |
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Snapshot: 2026-05-15T02:59:29Z on `origin/main` / `HEAD` `2e93264919f38835410668ff6ca588606bc629f0`.
|
||||
|
||||
This is the worker-1 roadmap/board audit and release-readiness evidence map for the
|
||||
Claw Code 2.0 final gate. It is intentionally repo-local and non-destructive: it
|
||||
Brew Code 2.0 final gate. It is intentionally repo-local and non-destructive: it
|
||||
references `.omx/ultragoal` evidence without modifying leader-owned ultragoal
|
||||
state, and it does not merge PRs or close issues owned by the W3/W4 lanes.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
This guide covers two common offline/local workflows:
|
||||
|
||||
1. running Claw against an OpenAI-compatible local model server such as Ollama, llama.cpp, or vLLM; and
|
||||
2. installing local skills from disk so Claw can discover them without network access.
|
||||
1. running Brewcode against an OpenAI-compatible local model server such as Ollama, llama.cpp, or vLLM; and
|
||||
2. installing local skills from disk so Brewcode can discover them without network access.
|
||||
|
||||
## Claw is not Claude-only
|
||||
## Brewcode is not Claude-only
|
||||
|
||||
Claw Code is a Claude-Code-shaped workflow/runtime, not a Claude-only product. It supports Anthropic directly and can target OpenAI-compatible, provider-routed, and local models depending on configuration. Non-Claude providers are supported honestly: they may require stricter tool-call and response-shape compatibility, and some slash/tool workflows can be rougher than first-party Anthropic/OpenAI paths. Provider-specific identity leaks are bugs, not intended product positioning.
|
||||
Brew Code is a Claude-Code-shaped workflow/runtime, not a Claude-only product. It supports Anthropic directly and can target OpenAI-compatible, provider-routed, and local models depending on configuration. Non-Claude providers are supported honestly: they may require stricter tool-call and response-shape compatibility, and some slash/tool workflows can be rougher than first-party Anthropic/OpenAI paths. Provider-specific identity leaks are bugs, not intended product positioning.
|
||||
|
||||
If you need the most polished daily-driver experience for a specific non-Claude model today, compare that provider’s native tools. If you need runtime/provider hackability, Claw’s OpenAI-compatible route is the intended extension path.
|
||||
If you need the most polished daily-driver experience for a specific non-Claude model today, compare that provider’s native tools. If you need runtime/provider hackability, Brewcode’s OpenAI-compatible route is the intended extension path.
|
||||
|
||||
## OpenAI-compatible routing basics
|
||||
|
||||
|
|
@ -18,19 +18,35 @@ Set `OPENAI_BASE_URL` to the server’s `/v1` endpoint and set `OPENAI_API_KEY`
|
|||
```bash
|
||||
export OPENAI_BASE_URL="http://127.0.0.1:11434/v1"
|
||||
export OPENAI_API_KEY="local-dev-token"
|
||||
claw --model "qwen3:latest" prompt "Reply exactly HELLO_WORLD_123"
|
||||
brewcode --model "openai/qwen3:latest" prompt "Reply exactly HELLO_WORLD_123"
|
||||
```
|
||||
|
||||
### How the `openai/` prefix interacts with each backend
|
||||
|
||||
Brewcode identifies the gateway behind your `OPENAI_BASE_URL` and adjusts the model name in the wire request accordingly. You always pass `--model "openai/<name>"` to brewcode's CLI parser; what reaches the server depends on the gateway:
|
||||
|
||||
| Gateway (detected from `OPENAI_BASE_URL`) | Example URL | Wire `model` field |
|
||||
|---|---|---|
|
||||
| **OpenAI** (default) | `https://api.openai.com/v1` | `<name>` — prefix stripped |
|
||||
| **OpenRouter** | `https://openrouter.ai/api/v1` | `openai/<name>` — slug preserved |
|
||||
| **Ollama Cloud** | `https://ollama.com/v1` | `<name>` — prefix stripped |
|
||||
| **Ollama (local)** | `http://127.0.0.1:11434/v1`, `localhost:11434`, `0.0.0.0:11434` | `<name>` — prefix stripped |
|
||||
| **xAI** | `https://api.x.ai/v1` | `<name>` — prefix stripped |
|
||||
| **DashScope** | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `<name>` — prefix stripped |
|
||||
| **Generic** (anything else: vLLM, llama.cpp, LiteLLM, in-house proxies) | any custom URL | `<name>` — prefix stripped |
|
||||
|
||||
OpenRouter is the only backend whose protocol genuinely uses slash-namespaced model slugs (`openai/gpt-4.1-mini`, `anthropic/claude-3.7-sonnet`, etc.). Every other OpenAI-compatible backend wants the bare model name and will 404 if the routing prefix leaks into the request body.
|
||||
|
||||
Routing notes:
|
||||
|
||||
- Use the `openai/` prefix for OpenAI-compatible gateways when you need prefix routing to win over ambient Anthropic credentials, for example `--model "openai/gpt-4.1-mini"` with OpenRouter.
|
||||
- For local servers, prefer the exact model ID reported by the server (`qwen3:latest`, `llama3.2`, `Qwen/Qwen2.5-Coder-7B-Instruct`, etc.). If your local gateway exposes slash-containing IDs, use that exact slug.
|
||||
- Use the `openai/` prefix on the CLI to make prefix routing win over ambient Anthropic credentials, regardless of which OpenAI-compatible backend you are hitting. Example: `--model "openai/gpt-oss:20b"` with `OPENAI_BASE_URL=https://ollama.com/v1` sends `"model": "gpt-oss:20b"` to Ollama Cloud.
|
||||
- For local servers, the exact model ID reported by the server is what reaches the wire — `qwen3:latest`, `llama3.2`, `Qwen/Qwen2.5-Coder-7B-Instruct`, etc. The `openai/` prefix is stripped, but slash-containing IDs after the prefix are preserved.
|
||||
- If you have multiple provider keys in your environment, remove unrelated keys while smoke-testing a local route or choose a model prefix that unambiguously selects the intended provider.
|
||||
- Tool workflows need model/server support for OpenAI-compatible tool calls. Plain prompt smoke tests can pass even when slash/tool workflows still fail because the server returns an incompatible tool-call shape.
|
||||
|
||||
## Raw `/v1/chat/completions` smoke test
|
||||
|
||||
Before debugging Claw, verify the local server speaks the expected wire format:
|
||||
Before debugging Brewcode, verify the local server speaks the expected wire format:
|
||||
|
||||
```bash
|
||||
curl -sS "$OPENAI_BASE_URL/chat/completions" \
|
||||
|
|
@ -43,7 +59,7 @@ curl -sS "$OPENAI_BASE_URL/chat/completions" \
|
|||
}'
|
||||
```
|
||||
|
||||
Expected result: a JSON response with one assistant message containing `HELLO_WORLD_123`. If this fails, fix the local server, model name, or auth token before changing Claw settings.
|
||||
Expected result: a JSON response with one assistant message containing `HELLO_WORLD_123`. If this fails, fix the local server, model name, or auth token before changing Brewcode settings.
|
||||
|
||||
## Ollama
|
||||
|
||||
|
|
@ -59,25 +75,25 @@ In another shell:
|
|||
```bash
|
||||
export OPENAI_BASE_URL="http://127.0.0.1:11434/v1"
|
||||
export OPENAI_API_KEY="local-dev-token"
|
||||
claw --model "qwen3:latest" prompt "Reply exactly HELLO_WORLD_123"
|
||||
brewcode --model "qwen3:latest" prompt "Reply exactly HELLO_WORLD_123"
|
||||
```
|
||||
|
||||
If Ollama is running without auth and your build accepts authless local OpenAI-compatible servers, `unset OPENAI_API_KEY` is also acceptable. Use a placeholder token rather than a real cloud API key for local testing.
|
||||
|
||||
## llama.cpp server
|
||||
|
||||
Start a llama.cpp OpenAI-compatible server with the model name you want Claw to send:
|
||||
Start a llama.cpp OpenAI-compatible server with the model name you want Brewcode to send:
|
||||
|
||||
```bash
|
||||
llama-server -m ./models/qwen2.5-coder.gguf --host 127.0.0.1 --port 8080 --alias qwen2.5-coder
|
||||
```
|
||||
|
||||
Then smoke-test through Claw:
|
||||
Then smoke-test through Brewcode:
|
||||
|
||||
```bash
|
||||
export OPENAI_BASE_URL="http://127.0.0.1:8080/v1"
|
||||
export OPENAI_API_KEY="local-dev-token"
|
||||
claw --model "qwen2.5-coder" prompt "Reply exactly HELLO_WORLD_123"
|
||||
brewcode --model "qwen2.5-coder" prompt "Reply exactly HELLO_WORLD_123"
|
||||
```
|
||||
|
||||
## vLLM or another OpenAI-compatible server
|
||||
|
|
@ -88,17 +104,17 @@ Start vLLM with an OpenAI-compatible API server:
|
|||
vllm serve Qwen/Qwen2.5-Coder-7B-Instruct --host 127.0.0.1 --port 8000
|
||||
```
|
||||
|
||||
Then route Claw to it:
|
||||
Then route Brewcode to it:
|
||||
|
||||
```bash
|
||||
export OPENAI_BASE_URL="http://127.0.0.1:8000/v1"
|
||||
export OPENAI_API_KEY="local-dev-token"
|
||||
claw --model "Qwen/Qwen2.5-Coder-7B-Instruct" prompt "Reply exactly HELLO_WORLD_123"
|
||||
brewcode --model "Qwen/Qwen2.5-Coder-7B-Instruct" prompt "Reply exactly HELLO_WORLD_123"
|
||||
```
|
||||
|
||||
## Local skills install from disk
|
||||
|
||||
Skills are discovered from Claw skill roots such as `.claw/skills/` in a workspace and `~/.claw/skills/` for user-level installs. Legacy `.codex/skills/` roots may also be scanned for compatibility, but new local Claw projects should prefer `.claw/skills/`.
|
||||
Skills are discovered from Brewcode skill roots such as `.brewcode/skills/` in a workspace and `~/.brewcode/skills/` for user-level installs. Legacy `.codex/skills/` roots may also be scanned for compatibility, but new local Brewcode projects should prefer `.brewcode/skills/`.
|
||||
|
||||
A skill directory should contain a `SKILL.md` file with frontmatter:
|
||||
|
||||
|
|
@ -129,22 +145,22 @@ Install a skill from a local path in the interactive REPL:
|
|||
Or inspect skills from the direct CLI surface:
|
||||
|
||||
```bash
|
||||
claw skills --output-format json
|
||||
brewcode skills --output-format json
|
||||
```
|
||||
|
||||
Offline install checklist:
|
||||
|
||||
- Install the specific skill directory, not only the repository root, unless that repository root itself contains `SKILL.md`.
|
||||
- Keep the frontmatter `name` aligned with the directory name users will type.
|
||||
- After installing, run `/skills list` or `claw skills --output-format json` to confirm the discovered name and source path.
|
||||
- If a skill invocation fails with an HTTP/provider error, the skill may have installed correctly but the current model/provider call failed. Run `claw doctor`, verify provider credentials, and try a simple prompt smoke test before reinstalling the skill.
|
||||
- After installing, run `/skills list` or `brewcode skills --output-format json` to confirm the discovered name and source path.
|
||||
- If a skill invocation fails with an HTTP/provider error, the skill may have installed correctly but the current model/provider call failed. Run `brewcode doctor`, verify provider credentials, and try a simple prompt smoke test before reinstalling the skill.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Check |
|
||||
|---|---|
|
||||
| Claw still asks for Anthropic credentials | Use an explicit OpenAI-compatible model route or remove unrelated Anthropic env vars during local smoke tests. |
|
||||
| Brewcode still asks for Anthropic credentials | Use an explicit OpenAI-compatible model route or remove unrelated Anthropic env vars during local smoke tests. |
|
||||
| `model not found` from local server | Use the exact model ID exposed by Ollama/llama.cpp/vLLM. |
|
||||
| Plain prompt works but tools fail | Confirm the model/server supports OpenAI-compatible tool calls and response shapes. |
|
||||
| Skill says installed but `/skills <name>` fails | Check `/skills list` for the discovered name and source; verify provider credentials separately with `claw doctor`. |
|
||||
| Skill says installed but `/skills <name>` fails | Check `/skills list` for the discovered name and source; verify provider credentials separately with `brewcode doctor`. |
|
||||
| A local docs/log file contains secrets | Redact it before using `@path` file context or attaching it to an issue. |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Navigation and file context guide
|
||||
|
||||
This guide answers the common “how do I browse output?” and “how do I submit a file?” questions for Claw Code. Claw is an agent CLI, not a full file manager: terminal navigation comes from your shell or terminal, while file context is passed explicitly in prompts.
|
||||
This guide answers the common “how do I browse output?” and “how do I submit a file?” questions for Brew Code. Brewcode is an agent CLI, not a full file manager: terminal navigation comes from your shell or terminal, while file context is passed explicitly in prompts.
|
||||
|
||||
## Prompt and terminal navigation
|
||||
|
||||
|
|
@ -9,13 +9,13 @@ Use your terminal’s normal controls for command history and long output:
|
|||
- `Up` / `Down` usually move through shell or REPL prompt history.
|
||||
- `Ctrl-r` searches shell history in most shells.
|
||||
- Long command output is viewed with your terminal scrollback. In tmux, enter copy mode with `Ctrl-b [` then use arrows, PageUp/PageDown, search, or your mouse depending on tmux config.
|
||||
- If output is too large to scroll comfortably, redirect it to a file and give that file to Claw as context:
|
||||
- If output is too large to scroll comfortably, redirect it to a file and give that file to Brewcode as context:
|
||||
```bash
|
||||
cargo test --workspace 2>&1 | tee logs/test-output.txt
|
||||
claw prompt "Use @logs/test-output.txt as context and summarize the failing tests."
|
||||
brewcode prompt "Use @logs/test-output.txt as context and summarize the failing tests."
|
||||
```
|
||||
|
||||
Claw may provide slash commands that inspect workspace state, but those commands do not replace your terminal’s scrollback or shell history.
|
||||
Brewcode may provide slash commands that inspect workspace state, but those commands do not replace your terminal’s scrollback or shell history.
|
||||
|
||||
## Submit repository files with `@path`
|
||||
|
||||
|
|
@ -33,11 +33,11 @@ Tips:
|
|||
- Prefer the smallest useful file set. Large directories or logs can consume context quickly.
|
||||
- Use exact paths when possible (`@rust/crates/runtime/src/lib.rs`) instead of vague descriptions.
|
||||
- For generated logs, save them under a temporary or ignored directory such as `logs/` and reference the file.
|
||||
- If the file is outside the repository, copy it into a safe workspace location first or use an app/UI attachment feature if your Claw surface supports attachments.
|
||||
- If the file is outside the repository, copy it into a safe workspace location first or use an app/UI attachment feature if your Brewcode surface supports attachments.
|
||||
|
||||
## Browse or inspect files
|
||||
|
||||
Claw can answer questions about files you reference, and you can ask it to inspect likely locations:
|
||||
Brewcode can answer questions about files you reference, and you can ask it to inspect likely locations:
|
||||
|
||||
```text
|
||||
Find where provider routing is implemented and summarize the relevant files.
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
# Claw Code 2.0 PR and Issue Resolution Gate
|
||||
# Brew Code 2.0 PR and Issue Resolution Gate
|
||||
|
||||
This gate was added to the Claw Code 2.0 Ultragoal after the explicit requirement:
|
||||
This gate was added to the Brew Code 2.0 Ultragoal after the explicit requirement:
|
||||
|
||||
> all PRs should be merged and all issues should be resolved if resolvable and correct.
|
||||
|
||||
## Scope
|
||||
|
||||
Before the Claw Code 2.0 Ultragoal can be marked complete:
|
||||
Before the Brew Code 2.0 Ultragoal can be marked complete:
|
||||
|
||||
1. Every open GitHub PR at the current final-gate snapshot must be triaged.
|
||||
2. PRs that are correct, compatible with Claw Code 2.0 direction, and pass required verification must be merged.
|
||||
3. PRs that are stale, incorrect, duplicative, unsafe, spam, or outside Claw Code scope must not be merged; each needs a recorded rationale.
|
||||
2. PRs that are correct, compatible with Brew Code 2.0 direction, and pass required verification must be merged.
|
||||
3. PRs that are stale, incorrect, duplicative, unsafe, spam, or outside Brew Code scope must not be merged; each needs a recorded rationale.
|
||||
4. Every open GitHub issue at the current final-gate snapshot must be triaged.
|
||||
5. Issues that are resolvable and correct must be fixed or explicitly linked to a merged fix.
|
||||
6. Issues that are spam, duplicates, incorrect, unactionable, externally blocked, or not Claw Code work must be closed or labeled/commented with rationale when repository policy allows.
|
||||
6. Issues that are spam, duplicates, incorrect, unactionable, externally blocked, or not Brew Code work must be closed or labeled/commented with rationale when repository policy allows.
|
||||
7. The final completion audit must use a fresh GitHub snapshot, not only the planning snapshot.
|
||||
|
||||
## Current live snapshot
|
||||
|
|
@ -44,7 +44,7 @@ Automation lanes may recommend labels, comments, defer/close rationales, or merg
|
|||
|
||||
## G012 final PR reconciliation snapshot
|
||||
|
||||
Worker-3 captured a fresh PR ledger for the final Claw Code 2.0 gate in `docs/pr-triage-g012-final-gate.json`.
|
||||
Worker-3 captured a fresh PR ledger for the final Brew Code 2.0 gate in `docs/pr-triage-g012-final-gate.json`.
|
||||
|
||||
- Captured on: 2026-05-15T02:58:00Z during G012 final-gate execution.
|
||||
- Commands: `gh pr list --state open --limit 100 ...` plus `gh pr view <number> ...` for per-PR file and merge-state evidence.
|
||||
|
|
|
|||
|
|
@ -114,10 +114,10 @@
|
|||
},
|
||||
{
|
||||
"number": 3021,
|
||||
"title": "Document TweetClaw skill install example",
|
||||
"title": "Document TweetBrewcode skill install example",
|
||||
"url": "https://github.com/ultraworkers/claw-code/pull/3021",
|
||||
"author": "kriptoburak",
|
||||
"head": "codex/add-tweetclaw-skill-example",
|
||||
"head": "codex/add-tweetbrewcode-skill-example",
|
||||
"base": "main",
|
||||
"updatedAt": "2026-05-12T20:24:00Z",
|
||||
"mergeable": "MERGEABLE",
|
||||
|
|
@ -137,10 +137,10 @@
|
|||
},
|
||||
{
|
||||
"number": 3019,
|
||||
"title": "feat: claw parity batch \u2014 memory/compact/rules/pricing/hooks/perms",
|
||||
"title": "feat: brewcode parity batch \u2014 memory/compact/rules/pricing/hooks/perms",
|
||||
"url": "https://github.com/ultraworkers/claw-code/pull/3019",
|
||||
"author": "MoSaleh-AKB",
|
||||
"head": "feat/claw-parity-batch-mosaleh-2026-05-08",
|
||||
"head": "feat/brewcode-parity-batch-mosaleh-2026-05-08",
|
||||
"base": "main",
|
||||
"updatedAt": "2026-05-12T12:05:47Z",
|
||||
"mergeable": "MERGEABLE",
|
||||
|
|
@ -167,7 +167,7 @@
|
|||
},
|
||||
{
|
||||
"number": 3018,
|
||||
"title": "feat: project rules with .claw/rules/ and multi-framework auto-import",
|
||||
"title": "feat: project rules with .brewcode/rules/ and multi-framework auto-import",
|
||||
"url": "https://github.com/ultraworkers/claw-code/pull/3018",
|
||||
"author": "TheArchitectit",
|
||||
"head": "feat/project-rules-v2",
|
||||
|
|
@ -566,7 +566,7 @@
|
|||
},
|
||||
{
|
||||
"number": 2934,
|
||||
"title": "feat: install and monitor latest claw-code",
|
||||
"title": "feat: install and monitor latest brew-code",
|
||||
"url": "https://github.com/ultraworkers/claw-code/pull/2934",
|
||||
"author": "bloodf",
|
||||
"head": "heitor/auto-update",
|
||||
|
|
@ -1041,7 +1041,7 @@
|
|||
},
|
||||
{
|
||||
"number": 2830,
|
||||
"title": "feat: project rules with .claw/rules/ and multi-framework auto-import",
|
||||
"title": "feat: project rules with .brewcode/rules/ and multi-framework auto-import",
|
||||
"url": "https://github.com/ultraworkers/claw-code/pull/2830",
|
||||
"author": "TheArchitectit",
|
||||
"head": "feat/project-rules",
|
||||
|
|
@ -1113,10 +1113,10 @@
|
|||
},
|
||||
{
|
||||
"number": 2827,
|
||||
"title": "feat(analog): add claw-analog minimal harness",
|
||||
"title": "feat(analog): add brewcode-analog minimal harness",
|
||||
"url": "https://github.com/ultraworkers/claw-code/pull/2827",
|
||||
"author": "gismo212",
|
||||
"head": "pr/claw-analog",
|
||||
"head": "pr/brewcode-analog",
|
||||
"base": "main",
|
||||
"updatedAt": "2026-04-28T13:05:11Z",
|
||||
"mergeable": "MERGEABLE",
|
||||
|
|
@ -1128,12 +1128,12 @@
|
|||
"deletions": 0,
|
||||
"files": [
|
||||
"how_to_run.md",
|
||||
"rust/crates/claw-analog/Cargo.toml",
|
||||
"rust/crates/claw-analog/src/agents.rs",
|
||||
"rust/crates/claw-analog/src/config_cmd.rs",
|
||||
"rust/crates/claw-analog/src/doctor.rs",
|
||||
"rust/crates/claw-analog/src/lib.rs",
|
||||
"rust/crates/claw-analog/src/main.rs"
|
||||
"rust/crates/brewcode-analog/Cargo.toml",
|
||||
"rust/crates/brewcode-analog/src/agents.rs",
|
||||
"rust/crates/brewcode-analog/src/config_cmd.rs",
|
||||
"rust/crates/brewcode-analog/src/doctor.rs",
|
||||
"rust/crates/brewcode-analog/src/lib.rs",
|
||||
"rust/crates/brewcode-analog/src/main.rs"
|
||||
],
|
||||
"files_truncated": false,
|
||||
"decision": "defer_not_merged",
|
||||
|
|
@ -1142,7 +1142,7 @@
|
|||
},
|
||||
{
|
||||
"number": 2826,
|
||||
"title": "feat(rag): add claw-rag-service",
|
||||
"title": "feat(rag): add brewcode-rag-service",
|
||||
"url": "https://github.com/ultraworkers/claw-code/pull/2826",
|
||||
"author": "gismo212",
|
||||
"head": "pr/rag-service",
|
||||
|
|
@ -1157,17 +1157,17 @@
|
|||
"deletions": 0,
|
||||
"files": [
|
||||
"docs/rag-web-ui.md",
|
||||
"rust/crates/claw-rag-service/Cargo.toml",
|
||||
"rust/crates/claw-rag-service/Dockerfile",
|
||||
"rust/crates/claw-rag-service/src/chunk.rs",
|
||||
"rust/crates/claw-rag-service/src/db.rs",
|
||||
"rust/crates/claw-rag-service/src/embed.rs",
|
||||
"rust/crates/claw-rag-service/src/ingest.rs",
|
||||
"rust/crates/claw-rag-service/src/lib.rs",
|
||||
"rust/crates/claw-rag-service/src/main.rs",
|
||||
"rust/crates/claw-rag-service/src/qdrant_index.rs",
|
||||
"rust/crates/claw-rag-service/src/search.rs",
|
||||
"rust/crates/claw-rag-service/static/index.html"
|
||||
"rust/crates/brewcode-rag-service/Cargo.toml",
|
||||
"rust/crates/brewcode-rag-service/Dockerfile",
|
||||
"rust/crates/brewcode-rag-service/src/chunk.rs",
|
||||
"rust/crates/brewcode-rag-service/src/db.rs",
|
||||
"rust/crates/brewcode-rag-service/src/embed.rs",
|
||||
"rust/crates/brewcode-rag-service/src/ingest.rs",
|
||||
"rust/crates/brewcode-rag-service/src/lib.rs",
|
||||
"rust/crates/brewcode-rag-service/src/main.rs",
|
||||
"rust/crates/brewcode-rag-service/src/qdrant_index.rs",
|
||||
"rust/crates/brewcode-rag-service/src/search.rs",
|
||||
"rust/crates/brewcode-rag-service/static/index.html"
|
||||
],
|
||||
"files_truncated": false,
|
||||
"decision": "defer_not_merged",
|
||||
|
|
@ -1352,7 +1352,7 @@
|
|||
},
|
||||
{
|
||||
"number": 2810,
|
||||
"title": "feat: interactive provider wizard (/setup, claw setup, Ctrl+P)",
|
||||
"title": "feat: interactive provider wizard (/setup, brewcode setup, Ctrl+P)",
|
||||
"url": "https://github.com/ultraworkers/claw-code/pull/2810",
|
||||
"author": "TheArchitectit",
|
||||
"head": "feat/provider-wizard",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
# Roadmap PR goal intake
|
||||
|
||||
Captured: 2026-05-14 (Asia/Seoul) during the Claw Code 2.0 Ultragoal run.
|
||||
Captured: 2026-05-14 (Asia/Seoul) during the Brew Code 2.0 Ultragoal run.
|
||||
|
||||
Purpose: make the user's follow-up requirement durable: all roadmap PRs should be merged when correct/resolvable, and unresolved roadmap deltas should become Ultragoal work rather than being lost. This file is a tracked companion to the leader-owned `.omx/ultragoal/goals.json` and `.omx/ultragoal/ledger.jsonl` artifacts.
|
||||
|
||||
## Merge policy
|
||||
|
||||
- Merge only PRs that are still relevant to Claw Code 2.0, are non-draft, target `main`, and are conflict-free after a fresh mergeability refresh.
|
||||
- Merge only PRs that are still relevant to Brew Code 2.0, are non-draft, target `main`, and are conflict-free after a fresh mergeability refresh.
|
||||
- Prefer squash merges with a Lore-style body when GitHub allows a direct PR merge.
|
||||
- If a PR is documentation-only but adds a real roadmap gap, merging it is acceptable once checks/conflicts are clean.
|
||||
- If a PR is stale, duplicated by already-landed work, or not product-aligned, do not force-merge; record the rationale and map any still-correct requirement into G011/G012.
|
||||
|
|
@ -44,7 +44,7 @@ These have no check rollup in the live snapshot; validate locally or refresh CI
|
|||
|
||||
## Product-fit review before merge
|
||||
|
||||
These may be broader than the Claw Code 2.0 roadmap scope and need a product-fit decision before merge.
|
||||
These may be broader than the Brew Code 2.0 roadmap scope and need a product-fit decision before merge.
|
||||
|
||||
| PR | Title | Branch | Checks | Mergeable | URL |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
|
|
|
|||
|
|
@ -6,31 +6,31 @@ This page is the PowerShell-first path for installing, verifying, and safely swi
|
|||
|
||||
### Option A: build from source in PowerShell
|
||||
|
||||
Use this when you are developing Claw Code or testing a local checkout.
|
||||
Use this when you are developing Brew Code or testing a local checkout.
|
||||
|
||||
```powershell
|
||||
git clone https://github.com/ultraworkers/claw-code
|
||||
Set-Location .\claw-code\rust
|
||||
Set-Location .\brew-code\rust
|
||||
cargo build --workspace
|
||||
.\target\debug\claw.exe --help
|
||||
.\target\debug\claw.exe doctor
|
||||
.\target\debug\brewcode.exe --help
|
||||
.\target\debug\brewcode.exe doctor
|
||||
```
|
||||
|
||||
For an optimized local binary:
|
||||
|
||||
```powershell
|
||||
Set-Location .\claw-code\rust
|
||||
Set-Location .\brew-code\rust
|
||||
cargo build --workspace --release
|
||||
.\target\release\claw.exe --help
|
||||
.\target\release\brewcode.exe --help
|
||||
```
|
||||
|
||||
### Option B: use a release artifact
|
||||
|
||||
Use this when a GitHub release publishes a Windows artifact. The release workflow publishes `claw-windows-x64.exe` plus `claw-windows-x64.exe.sha256`; if a future release wraps the binary in a ZIP, prefer the `windows-x86_64` / `pc-windows-msvc` asset and its matching checksum file.
|
||||
Use this when a GitHub release publishes a Windows artifact. The release workflow publishes `brewcode-windows-x64.exe` plus `brewcode-windows-x64.exe.sha256`; if a future release wraps the binary in a ZIP, prefer the `windows-x86_64` / `pc-windows-msvc` asset and its matching checksum file.
|
||||
|
||||
```powershell
|
||||
$Asset = "claw-windows-x64.exe"
|
||||
$InstallRoot = "$env:LOCALAPPDATA\Programs\claw"
|
||||
$Asset = "brewcode-windows-x64.exe"
|
||||
$InstallRoot = "$env:LOCALAPPDATA\Programs\brewcode"
|
||||
New-Item -ItemType Directory -Force $InstallRoot | Out-Null
|
||||
|
||||
# Download $Asset and $Asset.sha256 from the release page, then verify them:
|
||||
|
|
@ -38,15 +38,15 @@ $Actual = (Get-FileHash ".\$Asset" -Algorithm SHA256).Hash.ToLowerInvariant()
|
|||
$Expected = (Get-Content ".\$Asset.sha256" | Select-Object -First 1).Split()[0].ToLowerInvariant()
|
||||
if ($Actual -ne $Expected) { throw "checksum mismatch for $Asset" }
|
||||
|
||||
Copy-Item ".\$Asset" "$InstallRoot\claw.exe" -Force
|
||||
& "$InstallRoot\claw.exe" --help
|
||||
& "$InstallRoot\claw.exe" doctor
|
||||
Copy-Item ".\$Asset" "$InstallRoot\brewcode.exe" -Force
|
||||
& "$InstallRoot\brewcode.exe" --help
|
||||
& "$InstallRoot\brewcode.exe" doctor
|
||||
```
|
||||
|
||||
To make that binary available in new PowerShell windows:
|
||||
|
||||
```powershell
|
||||
$InstallRoot = "$env:LOCALAPPDATA\Programs\claw"
|
||||
$InstallRoot = "$env:LOCALAPPDATA\Programs\brewcode"
|
||||
[Environment]::SetEnvironmentVariable(
|
||||
"Path",
|
||||
[Environment]::GetEnvironmentVariable("Path", "User") + ";$InstallRoot",
|
||||
|
|
@ -54,7 +54,7 @@ $InstallRoot = "$env:LOCALAPPDATA\Programs\claw"
|
|||
)
|
||||
```
|
||||
|
||||
Open a new terminal before running `claw --help` from another directory.
|
||||
Open a new terminal before running `brewcode --help` from another directory.
|
||||
|
||||
### Option C: WSL
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ Then inside WSL:
|
|||
|
||||
```bash
|
||||
git clone https://github.com/ultraworkers/claw-code
|
||||
cd claw-code
|
||||
cd brew-code
|
||||
./install.sh
|
||||
```
|
||||
|
||||
|
|
@ -78,11 +78,11 @@ cd claw-code
|
|||
Run these before using live prompts:
|
||||
|
||||
```powershell
|
||||
Set-Location .\claw-code\rust
|
||||
.\target\debug\claw.exe --help
|
||||
.\target\debug\claw.exe doctor
|
||||
.\target\debug\claw.exe status --output-format json
|
||||
.\target\debug\claw.exe config --output-format json
|
||||
Set-Location .\brew-code\rust
|
||||
.\target\debug\brewcode.exe --help
|
||||
.\target\debug\brewcode.exe doctor
|
||||
.\target\debug\brewcode.exe status --output-format json
|
||||
.\target\debug\brewcode.exe config --output-format json
|
||||
```
|
||||
|
||||
`doctor`, `status`, `config`, and `version` support `--output-format json`; do not use a separate `--json` suffix.
|
||||
|
|
@ -111,7 +111,7 @@ Remove-Item Env:\ANTHROPIC_API_KEY -ErrorAction SilentlyContinue
|
|||
|
||||
## Safe provider switching examples
|
||||
|
||||
Provider routing is model-prefix first. When multiple credentials exist, choose an explicit model prefix so `claw` does not infer the wrong backend.
|
||||
Provider routing is model-prefix first. When multiple credentials exist, choose an explicit model prefix so `brewcode` does not infer the wrong backend.
|
||||
|
||||
### Anthropic direct
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ $env:ANTHROPIC_API_KEY = "sk-ant-REPLACE_ME"
|
|||
Remove-Item Env:\OPENAI_BASE_URL -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:\OPENAI_API_KEY -ErrorAction SilentlyContinue
|
||||
|
||||
.\target\debug\claw.exe --model "sonnet" prompt "reply with ready"
|
||||
.\target\debug\brewcode.exe --model "sonnet" prompt "reply with ready"
|
||||
```
|
||||
|
||||
### OpenAI-compatible gateway or OpenRouter
|
||||
|
|
@ -130,7 +130,7 @@ Remove-Item Env:\ANTHROPIC_API_KEY -ErrorAction SilentlyContinue
|
|||
$env:OPENAI_BASE_URL = "https://openrouter.ai/api/v1"
|
||||
$env:OPENAI_API_KEY = "sk-or-v1-REPLACE_ME"
|
||||
|
||||
.\target\debug\claw.exe --model "openai/gpt-4.1-mini" prompt "reply with ready"
|
||||
.\target\debug\brewcode.exe --model "openai/gpt-4.1-mini" prompt "reply with ready"
|
||||
```
|
||||
|
||||
For the default OpenAI-compatible API, omit `OPENAI_BASE_URL` or set it to `https://api.openai.com/v1`, and keep the `openai/` or `gpt-` model prefix explicit.
|
||||
|
|
@ -144,7 +144,7 @@ Remove-Item Env:\ANTHROPIC_API_KEY -ErrorAction SilentlyContinue
|
|||
$env:OPENAI_BASE_URL = "http://127.0.0.1:11434/v1"
|
||||
$env:OPENAI_API_KEY = "local-dev-token"
|
||||
|
||||
.\target\debug\claw.exe --model "llama3.2" prompt "reply with ready"
|
||||
.\target\debug\brewcode.exe --model "llama3.2" prompt "reply with ready"
|
||||
```
|
||||
|
||||
If the local server is authless, remove `OPENAI_API_KEY` instead of putting a real cloud key into local testing:
|
||||
|
|
@ -159,7 +159,7 @@ Remove-Item Env:\OPENAI_API_KEY -ErrorAction SilentlyContinue
|
|||
Remove-Item Env:\ANTHROPIC_API_KEY -ErrorAction SilentlyContinue
|
||||
$env:DASHSCOPE_API_KEY = "sk-REPLACE_ME"
|
||||
|
||||
.\target\debug\claw.exe --model "qwen-plus" prompt "reply with ready"
|
||||
.\target\debug\brewcode.exe --model "qwen-plus" prompt "reply with ready"
|
||||
```
|
||||
|
||||
## Windows and WSL notifications
|
||||
|
|
@ -169,8 +169,8 @@ Notification support is exposed through the `notifications` slash command in the
|
|||
Native PowerShell smoke path:
|
||||
|
||||
```powershell
|
||||
Set-Location .\claw-code\rust
|
||||
.\target\debug\claw.exe
|
||||
Set-Location .\brew-code\rust
|
||||
.\target\debug\brewcode.exe
|
||||
# inside the REPL:
|
||||
/notifications
|
||||
```
|
||||
|
|
@ -178,18 +178,18 @@ Set-Location .\claw-code\rust
|
|||
WSL smoke path:
|
||||
|
||||
```bash
|
||||
cd claw-code/rust
|
||||
./target/debug/claw
|
||||
cd brew-code/rust
|
||||
./target/debug/brewcode
|
||||
# inside the REPL:
|
||||
/notifications
|
||||
```
|
||||
|
||||
When moving between PowerShell and WSL, keep provider keys in the environment where `claw` is actually running; Windows user env vars set with `setx` are not automatically the same as WSL shell exports.
|
||||
When moving between PowerShell and WSL, keep provider keys in the environment where `brewcode` is actually running; Windows user env vars set with `setx` are not automatically the same as WSL shell exports.
|
||||
|
||||
## Troubleshooting checklist
|
||||
|
||||
- `claw` not found: use `claw.exe` on Windows or run the binary by full path (`.\target\debug\claw.exe`).
|
||||
- `brewcode` not found: use `brewcode.exe` on Windows or run the binary by full path (`.\target\debug\brewcode.exe`).
|
||||
- `cargo` not found: reopen PowerShell after installing Rust from <https://rustup.rs/>.
|
||||
- `401 Invalid bearer token`: put `sk-ant-*` values in `ANTHROPIC_API_KEY`, not `ANTHROPIC_AUTH_TOKEN`.
|
||||
- Wrong provider selected: add an explicit model prefix such as `openai/gpt-4.1-mini`, `qwen-plus`, or `grok`.
|
||||
- Release ZIP extracted but command still fails: open a new terminal after updating the user `Path`, or call `& "$env:LOCALAPPDATA\Programs\claw\claw.exe"` directly.
|
||||
- Release ZIP extracted but command still fails: open a new terminal after updating the user `Path`, or call `& "$env:LOCALAPPDATA\Programs\brewcode\brewcode.exe"` directly.
|
||||
|
|
|
|||
62
install.sh
62
install.sh
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
# Claw Code installer
|
||||
# Brew Code installer
|
||||
#
|
||||
# Detects the host OS, verifies the Rust toolchain (rustc + cargo),
|
||||
# builds the `claw` binary from the `rust/` workspace, and runs a
|
||||
# builds the `brewcode` binary from the `rust/` workspace, and runs a
|
||||
# post-install verification step. Supports Linux, macOS, and WSL.
|
||||
#
|
||||
# Usage:
|
||||
|
|
@ -12,8 +12,8 @@
|
|||
# ./install.sh --help # print usage
|
||||
#
|
||||
# Environment overrides:
|
||||
# CLAW_BUILD_PROFILE=debug|release same as --release toggle
|
||||
# CLAW_SKIP_VERIFY=1 same as --no-verify
|
||||
# BREWCODE_BUILD_PROFILE=debug|release same as --release toggle
|
||||
# BREWCODE_SKIP_VERIFY=1 same as --no-verify
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ print_banner() {
|
|||
\____||_| \__,_| \_/\_/ \____\___/ \__,_|\___|
|
||||
EOF
|
||||
printf '%s\n' "${COLOR_RESET}"
|
||||
printf '%sClaw Code installer%s\n' "${COLOR_DIM}" "${COLOR_RESET}"
|
||||
printf '%sBrew Code installer%s\n' "${COLOR_DIM}" "${COLOR_RESET}"
|
||||
}
|
||||
|
||||
print_usage() {
|
||||
|
|
@ -80,8 +80,8 @@ Options:
|
|||
-h, --help Show this help text and exit.
|
||||
|
||||
Environment overrides:
|
||||
CLAW_BUILD_PROFILE debug | release
|
||||
CLAW_SKIP_VERIFY set to 1 to skip verification
|
||||
BREWCODE_BUILD_PROFILE debug | release
|
||||
BREWCODE_SKIP_VERIFY set to 1 to skip verification
|
||||
EOF
|
||||
}
|
||||
|
||||
|
|
@ -89,8 +89,8 @@ EOF
|
|||
# Argument parsing
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
BUILD_PROFILE="${CLAW_BUILD_PROFILE:-debug}"
|
||||
SKIP_VERIFY="${CLAW_SKIP_VERIFY:-0}"
|
||||
BUILD_PROFILE="${BREWCODE_BUILD_PROFILE:-debug}"
|
||||
SKIP_VERIFY="${BREWCODE_SKIP_VERIFY:-0}"
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
|
|
@ -163,9 +163,9 @@ ${COLOR_DIM}---------------${COLOR_RESET}
|
|||
cd rust && cargo clean && cargo build --workspace
|
||||
If the failure mentions ring/openssl, double check step 2.
|
||||
|
||||
${COLOR_BOLD}6. 'claw' not found after install${COLOR_RESET}
|
||||
${COLOR_BOLD}6. 'brewcode' not found after install${COLOR_RESET}
|
||||
The binary lives at:
|
||||
rust/target/${BUILD_PROFILE}/claw
|
||||
rust/target/${BUILD_PROFILE}/brewcode
|
||||
Add it to your PATH or invoke it with the full path.
|
||||
|
||||
EOF
|
||||
|
|
@ -305,7 +305,7 @@ fi
|
|||
# Step 4: build the workspace
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
step "Building the claw workspace (${BUILD_PROFILE})"
|
||||
step "Building the brewcode workspace (${BUILD_PROFILE})"
|
||||
|
||||
CARGO_FLAGS=("build" "--workspace")
|
||||
if [ "${BUILD_PROFILE}" = "release" ]; then
|
||||
|
|
@ -320,15 +320,15 @@ info "this may take a few minutes on the first build"
|
|||
CARGO_TERM_COLOR="${CARGO_TERM_COLOR:-always}" cargo "${CARGO_FLAGS[@]}"
|
||||
)
|
||||
|
||||
CLAW_BIN="${RUST_DIR}/target/${BUILD_PROFILE}/claw"
|
||||
BREWCODE_BIN="${RUST_DIR}/target/${BUILD_PROFILE}/brewcode"
|
||||
|
||||
if [ ! -x "${CLAW_BIN}" ]; then
|
||||
error "Expected binary not found at ${CLAW_BIN}"
|
||||
if [ ! -x "${BREWCODE_BIN}" ]; then
|
||||
error "Expected binary not found at ${BREWCODE_BIN}"
|
||||
error "The build reported success but the binary is missing — check cargo output above."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ok "built ${CLAW_BIN}"
|
||||
ok "built ${BREWCODE_BIN}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Step 5: post-install verification
|
||||
|
|
@ -337,22 +337,22 @@ ok "built ${CLAW_BIN}"
|
|||
step "Verifying the installed binary"
|
||||
|
||||
if [ "${SKIP_VERIFY}" = "1" ]; then
|
||||
warn "verification skipped (--no-verify or CLAW_SKIP_VERIFY=1)"
|
||||
warn "verification skipped (--no-verify or BREWCODE_SKIP_VERIFY=1)"
|
||||
else
|
||||
info "running: claw --version"
|
||||
if VERSION_OUT="$("${CLAW_BIN}" --version 2>&1)"; then
|
||||
ok "claw --version -> ${VERSION_OUT}"
|
||||
info "running: brewcode --version"
|
||||
if VERSION_OUT="$("${BREWCODE_BIN}" --version 2>&1)"; then
|
||||
ok "brewcode --version -> ${VERSION_OUT}"
|
||||
else
|
||||
error "claw --version failed:"
|
||||
error "brewcode --version failed:"
|
||||
printf '%s\n' "${VERSION_OUT}" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "running: claw --help (smoke test)"
|
||||
if "${CLAW_BIN}" --help >/dev/null 2>&1; then
|
||||
ok "claw --help responded"
|
||||
info "running: brewcode --help (smoke test)"
|
||||
if "${BREWCODE_BIN}" --help >/dev/null 2>&1; then
|
||||
ok "brewcode --help responded"
|
||||
else
|
||||
error "claw --help failed"
|
||||
error "brewcode --help failed"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
|
@ -364,28 +364,28 @@ fi
|
|||
step "Next steps"
|
||||
|
||||
cat <<EOF
|
||||
${COLOR_GREEN}Claw Code is built and ready.${COLOR_RESET}
|
||||
${COLOR_GREEN}Brew Code is built and ready.${COLOR_RESET}
|
||||
|
||||
Binary: ${COLOR_BOLD}${CLAW_BIN}${COLOR_RESET}
|
||||
Binary: ${COLOR_BOLD}${BREWCODE_BIN}${COLOR_RESET}
|
||||
Profile: ${BUILD_PROFILE}
|
||||
|
||||
Try it out:
|
||||
|
||||
${COLOR_DIM}# interactive REPL${COLOR_RESET}
|
||||
${CLAW_BIN}
|
||||
${BREWCODE_BIN}
|
||||
|
||||
${COLOR_DIM}# one-shot prompt${COLOR_RESET}
|
||||
${CLAW_BIN} prompt "summarize this repository"
|
||||
${BREWCODE_BIN} prompt "summarize this repository"
|
||||
|
||||
${COLOR_DIM}# health check (run /doctor inside the REPL)${COLOR_RESET}
|
||||
${CLAW_BIN}
|
||||
${BREWCODE_BIN}
|
||||
/doctor
|
||||
|
||||
Authentication:
|
||||
|
||||
export ANTHROPIC_API_KEY="sk-ant-..."
|
||||
${COLOR_DIM}# or use OAuth:${COLOR_RESET}
|
||||
${CLAW_BIN} login
|
||||
${BREWCODE_BIN} login
|
||||
|
||||
For deeper docs, see USAGE.md and rust/README.md.
|
||||
EOF
|
||||
|
|
|
|||
14
prd.json
14
prd.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"version": "1.0",
|
||||
"description": "Clawable Coding Harness - Clear roadmap stories and commit each",
|
||||
"description": "Brewcodeable Coding Harness - Clear roadmap stories and commit each",
|
||||
"stories": [
|
||||
{
|
||||
"id": "US-001",
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
"Duplicate terminal-event suppression with fingerprinting",
|
||||
"Lane ownership/scope binding in events",
|
||||
"Nudge acknowledgment with dedupe contract",
|
||||
"clawhip consumes typed lane events instead of pane scraping"
|
||||
"brewcodehip consumes typed lane events instead of pane scraping"
|
||||
],
|
||||
"passes": true,
|
||||
"priority": "P0"
|
||||
|
|
@ -194,7 +194,7 @@
|
|||
{
|
||||
"id": "US-014",
|
||||
"title": "Phase 2 - Event provenance / environment labeling",
|
||||
"description": "Every emitted event should declare its source (live_lane, test, healthcheck, replay, transport) so claws do not mistake test noise for production truth. Include environment/channel label, emitter identity, and confidence/trust level.",
|
||||
"description": "Every emitted event should declare its source (live_lane, test, healthcheck, replay, transport) so brewcodes do not mistake test noise for production truth. Include environment/channel label, emitter identity, and confidence/trust level.",
|
||||
"acceptanceCriteria": [
|
||||
"EventProvenance enum with live_lane, test, healthcheck, replay, transport variants",
|
||||
"Environment/channel label attached to all events",
|
||||
|
|
@ -235,10 +235,10 @@
|
|||
{
|
||||
"id": "US-017",
|
||||
"title": "Phase 2 - Lane ownership / scope binding",
|
||||
"description": "Each session and lane event should declare who owns it and what workflow scope it belongs to. Attach owner/assignee identity, workflow scope (claw-code-dogfood, external-git-maintenance, infra-health, manual-operator), and mark whether watcher is expected to act, observe only, or ignore.",
|
||||
"description": "Each session and lane event should declare who owns it and what workflow scope it belongs to. Attach owner/assignee identity, workflow scope (brew-code-dogfood, external-git-maintenance, infra-health, manual-operator), and mark whether watcher is expected to act, observe only, or ignore.",
|
||||
"acceptanceCriteria": [
|
||||
"Owner/assignee identity attached to sessions and lane events",
|
||||
"Workflow scope field (claw-code-dogfood, external-git-maintenance, etc.)",
|
||||
"Workflow scope field (brew-code-dogfood, external-git-maintenance, etc.)",
|
||||
"Watcher action expectation field (act, observe-only, ignore)",
|
||||
"Preserve scope through session restarts, resumes, and late terminal events",
|
||||
"Tests verify ownership and scope binding"
|
||||
|
|
@ -249,7 +249,7 @@
|
|||
{
|
||||
"id": "US-018",
|
||||
"title": "Phase 2 - Nudge acknowledgment / dedupe contract",
|
||||
"description": "Periodic clawhip nudges should carry nudge id/cycle id and delivery timestamp. Expose whether claw has already acknowledged or responded for that cycle. Distinguish new nudge, retry nudge, and stale duplicate.",
|
||||
"description": "Periodic brewcodehip nudges should carry nudge id/cycle id and delivery timestamp. Expose whether brewcode has already acknowledged or responded for that cycle. Distinguish new nudge, retry nudge, and stale duplicate.",
|
||||
"acceptanceCriteria": [
|
||||
"Nudge id / cycle id and delivery timestamp attached",
|
||||
"Acknowledgment state exposed (already acknowledged or not)",
|
||||
|
|
@ -263,7 +263,7 @@
|
|||
{
|
||||
"id": "US-019",
|
||||
"title": "Phase 2 - Stable roadmap-id assignment for newly filed pinpoints",
|
||||
"description": "When a claw records a new pinpoint/follow-up, assign or expose a stable tracking id immediately. Expose that id in structured event/report payload and preserve across edits, reorderings, and summary compression.",
|
||||
"description": "When a brewcode records a new pinpoint/follow-up, assign or expose a stable tracking id immediately. Expose that id in structured event/report payload and preserve across edits, reorderings, and summary compression.",
|
||||
"acceptanceCriteria": [
|
||||
"Canonical roadmap id assigned at filing time",
|
||||
"Roadmap id exposed in structured event/report payload",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
# run-brewcode.ps1 — launch brewcode with an Ollama Cloud model.
|
||||
#
|
||||
# Reads OPENAI_API_KEY / OPENAI_BASE_URL from .env and exports them into the
|
||||
# session environment, because brewcode only honors OPENAI_BASE_URL from the real
|
||||
# process environment (not from the .env file directly).
|
||||
#
|
||||
# Usage:
|
||||
# .\run-brewcode.ps1 # default model (kimi-k2.6)
|
||||
# .\run-brewcode.ps1 qwen3-coder:480b # any Ollama Cloud model id
|
||||
# .\run-brewcode.ps1 kimi-k2-thinking
|
||||
#
|
||||
# The "openai/" prefix is added automatically — required so kimi-* models
|
||||
# route to Ollama Cloud instead of DashScope.
|
||||
|
||||
param(
|
||||
[string]$Model = "kimi-k2.6"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$envFile = Join-Path $PSScriptRoot ".env"
|
||||
|
||||
if (-not (Test-Path $envFile)) {
|
||||
Write-Error ".env not found at $envFile"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Parse simple KEY=VALUE lines from .env (ignores blanks and # comments).
|
||||
foreach ($line in Get-Content $envFile) {
|
||||
$trimmed = $line.Trim()
|
||||
if ($trimmed -eq "" -or $trimmed.StartsWith("#")) { continue }
|
||||
$eq = $trimmed.IndexOf("=")
|
||||
if ($eq -lt 1) { continue }
|
||||
$key = $trimmed.Substring(0, $eq).Trim()
|
||||
$val = $trimmed.Substring($eq + 1).Trim()
|
||||
if ($key -eq "OPENAI_API_KEY" -or $key -eq "OPENAI_BASE_URL") {
|
||||
Set-Item -Path "Env:$key" -Value $val
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $env:OPENAI_API_KEY -or -not $env:OPENAI_BASE_URL) {
|
||||
Write-Error "OPENAI_API_KEY / OPENAI_BASE_URL not found (uncommented) in .env"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Endpoint : $env:OPENAI_BASE_URL"
|
||||
Write-Host "Model : openai/$Model"
|
||||
Write-Host ""
|
||||
|
||||
& "$PSScriptRoot\rust\target\debug\brewcode.exe" --model "openai/$Model" --dangerously-skip-permissions
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# TUI Enhancement Plan — Claw Code (`rusty-claude-cli`)
|
||||
# TUI Enhancement Plan — Brew Code (`rusty-claude-cli`)
|
||||
|
||||
## Executive Summary
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claw Code (clawcode.dev) when working with code in this repository.
|
||||
This file provides guidance to Brew Code (clawcode.dev) when working with code in this repository.
|
||||
|
||||
## Detected stack
|
||||
- Languages: Rust.
|
||||
|
|
@ -12,5 +12,5 @@ This file provides guidance to Claw Code (clawcode.dev) when working with code i
|
|||
|
||||
## Working agreement
|
||||
- Prefer small, reviewable changes and keep generated bootstrap files aligned with actual repo workflows.
|
||||
- Keep shared defaults in `.claw.json`; reserve `.claw/settings.local.json` for machine-local overrides.
|
||||
- Keep shared defaults in `.brewcode.json`; reserve `.brewcode/settings.local.json` for machine-local overrides.
|
||||
- Do not overwrite existing `CLAUDE.md` content automatically; update it intentionally when repo workflows change.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Mock LLM parity harness
|
||||
|
||||
This milestone adds a deterministic Anthropic-compatible mock service plus a reproducible CLI harness for the Rust `claw` binary.
|
||||
This milestone adds a deterministic Anthropic-compatible mock service plus a reproducible CLI harness for the Rust `brewcode` binary.
|
||||
|
||||
## Artifacts
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Parity Status — claw-code Rust Port
|
||||
# Parity Status — brew-code Rust Port
|
||||
|
||||
Last updated: 2026-04-03
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 🦞 Claw Code — Rust Implementation
|
||||
# 🦞 Brew Code — Rust Implementation
|
||||
|
||||
A high-performance Rust rewrite of the Claw Code CLI agent harness. Built for speed, safety, and native tool execution.
|
||||
A high-performance Rust rewrite of the Brew Code CLI agent harness. Built for speed, safety, and native tool execution.
|
||||
|
||||
For a task-oriented guide with copy/paste examples, see [`../USAGE.md`](../USAGE.md).
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ Primary artifacts:
|
|||
| Todo tracking | ✅ |
|
||||
| Notebook editing | ✅ |
|
||||
| CLAUDE.md / project memory | ✅ |
|
||||
| Config file hierarchy (`.claw.json` + merged config sections) | ✅ |
|
||||
| Config file hierarchy (`.brewcode.json` + merged config sections) | ✅ |
|
||||
| Permission system | ✅ |
|
||||
| MCP server lifecycle + inspection | ✅ |
|
||||
| Session persistence + resume | ✅ |
|
||||
|
|
@ -118,7 +118,7 @@ Short names resolve to the latest model versions:
|
|||
Representative current surface:
|
||||
|
||||
```text
|
||||
claw [OPTIONS] [COMMAND]
|
||||
brewcode [OPTIONS] [COMMAND]
|
||||
|
||||
Flags:
|
||||
--model MODEL
|
||||
|
|
@ -145,7 +145,7 @@ Top-level commands:
|
|||
init
|
||||
```
|
||||
|
||||
`claw acp` is a local discoverability surface for editor-first users: it reports the current ACP/Zed status without starting the runtime. As of April 16, 2026, claw-code does **not** ship an ACP/Zed daemon or JSON-RPC entrypoint yet, and `claw acp serve` is only a status alias until the real protocol surface lands. Status queries exit 0 and expose the same machine-readable contract via `--output-format json`; malformed ACP invocations exit 1 with `kind: unsupported_acp_invocation`.
|
||||
`brewcode acp` is a local discoverability surface for editor-first users: it reports the current ACP/Zed status without starting the runtime. As of April 16, 2026, brew-code does **not** ship an ACP/Zed daemon or JSON-RPC entrypoint yet, and `brewcode acp serve` is only a status alias until the real protocol surface lands. Status queries exit 0 and expose the same machine-readable contract via `--output-format json`; malformed ACP invocations exit 1 with `kind: unsupported_acp_invocation`.
|
||||
|
||||
The command surface is moving quickly. For the canonical live help text, run:
|
||||
|
||||
|
|
@ -165,7 +165,7 @@ The REPL now exposes a much broader surface than the original minimal shell:
|
|||
- automation / analysis: `/review`, `/advisor`, `/insights`, `/security-review`, `/subagent`, `/team`, `/telemetry`, `/providers`, `/cron`, and more
|
||||
- plugin management: `/plugin` (with aliases `/plugins`, `/marketplace`)
|
||||
|
||||
Notable claw-first surfaces now available directly in slash form:
|
||||
Notable brewcode-first surfaces now available directly in slash form:
|
||||
- `/skills [list|install <path>|help]`
|
||||
- `/agents [list|help]`
|
||||
- `/mcp [list|show <server>|help]`
|
||||
|
|
@ -188,7 +188,7 @@ rust/
|
|||
├── mock-anthropic-service/ # Deterministic local Anthropic-compatible mock
|
||||
├── plugins/ # Plugin metadata, manager, install/enable/disable surfaces
|
||||
├── runtime/ # Session, config, permissions, MCP, prompts, auth/runtime loop
|
||||
├── rusty-claude-cli/ # Main CLI binary (`claw`)
|
||||
├── rusty-claude-cli/ # Main CLI binary (`brewcode`)
|
||||
├── telemetry/ # Session tracing and usage telemetry types
|
||||
└── tools/ # Built-in tools, skill resolution, tool search, agent runtime surfaces
|
||||
```
|
||||
|
|
@ -209,7 +209,7 @@ rust/
|
|||
|
||||
- **~20K lines** of Rust
|
||||
- **9 crates** in workspace
|
||||
- **Binary name:** `claw`
|
||||
- **Binary name:** `brewcode`
|
||||
- **Default model:** `claude-opus-4-6`
|
||||
- **Default permissions:** `danger-full-access`
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# TUI Enhancement Plan — Claw Code (`rusty-claude-cli`)
|
||||
# TUI Enhancement Plan — Brew Code (`rusty-claude-cli`)
|
||||
|
||||
## Executive Summary
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@ Use that guide for:
|
|||
|
||||
- workspace build and test commands
|
||||
- authentication setup
|
||||
- interactive and one-shot `claw` examples
|
||||
- interactive and one-shot `brewcode` examples
|
||||
- session resume workflows
|
||||
- mock parity harness commands
|
||||
|
|
|
|||
|
|
@ -235,4 +235,35 @@ mod tests {
|
|||
other => panic!("Expected ProviderClient::OpenAi for qwen-plus, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn openai_model_with_ollama_cloud_base_url_resolves_to_ollama_cloud_gateway() {
|
||||
// Regression: `--model openai/<x>` with `OPENAI_BASE_URL=https://ollama.com/v1`
|
||||
// used to send `model: "openai/<x>"` on the wire and Ollama Cloud 404'd
|
||||
// because it assumed any non-default OpenAI base URL was OpenRouter.
|
||||
// After the Gateway refactor the client must recognize Ollama Cloud
|
||||
// distinctly so the wire model is bare and future per-gateway logic
|
||||
// (body-size pre-flight, error mapping) sees the right backend.
|
||||
use crate::providers::openai_compat::Gateway;
|
||||
|
||||
let _lock = env_lock();
|
||||
let _ollama_key = EnvVarGuard::set("OPENAI_API_KEY", Some("ollama-cloud-key"));
|
||||
let _ollama_url = EnvVarGuard::set("OPENAI_BASE_URL", Some("https://ollama.com/v1"));
|
||||
let _anthropic = EnvVarGuard::set("ANTHROPIC_API_KEY", None);
|
||||
let _anthropic_tok = EnvVarGuard::set("ANTHROPIC_AUTH_TOKEN", None);
|
||||
|
||||
let client = ProviderClient::from_model("openai/gpt-oss:20b")
|
||||
.expect("openai/<model> with OPENAI_BASE_URL should build");
|
||||
match client {
|
||||
ProviderClient::OpenAi(c) => {
|
||||
assert_eq!(
|
||||
c.gateway(),
|
||||
Gateway::OllamaCloud,
|
||||
"Ollama Cloud base URL must produce OllamaCloud gateway, got {:?}",
|
||||
c.gateway()
|
||||
);
|
||||
}
|
||||
other => panic!("Expected ProviderClient::OpenAi, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1086,7 +1086,7 @@ mod tests {
|
|||
let _guard = env_lock();
|
||||
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
||||
std::env::remove_var("ANTHROPIC_API_KEY");
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
let error = super::read_api_key().expect_err("missing key should error");
|
||||
assert!(matches!(
|
||||
error,
|
||||
|
|
@ -1156,7 +1156,7 @@ mod tests {
|
|||
fn auth_source_from_env_or_saved_ignores_saved_oauth_when_env_absent() {
|
||||
let _guard = env_lock();
|
||||
let config_home = temp_config_home();
|
||||
std::env::set_var("CLAW_CONFIG_HOME", &config_home);
|
||||
std::env::set_var("BREWCODE_CONFIG_HOME", &config_home);
|
||||
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
||||
std::env::remove_var("ANTHROPIC_API_KEY");
|
||||
save_oauth_credentials(&runtime::OAuthTokenSet {
|
||||
|
|
@ -1171,7 +1171,7 @@ mod tests {
|
|||
assert!(error.to_string().contains("ANTHROPIC_API_KEY"));
|
||||
|
||||
clear_oauth_credentials().expect("clear credentials");
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
cleanup_temp_config_home(&config_home);
|
||||
}
|
||||
|
||||
|
|
@ -1195,7 +1195,7 @@ mod tests {
|
|||
fn resolve_saved_oauth_token_refreshes_expired_credentials() {
|
||||
let _guard = env_lock();
|
||||
let config_home = temp_config_home();
|
||||
std::env::set_var("CLAW_CONFIG_HOME", &config_home);
|
||||
std::env::set_var("BREWCODE_CONFIG_HOME", &config_home);
|
||||
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
||||
std::env::remove_var("ANTHROPIC_API_KEY");
|
||||
save_oauth_credentials(&runtime::OAuthTokenSet {
|
||||
|
|
@ -1219,7 +1219,7 @@ mod tests {
|
|||
assert_eq!(stored.access_token, "refreshed-token");
|
||||
|
||||
clear_oauth_credentials().expect("clear credentials");
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
cleanup_temp_config_home(&config_home);
|
||||
}
|
||||
|
||||
|
|
@ -1227,7 +1227,7 @@ mod tests {
|
|||
fn resolve_startup_auth_source_ignores_saved_oauth_without_loading_config() {
|
||||
let _guard = env_lock();
|
||||
let config_home = temp_config_home();
|
||||
std::env::set_var("CLAW_CONFIG_HOME", &config_home);
|
||||
std::env::set_var("BREWCODE_CONFIG_HOME", &config_home);
|
||||
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
||||
std::env::remove_var("ANTHROPIC_API_KEY");
|
||||
save_oauth_credentials(&runtime::OAuthTokenSet {
|
||||
|
|
@ -1243,7 +1243,7 @@ mod tests {
|
|||
assert!(error.to_string().contains("ANTHROPIC_API_KEY"));
|
||||
|
||||
clear_oauth_credentials().expect("clear credentials");
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
cleanup_temp_config_home(&config_home);
|
||||
}
|
||||
|
||||
|
|
@ -1251,7 +1251,7 @@ mod tests {
|
|||
fn resolve_saved_oauth_token_preserves_refresh_token_when_refresh_response_omits_it() {
|
||||
let _guard = env_lock();
|
||||
let config_home = temp_config_home();
|
||||
std::env::set_var("CLAW_CONFIG_HOME", &config_home);
|
||||
std::env::set_var("BREWCODE_CONFIG_HOME", &config_home);
|
||||
std::env::remove_var("ANTHROPIC_AUTH_TOKEN");
|
||||
std::env::remove_var("ANTHROPIC_API_KEY");
|
||||
save_oauth_credentials(&runtime::OAuthTokenSet {
|
||||
|
|
@ -1276,7 +1276,7 @@ mod tests {
|
|||
assert_eq!(stored.refresh_token.as_deref(), Some("refresh-token"));
|
||||
|
||||
clear_oauth_credentials().expect("clear credentials");
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
cleanup_temp_config_home(&config_home);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -347,8 +347,7 @@ pub fn detect_provider_kind(model: &str) -> ProviderKind {
|
|||
// even when the model name has no recognized prefix — this is the
|
||||
// common case for local providers (Ollama, LM Studio, vLLM, etc.)
|
||||
// where model names like "qwen2.5-coder:7b" don't match any prefix.
|
||||
if std::env::var_os("OPENAI_BASE_URL").is_some() && openai_compat::has_api_key("OPENAI_API_KEY")
|
||||
{
|
||||
if env_or_dotenv_present("OPENAI_BASE_URL") && openai_compat::has_api_key("OPENAI_API_KEY") {
|
||||
return ProviderKind::OpenAi;
|
||||
}
|
||||
if anthropic::has_auth_from_env_or_saved().unwrap_or(false) {
|
||||
|
|
@ -362,7 +361,7 @@ pub fn detect_provider_kind(model: &str) -> ProviderKind {
|
|||
}
|
||||
// Last resort: if OPENAI_BASE_URL is set without OPENAI_API_KEY (some
|
||||
// local providers like Ollama don't require auth), still route there.
|
||||
if std::env::var_os("OPENAI_BASE_URL").is_some() {
|
||||
if env_or_dotenv_present("OPENAI_BASE_URL") {
|
||||
return ProviderKind::OpenAi;
|
||||
}
|
||||
ProviderKind::Anthropic
|
||||
|
|
@ -625,6 +624,17 @@ pub fn model_token_limit(model: &str) -> Option<ModelTokenLimit> {
|
|||
max_output_tokens: 32_768,
|
||||
context_window_tokens: 1_047_576,
|
||||
}),
|
||||
// GPT-4o family via the OpenAI API.
|
||||
"gpt-4o" | "gpt-4o-mini" => Some(ModelTokenLimit {
|
||||
max_output_tokens: 16_384,
|
||||
context_window_tokens: 128_000,
|
||||
}),
|
||||
// Qwen3-Coder via Ollama Cloud (or any OpenAI-compat gateway exposing
|
||||
// the same model IDs). Context window per Alibaba: up to 256K tokens.
|
||||
"qwen3-coder:480b" | "qwen3-coder:30b" | "qwen3-coder" => Some(ModelTokenLimit {
|
||||
max_output_tokens: 32_768,
|
||||
context_window_tokens: 256_000,
|
||||
}),
|
||||
// GPT-5.4 family via the OpenAI API.
|
||||
"gpt-5.4" => Some(ModelTokenLimit {
|
||||
max_output_tokens: 128_000,
|
||||
|
|
@ -1036,7 +1046,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn qwen_prefix_routes_to_dashscope_not_anthropic() {
|
||||
// User request from Discord #clawcode-get-help: web3g wants to use
|
||||
// User request from Discord #brewcodecode-get-help: web3g wants to use
|
||||
// Qwen 3.6 Plus via native Alibaba DashScope API (not OpenRouter,
|
||||
// which has lower rate limits). metadata_for_model must route
|
||||
// qwen/* and bare qwen-* to the OpenAi provider kind pointed at
|
||||
|
|
@ -1129,8 +1139,8 @@ mod tests {
|
|||
.as_nanos();
|
||||
let root = std::env::temp_dir().join(format!("api-plugin-max-tokens-{nanos}"));
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
std::fs::create_dir_all(cwd.join(".claw")).expect("project config dir");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
std::fs::create_dir_all(cwd.join(".brewcode")).expect("project config dir");
|
||||
std::fs::create_dir_all(&home).expect("home config dir");
|
||||
std::fs::write(
|
||||
home.join("settings.json"),
|
||||
|
|
@ -1695,4 +1705,12 @@ NO_EQUALS_LINE
|
|||
// (env_lock only protects within a single binary). The detection logic
|
||||
// is covered: OPENAI_BASE_URL alone routes to OpenAi as a last-resort
|
||||
// fallback in detect_provider_kind().
|
||||
|
||||
#[test]
|
||||
fn gpt_4o_mini_is_registered_with_correct_limits() {
|
||||
let limit =
|
||||
model_token_limit("gpt-4o-mini").expect("gpt-4o-mini must be in the model registry");
|
||||
assert_eq!(limit.max_output_tokens, 16_384);
|
||||
assert_eq!(limit.context_window_tokens, 128_000);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ impl OpenAiCompatConfig {
|
|||
|
||||
/// Alibaba `DashScope` compatible-mode endpoint (Qwen family models).
|
||||
/// Uses the OpenAI-compatible REST shape at /compatible-mode/v1.
|
||||
/// Requested via Discord #clawcode-get-help: native Alibaba API for
|
||||
/// Requested via Discord #brewcodecode-get-help: native Alibaba API for
|
||||
/// higher rate limits than going through `OpenRouter`.
|
||||
#[must_use]
|
||||
pub const fn dashscope() -> Self {
|
||||
|
|
@ -97,6 +97,96 @@ impl OpenAiCompatConfig {
|
|||
}
|
||||
}
|
||||
|
||||
/// Concrete OpenAI-compatible gateway behind the user's `OPENAI_BASE_URL` /
|
||||
/// `XAI_BASE_URL` / `DASHSCOPE_BASE_URL`. Each variant owns the conventions
|
||||
/// that distinguish it from the others on the wire.
|
||||
///
|
||||
/// Detection is URL-driven so that downstream consumers do not have to
|
||||
/// re-discover which backend they are talking to. The mapping is intentionally
|
||||
/// explicit: a new backend ships as a new variant plus one URL pattern, never
|
||||
/// as another guess inside request-building code.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Gateway {
|
||||
/// `https://api.openai.com/v1` — bare model names.
|
||||
OpenAi,
|
||||
/// `https://openrouter.ai/api/v1` — slash-namespaced slugs
|
||||
/// (for example `openai/gpt-4.1-mini`) MUST be preserved on the wire.
|
||||
OpenRouter,
|
||||
/// `https://ollama.com/v1` (Ollama Cloud) — bare model names.
|
||||
OllamaCloud,
|
||||
/// `http://127.0.0.1:11434/v1`, `http://localhost:11434/v1`,
|
||||
/// `http://0.0.0.0:11434/v1` — bare model names.
|
||||
OllamaLocal,
|
||||
/// `https://api.x.ai/v1` — bare model names.
|
||||
XAi,
|
||||
/// `https://dashscope.aliyuncs.com/compatible-mode/v1` — bare model names.
|
||||
DashScope,
|
||||
/// Anything else with a custom `OPENAI_BASE_URL` (vLLM, llama.cpp,
|
||||
/// LiteLLM, in-house proxies, ...). Conservative defaults: bare model
|
||||
/// names, no slug preservation.
|
||||
Generic,
|
||||
}
|
||||
|
||||
impl Gateway {
|
||||
/// Identify the gateway behind a given base URL. Matching is
|
||||
/// case-insensitive and tolerates trailing slashes. Returns
|
||||
/// [`Gateway::Generic`] when nothing else matches so that unknown
|
||||
/// OpenAI-compatible backends still work with safe defaults.
|
||||
#[must_use]
|
||||
pub fn detect_from_base_url(url: &str) -> Self {
|
||||
let lower = url.trim_end_matches('/').to_ascii_lowercase();
|
||||
let default_openai = DEFAULT_OPENAI_BASE_URL
|
||||
.trim_end_matches('/')
|
||||
.to_ascii_lowercase();
|
||||
if lower == default_openai {
|
||||
return Self::OpenAi;
|
||||
}
|
||||
if lower.contains("openrouter.ai") {
|
||||
return Self::OpenRouter;
|
||||
}
|
||||
if lower.contains("ollama.com") {
|
||||
return Self::OllamaCloud;
|
||||
}
|
||||
if lower.contains("127.0.0.1:11434")
|
||||
|| lower.contains("localhost:11434")
|
||||
|| lower.contains("0.0.0.0:11434")
|
||||
{
|
||||
return Self::OllamaLocal;
|
||||
}
|
||||
if lower.contains("api.x.ai") {
|
||||
return Self::XAi;
|
||||
}
|
||||
if lower.contains("dashscope.aliyuncs.com") {
|
||||
return Self::DashScope;
|
||||
}
|
||||
Self::Generic
|
||||
}
|
||||
|
||||
/// Whether the gateway expects `openai/<model>` slugs to be preserved
|
||||
/// in the wire request body. Only OpenRouter's namespace convention
|
||||
/// requires this; every other backend wants the bare model name and
|
||||
/// will 404 on a leaked prefix.
|
||||
#[must_use]
|
||||
pub const fn preserves_slug_prefix(self) -> bool {
|
||||
matches!(self, Self::OpenRouter)
|
||||
}
|
||||
|
||||
/// Stable identifier suitable for diagnostics, logs, and the `brewcode
|
||||
/// doctor` report. Does not leak into the wire protocol.
|
||||
#[must_use]
|
||||
pub const fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::OpenAi => "OpenAI",
|
||||
Self::OpenRouter => "OpenRouter",
|
||||
Self::OllamaCloud => "Ollama Cloud",
|
||||
Self::OllamaLocal => "Ollama (local)",
|
||||
Self::XAi => "xAI",
|
||||
Self::DashScope => "DashScope",
|
||||
Self::Generic => "Generic OpenAI-compatible",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OpenAiCompatClient {
|
||||
http: reqwest::Client,
|
||||
|
|
@ -117,6 +207,16 @@ impl OpenAiCompatClient {
|
|||
pub fn base_url(&self) -> &str {
|
||||
&self.base_url
|
||||
}
|
||||
|
||||
/// Concrete gateway this client is talking to, derived from its base
|
||||
/// URL. Cheap (no env access, just substring matching) so callers are
|
||||
/// free to use this in hot paths instead of re-detecting from a raw
|
||||
/// URL string. Surfaces in diagnostics and is the single source of
|
||||
/// truth for wire-level conventions.
|
||||
#[must_use]
|
||||
pub fn gateway(&self) -> Gateway {
|
||||
Gateway::detect_from_base_url(&self.base_url)
|
||||
}
|
||||
#[must_use]
|
||||
pub fn new(api_key: impl Into<String>, config: OpenAiCompatConfig) -> Self {
|
||||
Self {
|
||||
|
|
@ -926,14 +1026,16 @@ fn wire_model_for_base_url<'a>(
|
|||
let lowered_prefix = prefix.to_ascii_lowercase();
|
||||
|
||||
if lowered_prefix == "openai" {
|
||||
let trimmed_base_url = base_url.trim_end_matches('/');
|
||||
let default_openai = DEFAULT_OPENAI_BASE_URL.trim_end_matches('/');
|
||||
if config.provider_name == "OpenAI" && trimmed_base_url != default_openai {
|
||||
// OpenAI-compatible gateways such as OpenRouter commonly use
|
||||
// slash-containing model slugs (for example `openai/gpt-4.1-mini`).
|
||||
// Preserve the slug when the user configured a non-default OpenAI
|
||||
// base URL; the prefix still routed to the OpenAI-compatible client,
|
||||
// but the gateway owns the final model namespace.
|
||||
// Only OpenRouter-style gateways expect the `openai/<model>` slug
|
||||
// preserved in the wire body. Every other OpenAI-compatible backend
|
||||
// (default OpenAI, Ollama Cloud, local Ollama, llama.cpp, vLLM,
|
||||
// generic proxies) wants the bare model name and 404s on a leaked
|
||||
// prefix. The gateway identity is derived from the configured base
|
||||
// URL so this stays correct even when the user points
|
||||
// `OPENAI_BASE_URL` at a non-OpenRouter backend.
|
||||
if config.provider_name == "OpenAI"
|
||||
&& Gateway::detect_from_base_url(base_url).preserves_slug_prefix()
|
||||
{
|
||||
return Cow::Borrowed(model);
|
||||
}
|
||||
return Cow::Borrowed(&model[pos + 1..]);
|
||||
|
|
@ -1514,7 +1616,14 @@ pub fn has_api_key(key: &str) -> bool {
|
|||
|
||||
#[must_use]
|
||||
pub fn read_base_url(config: OpenAiCompatConfig) -> String {
|
||||
std::env::var(config.base_url_env).unwrap_or_else(|_| config.default_base_url.to_string())
|
||||
// Resolve via the same process-env-then-`.env`-file path used for the API
|
||||
// key. Reading only the process env here let a base URL configured solely
|
||||
// in `.env` be silently dropped, sending requests to the provider default
|
||||
// endpoint with a mismatched key.
|
||||
read_env_non_empty(config.base_url_env)
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_else(|| config.default_base_url.to_string())
|
||||
}
|
||||
|
||||
fn chat_completions_endpoint(base_url: &str) -> String {
|
||||
|
|
@ -2648,4 +2757,218 @@ mod tests {
|
|||
assert_eq!(super::strip_routing_prefix("kimi-k2.5"), "kimi-k2.5"); // no prefix, unchanged
|
||||
assert_eq!(super::strip_routing_prefix("kimi/kimi-k1.5"), "kimi-k1.5");
|
||||
}
|
||||
|
||||
// ----- Gateway identification ------------------------------------------
|
||||
//
|
||||
// These tests pin down which OpenAI-compatible backend a given base URL
|
||||
// resolves to. The mapping is the single source of truth for wire-level
|
||||
// conventions (slug preservation, future per-gateway tuning) and must
|
||||
// stay stable: a regression here silently breaks every consumer.
|
||||
|
||||
#[test]
|
||||
fn gateway_detects_default_openai() {
|
||||
assert_eq!(
|
||||
super::Gateway::detect_from_base_url("https://api.openai.com/v1"),
|
||||
super::Gateway::OpenAi
|
||||
);
|
||||
// trailing slash tolerated
|
||||
assert_eq!(
|
||||
super::Gateway::detect_from_base_url("https://api.openai.com/v1/"),
|
||||
super::Gateway::OpenAi
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_detects_openrouter() {
|
||||
assert_eq!(
|
||||
super::Gateway::detect_from_base_url("https://openrouter.ai/api/v1"),
|
||||
super::Gateway::OpenRouter
|
||||
);
|
||||
// case-insensitive
|
||||
assert_eq!(
|
||||
super::Gateway::detect_from_base_url("https://OPENROUTER.AI/api/v1"),
|
||||
super::Gateway::OpenRouter
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_detects_ollama_cloud() {
|
||||
assert_eq!(
|
||||
super::Gateway::detect_from_base_url("https://ollama.com/v1"),
|
||||
super::Gateway::OllamaCloud
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_detects_local_ollama_variants() {
|
||||
assert_eq!(
|
||||
super::Gateway::detect_from_base_url("http://127.0.0.1:11434/v1"),
|
||||
super::Gateway::OllamaLocal
|
||||
);
|
||||
assert_eq!(
|
||||
super::Gateway::detect_from_base_url("http://localhost:11434/v1"),
|
||||
super::Gateway::OllamaLocal
|
||||
);
|
||||
assert_eq!(
|
||||
super::Gateway::detect_from_base_url("http://0.0.0.0:11434/v1"),
|
||||
super::Gateway::OllamaLocal
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_detects_xai_and_dashscope() {
|
||||
assert_eq!(
|
||||
super::Gateway::detect_from_base_url("https://api.x.ai/v1"),
|
||||
super::Gateway::XAi
|
||||
);
|
||||
assert_eq!(
|
||||
super::Gateway::detect_from_base_url(
|
||||
"https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
),
|
||||
super::Gateway::DashScope
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_unknown_url_falls_back_to_generic() {
|
||||
// vLLM / llama.cpp / LiteLLM / in-house proxies on arbitrary URLs.
|
||||
assert_eq!(
|
||||
super::Gateway::detect_from_base_url("http://127.0.0.1:8000/v1"),
|
||||
super::Gateway::Generic
|
||||
);
|
||||
assert_eq!(
|
||||
super::Gateway::detect_from_base_url("https://my-proxy.example.com/v1"),
|
||||
super::Gateway::Generic
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_openrouter_preserves_slug_prefix() {
|
||||
// Contract: OpenRouter is the sole gateway that wants `openai/<model>`
|
||||
// preserved on the wire. Everything else (including Generic) strips.
|
||||
assert!(super::Gateway::OpenRouter.preserves_slug_prefix());
|
||||
assert!(!super::Gateway::OpenAi.preserves_slug_prefix());
|
||||
assert!(!super::Gateway::OllamaCloud.preserves_slug_prefix());
|
||||
assert!(!super::Gateway::OllamaLocal.preserves_slug_prefix());
|
||||
assert!(!super::Gateway::XAi.preserves_slug_prefix());
|
||||
assert!(!super::Gateway::DashScope.preserves_slug_prefix());
|
||||
assert!(!super::Gateway::Generic.preserves_slug_prefix());
|
||||
}
|
||||
|
||||
// ----- wire_model_for_base_url across every gateway --------------------
|
||||
//
|
||||
// These tests directly drive the function that produces the `"model"`
|
||||
// field of each chat-completion request. They are the regression net for
|
||||
// the original Ollama Cloud bug (where `openai/gpt-oss:20b` leaked as-is
|
||||
// and Ollama 404'd).
|
||||
|
||||
#[test]
|
||||
fn wire_model_strips_openai_prefix_for_default_openai() {
|
||||
let wire = super::wire_model_for_base_url(
|
||||
"openai/gpt-4o-mini",
|
||||
super::OpenAiCompatConfig::openai(),
|
||||
"https://api.openai.com/v1",
|
||||
);
|
||||
assert_eq!(wire, "gpt-4o-mini");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wire_model_preserves_slug_for_openrouter() {
|
||||
let wire = super::wire_model_for_base_url(
|
||||
"openai/gpt-4.1-mini",
|
||||
super::OpenAiCompatConfig::openai(),
|
||||
"https://openrouter.ai/api/v1",
|
||||
);
|
||||
assert_eq!(wire, "openai/gpt-4.1-mini");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wire_model_strips_openai_prefix_for_ollama_cloud() {
|
||||
// Regression: previously this left `openai/gpt-oss:20b` in the wire
|
||||
// body and Ollama Cloud 404'd. Must produce the bare model name.
|
||||
let wire = super::wire_model_for_base_url(
|
||||
"openai/gpt-oss:20b",
|
||||
super::OpenAiCompatConfig::openai(),
|
||||
"https://ollama.com/v1",
|
||||
);
|
||||
assert_eq!(wire, "gpt-oss:20b");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wire_model_strips_openai_prefix_for_local_ollama() {
|
||||
let wire = super::wire_model_for_base_url(
|
||||
"openai/qwen3:latest",
|
||||
super::OpenAiCompatConfig::openai(),
|
||||
"http://127.0.0.1:11434/v1",
|
||||
);
|
||||
assert_eq!(wire, "qwen3:latest");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wire_model_strips_openai_prefix_for_generic_gateway() {
|
||||
// vLLM / llama.cpp on a custom port: not OpenRouter, must strip.
|
||||
let wire = super::wire_model_for_base_url(
|
||||
"openai/Qwen2.5-Coder-7B-Instruct",
|
||||
super::OpenAiCompatConfig::openai(),
|
||||
"http://127.0.0.1:8000/v1",
|
||||
);
|
||||
assert_eq!(wire, "Qwen2.5-Coder-7B-Instruct");
|
||||
}
|
||||
|
||||
// ----- OpenAiCompatClient::gateway() per-config integration ------------
|
||||
//
|
||||
// Verifies the client carries the right Gateway identity for every
|
||||
// (config, base URL) combination. Tests use `with_base_url` to inject
|
||||
// the URL directly rather than mutating process env, so they are pure
|
||||
// unit tests and race-free in cargo's default parallel runner.
|
||||
|
||||
#[test]
|
||||
fn client_gateway_follows_base_url_to_default_openai() {
|
||||
let client = super::OpenAiCompatClient::new("test-key", super::OpenAiCompatConfig::openai())
|
||||
.with_base_url("https://api.openai.com/v1");
|
||||
assert_eq!(client.gateway(), super::Gateway::OpenAi);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_gateway_follows_base_url_to_ollama_cloud() {
|
||||
let client = super::OpenAiCompatClient::new("test-key", super::OpenAiCompatConfig::openai())
|
||||
.with_base_url("https://ollama.com/v1");
|
||||
assert_eq!(client.gateway(), super::Gateway::OllamaCloud);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_gateway_follows_base_url_to_openrouter() {
|
||||
let client = super::OpenAiCompatClient::new("test-key", super::OpenAiCompatConfig::openai())
|
||||
.with_base_url("https://openrouter.ai/api/v1");
|
||||
assert_eq!(client.gateway(), super::Gateway::OpenRouter);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_gateway_follows_base_url_to_local_ollama() {
|
||||
let client = super::OpenAiCompatClient::new("test-key", super::OpenAiCompatConfig::openai())
|
||||
.with_base_url("http://127.0.0.1:11434/v1");
|
||||
assert_eq!(client.gateway(), super::Gateway::OllamaLocal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_gateway_follows_base_url_to_xai() {
|
||||
let client = super::OpenAiCompatClient::new("test-key", super::OpenAiCompatConfig::xai())
|
||||
.with_base_url("https://api.x.ai/v1");
|
||||
assert_eq!(client.gateway(), super::Gateway::XAi);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_gateway_follows_base_url_to_dashscope() {
|
||||
let client =
|
||||
super::OpenAiCompatClient::new("test-key", super::OpenAiCompatConfig::dashscope())
|
||||
.with_base_url("https://dashscope.aliyuncs.com/compatible-mode/v1");
|
||||
assert_eq!(client.gateway(), super::Gateway::DashScope);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_gateway_is_generic_for_unknown_proxy() {
|
||||
let client = super::OpenAiCompatClient::new("test-key", super::OpenAiCompatConfig::openai())
|
||||
.with_base_url("https://my-proxy.example.com/v1");
|
||||
assert_eq!(client.gateway(), super::Gateway::Generic);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ async fn send_message_applies_request_profile_and_records_telemetry() {
|
|||
.with_base_url(server.base_url())
|
||||
.with_client_identity(ClientIdentity::new("claude-code", "9.9.9").with_runtime("rust-cli"))
|
||||
.with_beta("tools-2026-04-01")
|
||||
.with_extra_body_param("metadata", json!({"source": "clawd-code"}))
|
||||
.with_extra_body_param("metadata", json!({"source": "brewcoded-code"}))
|
||||
.with_session_tracer(SessionTracer::new("session-telemetry", sink.clone()));
|
||||
|
||||
let response = client
|
||||
|
|
@ -191,7 +191,7 @@ async fn send_message_applies_request_profile_and_records_telemetry() {
|
|||
);
|
||||
let body: serde_json::Value =
|
||||
serde_json::from_str(&request.body).expect("request body should be json");
|
||||
assert_eq!(body["metadata"]["source"], json!("clawd-code"));
|
||||
assert_eq!(body["metadata"]["source"], json!("brewcoded-code"));
|
||||
assert!(
|
||||
body.get("betas").is_none(),
|
||||
"betas must travel via the anthropic-beta header, not the request body"
|
||||
|
|
|
|||
|
|
@ -162,14 +162,27 @@ async fn send_message_preserves_deepseek_reasoning_content_before_text() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn custom_openai_gateway_preserves_slash_model_ids_and_extra_body_params() {
|
||||
async fn custom_openai_gateway_strips_openai_prefix_and_sanitizes_extra_body() {
|
||||
// Contract:
|
||||
// 1. `--model "openai/<name>"` against a generic OpenAI-compatible
|
||||
// gateway (anything other than OpenRouter) MUST send a bare model
|
||||
// name on the wire. Leaking the `openai/` prefix causes 404s on
|
||||
// backends like Ollama Cloud, local Ollama, vLLM, and llama.cpp.
|
||||
// Only OpenRouter preserves the slug — covered by the
|
||||
// `wire_model_preserves_slug_for_openrouter` unit test in the
|
||||
// openai_compat tests module.
|
||||
//
|
||||
// 2. `extra_body` passes user-supplied params through to the server
|
||||
// while quietly dropping any attempt to override sensitive top-
|
||||
// level fields like `model`, `tools`, or `tool_choice` — defends
|
||||
// against prompt-injection style overrides.
|
||||
let state = Arc::new(Mutex::new(Vec::<CapturedRequest>::new()));
|
||||
let body = concat!(
|
||||
"{",
|
||||
"\"id\":\"chatcmpl_slash_model\",",
|
||||
"\"model\":\"openai/gpt-4.1-mini\",",
|
||||
"\"id\":\"chatcmpl_bare_model\",",
|
||||
"\"model\":\"gpt-4.1-mini\",",
|
||||
"\"choices\":[{",
|
||||
"\"message\":{\"role\":\"assistant\",\"content\":\"Gateway accepted slug\",\"tool_calls\":[]},",
|
||||
"\"message\":{\"role\":\"assistant\",\"content\":\"Gateway accepted bare model\",\"tool_calls\":[]},",
|
||||
"\"finish_reason\":\"stop\"",
|
||||
"}],",
|
||||
"\"usage\":{\"prompt_tokens\":3,\"completion_tokens\":2}",
|
||||
|
|
@ -200,13 +213,18 @@ async fn custom_openai_gateway_preserves_slash_model_ids_and_extra_body_params()
|
|||
.await
|
||||
.expect("gateway request should succeed");
|
||||
|
||||
assert_eq!(response.model, "openai/gpt-4.1-mini");
|
||||
// The server echoes back whatever model string it received in its
|
||||
// response — assert this independently of the wire format.
|
||||
assert_eq!(response.model, "gpt-4.1-mini");
|
||||
assert_eq!(response.total_tokens(), 5);
|
||||
|
||||
let captured = state.lock().await;
|
||||
let request = captured.first().expect("captured request");
|
||||
let body: serde_json::Value = serde_json::from_str(&request.body).expect("json body");
|
||||
assert_eq!(body["model"], json!("openai/gpt-4.1-mini"));
|
||||
// The prefix is stripped: mock server's URL is 127.0.0.1:<random-port>
|
||||
// which detects as Gateway::Generic. The user typed `openai/gpt-4.1-mini`
|
||||
// but the wire body carries the bare name. This is the regression fix.
|
||||
assert_eq!(body["model"], json!("gpt-4.1-mini"));
|
||||
assert_eq!(
|
||||
body["web_search_options"],
|
||||
json!({"search_context_size": "low"})
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[
|
|||
SlashCommandSpec {
|
||||
name: "plugin",
|
||||
aliases: &["plugins", "marketplace"],
|
||||
summary: "Manage Claw Code plugins",
|
||||
summary: "Manage Brew Code plugins",
|
||||
argument_hint: Some(
|
||||
"[list|install <path>|enable <name>|disable <name>|uninstall <id>|update <id>]",
|
||||
),
|
||||
|
|
@ -2090,12 +2090,12 @@ pub struct PluginsCommandResult {
|
|||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum DefinitionSource {
|
||||
ProjectClaw,
|
||||
ProjectBrewcode,
|
||||
ProjectCodex,
|
||||
ProjectClaude,
|
||||
UserClawConfigHome,
|
||||
UserBrewcodeConfigHome,
|
||||
UserCodexHome,
|
||||
UserClaw,
|
||||
UserBrewcode,
|
||||
UserCodex,
|
||||
UserClaude,
|
||||
}
|
||||
|
|
@ -2120,11 +2120,11 @@ impl DefinitionScope {
|
|||
impl DefinitionSource {
|
||||
fn report_scope(self) -> DefinitionScope {
|
||||
match self {
|
||||
Self::ProjectClaw | Self::ProjectCodex | Self::ProjectClaude => {
|
||||
Self::ProjectBrewcode | Self::ProjectCodex | Self::ProjectClaude => {
|
||||
DefinitionScope::Project
|
||||
}
|
||||
Self::UserClawConfigHome | Self::UserCodexHome => DefinitionScope::UserConfigHome,
|
||||
Self::UserClaw | Self::UserCodex | Self::UserClaude => DefinitionScope::UserHome,
|
||||
Self::UserBrewcodeConfigHome | Self::UserCodexHome => DefinitionScope::UserConfigHome,
|
||||
Self::UserBrewcode | Self::UserCodex | Self::UserClaude => DefinitionScope::UserHome,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2661,7 +2661,7 @@ fn render_mcp_report_for(
|
|||
Err(err) => {
|
||||
let empty = std::collections::BTreeMap::new();
|
||||
Ok(format!(
|
||||
"Config load error\n Status fail\n Summary runtime config failed to load; reporting partial MCP view\n Details {err}\n Hint `claw doctor` classifies config parse errors; fix the listed field and rerun\n\n{}",
|
||||
"Config load error\n Status fail\n Summary runtime config failed to load; reporting partial MCP view\n Details {err}\n Hint `brewcode doctor` classifies config parse errors; fix the listed field and rerun\n\n{}",
|
||||
render_mcp_summary_report(cwd, &empty)
|
||||
))
|
||||
}
|
||||
|
|
@ -2688,7 +2688,7 @@ fn render_mcp_report_for(
|
|||
runtime_config.mcp().get(server_name),
|
||||
)),
|
||||
Err(err) => Ok(format!(
|
||||
"Config load error\n Status fail\n Summary runtime config failed to load; cannot resolve `{server_name}`\n Details {err}\n Hint `claw doctor` classifies config parse errors; fix the listed field and rerun"
|
||||
"Config load error\n Status fail\n Summary runtime config failed to load; cannot resolve `{server_name}`\n Details {err}\n Hint `brewcode doctor` classifies config parse errors; fix the listed field and rerun"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
|
@ -2696,13 +2696,13 @@ fn render_mcp_report_for(
|
|||
// `mcp list <filter>` — list does not accept arguments; treat as unsupported action.
|
||||
Ok(render_mcp_unsupported_action_text(
|
||||
args,
|
||||
"list accepts no filter argument; use `claw mcp list`",
|
||||
"list accepts no filter argument; use `brewcode mcp list`",
|
||||
))
|
||||
}
|
||||
Some(args) if matches!(args.split_whitespace().next(), Some("info" | "describe")) => {
|
||||
Ok(render_mcp_unsupported_action_text(
|
||||
args,
|
||||
"use `claw mcp show <server>` to inspect a server",
|
||||
"use `brewcode mcp show <server>` to inspect a server",
|
||||
))
|
||||
}
|
||||
Some(args) => Ok(render_mcp_usage(Some(args))),
|
||||
|
|
@ -2725,7 +2725,7 @@ fn render_mcp_unsupported_action_json(action: &str, hint: &str) -> Value {
|
|||
"hint": hint,
|
||||
"usage": {
|
||||
"slash_command": "/mcp [list|show <server>|help]",
|
||||
"direct_cli": "claw mcp [list|show <server>|help]",
|
||||
"direct_cli": "brewcode mcp [list|show <server>|help]",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -2814,13 +2814,13 @@ fn render_mcp_report_json_for(
|
|||
Some(args) if args.split_whitespace().next() == Some("list") && args.contains(' ') => {
|
||||
Ok(render_mcp_unsupported_action_json(
|
||||
args,
|
||||
"list accepts no filter argument; use `claw mcp list`",
|
||||
"list accepts no filter argument; use `brewcode mcp list`",
|
||||
))
|
||||
}
|
||||
Some(args) if matches!(args.split_whitespace().next(), Some("info" | "describe")) => {
|
||||
Ok(render_mcp_unsupported_action_json(
|
||||
args,
|
||||
"use `claw mcp show <server>` to inspect a server",
|
||||
"use `brewcode mcp show <server>` to inspect a server",
|
||||
))
|
||||
}
|
||||
Some(args) => Ok(render_mcp_usage_json(Some(args))),
|
||||
|
|
@ -2927,8 +2927,8 @@ fn discover_definition_roots(cwd: &Path, leaf: &str) -> Vec<(DefinitionSource, P
|
|||
for ancestor in cwd.ancestors() {
|
||||
push_unique_root(
|
||||
&mut roots,
|
||||
DefinitionSource::ProjectClaw,
|
||||
ancestor.join(".claw").join(leaf),
|
||||
DefinitionSource::ProjectBrewcode,
|
||||
ancestor.join(".brewcode").join(leaf),
|
||||
);
|
||||
push_unique_root(
|
||||
&mut roots,
|
||||
|
|
@ -2942,11 +2942,11 @@ fn discover_definition_roots(cwd: &Path, leaf: &str) -> Vec<(DefinitionSource, P
|
|||
);
|
||||
}
|
||||
|
||||
if let Ok(claw_config_home) = env::var("CLAW_CONFIG_HOME") {
|
||||
if let Ok(brewcode_config_home) = env::var("BREWCODE_CONFIG_HOME") {
|
||||
push_unique_root(
|
||||
&mut roots,
|
||||
DefinitionSource::UserClawConfigHome,
|
||||
PathBuf::from(claw_config_home).join(leaf),
|
||||
DefinitionSource::UserBrewcodeConfigHome,
|
||||
PathBuf::from(brewcode_config_home).join(leaf),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -2970,8 +2970,8 @@ fn discover_definition_roots(cwd: &Path, leaf: &str) -> Vec<(DefinitionSource, P
|
|||
let home = PathBuf::from(home);
|
||||
push_unique_root(
|
||||
&mut roots,
|
||||
DefinitionSource::UserClaw,
|
||||
home.join(".claw").join(leaf),
|
||||
DefinitionSource::UserBrewcode,
|
||||
home.join(".brewcode").join(leaf),
|
||||
);
|
||||
push_unique_root(
|
||||
&mut roots,
|
||||
|
|
@ -2995,19 +2995,19 @@ fn discover_skill_roots(cwd: &Path) -> Vec<SkillRoot> {
|
|||
for ancestor in cwd.ancestors() {
|
||||
push_unique_skill_root(
|
||||
&mut roots,
|
||||
DefinitionSource::ProjectClaw,
|
||||
ancestor.join(".claw").join("skills"),
|
||||
DefinitionSource::ProjectBrewcode,
|
||||
ancestor.join(".brewcode").join("skills"),
|
||||
SkillOrigin::SkillsDir,
|
||||
);
|
||||
push_unique_skill_root(
|
||||
&mut roots,
|
||||
DefinitionSource::ProjectClaw,
|
||||
DefinitionSource::ProjectBrewcode,
|
||||
ancestor.join(".omc").join("skills"),
|
||||
SkillOrigin::SkillsDir,
|
||||
);
|
||||
push_unique_skill_root(
|
||||
&mut roots,
|
||||
DefinitionSource::ProjectClaw,
|
||||
DefinitionSource::ProjectBrewcode,
|
||||
ancestor.join(".agents").join("skills"),
|
||||
SkillOrigin::SkillsDir,
|
||||
);
|
||||
|
|
@ -3025,8 +3025,8 @@ fn discover_skill_roots(cwd: &Path) -> Vec<SkillRoot> {
|
|||
);
|
||||
push_unique_skill_root(
|
||||
&mut roots,
|
||||
DefinitionSource::ProjectClaw,
|
||||
ancestor.join(".claw").join("commands"),
|
||||
DefinitionSource::ProjectBrewcode,
|
||||
ancestor.join(".brewcode").join("commands"),
|
||||
SkillOrigin::LegacyCommandsDir,
|
||||
);
|
||||
push_unique_skill_root(
|
||||
|
|
@ -3043,18 +3043,18 @@ 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);
|
||||
if let Ok(brewcode_config_home) = env::var("BREWCODE_CONFIG_HOME") {
|
||||
let brewcode_config_home = PathBuf::from(brewcode_config_home);
|
||||
push_unique_skill_root(
|
||||
&mut roots,
|
||||
DefinitionSource::UserClawConfigHome,
|
||||
claw_config_home.join("skills"),
|
||||
DefinitionSource::UserBrewcodeConfigHome,
|
||||
brewcode_config_home.join("skills"),
|
||||
SkillOrigin::SkillsDir,
|
||||
);
|
||||
push_unique_skill_root(
|
||||
&mut roots,
|
||||
DefinitionSource::UserClawConfigHome,
|
||||
claw_config_home.join("commands"),
|
||||
DefinitionSource::UserBrewcodeConfigHome,
|
||||
brewcode_config_home.join("commands"),
|
||||
SkillOrigin::LegacyCommandsDir,
|
||||
);
|
||||
}
|
||||
|
|
@ -3079,20 +3079,20 @@ fn discover_skill_roots(cwd: &Path) -> Vec<SkillRoot> {
|
|||
let home = PathBuf::from(home);
|
||||
push_unique_skill_root(
|
||||
&mut roots,
|
||||
DefinitionSource::UserClaw,
|
||||
home.join(".claw").join("skills"),
|
||||
DefinitionSource::UserBrewcode,
|
||||
home.join(".brewcode").join("skills"),
|
||||
SkillOrigin::SkillsDir,
|
||||
);
|
||||
push_unique_skill_root(
|
||||
&mut roots,
|
||||
DefinitionSource::UserClaw,
|
||||
DefinitionSource::UserBrewcode,
|
||||
home.join(".omc").join("skills"),
|
||||
SkillOrigin::SkillsDir,
|
||||
);
|
||||
push_unique_skill_root(
|
||||
&mut roots,
|
||||
DefinitionSource::UserClaw,
|
||||
home.join(".claw").join("commands"),
|
||||
DefinitionSource::UserBrewcode,
|
||||
home.join(".brewcode").join("commands"),
|
||||
SkillOrigin::LegacyCommandsDir,
|
||||
);
|
||||
push_unique_skill_root(
|
||||
|
|
@ -3204,18 +3204,18 @@ 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(brewcode_config_home) = env::var("BREWCODE_CONFIG_HOME") {
|
||||
return Ok(PathBuf::from(brewcode_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(".brewcode").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 BREWCODE_CONFIG_HOME or HOME",
|
||||
))
|
||||
}
|
||||
|
||||
|
|
@ -3907,8 +3907,8 @@ fn render_agents_usage(unexpected: Option<&str>) -> String {
|
|||
let mut lines = vec![
|
||||
"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(),
|
||||
" Direct CLI brewcode agents".to_string(),
|
||||
" Sources .brewcode/agents, ~/.brewcode/agents, $BREWCODE_CONFIG_HOME/agents".to_string(),
|
||||
];
|
||||
if let Some(args) = unexpected {
|
||||
lines.push(format!(" Unexpected {args}"));
|
||||
|
|
@ -3922,8 +3922,8 @@ fn render_agents_usage_json(unexpected: Option<&str>) -> Value {
|
|||
"action": "help",
|
||||
"usage": {
|
||||
"slash_command": "/agents [list|help]",
|
||||
"direct_cli": "claw agents [list|help]",
|
||||
"sources": [".claw/agents", "~/.claw/agents", "$CLAW_CONFIG_HOME/agents"],
|
||||
"direct_cli": "brewcode agents [list|help]",
|
||||
"sources": [".brewcode/agents", "~/.brewcode/agents", "$BREWCODE_CONFIG_HOME/agents"],
|
||||
},
|
||||
"unexpected": unexpected,
|
||||
})
|
||||
|
|
@ -3934,10 +3934,10 @@ fn render_skills_usage(unexpected: Option<&str>) -> String {
|
|||
"Skills".to_string(),
|
||||
" Usage /skills [list|install <path>|help|<skill> [args]]".to_string(),
|
||||
" Alias /skill".to_string(),
|
||||
" Direct CLI claw skills [list|install <path>|help|<skill> [args]]".to_string(),
|
||||
" Direct CLI brewcode skills [list|install <path>|help|<skill> [args]]".to_string(),
|
||||
" Invoke /skills help overview -> $help overview".to_string(),
|
||||
" Install root $CLAW_CONFIG_HOME/skills or ~/.claw/skills".to_string(),
|
||||
" Sources .claw/skills, .omc/skills, .agents/skills, .codex/skills, .claude/skills, ~/.claw/skills, ~/.omc/skills, ~/.claude/skills/omc-learned, ~/.codex/skills, ~/.claude/skills, legacy /commands".to_string(),
|
||||
" Install root $BREWCODE_CONFIG_HOME/skills or ~/.brewcode/skills".to_string(),
|
||||
" Sources .brewcode/skills, .omc/skills, .agents/skills, .codex/skills, .claude/skills, ~/.brewcode/skills, ~/.omc/skills, ~/.claude/skills/omc-learned, ~/.codex/skills, ~/.claude/skills, legacy /commands".to_string(),
|
||||
];
|
||||
if let Some(args) = unexpected {
|
||||
lines.push(format!(" Unexpected {args}"));
|
||||
|
|
@ -3952,16 +3952,16 @@ fn render_skills_usage_json(unexpected: Option<&str>) -> Value {
|
|||
"usage": {
|
||||
"slash_command": "/skills [list|install <path>|help|<skill> [args]]",
|
||||
"aliases": ["/skill"],
|
||||
"direct_cli": "claw skills [list|install <path>|help|<skill> [args]]",
|
||||
"direct_cli": "brewcode skills [list|install <path>|help|<skill> [args]]",
|
||||
"invoke": "/skills help overview -> $help overview",
|
||||
"install_root": "$CLAW_CONFIG_HOME/skills or ~/.claw/skills",
|
||||
"install_root": "$BREWCODE_CONFIG_HOME/skills or ~/.brewcode/skills",
|
||||
"sources": [
|
||||
".claw/skills",
|
||||
".brewcode/skills",
|
||||
".omc/skills",
|
||||
".agents/skills",
|
||||
".codex/skills",
|
||||
".claude/skills",
|
||||
"~/.claw/skills",
|
||||
"~/.brewcode/skills",
|
||||
"~/.omc/skills",
|
||||
"~/.claude/skills/omc-learned",
|
||||
"~/.codex/skills",
|
||||
|
|
@ -3978,8 +3978,8 @@ fn render_mcp_usage(unexpected: Option<&str>) -> String {
|
|||
let mut lines = vec![
|
||||
"MCP".to_string(),
|
||||
" Usage /mcp [list|show <server>|help]".to_string(),
|
||||
" Direct CLI claw mcp [list|show <server>|help]".to_string(),
|
||||
" Sources .claw/settings.json, .claw/settings.local.json".to_string(),
|
||||
" Direct CLI brewcode mcp [list|show <server>|help]".to_string(),
|
||||
" Sources .brewcode/settings.json, .brewcode/settings.local.json".to_string(),
|
||||
];
|
||||
if let Some(args) = unexpected {
|
||||
lines.push(format!(" Unexpected {args}"));
|
||||
|
|
@ -3993,8 +3993,8 @@ fn render_mcp_usage_json(unexpected: Option<&str>) -> Value {
|
|||
"action": "help",
|
||||
"usage": {
|
||||
"slash_command": "/mcp [list|show <server>|help]",
|
||||
"direct_cli": "claw mcp [list|show <server>|help]",
|
||||
"sources": [".claw/settings.json", ".claw/settings.local.json"],
|
||||
"direct_cli": "brewcode mcp [list|show <server>|help]",
|
||||
"sources": [".brewcode/settings.json", ".brewcode/settings.local.json"],
|
||||
},
|
||||
"unexpected": unexpected,
|
||||
})
|
||||
|
|
@ -4078,14 +4078,14 @@ fn format_mcp_oauth(oauth: Option<&McpOAuthConfig>) -> String {
|
|||
|
||||
fn definition_source_id(source: DefinitionSource) -> &'static str {
|
||||
match source {
|
||||
DefinitionSource::ProjectClaw
|
||||
DefinitionSource::ProjectBrewcode
|
||||
| DefinitionSource::ProjectCodex
|
||||
| DefinitionSource::ProjectClaude => "project_claw",
|
||||
DefinitionSource::UserClawConfigHome | DefinitionSource::UserCodexHome => {
|
||||
"user_claw_config_home"
|
||||
| DefinitionSource::ProjectClaude => "project_brewcode",
|
||||
DefinitionSource::UserBrewcodeConfigHome | DefinitionSource::UserCodexHome => {
|
||||
"user_brewcode_config_home"
|
||||
}
|
||||
DefinitionSource::UserClaw | DefinitionSource::UserCodex | DefinitionSource::UserClaude => {
|
||||
"user_claw"
|
||||
DefinitionSource::UserBrewcode | DefinitionSource::UserCodex | DefinitionSource::UserClaude => {
|
||||
"user_brewcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4774,7 +4774,7 @@ mod tests {
|
|||
#[test]
|
||||
fn skills_show_and_list_filter_do_not_invoke_model() {
|
||||
// `show`, `info`, `list <filter>` must route to Local, not Invoke.
|
||||
// Regression for: `claw skills show plan` unexpectedly spawned a model session.
|
||||
// Regression for: `brewcode skills show plan` unexpectedly spawned a model session.
|
||||
for token in &["show", "info", "describe"] {
|
||||
assert_eq!(
|
||||
classify_skills_slash_command(Some(token)),
|
||||
|
|
@ -4975,7 +4975,7 @@ mod tests {
|
|||
|
||||
// then
|
||||
assert!(help.contains("/plugin"));
|
||||
assert!(help.contains("Summary Manage Claw Code plugins"));
|
||||
assert!(help.contains("Summary Manage Brew Code plugins"));
|
||||
assert!(help.contains("Aliases /plugins, /marketplace"));
|
||||
assert!(help.contains("Category Tools"));
|
||||
}
|
||||
|
|
@ -5303,12 +5303,12 @@ mod tests {
|
|||
assert_eq!(report["agents"][1]["name"], "verifier");
|
||||
assert_eq!(report["agents"][2]["name"], "planner");
|
||||
assert_eq!(report["agents"][2]["active"], false);
|
||||
assert_eq!(report["agents"][2]["shadowed_by"]["id"], "project_claw");
|
||||
assert_eq!(report["agents"][2]["shadowed_by"]["id"], "project_brewcode");
|
||||
|
||||
let help = handle_agents_slash_command_json(Some("help"), &workspace).expect("agents help");
|
||||
assert_eq!(help["kind"], "agents");
|
||||
assert_eq!(help["action"], "help");
|
||||
assert_eq!(help["usage"]["direct_cli"], "claw agents [list|help]");
|
||||
assert_eq!(help["usage"]["direct_cli"], "brewcode agents [list|help]");
|
||||
|
||||
let unexpected = handle_agents_slash_command_json(Some("show planner"), &workspace)
|
||||
.expect("agents usage");
|
||||
|
|
@ -5368,8 +5368,8 @@ mod tests {
|
|||
#[test]
|
||||
fn resolves_project_skills_and_legacy_commands_from_shared_registry() {
|
||||
let workspace = temp_dir("resolve-project-skills");
|
||||
let project_skills = workspace.join(".claw").join("skills");
|
||||
let legacy_commands = workspace.join(".claw").join("commands");
|
||||
let project_skills = workspace.join(".brewcode").join("skills");
|
||||
let legacy_commands = workspace.join(".brewcode").join("commands");
|
||||
|
||||
write_skill(&project_skills, "plan", "Project planning guidance");
|
||||
write_legacy_command(&legacy_commands, "handoff", "Legacy handoff guidance");
|
||||
|
|
@ -5422,10 +5422,10 @@ mod tests {
|
|||
assert_eq!(report["summary"]["active"], 3);
|
||||
assert_eq!(report["summary"]["shadowed"], 1);
|
||||
assert_eq!(report["skills"][0]["name"], "plan");
|
||||
assert_eq!(report["skills"][0]["source"]["id"], "project_claw");
|
||||
assert_eq!(report["skills"][0]["source"]["id"], "project_brewcode");
|
||||
assert_eq!(report["skills"][1]["name"], "deploy");
|
||||
assert_eq!(report["skills"][1]["origin"]["id"], "legacy_commands_dir");
|
||||
assert_eq!(report["skills"][3]["shadowed_by"]["id"], "project_claw");
|
||||
assert_eq!(report["skills"][3]["shadowed_by"]["id"], "project_brewcode");
|
||||
|
||||
let help = handle_skills_slash_command_json(Some("help"), &workspace).expect("skills help");
|
||||
assert_eq!(help["kind"], "skills");
|
||||
|
|
@ -5433,7 +5433,7 @@ mod tests {
|
|||
assert_eq!(help["usage"]["aliases"][0], "/skill");
|
||||
assert_eq!(
|
||||
help["usage"]["direct_cli"],
|
||||
"claw skills [list|install <path>|help|<skill> [args]]"
|
||||
"brewcode skills [list|install <path>|help|<skill> [args]]"
|
||||
);
|
||||
|
||||
let _ = fs::remove_dir_all(workspace);
|
||||
|
|
@ -5447,9 +5447,9 @@ mod tests {
|
|||
let 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("Direct CLI claw agents"));
|
||||
assert!(agents_help.contains("Direct CLI brewcode agents"));
|
||||
assert!(agents_help
|
||||
.contains("Sources .claw/agents, ~/.claw/agents, $CLAW_CONFIG_HOME/agents"));
|
||||
.contains("Sources .brewcode/agents, ~/.brewcode/agents, $BREWCODE_CONFIG_HOME/agents"));
|
||||
|
||||
let agents_unexpected =
|
||||
super::handle_agents_slash_command(Some("show planner"), &cwd).expect("agents usage");
|
||||
|
|
@ -5461,7 +5461,7 @@ mod tests {
|
|||
.contains("Usage /skills [list|install <path>|help|<skill> [args]]"));
|
||||
assert!(skills_help.contains("Alias /skill"));
|
||||
assert!(skills_help.contains("Invoke /skills help overview -> $help overview"));
|
||||
assert!(skills_help.contains("Install root $CLAW_CONFIG_HOME/skills or ~/.claw/skills"));
|
||||
assert!(skills_help.contains("Install root $BREWCODE_CONFIG_HOME/skills or ~/.brewcode/skills"));
|
||||
assert!(skills_help.contains(".omc/skills"));
|
||||
assert!(skills_help.contains(".agents/skills"));
|
||||
assert!(skills_help.contains("~/.claude/skills/omc-learned"));
|
||||
|
|
@ -5571,7 +5571,7 @@ mod tests {
|
|||
|
||||
let help = super::handle_mcp_slash_command(Some("help"), &cwd).expect("mcp help");
|
||||
assert!(help.contains("Usage /mcp [list|show <server>|help]"));
|
||||
assert!(help.contains("Direct CLI claw mcp [list|show <server>|help]"));
|
||||
assert!(help.contains("Direct CLI brewcode mcp [list|show <server>|help]"));
|
||||
|
||||
let unexpected =
|
||||
super::handle_mcp_slash_command(Some("show alpha beta"), &cwd).expect("mcp usage");
|
||||
|
|
@ -5594,10 +5594,10 @@ mod tests {
|
|||
fn renders_mcp_reports_from_loaded_config() {
|
||||
let workspace = temp_dir("mcp-config-workspace");
|
||||
let config_home = temp_dir("mcp-config-home");
|
||||
fs::create_dir_all(workspace.join(".claw")).expect("workspace config dir");
|
||||
fs::create_dir_all(workspace.join(".brewcode")).expect("workspace config dir");
|
||||
fs::create_dir_all(&config_home).expect("config home");
|
||||
fs::write(
|
||||
workspace.join(".claw").join("settings.json"),
|
||||
workspace.join(".brewcode").join("settings.json"),
|
||||
r#"{
|
||||
"mcpServers": {
|
||||
"alpha": {
|
||||
|
|
@ -5622,7 +5622,7 @@ mod tests {
|
|||
)
|
||||
.expect("write settings");
|
||||
fs::write(
|
||||
workspace.join(".claw").join("settings.local.json"),
|
||||
workspace.join(".brewcode").join("settings.local.json"),
|
||||
r#"{
|
||||
"mcpServers": {
|
||||
"remote": {
|
||||
|
|
@ -5673,10 +5673,10 @@ mod tests {
|
|||
fn renders_mcp_reports_as_json() {
|
||||
let workspace = temp_dir("mcp-json-workspace");
|
||||
let config_home = temp_dir("mcp-json-home");
|
||||
fs::create_dir_all(workspace.join(".claw")).expect("workspace config dir");
|
||||
fs::create_dir_all(workspace.join(".brewcode")).expect("workspace config dir");
|
||||
fs::create_dir_all(&config_home).expect("config home");
|
||||
fs::write(
|
||||
workspace.join(".claw").join("settings.json"),
|
||||
workspace.join(".brewcode").join("settings.json"),
|
||||
r#"{
|
||||
"mcpServers": {
|
||||
"alpha": {
|
||||
|
|
@ -5701,7 +5701,7 @@ mod tests {
|
|||
)
|
||||
.expect("write settings");
|
||||
fs::write(
|
||||
workspace.join(".claw").join("settings.local.json"),
|
||||
workspace.join(".brewcode").join("settings.local.json"),
|
||||
r#"{
|
||||
"mcpServers": {
|
||||
"remote": {
|
||||
|
|
@ -5748,7 +5748,7 @@ mod tests {
|
|||
let help =
|
||||
render_mcp_report_json_for(&loader, &workspace, Some("help")).expect("mcp help json");
|
||||
assert_eq!(help["action"], "help");
|
||||
assert_eq!(help["usage"]["sources"][0], ".claw/settings.json");
|
||||
assert_eq!(help["usage"]["sources"][0], ".brewcode/settings.json");
|
||||
|
||||
let _ = fs::remove_dir_all(workspace);
|
||||
let _ = fs::remove_dir_all(config_home);
|
||||
|
|
@ -5756,19 +5756,19 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn mcp_degrades_gracefully_on_malformed_mcp_config_144() {
|
||||
// #144: mirror of #143's partial-success contract for `claw mcp`.
|
||||
// #144: mirror of #143's partial-success contract for `brewcode mcp`.
|
||||
// Previously `mcp` hard-failed on any config parse error, hiding
|
||||
// well-formed servers and forcing claws to fall back to `doctor`.
|
||||
// well-formed servers and forcing brewcodes to fall back to `doctor`.
|
||||
// Now `mcp` emits a degraded envelope instead: exit 0, status:
|
||||
// "degraded", config_load_error populated, servers[] empty.
|
||||
let _guard = env_guard();
|
||||
let workspace = temp_dir("mcp-degrades-144");
|
||||
let config_home = temp_dir("mcp-degrades-144-cfg");
|
||||
fs::create_dir_all(workspace.join(".claw")).expect("create workspace .claw dir");
|
||||
fs::create_dir_all(workspace.join(".brewcode")).expect("create workspace .brewcode dir");
|
||||
fs::create_dir_all(&config_home).expect("create config home");
|
||||
// One valid server + one malformed entry missing `command`.
|
||||
fs::write(
|
||||
workspace.join(".claw.json"),
|
||||
workspace.join(".brewcode.json"),
|
||||
r#"{
|
||||
"mcpServers": {
|
||||
"everything": {"command": "npx", "args": ["-y", "@modelcontextprotocol/server-everything"]},
|
||||
|
|
@ -5777,7 +5777,7 @@ mod tests {
|
|||
}
|
||||
"#,
|
||||
)
|
||||
.expect("write malformed .claw.json");
|
||||
.expect("write malformed .brewcode.json");
|
||||
|
||||
let loader = ConfigLoader::new(&workspace, &config_home);
|
||||
// list action: must return Ok (not Err) with degraded envelope.
|
||||
|
|
|
|||
|
|
@ -76,12 +76,12 @@ fn upstream_repo_candidates(primary_repo_root: &Path) -> Vec<PathBuf> {
|
|||
}
|
||||
|
||||
for ancestor in primary_repo_root.ancestors().take(4) {
|
||||
candidates.push(ancestor.join("claw-code"));
|
||||
candidates.push(ancestor.join("clawd-code"));
|
||||
candidates.push(ancestor.join("brew-code"));
|
||||
candidates.push(ancestor.join("brewcoded-code"));
|
||||
}
|
||||
|
||||
candidates.push(primary_repo_root.join("reference-source").join("claw-code"));
|
||||
candidates.push(primary_repo_root.join("vendor").join("claw-code"));
|
||||
candidates.push(primary_repo_root.join("reference-source").join("brew-code"));
|
||||
candidates.push(primary_repo_root.join("vendor").join("brew-code"));
|
||||
|
||||
let mut deduped = Vec::new();
|
||||
for candidate in candidates {
|
||||
|
|
|
|||
|
|
@ -312,14 +312,14 @@ impl PluginTool {
|
|||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.env("CLAWD_PLUGIN_ID", &self.plugin_id)
|
||||
.env("CLAWD_PLUGIN_NAME", &self.plugin_name)
|
||||
.env("CLAWD_TOOL_NAME", &self.definition.name)
|
||||
.env("CLAWD_TOOL_INPUT", &input_json);
|
||||
.env("BREWCODED_PLUGIN_ID", &self.plugin_id)
|
||||
.env("BREWCODED_PLUGIN_NAME", &self.plugin_name)
|
||||
.env("BREWCODED_TOOL_NAME", &self.definition.name)
|
||||
.env("BREWCODED_TOOL_INPUT", &input_json);
|
||||
if let Some(root) = &self.root {
|
||||
process
|
||||
.current_dir(root)
|
||||
.env("CLAWD_PLUGIN_ROOT", root.display().to_string());
|
||||
.env("BREWCODED_PLUGIN_ROOT", root.display().to_string());
|
||||
}
|
||||
|
||||
let mut child = process.spawn()?;
|
||||
|
|
@ -1635,15 +1635,15 @@ fn detect_claude_code_manifest_contract_gaps(
|
|||
for (field, detail) in [
|
||||
(
|
||||
"skills",
|
||||
"plugin manifest field `skills` uses the Claude Code plugin contract; `claw` does not load plugin-managed skills and instead discovers skills from local roots such as `.claw/skills`, `.omc/skills`, `.agents/skills`, `~/.omc/skills`, and `~/.claude/skills/omc-learned`.",
|
||||
"plugin manifest field `skills` uses the Claude Code plugin contract; `brewcode` does not load plugin-managed skills and instead discovers skills from local roots such as `.brewcode/skills`, `.omc/skills`, `.agents/skills`, `~/.omc/skills`, and `~/.claude/skills/omc-learned`.",
|
||||
),
|
||||
(
|
||||
"mcpServers",
|
||||
"plugin manifest field `mcpServers` uses the Claude Code plugin contract; `claw` does not import MCP servers from plugin manifests.",
|
||||
"plugin manifest field `mcpServers` uses the Claude Code plugin contract; `brewcode` does not import MCP servers from plugin manifests.",
|
||||
),
|
||||
(
|
||||
"agents",
|
||||
"plugin manifest field `agents` uses the Claude Code plugin contract; `claw` does not load plugin-managed agent markdown catalogs from plugin manifests.",
|
||||
"plugin manifest field `agents` uses the Claude Code plugin contract; `brewcode` does not load plugin-managed agent markdown catalogs from plugin manifests.",
|
||||
),
|
||||
] {
|
||||
if root.contains_key(field) {
|
||||
|
|
@ -1659,7 +1659,7 @@ fn detect_claude_code_manifest_contract_gaps(
|
|||
.is_some_and(|commands| commands.iter().any(Value::is_string))
|
||||
{
|
||||
errors.push(PluginManifestValidationError::UnsupportedManifestContract {
|
||||
detail: "plugin manifest field `commands` uses Claude Code-style directory globs; `claw` slash dispatch is still built-in and does not load plugin slash command markdown files.".to_string(),
|
||||
detail: "plugin manifest field `commands` uses Claude Code-style directory globs; `brewcode` slash dispatch is still built-in and does not load plugin slash command markdown files.".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1671,7 +1671,7 @@ fn detect_claude_code_manifest_contract_gaps(
|
|||
) {
|
||||
errors.push(PluginManifestValidationError::UnsupportedManifestContract {
|
||||
detail: format!(
|
||||
"plugin hook `{hook_name}` uses the Claude Code lifecycle contract; `claw` plugins currently support only PreToolUse, PostToolUse, and PostToolUseFailure."
|
||||
"plugin hook `{hook_name}` uses the Claude Code lifecycle contract; `brewcode` plugins currently support only PreToolUse, PostToolUse, and PostToolUseFailure."
|
||||
),
|
||||
});
|
||||
}
|
||||
|
|
@ -2296,7 +2296,7 @@ fn ensure_object<'a>(root: &'a mut Map<String, Value>, key: &str) -> &'a mut Map
|
|||
}
|
||||
|
||||
/// Environment variable lock for test isolation.
|
||||
/// Guards against concurrent modification of `CLAW_CONFIG_HOME`.
|
||||
/// Guards against concurrent modification of `BREWCODE_CONFIG_HOME`.
|
||||
#[cfg(test)]
|
||||
fn env_lock() -> &'static std::sync::Mutex<()> {
|
||||
static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
|
|
@ -2465,7 +2465,7 @@ mod tests {
|
|||
let script_path = root.join("tools").join("echo-json.sh");
|
||||
write_file(
|
||||
&script_path,
|
||||
"#!/bin/sh\nINPUT=$(cat)\nprintf '{\"plugin\":\"%s\",\"tool\":\"%s\",\"input\":%s}\\n' \"$CLAWD_PLUGIN_ID\" \"$CLAWD_TOOL_NAME\" \"$INPUT\"\n",
|
||||
"#!/bin/sh\nINPUT=$(cat)\nprintf '{\"plugin\":\"%s\",\"tool\":\"%s\",\"input\":%s}\\n' \"$BREWCODED_PLUGIN_ID\" \"$BREWCODED_TOOL_NAME\" \"$INPUT\"\n",
|
||||
);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
|
|
@ -3537,18 +3537,18 @@ mod tests {
|
|||
let _ = fs::remove_dir_all(bundled_root);
|
||||
}
|
||||
|
||||
/// Regression test for ROADMAP #41: verify that `CLAW_CONFIG_HOME` isolation prevents
|
||||
/// host `~/.claw/plugins/` from bleeding into test runs.
|
||||
/// Regression test for ROADMAP #41: verify that `BREWCODE_CONFIG_HOME` isolation prevents
|
||||
/// host `~/.brewcode/plugins/` from bleeding into test runs.
|
||||
#[test]
|
||||
fn claw_config_home_isolation_prevents_host_plugin_leakage() {
|
||||
fn brewcode_config_home_isolation_prevents_host_plugin_leakage() {
|
||||
let _guard = env_guard();
|
||||
|
||||
// Create a temp directory to act as our isolated CLAW_CONFIG_HOME
|
||||
// Create a temp directory to act as our isolated BREWCODE_CONFIG_HOME
|
||||
let config_home = temp_dir("isolated-home");
|
||||
let bundled_root = temp_dir("isolated-bundled");
|
||||
|
||||
// Set CLAW_CONFIG_HOME to our temp directory
|
||||
std::env::set_var("CLAW_CONFIG_HOME", &config_home);
|
||||
// Set BREWCODE_CONFIG_HOME to our temp directory
|
||||
std::env::set_var("BREWCODE_CONFIG_HOME", &config_home);
|
||||
|
||||
// Create a test fixture plugin in the isolated config home
|
||||
let install_root = config_home.join("plugins").join("installed");
|
||||
|
|
@ -3562,7 +3562,7 @@ mod tests {
|
|||
}"#,
|
||||
);
|
||||
|
||||
// Create PluginManager with isolated bundled_root - it should use the temp config_home, not host ~/.claw/
|
||||
// Create PluginManager with isolated bundled_root - it should use the temp config_home, not host ~/.brewcode/
|
||||
let mut config = PluginManagerConfig::new(&config_home);
|
||||
config.bundled_root = Some(bundled_root.clone());
|
||||
let manager = PluginManager::new(config);
|
||||
|
|
@ -3576,7 +3576,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
installed.len(),
|
||||
1,
|
||||
"should only see the test fixture plugin, not host ~/.claw/plugins/"
|
||||
"should only see the test fixture plugin, not host ~/.brewcode/plugins/"
|
||||
);
|
||||
assert_eq!(
|
||||
installed[0].metadata.id, "isolated-test-plugin@external",
|
||||
|
|
@ -3584,7 +3584,7 @@ mod tests {
|
|||
);
|
||||
|
||||
// Cleanup
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
let _ = fs::remove_dir_all(config_home);
|
||||
let _ = fs::remove_dir_all(bundled_root);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@ mod tests {
|
|||
fn approval_token_blocks_until_owner_grants_policy_exception() {
|
||||
let mut ledger = ApprovalTokenLedger::new();
|
||||
let scope = ApprovalScope::new("main_push_forbidden", "git push")
|
||||
.with_repository("sisyphus/claw-code")
|
||||
.with_repository("sisyphus/brew-code")
|
||||
.with_branch("main");
|
||||
ledger.insert(ApprovalTokenGrant::pending(
|
||||
"tok-pending",
|
||||
|
|
@ -393,7 +393,7 @@ mod tests {
|
|||
fn approval_token_is_one_time_use_and_rejects_replay() {
|
||||
let mut ledger = ApprovalTokenLedger::new();
|
||||
let scope = ApprovalScope::new("release_requires_owner", "release publish")
|
||||
.with_repository("sisyphus/claw-code");
|
||||
.with_repository("sisyphus/brew-code");
|
||||
ledger.insert(ApprovalTokenGrant::granted(
|
||||
"tok-once",
|
||||
scope.clone(),
|
||||
|
|
@ -421,10 +421,10 @@ mod tests {
|
|||
fn approval_token_rejects_scope_expansion_expiry_and_revocation() {
|
||||
let mut ledger = ApprovalTokenLedger::new();
|
||||
let scope = ApprovalScope::new("main_push_forbidden", "git push")
|
||||
.with_repository("sisyphus/claw-code")
|
||||
.with_repository("sisyphus/brew-code")
|
||||
.with_branch("main");
|
||||
let dev_scope = ApprovalScope::new("main_push_forbidden", "git push")
|
||||
.with_repository("sisyphus/claw-code")
|
||||
.with_repository("sisyphus/brew-code")
|
||||
.with_branch("dev");
|
||||
|
||||
ledger.insert(
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::json::JsonValue;
|
|||
use crate::sandbox::{FilesystemIsolationMode, SandboxConfig};
|
||||
|
||||
/// Schema name advertised by generated settings files.
|
||||
pub const CLAW_SETTINGS_SCHEMA_NAME: &str = "SettingsSchema";
|
||||
pub const BREWCODE_SETTINGS_SCHEMA_NAME: &str = "SettingsSchema";
|
||||
|
||||
/// Origin of a loaded settings file in the configuration precedence chain.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
|
|
@ -176,7 +176,7 @@ pub struct McpOAuthConfig {
|
|||
pub xaa: Option<bool>,
|
||||
}
|
||||
|
||||
/// OAuth client configuration used by the main Claw runtime.
|
||||
/// OAuth client configuration used by the main Brewcode runtime.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct OAuthConfig {
|
||||
pub client_id: String,
|
||||
|
|
@ -242,8 +242,8 @@ impl ConfigLoader {
|
|||
#[must_use]
|
||||
pub fn discover(&self) -> Vec<ConfigEntry> {
|
||||
let user_legacy_path = self.config_home.parent().map_or_else(
|
||||
|| PathBuf::from(".claw.json"),
|
||||
|parent| parent.join(".claw.json"),
|
||||
|| PathBuf::from(".brewcode.json"),
|
||||
|parent| parent.join(".brewcode.json"),
|
||||
);
|
||||
vec![
|
||||
ConfigEntry {
|
||||
|
|
@ -256,15 +256,15 @@ impl ConfigLoader {
|
|||
},
|
||||
ConfigEntry {
|
||||
source: ConfigSource::Project,
|
||||
path: self.cwd.join(".claw.json"),
|
||||
path: self.cwd.join(".brewcode.json"),
|
||||
},
|
||||
ConfigEntry {
|
||||
source: ConfigSource::Project,
|
||||
path: self.cwd.join(".claw").join("settings.json"),
|
||||
path: self.cwd.join(".brewcode").join("settings.json"),
|
||||
},
|
||||
ConfigEntry {
|
||||
source: ConfigSource::Local,
|
||||
path: self.cwd.join(".claw").join("settings.local.json"),
|
||||
path: self.cwd.join(".brewcode").join("settings.local.json"),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -586,10 +586,10 @@ impl RuntimePluginConfig {
|
|||
#[must_use]
|
||||
/// Returns the default per-user config directory used by the runtime.
|
||||
pub fn default_config_home() -> PathBuf {
|
||||
std::env::var_os("CLAW_CONFIG_HOME")
|
||||
std::env::var_os("BREWCODE_CONFIG_HOME")
|
||||
.map(PathBuf::from)
|
||||
.or_else(|| std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".claw")))
|
||||
.unwrap_or_else(|| PathBuf::from(".claw"))
|
||||
.or_else(|| std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".brewcode")))
|
||||
.unwrap_or_else(|| PathBuf::from(".brewcode"))
|
||||
}
|
||||
|
||||
impl RuntimeHookConfig {
|
||||
|
|
@ -700,7 +700,7 @@ struct ParsedConfigFile {
|
|||
}
|
||||
|
||||
fn read_optional_json_object(path: &Path) -> Result<Option<ParsedConfigFile>, ConfigError> {
|
||||
let is_legacy_config = path.file_name().and_then(|name| name.to_str()) == Some(".claw.json");
|
||||
let is_legacy_config = path.file_name().and_then(|name| name.to_str()) == Some(".brewcode.json");
|
||||
let contents = match fs::read_to_string(path) {
|
||||
Ok(contents) => contents,
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
|
||||
|
|
@ -1280,7 +1280,7 @@ mod tests {
|
|||
use super::{
|
||||
deep_merge_objects, parse_permission_mode_label, ConfigLoader, ConfigSource,
|
||||
McpServerConfig, McpTransport, ResolvedPermissionMode, RuntimeFeatureConfig,
|
||||
RuntimeHookConfig, RuntimePluginConfig, CLAW_SETTINGS_SCHEMA_NAME,
|
||||
RuntimeHookConfig, RuntimePluginConfig, BREWCODE_SETTINGS_SCHEMA_NAME,
|
||||
};
|
||||
use crate::json::JsonValue;
|
||||
use crate::sandbox::FilesystemIsolationMode;
|
||||
|
|
@ -1309,7 +1309,7 @@ mod tests {
|
|||
fn rejects_non_object_settings_files() {
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
fs::create_dir_all(&cwd).expect("project dir");
|
||||
fs::write(home.join("settings.json"), "[]").expect("write bad settings");
|
||||
|
|
@ -1330,12 +1330,12 @@ mod tests {
|
|||
fn loads_and_merges_claude_code_config_files_by_precedence() {
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
fs::create_dir_all(cwd.join(".claw")).expect("project config dir");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
fs::create_dir_all(cwd.join(".brewcode")).expect("project config dir");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
|
||||
fs::write(
|
||||
home.parent().expect("home parent").join(".claw.json"),
|
||||
home.parent().expect("home parent").join(".brewcode.json"),
|
||||
r#"{"model":"haiku","env":{"A":"1"},"mcpServers":{"home":{"command":"uvx","args":["home"]}}}"#,
|
||||
)
|
||||
.expect("write user compat config");
|
||||
|
|
@ -1345,17 +1345,17 @@ mod tests {
|
|||
)
|
||||
.expect("write user settings");
|
||||
fs::write(
|
||||
cwd.join(".claw.json"),
|
||||
cwd.join(".brewcode.json"),
|
||||
r#"{"model":"project-compat","env":{"B":"2"}}"#,
|
||||
)
|
||||
.expect("write project compat config");
|
||||
fs::write(
|
||||
cwd.join(".claw").join("settings.json"),
|
||||
cwd.join(".brewcode").join("settings.json"),
|
||||
r#"{"env":{"C":"3"},"hooks":{"PostToolUse":["project"],"PostToolUseFailure":["project-failure"]},"permissions":{"ask":["Edit"]},"mcpServers":{"project":{"command":"uvx","args":["project"]}}}"#,
|
||||
)
|
||||
.expect("write project settings");
|
||||
fs::write(
|
||||
cwd.join(".claw").join("settings.local.json"),
|
||||
cwd.join(".brewcode").join("settings.local.json"),
|
||||
r#"{"model":"opus","permissionMode":"acceptEdits"}"#,
|
||||
)
|
||||
.expect("write local settings");
|
||||
|
|
@ -1364,7 +1364,7 @@ mod tests {
|
|||
.load()
|
||||
.expect("config should load");
|
||||
|
||||
assert_eq!(CLAW_SETTINGS_SCHEMA_NAME, "SettingsSchema");
|
||||
assert_eq!(BREWCODE_SETTINGS_SCHEMA_NAME, "SettingsSchema");
|
||||
assert_eq!(loaded.loaded_entries().len(), 5);
|
||||
assert_eq!(loaded.loaded_entries()[0].source, ConfigSource::User);
|
||||
assert_eq!(
|
||||
|
|
@ -1416,12 +1416,12 @@ mod tests {
|
|||
fn parses_sandbox_config() {
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
fs::create_dir_all(cwd.join(".claw")).expect("project config dir");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
fs::create_dir_all(cwd.join(".brewcode")).expect("project config dir");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
|
||||
fs::write(
|
||||
cwd.join(".claw").join("settings.local.json"),
|
||||
cwd.join(".brewcode").join("settings.local.json"),
|
||||
r#"{
|
||||
"sandbox": {
|
||||
"enabled": true,
|
||||
|
|
@ -1455,8 +1455,8 @@ mod tests {
|
|||
// given
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
fs::create_dir_all(cwd.join(".claw")).expect("project config dir");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
fs::create_dir_all(cwd.join(".brewcode")).expect("project config dir");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
fs::write(
|
||||
home.join("settings.json"),
|
||||
|
|
@ -1491,7 +1491,7 @@ mod tests {
|
|||
// given
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
fs::create_dir_all(&cwd).expect("project dir");
|
||||
fs::write(home.join("settings.json"), "{}").expect("write empty settings");
|
||||
|
|
@ -1515,7 +1515,7 @@ mod tests {
|
|||
// given
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
fs::create_dir_all(&cwd).expect("project dir");
|
||||
fs::write(
|
||||
|
|
@ -1541,7 +1541,7 @@ mod tests {
|
|||
// given
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
fs::create_dir_all(&cwd).expect("project dir");
|
||||
fs::write(
|
||||
|
|
@ -1586,7 +1586,7 @@ mod tests {
|
|||
// given
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
fs::create_dir_all(&cwd).expect("project dir");
|
||||
fs::write(home.join("settings.json"), "{}").expect("write empty settings");
|
||||
|
|
@ -1606,8 +1606,8 @@ mod tests {
|
|||
fn parses_typed_mcp_and_oauth_config() {
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
fs::create_dir_all(cwd.join(".claw")).expect("project config dir");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
fs::create_dir_all(cwd.join(".brewcode")).expect("project config dir");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
|
||||
fs::write(
|
||||
|
|
@ -1645,7 +1645,7 @@ mod tests {
|
|||
)
|
||||
.expect("write user settings");
|
||||
fs::write(
|
||||
cwd.join(".claw").join("settings.local.json"),
|
||||
cwd.join(".brewcode").join("settings.local.json"),
|
||||
r#"{
|
||||
"mcpServers": {
|
||||
"remote-server": {
|
||||
|
|
@ -1700,7 +1700,7 @@ mod tests {
|
|||
fn infers_http_mcp_servers_from_url_only_config() {
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
fs::create_dir_all(&cwd).expect("project dir");
|
||||
fs::write(
|
||||
|
|
@ -1738,8 +1738,8 @@ mod tests {
|
|||
fn parses_plugin_config_from_enabled_plugins() {
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
fs::create_dir_all(cwd.join(".claw")).expect("project config dir");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
fs::create_dir_all(cwd.join(".brewcode")).expect("project config dir");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
|
||||
fs::write(
|
||||
|
|
@ -1776,8 +1776,8 @@ mod tests {
|
|||
fn parses_plugin_config() {
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
fs::create_dir_all(cwd.join(".claw")).expect("project config dir");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
fs::create_dir_all(cwd.join(".brewcode")).expect("project config dir");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
|
||||
fs::write(
|
||||
|
|
@ -1829,7 +1829,7 @@ mod tests {
|
|||
// given
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
fs::create_dir_all(&cwd).expect("project dir");
|
||||
fs::write(
|
||||
|
|
@ -1856,8 +1856,8 @@ mod tests {
|
|||
// given
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
fs::create_dir_all(cwd.join(".claw")).expect("project config dir");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
fs::create_dir_all(cwd.join(".brewcode")).expect("project config dir");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
|
||||
fs::write(
|
||||
|
|
@ -1866,7 +1866,7 @@ mod tests {
|
|||
)
|
||||
.expect("write user settings");
|
||||
fs::write(
|
||||
cwd.join(".claw").join("settings.local.json"),
|
||||
cwd.join(".brewcode").join("settings.local.json"),
|
||||
r#"{"aliases":{"smart":"claude-sonnet-4-6","cheap":"grok-3-mini"}}"#,
|
||||
)
|
||||
.expect("write local settings");
|
||||
|
|
@ -1899,7 +1899,7 @@ mod tests {
|
|||
// given
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
fs::create_dir_all(&cwd).expect("project dir");
|
||||
fs::write(home.join("settings.json"), "").expect("write empty settings");
|
||||
|
|
@ -1954,9 +1954,9 @@ mod tests {
|
|||
// given
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
let project_settings = cwd.join(".claw").join("settings.json");
|
||||
fs::create_dir_all(cwd.join(".claw")).expect("project config dir");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
let project_settings = cwd.join(".brewcode").join("settings.json");
|
||||
fs::create_dir_all(cwd.join(".brewcode")).expect("project config dir");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
|
||||
fs::write(
|
||||
|
|
@ -2053,7 +2053,7 @@ mod tests {
|
|||
// given
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
let user_settings = home.join("settings.json");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
fs::create_dir_all(&cwd).expect("project dir");
|
||||
|
|
@ -2091,7 +2091,7 @@ mod tests {
|
|||
// given
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
let user_settings = home.join("settings.json");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
fs::create_dir_all(&cwd).expect("project dir");
|
||||
|
|
@ -2134,7 +2134,7 @@ mod tests {
|
|||
// given
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
let user_settings = home.join("settings.json");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
fs::create_dir_all(&cwd).expect("project dir");
|
||||
|
|
@ -2176,7 +2176,7 @@ mod tests {
|
|||
// given
|
||||
let root = temp_dir();
|
||||
let cwd = root.join("project");
|
||||
let home = root.join("home").join(".claw");
|
||||
let home = root.join("home").join(".brewcode");
|
||||
let user_settings = home.join("settings.json");
|
||||
fs::create_dir_all(&home).expect("home config dir");
|
||||
fs::create_dir_all(&cwd).expect("project dir");
|
||||
|
|
|
|||
|
|
@ -791,7 +791,7 @@ mod tests {
|
|||
#[test]
|
||||
fn check_unsupported_format_rejects_toml() {
|
||||
// given
|
||||
let path = PathBuf::from("/home/.claw/settings.toml");
|
||||
let path = PathBuf::from("/home/.brewcode/settings.toml");
|
||||
|
||||
// when
|
||||
let result = check_unsupported_format(&path);
|
||||
|
|
@ -806,7 +806,7 @@ mod tests {
|
|||
#[test]
|
||||
fn check_unsupported_format_allows_json() {
|
||||
// given
|
||||
let path = PathBuf::from("/home/.claw/settings.json");
|
||||
let path = PathBuf::from("/home/.brewcode/settings.json");
|
||||
|
||||
// when / then
|
||||
assert!(check_unsupported_format(&path).is_ok());
|
||||
|
|
|
|||
|
|
@ -786,7 +786,7 @@ mod tests {
|
|||
.duration_since(UNIX_EPOCH)
|
||||
.expect("time should move forward")
|
||||
.as_nanos();
|
||||
std::env::temp_dir().join(format!("clawd-native-{name}-{unique}"))
|
||||
std::env::temp_dir().join(format!("brewcoded-native-{name}-{unique}"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ impl SessionIdentity {
|
|||
pub struct LaneOwnership {
|
||||
/// Owner/assignee identity
|
||||
pub owner: String,
|
||||
/// Workflow scope (e.g., claw-code-dogfood, external-git-maintenance)
|
||||
/// Workflow scope (e.g., brew-code-dogfood, external-git-maintenance)
|
||||
pub workflow_scope: String,
|
||||
/// Whether the watcher is expected to act, observe, or ignore
|
||||
pub watcher_action: WatcherAction,
|
||||
|
|
@ -231,7 +231,7 @@ pub struct LaneEventMetadata {
|
|||
/// Environment/channel label (e.g., production, staging, dev)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub environment_label: Option<String>,
|
||||
/// Emitter identity (e.g., clawd, plugin-name, operator-id)
|
||||
/// Emitter identity (e.g., brewcoded, plugin-name, operator-id)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub emitter_identity: Option<String>,
|
||||
/// Confidence/trust level for downstream automation
|
||||
|
|
@ -1613,13 +1613,13 @@ mod tests {
|
|||
#[test]
|
||||
fn lane_ownership_binding_includes_workflow_scope() {
|
||||
let ownership = LaneOwnership {
|
||||
owner: "claw-1".to_string(),
|
||||
workflow_scope: "claw-code-dogfood".to_string(),
|
||||
owner: "brewcode-1".to_string(),
|
||||
workflow_scope: "brew-code-dogfood".to_string(),
|
||||
watcher_action: WatcherAction::Act,
|
||||
};
|
||||
|
||||
assert_eq!(ownership.owner, "claw-1");
|
||||
assert_eq!(ownership.workflow_scope, "claw-code-dogfood");
|
||||
assert_eq!(ownership.owner, "brewcode-1");
|
||||
assert_eq!(ownership.workflow_scope, "brew-code-dogfood");
|
||||
assert_eq!(ownership.watcher_action, WatcherAction::Act);
|
||||
}
|
||||
|
||||
|
|
@ -2219,11 +2219,11 @@ mod tests {
|
|||
fn lane_event_metadata_includes_us014_fields() {
|
||||
let meta = LaneEventMetadata::new(42, EventProvenance::LiveLane)
|
||||
.with_environment("production")
|
||||
.with_emitter("clawd-1")
|
||||
.with_emitter("brewcoded-1")
|
||||
.with_confidence(ConfidenceLevel::High);
|
||||
|
||||
assert_eq!(meta.environment_label, Some("production".to_string()));
|
||||
assert_eq!(meta.emitter_identity, Some("clawd-1".to_string()));
|
||||
assert_eq!(meta.emitter_identity, Some("brewcoded-1".to_string()));
|
||||
assert_eq!(meta.confidence_level, Some(ConfidenceLevel::High));
|
||||
}
|
||||
|
||||
|
|
@ -2382,7 +2382,7 @@ mod tests {
|
|||
fn lane_ownership_attached_to_metadata() {
|
||||
let ownership = LaneOwnership {
|
||||
owner: "bot-1".to_string(),
|
||||
workflow_scope: "claw-code-dogfood".to_string(),
|
||||
workflow_scope: "brew-code-dogfood".to_string(),
|
||||
watcher_action: WatcherAction::Act,
|
||||
};
|
||||
|
||||
|
|
@ -2399,7 +2399,7 @@ mod tests {
|
|||
assert_eq!(event.metadata.ownership.as_ref().unwrap().owner, "bot-1");
|
||||
assert_eq!(
|
||||
event.metadata.ownership.as_ref().unwrap().workflow_scope,
|
||||
"claw-code-dogfood"
|
||||
"brew-code-dogfood"
|
||||
);
|
||||
assert_eq!(
|
||||
event.metadata.ownership.as_ref().unwrap().watcher_action,
|
||||
|
|
@ -2490,7 +2490,7 @@ mod tests {
|
|||
|
||||
let observe_ownership = LaneOwnership {
|
||||
owner: "monitor-bot".to_string(),
|
||||
workflow_scope: "claw-code-dogfood".to_string(),
|
||||
workflow_scope: "brew-code-dogfood".to_string(),
|
||||
watcher_action: WatcherAction::Observe,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Core runtime primitives for the `claw` CLI and supporting crates.
|
||||
//! Core runtime primitives for the `brewcode` CLI and supporting crates.
|
||||
//!
|
||||
//! This crate owns session persistence, permission evaluation, prompt assembly,
|
||||
//! MCP plumbing, tool-facing file operations, and the core conversation loop
|
||||
|
|
@ -69,7 +69,7 @@ pub use config::{
|
|||
McpServerConfig, McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig,
|
||||
ProviderFallbackConfig, ResolvedPermissionMode, RuntimeConfig, RuntimeFeatureConfig,
|
||||
RuntimeHookConfig, RuntimePermissionRuleConfig, RuntimePluginConfig, ScopedMcpServerConfig,
|
||||
CLAW_SETTINGS_SCHEMA_NAME,
|
||||
BREWCODE_SETTINGS_SCHEMA_NAME,
|
||||
};
|
||||
pub use config_validate::{
|
||||
check_unsupported_format, format_diagnostics, validate_config_file, ConfigDiagnostic,
|
||||
|
|
@ -172,7 +172,7 @@ pub use session::{
|
|||
};
|
||||
pub use sse::{IncrementalSseParser, SseEvent};
|
||||
pub use stale_base::{
|
||||
check_base_commit, format_stale_base_warning, read_claw_base_file, resolve_expected_base,
|
||||
check_base_commit, format_stale_base_warning, read_brewcode_base_file, resolve_expected_base,
|
||||
BaseCommitSource, BaseCommitState,
|
||||
};
|
||||
pub use stale_branch::{
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
//! answers `initialize`, `tools/list`, and `tools/call` requests. The framing
|
||||
//! matches the client transport implemented in [`crate::mcp_stdio`] so this
|
||||
//! server can be driven by either an external MCP client (e.g. Claude
|
||||
//! Desktop) or `claw`'s own [`McpServerManager`](crate::McpServerManager).
|
||||
//! Desktop) or `brewcode`'s own [`McpServerManager`](crate::McpServerManager).
|
||||
//!
|
||||
//! The server is intentionally small: it exposes a list of pre-built
|
||||
//! [`McpTool`] descriptors and delegates `tools/call` to a caller-supplied
|
||||
|
|
@ -37,7 +37,7 @@ pub const MCP_SERVER_PROTOCOL_VERSION: &str = "2025-03-26";
|
|||
/// Returning `Ok(text)` yields a single `text` content block and
|
||||
/// `isError: false`. Returning `Err(message)` yields a `text` block with the
|
||||
/// error and `isError: true`, mirroring the error-surfacing convention used
|
||||
/// elsewhere in claw.
|
||||
/// elsewhere in brewcode.
|
||||
pub type ToolCallHandler =
|
||||
Box<dyn Fn(&str, &JsonValue) -> Result<String, String> + Send + Sync + 'static>;
|
||||
|
||||
|
|
|
|||
|
|
@ -331,7 +331,7 @@ fn generate_random_token(bytes: usize) -> io::Result<String> {
|
|||
}
|
||||
|
||||
fn credentials_home_dir() -> io::Result<PathBuf> {
|
||||
if let Some(path) = std::env::var_os("CLAW_CONFIG_HOME") {
|
||||
if let Some(path) = std::env::var_os("BREWCODE_CONFIG_HOME") {
|
||||
return Ok(PathBuf::from(path));
|
||||
}
|
||||
let home = std::env::var_os("HOME")
|
||||
|
|
@ -340,10 +340,10 @@ fn credentials_home_dir() -> io::Result<PathBuf> {
|
|||
io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"HOME is not set (on Windows, set USERPROFILE or HOME, \
|
||||
or use CLAW_CONFIG_HOME to point directly at the config directory)",
|
||||
or use BREWCODE_CONFIG_HOME to point directly at the config directory)",
|
||||
)
|
||||
})?;
|
||||
Ok(PathBuf::from(home).join(".claw"))
|
||||
Ok(PathBuf::from(home).join(".brewcode"))
|
||||
}
|
||||
|
||||
fn read_credentials_root(path: &PathBuf) -> io::Result<Map<String, Value>> {
|
||||
|
|
@ -555,7 +555,7 @@ mod tests {
|
|||
fn oauth_credentials_round_trip_and_clear_preserves_other_fields() {
|
||||
let _guard = env_lock();
|
||||
let config_home = temp_config_home();
|
||||
std::env::set_var("CLAW_CONFIG_HOME", &config_home);
|
||||
std::env::set_var("BREWCODE_CONFIG_HOME", &config_home);
|
||||
let path = credentials_path().expect("credentials path");
|
||||
std::fs::create_dir_all(path.parent().expect("parent")).expect("create parent");
|
||||
std::fs::write(&path, "{\"other\":\"value\"}\n").expect("seed credentials");
|
||||
|
|
@ -581,7 +581,7 @@ mod tests {
|
|||
assert!(cleared.contains("\"other\": \"value\""));
|
||||
assert!(!cleared.contains("\"oauth\""));
|
||||
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
std::fs::remove_dir_all(config_home).expect("cleanup temp dir");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -240,8 +240,8 @@ fn discover_instruction_files(cwd: &Path) -> std::io::Result<Vec<ContextFile>> {
|
|||
for candidate in [
|
||||
dir.join("CLAUDE.md"),
|
||||
dir.join("CLAUDE.local.md"),
|
||||
dir.join(".claw").join("CLAUDE.md"),
|
||||
dir.join(".claw").join("instructions.md"),
|
||||
dir.join(".brewcode").join("CLAUDE.md"),
|
||||
dir.join(".brewcode").join("instructions.md"),
|
||||
] {
|
||||
push_context_file(&mut files, candidate)?;
|
||||
}
|
||||
|
|
@ -477,7 +477,7 @@ fn render_config_section(config: &RuntimeConfig) -> String {
|
|||
let mut lines = vec!["# Runtime config".to_string()];
|
||||
if config.loaded_entries().is_empty() {
|
||||
lines.extend(prepend_bullets(vec![
|
||||
"No Claw Code settings files loaded.".to_string()
|
||||
"No Brew Code settings files loaded.".to_string()
|
||||
]));
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
|
@ -581,23 +581,23 @@ mod tests {
|
|||
fn discovers_instruction_files_from_ancestor_chain() {
|
||||
let root = temp_dir();
|
||||
let nested = root.join("apps").join("api");
|
||||
fs::create_dir_all(nested.join(".claw")).expect("nested claw dir");
|
||||
fs::create_dir_all(nested.join(".brewcode")).expect("nested brewcode dir");
|
||||
fs::write(root.join("CLAUDE.md"), "root instructions").expect("write root instructions");
|
||||
fs::write(root.join("CLAUDE.local.md"), "local instructions")
|
||||
.expect("write local instructions");
|
||||
fs::create_dir_all(root.join("apps")).expect("apps dir");
|
||||
fs::create_dir_all(root.join("apps").join(".claw")).expect("apps claw dir");
|
||||
fs::create_dir_all(root.join("apps").join(".brewcode")).expect("apps brewcode dir");
|
||||
fs::write(root.join("apps").join("CLAUDE.md"), "apps instructions")
|
||||
.expect("write apps instructions");
|
||||
fs::write(
|
||||
root.join("apps").join(".claw").join("instructions.md"),
|
||||
root.join("apps").join(".brewcode").join("instructions.md"),
|
||||
"apps dot claude instructions",
|
||||
)
|
||||
.expect("write apps dot claude instructions");
|
||||
fs::write(nested.join(".claw").join("CLAUDE.md"), "nested rules")
|
||||
fs::write(nested.join(".brewcode").join("CLAUDE.md"), "nested rules")
|
||||
.expect("write nested rules");
|
||||
fs::write(
|
||||
nested.join(".claw").join("instructions.md"),
|
||||
nested.join(".brewcode").join("instructions.md"),
|
||||
"nested instructions",
|
||||
)
|
||||
.expect("write nested instructions");
|
||||
|
|
@ -657,7 +657,7 @@ mod tests {
|
|||
#[test]
|
||||
fn displays_context_paths_compactly() {
|
||||
assert_eq!(
|
||||
display_context_path(Path::new("/tmp/project/.claw/CLAUDE.md")),
|
||||
display_context_path(Path::new("/tmp/project/.brewcode/CLAUDE.md")),
|
||||
"CLAUDE.md"
|
||||
);
|
||||
}
|
||||
|
|
@ -817,10 +817,10 @@ mod tests {
|
|||
#[test]
|
||||
fn load_system_prompt_reads_claude_files_and_config() {
|
||||
let root = temp_dir();
|
||||
fs::create_dir_all(root.join(".claw")).expect("claw dir");
|
||||
fs::create_dir_all(root.join(".brewcode")).expect("brewcode dir");
|
||||
fs::write(root.join("CLAUDE.md"), "Project rules").expect("write instructions");
|
||||
fs::write(
|
||||
root.join(".claw").join("settings.json"),
|
||||
root.join(".brewcode").join("settings.json"),
|
||||
r#"{"permissionMode":"acceptEdits"}"#,
|
||||
)
|
||||
.expect("write settings");
|
||||
|
|
@ -829,9 +829,9 @@ mod tests {
|
|||
ensure_valid_cwd();
|
||||
let previous = std::env::current_dir().expect("cwd");
|
||||
let original_home = std::env::var("HOME").ok();
|
||||
let original_claw_home = std::env::var("CLAW_CONFIG_HOME").ok();
|
||||
let original_brewcode_home = std::env::var("BREWCODE_CONFIG_HOME").ok();
|
||||
std::env::set_var("HOME", &root);
|
||||
std::env::set_var("CLAW_CONFIG_HOME", root.join("missing-home"));
|
||||
std::env::set_var("BREWCODE_CONFIG_HOME", root.join("missing-home"));
|
||||
std::env::set_current_dir(&root).expect("change cwd");
|
||||
let prompt = super::load_system_prompt(
|
||||
&root,
|
||||
|
|
@ -852,10 +852,10 @@ mod tests {
|
|||
} else {
|
||||
std::env::remove_var("HOME");
|
||||
}
|
||||
if let Some(value) = original_claw_home {
|
||||
std::env::set_var("CLAW_CONFIG_HOME", value);
|
||||
if let Some(value) = original_brewcode_home {
|
||||
std::env::set_var("BREWCODE_CONFIG_HOME", value);
|
||||
} else {
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
}
|
||||
|
||||
assert!(prompt.contains("Project rules"));
|
||||
|
|
@ -910,10 +910,10 @@ mod tests {
|
|||
#[test]
|
||||
fn renders_claude_code_style_sections_with_project_context() {
|
||||
let root = temp_dir();
|
||||
fs::create_dir_all(root.join(".claw")).expect("claw dir");
|
||||
fs::create_dir_all(root.join(".brewcode")).expect("brewcode dir");
|
||||
fs::write(root.join("CLAUDE.md"), "Project rules").expect("write CLAUDE.md");
|
||||
fs::write(
|
||||
root.join(".claw").join("settings.json"),
|
||||
root.join(".brewcode").join("settings.json"),
|
||||
r#"{"permissionMode":"acceptEdits"}"#,
|
||||
)
|
||||
.expect("write settings");
|
||||
|
|
@ -952,9 +952,9 @@ mod tests {
|
|||
fn discovers_dot_claude_instructions_markdown() {
|
||||
let root = temp_dir();
|
||||
let nested = root.join("apps").join("api");
|
||||
fs::create_dir_all(nested.join(".claw")).expect("nested claw dir");
|
||||
fs::create_dir_all(nested.join(".brewcode")).expect("nested brewcode dir");
|
||||
fs::write(
|
||||
nested.join(".claw").join("instructions.md"),
|
||||
nested.join(".brewcode").join("instructions.md"),
|
||||
"instruction markdown",
|
||||
)
|
||||
.expect("write instructions.md");
|
||||
|
|
@ -963,7 +963,7 @@ mod tests {
|
|||
assert!(context
|
||||
.instruction_files
|
||||
.iter()
|
||||
.any(|file| file.path.ends_with(".claw/instructions.md")));
|
||||
.any(|file| file.path.ends_with(".brewcode/instructions.md")));
|
||||
assert!(
|
||||
render_instruction_files(&context.instruction_files).contains("instruction markdown")
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::Value;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
pub const REPORT_SCHEMA_V1: &str = "claw.report.v1";
|
||||
pub const DEFAULT_PROJECTION_POLICY_V1: &str = "claw.report.projection.v1";
|
||||
pub const REPORT_SCHEMA_V1: &str = "brewcode.report.v1";
|
||||
pub const DEFAULT_PROJECTION_POLICY_V1: &str = "brewcode.report.projection.v1";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
|
@ -459,7 +459,7 @@ mod tests {
|
|||
|
||||
fn capabilities(families: &[&str], max_sensitivity: SensitivityClass) -> ConsumerCapabilities {
|
||||
ConsumerCapabilities {
|
||||
consumer: "clawhip".to_string(),
|
||||
consumer: "brewcodehip".to_string(),
|
||||
schema_versions: [REPORT_SCHEMA_V1.to_string()].into_iter().collect(),
|
||||
field_families: families
|
||||
.iter()
|
||||
|
|
@ -538,7 +538,7 @@ mod tests {
|
|||
fn capability_negotiation_omits_unsupported_field_families() {
|
||||
let report = fixture_report();
|
||||
let capabilities = capabilities(&["claims"], SensitivityClass::Internal);
|
||||
let projection = project_report(&report, &capabilities, "legacy_clawhip");
|
||||
let projection = project_report(&report, &capabilities, "legacy_brewcodehip");
|
||||
|
||||
assert!(projection.provenance.downgraded);
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -242,11 +242,11 @@ pub fn build_linux_sandbox_command(
|
|||
("HOME".to_string(), sandbox_home.display().to_string()),
|
||||
("TMPDIR".to_string(), sandbox_tmp.display().to_string()),
|
||||
(
|
||||
"CLAWD_SANDBOX_FILESYSTEM_MODE".to_string(),
|
||||
"BREWCODED_SANDBOX_FILESYSTEM_MODE".to_string(),
|
||||
status.filesystem_mode.as_str().to_string(),
|
||||
),
|
||||
(
|
||||
"CLAWD_SANDBOX_ALLOWED_MOUNTS".to_string(),
|
||||
"BREWCODED_SANDBOX_ALLOWED_MOUNTS".to_string(),
|
||||
status.allowed_mounts.join(":"),
|
||||
),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1807,7 +1807,7 @@ mod tests {
|
|||
/// Per-worktree session isolation: returns a session directory namespaced
|
||||
/// by the workspace fingerprint of the given working directory.
|
||||
/// This prevents parallel `opencode serve` instances from colliding.
|
||||
/// Called by external consumers (e.g. clawhip) to enumerate sessions for a CWD.
|
||||
/// Called by external consumers (e.g. brewcodehip) to enumerate sessions for a CWD.
|
||||
#[allow(dead_code)]
|
||||
pub fn workspace_sessions_dir(cwd: &std::path::Path) -> Result<std::path::PathBuf, SessionError> {
|
||||
let store = crate::session_control::SessionStore::from_cwd(cwd)
|
||||
|
|
@ -1822,7 +1822,7 @@ mod workspace_sessions_dir_tests {
|
|||
|
||||
#[test]
|
||||
fn workspace_sessions_dir_returns_fingerprinted_path_for_valid_cwd() {
|
||||
let tmp = std::env::temp_dir().join("claw-session-dir-test");
|
||||
let tmp = std::env::temp_dir().join("brewcode-session-dir-test");
|
||||
fs::create_dir_all(&tmp).expect("create temp dir");
|
||||
|
||||
let result = workspace_sessions_dir(&tmp);
|
||||
|
|
@ -1842,8 +1842,8 @@ mod workspace_sessions_dir_tests {
|
|||
|
||||
#[test]
|
||||
fn workspace_sessions_dir_differs_for_different_cwds() {
|
||||
let tmp_a = std::env::temp_dir().join("claw-session-dir-a");
|
||||
let tmp_b = std::env::temp_dir().join("claw-session-dir-b");
|
||||
let tmp_a = std::env::temp_dir().join("brewcode-session-dir-a");
|
||||
let tmp_b = std::env::temp_dir().join("brewcode-session-dir-b");
|
||||
fs::create_dir_all(&tmp_a).expect("create dir a");
|
||||
fs::create_dir_all(&tmp_b).expect("create dir b");
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ use crate::session::{Session, SessionError};
|
|||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SessionStore {
|
||||
/// Resolved root of the session namespace, e.g.
|
||||
/// `/home/user/project/.claw/sessions/a1b2c3d4e5f60718/`.
|
||||
/// `/home/user/project/.brewcode/sessions/a1b2c3d4e5f60718/`.
|
||||
sessions_root: PathBuf,
|
||||
/// The canonical workspace path that was fingerprinted.
|
||||
workspace_root: PathBuf,
|
||||
|
|
@ -28,7 +28,7 @@ pub struct SessionStore {
|
|||
impl SessionStore {
|
||||
/// Build a store from the server's current working directory.
|
||||
///
|
||||
/// The on-disk layout becomes `<cwd>/.claw/sessions/<workspace_hash>/`.
|
||||
/// The on-disk layout becomes `<cwd>/.brewcode/sessions/<workspace_hash>/`.
|
||||
pub fn from_cwd(cwd: impl AsRef<Path>) -> Result<Self, SessionControlError> {
|
||||
let cwd = cwd.as_ref();
|
||||
// #151: canonicalize so equivalent paths (symlinks, relative vs
|
||||
|
|
@ -37,7 +37,7 @@ impl SessionStore {
|
|||
// fails (e.g. the directory doesn't exist yet).
|
||||
let canonical_cwd = fs::canonicalize(cwd).unwrap_or_else(|_| cwd.to_path_buf());
|
||||
let sessions_root = canonical_cwd
|
||||
.join(".claw")
|
||||
.join(".brewcode")
|
||||
.join("sessions")
|
||||
.join(workspace_fingerprint(&canonical_cwd));
|
||||
fs::create_dir_all(&sessions_root)?;
|
||||
|
|
@ -557,24 +557,24 @@ fn session_id_from_path(path: &Path) -> Option<String> {
|
|||
}
|
||||
|
||||
fn format_missing_session_reference(reference: &str, sessions_root: &Path) -> String {
|
||||
// #80: show the actual workspace-fingerprint directory instead of lying about .claw/sessions/
|
||||
// #80: show the actual workspace-fingerprint directory instead of lying about .brewcode/sessions/
|
||||
let fingerprint_dir = sessions_root
|
||||
.file_name()
|
||||
.and_then(|f| f.to_str())
|
||||
.unwrap_or("<unknown>");
|
||||
format!(
|
||||
"session not found: {reference}\nHint: managed sessions live in .claw/sessions/{fingerprint_dir}/ (workspace-specific partition).\nTry `{LATEST_SESSION_REFERENCE}` for the most recent session or `/session list` in the REPL."
|
||||
"session not found: {reference}\nHint: managed sessions live in .brewcode/sessions/{fingerprint_dir}/ (workspace-specific partition).\nTry `{LATEST_SESSION_REFERENCE}` for the most recent session or `/session list` in the REPL."
|
||||
)
|
||||
}
|
||||
|
||||
fn format_no_managed_sessions(sessions_root: &Path) -> String {
|
||||
// #80: show the actual workspace-fingerprint directory instead of lying about .claw/sessions/
|
||||
// #80: show the actual workspace-fingerprint directory instead of lying about .brewcode/sessions/
|
||||
let fingerprint_dir = sessions_root
|
||||
.file_name()
|
||||
.and_then(|f| f.to_str())
|
||||
.unwrap_or("<unknown>");
|
||||
format!(
|
||||
"no managed sessions found in .claw/sessions/{fingerprint_dir}/\nStart `claw` to create a session, then rerun with `--resume {LATEST_SESSION_REFERENCE}`.\nNote: claw partitions sessions per workspace fingerprint; sessions from other CWDs are invisible."
|
||||
"no managed sessions found in .brewcode/sessions/{fingerprint_dir}/\nStart `brewcode` to create a session, then rerun with `--resume {LATEST_SESSION_REFERENCE}`.\nNote: brewcode partitions sessions per workspace fingerprint; sessions from other CWDs are invisible."
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -926,7 +926,7 @@ mod tests {
|
|||
let workspace_b = fs::canonicalize(&workspace_b).unwrap_or(workspace_b);
|
||||
|
||||
let store_b = SessionStore::from_cwd(&workspace_b).expect("store b should build");
|
||||
let legacy_root = workspace_b.join(".claw").join("sessions");
|
||||
let legacy_root = workspace_b.join(".brewcode").join("sessions");
|
||||
fs::create_dir_all(&legacy_root).expect("legacy root should exist");
|
||||
let legacy_path = legacy_root.join("legacy-cross.jsonl");
|
||||
let session = Session::new()
|
||||
|
|
@ -960,7 +960,7 @@ mod tests {
|
|||
// #151: canonicalize for path-representation consistency with store.
|
||||
let base = fs::canonicalize(&base).unwrap_or(base);
|
||||
let store = SessionStore::from_cwd(&base).expect("store should build");
|
||||
let legacy_root = base.join(".claw").join("sessions");
|
||||
let legacy_root = base.join(".brewcode").join("sessions");
|
||||
let legacy_path = legacy_root.join("legacy-safe.jsonl");
|
||||
fs::create_dir_all(&legacy_root).expect("legacy root should exist");
|
||||
let session = Session::new()
|
||||
|
|
@ -990,7 +990,7 @@ mod tests {
|
|||
// #151: canonicalize for path-representation consistency with store.
|
||||
let base = fs::canonicalize(&base).unwrap_or(base);
|
||||
let store = SessionStore::from_cwd(&base).expect("store should build");
|
||||
let legacy_root = base.join(".claw").join("sessions");
|
||||
let legacy_root = base.join(".brewcode").join("sessions");
|
||||
let legacy_path = legacy_root.join("legacy-unbound.json");
|
||||
fs::create_dir_all(&legacy_root).expect("legacy root should exist");
|
||||
let session = Session::new().with_persistence_path(legacy_path.clone());
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ pub enum BaseCommitSource {
|
|||
File(String),
|
||||
}
|
||||
|
||||
/// Read the `.claw-base` file from the given directory and return the trimmed
|
||||
/// Read the `.brewcode-base` file from the given directory and return the trimmed
|
||||
/// commit hash, or `None` when the file is absent or empty.
|
||||
pub fn read_claw_base_file(cwd: &Path) -> Option<String> {
|
||||
let path = cwd.join(".claw-base");
|
||||
pub fn read_brewcode_base_file(cwd: &Path) -> Option<String> {
|
||||
let path = cwd.join(".brewcode-base");
|
||||
let content = std::fs::read_to_string(path).ok()?;
|
||||
let trimmed = content.trim();
|
||||
if trimmed.is_empty() {
|
||||
|
|
@ -36,7 +36,7 @@ pub fn read_claw_base_file(cwd: &Path) -> Option<String> {
|
|||
}
|
||||
|
||||
/// Resolve the expected base commit: prefer the `--base-commit` flag value,
|
||||
/// fall back to reading `.claw-base` from `cwd`.
|
||||
/// fall back to reading `.brewcode-base` from `cwd`.
|
||||
pub fn resolve_expected_base(flag_value: Option<&str>, cwd: &Path) -> Option<BaseCommitSource> {
|
||||
if let Some(value) = flag_value {
|
||||
let trimmed = value.trim();
|
||||
|
|
@ -44,7 +44,7 @@ pub fn resolve_expected_base(flag_value: Option<&str>, cwd: &Path) -> Option<Bas
|
|||
return Some(BaseCommitSource::Flag(trimmed.to_string()));
|
||||
}
|
||||
}
|
||||
read_claw_base_file(cwd).map(BaseCommitSource::File)
|
||||
read_brewcode_base_file(cwd).map(BaseCommitSource::File)
|
||||
}
|
||||
|
||||
/// Verify that the worktree HEAD matches `expected_base`.
|
||||
|
|
@ -250,14 +250,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn reads_claw_base_file() {
|
||||
fn reads_brewcode_base_file() {
|
||||
// given
|
||||
let root = temp_dir();
|
||||
fs::create_dir_all(&root).expect("create dir");
|
||||
fs::write(root.join(".claw-base"), "abc1234def5678\n").expect("write .claw-base");
|
||||
fs::write(root.join(".brewcode-base"), "abc1234def5678\n").expect("write .brewcode-base");
|
||||
|
||||
// when
|
||||
let value = read_claw_base_file(&root);
|
||||
let value = read_brewcode_base_file(&root);
|
||||
|
||||
// then
|
||||
assert_eq!(value, Some("abc1234def5678".to_string()));
|
||||
|
|
@ -265,13 +265,13 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn returns_none_for_missing_claw_base_file() {
|
||||
fn returns_none_for_missing_brewcode_base_file() {
|
||||
// given
|
||||
let root = temp_dir();
|
||||
fs::create_dir_all(&root).expect("create dir");
|
||||
|
||||
// when
|
||||
let value = read_claw_base_file(&root);
|
||||
let value = read_brewcode_base_file(&root);
|
||||
|
||||
// then
|
||||
assert!(value.is_none());
|
||||
|
|
@ -279,14 +279,14 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn returns_none_for_empty_claw_base_file() {
|
||||
fn returns_none_for_empty_brewcode_base_file() {
|
||||
// given
|
||||
let root = temp_dir();
|
||||
fs::create_dir_all(&root).expect("create dir");
|
||||
fs::write(root.join(".claw-base"), " \n").expect("write empty .claw-base");
|
||||
fs::write(root.join(".brewcode-base"), " \n").expect("write empty .brewcode-base");
|
||||
|
||||
// when
|
||||
let value = read_claw_base_file(&root);
|
||||
let value = read_brewcode_base_file(&root);
|
||||
|
||||
// then
|
||||
assert!(value.is_none());
|
||||
|
|
@ -298,7 +298,7 @@ mod tests {
|
|||
// given
|
||||
let root = temp_dir();
|
||||
fs::create_dir_all(&root).expect("create dir");
|
||||
fs::write(root.join(".claw-base"), "from_file\n").expect("write .claw-base");
|
||||
fs::write(root.join(".brewcode-base"), "from_file\n").expect("write .brewcode-base");
|
||||
|
||||
// when
|
||||
let source = resolve_expected_base(Some("from_flag"), &root);
|
||||
|
|
@ -316,7 +316,7 @@ mod tests {
|
|||
// given
|
||||
let root = temp_dir();
|
||||
fs::create_dir_all(&root).expect("create dir");
|
||||
fs::write(root.join(".claw-base"), "from_file\n").expect("write .claw-base");
|
||||
fs::write(root.join(".brewcode-base"), "from_file\n").expect("write .brewcode-base");
|
||||
|
||||
// when
|
||||
let source = resolve_expected_base(None, &root);
|
||||
|
|
@ -386,12 +386,12 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn matches_with_claw_base_file_in_real_repo() {
|
||||
fn matches_with_brewcode_base_file_in_real_repo() {
|
||||
// given
|
||||
let root = temp_dir();
|
||||
init_repo(&root);
|
||||
let sha = head_sha(&root);
|
||||
fs::write(root.join(".claw-base"), format!("{sha}\n")).expect("write .claw-base");
|
||||
fs::write(root.join(".brewcode-base"), format!("{sha}\n")).expect("write .brewcode-base");
|
||||
let source = resolve_expected_base(None, &root);
|
||||
|
||||
// when
|
||||
|
|
@ -403,12 +403,12 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn diverged_with_claw_base_file_after_new_commit() {
|
||||
fn diverged_with_brewcode_base_file_after_new_commit() {
|
||||
// given
|
||||
let root = temp_dir();
|
||||
init_repo(&root);
|
||||
let old_sha = head_sha(&root);
|
||||
fs::write(root.join(".claw-base"), format!("{old_sha}\n")).expect("write .claw-base");
|
||||
fs::write(root.join(".brewcode-base"), format!("{old_sha}\n")).expect("write .brewcode-base");
|
||||
commit_file(&root, "new.txt", "advance head");
|
||||
let new_sha = head_sha(&root);
|
||||
let source = resolve_expected_base(None, &root);
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ mod tests {
|
|||
objective: "Implement typed task packet format".to_string(),
|
||||
scope: TaskScope::Module,
|
||||
scope_path: Some("runtime/task system".to_string()),
|
||||
repo: "claw-code-parity".to_string(),
|
||||
repo: "brew-code-parity".to_string(),
|
||||
worktree: Some("/tmp/wt-1".to_string()),
|
||||
branch_policy: "origin/main only".to_string(),
|
||||
acceptance_tests: vec![
|
||||
|
|
@ -309,7 +309,7 @@ mod tests {
|
|||
let legacy = r#"{
|
||||
"objective": "Legacy packet",
|
||||
"scope": "workspace",
|
||||
"repo": "claw-code",
|
||||
"repo": "brew-code",
|
||||
"branch_policy": "origin/main only",
|
||||
"acceptance_tests": ["cargo test"],
|
||||
"commit_policy": "single commit",
|
||||
|
|
@ -364,7 +364,7 @@ mod tests {
|
|||
"objective": "Ship legacy task packet",
|
||||
"scope": "module",
|
||||
"scope_path": "runtime/task system",
|
||||
"repo": "claw-code-parity",
|
||||
"repo": "brew-code-parity",
|
||||
"worktree": "/tmp/wt-legacy",
|
||||
"branch_policy": "origin/main only",
|
||||
"acceptance_tests": ["cargo test --workspace"],
|
||||
|
|
|
|||
|
|
@ -369,7 +369,7 @@ mod tests {
|
|||
scope: TaskScope::Module,
|
||||
scope_path: Some("runtime/task system".to_string()),
|
||||
worktree: Some("/tmp/wt-task".to_string()),
|
||||
repo: "claw-code-parity".to_string(),
|
||||
repo: "brew-code-parity".to_string(),
|
||||
branch_policy: "origin/main only".to_string(),
|
||||
acceptance_tests: vec!["cargo test --workspace".to_string()],
|
||||
acceptance_criteria: vec!["task is inspectable".to_string()],
|
||||
|
|
|
|||
|
|
@ -877,8 +877,8 @@ fn push_event(
|
|||
emit_state_file(worker);
|
||||
}
|
||||
|
||||
/// Write current worker state to `.claw/worker-state.json` under the worker's cwd.
|
||||
/// This is the file-based observability surface: external observers (clawhip, orchestrators)
|
||||
/// Write current worker state to `.brewcode/worker-state.json` under the worker's cwd.
|
||||
/// This is the file-based observability surface: external observers (brewcodehip, orchestrators)
|
||||
/// poll this file instead of requiring an HTTP route on the opencode binary.
|
||||
#[derive(serde::Serialize)]
|
||||
struct StateSnapshot<'a> {
|
||||
|
|
@ -889,13 +889,13 @@ struct StateSnapshot<'a> {
|
|||
prompt_in_flight: bool,
|
||||
last_event: Option<&'a WorkerEvent>,
|
||||
updated_at: u64,
|
||||
/// Seconds since last state transition. Clawhip uses this to detect
|
||||
/// Seconds since last state transition. Brewcodehip uses this to detect
|
||||
/// stalled workers without computing epoch deltas.
|
||||
seconds_since_update: u64,
|
||||
}
|
||||
|
||||
fn emit_state_file(worker: &Worker) {
|
||||
let state_dir = std::path::Path::new(&worker.cwd).join(".claw");
|
||||
let state_dir = std::path::Path::new(&worker.cwd).join(".brewcode");
|
||||
if std::fs::create_dir_all(&state_dir).is_err() {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1445,7 +1445,7 @@ mod tests {
|
|||
.expect("tool permission observe should succeed");
|
||||
|
||||
let timed_out = registry
|
||||
.observe_startup_timeout(&worker.worker_id, "claw prompt", true, true)
|
||||
.observe_startup_timeout(&worker.worker_id, "brewcode prompt", true, true)
|
||||
.expect("startup timeout observe should succeed");
|
||||
let event = timed_out
|
||||
.events
|
||||
|
|
@ -1648,7 +1648,7 @@ mod tests {
|
|||
&worker.worker_id,
|
||||
Some("Implement worker handshake"),
|
||||
Some(WorkerTaskReceipt {
|
||||
repo: "claw-code".to_string(),
|
||||
repo: "brew-code".to_string(),
|
||||
task_kind: "repo_code".to_string(),
|
||||
source_surface: "omx_team".to_string(),
|
||||
expected_artifacts: vec!["patch".to_string(), "tests".to_string()],
|
||||
|
|
@ -1688,7 +1688,7 @@ mod tests {
|
|||
"Explain this KakaoTalk screenshot for a friend".to_string()
|
||||
),
|
||||
task_receipt: Some(WorkerTaskReceipt {
|
||||
repo: "claw-code".to_string(),
|
||||
repo: "brew-code".to_string(),
|
||||
task_kind: "repo_code".to_string(),
|
||||
source_surface: "omx_team".to_string(),
|
||||
expected_artifacts: vec!["patch".to_string(), "tests".to_string()],
|
||||
|
|
@ -1762,7 +1762,7 @@ mod tests {
|
|||
#[test]
|
||||
fn emit_state_file_writes_worker_status_on_transition() {
|
||||
let cwd_path = std::env::temp_dir().join(format!(
|
||||
"claw-state-test-{}",
|
||||
"brewcode-state-test-{}",
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
|
|
@ -1774,7 +1774,7 @@ mod tests {
|
|||
let worker = registry.create(cwd, &[], true);
|
||||
|
||||
// After create the worker is Spawning — state file should exist
|
||||
let state_path = cwd_path.join(".claw").join("worker-state.json");
|
||||
let state_path = cwd_path.join(".brewcode").join("worker-state.json");
|
||||
assert!(
|
||||
state_path.exists(),
|
||||
"state file should exist after worker creation"
|
||||
|
|
@ -1907,7 +1907,7 @@ mod tests {
|
|||
|
||||
// Now simulate startup timeout
|
||||
let timed_out = registry
|
||||
.observe_startup_timeout(&worker.worker_id, "claw prompt", true, true)
|
||||
.observe_startup_timeout(&worker.worker_id, "brewcode prompt", true, true)
|
||||
.expect("startup timeout observe should succeed");
|
||||
|
||||
let event = timed_out
|
||||
|
|
@ -1945,7 +1945,7 @@ mod tests {
|
|||
|
||||
// Simulate startup timeout while prompt is still in flight
|
||||
let timed_out = registry
|
||||
.observe_startup_timeout(&worker.worker_id, "claw prompt", true, true)
|
||||
.observe_startup_timeout(&worker.worker_id, "brewcode prompt", true, true)
|
||||
.expect("startup timeout observe should succeed");
|
||||
|
||||
let event = timed_out
|
||||
|
|
@ -1993,7 +1993,7 @@ mod tests {
|
|||
.expect("prompt send should record a prompt timestamp");
|
||||
|
||||
let timed_out = registry
|
||||
.observe_startup_timeout(&worker.worker_id, "claw worker", true, true)
|
||||
.observe_startup_timeout(&worker.worker_id, "brewcode worker", true, true)
|
||||
.expect("startup timeout observe should succeed");
|
||||
|
||||
let event = timed_out
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ license.workspace = true
|
|||
publish.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "claw"
|
||||
name = "brewcode"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
const STARTER_CLAW_JSON: &str = concat!(
|
||||
const STARTER_BREWCODE_JSON: &str = concat!(
|
||||
"{\n",
|
||||
" \"permissions\": {\n",
|
||||
" \"defaultMode\": \"dontAsk\"\n",
|
||||
" }\n",
|
||||
"}\n",
|
||||
);
|
||||
const GITIGNORE_COMMENT: &str = "# Claw Code local artifacts";
|
||||
const GITIGNORE_ENTRIES: [&str; 3] = [".claw/settings.local.json", ".claw/sessions/", ".clawhip/"];
|
||||
const GITIGNORE_COMMENT: &str = "# Brew Code local artifacts";
|
||||
const GITIGNORE_ENTRIES: [&str; 3] = [".brewcode/settings.local.json", ".brewcode/sessions/", ".brewcodehip/"];
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum InitStatus {
|
||||
|
|
@ -29,7 +29,7 @@ impl InitStatus {
|
|||
}
|
||||
|
||||
/// Machine-stable identifier for structured output (#142).
|
||||
/// Unlike `label()`, this never changes wording: claws can switch on
|
||||
/// Unlike `label()`, this never changes wording: brewcodes can switch on
|
||||
/// these values without brittle substring matching.
|
||||
#[must_use]
|
||||
pub(crate) fn json_tag(self) -> &'static str {
|
||||
|
|
@ -71,7 +71,7 @@ impl InitReport {
|
|||
lines.join("\n")
|
||||
}
|
||||
|
||||
/// Summary constant that claws can embed in JSON output without having
|
||||
/// Summary constant that brewcodes can embed in JSON output without having
|
||||
/// to read it out of the human-formatted `message` string (#142).
|
||||
pub(crate) const NEXT_STEP: &'static str = "Review and tailor the generated guidance";
|
||||
|
||||
|
|
@ -122,16 +122,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 brewcode_dir = cwd.join(".brewcode");
|
||||
artifacts.push(InitArtifact {
|
||||
name: ".claw/",
|
||||
status: ensure_dir(&claw_dir)?,
|
||||
name: ".brewcode/",
|
||||
status: ensure_dir(&brewcode_dir)?,
|
||||
});
|
||||
|
||||
let claw_json = cwd.join(".claw.json");
|
||||
let brewcode_json = cwd.join(".brewcode.json");
|
||||
artifacts.push(InitArtifact {
|
||||
name: ".claw.json",
|
||||
status: write_file_if_missing(&claw_json, STARTER_CLAW_JSON)?,
|
||||
name: ".brewcode.json",
|
||||
status: write_file_if_missing(&brewcode_json, STARTER_BREWCODE_JSON)?,
|
||||
});
|
||||
|
||||
let gitignore = cwd.join(".gitignore");
|
||||
|
|
@ -206,7 +206,7 @@ pub(crate) fn render_init_claude_md(cwd: &Path) -> String {
|
|||
let mut lines = vec![
|
||||
"# CLAUDE.md".to_string(),
|
||||
String::new(),
|
||||
"This file provides guidance to Claw Code (clawcode.dev) when working with code in this repository.".to_string(),
|
||||
"This file provides guidance to Brew Code (clawcode.dev) when working with code in this repository.".to_string(),
|
||||
String::new(),
|
||||
];
|
||||
|
||||
|
|
@ -251,7 +251,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 `.brewcode.json`; reserve `.brewcode/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());
|
||||
|
||||
|
|
@ -396,16 +396,16 @@ 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(".brewcode/"));
|
||||
assert!(rendered.contains(".brewcode.json"));
|
||||
assert!(rendered.contains("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(".brewcode").is_dir());
|
||||
assert!(root.join(".brewcode.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(".brewcode.json")).expect("read brewcode json"),
|
||||
concat!(
|
||||
"{\n",
|
||||
" \"permissions\": {\n",
|
||||
|
|
@ -415,9 +415,9 @@ 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(".clawhip/"));
|
||||
assert!(gitignore.contains(".brewcode/settings.local.json"));
|
||||
assert!(gitignore.contains(".brewcode/sessions/"));
|
||||
assert!(gitignore.contains(".brewcodehip/"));
|
||||
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"));
|
||||
|
|
@ -430,7 +430,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"), ".claw/settings.local.json\n").expect("write gitignore");
|
||||
fs::write(root.join(".gitignore"), ".brewcode/settings.local.json\n").expect("write gitignore");
|
||||
|
||||
let first = initialize_repo(&root).expect("first init should succeed");
|
||||
assert!(first
|
||||
|
|
@ -438,8 +438,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(".brewcode/"));
|
||||
assert!(second_rendered.contains(".brewcode.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)"));
|
||||
|
|
@ -448,9 +448,9 @@ 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(".clawhip/").count(), 1);
|
||||
assert_eq!(gitignore.matches(".brewcode/settings.local.json").count(), 1);
|
||||
assert_eq!(gitignore.matches(".brewcode/sessions/").count(), 1);
|
||||
assert_eq!(gitignore.matches(".brewcodehip/").count(), 1);
|
||||
|
||||
fs::remove_dir_all(root).expect("cleanup temp dir");
|
||||
}
|
||||
|
|
@ -468,8 +468,8 @@ mod tests {
|
|||
assert_eq!(
|
||||
created_names,
|
||||
vec![
|
||||
".claw/".to_string(),
|
||||
".claw.json".to_string(),
|
||||
".brewcode/".to_string(),
|
||||
".brewcode.json".to_string(),
|
||||
".gitignore".to_string(),
|
||||
"CLAUDE.md".to_string(),
|
||||
],
|
||||
|
|
@ -485,8 +485,8 @@ mod tests {
|
|||
assert_eq!(
|
||||
skipped_names,
|
||||
vec![
|
||||
".claw/".to_string(),
|
||||
".claw.json".to_string(),
|
||||
".brewcode/".to_string(),
|
||||
".brewcode.json".to_string(),
|
||||
".gitignore".to_string(),
|
||||
"CLAUDE.md".to_string(),
|
||||
],
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -930,10 +930,10 @@ mod tests {
|
|||
fn renders_links_as_colored_markdown_labels() {
|
||||
let terminal_renderer = TerminalRenderer::new();
|
||||
let markdown_output =
|
||||
terminal_renderer.render_markdown("See [Claw](https://example.com/docs) now.");
|
||||
terminal_renderer.render_markdown("See [Brewcode](https://example.com/docs) now.");
|
||||
let plain_text = strip_ansi(&markdown_output);
|
||||
|
||||
assert!(plain_text.contains("[Claw](https://example.com/docs)"));
|
||||
assert!(plain_text.contains("[Brewcode](https://example.com/docs)"));
|
||||
assert!(markdown_output.contains('\u{1b}'));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ fn status_command_applies_model_and_permission_mode_flags() {
|
|||
fs::create_dir_all(&temp_dir).expect("temp dir should exist");
|
||||
|
||||
// when
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_claw"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_brewcode"))
|
||||
.current_dir(&temp_dir)
|
||||
.args([
|
||||
"--model",
|
||||
|
|
@ -25,7 +25,7 @@ fn status_command_applies_model_and_permission_mode_flags() {
|
|||
"status",
|
||||
])
|
||||
.output()
|
||||
.expect("claw should launch");
|
||||
.expect("brewcode should launch");
|
||||
|
||||
// then
|
||||
assert_success(&output);
|
||||
|
|
@ -45,7 +45,7 @@ fn resume_flag_loads_a_saved_session_and_dispatches_status() {
|
|||
let session_path = write_session(&temp_dir, "resume-status");
|
||||
|
||||
// when
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_claw"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_brewcode"))
|
||||
.current_dir(&temp_dir)
|
||||
.args([
|
||||
"--resume",
|
||||
|
|
@ -53,7 +53,7 @@ fn resume_flag_loads_a_saved_session_and_dispatches_status() {
|
|||
"/status",
|
||||
])
|
||||
.output()
|
||||
.expect("claw should launch");
|
||||
.expect("brewcode should launch");
|
||||
|
||||
// then
|
||||
assert_success(&output);
|
||||
|
|
@ -73,16 +73,16 @@ fn slash_command_names_match_known_commands_and_suggest_nearby_unknown_ones() {
|
|||
fs::create_dir_all(&temp_dir).expect("temp dir should exist");
|
||||
|
||||
// when
|
||||
let help_output = Command::new(env!("CARGO_BIN_EXE_claw"))
|
||||
let help_output = Command::new(env!("CARGO_BIN_EXE_brewcode"))
|
||||
.current_dir(&temp_dir)
|
||||
.arg("/help")
|
||||
.output()
|
||||
.expect("claw should launch");
|
||||
let unknown_output = Command::new(env!("CARGO_BIN_EXE_claw"))
|
||||
.expect("brewcode should launch");
|
||||
let unknown_output = Command::new(env!("CARGO_BIN_EXE_brewcode"))
|
||||
.current_dir(&temp_dir)
|
||||
.arg("/zstats")
|
||||
.output()
|
||||
.expect("claw should launch");
|
||||
.expect("brewcode should launch");
|
||||
|
||||
// then
|
||||
assert_success(&help_output);
|
||||
|
|
@ -109,11 +109,11 @@ fn omc_namespaced_slash_commands_surface_a_targeted_compatibility_hint() {
|
|||
let temp_dir = unique_temp_dir("slash-dispatch-omc");
|
||||
fs::create_dir_all(&temp_dir).expect("temp dir should exist");
|
||||
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_claw"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_brewcode"))
|
||||
.current_dir(&temp_dir)
|
||||
.arg("/oh-my-claudecode:hud")
|
||||
.output()
|
||||
.expect("claw should launch");
|
||||
.expect("brewcode should launch");
|
||||
|
||||
assert!(
|
||||
!output.status.success(),
|
||||
|
|
@ -133,16 +133,16 @@ fn omc_namespaced_slash_commands_surface_a_targeted_compatibility_hint() {
|
|||
fn config_command_loads_defaults_from_standard_config_locations() {
|
||||
// given
|
||||
let temp_dir = unique_temp_dir("config-defaults");
|
||||
let config_home = temp_dir.join("home").join(".claw");
|
||||
fs::create_dir_all(temp_dir.join(".claw")).expect("project config dir should exist");
|
||||
let config_home = temp_dir.join("home").join(".brewcode");
|
||||
fs::create_dir_all(temp_dir.join(".brewcode")).expect("project config dir should exist");
|
||||
fs::create_dir_all(&config_home).expect("home config dir should exist");
|
||||
|
||||
fs::write(config_home.join("settings.json"), r#"{"model":"haiku"}"#)
|
||||
.expect("write user settings");
|
||||
fs::write(temp_dir.join(".claw.json"), r#"{"model":"sonnet"}"#)
|
||||
fs::write(temp_dir.join(".brewcode.json"), r#"{"model":"sonnet"}"#)
|
||||
.expect("write project settings");
|
||||
fs::write(
|
||||
temp_dir.join(".claw").join("settings.local.json"),
|
||||
temp_dir.join(".brewcode").join("settings.local.json"),
|
||||
r#"{"model":"opus"}"#,
|
||||
)
|
||||
.expect("write local settings");
|
||||
|
|
@ -150,7 +150,7 @@ fn config_command_loads_defaults_from_standard_config_locations() {
|
|||
|
||||
// when
|
||||
let output = command_in(&temp_dir)
|
||||
.env("CLAW_CONFIG_HOME", &config_home)
|
||||
.env("BREWCODE_CONFIG_HOME", &config_home)
|
||||
.args([
|
||||
"--resume",
|
||||
session_path.to_str().expect("utf8 path"),
|
||||
|
|
@ -158,7 +158,7 @@ fn config_command_loads_defaults_from_standard_config_locations() {
|
|||
"model",
|
||||
])
|
||||
.output()
|
||||
.expect("claw should launch");
|
||||
.expect("brewcode should launch");
|
||||
|
||||
// then
|
||||
assert_success(&output);
|
||||
|
|
@ -173,10 +173,10 @@ fn config_command_loads_defaults_from_standard_config_locations() {
|
|||
.to_str()
|
||||
.expect("utf8 path")
|
||||
));
|
||||
assert!(stdout.contains(temp_dir.join(".claw.json").to_str().expect("utf8 path")));
|
||||
assert!(stdout.contains(temp_dir.join(".brewcode.json").to_str().expect("utf8 path")));
|
||||
assert!(stdout.contains(
|
||||
temp_dir
|
||||
.join(".claw")
|
||||
.join(".brewcode")
|
||||
.join("settings.local.json")
|
||||
.to_str()
|
||||
.expect("utf8 path")
|
||||
|
|
@ -189,18 +189,18 @@ fn config_command_loads_defaults_from_standard_config_locations() {
|
|||
fn doctor_command_runs_as_a_local_shell_entrypoint() {
|
||||
// given
|
||||
let temp_dir = unique_temp_dir("doctor-entrypoint");
|
||||
let config_home = temp_dir.join("home").join(".claw");
|
||||
let config_home = temp_dir.join("home").join(".brewcode");
|
||||
fs::create_dir_all(&config_home).expect("config home should exist");
|
||||
|
||||
// when
|
||||
let output = command_in(&temp_dir)
|
||||
.env("CLAW_CONFIG_HOME", &config_home)
|
||||
.env("BREWCODE_CONFIG_HOME", &config_home)
|
||||
.env_remove("ANTHROPIC_API_KEY")
|
||||
.env_remove("ANTHROPIC_AUTH_TOKEN")
|
||||
.env("ANTHROPIC_BASE_URL", "http://127.0.0.1:9")
|
||||
.arg("doctor")
|
||||
.output()
|
||||
.expect("claw doctor should launch");
|
||||
.expect("brewcode doctor should launch");
|
||||
|
||||
// then
|
||||
assert_success(&output);
|
||||
|
|
@ -218,7 +218,7 @@ fn doctor_command_runs_as_a_local_shell_entrypoint() {
|
|||
#[test]
|
||||
fn local_smoke_commands_do_not_require_live_credentials() {
|
||||
let temp_dir = unique_temp_dir("offline-local-smoke path with spaces");
|
||||
let config_home = temp_dir.join("home with spaces").join(".claw");
|
||||
let config_home = temp_dir.join("home with spaces").join(".brewcode");
|
||||
fs::create_dir_all(&config_home).expect("config home should exist");
|
||||
fs::create_dir_all(&temp_dir).expect("temp dir should exist");
|
||||
|
||||
|
|
@ -231,13 +231,13 @@ fn local_smoke_commands_do_not_require_live_credentials() {
|
|||
let output = offline_command_in(&temp_dir, &config_home)
|
||||
.args(args)
|
||||
.output()
|
||||
.unwrap_or_else(|error| panic!("claw {args:?} should launch: {error}"));
|
||||
.unwrap_or_else(|error| panic!("brewcode {args:?} should launch: {error}"));
|
||||
|
||||
assert_success(&output);
|
||||
let stdout = String::from_utf8(output.stdout).expect("stdout should be utf8");
|
||||
let stderr = String::from_utf8(output.stderr).expect("stderr should be utf8");
|
||||
assert!(
|
||||
stdout.contains("claw")
|
||||
stdout.contains("brewcode")
|
||||
|| stdout.contains("Status")
|
||||
|| stdout.contains("Config")
|
||||
|| stdout.contains("Doctor"),
|
||||
|
|
@ -260,11 +260,11 @@ fn local_smoke_commands_do_not_require_live_credentials() {
|
|||
#[test]
|
||||
fn local_subcommand_help_does_not_fall_through_to_runtime_or_provider_calls() {
|
||||
let temp_dir = unique_temp_dir("subcommand-help");
|
||||
let config_home = temp_dir.join("home").join(".claw");
|
||||
let config_home = temp_dir.join("home").join(".brewcode");
|
||||
fs::create_dir_all(&config_home).expect("config home should exist");
|
||||
|
||||
let doctor_help = command_in(&temp_dir)
|
||||
.env("CLAW_CONFIG_HOME", &config_home)
|
||||
.env("BREWCODE_CONFIG_HOME", &config_home)
|
||||
.env_remove("ANTHROPIC_API_KEY")
|
||||
.env_remove("ANTHROPIC_AUTH_TOKEN")
|
||||
.env("ANTHROPIC_BASE_URL", "http://127.0.0.1:9")
|
||||
|
|
@ -272,7 +272,7 @@ fn local_subcommand_help_does_not_fall_through_to_runtime_or_provider_calls() {
|
|||
.output()
|
||||
.expect("doctor help should launch");
|
||||
let status_help = command_in(&temp_dir)
|
||||
.env("CLAW_CONFIG_HOME", &config_home)
|
||||
.env("BREWCODE_CONFIG_HOME", &config_home)
|
||||
.env_remove("ANTHROPIC_API_KEY")
|
||||
.env_remove("ANTHROPIC_AUTH_TOKEN")
|
||||
.env("ANTHROPIC_BASE_URL", "http://127.0.0.1:9")
|
||||
|
|
@ -282,13 +282,13 @@ fn local_subcommand_help_does_not_fall_through_to_runtime_or_provider_calls() {
|
|||
|
||||
assert_success(&doctor_help);
|
||||
let doctor_stdout = String::from_utf8(doctor_help.stdout).expect("stdout should be utf8");
|
||||
assert!(doctor_stdout.contains("Usage claw doctor"));
|
||||
assert!(doctor_stdout.contains("Usage brewcode doctor"));
|
||||
assert!(doctor_stdout.contains("local-only health report"));
|
||||
assert!(!doctor_stdout.contains("Thinking"));
|
||||
|
||||
assert_success(&status_help);
|
||||
let status_stdout = String::from_utf8(status_help.stdout).expect("stdout should be utf8");
|
||||
assert!(status_stdout.contains("Usage claw status"));
|
||||
assert!(status_stdout.contains("Usage brewcode status"));
|
||||
assert!(status_stdout.contains("local workspace snapshot"));
|
||||
assert!(!status_stdout.contains("Thinking"));
|
||||
|
||||
|
|
@ -303,7 +303,7 @@ fn local_subcommand_help_does_not_fall_through_to_runtime_or_provider_calls() {
|
|||
fn offline_command_in(cwd: &Path, config_home: &Path) -> Command {
|
||||
let mut command = command_in(cwd);
|
||||
command
|
||||
.env("CLAW_CONFIG_HOME", config_home)
|
||||
.env("BREWCODE_CONFIG_HOME", config_home)
|
||||
.env_remove("ANTHROPIC_API_KEY")
|
||||
.env_remove("ANTHROPIC_AUTH_TOKEN")
|
||||
.env_remove("OPENAI_API_KEY")
|
||||
|
|
@ -314,7 +314,7 @@ fn offline_command_in(cwd: &Path, config_home: &Path) -> Command {
|
|||
}
|
||||
|
||||
fn command_in(cwd: &Path) -> Command {
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_claw"));
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_brewcode"));
|
||||
command.current_dir(cwd);
|
||||
command
|
||||
}
|
||||
|
|
@ -347,7 +347,7 @@ fn unique_temp_dir(label: &str) -> PathBuf {
|
|||
.as_millis();
|
||||
let counter = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
std::env::temp_dir().join(format!(
|
||||
"claw-{label}-{}-{millis}-{counter}",
|
||||
"brewcode-{label}-{}-{millis}-{counter}",
|
||||
std::process::id()
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,9 +29,9 @@ fn compact_flag_prints_only_final_assistant_text_without_tool_call_details() {
|
|||
fs::create_dir_all(&home).expect("home should exist");
|
||||
fs::write(workspace.join("fixture.txt"), "alpha parity line\n").expect("fixture should write");
|
||||
|
||||
// when we run claw in compact text mode against a tool-using scenario
|
||||
// when we run brewcode in compact text mode against a tool-using scenario
|
||||
let prompt = format!("{SCENARIO_PREFIX}read_file_roundtrip");
|
||||
let output = run_claw(
|
||||
let output = run_brewcode(
|
||||
&workspace,
|
||||
&config_home,
|
||||
&home,
|
||||
|
|
@ -95,9 +95,9 @@ fn compact_flag_streaming_text_only_emits_final_message_text() {
|
|||
fs::create_dir_all(&config_home).expect("config home should exist");
|
||||
fs::create_dir_all(&home).expect("home should exist");
|
||||
|
||||
// when we invoke claw with --compact for the streaming text scenario
|
||||
// when we invoke brewcode with --compact for the streaming text scenario
|
||||
let prompt = format!("{SCENARIO_PREFIX}streaming_text");
|
||||
let output = run_claw(
|
||||
let output = run_brewcode(
|
||||
&workspace,
|
||||
&config_home,
|
||||
&home,
|
||||
|
|
@ -145,9 +145,9 @@ fn text_prompt_mode_prints_final_assistant_text_after_spinner() {
|
|||
fs::create_dir_all(&config_home).expect("config home should exist");
|
||||
fs::create_dir_all(&home).expect("home should exist");
|
||||
|
||||
// when we invoke claw in normal text prompt mode for the streaming text scenario
|
||||
// when we invoke brewcode in normal text prompt mode for the streaming text scenario
|
||||
let prompt = format!("{SCENARIO_PREFIX}streaming_text");
|
||||
let output = run_claw(
|
||||
let output = run_brewcode(
|
||||
&workspace,
|
||||
&config_home,
|
||||
&home,
|
||||
|
|
@ -204,7 +204,7 @@ fn compact_flag_with_json_output_emits_structured_json() {
|
|||
fs::create_dir_all(&home).expect("home should exist");
|
||||
|
||||
let prompt = format!("{SCENARIO_PREFIX}streaming_text");
|
||||
let output = run_claw(
|
||||
let output = run_brewcode(
|
||||
&workspace,
|
||||
&config_home,
|
||||
&home,
|
||||
|
|
@ -245,25 +245,25 @@ stderr:
|
|||
fs::remove_dir_all(&workspace).expect("workspace cleanup should succeed");
|
||||
}
|
||||
|
||||
fn run_claw(
|
||||
fn run_brewcode(
|
||||
cwd: &std::path::Path,
|
||||
config_home: &std::path::Path,
|
||||
home: &std::path::Path,
|
||||
base_url: &str,
|
||||
args: &[&str],
|
||||
) -> Output {
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_claw"));
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_brewcode"));
|
||||
command
|
||||
.current_dir(cwd)
|
||||
.env_clear()
|
||||
.env("ANTHROPIC_API_KEY", "test-compact-key")
|
||||
.env("ANTHROPIC_BASE_URL", base_url)
|
||||
.env("CLAW_CONFIG_HOME", config_home)
|
||||
.env("BREWCODE_CONFIG_HOME", config_home)
|
||||
.env("HOME", home)
|
||||
.env("NO_COLOR", "1")
|
||||
.env("PATH", "/usr/bin:/bin")
|
||||
.args(args);
|
||||
command.output().expect("claw should launch")
|
||||
command.output().expect("brewcode should launch")
|
||||
}
|
||||
|
||||
fn unique_temp_dir(label: &str) -> PathBuf {
|
||||
|
|
@ -273,7 +273,7 @@ fn unique_temp_dir(label: &str) -> PathBuf {
|
|||
.as_millis();
|
||||
let counter = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
std::env::temp_dir().join(format!(
|
||||
"claw-compact-{label}-{}-{millis}-{counter}",
|
||||
"brewcode-compact-{label}-{}-{millis}-{counter}",
|
||||
std::process::id()
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ fn compact_slash_command_in_repl_does_not_start_nested_tokio_runtime() {
|
|||
fs::create_dir_all(&home).expect("home should exist");
|
||||
|
||||
// when
|
||||
let output = run_claw_repl(&workspace, &config_home, &home, "/compact\n/exit\n");
|
||||
let output = run_brewcode_repl(&workspace, &config_home, &home, "/compact\n/exit\n");
|
||||
|
||||
// then
|
||||
assert!(
|
||||
|
|
@ -49,18 +49,18 @@ fn compact_slash_command_in_repl_does_not_start_nested_tokio_runtime() {
|
|||
fs::remove_dir_all(&workspace).expect("workspace cleanup should succeed");
|
||||
}
|
||||
|
||||
fn run_claw_repl(
|
||||
fn run_brewcode_repl(
|
||||
cwd: &std::path::Path,
|
||||
config_home: &std::path::Path,
|
||||
home: &std::path::Path,
|
||||
stdin: &str,
|
||||
) -> Output {
|
||||
let mut command = python_pty_command(env!("CARGO_BIN_EXE_claw"));
|
||||
let mut command = python_pty_command(env!("CARGO_BIN_EXE_brewcode"));
|
||||
let mut child = command
|
||||
.current_dir(cwd)
|
||||
.env_clear()
|
||||
.env("ANTHROPIC_API_KEY", "test-compact-repl-key")
|
||||
.env("CLAW_CONFIG_HOME", config_home)
|
||||
.env("BREWCODE_CONFIG_HOME", config_home)
|
||||
.env("HOME", home)
|
||||
.env("NO_COLOR", "1")
|
||||
.env("PATH", "/usr/bin:/bin")
|
||||
|
|
@ -68,7 +68,7 @@ fn run_claw_repl(
|
|||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("claw should launch");
|
||||
.expect("brewcode should launch");
|
||||
|
||||
child
|
||||
.stdin
|
||||
|
|
@ -77,10 +77,10 @@ fn run_claw_repl(
|
|||
.write_all(stdin.as_bytes())
|
||||
.expect("stdin should write");
|
||||
|
||||
child.wait_with_output().expect("claw should finish")
|
||||
child.wait_with_output().expect("brewcode should finish")
|
||||
}
|
||||
|
||||
fn python_pty_command(claw: &str) -> Command {
|
||||
fn python_pty_command(brewcode: &str) -> Command {
|
||||
let mut command = Command::new("python3");
|
||||
command.args([
|
||||
"-c",
|
||||
|
|
@ -90,10 +90,10 @@ import pty
|
|||
import subprocess
|
||||
import sys
|
||||
|
||||
claw = sys.argv[1]
|
||||
brewcode = sys.argv[1]
|
||||
payload = sys.stdin.buffer.read()
|
||||
master, slave = pty.openpty()
|
||||
child = subprocess.Popen([claw], stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
child = subprocess.Popen([brewcode], stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
os.close(slave)
|
||||
os.write(master, payload)
|
||||
stdout, stderr = child.communicate(timeout=30)
|
||||
|
|
@ -102,7 +102,7 @@ sys.stdout.buffer.write(stdout)
|
|||
sys.stderr.buffer.write(stderr)
|
||||
raise SystemExit(child.returncode)
|
||||
"#,
|
||||
claw,
|
||||
brewcode,
|
||||
]);
|
||||
command
|
||||
}
|
||||
|
|
@ -114,7 +114,7 @@ fn unique_temp_dir(label: &str) -> PathBuf {
|
|||
.as_millis();
|
||||
let counter = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
std::env::temp_dir().join(format!(
|
||||
"claw-{label}-{}-{millis}-{counter}",
|
||||
"brewcode-{label}-{}-{millis}-{counter}",
|
||||
std::process::id()
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,13 +308,13 @@ struct ScenarioReport {
|
|||
}
|
||||
|
||||
fn run_case(case: ScenarioCase, workspace: &HarnessWorkspace, base_url: &str) -> ScenarioRun {
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_claw"));
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_brewcode"));
|
||||
command
|
||||
.current_dir(&workspace.root)
|
||||
.env_clear()
|
||||
.env("ANTHROPIC_API_KEY", "test-parity-key")
|
||||
.env("ANTHROPIC_BASE_URL", base_url)
|
||||
.env("CLAW_CONFIG_HOME", &workspace.config_home)
|
||||
.env("BREWCODE_CONFIG_HOME", &workspace.config_home)
|
||||
.env("HOME", &workspace.home)
|
||||
.env("NO_COLOR", "1")
|
||||
.env("PATH", "/usr/bin:/bin")
|
||||
|
|
@ -345,16 +345,16 @@ fn run_case(case: ScenarioCase, workspace: &HarnessWorkspace, base_url: &str) ->
|
|||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("claw should launch");
|
||||
.expect("brewcode should launch");
|
||||
child
|
||||
.stdin
|
||||
.as_mut()
|
||||
.expect("stdin should be piped")
|
||||
.write_all(stdin.as_bytes())
|
||||
.expect("stdin should write");
|
||||
child.wait_with_output().expect("claw should finish")
|
||||
child.wait_with_output().expect("brewcode should finish")
|
||||
} else {
|
||||
command.output().expect("claw should launch")
|
||||
command.output().expect("brewcode should launch")
|
||||
};
|
||||
|
||||
assert_success(&output);
|
||||
|
|
@ -367,7 +367,7 @@ fn run_case(case: ScenarioCase, workspace: &HarnessWorkspace, base_url: &str) ->
|
|||
|
||||
#[allow(dead_code)]
|
||||
fn prepare_auto_compact_fixture(workspace: &HarnessWorkspace) {
|
||||
let sessions_dir = workspace.root.join(".claw").join("sessions");
|
||||
let sessions_dir = workspace.root.join(".brewcode").join("sessions");
|
||||
fs::create_dir_all(&sessions_dir).expect("sessions dir should exist");
|
||||
|
||||
// Write a pre-seeded session with 6 messages so auto-compact can remove them
|
||||
|
|
@ -423,7 +423,7 @@ fn prepare_plugin_fixture(workspace: &HarnessWorkspace) {
|
|||
let script_path = tool_dir.join("echo-json.sh");
|
||||
fs::write(
|
||||
&script_path,
|
||||
"#!/bin/sh\nINPUT=$(cat)\nprintf '{\"plugin\":\"%s\",\"tool\":\"%s\",\"input\":%s}\\n' \"$CLAWD_PLUGIN_ID\" \"$CLAWD_TOOL_NAME\" \"$INPUT\"\n",
|
||||
"#!/bin/sh\nINPUT=$(cat)\nprintf '{\"plugin\":\"%s\",\"tool\":\"%s\",\"input\":%s}\\n' \"$BREWCODED_PLUGIN_ID\" \"$BREWCODED_TOOL_NAME\" \"$INPUT\"\n",
|
||||
)
|
||||
.expect("plugin script should write");
|
||||
let mut permissions = fs::metadata(&script_path)
|
||||
|
|
@ -877,7 +877,7 @@ fn unique_temp_dir(label: &str) -> PathBuf {
|
|||
.as_millis();
|
||||
let counter = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
std::env::temp_dir().join(format!(
|
||||
"claw-mock-parity-{label}-{}-{millis}-{counter}",
|
||||
"brewcode-mock-parity-{label}-{}-{millis}-{counter}",
|
||||
std::process::id()
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ fn export_help_emits_bounded_json_when_requested_384() {
|
|||
assert_eq!(parsed["command"], "export");
|
||||
assert_eq!(
|
||||
parsed["usage"],
|
||||
"claw export [--session <id|latest>] [--output <path>] [--output-format <format>]"
|
||||
"brewcode export [--session <id|latest>] [--output <path>] [--output-format <format>]"
|
||||
);
|
||||
assert_eq!(parsed["defaults"]["session"], "latest");
|
||||
assert!(parsed["options"].as_array().expect("options").len() >= 4);
|
||||
|
|
@ -45,7 +45,7 @@ fn export_help_preserves_plaintext_in_text_mode_384() {
|
|||
let root = unique_temp_dir("export-help-text");
|
||||
fs::create_dir_all(&root).expect("temp dir should exist");
|
||||
|
||||
let output = run_claw(&root, &["export", "--help"], &[]);
|
||||
let output = run_brewcode(&root, &["export", "--help"], &[]);
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"stdout:\n{}\n\nstderr:\n{}",
|
||||
|
|
@ -54,7 +54,7 @@ fn export_help_preserves_plaintext_in_text_mode_384() {
|
|||
);
|
||||
let stdout = String::from_utf8(output.stdout).expect("stdout utf8");
|
||||
assert!(stdout.starts_with("Export\n"));
|
||||
assert!(stdout.contains("Usage claw export"));
|
||||
assert!(stdout.contains("Usage brewcode export"));
|
||||
serde_json::from_str::<Value>(&stdout).expect_err("text help should remain plaintext");
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ fn inventory_commands_emit_structured_json_when_requested() {
|
|||
&[
|
||||
("HOME", isolated_home.to_str().expect("utf8 home")),
|
||||
(
|
||||
"CLAW_CONFIG_HOME",
|
||||
"BREWCODE_CONFIG_HOME",
|
||||
isolated_config.to_str().expect("utf8 config home"),
|
||||
),
|
||||
(
|
||||
|
|
@ -268,7 +268,7 @@ fn plugins_json_surfaces_lifecycle_contract_when_plugin_is_installed() {
|
|||
&[
|
||||
("HOME", home.to_str().expect("home path should be utf8")),
|
||||
(
|
||||
"CLAW_CONFIG_HOME",
|
||||
"BREWCODE_CONFIG_HOME",
|
||||
config_home.to_str().expect("config path should be utf8"),
|
||||
),
|
||||
],
|
||||
|
|
@ -334,7 +334,7 @@ fn agents_command_emits_structured_agent_entries_when_requested() {
|
|||
&[
|
||||
("HOME", home.to_str().expect("utf8 home")),
|
||||
(
|
||||
"CLAW_CONFIG_HOME",
|
||||
"BREWCODE_CONFIG_HOME",
|
||||
isolated_config.to_str().expect("utf8 config home"),
|
||||
),
|
||||
(
|
||||
|
|
@ -350,12 +350,12 @@ fn agents_command_emits_structured_agent_entries_when_requested() {
|
|||
assert_eq!(parsed["summary"]["active"], 2);
|
||||
assert_eq!(parsed["summary"]["shadowed"], 1);
|
||||
assert_eq!(parsed["agents"][0]["name"], "planner");
|
||||
assert_eq!(parsed["agents"][0]["source"]["id"], "project_claw");
|
||||
assert_eq!(parsed["agents"][0]["source"]["id"], "project_brewcode");
|
||||
assert_eq!(parsed["agents"][0]["active"], true);
|
||||
assert_eq!(parsed["agents"][1]["name"], "verifier");
|
||||
assert_eq!(parsed["agents"][2]["name"], "planner");
|
||||
assert_eq!(parsed["agents"][2]["active"], false);
|
||||
assert_eq!(parsed["agents"][2]["shadowed_by"]["id"], "project_claw");
|
||||
assert_eq!(parsed["agents"][2]["shadowed_by"]["id"], "project_brewcode");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -453,7 +453,7 @@ fn doctor_and_resume_status_emit_json_when_requested() {
|
|||
);
|
||||
assert_eq!(
|
||||
install_source["deprecated_install"],
|
||||
"cargo install claw-code"
|
||||
"cargo install brew-code"
|
||||
);
|
||||
|
||||
let workspace = checks
|
||||
|
|
@ -519,7 +519,7 @@ fn resumed_inventory_commands_emit_structured_json_when_requested() {
|
|||
],
|
||||
&[
|
||||
(
|
||||
"CLAW_CONFIG_HOME",
|
||||
"BREWCODE_CONFIG_HOME",
|
||||
config_home.to_str().expect("utf8 config home"),
|
||||
),
|
||||
("HOME", home.to_str().expect("utf8 home")),
|
||||
|
|
@ -540,7 +540,7 @@ fn resumed_inventory_commands_emit_structured_json_when_requested() {
|
|||
],
|
||||
&[
|
||||
(
|
||||
"CLAW_CONFIG_HOME",
|
||||
"BREWCODE_CONFIG_HOME",
|
||||
config_home.to_str().expect("utf8 config home"),
|
||||
),
|
||||
("HOME", home.to_str().expect("utf8 home")),
|
||||
|
|
@ -562,7 +562,7 @@ fn resumed_inventory_commands_emit_structured_json_when_requested() {
|
|||
],
|
||||
&[
|
||||
(
|
||||
"CLAW_CONFIG_HOME",
|
||||
"BREWCODE_CONFIG_HOME",
|
||||
config_home.to_str().expect("utf8 config home"),
|
||||
),
|
||||
("HOME", home.to_str().expect("utf8 home")),
|
||||
|
|
@ -590,7 +590,7 @@ fn resumed_inventory_commands_emit_structured_json_when_requested() {
|
|||
],
|
||||
&[
|
||||
(
|
||||
"CLAW_CONFIG_HOME",
|
||||
"BREWCODE_CONFIG_HOME",
|
||||
config_home.to_str().expect("utf8 config home"),
|
||||
),
|
||||
("HOME", home.to_str().expect("utf8 home")),
|
||||
|
|
@ -687,11 +687,11 @@ fn mcp_json_reports_required_optional_and_redacts_secret_values() {
|
|||
let root = unique_temp_dir("mcp-required-optional");
|
||||
let config_home = root.join("config-home");
|
||||
let home = root.join("home");
|
||||
fs::create_dir_all(root.join(".claw")).expect("workspace config should exist");
|
||||
fs::create_dir_all(root.join(".brewcode")).expect("workspace config should exist");
|
||||
fs::create_dir_all(&config_home).expect("config home should exist");
|
||||
fs::create_dir_all(&home).expect("home should exist");
|
||||
fs::write(
|
||||
root.join(".claw").join("settings.json"),
|
||||
root.join(".brewcode").join("settings.json"),
|
||||
r#"{
|
||||
"mcpServers": {
|
||||
"required-stdio": {
|
||||
|
|
@ -716,7 +716,7 @@ fn mcp_json_reports_required_optional_and_redacts_secret_values() {
|
|||
|
||||
let envs = [
|
||||
(
|
||||
"CLAW_CONFIG_HOME",
|
||||
"BREWCODE_CONFIG_HOME",
|
||||
config_home.to_str().expect("config home"),
|
||||
),
|
||||
("HOME", home.to_str().expect("home")),
|
||||
|
|
@ -770,7 +770,7 @@ fn mcp_degraded_config_and_failed_usage_are_distinct_json_contracts() {
|
|||
fs::create_dir_all(&config_home).expect("config home should exist");
|
||||
fs::create_dir_all(&home).expect("home should exist");
|
||||
fs::write(
|
||||
root.join(".claw.json"),
|
||||
root.join(".brewcode.json"),
|
||||
r#"{
|
||||
"mcpServers": {
|
||||
"missing-command": {
|
||||
|
|
@ -783,7 +783,7 @@ fn mcp_degraded_config_and_failed_usage_are_distinct_json_contracts() {
|
|||
.expect("malformed mcp config should write");
|
||||
let envs = [
|
||||
(
|
||||
"CLAW_CONFIG_HOME",
|
||||
"BREWCODE_CONFIG_HOME",
|
||||
config_home.to_str().expect("config home"),
|
||||
),
|
||||
("HOME", home.to_str().expect("home")),
|
||||
|
|
@ -799,7 +799,7 @@ fn mcp_degraded_config_and_failed_usage_are_distinct_json_contracts() {
|
|||
assert_eq!(degraded["configured_servers"], 0);
|
||||
assert!(degraded["servers"].as_array().expect("servers").is_empty());
|
||||
|
||||
let failed_output = run_claw(
|
||||
let failed_output = run_brewcode(
|
||||
&root,
|
||||
&["--output-format", "json", "mcp", "list", "extra"],
|
||||
&envs,
|
||||
|
|
@ -822,7 +822,7 @@ fn assert_json_command(current_dir: &Path, args: &[&str]) -> Value {
|
|||
}
|
||||
|
||||
fn assert_json_command_with_env(current_dir: &Path, args: &[&str], envs: &[(&str, &str)]) -> Value {
|
||||
let output = run_claw(current_dir, args, envs);
|
||||
let output = run_brewcode(current_dir, args, envs);
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"stdout:\n{}\n\nstderr:\n{}",
|
||||
|
|
@ -832,17 +832,17 @@ fn assert_json_command_with_env(current_dir: &Path, args: &[&str], envs: &[(&str
|
|||
serde_json::from_slice(&output.stdout).expect("stdout should be valid json")
|
||||
}
|
||||
|
||||
fn run_claw(current_dir: &Path, args: &[&str], envs: &[(&str, &str)]) -> Output {
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_claw"));
|
||||
fn run_brewcode(current_dir: &Path, args: &[&str], envs: &[(&str, &str)]) -> Output {
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_brewcode"));
|
||||
command.current_dir(current_dir).args(args);
|
||||
for (key, value) in envs {
|
||||
command.env(key, value);
|
||||
}
|
||||
command.output().expect("claw should launch")
|
||||
command.output().expect("brewcode should launch")
|
||||
}
|
||||
|
||||
fn write_upstream_fixture(root: &Path) -> PathBuf {
|
||||
let upstream = root.join("claw-code");
|
||||
let upstream = root.join("brew-code");
|
||||
let src = upstream.join("src");
|
||||
let entrypoints = src.join("entrypoints");
|
||||
fs::create_dir_all(&entrypoints).expect("upstream entrypoints dir should exist");
|
||||
|
|
@ -900,7 +900,7 @@ fn unique_temp_dir(label: &str) -> PathBuf {
|
|||
.as_millis();
|
||||
let counter = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
std::env::temp_dir().join(format!(
|
||||
"claw-output-format-{label}-{}-{millis}-{counter}",
|
||||
"brewcode-output-format-{label}-{}-{millis}-{counter}",
|
||||
std::process::id()
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ fn resumed_binary_accepts_slash_commands_with_arguments() {
|
|||
.expect("session should persist");
|
||||
|
||||
// when
|
||||
let output = run_claw(
|
||||
let output = run_brewcode(
|
||||
&temp_dir,
|
||||
&[
|
||||
"--resume",
|
||||
|
|
@ -56,7 +56,7 @@ fn resumed_binary_accepts_slash_commands_with_arguments() {
|
|||
assert!(stdout.contains("Session cleared"));
|
||||
assert!(stdout.contains("Mode resumed session reset"));
|
||||
assert!(stdout.contains("Previous session"));
|
||||
assert!(stdout.contains("Resume previous claw --resume"));
|
||||
assert!(stdout.contains("Resume previous brewcode --resume"));
|
||||
assert!(stdout.contains("Backup "));
|
||||
assert!(stdout.contains("Session file "));
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ fn status_command_applies_cli_flags_end_to_end() {
|
|||
fs::create_dir_all(&temp_dir).expect("temp dir should exist");
|
||||
|
||||
// when
|
||||
let output = run_claw(
|
||||
let output = run_brewcode(
|
||||
&temp_dir,
|
||||
&[
|
||||
"--model",
|
||||
|
|
@ -117,8 +117,8 @@ fn resumed_config_command_loads_settings_files_end_to_end() {
|
|||
// given
|
||||
let temp_dir = unique_temp_dir("resume-config");
|
||||
let project_dir = temp_dir.join("project");
|
||||
let config_home = temp_dir.join("home").join(".claw");
|
||||
fs::create_dir_all(project_dir.join(".claw")).expect("project config dir should exist");
|
||||
let config_home = temp_dir.join("home").join(".brewcode");
|
||||
fs::create_dir_all(project_dir.join(".brewcode")).expect("project config dir should exist");
|
||||
fs::create_dir_all(&config_home).expect("config home should exist");
|
||||
|
||||
let session_path = project_dir.join("session.jsonl");
|
||||
|
|
@ -130,13 +130,13 @@ fn resumed_config_command_loads_settings_files_end_to_end() {
|
|||
fs::write(config_home.join("settings.json"), r#"{"model":"haiku"}"#)
|
||||
.expect("user config should write");
|
||||
fs::write(
|
||||
project_dir.join(".claw").join("settings.local.json"),
|
||||
project_dir.join(".brewcode").join("settings.local.json"),
|
||||
r#"{"model":"opus"}"#,
|
||||
)
|
||||
.expect("local config should write");
|
||||
|
||||
// when
|
||||
let output = run_claw_with_env(
|
||||
let output = run_brewcode_with_env(
|
||||
&project_dir,
|
||||
&[
|
||||
"--resume",
|
||||
|
|
@ -144,7 +144,7 @@ fn resumed_config_command_loads_settings_files_end_to_end() {
|
|||
"/config",
|
||||
"model",
|
||||
],
|
||||
&[("CLAW_CONFIG_HOME", config_home.to_str().expect("utf8 path"))],
|
||||
&[("BREWCODE_CONFIG_HOME", config_home.to_str().expect("utf8 path"))],
|
||||
);
|
||||
|
||||
// then
|
||||
|
|
@ -166,7 +166,7 @@ fn resumed_config_command_loads_settings_files_end_to_end() {
|
|||
));
|
||||
assert!(stdout.contains(
|
||||
project_dir
|
||||
.join(".claw")
|
||||
.join(".brewcode")
|
||||
.join("settings.local.json")
|
||||
.to_str()
|
||||
.expect("utf8 path")
|
||||
|
|
@ -206,7 +206,7 @@ fn resume_latest_restores_the_most_recent_managed_session() {
|
|||
.expect("newer session should persist");
|
||||
|
||||
// when
|
||||
let output = run_claw(&project_dir, &["--resume", "latest", "/status"]);
|
||||
let output = run_brewcode(&project_dir, &["--resume", "latest", "/status"]);
|
||||
|
||||
// then
|
||||
assert!(
|
||||
|
|
@ -240,9 +240,9 @@ fn resumed_status_command_emits_structured_json_when_requested() {
|
|||
.expect("session should persist");
|
||||
|
||||
// when
|
||||
// Use an isolated CLAW_CONFIG_HOME so ~/.claw/settings.json is not loaded,
|
||||
// Use an isolated BREWCODE_CONFIG_HOME so ~/.brewcode/settings.json is not loaded,
|
||||
// which would cause loaded_config_files to be non-zero (#65).
|
||||
let output = run_claw_with_env(
|
||||
let output = run_brewcode_with_env(
|
||||
&temp_dir,
|
||||
&[
|
||||
"--output-format",
|
||||
|
|
@ -251,7 +251,7 @@ fn resumed_status_command_emits_structured_json_when_requested() {
|
|||
session_path.to_str().expect("utf8 path"),
|
||||
"/status",
|
||||
],
|
||||
&[("CLAW_CONFIG_HOME", config_home.to_str().expect("utf8 path"))],
|
||||
&[("BREWCODE_CONFIG_HOME", config_home.to_str().expect("utf8 path"))],
|
||||
);
|
||||
|
||||
// then
|
||||
|
|
@ -296,7 +296,7 @@ fn resumed_status_surfaces_persisted_model() {
|
|||
session.save_to_path(&session_path).expect("persist ok");
|
||||
|
||||
// when
|
||||
let output = run_claw(
|
||||
let output = run_brewcode(
|
||||
&temp_dir,
|
||||
&[
|
||||
"--output-format",
|
||||
|
|
@ -334,7 +334,7 @@ fn resumed_sandbox_command_emits_structured_json_when_requested() {
|
|||
.expect("session should persist");
|
||||
|
||||
// when
|
||||
let output = run_claw(
|
||||
let output = run_brewcode(
|
||||
&temp_dir,
|
||||
&[
|
||||
"--output-format",
|
||||
|
|
@ -374,7 +374,7 @@ fn resumed_version_command_emits_structured_json() {
|
|||
.save_to_path(&session_path)
|
||||
.expect("session should persist");
|
||||
|
||||
let output = run_claw(
|
||||
let output = run_brewcode(
|
||||
&temp_dir,
|
||||
&[
|
||||
"--output-format",
|
||||
|
|
@ -409,7 +409,7 @@ fn resumed_export_command_emits_structured_json() {
|
|||
.expect("write ok");
|
||||
session.save_to_path(&session_path).expect("persist ok");
|
||||
|
||||
let output = run_claw(
|
||||
let output = run_brewcode(
|
||||
&temp_dir,
|
||||
&[
|
||||
"--output-format",
|
||||
|
|
@ -441,7 +441,7 @@ fn resumed_help_command_emits_structured_json() {
|
|||
.save_to_path(&session_path)
|
||||
.expect("persist ok");
|
||||
|
||||
let output = run_claw(
|
||||
let output = run_brewcode(
|
||||
&temp_dir,
|
||||
&[
|
||||
"--output-format",
|
||||
|
|
@ -476,7 +476,7 @@ fn resumed_no_command_emits_restored_json() {
|
|||
.expect("write ok");
|
||||
session.save_to_path(&session_path).expect("persist ok");
|
||||
|
||||
let output = run_claw(
|
||||
let output = run_brewcode(
|
||||
&temp_dir,
|
||||
&[
|
||||
"--output-format",
|
||||
|
|
@ -508,7 +508,7 @@ fn resumed_stub_command_emits_not_implemented_json() {
|
|||
.save_to_path(&session_path)
|
||||
.expect("persist ok");
|
||||
|
||||
let output = run_claw(
|
||||
let output = run_brewcode(
|
||||
&temp_dir,
|
||||
&[
|
||||
"--output-format",
|
||||
|
|
@ -534,21 +534,21 @@ fn resumed_stub_command_emits_not_implemented_json() {
|
|||
);
|
||||
}
|
||||
|
||||
fn run_claw(current_dir: &Path, args: &[&str]) -> Output {
|
||||
run_claw_with_env(current_dir, args, &[])
|
||||
fn run_brewcode(current_dir: &Path, args: &[&str]) -> Output {
|
||||
run_brewcode_with_env(current_dir, args, &[])
|
||||
}
|
||||
|
||||
fn workspace_session(root: &Path) -> Session {
|
||||
Session::new().with_workspace_root(root.to_path_buf())
|
||||
}
|
||||
|
||||
fn run_claw_with_env(current_dir: &Path, args: &[&str], envs: &[(&str, &str)]) -> Output {
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_claw"));
|
||||
fn run_brewcode_with_env(current_dir: &Path, args: &[&str], envs: &[(&str, &str)]) -> Output {
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_brewcode"));
|
||||
command.current_dir(current_dir).args(args);
|
||||
for (key, value) in envs {
|
||||
command.env(key, value);
|
||||
}
|
||||
command.output().expect("claw should launch")
|
||||
command.output().expect("brewcode should launch")
|
||||
}
|
||||
|
||||
fn unique_temp_dir(label: &str) -> PathBuf {
|
||||
|
|
@ -558,7 +558,7 @@ fn unique_temp_dir(label: &str) -> PathBuf {
|
|||
.as_millis();
|
||||
let counter = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
std::env::temp_dir().join(format!(
|
||||
"claw-{label}-{}-{millis}-{counter}",
|
||||
"brewcode-{label}-{}-{millis}-{counter}",
|
||||
std::process::id()
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2988,7 +2988,7 @@ fn build_http_client() -> Result<Client, String> {
|
|||
Client::builder()
|
||||
.timeout(Duration::from_secs(20))
|
||||
.redirect(reqwest::redirect::Policy::limited(10))
|
||||
.user_agent("clawd-rust-tools/0.1")
|
||||
.user_agent("brewcoded-rust-tools/0.1")
|
||||
.build()
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
|
@ -3009,7 +3009,7 @@ fn normalize_fetch_url(url: &str) -> Result<String, String> {
|
|||
}
|
||||
|
||||
fn build_search_url(query: &str) -> Result<reqwest::Url, String> {
|
||||
if let Ok(base) = std::env::var("CLAWD_WEB_SEARCH_BASE_URL") {
|
||||
if let Ok(base) = std::env::var("BREWCODED_WEB_SEARCH_BASE_URL") {
|
||||
let mut url = reqwest::Url::parse(&base).map_err(|error| error.to_string())?;
|
||||
url.query_pairs_mut().append_pair("q", query);
|
||||
return Ok(url);
|
||||
|
|
@ -3360,11 +3360,11 @@ fn validate_todos(todos: &[TodoItem]) -> Result<(), String> {
|
|||
}
|
||||
|
||||
fn todo_store_path() -> Result<std::path::PathBuf, String> {
|
||||
if let Ok(path) = std::env::var("CLAWD_TODO_STORE") {
|
||||
if let Ok(path) = std::env::var("BREWCODED_TODO_STORE") {
|
||||
return Ok(std::path::PathBuf::from(path));
|
||||
}
|
||||
let cwd = std::env::current_dir().map_err(|error| error.to_string())?;
|
||||
Ok(cwd.join(".clawd-todos.json"))
|
||||
Ok(cwd.join(".brewcoded-todos.json"))
|
||||
}
|
||||
|
||||
fn resolve_skill_path(skill: &str) -> Result<std::path::PathBuf, String> {
|
||||
|
|
@ -3409,8 +3409,8 @@ fn skill_lookup_roots() -> Vec<SkillLookupRoot> {
|
|||
push_project_skill_lookup_roots(&mut roots, &cwd);
|
||||
}
|
||||
|
||||
if let Ok(claw_config_home) = std::env::var("CLAW_CONFIG_HOME") {
|
||||
push_prefixed_skill_lookup_roots(&mut roots, std::path::Path::new(&claw_config_home));
|
||||
if let Ok(brewcode_config_home) = std::env::var("BREWCODE_CONFIG_HOME") {
|
||||
push_prefixed_skill_lookup_roots(&mut roots, std::path::Path::new(&brewcode_config_home));
|
||||
}
|
||||
if let Ok(codex_home) = std::env::var("CODEX_HOME") {
|
||||
push_prefixed_skill_lookup_roots(&mut roots, std::path::Path::new(&codex_home));
|
||||
|
|
@ -3438,7 +3438,7 @@ fn skill_lookup_roots() -> Vec<SkillLookupRoot> {
|
|||
}
|
||||
push_skill_lookup_root(
|
||||
&mut roots,
|
||||
std::path::PathBuf::from("/home/bellman/.claw/skills"),
|
||||
std::path::PathBuf::from("/home/bellman/.brewcode/skills"),
|
||||
SkillLookupOrigin::SkillsDir,
|
||||
);
|
||||
push_skill_lookup_root(
|
||||
|
|
@ -3454,7 +3454,7 @@ fn push_project_skill_lookup_roots(roots: &mut Vec<SkillLookupRoot>, cwd: &std::
|
|||
for ancestor in cwd.ancestors() {
|
||||
push_prefixed_skill_lookup_roots(roots, &ancestor.join(".omc"));
|
||||
push_prefixed_skill_lookup_roots(roots, &ancestor.join(".agents"));
|
||||
push_prefixed_skill_lookup_roots(roots, &ancestor.join(".claw"));
|
||||
push_prefixed_skill_lookup_roots(roots, &ancestor.join(".brewcode"));
|
||||
push_prefixed_skill_lookup_roots(roots, &ancestor.join(".codex"));
|
||||
push_prefixed_skill_lookup_roots(roots, &ancestor.join(".claude"));
|
||||
}
|
||||
|
|
@ -3462,7 +3462,7 @@ fn push_project_skill_lookup_roots(roots: &mut Vec<SkillLookupRoot>, cwd: &std::
|
|||
|
||||
fn push_home_skill_lookup_roots(roots: &mut Vec<SkillLookupRoot>, home: &std::path::Path) {
|
||||
push_prefixed_skill_lookup_roots(roots, &home.join(".omc"));
|
||||
push_prefixed_skill_lookup_roots(roots, &home.join(".claw"));
|
||||
push_prefixed_skill_lookup_roots(roots, &home.join(".brewcode"));
|
||||
push_prefixed_skill_lookup_roots(roots, &home.join(".codex"));
|
||||
push_prefixed_skill_lookup_roots(roots, &home.join(".claude"));
|
||||
push_skill_lookup_root(
|
||||
|
|
@ -3717,7 +3717,7 @@ where
|
|||
}
|
||||
|
||||
fn spawn_agent_job(job: AgentJob) -> Result<(), String> {
|
||||
let thread_name = format!("clawd-agent-{}", job.manifest.agent_id);
|
||||
let thread_name = format!("brewcoded-agent-{}", job.manifest.agent_id);
|
||||
std::thread::Builder::new()
|
||||
.name(thread_name)
|
||||
.spawn(move || {
|
||||
|
|
@ -3835,7 +3835,7 @@ fn allowed_tools_for_subagent(subagent_type: &str) -> BTreeSet<String> {
|
|||
"SendUserMessage",
|
||||
"PowerShell",
|
||||
],
|
||||
"claw-guide" => vec![
|
||||
"brewcode-guide" => vec![
|
||||
"read_file",
|
||||
"glob_search",
|
||||
"grep_search",
|
||||
|
|
@ -5205,14 +5205,14 @@ fn canonical_tool_token(value: &str) -> String {
|
|||
}
|
||||
|
||||
fn agent_store_dir() -> Result<std::path::PathBuf, String> {
|
||||
if let Ok(path) = std::env::var("CLAWD_AGENT_STORE") {
|
||||
if let Ok(path) = std::env::var("BREWCODED_AGENT_STORE") {
|
||||
return Ok(std::path::PathBuf::from(path));
|
||||
}
|
||||
let cwd = std::env::current_dir().map_err(|error| error.to_string())?;
|
||||
if let Some(workspace_root) = cwd.ancestors().nth(2) {
|
||||
return Ok(workspace_root.join(".clawd-agents"));
|
||||
return Ok(workspace_root.join(".brewcoded-agents"));
|
||||
}
|
||||
Ok(cwd.join(".clawd-agents"))
|
||||
Ok(cwd.join(".brewcoded-agents"))
|
||||
}
|
||||
|
||||
fn make_agent_id() -> String {
|
||||
|
|
@ -5253,7 +5253,7 @@ fn normalize_subagent_type(subagent_type: Option<&str>) -> String {
|
|||
"verification" | "verificationagent" | "verify" | "verifier" => {
|
||||
String::from("Verification")
|
||||
}
|
||||
"clawguide" | "clawguideagent" | "guide" => String::from("claw-guide"),
|
||||
"brewcodeguide" | "brewcodeguideagent" | "guide" => String::from("brewcode-guide"),
|
||||
"statusline" | "statuslinesetup" => String::from("statusline-setup"),
|
||||
_ => trimmed.to_string(),
|
||||
}
|
||||
|
|
@ -5946,12 +5946,12 @@ fn config_file_for_scope(scope: ConfigScope) -> Result<PathBuf, String> {
|
|||
let cwd = std::env::current_dir().map_err(|error| error.to_string())?;
|
||||
Ok(match scope {
|
||||
ConfigScope::Global => config_home_dir()?.join("settings.json"),
|
||||
ConfigScope::Settings => cwd.join(".claw").join("settings.local.json"),
|
||||
ConfigScope::Settings => cwd.join(".brewcode").join("settings.local.json"),
|
||||
})
|
||||
}
|
||||
|
||||
fn config_home_dir() -> Result<PathBuf, String> {
|
||||
if let Ok(path) = std::env::var("CLAW_CONFIG_HOME") {
|
||||
if let Ok(path) = std::env::var("BREWCODE_CONFIG_HOME") {
|
||||
return Ok(PathBuf::from(path));
|
||||
}
|
||||
let home = std::env::var("HOME")
|
||||
|
|
@ -5959,10 +5959,10 @@ fn config_home_dir() -> Result<PathBuf, String> {
|
|||
.map_err(|_| {
|
||||
String::from(
|
||||
"HOME is not set (on Windows, set USERPROFILE or HOME, \
|
||||
or use CLAW_CONFIG_HOME to point directly at the config directory)",
|
||||
or use BREWCODE_CONFIG_HOME to point directly at the config directory)",
|
||||
)
|
||||
})?;
|
||||
Ok(PathBuf::from(home).join(".claw"))
|
||||
Ok(PathBuf::from(home).join(".brewcode"))
|
||||
}
|
||||
|
||||
fn read_json_object(path: &Path) -> Result<serde_json::Map<String, Value>, String> {
|
||||
|
|
@ -6421,7 +6421,7 @@ mod tests {
|
|||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("time")
|
||||
.as_nanos();
|
||||
std::env::temp_dir().join(format!("clawd-tools-{unique}-{name}"))
|
||||
std::env::temp_dir().join(format!("brewcoded-tools-{unique}-{name}"))
|
||||
}
|
||||
|
||||
fn run_git(cwd: &Path, args: &[&str]) {
|
||||
|
|
@ -6583,14 +6583,14 @@ mod tests {
|
|||
#[test]
|
||||
fn worker_create_merges_config_trusted_roots_without_per_call_override() {
|
||||
use std::fs;
|
||||
// Write a .claw/settings.json in a temp dir with trustedRoots
|
||||
// Write a .brewcode/settings.json in a temp dir with trustedRoots
|
||||
let worktree = temp_path("config-trust-worktree");
|
||||
let claw_dir = worktree.join(".claw");
|
||||
fs::create_dir_all(&claw_dir).expect("create .claw dir");
|
||||
let brewcode_dir = worktree.join(".brewcode");
|
||||
fs::create_dir_all(&brewcode_dir).expect("create .brewcode dir");
|
||||
// Use the actual OS temp dir so the worktree path matches the allowlist
|
||||
let tmp_root = std::env::temp_dir().to_str().expect("utf-8").to_string();
|
||||
let settings = format!("{{\"trustedRoots\": [\"{tmp_root}\"]}}");
|
||||
fs::write(claw_dir.join("settings.json"), settings).expect("write settings");
|
||||
fs::write(brewcode_dir.join("settings.json"), settings).expect("write settings");
|
||||
|
||||
// WorkerCreate with no per-call trusted_roots — config should supply them
|
||||
let cwd = worktree.to_str().expect("valid utf-8").to_string();
|
||||
|
|
@ -6618,10 +6618,10 @@ mod tests {
|
|||
use std::fs;
|
||||
|
||||
let worktree = temp_path("config-and-call-trust-worktree");
|
||||
let claw_dir = worktree.join(".claw");
|
||||
fs::create_dir_all(&claw_dir).expect("create .claw dir");
|
||||
let brewcode_dir = worktree.join(".brewcode");
|
||||
fs::create_dir_all(&brewcode_dir).expect("create .brewcode dir");
|
||||
fs::write(
|
||||
claw_dir.join("settings.json"),
|
||||
brewcode_dir.join("settings.json"),
|
||||
r#"{"trustedRoots": ["/definitely/not/this/worktree"]}"#,
|
||||
)
|
||||
.expect("write settings");
|
||||
|
|
@ -6790,7 +6790,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn recovery_loop_state_file_reflects_transitions() {
|
||||
// End-to-end proof: .claw/worker-state.json reflects every transition
|
||||
// End-to-end proof: .brewcode/worker-state.json reflects every transition
|
||||
// through the stall-detect -> resolve-trust -> ready loop.
|
||||
use std::fs;
|
||||
|
||||
|
|
@ -6798,7 +6798,7 @@ mod tests {
|
|||
let worktree = temp_path("recovery-loop-state");
|
||||
fs::create_dir_all(&worktree).expect("create worktree");
|
||||
let cwd = worktree.to_str().expect("utf-8").to_string();
|
||||
let state_path = worktree.join(".claw").join("worker-state.json");
|
||||
let state_path = worktree.join(".brewcode").join("worker-state.json");
|
||||
|
||||
// 1. Create worker WITHOUT trusted_roots
|
||||
let created = execute_tool("WorkerCreate", &json!({"cwd": cwd}))
|
||||
|
|
@ -6900,7 +6900,7 @@ mod tests {
|
|||
"worker should stall at trust_required when trust prompt seen without allowlist"
|
||||
);
|
||||
assert_eq!(stalled_output["trust_gate_cleared"], false);
|
||||
// 3. Clawhip calls WorkerResolveTrust to unblock
|
||||
// 3. Brewcodehip calls WorkerResolveTrust to unblock
|
||||
let resolved = execute_tool("WorkerResolveTrust", &json!({"worker_id": worker_id}))
|
||||
.expect("WorkerResolveTrust should succeed");
|
||||
let resolved_output: serde_json::Value = serde_json::from_str(&resolved).expect("json");
|
||||
|
|
@ -6928,7 +6928,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn stall_detect_and_restart_recovery_end_to_end() {
|
||||
// Worker stalls at trust_required, clawhip restarts instead of resolving
|
||||
// Worker stalls at trust_required, brewcodehip restarts instead of resolving
|
||||
let created = execute_tool(
|
||||
"WorkerCreate",
|
||||
&json!({"cwd": "/no/trusted/root/restart-test"}),
|
||||
|
|
@ -7332,7 +7332,7 @@ mod tests {
|
|||
fn web_search_extracts_and_filters_results() {
|
||||
// Serialize env-var mutation so this test cannot race with the sibling
|
||||
// web_search_handles_generic_links_and_invalid_base_url test that also
|
||||
// sets CLAWD_WEB_SEARCH_BASE_URL. Without the lock, parallel test
|
||||
// sets BREWCODED_WEB_SEARCH_BASE_URL. Without the lock, parallel test
|
||||
// runners can interleave the set/remove calls and cause assertion
|
||||
// failures on the wrong port.
|
||||
let _guard = env_lock()
|
||||
|
|
@ -7353,7 +7353,7 @@ mod tests {
|
|||
}));
|
||||
|
||||
std::env::set_var(
|
||||
"CLAWD_WEB_SEARCH_BASE_URL",
|
||||
"BREWCODED_WEB_SEARCH_BASE_URL",
|
||||
format!("http://{}/search", server.addr()),
|
||||
);
|
||||
let result = execute_tool(
|
||||
|
|
@ -7365,7 +7365,7 @@ mod tests {
|
|||
}),
|
||||
)
|
||||
.expect("WebSearch should succeed");
|
||||
std::env::remove_var("CLAWD_WEB_SEARCH_BASE_URL");
|
||||
std::env::remove_var("BREWCODED_WEB_SEARCH_BASE_URL");
|
||||
|
||||
let output: serde_json::Value = serde_json::from_str(&result).expect("valid json");
|
||||
assert_eq!(output["query"], "rust web search");
|
||||
|
|
@ -7401,7 +7401,7 @@ mod tests {
|
|||
}));
|
||||
|
||||
std::env::set_var(
|
||||
"CLAWD_WEB_SEARCH_BASE_URL",
|
||||
"BREWCODED_WEB_SEARCH_BASE_URL",
|
||||
format!("http://{}/fallback", server.addr()),
|
||||
);
|
||||
let result = execute_tool(
|
||||
|
|
@ -7411,7 +7411,7 @@ mod tests {
|
|||
}),
|
||||
)
|
||||
.expect("WebSearch fallback parsing should succeed");
|
||||
std::env::remove_var("CLAWD_WEB_SEARCH_BASE_URL");
|
||||
std::env::remove_var("BREWCODED_WEB_SEARCH_BASE_URL");
|
||||
|
||||
let output: serde_json::Value = serde_json::from_str(&result).expect("valid json");
|
||||
let results = output["results"].as_array().expect("results array");
|
||||
|
|
@ -7424,10 +7424,10 @@ mod tests {
|
|||
assert_eq!(content[0]["url"], "https://example.com/one");
|
||||
assert_eq!(content[1]["url"], "https://docs.rs/tokio");
|
||||
|
||||
std::env::set_var("CLAWD_WEB_SEARCH_BASE_URL", "://bad-base-url");
|
||||
std::env::set_var("BREWCODED_WEB_SEARCH_BASE_URL", "://bad-base-url");
|
||||
let error = execute_tool("WebSearch", &json!({ "query": "generic links" }))
|
||||
.expect_err("invalid base URL should fail");
|
||||
std::env::remove_var("CLAWD_WEB_SEARCH_BASE_URL");
|
||||
std::env::remove_var("BREWCODED_WEB_SEARCH_BASE_URL");
|
||||
assert!(error.contains("relative URL without a base") || error.contains("empty host"));
|
||||
}
|
||||
|
||||
|
|
@ -7452,7 +7452,7 @@ mod tests {
|
|||
|
||||
// when
|
||||
std::env::set_var(
|
||||
"CLAWD_WEB_SEARCH_BASE_URL",
|
||||
"BREWCODED_WEB_SEARCH_BASE_URL",
|
||||
format!("http://{}/search", server.addr()),
|
||||
);
|
||||
let result = execute_tool(
|
||||
|
|
@ -7462,7 +7462,7 @@ mod tests {
|
|||
}),
|
||||
)
|
||||
.expect("WebSearch should succeed");
|
||||
std::env::remove_var("CLAWD_WEB_SEARCH_BASE_URL");
|
||||
std::env::remove_var("BREWCODED_WEB_SEARCH_BASE_URL");
|
||||
|
||||
// then
|
||||
let output: serde_json::Value = serde_json::from_str(&result).expect("valid json");
|
||||
|
|
@ -7498,7 +7498,7 @@ mod tests {
|
|||
|
||||
// when
|
||||
std::env::set_var(
|
||||
"CLAWD_WEB_SEARCH_BASE_URL",
|
||||
"BREWCODED_WEB_SEARCH_BASE_URL",
|
||||
format!("http://{}/search", server.addr()),
|
||||
);
|
||||
let result = execute_tool(
|
||||
|
|
@ -7508,7 +7508,7 @@ mod tests {
|
|||
}),
|
||||
)
|
||||
.expect("WebSearch should succeed");
|
||||
std::env::remove_var("CLAWD_WEB_SEARCH_BASE_URL");
|
||||
std::env::remove_var("BREWCODED_WEB_SEARCH_BASE_URL");
|
||||
|
||||
// then
|
||||
let output: serde_json::Value = serde_json::from_str(&result).expect("valid json");
|
||||
|
|
@ -7589,7 +7589,7 @@ mod tests {
|
|||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let path = temp_path("todos.json");
|
||||
std::env::set_var("CLAWD_TODO_STORE", &path);
|
||||
std::env::set_var("BREWCODED_TODO_STORE", &path);
|
||||
|
||||
let first = execute_tool(
|
||||
"TodoWrite",
|
||||
|
|
@ -7615,7 +7615,7 @@ mod tests {
|
|||
}),
|
||||
)
|
||||
.expect("TodoWrite should succeed");
|
||||
std::env::remove_var("CLAWD_TODO_STORE");
|
||||
std::env::remove_var("BREWCODED_TODO_STORE");
|
||||
let _ = std::fs::remove_file(path);
|
||||
|
||||
let second_output: serde_json::Value = serde_json::from_str(&second).expect("valid json");
|
||||
|
|
@ -7636,7 +7636,7 @@ mod tests {
|
|||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let path = temp_path("todos-errors.json");
|
||||
std::env::set_var("CLAWD_TODO_STORE", &path);
|
||||
std::env::set_var("BREWCODED_TODO_STORE", &path);
|
||||
|
||||
let empty = execute_tool("TodoWrite", &json!({ "todos": [] }))
|
||||
.expect_err("empty todos should fail");
|
||||
|
|
@ -7676,7 +7676,7 @@ mod tests {
|
|||
}),
|
||||
)
|
||||
.expect("completed todos should succeed");
|
||||
std::env::remove_var("CLAWD_TODO_STORE");
|
||||
std::env::remove_var("BREWCODED_TODO_STORE");
|
||||
let _ = fs::remove_file(path);
|
||||
|
||||
let output: serde_json::Value = serde_json::from_str(&nudge).expect("valid json");
|
||||
|
|
@ -7744,8 +7744,8 @@ mod tests {
|
|||
fn skill_resolves_project_local_skills_and_legacy_commands() {
|
||||
let _guard = env_guard();
|
||||
let root = temp_path("project-skills");
|
||||
let skill_dir = root.join(".claw").join("skills").join("plan");
|
||||
let command_dir = root.join(".claw").join("commands");
|
||||
let skill_dir = root.join(".brewcode").join("skills").join("plan");
|
||||
let command_dir = root.join(".brewcode").join("commands");
|
||||
fs::create_dir_all(&skill_dir).expect("skill dir should exist");
|
||||
fs::create_dir_all(&command_dir).expect("command dir should exist");
|
||||
fs::write(
|
||||
|
|
@ -7769,7 +7769,7 @@ mod tests {
|
|||
assert!(skill_output["path"]
|
||||
.as_str()
|
||||
.expect("path")
|
||||
.ends_with(".claw/skills/plan/SKILL.md"));
|
||||
.ends_with(".brewcode/skills/plan/SKILL.md"));
|
||||
|
||||
let command_result = execute_tool("Skill", &json!({ "skill": "/handoff" }))
|
||||
.expect("legacy command should resolve");
|
||||
|
|
@ -7778,7 +7778,7 @@ mod tests {
|
|||
assert!(command_output["path"]
|
||||
.as_str()
|
||||
.expect("path")
|
||||
.ends_with(".claw/commands/handoff.md"));
|
||||
.ends_with(".brewcode/commands/handoff.md"));
|
||||
|
||||
std::env::set_current_dir(&original_dir).expect("restore cwd");
|
||||
fs::remove_dir_all(root).expect("temp project should clean up");
|
||||
|
|
@ -7801,11 +7801,11 @@ mod tests {
|
|||
.expect("skill file should exist");
|
||||
|
||||
let original_home = std::env::var("HOME").ok();
|
||||
let original_config_home = std::env::var("CLAW_CONFIG_HOME").ok();
|
||||
let original_config_home = std::env::var("BREWCODE_CONFIG_HOME").ok();
|
||||
let original_codex_home = std::env::var("CODEX_HOME").ok();
|
||||
let original_dir = std::env::current_dir().expect("cwd");
|
||||
std::env::set_var("HOME", &home);
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
std::env::remove_var("CODEX_HOME");
|
||||
std::env::set_current_dir(&nested).expect("set cwd");
|
||||
|
||||
|
|
@ -7825,8 +7825,8 @@ mod tests {
|
|||
None => std::env::remove_var("HOME"),
|
||||
}
|
||||
match original_config_home {
|
||||
Some(value) => std::env::set_var("CLAW_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("CLAW_CONFIG_HOME"),
|
||||
Some(value) => std::env::set_var("BREWCODE_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("BREWCODE_CONFIG_HOME"),
|
||||
}
|
||||
match original_codex_home {
|
||||
Some(value) => std::env::set_var("CODEX_HOME", value),
|
||||
|
|
@ -7859,11 +7859,11 @@ mod tests {
|
|||
.expect("agents skill file should exist");
|
||||
|
||||
let original_home = std::env::var("HOME").ok();
|
||||
let original_config_home = std::env::var("CLAW_CONFIG_HOME").ok();
|
||||
let original_config_home = std::env::var("BREWCODE_CONFIG_HOME").ok();
|
||||
let original_codex_home = std::env::var("CODEX_HOME").ok();
|
||||
let original_dir = std::env::current_dir().expect("cwd");
|
||||
std::env::set_var("HOME", &home);
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
std::env::remove_var("CODEX_HOME");
|
||||
std::env::set_current_dir(&nested).expect("set cwd");
|
||||
|
||||
|
|
@ -7895,8 +7895,8 @@ mod tests {
|
|||
None => std::env::remove_var("HOME"),
|
||||
}
|
||||
match original_config_home {
|
||||
Some(value) => std::env::set_var("CLAW_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("CLAW_CONFIG_HOME"),
|
||||
Some(value) => std::env::set_var("BREWCODE_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("BREWCODE_CONFIG_HOME"),
|
||||
}
|
||||
match original_codex_home {
|
||||
Some(value) => std::env::set_var("CODEX_HOME", value),
|
||||
|
|
@ -7923,11 +7923,11 @@ mod tests {
|
|||
.expect("learned skill file should exist");
|
||||
|
||||
let original_home = std::env::var("HOME").ok();
|
||||
let original_config_home = std::env::var("CLAW_CONFIG_HOME").ok();
|
||||
let original_config_home = std::env::var("BREWCODE_CONFIG_HOME").ok();
|
||||
let original_codex_home = std::env::var("CODEX_HOME").ok();
|
||||
let original_claude_config_dir = std::env::var("CLAUDE_CONFIG_DIR").ok();
|
||||
std::env::set_var("HOME", &home);
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
std::env::remove_var("CODEX_HOME");
|
||||
std::env::set_var("CLAUDE_CONFIG_DIR", &claude_config_dir);
|
||||
|
||||
|
|
@ -7946,8 +7946,8 @@ mod tests {
|
|||
None => std::env::remove_var("HOME"),
|
||||
}
|
||||
match original_config_home {
|
||||
Some(value) => std::env::set_var("CLAW_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("CLAW_CONFIG_HOME"),
|
||||
Some(value) => std::env::set_var("BREWCODE_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("BREWCODE_CONFIG_HOME"),
|
||||
}
|
||||
match original_codex_home {
|
||||
Some(value) => std::env::set_var("CODEX_HOME", value),
|
||||
|
|
@ -7982,11 +7982,11 @@ mod tests {
|
|||
.expect("direct command file should exist");
|
||||
|
||||
let original_home = std::env::var("HOME").ok();
|
||||
let original_config_home = std::env::var("CLAW_CONFIG_HOME").ok();
|
||||
let original_config_home = std::env::var("BREWCODE_CONFIG_HOME").ok();
|
||||
let original_codex_home = std::env::var("CODEX_HOME").ok();
|
||||
let original_claude_config_dir = std::env::var("CLAUDE_CONFIG_DIR").ok();
|
||||
std::env::set_var("HOME", &home);
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
std::env::remove_var("CODEX_HOME");
|
||||
std::env::set_var("CLAUDE_CONFIG_DIR", &claude_config_dir);
|
||||
|
||||
|
|
@ -8018,8 +8018,8 @@ mod tests {
|
|||
None => std::env::remove_var("HOME"),
|
||||
}
|
||||
match original_config_home {
|
||||
Some(value) => std::env::set_var("CLAW_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("CLAW_CONFIG_HOME"),
|
||||
Some(value) => std::env::set_var("BREWCODE_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("BREWCODE_CONFIG_HOME"),
|
||||
}
|
||||
match original_codex_home {
|
||||
Some(value) => std::env::set_var("CODEX_HOME", value),
|
||||
|
|
@ -8049,11 +8049,11 @@ mod tests {
|
|||
.expect("legacy command file should exist");
|
||||
|
||||
let original_home = std::env::var("HOME").ok();
|
||||
let original_config_home = std::env::var("CLAW_CONFIG_HOME").ok();
|
||||
let original_config_home = std::env::var("BREWCODE_CONFIG_HOME").ok();
|
||||
let original_codex_home = std::env::var("CODEX_HOME").ok();
|
||||
let original_dir = std::env::current_dir().expect("cwd");
|
||||
std::env::set_var("HOME", &home);
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
std::env::remove_var("CODEX_HOME");
|
||||
std::env::set_current_dir(&nested).expect("set cwd");
|
||||
|
||||
|
|
@ -8073,8 +8073,8 @@ mod tests {
|
|||
None => std::env::remove_var("HOME"),
|
||||
}
|
||||
match original_config_home {
|
||||
Some(value) => std::env::set_var("CLAW_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("CLAW_CONFIG_HOME"),
|
||||
Some(value) => std::env::set_var("BREWCODE_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("BREWCODE_CONFIG_HOME"),
|
||||
}
|
||||
match original_codex_home {
|
||||
Some(value) => std::env::set_var("CODEX_HOME", value),
|
||||
|
|
@ -8122,7 +8122,7 @@ mod tests {
|
|||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let dir = temp_path("agent-store");
|
||||
std::env::set_var("CLAWD_AGENT_STORE", &dir);
|
||||
std::env::set_var("BREWCODED_AGENT_STORE", &dir);
|
||||
let captured = Arc::new(Mutex::new(None::<AgentJob>));
|
||||
let captured_for_spawn = Arc::clone(&captured);
|
||||
|
||||
|
|
@ -8142,7 +8142,7 @@ mod tests {
|
|||
},
|
||||
)
|
||||
.expect("Agent should succeed");
|
||||
std::env::remove_var("CLAWD_AGENT_STORE");
|
||||
std::env::remove_var("BREWCODED_AGENT_STORE");
|
||||
|
||||
assert_eq!(manifest.name, "ship-audit");
|
||||
assert_eq!(manifest.subagent_type.as_deref(), Some("Explore"));
|
||||
|
|
@ -8205,7 +8205,7 @@ mod tests {
|
|||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let dir = temp_path("agent-runner");
|
||||
std::env::set_var("CLAWD_AGENT_STORE", &dir);
|
||||
std::env::set_var("BREWCODED_AGENT_STORE", &dir);
|
||||
|
||||
let completed = execute_agent_with_spawn(
|
||||
AgentInput {
|
||||
|
|
@ -8635,7 +8635,7 @@ mod tests {
|
|||
);
|
||||
assert_eq!(spawn_error_manifest_json["derivedState"], "truly_idle");
|
||||
|
||||
std::env::remove_var("CLAWD_AGENT_STORE");
|
||||
std::env::remove_var("BREWCODED_AGENT_STORE");
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
|
|
@ -9461,7 +9461,7 @@ mod tests {
|
|||
#[test]
|
||||
fn brief_returns_sent_message_and_attachment_metadata() {
|
||||
let attachment = std::env::temp_dir().join(format!(
|
||||
"clawd-brief-{}.png",
|
||||
"brewcoded-brief-{}.png",
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("time")
|
||||
|
|
@ -9492,7 +9492,7 @@ mod tests {
|
|||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"clawd-config-{}",
|
||||
"brewcoded-config-{}",
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("time")
|
||||
|
|
@ -9500,19 +9500,19 @@ mod tests {
|
|||
));
|
||||
let home = root.join("home");
|
||||
let cwd = root.join("cwd");
|
||||
std::fs::create_dir_all(home.join(".claw")).expect("home dir");
|
||||
std::fs::create_dir_all(cwd.join(".claw")).expect("cwd dir");
|
||||
std::fs::create_dir_all(home.join(".brewcode")).expect("home dir");
|
||||
std::fs::create_dir_all(cwd.join(".brewcode")).expect("cwd dir");
|
||||
std::fs::write(
|
||||
home.join(".claw").join("settings.json"),
|
||||
home.join(".brewcode").join("settings.json"),
|
||||
r#"{"verbose":false}"#,
|
||||
)
|
||||
.expect("write global settings");
|
||||
|
||||
let original_home = std::env::var("HOME").ok();
|
||||
let original_config_home = std::env::var("CLAW_CONFIG_HOME").ok();
|
||||
let original_config_home = std::env::var("BREWCODE_CONFIG_HOME").ok();
|
||||
let original_dir = std::env::current_dir().expect("cwd");
|
||||
std::env::set_var("HOME", &home);
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
std::env::set_current_dir(&cwd).expect("set cwd");
|
||||
|
||||
let get = execute_tool("Config", &json!({"setting": "verbose"})).expect("get config");
|
||||
|
|
@ -9546,8 +9546,8 @@ mod tests {
|
|||
None => std::env::remove_var("HOME"),
|
||||
}
|
||||
match original_config_home {
|
||||
Some(value) => std::env::set_var("CLAW_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("CLAW_CONFIG_HOME"),
|
||||
Some(value) => std::env::set_var("BREWCODE_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("BREWCODE_CONFIG_HOME"),
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(root);
|
||||
}
|
||||
|
|
@ -9558,7 +9558,7 @@ mod tests {
|
|||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"clawd-plan-mode-{}",
|
||||
"brewcoded-plan-mode-{}",
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("time")
|
||||
|
|
@ -9566,19 +9566,19 @@ mod tests {
|
|||
));
|
||||
let home = root.join("home");
|
||||
let cwd = root.join("cwd");
|
||||
std::fs::create_dir_all(home.join(".claw")).expect("home dir");
|
||||
std::fs::create_dir_all(cwd.join(".claw")).expect("cwd dir");
|
||||
std::fs::create_dir_all(home.join(".brewcode")).expect("home dir");
|
||||
std::fs::create_dir_all(cwd.join(".brewcode")).expect("cwd dir");
|
||||
std::fs::write(
|
||||
cwd.join(".claw").join("settings.local.json"),
|
||||
cwd.join(".brewcode").join("settings.local.json"),
|
||||
r#"{"permissions":{"defaultMode":"acceptEdits"}}"#,
|
||||
)
|
||||
.expect("write local settings");
|
||||
|
||||
let original_home = std::env::var("HOME").ok();
|
||||
let original_config_home = std::env::var("CLAW_CONFIG_HOME").ok();
|
||||
let original_config_home = std::env::var("BREWCODE_CONFIG_HOME").ok();
|
||||
let original_dir = std::env::current_dir().expect("cwd");
|
||||
std::env::set_var("HOME", &home);
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
std::env::set_current_dir(&cwd).expect("set cwd");
|
||||
|
||||
let enter = execute_tool("EnterPlanMode", &json!({})).expect("enter plan mode");
|
||||
|
|
@ -9588,11 +9588,11 @@ mod tests {
|
|||
assert_eq!(enter_output["previousLocalMode"], "acceptEdits");
|
||||
assert_eq!(enter_output["currentLocalMode"], "plan");
|
||||
|
||||
let local_settings = std::fs::read_to_string(cwd.join(".claw").join("settings.local.json"))
|
||||
let local_settings = std::fs::read_to_string(cwd.join(".brewcode").join("settings.local.json"))
|
||||
.expect("local settings after enter");
|
||||
assert!(local_settings.contains(r#""defaultMode": "plan""#));
|
||||
let state =
|
||||
std::fs::read_to_string(cwd.join(".claw").join("tool-state").join("plan-mode.json"))
|
||||
std::fs::read_to_string(cwd.join(".brewcode").join("tool-state").join("plan-mode.json"))
|
||||
.expect("plan mode state");
|
||||
assert!(state.contains(r#""hadLocalOverride": true"#));
|
||||
assert!(state.contains(r#""previousLocalMode": "acceptEdits""#));
|
||||
|
|
@ -9604,11 +9604,11 @@ mod tests {
|
|||
assert_eq!(exit_output["previousLocalMode"], "acceptEdits");
|
||||
assert_eq!(exit_output["currentLocalMode"], "acceptEdits");
|
||||
|
||||
let local_settings = std::fs::read_to_string(cwd.join(".claw").join("settings.local.json"))
|
||||
let local_settings = std::fs::read_to_string(cwd.join(".brewcode").join("settings.local.json"))
|
||||
.expect("local settings after exit");
|
||||
assert!(local_settings.contains(r#""defaultMode": "acceptEdits""#));
|
||||
assert!(!cwd
|
||||
.join(".claw")
|
||||
.join(".brewcode")
|
||||
.join("tool-state")
|
||||
.join("plan-mode.json")
|
||||
.exists());
|
||||
|
|
@ -9619,8 +9619,8 @@ mod tests {
|
|||
None => std::env::remove_var("HOME"),
|
||||
}
|
||||
match original_config_home {
|
||||
Some(value) => std::env::set_var("CLAW_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("CLAW_CONFIG_HOME"),
|
||||
Some(value) => std::env::set_var("BREWCODE_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("BREWCODE_CONFIG_HOME"),
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(root);
|
||||
}
|
||||
|
|
@ -9631,7 +9631,7 @@ mod tests {
|
|||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"clawd-plan-mode-empty-{}",
|
||||
"brewcoded-plan-mode-empty-{}",
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("time")
|
||||
|
|
@ -9639,14 +9639,14 @@ mod tests {
|
|||
));
|
||||
let home = root.join("home");
|
||||
let cwd = root.join("cwd");
|
||||
std::fs::create_dir_all(home.join(".claw")).expect("home dir");
|
||||
std::fs::create_dir_all(cwd.join(".claw")).expect("cwd dir");
|
||||
std::fs::create_dir_all(home.join(".brewcode")).expect("home dir");
|
||||
std::fs::create_dir_all(cwd.join(".brewcode")).expect("cwd dir");
|
||||
|
||||
let original_home = std::env::var("HOME").ok();
|
||||
let original_config_home = std::env::var("CLAW_CONFIG_HOME").ok();
|
||||
let original_config_home = std::env::var("BREWCODE_CONFIG_HOME").ok();
|
||||
let original_dir = std::env::current_dir().expect("cwd");
|
||||
std::env::set_var("HOME", &home);
|
||||
std::env::remove_var("CLAW_CONFIG_HOME");
|
||||
std::env::remove_var("BREWCODE_CONFIG_HOME");
|
||||
std::env::set_current_dir(&cwd).expect("set cwd");
|
||||
|
||||
let enter = execute_tool("EnterPlanMode", &json!({})).expect("enter plan mode");
|
||||
|
|
@ -9659,7 +9659,7 @@ mod tests {
|
|||
assert_eq!(exit_output["changed"], true);
|
||||
assert_eq!(exit_output["currentLocalMode"], serde_json::Value::Null);
|
||||
|
||||
let local_settings = std::fs::read_to_string(cwd.join(".claw").join("settings.local.json"))
|
||||
let local_settings = std::fs::read_to_string(cwd.join(".brewcode").join("settings.local.json"))
|
||||
.expect("local settings after exit");
|
||||
let local_settings_json: serde_json::Value =
|
||||
serde_json::from_str(&local_settings).expect("valid settings json");
|
||||
|
|
@ -9669,7 +9669,7 @@ mod tests {
|
|||
"permissions override should be removed on exit"
|
||||
);
|
||||
assert!(!cwd
|
||||
.join(".claw")
|
||||
.join(".brewcode")
|
||||
.join("tool-state")
|
||||
.join("plan-mode.json")
|
||||
.exists());
|
||||
|
|
@ -9680,8 +9680,8 @@ mod tests {
|
|||
None => std::env::remove_var("HOME"),
|
||||
}
|
||||
match original_config_home {
|
||||
Some(value) => std::env::set_var("CLAW_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("CLAW_CONFIG_HOME"),
|
||||
Some(value) => std::env::set_var("BREWCODE_CONFIG_HOME", value),
|
||||
None => std::env::remove_var("BREWCODE_CONFIG_HOME"),
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(root);
|
||||
}
|
||||
|
|
@ -9753,7 +9753,7 @@ mod tests {
|
|||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let dir = std::env::temp_dir().join(format!(
|
||||
"clawd-pwsh-bin-{}",
|
||||
"brewcoded-pwsh-bin-{}",
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("time")
|
||||
|
|
@ -9810,7 +9810,7 @@ printf 'pwsh:%s' "$1"
|
|||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let original_path = std::env::var("PATH").unwrap_or_default();
|
||||
let empty_dir = std::env::temp_dir().join(format!(
|
||||
"clawd-empty-bin-{}",
|
||||
"brewcoded-empty-bin-{}",
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("time")
|
||||
|
|
@ -10150,7 +10150,7 @@ printf 'pwsh:%s' "$1"
|
|||
scope: TaskScope::Module,
|
||||
scope_path: Some("runtime/task system".to_string()),
|
||||
worktree: Some("/tmp/wt-packet".to_string()),
|
||||
repo: "claw-code-parity".to_string(),
|
||||
repo: "brew-code-parity".to_string(),
|
||||
branch_policy: "origin/main only".to_string(),
|
||||
acceptance_tests: vec![
|
||||
"cargo build --workspace".to_string(),
|
||||
|
|
@ -10177,7 +10177,7 @@ printf 'pwsh:%s' "$1"
|
|||
assert_eq!(output["status"], "created");
|
||||
assert_eq!(output["prompt"], "Ship packetized runtime task");
|
||||
assert_eq!(output["description"], "runtime/task system");
|
||||
assert_eq!(output["task_packet"]["repo"], "claw-code-parity");
|
||||
assert_eq!(output["task_packet"]["repo"], "brew-code-parity");
|
||||
assert_eq!(output["task_packet"]["resources"][0]["kind"], "module");
|
||||
assert_eq!(
|
||||
output["task_packet"]["resources"][0]["value"],
|
||||
|
|
|
|||
|
|
@ -486,7 +486,7 @@ mod tests {
|
|||
fn extracts_text_from_file_on_disk() {
|
||||
// given
|
||||
let pdf_bytes = build_simple_pdf("Disk Test");
|
||||
let dir = std::env::temp_dir().join("clawd-pdf-extract-test");
|
||||
let dir = std::env::temp_dir().join("brewcoded-pdf-extract-test");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let pdf_path = dir.join("test.pdf");
|
||||
std::fs::write(&pdf_path, &pdf_bytes).unwrap();
|
||||
|
|
@ -528,7 +528,7 @@ mod tests {
|
|||
fn maybe_extract_pdf_from_prompt_extracts_existing_file() {
|
||||
// given
|
||||
let pdf_bytes = build_simple_pdf("Auto Extracted");
|
||||
let dir = std::env::temp_dir().join("clawd-pdf-auto-extract-test");
|
||||
let dir = std::env::temp_dir().join("brewcoded-pdf-auto-extract-test");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let pdf_path = dir.join("auto.pdf");
|
||||
std::fs::write(&pdf_path, &pdf_bytes).unwrap();
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ fn temp_path(name: &str) -> PathBuf {
|
|||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("time")
|
||||
.as_nanos();
|
||||
std::env::temp_dir().join(format!("claw-path-scope-{unique}-{name}"))
|
||||
std::env::temp_dir().join(format!("brewcode-path-scope-{unique}-{name}"))
|
||||
}
|
||||
|
||||
fn workspace_write_registry() -> GlobalToolRegistry {
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
#!/usr/bin/env bash
|
||||
# dogfood-build.sh — Build claw from current checkout and verify provenance.
|
||||
# dogfood-build.sh — Build brewcode from current checkout and verify provenance.
|
||||
#
|
||||
# Injects GIT_SHA at build time so version JSON is non-null.
|
||||
# Suppresses Cargo compile noise on stderr.
|
||||
# Prints the verified binary path on success. Use as:
|
||||
#
|
||||
# CLAW=$(bash scripts/dogfood-build.sh)
|
||||
# BREWCODE=$(bash scripts/dogfood-build.sh)
|
||||
#
|
||||
# Then dogfood with config isolation (avoids real user config bleeding in):
|
||||
#
|
||||
# CLAW_CONFIG_HOME=$(mktemp -d) $CLAW plugins list --output-format json
|
||||
# BREWCODE_CONFIG_HOME=$(mktemp -d) $BREWCODE plugins list --output-format json
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
RUST_DIR="$REPO_ROOT/rust"
|
||||
BINARY="$RUST_DIR/target/debug/claw"
|
||||
BINARY="$RUST_DIR/target/debug/brewcode"
|
||||
EXPECTED_SHA="$(git -C "$REPO_ROOT" rev-parse --short HEAD)"
|
||||
|
||||
echo "▶ Building claw from $REPO_ROOT" >&2
|
||||
echo "▶ Building brewcode from $REPO_ROOT" >&2
|
||||
echo " Commit: $(git -C "$REPO_ROOT" log --oneline -1)" >&2
|
||||
|
||||
# Inject GIT_SHA so version JSON returns a non-null sha.
|
||||
|
|
@ -56,13 +56,13 @@ fi
|
|||
|
||||
echo "✓ Binary verified: $BINARY_SHA == HEAD" >&2
|
||||
echo "" >&2
|
||||
echo " export CLAW=$BINARY" >&2
|
||||
echo " export BREWCODE=$BINARY" >&2
|
||||
echo "" >&2
|
||||
echo " Dogfood with isolated config (no real user config on stderr):" >&2
|
||||
echo " CLAW_ISOLATED=\$(mktemp -d)" >&2
|
||||
echo " CLAW_CONFIG_HOME=\$CLAW_ISOLATED \$CLAW plugins list --output-format json" >&2
|
||||
echo " rm -rf \$CLAW_ISOLATED" >&2
|
||||
echo " BREWCODE_ISOLATED=\$(mktemp -d)" >&2
|
||||
echo " BREWCODE_CONFIG_HOME=\$BREWCODE_ISOLATED \$BREWCODE plugins list --output-format json" >&2
|
||||
echo " rm -rf \$BREWCODE_ISOLATED" >&2
|
||||
echo "" >&2
|
||||
echo " cargo run overhead: ~1s/invocation vs 7ms for pre-built binary." >&2
|
||||
echo " Prefer pre-built binary (\$CLAW) for dogfood loops." >&2
|
||||
echo " Prefer pre-built binary (\$BREWCODE) for dogfood loops." >&2
|
||||
echo "$BINARY"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate the canonical Claw Code 2.0 execution board from frozen roadmap evidence."""
|
||||
"""Generate the canonical Brew Code 2.0 execution board from frozen roadmap evidence."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
|
|
@ -32,22 +32,22 @@ STATUSES = {
|
|||
"stale_done",
|
||||
"superseded",
|
||||
"deferred_with_rationale",
|
||||
"rejected_not_claw",
|
||||
"rejected_not_brewcode",
|
||||
}
|
||||
RELEASE_BUCKETS = {
|
||||
"alpha_blocker",
|
||||
"beta_adoption",
|
||||
"ga_ecosystem",
|
||||
"post_2_0_research",
|
||||
"rejected_not_claw",
|
||||
"rejected_not_brewcode",
|
||||
"context",
|
||||
"2.x_intake",
|
||||
}
|
||||
|
||||
STRUCTURAL_HEADINGS = {
|
||||
"Clawable Coding Harness Roadmap",
|
||||
"Brewcodeable Coding Harness Roadmap",
|
||||
"Goal",
|
||||
'Definition of "clawable"',
|
||||
'Definition of "brewcodeable"',
|
||||
"Current Pain Points",
|
||||
"Product Principles",
|
||||
"Roadmap",
|
||||
|
|
@ -66,10 +66,10 @@ CATEGORY_KEYWORDS = [
|
|||
("docs_license", ["docs", "readme", "usage", "license", "help", "onboarding"]),
|
||||
("ide_acp", ["zed", "acp", "editor", "daemon"]),
|
||||
("plugin_mcp", ["plugin", "mcp", "marketplace", "server"]),
|
||||
("event_report", ["event", "report", "schema", "projection", "redaction", "clawhip", "lane"]),
|
||||
("event_report", ["event", "report", "schema", "projection", "redaction", "brewcodehip", "lane"]),
|
||||
("branch_recovery", ["branch", "stale", "recovery", "green", "flake"]),
|
||||
("boot", ["boot", "worker", "startup", "ready", "prompt"]),
|
||||
("task_policy", ["task", "policy", "claw-native", "dashboard", "lane board"]),
|
||||
("task_policy", ["task", "policy", "brewcode-native", "dashboard", "lane board"]),
|
||||
("ux_tui", ["tui", "statusline", "keymap", "clickable", "copy", "paste"]),
|
||||
("anti_slop", ["spam", "slop", "issue hygiene", "bot"]),
|
||||
]
|
||||
|
|
@ -106,9 +106,9 @@ def find_source_omx(repo_root: Path) -> Path:
|
|||
candidates.append(repo_root / ".omx")
|
||||
candidates.extend(parent / ".omx" for parent in repo_root.parents)
|
||||
for candidate in candidates:
|
||||
if (candidate / "plans" / "claw-code-2-0-adaptive-plan.md").exists() and (candidate / "research").exists():
|
||||
if (candidate / "plans" / "brew-code-2-0-adaptive-plan.md").exists() and (candidate / "research").exists():
|
||||
return candidate
|
||||
raise FileNotFoundError("could not locate source .omx with plans/claw-code-2-0-adaptive-plan.md and research/")
|
||||
raise FileNotFoundError("could not locate source .omx with plans/brew-code-2-0-adaptive-plan.md and research/")
|
||||
|
||||
|
||||
def parse_roadmap(path: Path) -> tuple[list[RoadmapRecord], list[RoadmapRecord]]:
|
||||
|
|
@ -159,7 +159,7 @@ def stream_for(record: RoadmapRecord) -> str:
|
|||
if "phase 3" in combined or category_for(combined) == "branch_recovery":
|
||||
return "stream_3_branch_test_recovery"
|
||||
if "phase 4" in combined or category_for(combined) == "task_policy":
|
||||
return "stream_4_claws_first_execution"
|
||||
return "stream_4_brewcodes_first_execution"
|
||||
if "phase 5" in combined or category_for(combined) == "plugin_mcp":
|
||||
return "stream_5_plugin_mcp_lifecycle"
|
||||
if any(k in combined for k in ["windows", "install", "provider", "docs", "license", "session hygiene", "compact"]):
|
||||
|
|
@ -174,8 +174,8 @@ def release_bucket_for(record: RoadmapRecord, status: str) -> str:
|
|||
category = category_for(combined)
|
||||
if status == "context":
|
||||
return "context"
|
||||
if status == "rejected_not_claw":
|
||||
return "rejected_not_claw"
|
||||
if status == "rejected_not_brewcode":
|
||||
return "rejected_not_brewcode"
|
||||
if any(k in combined for k in ["phase 1", "phase 2", "phase 3", "phase 4", "p0", "p1", "security", "sandbox", "trust", "worker", "event", "branch freshness"]):
|
||||
return "alpha_blocker"
|
||||
if category in {"windows_install", "provider", "sessions", "docs_license", "anti_slop"}:
|
||||
|
|
@ -198,8 +198,8 @@ def status_for(record: RoadmapRecord) -> str:
|
|||
return "active"
|
||||
if "pinpoint" not in title.lower() and not any(word in combined for word in ["gap", "routing"]):
|
||||
return "context"
|
||||
if any(word in combined for word in ["rejected_not_claw", "not claw", "outside claw"]):
|
||||
return "rejected_not_claw"
|
||||
if any(word in combined for word in ["rejected_not_brewcode", "not brewcode", "outside brewcode"]):
|
||||
return "rejected_not_brewcode"
|
||||
if "superseded" in combined:
|
||||
return "superseded"
|
||||
if "deferred" in combined or "post-2.0" in combined or "post_2_0" in combined:
|
||||
|
|
@ -216,8 +216,8 @@ def status_for(record: RoadmapRecord) -> str:
|
|||
def deferral_for(record: RoadmapRecord, status: str) -> str:
|
||||
if status == "deferred_with_rationale":
|
||||
return "Deferred by roadmap/approved plan until prerequisite contracts or post-2.0 research admission gates are satisfied."
|
||||
if status == "rejected_not_claw":
|
||||
return "Rejected because the source describes clone-only breadth or behavior outside Claw's machine-truth/clawable-harness identity."
|
||||
if status == "rejected_not_brewcode":
|
||||
return "Rejected because the source describes clone-only breadth or behavior outside Brewcode's machine-truth/brewcodeable-harness identity."
|
||||
if status == "superseded":
|
||||
return "Superseded by a newer roadmap entry or canonical Rust/control-plane contract; keep only for audit traceability."
|
||||
if status == "stale_done":
|
||||
|
|
@ -356,7 +356,7 @@ def summarize_counts(items: list[dict[str, Any]], key: str) -> dict[str, int]:
|
|||
|
||||
def render_markdown(board: dict[str, Any]) -> str:
|
||||
lines = [
|
||||
"# Claw Code 2.0 Canonical Board",
|
||||
"# Brew Code 2.0 Canonical Board",
|
||||
"",
|
||||
f"Generated: `{board['generated_at']}`",
|
||||
f"Roadmap SHA-256 prefix: `{board['sources']['roadmap']['sha256_prefix']}`",
|
||||
|
|
@ -423,19 +423,19 @@ def build_board(repo_root: Path) -> dict[str, Any]:
|
|||
roadmap_path = repo_root / "ROADMAP.md"
|
||||
source_omx = find_source_omx(repo_root)
|
||||
research = source_omx / "research"
|
||||
plan_path = source_omx / "plans" / "claw-code-2-0-adaptive-plan.md"
|
||||
plan_path = source_omx / "plans" / "brew-code-2-0-adaptive-plan.md"
|
||||
headings, actions = parse_roadmap(roadmap_path)
|
||||
items = [roadmap_item(record, i) for i, record in enumerate(headings, 1)]
|
||||
items.extend(roadmap_item(record, i) for i, record in enumerate(actions, 1))
|
||||
|
||||
latest_issues = load_json(research / "claw-open-latest.json")
|
||||
all_issues = load_json(research / "claw-issues.json")
|
||||
items.extend(issue_item(issue, "claw-open-latest", "latest_open_issue", "2.x_intake") for issue in latest_issues)
|
||||
latest_issues = load_json(research / "brewcode-open-latest.json")
|
||||
all_issues = load_json(research / "brewcode-issues.json")
|
||||
items.extend(issue_item(issue, "brewcode-open-latest", "latest_open_issue", "2.x_intake") for issue in latest_issues)
|
||||
# Include a small real-issue sample from the full freeze to keep the board tied to the larger issue manifest without exploding scope.
|
||||
for issue in all_issues[:50]:
|
||||
title_body = f"{issue.get('title','')} {issue.get('body','')}".lower()
|
||||
if any(k in title_body for k in ["security", "windows", "install", "provider", "model", "session", "license", "zed", "spam", "plugin"]):
|
||||
items.append(issue_item(issue, "claw-issues", "issue_theme", "beta_adoption"))
|
||||
items.append(issue_item(issue, "brewcode-issues", "issue_theme", "beta_adoption"))
|
||||
for source_name in ["opencode", "codex"]:
|
||||
repo_meta = load_json(research / f"{source_name}-repo.json")
|
||||
items.append(repo_context_item(repo_meta, source_name))
|
||||
|
|
@ -462,13 +462,13 @@ def build_board(repo_root: Path) -> dict[str, Any]:
|
|||
"ordered_action_count": len(actions),
|
||||
},
|
||||
"approved_plan": {
|
||||
"path": ".omx/plans/claw-code-2-0-adaptive-plan.md",
|
||||
"path": ".omx/plans/brew-code-2-0-adaptive-plan.md",
|
||||
"sha256_prefix": sha256_prefix(plan_path),
|
||||
},
|
||||
"research": {
|
||||
"root": str(source_omx / "research"),
|
||||
"claw_open_latest_count": len(latest_issues),
|
||||
"claw_issues_count": len(all_issues),
|
||||
"brewcode_open_latest_count": len(latest_issues),
|
||||
"brewcode_issues_count": len(all_issues),
|
||||
"opencode_repo": ".omx/research/opencode-repo.json",
|
||||
"codex_repo": ".omx/research/codex-repo.json",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Validate the generated Claw Code 2.0 board coverage and schema."""
|
||||
"""Validate the generated Brew Code 2.0 board coverage and schema."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
|
|
@ -26,7 +26,7 @@ STATUSES = {
|
|||
"stale_done",
|
||||
"superseded",
|
||||
"deferred_with_rationale",
|
||||
"rejected_not_claw",
|
||||
"rejected_not_brewcode",
|
||||
}
|
||||
|
||||
def roadmap_heading_lines(path: Path) -> list[int]:
|
||||
|
|
|
|||
|
|
@ -58,16 +58,16 @@ class WorkspacePathScopeTests(unittest.TestCase):
|
|||
outside = root / 'outside'
|
||||
workspace.mkdir()
|
||||
outside.mkdir()
|
||||
previous = os.environ.get('CLAW_SCOPE_OUTSIDE')
|
||||
os.environ['CLAW_SCOPE_OUTSIDE'] = str(outside)
|
||||
previous = os.environ.get('BREWCODE_SCOPE_OUTSIDE')
|
||||
os.environ['BREWCODE_SCOPE_OUTSIDE'] = str(outside)
|
||||
try:
|
||||
self.assertEqual((f'{outside}/secret.txt',), extract_path_candidates('cat $CLAW_SCOPE_OUTSIDE/secret.txt'))
|
||||
decision = WorkspacePathScope.from_root(workspace).validate_payload('cat $CLAW_SCOPE_OUTSIDE/secret.txt')
|
||||
self.assertEqual((f'{outside}/secret.txt',), extract_path_candidates('cat $BREWCODE_SCOPE_OUTSIDE/secret.txt'))
|
||||
decision = WorkspacePathScope.from_root(workspace).validate_payload('cat $BREWCODE_SCOPE_OUTSIDE/secret.txt')
|
||||
finally:
|
||||
if previous is None:
|
||||
os.environ.pop('CLAW_SCOPE_OUTSIDE', None)
|
||||
os.environ.pop('BREWCODE_SCOPE_OUTSIDE', None)
|
||||
else:
|
||||
os.environ['CLAW_SCOPE_OUTSIDE'] = previous
|
||||
os.environ['BREWCODE_SCOPE_OUTSIDE'] = previous
|
||||
|
||||
self.assertFalse(decision.allowed)
|
||||
self.assertIn(str(outside.resolve()), decision.resolved or '')
|
||||
|
|
|
|||
Loading…
Reference in New Issue