fix(implement-loop): stamp a repo-root anchor into every stage prompt

In worktree sessions a dispatched agent's cwd can resolve to the
primary checkout instead of the loop's working tree — observed as a
per-dispatch race: 6/9 quality dispatches returning infra_blocked with
the sha256-of-empty fingerprint over a real diff, and a tree-check
counting the primary checkout's clean tree into a false no-op BLOCKED
on a genuinely-DONE iteration.

Remedy (validated in the field on the reporting run): the carrier
gains repo_root, stamped into every stage prompt as an explicit
'git -C <root>' anchor with a wrong-directory tripwire (an empty diff
or unexpected HEAD sha = wrong cwd, re-run against the anchor). When
the carrier omits it, the first dispatched stage (mini preflight /
standard plan-index) probes 'git rev-parse --show-toplevel' as a
fallback — itself subject to the same race, so SKILL.md documents the
field as effectively required in worktree sessions.
compiler-driven-edit gets the same carrier field (same defect class,
carrier-only). agent-template stage list updated in passing
(plan-extract[-all], end-verify).

closes #25
This commit is contained in:
2026-07-17 15:35:11 +02:00
parent f5ba8c3747
commit 1cb52fb821
4 changed files with 92 additions and 26 deletions
+21 -3
View File
@@ -50,6 +50,12 @@
// Carrier — Workflow `args`:
// edit_description: what type/signature change to make
// def_site: the definition site (file + symbol) it originates from
// repo_root: optional but strongly recommended (required in worktree
// sessions — issue #25): absolute path of the working tree
// this edit operates on, stamped into both stage prompts
// as a `git -C` anchor so a dispatched agent whose cwd
// resolves to the primary checkout cannot verify the
// wrong tree.
// A non-object carrier or a missing required field fails fast as an infra
// BLOCKED before any agent is dispatched (issue #24).
@@ -87,7 +93,7 @@ if ((carrier !== undefined && carrier !== null && typeof carrier !== 'object') |
}
}
const { edit_description, def_site } = carrier || {}
const { edit_description, def_site, repo_root } = carrier || {}
const missingFields = Object.entries({ edit_description, def_site })
.filter(([, v]) => typeof v !== 'string' || !v.trim())
@@ -107,6 +113,18 @@ const STANDING =
'(its "## Skills plugin: project facts" name the build and test commands) and ' +
'`git log -10 --format=full`. Resolve the build/test commands from those facts.'
// Working-directory anchor (issue #25): same defect class as implement-loop —
// in a worktree session a dispatched agent's cwd can resolve to the primary
// checkout and verify the wrong (clean) tree. Carrier-supplied only here (two
// stages, no cheap probe slot); '' when absent.
const ANCHOR =
typeof repo_root === 'string' && repo_root.trim()
? `\n\nWORKING-DIRECTORY ANCHOR (do not skip): the working tree lives at ${repo_root.trim()}. Run EVERY git ` +
`command as \`git -C ${repo_root.trim()} ...\` and run the build/test commands from that directory. An empty ` +
'`git diff HEAD` or an unexpected HEAD sha means you are in the WRONG directory — re-run against the anchor; ' +
'never report a clean tree from an unanchored cwd.'
: ''
const EDIT_SCHEMA = {
type: 'object',
additionalProperties: false,
@@ -159,7 +177,7 @@ const VERIFY_SCHEMA = {
// ---- Phase 1 — make the edit and let the compiler enumerate the sites ----
phase('Edit + propagate')
const edit = await agent(
`${STANDING}\n\nMake this behaviour-preserving type/signature edit at its definition site, then propagate it ` +
`${STANDING}${ANCHOR}\n\nMake this behaviour-preserving type/signature edit at its definition site, then propagate it ` +
'MECHANICALLY across every site the build flags — let the type checker enumerate the edit sites. Introduce NO ' +
'new behaviour, add NO new test, change NO existing test. If a flagged site cannot be resolved without making a ' +
'design decision, OR resolving it would encode new behaviour, STOP and report it as a hole rather than guessing. ' +
@@ -207,7 +225,7 @@ if (!edit.applied_changes) {
// ---- Phase 2 — verify (the independently-invokable phase) ---------------
phase('Verify')
const verify = await agent(
`${STANDING}\n\nVerify the edit is behaviour-preserving WITHOUT changing any code or test. Run the project build ` +
`${STANDING}${ANCHOR}\n\nVerify the edit is behaviour-preserving WITHOUT changing any code or test. Run the project build ` +
'and the full test suite. Report whether the build is clean and whether the suite is green AND UNCHANGED — i.e. ' +
'every test passes and NO test was added, removed, or weakened versus the pre-edit baseline (compare `git diff ' +
'HEAD` for test-file changes). Also report working_tree_dirty — whether `git status --porcelain` / `git diff HEAD` ' +