Skip to content
Merged
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ dist-ssr
bun.lock
.notes.md
.brunch/
.cook/
brunch.db*
todo.txt

Expand Down
16 changes: 8 additions & 8 deletions docs/design/orchestrator.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,23 +221,23 @@ Cook decides between **fixture mode** (greenfield) and **codebase mode** (brownf
| Plan location | Mode | Worktree behavior | POC status |
|---|---|---|---|
| `<dir>/plan.yaml` | Fixture (greenfield) | Empty worktree | Implemented |
| `<dir>/.cook/plan.yaml` | Codebase (brownfield) | Worktree seeded from `<dir>` | Reserved; seed implementation deferred |
| `<dir>/.brunch/cook/plan.yaml` | Codebase (brownfield) | `git worktree add` on `cook/<runId>`, plus CoW copy of missing top-level runtime deps | Implemented |

Naming intuition: a **fixture** *is* a plan with supporting artifacts (`plan.yaml` at root, like a manifest); a **codebase** *has* a plan as configuration (`.cook/plan.yaml`, like `.eslintrc` or `.github/`).
Naming intuition: a **fixture** *is* a plan with supporting artifacts (`plan.yaml` at root, like a manifest); a **codebase** *has* a plan as configuration (`.brunch/cook/plan.yaml`, alongside other brunch workspace state).

The plan may declare `mode: greenfield | brownfield` to override the default inferred from location.

POC implements fixture mode end-to-end; codebase mode returns a structured "not yet implemented" error on the reserved resolver branch. The seed step (likely `git worktree add` when `.git` exists; filtered copy fallback otherwise) is the only meaningful added work to enable brownfield — engine, registry, agents, and reports are mode-agnostic.
POC implements fixture mode end-to-end and codebase mode for git repositories. In codebase mode, cook creates a fresh `cook/<runId>` branch with `git worktree add`, then CoW-copies missing top-level runtime dependencies such as `node_modules/`. If filesystem clone/reflink support is unavailable, the copy helper falls back to a normal recursive copy: slower and larger on disk, but semantically equivalent.

## 8. Worktree isolation

Each run gets an isolated worktree at `<cwd>/.cook/runs/<runId>/worktree/`, where `<cwd>` is the directory the user invoked `brunch cook` from (not the fixture/plan directory). Reports land alongside at `<cwd>/.cook/runs/<runId>/reports.jsonl`. Agents write freely inside the worktree; the fixture directory (`<dir>`) and the invoking repo are never mutated. No commits, no pushes. Recovery = throw the worktree away and start a new run.
Each run gets an isolated worktree at `<cwd>/.brunch/cook/runs/<runId>/worktree/`, where `<cwd>` is the directory the user invoked `brunch cook` from (not the fixture/plan directory). Reports land alongside at `<cwd>/.brunch/cook/runs/<runId>/reports.jsonl`. Agents write freely inside the worktree; the fixture directory (`<dir>`) and the invoking repo are never mutated. Codebase-mode agents may commit on the generated `cook/<runId>` branch, but cook never pushes. Recovery = throw the worktree away and start a new run.

The run location is cwd-scoped rather than fixture-scoped so that:

- **Fixtures stay pristine.** Checked-in fixture directories (e.g. `fixtures/txt/`) contain only `plan.yaml` and are byte-identical before and after a run.
- **No path traversal.** Because the worktree is not a descendant of the fixture dir, agents cannot accidentally read or write fixture-level files.
- **Easy cleanup.** `rm -rf .cook/runs/` in the invoking directory clears all run history. `.cook/` is gitignored at the repo level.
- **Easy cleanup.** `rm -rf .brunch/cook/runs/` in the invoking directory clears run artifacts. Codebase mode also creates a git worktree and branch, so full cleanup is `git worktree remove .brunch/cook/runs/<runId>/worktree` and `git branch -D cook/<runId>`. `.brunch/` is gitignored at the repo level.

`--worktree <path>` overrides the default location for explicit pinning.

Expand Down Expand Up @@ -284,8 +284,8 @@ The design above is the target shape. The POC builds a deliberate subset and def
| Design element | Full design | POC posture |
|---|---|---|
| **Action dispatch** | `ActionRegistry` registers handlers by name; engines look up by name; new actions (e.g. `lint`, `human-review`, `research`) register without engine surgery. | Inline handler dispatch per engine (e.g. a record literal or switch). Promote to a real registry when a 3rd action type lands. |
| **Plan resolver** | Dual-mode by plan location: `<dir>/plan.yaml` → fixture (greenfield); `<dir>/.cook/plan.yaml` → codebase (brownfield). | Fixture mode only. CLI takes `<fixture-dir>` directly; codebase branch is documented here, not coded. |
| **Brownfield seed** | When codebase mode is used and `<dir>/.git` exists, prefer `git worktree add`; otherwise filtered copy (`rsync` excluding `.git`, `node_modules`, `dist`, `.cook/runs/`). | Not implemented. Greenfield-only execution; `mkdir` creates an empty worktree. |
| **Plan resolver** | Dual-mode by plan location: `<dir>/plan.yaml` → fixture (greenfield); `<dir>/.brunch/cook/plan.yaml` → codebase (brownfield). | Implemented for fixture mode and git-backed codebase mode. |
| **Brownfield seed** | When codebase mode is used and `<dir>/.git` exists, prefer `git worktree add`; copy missing runtime deps with CoW/reflink where possible. | Implemented for git-backed repos; non-git filtered copy remains deferred. |
| **Token-pointer discipline** | Universal rule: tokens between transitions carry only `{ reportId, sliceId, epicId }` pointers; all event content lives in `reports.jsonl`. Applied across both engines. | Petrinet engine enforces this internally (it's a hard constraint of the substrate). Procedural engine is free to pass data through normal function calls — each engine handles its own state shape, the shared seam is just inputs and outputs. |
| **Layer 2 adapter tests** | Per-engine internal tests (net compilation / solver / transition firing for petrinet; topo sort / inner-loop state transitions / retry counter for procedural). | Optional. Defer until a debugging need surfaces. Layer 1 (contract) + Layer 3 (integration) are mandatory; Layer 2 is added if and when it pays for itself. |
| **Streaming UX formatting** | Compact per-event lines like `[slice-1 ▸ test-writer] tests-written → 3 files`. | Implemented: elapsed timing, icons (▸/✓/✗/●/○), structured header/footer, `--verbose` for raw pi output. JSON stays in `reports.jsonl` only. |
Expand Down Expand Up @@ -315,4 +315,4 @@ Full comparison table in the POC summary doc.
| **report** | One structured event line in `reports.jsonl`. Carries the durable content; tokens carry only pointers to reports. |
| **worktree** | Isolated filesystem location where agents write during a run. Per-run; ephemeral. |
| **fixture mode** | Greenfield execution: plan at `<dir>/plan.yaml`, empty worktree. POC default. |
| **codebase mode** | Brownfield execution: plan at `<dir>/.cook/plan.yaml`, worktree seeded from `<dir>`. Reserved, not implemented in POC. |
| **codebase mode** | Brownfield execution: plan at `<dir>/.brunch/cook/plan.yaml`, worktree seeded from `<dir>` on generated `cook/<runId>` branch. |
123 changes: 123 additions & 0 deletions docs/praxis/orchestration-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Orchestration guide — cook on brunch

Run `brunch cook` against the brunch repo in codebase (brownfield) mode.

## Pre-flight

```sh
which pi && pi --version # pi >= 0.74
npm run build # dist/ fresh
git status --porcelain --untracked-files=no # must be empty
```

`.brunch/` is already gitignored, so cook artifacts won't appear in `git status`.

## Author the plan

Cook reads ONE file: `.brunch/cook/plan.yaml`.

**Target shape: read it from a spec-graph projection.** The intended long-term path is `petri-graph-compilation` (blocked on `intent-graph-semantics` / FE-700): cook compiles its net directly from workspace plan-graph nodes + relation policy, no `plan.yaml` step at all. The plan-graph projection becomes the source of truth; `.brunch/cook/plan.yaml` either disappears or becomes a derived artifact emitted by the compiler.

**Not done yet.** Until `petri-graph-compilation` lands, the bridge from spec/frontier to cook plan is manual. Two interim mechanisms:

### A. `/ln-scope`-then-translate (most disciplined interim)

Run `/ln-scope` on a `memory/PLAN.md` frontier to produce a scope card (Target Behavior + Acceptance + Verification). Translate the scope card to YAML by hand — usually 15–30 lines, 2–5 minutes. The scope card is the human-readable contract you can verify before spending pi tokens.

### B. One-shot pi translation (cheap interim)

Extract the frontier section and ask pi for YAML:

```sh
FRONTIER="<id>"
awk "/^### $FRONTIER\$/,/^### /" memory/PLAN.md | head -n -1 > /tmp/f.md
pi -p --no-session --provider anthropic --model claude-haiku-4-5 \
--tools "read,write" \
"Translate /tmp/f.md into .brunch/cook/plan.yaml. One epic, one slice per
Acceptance line (max 2). Each slice needs a verification.target pointing
at a real bun-test file. Definitions name exact file + change + constraint.
Output only YAML." > .brunch/cook/plan.yaml
```

Always review — pi hallucinates file paths.

### C. Hand-author (escape hatch)

For one-off experiments or when no frontier exists:

```yaml
epics:
- id: <epic-id>
summary: <one-line>
depends_on: []
verification: []

slices:
- id: <slice-id>
epic_id: <epic-id>
definition: |
Modify `<symbol>` in `<file>`:
- <what>
- <constraint>
Do not modify <thing-to-preserve>.
depends_on: []
verification:
- kind: unit-test
target: <path/to/existing.test.ts>
```

### Discipline (applies to all three)

- Every slice needs a real `verification.target` (an existing test file) or `bun test` halts with no output → retry exhaustion.
- Definitions name exact file + exact change + exact constraint. Vague slices halt or short-circuit.
- 1–2 slices per run; more triggers more disk usage even with CoW.

## Cook

```sh
node --env-file=.env bin/brunch.js cook "$(pwd)" --policy=serial --max-retries=1
```

`"$(pwd)"` (absolute path) is required — relative `.` resolves against brunch's packageRoot in the spawned CLI, not your shell pwd.

## Inspect

```sh
RUN=$(ls -t .brunch/cook/runs/ | head -1)

# Source byte-identical (brownfield invariant)
git diff HEAD --stat # empty
git status --porcelain --untracked-files=no # empty

# Modification lives in the slice worktree, not on the cook branch as a commit
diff -r src/ ".brunch/cook/runs/$RUN/worktree/<slice-id>/src/" | head
cat ".brunch/cook/runs/$RUN/reports.jsonl"
```

## Promote (manual)

```sh
cp -R ".brunch/cook/runs/$RUN/worktree/__epic__/<epic-id>/." .
git status # review and commit normally
```

No automatic `git merge cook/<runId>` yet — that's the deferred `cook-artifact-lifecycle` frontier.

## Cleanup

```sh
RUN_ID=$(basename "$(ls -td .brunch/cook/runs/*/ | head -1)")
git worktree remove --force ".brunch/cook/runs/$RUN_ID/worktree"
git branch -D "cook/$RUN_ID"
git branch --list "cook-slice/$RUN_ID/*" | xargs -n1 git branch -D
rm -rf ".brunch/cook/runs/$RUN_ID"
rm -f .brunch/cook/plan.yaml
```

Periodic stragglers: `git worktree prune` + `git branch --list 'cook*' | xargs -n1 git branch -D`.

## Known limitations

- **Pi evaluator may short-circuit.** Pi has `read,write,edit,bash` even during `evaluate-done` and may fix the file during evaluation rather than going through write-tests → write-code → run-tests. Non-deterministic.
- **No commit on the cook branch.** Modification is in untracked subdirs of the cook branch's worktree, not committed. Promotion is manual `cp -R`.
- **Plan vs frontier mismatch.** `.brunch/cook/plan.yaml` is orchestrator runtime, not planning vocabulary. `/ln-scope` or pi-assisted translation is the bridge.
Loading
Loading