Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
14 changes: 7 additions & 7 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) | Worktree seeded from `<dir>` | Reserved; seed implementation deferred |

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.

## 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. No commits, no 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 all run history. `.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). | 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`, `.brunch/cook/runs/`). | Not implemented. Greenfield-only execution; `mkdir` creates an empty worktree. |
| **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>`. Reserved, not implemented in POC. |
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