Skip to content

FE-755: Cook codebase-mode β€” brownfield resolver for brunch cook#155

Open
kostandinang wants to merge 1 commit into
ka/fe-747-petri-declarative-routingfrom
ka/fe-755-cook-codebase-mode
Open

FE-755: Cook codebase-mode β€” brownfield resolver for brunch cook#155
kostandinang wants to merge 1 commit into
ka/fe-747-petri-declarative-routingfrom
ka/fe-755-cook-codebase-mode

Conversation

@kostandinang
Copy link
Copy Markdown
Contributor

@kostandinang kostandinang commented May 26, 2026

TL;DR. brunch cook <dir> now runs against an existing repo. When <dir>/.brunch/cook/plan.yaml exists and the working tree's tracked files are clean, cook initializes the sandbox via git worktree add <sandboxDir> -b cook/<runId> HEAD from the source repo, per-slice worktrees are seeded by file-copy from the parent (excluding .git, sibling slice dirs, __epic__/), and pi-actions run unchanged against pre-existing code. Source branch stays byte-identical. Also consolidates cook's filesystem footprint from <cwd>/.cook/ to <cwd>/.brunch/cook/.

What changed

Codebase-mode resolver

  • New resolveCookMode(dir): ResolvedCookMode β€” pure discriminated union returning {mode: 'fixture' | 'codebase' | 'error'}. Replaces the "not yet implemented" early-exit at cook-cli.ts:65-70.
  • Clean-tree gate (git status --porcelain --untracked-files=no) refuses brownfield against an uncommitted working tree; untracked files (e.g., the user's freshly-authored .brunch/cook/plan.yaml) don't trip the gate.
  • Rejects codebase mode when <dir> is not a git repo.

Worktree lifecycle

  • createSandbox extended with CreateSandboxOptions = { mode: 'fixture' } | { mode: 'codebase'; sourceDir: string }.
  • Codebase mode invokes git worktree add <sandboxDir> -b cook/<runId> HEAD from cwd of the source repo; fixture mode unchanged (empty mkdir).
  • OrchestratorInput gains an optional sandboxMode: 'fixture' | 'codebase' threaded through to net-compiler.ts.

Per-slice worktree population

  • New seedSliceFromParentWorktree(parentSandboxDir, sliceId, plan): string β€” file-copies parent worktree contents into the slice dir, excluding .git, sibling slice subdirs, and __epic__/.
  • net-compiler.ts branches on input.sandboxMode === 'codebase' at slice-dir creation.

Path consolidation (.cook/ β†’ .brunch/cook/)

  • All cook state now lives under .brunch/cook/runs/<runId>/worktree/ (matching the existing .brunch/ workspace convention).
  • Updates SPEC Β§D50 (resolver path), Β§A49 (worktree path), Β§I123-K (invariant + evidence), Β§Lexicon (worktree, codebase mode), docs/design/orchestrator.md, and .gitignore (the .brunch/ ignore already covered the new paths; redundant .cook/ line removed).

pi-actions.ts unchanged.

Why now

Per SPEC Β§D50, codebase mode was reserved at the resolver level but not implemented. Without it, brunch cook could only run greenfield fixtures β€” agents generate code from scratch in fresh worktrees. The orchestrator stayed a fixture-only substrate even after Petri Phases 0–2 (FE-730, FE-738, FE-743, FE-745) and declarative routing (FE-747) landed. Codebase mode is the smallest step from "orchestrator-as-substrate" to "orchestrator-as-product."

Verification

  • New unit tests across cook-cli.test.ts (resolveCookMode β€” 5 tests), worktree.test.ts (codebase-mode createSandbox β€” 3 tests), epic-sandbox-merge.test.ts (seedSliceFromParentWorktree β€” 5 tests). Existing greenfield-mode tests unchanged.
  • New brownfield-smoke.integration.test.ts β€” tmpdir + real git + fake actions. Asserts source branch byte-identical, modification landed in slice worktree, parent worktree on cook/<runId>. (fixtures/brownfield-smoke/ lives as a test-setup function rather than a committed directory because nested .git/ inside the brunch repo creates submodule weirdness.)
  • npm run verify green β€” 123 test files, 1447 tests pass.
  • 2026-05-26 outer-loop smoke with real pi against a tmpdir git repo containing a known subtract bug confirmed in-place file modification (pi edited the existing line, did not overwrite) and source-byte-identical isolation. Took 1m 4s wall time.

Out of scope

Three follow-ons surfaced during the outer-loop smoke and review, all explicitly deferred:

  • pi-actions evaluator scoping. pi-actions.ts:70 passes --tools read,write,edit,bash to every action including evaluate-done. Real pi fixed the buggy file during evaluation and reported done: true on the first call, collapsing the intended TDD-shaped flow (evaluate β†’ write-tests β†’ write-code β†’ run-tests β†’ evaluate). Affects both modes but more visible in brownfield. Worth its own frontier.
  • Cook artifact promotion path. The cook branch (cook/<runId>) has HEAD === source HEAD; the modification lives in two untracked subdirs of the cook branch's working tree (<runDir>/worktree/<sliceId>/ and <runDir>/worktree/__epic__/<epicId>/). No git merge cook/<runId> story today. Pairs with worktree + branch GC. Worth a cook-artifact-lifecycle frontier.
  • Multi-slice brownfield over-copy. Each slice worktree file-copies the entire cwd repo, producing O(slices Γ— repoSize) duplication. TODO captured in seedSliceFromParentWorktree. Mitigation: real git worktree add per slice off the run-level cook branch, or diff-based epic merge.

Sandcastle adoption deferred. A 2026-05-26 spike evaluated @ai-hero/sandcastle for hybrid adoption. Technically viable (built-in pi agent provider, decoupled worktree primitives, noSandbox() available without Docker requirement) but pre-1.0 with non-trivial effect runtime deps. Revisit when sandcastle ships 1.0 or when multi-slice over-copy becomes a measurable bottleneck.

Traceability

Requirements 46–50; SPEC Β§D50 (codebase-mode resolver), Β§A49 (worktree isolation), Β§I123-K (codebase mode preserves source-byte-identity); frontier cook-codebase-mode in memory/PLAN.md (now Recently Completed with the three follow-on findings captured). Predecessor: FE-747 (declarative routing) β€” base of this stack.

`brunch cook <dir>` now runs against an existing repo. When
`<dir>/.brunch/cook/plan.yaml` exists and the working tree's tracked
files are clean, cook initializes the sandbox via
`git worktree add <sandboxDir> -b cook/<runId> HEAD` from the source
repo, per-slice worktrees are seeded by file-copy from the parent
(excluding `.git`, sibling slice dirs, and `__epic__/`), and pi-actions
run unchanged against pre-existing code. The source branch in `<dir>`
stays byte-identical.

Also consolidates cook's filesystem footprint from `<cwd>/.cook/` to
`<cwd>/.brunch/cook/` so all cook state lives under the existing
`.brunch/` workspace convention.

Acceptance criteria covered:
- Resolver replaces the "not yet implemented" early-exit at
  cook-cli.ts:65-70 with a pure `resolveCookMode(dir)` discriminated
  union (fixture / codebase / error).
- Clean-tree gate refuses brownfield runs with uncommitted tracked
  changes (`git status --porcelain --untracked-files=no`).
- `createSandbox` gains a `CreateSandboxOptions` discriminated union;
  codebase mode invokes `git worktree add` on branch `cook/<runId>`.
- `seedSliceFromParentWorktree` populates per-slice dirs from the
  parent worktree contents.
- `OrchestratorInput.sandboxMode?: 'fixture' | 'codebase'` threads the
  mode through to net-compiler.
- `pi-actions.ts` unchanged. Existing greenfield fixture-mode tests
  pass; new tests in cook-cli.test.ts (resolveCookMode), worktree.test.ts
  (codebase-mode createSandbox), epic-sandbox-merge.test.ts
  (seedSliceFromParentWorktree), plus a tmpdir+fake-actions
  brownfield-smoke.integration.test.ts that pins source-byte-identical
  isolation. Total: 1447/1447 tests pass.

Known follow-ons (out of scope for this PR):
- pi-actions evaluator currently gets full write tools, collapsing the
  TDD-shaped workflow when pi can satisfy the slice during evaluation.
- No "promote cook artifact back" story β€” modification lives in
  untracked subdirs of the cook branch's worktree, not as commits.
- Multi-slice brownfield over-copy (TODO in `seedSliceFromParentWorktree`)
  β€” slice 2's 1-slice fixture didn't exercise it.

A 2026-05-26 spike evaluated `@ai-hero/sandcastle` for hybrid adoption.
Technically viable (built-in pi provider, decoupled worktree primitives,
noSandbox available) but deferred until sandcastle ships 1.0 or
multi-slice over-copy becomes a measurable bottleneck.

A 2026-05-26 outer-loop smoke against a tmpdir git repo with real pi
confirmed in-place file modification and source-byte-identical isolation
end-to-end.

Updates SPEC Β§D50, Β§A49, Β§I123-K, Β§Lexicon (path consolidation) and
PLAN.md `cook-codebase-mode` frontier definition (now Recently
Completed with three follow-on findings).

Co-Authored-By: Claude <noreply@anthropic.com>
@kostandinang kostandinang changed the title FE-755: Cook codebase-mode β€” brownfield resolver for brunch cook. FE-755: Cook codebase-mode β€” brownfield resolver for brunch cook May 26, 2026
@kostandinang kostandinang marked this pull request as ready for review May 26, 2026 17:03
@cursor
Copy link
Copy Markdown

cursor Bot commented May 26, 2026

PR Summary

Medium Risk
Adds git worktree lifecycle and brownfield file-copy seeding where a bug could break source-repo isolation or run against a dirty tree; mitigated by the clean-tree gate, byte-identical checks, and new unit/integration tests.

Overview
brunch cook can run brownfield when <dir>/.brunch/cook/plan.yaml exists: a new resolveCookMode picks fixture vs codebase vs error, enforces a clean tracked working tree (untracked .brunch/ is OK), and wires runCook through codebase sandbox creation instead of the old "not yet implemented" exit.

Sandbox layout moves under .brunch: run output is now <cwd>/.brunch/cook/runs/<runId>/worktree/ (docs/SPEC/plan updated; redundant .cook/ ignore removed). Codebase runs use git worktree add on cook/<runId> from the source repo; greenfield still gets an empty worktree.

Per-slice brownfield seeding: OrchestratorInput.sandboxMode drives net-compiler to call seedSliceFromParentWorktree (file-copy from the parent worktree, skipping .git, sibling slice dirs, and __epic__/). pi-actions is unchanged.

Tests cover resolver gates, worktree branch/isolation, seed exclusions, and a tmpdir brownfield smoke integration run.

Reviewed by Cursor Bugbot for commit fa4ff77. Bugbot is set up for automated code reviews on this repo. Configure here.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fa4ff77. Configure here.


return sliceDir;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dep seed wipes brownfield base

High Severity

In codebase mode, slices are pre-filled from the parent worktree, but every action still runs seedSliceSandboxFromDeps with preserveExisting. When a slice has depends_on and dependency worktrees exist, that helper deletes every file in the slice sandbox that is not also present in a dependency tree. Most of the brownfield checkout is removed before agents run, so dependent brownfield slices cannot modify the existing repo as intended.

Additional Locations (1)
Fix in CursorΒ Fix in Web

Reviewed by Cursor Bugbot for commit fa4ff77. Configure here.

@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented May 26, 2026

πŸ€– Augment PR Summary

Summary: This PR implements β€œcodebase mode” for brunch cook <dir>, enabling brownfield runs against an existing git repo while keeping the source branch’s tracked files unchanged.

Changes:

  • Introduced resolveCookMode(dir) (fixture/codebase/error) and replaced the prior β€œnot yet implemented” exit.
  • Added a clean-tree gate for codebase mode using git status --porcelain --untracked-files=no and rejected non-git dirs.
  • Extended createSandbox to support codebase mode via git worktree add -b cook/<runId> ... HEAD.
  • Added per-slice seeding from the parent worktree (excluding .git, sibling slice dirs, and __epic__/) and wired it into the Petri net compiler.
  • Threaded sandboxMode through orchestrator input so engines can branch on fixture vs codebase behavior.
  • Consolidated cook run output paths from .cook/ to .brunch/cook/ and updated docs/spec/plan accordingly.
  • Added unit + integration coverage, including an end-to-end brownfield smoke test using a real tmp git repo.

Technical notes: Codebase mode checks out a separate worktree branch (cook/<runId>) and runs agents against seeded slice worktrees without mutating the source repo’s HEAD or tracked-file status.

πŸ€– Was this summary useful? React with πŸ‘ or πŸ‘Ž

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

mkdirSync(sliceDir, { recursive: true });

const excludedNames = new Set<string>(['.git', EPIC_MERGE_SEGMENT]);
for (const s of plan.slices) excludedNames.add(s.id);
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seedSliceFromParentWorktree assumes slice IDs won’t collide with real top-level repo entries, but in codebase mode the parent worktree is a full checkout β€” if a slice id matches an existing dir (e.g. src), the seed will skip that dir and/or copy repo-root files into it. Consider adding a guard (or reserving a dedicated slice root dir) to prevent slice-id/name collisions from corrupting the seeded worktree.

Severity: high

Fix This in Augment

πŸ€– Was this useful? React with πŸ‘ or πŸ‘Ž, or πŸš€ if it prevented an incident/outage.

for (const slice of plan.slices) {
mkdirSync(resolveSliceWorktreeDir(input.sandboxDir, slice.id), { recursive: true });
if (input.sandboxMode === 'codebase') {
seedSliceFromParentWorktree(input.sandboxDir, slice.id, plan);
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In codebase mode you seed the slice dirs up-front here, but later calls to seedSliceSandboxFromDeps(..., { preserveExisting: true }) still delete any files not present in dependency slices when depFiles.size > 0, which can wipe slice-local outputs between actions for slices with depends_on. That seems likely to break multi-step slices with dependencies (tests written in write-tests disappearing before write-code / run-tests).

Severity: medium

Fix This in Augment

πŸ€– Was this useful? React with πŸ‘ or πŸ‘Ž, or πŸš€ if it prevented an incident/outage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant