CLAUDE.md · diff

git:20261030.199f733 to git:20261030.67f0144

2 added, 2 removed. Audit A to A.

## Équipe multi-agents (plugin brigade)
The section title and the bold labels of the bindings below stay in French on purpose: `/brigade:init` and the roles look them up by these exact words. The content is English.
- **Language** : code, docs and issues are in English. The conversation with the chef is in French, and so is every dialogue with the agents. The word for each object is in `docs/glossary.md`.
- **Acteurs** : the chef, the second (local) and the kitchen (runtime, manager, cooks, pass)
- **Interface** : all interface work is carried by the kitchen's design cook (#837)
- **Branche d'intégration** : `main`
- **Worktrees** : `<parent of the repo>/brigade-wt/<n>-<slug>` — a sibling of the repo, the parent is computed (`dirname "$(git rev-parse --show-toplevel)"`)
- **Setup worktree** : `.claude/brigade/worktree-setup.sh <n> <WT>`
- **Gates** : `.claude/brigade/gates.sh <WT>`, then `/code-review`
- **Zones de fichiers** : three product zones, with different rules.
- **Runtime** — `runtime/`: the V2 runtime, TypeScript run by Node 26, with its own `package.json`. A runtime issue writes here, and nowhere in the plugin zone.
- **App** — `app/`: the V2 desktop app (Electron, xterm.js, node-pty), TypeScript with no build, with its own `package.json`. An app issue writes here and adds **nothing** to the runtime: the app runs the commands that exist, it duplicates none. `app/src/vue/` (the skin) is carried by the design cook (#837). The mockup is in `docs/maquettes/app/`; an app ticket names the screen it serves.
- **Plugin V1** — `commands/` (role definitions), `agents/` (the spawned dev-teammate), `codex/skills/` (Codex mirror, the source `/brigade:sync` copies into the consuming project), `.claude-plugin/` (manifests). `evals/` holds the evaluation cases of these roles.
- **Common** — `scripts/` (release tooling), `docs/`, `.claude/brigade/` (gates and worktree setup).
- **Dev local** :
- **Runtime** — Node 26, no build. Tests: `npm --prefix runtime test` (they pass on a bare clone, with nothing installed). Type check: `npm --prefix runtime run typecheck` (needs the dev dependencies, which the worktree setup installs). Start: `npm --prefix runtime start`, after the setup — the state lives in `$BRIGADE_STATE_DIR`. **Nine variables are required** (`BRIGADE_STATE_DIR`, `BRIGADE_PROJECT`, `BRIGADE_GITHUB_REPO`, `BRIGADE_REPO_DIR`, `BRIGADE_BASE_BRANCH`, and the calibration of the manager and the reviewer: `BRIGADE_MANAGER_MODEL`, `BRIGADE_MANAGER_EFFORT`, `BRIGADE_REVIEWER_MODEL`, `BRIGADE_REVIEWER_EFFORT`): the setup sets only one, you set the other eight, and the runtime refuses to start, naming the one that is missing. The detail is in `docs/runtime/exploitation.md` (the sections on the five variables with no default, and on the dev workstation). **The runtime doc is split into pages: open the index `docs/runtime/README.md` first, then only the pages of your part** (`docs/runtime.md` is only a pointer to that index). **Started this way, it is a real kitchen: `start` launches real cooks on the Max quota, with no permission prompt.** The chef's commands run as `npm --prefix runtime run <command>`: `log` (read the log), `rail` (the ticket rail), `guardrails` (see and set the caps), `base` (see the integration base, replay its gates when red), `installation` (check that a project is ready to be served — the walkthrough is in `docs/installer.md`), plus `status`, `station`, `pass` and `grant` — the list in `runtime/package.json` is authoritative. No daemon runs on the dev workstation; the tests drive a fake `claude` (`BRIGADE_CLAUDE_BIN`) and use no quota.
- **App** — Tests: `npm --prefix app test`; type check: `npm --prefix app run typecheck` — both need the dependencies, which the worktree setup installs **without the Electron binary** (311 MB), which no test launches. Start: `npm --prefix app start`, which installs that binary on first launch. **Started this way, it is a real app: every space opened launches a real `claude` in the project folder** — to try it without quota, use `BRIGADE_CLAUDE_BIN=app/test/aides/faux-claude.sh` and `BRIGADE_APP_CONFIG=<a trial file>`. **And a project that declares `hote` (the name by which your `ssh` reaches its box) is read by a real `ssh` every thirty seconds** — use `BRIGADE_SSH_BIN=<a fake ssh>` to try it without a box. Projects are declared in `~/.config/brigade/app.json`, outside the repo. The detail is in `docs/app.md`.
- - **Plugin V1** — nothing to run. To try a modified role, you have to install it: bump `version` in `.claude-plugin/plugin.json`, then run `scripts/update-partout.sh`. The published plugin comes from `main`, not from `v2`: the release path (same version on both sides, bump on `main` first, carry over path by path) is written in `scripts/README.md`.
+ - **Plugin V1** — nothing to run. To try a modified role, you have to install it: bump `version` in `.claude-plugin/plugin.json`, then run `scripts/update-partout.sh`. The published plugin comes from `main`: the release path (bump `version` in `plugin.json`, merge to `main`) is written in `scripts/README.md`.
- **What the setup gives a worktree** — `BRIGADE_STATE_DIR=<WT>/.brigade-state` (local database `log.db`, lock `lock.db`, `runs/`; ignored by git) and `BRIGADE_PORT=20000 + <issue number>`. Two worktrees have two directories and two ports: they do not collide. Never write a state path or a port by hand. The tests use neither: each creates its own temporary directory and listens on port 0.
- **Doc vivante** : `docs/` — every delivery that changes the behavior of a role updates the matching doc under `docs/`.
- **Specs / plans** : `docs/superpowers/specs/<YYYY-MM-DD>-<slug>.md`
- **Doc produit** : `README.md` — it describes the protocol as the user lives it (roles, handoffs, non-negotiable rules).
### Conventions of this repo
- **In the plugin zone, the Codex mirror is not optional.** Every change to a `commands/<role>.md` must be carried into `codex/skills/<role>/`. The gates require it: a role modified since the integration branch, with nothing moving under its mirror, turns them red. They see that the mirror moved, not that it says the same thing — correctness is up to the dev. A change that only touches `runtime/` or `app/` owes nothing to the mirror.
- - **The gates play the runtime tests, then the app's**, as soon as the zone's `package.json` exists, and its type check if declared. **They play only the tests the change touches** (#1004): `.claude/brigade/tests-touches.mjs` keeps a test file when a changed file is in what it imports, or what it names (a `src/main.ts` it launches, a `docs/…` it reads), itself or through the test helpers it uses. A helper's own imports of product code are not followed (a shared helper such as `test/aides/cuisine.ts` imports almost everything), and neither are a helper's type-only imports, which the type check covers — a test's are, resolved by their path: a file its tests import only for its types still plays them, and the type check with them. That is on GitHub Actions (`.github/workflows/tests.yml`, on every PR to `v2`, whose check the pass waits for before merging). On a station or a dev workstation (outside CI), the gates play only the tests **close to** the change (`--proches`): the test files it modifies, and those named after a file it modifies (`src/rail.ts` → `test/rail.test.ts`) — a few seconds; `BRIGADE_GATES_TESTS=touches` plays the touched ones there too. On the base itself the diff is empty and everything runs (CI after each merge, and the pass's base check), and that is where a test touched without anything saying so is caught. A change whose diff is empty beyond the base — an empty commit, judged by the pass merged with the base — is not the base: it touches nothing and plays no test (#1889). `BRIGADE_GATES_TOUT=1` plays everything, and so does a change to a `package.json`, a `tsconfig` or the worktree setup — except a zone's `package.json` whose only change is in its `scripts`, `test`, `pretest` and `posttest` aside (a command added or removed): that plays only the tests in whose closure a file names `package.json` outside its comments (#1611). A missing zone is not a failure. No network, no real `claude`, no screen: **no test launches Electron** — what is proven about the app lives in modules played by `node --test`, the view stays thin. The app tests on a real pty are skipped, saying so, where node-pty is not shipped compiled (Linux).
+ - **The gates play the runtime tests, then the app's**, as soon as the zone's `package.json` exists, and its type check if declared. **They play only the tests the change touches** (#1004): `.claude/brigade/tests-touches.mjs` keeps a test file when a changed file is in what it imports, or what it names (a `src/main.ts` it launches, a `docs/…` it reads), itself or through the test helpers it uses. A helper's own imports of product code are not followed (a shared helper such as `test/aides/cuisine.ts` imports almost everything), and neither are a helper's type-only imports, which the type check covers — a test's are, resolved by their path: a file its tests import only for its types still plays them, and the type check with them. That is on GitHub Actions (`.github/workflows/tests.yml`, on every PR to `main`, whose check the pass waits for before merging). On a station or a dev workstation (outside CI), the gates play only the tests **close to** the change (`--proches`): the test files it modifies, and those named after a file it modifies (`src/rail.ts` → `test/rail.test.ts`) — a few seconds; `BRIGADE_GATES_TESTS=touches` plays the touched ones there too. On the base itself the diff is empty and everything runs (CI after each merge, and the pass's base check), and that is where a test touched without anything saying so is caught. A change whose diff is empty beyond the base — an empty commit, judged by the pass merged with the base — is not the base: it touches nothing and plays no test (#1889). `BRIGADE_GATES_TOUT=1` plays everything, and so does a change to a `package.json`, a `tsconfig` or the worktree setup — except a zone's `package.json` whose only change is in its `scripts`, `test`, `pretest` and `posttest` aside (a command added or removed): that plays only the tests in whose closure a file names `package.json` outside its comments (#1611). A missing zone is not a failure. No network, no real `claude`, no screen: **no test launches Electron** — what is proven about the app lives in modules played by `node --test`, the view stays thin. The app tests on a real pty are skipped, saying so, where node-pty is not shipped compiled (Linux).
- **Several suites run at the same time on the machine** — each dev's gates — and each then takes three to four times as long. A test must therefore **never race the clock**: it waits for a fact (`jusqua`, a file written, a dead process), not for a duration. A timeout there is only a wide guard, so that a broken test eventually says so; a real runtime timer set short in a test (inactivity, gates cap) must stay true when everything takes ten times longer to start. Nothing is shared between two worktrees except Node's compile cache, which is only a cache. To try it: several `npm --prefix runtime test` side by side, several times.
- **Never kill a process by its name** (`pkill -f src/main.ts`): the tests of every worktree launch real `main.ts`, and you would fail another one's suite. A stuck test is killed by its pid. One that *waits* forever stops by itself: after two minutes it fails, named, and its file's process exits at once, even if it still holds a timer or a child process (`--test-force-exit`) — the gates hand control back, red. A test stuck in **synchronous** code (a loop) is stopped by no timer of its runner: the gates' **clock guard** catches it — past `300 s` (`BRIGADE_GATES_DELAI_TESTS`, in whole seconds), `gates.sh` kills the suite's process group, test files included, and goes red (`FAIL tests du runtime arrêtés par la garde d'horloge`). It does not name the test, and it guards only the gates: an `npm --prefix runtime test` run by hand stays held. One thing is not enforced: a process a test launched without killing it at the end outlives it.
- **A runtime or app test failing in the gates is read without replaying**: its name and its error are in the gates output, and the whole output of the suite is kept in `.brigade-state/gates/` of the worktree (one week). A red that does not reproduce is diagnosed there — not by replaying until it comes back.
- **An added role must be declared** in `commands` of `.claude-plugin/plugin.json`, or it never loads. The gates catch the omission.
- **A plugin update does not change the running session**: roles are loaded at startup. Never expect an orchestration in progress to pick up a freshly published fix.