baldart 3.41.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/VERSION +1 -1
  3. package/framework/.claude/agents/REGISTRY.md +72 -24
  4. package/framework/.claude/agents/api-perf-cost-auditor.md +12 -5
  5. package/framework/.claude/agents/code-reviewer.md +30 -23
  6. package/framework/.claude/agents/codebase-architect.md +47 -43
  7. package/framework/.claude/agents/coder.md +29 -18
  8. package/framework/.claude/agents/doc-reviewer.md +55 -28
  9. package/framework/.claude/agents/plan-auditor.md +77 -12
  10. package/framework/.claude/agents/prd-card-writer.md +43 -13
  11. package/framework/.claude/agents/prd.md +22 -3
  12. package/framework/.claude/agents/qa-sentinel.md +33 -15
  13. package/framework/.claude/agents/security-reviewer.md +65 -10
  14. package/framework/.claude/agents/senior-researcher.md +8 -1
  15. package/framework/.claude/agents/ui-expert.md +22 -7
  16. package/framework/.claude/commands/check.md +31 -11
  17. package/framework/.claude/commands/codexreview.md +48 -29
  18. package/framework/.claude/commands/new.md +29 -330
  19. package/framework/.claude/commands/qa.md +57 -37
  20. package/framework/.claude/skills/context-primer/SKILL.md +29 -8
  21. package/framework/.claude/skills/doc-writing-for-rag/SKILL.md +36 -36
  22. package/framework/.claude/skills/frontend-design/SKILL.md +10 -8
  23. package/framework/.claude/skills/new/SKILL.md +409 -302
  24. package/framework/.claude/skills/prd/SKILL.md +67 -38
  25. package/framework/.claude/skills/prd/assets/card-template.yml +22 -26
  26. package/framework/.claude/skills/prd/assets/epic-template.yml +5 -5
  27. package/framework/.claude/skills/prd/assets/state-template.md +25 -3
  28. package/framework/.claude/skills/prd/references/api-perf-gate.md +143 -33
  29. package/framework/.claude/skills/prd/references/audit-phase.md +48 -34
  30. package/framework/.claude/skills/prd/references/backlog-phase.md +38 -11
  31. package/framework/.claude/skills/prd/references/discovery-phase.md +121 -44
  32. package/framework/.claude/skills/prd/references/impact-analysis.md +127 -23
  33. package/framework/.claude/skills/prd/references/prd-add-phase.md +18 -214
  34. package/framework/.claude/skills/prd/references/prd-writing-phase.md +52 -42
  35. package/framework/.claude/skills/prd/references/research-phase.md +105 -19
  36. package/framework/.claude/skills/prd/references/ui-design-phase.md +20 -8
  37. package/framework/.claude/skills/prd/references/validation-phase.md +97 -72
  38. package/framework/.claude/skills/prd-add/SKILL.md +70 -20
  39. package/framework/.claude/skills/simplify/SKILL.md +22 -12
  40. package/framework/.claude/skills/ui-design/SKILL.md +26 -7
  41. package/framework/.claude/skills/webapp-testing/SKILL.md +6 -4
  42. package/framework/.claude/skills/worktree-manager/SKILL.md +206 -143
  43. package/framework/agents/coding-standards.md +85 -0
  44. package/framework/agents/skills-mapping.md +85 -82
  45. package/framework/agents/testing.md +6 -4
  46. package/framework/templates/baldart.config.template.yml +29 -7
  47. package/package.json +1 -1
  48. package/src/commands/configure.js +43 -9
  49. package/framework/.claude/skills/prd-add/references/impact-analysis.md +0 -233
@@ -16,7 +16,7 @@ description: >
16
16
 
17
17
  ## Project Context
18
18
 
19
- **Reads from `baldart.config.yml`:** `paths.backlog_dir`, `paths.references_dir`.
19
+ **Reads from `baldart.config.yml`:** `paths.backlog_dir`, `paths.references_dir`, `git.trunk_branch` (integration trunk; default `develop`), `git.merge_strategy`, `paths.high_risk_modules` (risk-detector path list), `paths.metrics` (default `docs/metrics`), `stack.language` / `stack.database` / `stack.deployment` (gate guards), and `features.has_e2e_review` / `features.has_design_system` / `features.has_lsp_layer`.
20
20
  **Gated by features:** `features.has_backlog` (skill REFUSES to run when `false` — this orchestrator operates on backlog cards by definition).
21
21
  **Overlay:** loads `.baldart/overlays/new.md` if present — project-specific canonical-docs registry (e.g. ssot-registry, linking-protocol guides), tooling paths (e.g. validation scripts).
22
22
  **On missing/empty keys:** ask the user; do not assume defaults. See `framework/agents/project-context.md` § 3.
@@ -49,7 +49,7 @@ This file is your single source of truth — if your context gets compacted or y
49
49
 
50
50
  ### Tracking file format
51
51
 
52
- At batch start, create `/tmp/batch-tracker-<FIRST-CARD-ID>.md` with:
52
+ At batch start — **before Phase 0 runs** (Phase 0 step 1 writes to the `## Worktree` section and Phase 0 step 5 writes the phase log, so the file MUST already exist) — create `/tmp/batch-tracker-<FIRST-CARD-ID>.md` with:
53
53
 
54
54
  ```markdown
55
55
  # Batch Run: [CARD-IDS]
@@ -61,14 +61,17 @@ Started: [timestamp]
61
61
  Total cards: [N]
62
62
 
63
63
  ## Workspace Snapshot
64
- (none — Phase 0 will populate if `git stash` was used on the main repo)
64
+ (none — Phase 0 will populate if the main repo had uncommitted work; the stash entry, when present, is recorded by its MESSAGE label, never a positional `stash@{N}`)
65
65
 
66
66
  ## Worktree
67
67
  Branch: [feat/FEAT-XXXX-slug]
68
+ Slug: [the slug used to derive Branch/Path — see Pre-flight step 4]
68
69
  Path: [.worktrees/feat-FEAT-XXXX-slug]
69
70
  Port: [from registry]
71
+ Created: [ISO-8601 timestamp the worktree was created — Pre-flight step 5 backfills; Phase 8 cycle_time_mins start anchor]
70
72
  Group parent: [FEAT-XXXX or "standalone"]
71
73
  Main repo: [/absolute/path/to/main/repo]
74
+ Trunk branch: [resolved git.trunk_branch — Phase 0 step 0 populates]
72
75
 
73
76
  ## Card Queue
74
77
  - [ ] CARD-001 — [title from backlog]
@@ -109,7 +112,7 @@ Main repo: [/absolute/path/to/main/repo]
109
112
  - Test results (new + existing count, pass/fail)
110
113
  - Fix cycles count
111
114
  - UX testing result (PASS/FAIL/SKIP + test file path if written)
112
- - QA result (profile used: skip/light/balanced/deep | verdict: PASS/FAIL/SKIP | confidence % | findings: N blockers, N majors | E2E: PASS/FAIL/SKIP)
115
+ - QA result (profile used: skip/light/balanced/deep | verdict: PASS/FAIL/SKIP | confidence % | failing gates: which of lint/tsc/test/build/markdownlint, or "none")
113
116
  - QA findings file (e.g. `/qa/FEAT-XXXX.md` or "skipped")
114
117
  - **card_status: DONE (verified)** — confirms the backlog YAML was updated and re-read to verify
115
118
  - **When blocked**: log the blocker in `## Issues & Flags`.
@@ -125,7 +128,11 @@ Main repo: [/absolute/path/to/main/repo]
125
128
 
126
129
  **Steps:**
127
130
 
128
- 1. **Resolve `$MAIN`** the absolute path of the main repo (not a worktree). If `/new` was invoked from inside a worktree, walk up to the parent repo via `git rev-parse --show-superproject-working-tree` or `git worktree list` until you find the non-worktree root. Persist as `Main repo:` in the tracker `## Worktree` section.
131
+ 0. **Feature gate + config resolution (BLOCKING)** before anything else:
132
+ - Read `features.has_backlog` from `baldart.config.yml`. If it is `false` (or the key is absent and the user confirms there is no backlog), this skill REFUSES to run: print "`/new` operates on backlog cards and requires `features.has_backlog: true` — run `npx baldart configure` to enable." and HALT. Do NOT proceed to step 1.
133
+ - Resolve `$TRUNK` = `git.trunk_branch` from `baldart.config.yml` (default `develop` only when the key is absent). Persist it as `Trunk branch:` in the tracker `## Worktree` section. **Every later Phase 0 / Phase 6c bash snippet that references the integration trunk MUST use `$TRUNK`, never a baked-in `develop`.** Begin every later consumer with a guard: if `$TRUNK` is empty → HALT with "Trunk branch unresolved — re-read `git.trunk_branch` from config".
134
+
135
+ 1. **Resolve `$MAIN`** — the absolute path of the main repo (not a worktree). If `/new` was invoked from inside a worktree, walk up to the parent repo via `git rev-parse --show-superproject-working-tree` or `git worktree list` until you find the non-worktree root. Persist as `Main repo:` in the tracker `## Worktree` section. **Write `$MAIN` to the tracker the moment it is computed** — every later consumer (Phase 6c, Phase 6b) MUST re-read it from the tracker and HALT with "`$MAIN` absent from tracker" if the field is missing or empty, never silently use an undefined `$MAIN` (it does not survive context compaction).
129
136
 
130
137
  2. **Fetch remote state**:
131
138
  ```bash
@@ -139,41 +146,40 @@ Main repo: [/absolute/path/to/main/repo]
139
146
  If the output is non-empty, invoke `AskUserQuestion`:
140
147
  - Question: `"Main repo ($MAIN) ha modifiche non committate prima di partire con la batch. Come procedo?"`
141
148
  - Options (max 4):
142
- 1. **"Stash automatico (restore in Phase 6c)"** — run `git -C "$MAIN" stash push -u -m "baldart-new-phase0-<FIRST-CARD-ID>-<timestamp>"`, capture the returned stash ref (e.g. `stash@{0}`), and write it to the tracker under `## Workspace Snapshot: <ref> (created <timestamp>)`. Phase 6c will pop it.
149
+ 1. **"Stash automatico (restore in Phase 6c)"** — `$MAIN` is the main checkout (NOT a worktree), so a stash here is safe. Run `git -C "$MAIN" stash push -u -m "baldart-new-phase0-<FIRST-CARD-ID>-<timestamp>"` and write the **stash MESSAGE label** (`baldart-new-phase0-<FIRST-CARD-ID>-<timestamp>`) to the tracker under `## Workspace Snapshot: <message-label> (created <timestamp>)`. Phase 6c resolves the stash by this message — NEVER by a positional `stash@{N}`, because other stashes pushed during the batch (on the main checkout) would shift the index. Do NOT record `stash@{0}`.
143
150
  2. **"Committa adesso (apri editor)"** — halt the batch with a clear message asking the user to commit in their terminal and re-invoke `/new`. Do NOT proceed to step 4.
144
151
  3. **"Procedi senza stash (lavoro mio, lo gestisco io)"** — explicit user override. Log in tracker: `## Workspace Snapshot: dirty-tree override @ <timestamp> — user accepted responsibility`. Phase 6c will still check post-merge state but will NOT attempt restore.
145
152
  4. **"Abort batch"** — halt the batch, leave the tracker file in place, exit.
146
153
 
147
154
  Do NOT silently proceed. Do NOT auto-stash without the user picking option 1.
148
155
 
149
- 4. **Divergence gate (BLOCKING)** — compare local `develop` against `origin/develop`:
156
+ 4. **Divergence gate (BLOCKING)** — compare local `$TRUNK` against `origin/$TRUNK` (the trunk resolved in step 0):
150
157
  ```bash
151
- git -C "$MAIN" rev-list --left-right --count origin/develop...develop
158
+ git -C "$MAIN" rev-list --left-right --count "origin/$TRUNK...$TRUNK"
152
159
  ```
153
160
  The output is `<behind>\t<ahead>`. Branch on the result:
154
161
 
155
162
  - **`0\t0`** — synchronized. Proceed to step 5.
156
163
  - **`N\t0`** (only behind, N > 0) — invoke `AskUserQuestion`:
157
- - Question: `"Local develop è behind origin/develop di N commit. Come procedo?"`
164
+ - Question: `"Local $TRUNK è behind origin/$TRUNK di N commit. Come procedo?"`
158
165
  - Options: `[Fast-forward pull adesso]` / `[Procedi senza pull]` / `[Abort]`.
159
- - On `Fast-forward pull adesso`: if `$MAIN` HEAD == develop, run `git -C "$MAIN" pull --ff-only origin develop`; otherwise log "main HEAD is `<branch>` — fetch already updated `refs/remotes/origin/develop`, local `develop` ref will be reconciled in Phase 6c".
166
+ - On `Fast-forward pull adesso`: if `$MAIN` HEAD == `$TRUNK`, run `git -C "$MAIN" pull --ff-only origin "$TRUNK"`; otherwise log "main HEAD is `<branch>` — fetch already updated `refs/remotes/origin/$TRUNK`, local `$TRUNK` ref will be reconciled in Phase 6c".
160
167
  - **`0\tM`** (only ahead, M > 0 — UNPUSHED LOCAL COMMITS, this is the FEAT-0006 pattern) — invoke `AskUserQuestion`:
161
- - Question: `"Local develop è ahead di origin/develop di M commit (commit locali non pushati). Cosa faccio?"`
162
- - Show the commit list inline: `git -C "$MAIN" log --oneline origin/develop..develop`.
163
- - Options: `[Push adesso (git push origin develop)]` / `[Cherry-pick a parte e continua]` / `[Lascia così e procedi (lo gestisco io)]` / `[Abort]`.
168
+ - Question: `"Local $TRUNK è ahead di origin/$TRUNK di M commit (commit locali non pushati). Cosa faccio?"`
169
+ - Show the commit list inline: `git -C "$MAIN" log --oneline "origin/$TRUNK..$TRUNK"`.
170
+ - Options: `[Push adesso (git push origin $TRUNK)]` / `[Cherry-pick a parte e continua]` / `[Lascia così e procedi (lo gestisco io)]` / `[Abort]`.
164
171
  - **`N\tM`** (diverged both ways) — invoke `AskUserQuestion`:
165
- - Question: `"Local develop è diverged da origin/develop: N behind, M ahead. Servono entrambe le riconciliazioni. Come procedo?"`
166
- - Options: `[Rebase local develop su origin/develop]` / `[Mostrami i commit e fammi decidere]` / `[Abort]`.
167
-
168
- The same gate applies if the user-configured trunk branch is something other than `develop` — read `git.trunk_branch` from `baldart.config.yml` if present, default to `develop`.
172
+ - Question: `"Local $TRUNK è diverged da origin/$TRUNK: N behind, M ahead. Servono entrambe le riconciliazioni. Come procedo?"`
173
+ - Options: `[Rebase local $TRUNK su origin/$TRUNK]` / `[Mostrami i commit e fammi decidere]` / `[Abort]`.
169
174
 
170
175
  5. **Log and proceed** — write to the tracker:
171
176
  ```
172
177
  ## Phase 0 — Workspace Hygiene Pre-flight
173
178
  Status: PASS
174
179
  Main repo: $MAIN
175
- Dirty: <none | stashed: stash@{0} | user-override>
176
- Divergence (local…origin/develop): <0\t0 | resolved: ff-pull | resolved: pushed | user-deferred>
180
+ Trunk branch: $TRUNK
181
+ Dirty: <none | stashed: <message-label> | user-override>
182
+ Divergence (local…origin/$TRUNK): <0\t0 | resolved: ff-pull | resolved: pushed | user-deferred>
177
183
  Completed: <timestamp>
178
184
  ```
179
185
  If any step ended in HALT, set `Status: HALT` and stop — do NOT continue to `## Pre-flight (once)`.
@@ -205,14 +211,14 @@ Main repo: [/absolute/path/to/main/repo]
205
211
 
206
212
  **Decision logic:**
207
213
 
208
- 1. Read `execution_strategy.recommended_mode` from the epic parent card (if it exists). If present, use it as the default.
214
+ 1. Read `execution_strategy.recommended_mode` from the epic parent card (if it exists). If present, use it as the default. The `execution_strategy` block is authored by `prd-card-writer` with the canonical shape `groups:` (a list, each entry has `level:` (int) + `cards:` (list)) — consume that shape, not a flat `parallel_group` field.
209
215
  2. If no `execution_strategy` exists, compute:
210
216
  - Count total cards in batch
211
- - Count max cards in any single parallel group (from `parallel_group` field)
212
- - If any card lacks `parallel_group`, compute it on-the-fly: build dependency graph from `depends_on`/`blocks`, add conflict edges for cards sharing `(MODIFY)` files, compute topological layers via BFS.
217
+ - Count max cards in any single group (from `execution_strategy.groups[].cards`, i.e. cards sharing the same `level`)
218
+ - If no `execution_strategy.groups` exist, compute the levels on-the-fly: build dependency graph from `depends_on`/`blocks`, add conflict edges for cards sharing `(MODIFY)` files, compute topological layers via BFS — each topological layer becomes one group `level`.
213
219
  3. Apply threshold:
214
- - **Sequential mode** if ANY of: total cards <= 3 — OR all cards are in different parallel groups (max group size = 1, nothing to parallelize) — OR all cards form a linear dependency chain.
215
- - **Team mode** if: total cards > 3 AND at least one parallel group has 2+ cards.
220
+ - **Sequential mode** if ANY of: total cards <= 3 — OR all cards are in different groups (max group size = 1, nothing to parallelize) — OR all cards form a linear dependency chain.
221
+ - **Team mode** if: total cards > 3 AND at least one group has 2+ cards.
216
222
  4. Log decision in tracker:
217
223
  ```
218
224
  ## Execution Mode
@@ -227,20 +233,20 @@ Main repo: [/absolute/path/to/main/repo]
227
233
  ```
228
234
  Proceed without asking (the PRD already approved the strategy).
229
235
 
230
- When `mode == sequential`, the per-card pipeline below runs exactly as documented. The `parallel_group` field is simply ignored. When `mode == team`, skip the per-card pipeline and follow the **Team Mode** section at the end of this document.
236
+ When `mode == sequential`, the per-card pipeline below runs exactly as documented. The `execution_strategy.groups` levels are simply ignored. When `mode == team`, skip the per-card pipeline and follow the **Team Mode** section at the end of this document.
231
237
 
232
238
  3d. **Codex batch cross-card grounding check** (runs in background during worktree setup)
233
239
 
234
- > **Why**: GPT-5.4 reviews the full card batch for cross-card conflicts that per-card plan-auditor checks cannot detect (each plan-auditor sees only one card).
240
+ > **Why**: a non-Anthropic frontier model (Codex, via `codex-companion.mjs`) reviews the full card batch for cross-card conflicts that per-card plan-auditor checks cannot detect (each plan-auditor sees only one card).
235
241
 
236
242
  **Skip if**: batch has only 1 card (no cross-card conflicts possible).
237
243
 
238
244
  Launch via `Bash` with `run_in_background: true` and `timeout: 300000`:
239
245
 
240
246
  ```bash
241
- AUDIT_FILE="/tmp/codex-crosscard-$(date +%Y-%m-%d).md" && \
247
+ AUDIT_FILE="/tmp/codex-crosscard-<FIRST-CARD-ID>-<SESSION-ID>.md" && \
242
248
  CODEX_SCRIPT="$(ls -d ~/.claude/plugins/marketplaces/openai-codex/plugins/codex/scripts/codex-companion.mjs ~/.claude/plugins/cache/openai-codex/codex/*/scripts/codex-companion.mjs 2>/dev/null | sort -V | tail -1)" && \
243
- [ -z "$CODEX_SCRIPT" ] && echo "CODEX_NOT_FOUND" && exit 1; \
249
+ [ -z "$CODEX_SCRIPT" ] && echo "CODEX_NOT_FOUND" | tee "$AUDIT_FILE" && exit 1; \
244
250
  node "$CODEX_SCRIPT" task --wait "
245
251
  Cross-card grounding check for a batch of backlog cards about to be implemented together.
246
252
  Your job is to find conflicts BETWEEN cards that per-card reviewers would miss.
@@ -256,7 +262,7 @@ Main repo: [/absolute/path/to/main/repo]
256
262
  (e.g., Card A adds field X to interface Foo, Card B restructures Foo entirely)
257
263
  2. IMPLICIT DEPENDENCIES: Does Card A change a type/function that Card B's
258
264
  requirements assume stays unchanged? Flag missing depends_on.
259
- 3. EXECUTION ORDER RISKS: Given the parallel_group assignments, will parallel
265
+ 3. EXECUTION ORDER RISKS: Given the execution_strategy group/level assignments, will parallel
260
266
  execution cause merge conflicts or type errors?
261
267
  4. SHARED STATE MUTATIONS: Do multiple cards write to the same persistence
262
268
  {entity} ({collection|table|item} per `stack.database`) in ways that could
@@ -276,59 +282,61 @@ Main repo: [/absolute/path/to/main/repo]
276
282
  - `${CARD_PATHS}`: newline-separated list of all `- backlog/FEAT-XXXX-*.yml` paths in the batch
277
283
  - `${FILE_OWNERSHIP_MAP}`: the file-ownership map built in step 3b
278
284
 
285
+ > `<SESSION-ID>` is the current session id (e.g. `$CLAUDE_CODE_SESSION_ID`, or a run UUID if unset). The task-scoped unique name prevents two concurrent `/new` sessions on the same day from clobbering each other's findings (a date-only name silently collides). Record the exact `$AUDIT_FILE` path in the tracker under `## Cross-Card Conflicts (Codex)` so the result-handling step reads the SAME path — never re-derive it by wildcard/timestamp.
286
+
279
287
  **Result handling** (read before Phase 1 of first card):
280
- - Read `/tmp/codex-crosscard-{YYYY-MM-DD}.md` after background command completes.
281
- - If **PASS** or file empty/missing: proceed normally.
288
+ - **Background completion barrier (BLOCKING):** record the background task id when you launch it; do NOT read `$AUDIT_FILE` until that task has completed. Because this gate's verdict steers Phase-1 card ordering, the first card MUST NOT start while the check is in flight.
289
+ - **TIMED_OUT branch:** the launch sets `timeout: 300000` (5 min). If the background task hits the timeout without producing a terminal result (the tail of `$AUDIT_FILE` shows no `PASS` / conflict block / `CODEX_NOT_FOUND`), treat it as **TIMED_OUT** — do NOT leave the task "in-progress" and silently proceed. Log `cross-card-check: TIMED_OUT` in `## Cross-Card Conflicts (Codex)` and take the SAME fallback as `CODEX_NOT_FOUND`: spawn `code-reviewer` over the batch with the four conflict questions. Never start the batch with cross-card review in an unresolved/in-flight state.
290
+ - Read the exact `$AUDIT_FILE` path recorded in the tracker after the background command completes.
291
+ - If the file contains **`CODEX_NOT_FOUND`** (Codex unavailable): do NOT silently proceed — **fallback**: spawn the `code-reviewer` agent over the full batch's card YAMLs + file-ownership map with the same cross-card conflict questions (FILE_CONFLICT / IMPLICIT_DEP / ORDER_RISK / STATE_MUTATION). If `code-reviewer` is also unavailable, surface `CAPABILITY_UNAVAILABLE: cross-card-review` to the user and ask whether to proceed without cross-card detection or halt. Never run a multi-card batch with cross-card review silently skipped.
292
+ - If **PASS** or file empty: proceed normally.
282
293
  - If **conflicts found**: log in tracker under `## Cross-Card Conflicts (Codex)` and present to user. For each conflict:
283
294
  - `FILE_CONFLICT` / `ORDER_RISK` → force the conflicting cards sequential (update file-ownership map).
284
295
  - `IMPLICIT_DEP` → add `depends_on` entry to tracker notes (do NOT modify backlog YAML).
285
296
  - `STATE_MUTATION` → add warning to both cards' Phase 2 briefings.
286
297
 
287
298
  4. **Worktree setup** — delegate to the **worktree-manager** skill (`/nw` in programmatic mode):
288
- a. Pass all card IDs and their `group.parent` fields to the skill's grouping logic.
289
- b. The skill handles: grouping cards by `group.parent`, deriving branch names from `git_strategy.branch`, creating the worktree in `.worktrees/`, installing dependencies, copying env files, assigning a free port, and verifying the build.
290
- c. The skill updates `.worktrees/registry.json` with the worktree entry (including all card IDs in the `cards` field).
291
- d. If build fails the skill STOPs and reports. Do NOT continue.
292
- e. Record the worktree path, branch, and port from the skill's output in the tracker.
293
- 6. Create the tracking file `/tmp/batch-tracker-<FIRST-CARD-ID>.md` (include worktree path and branch name).
294
- 7. Create a task list to track progress across all cards.
299
+ a. **Resolve the `slug`** (required by the `/nw` programmatic API — `{ cards, groupParent, slug, branch? }`). Derive it deterministically: if the parent card (or the lead card) has `git_strategy.branch` set, parse the slug from it (`feat/<PARENT-ID>-<slug>` → `<slug>`); otherwise slugify the parent/lead card `title` (lowercase, non-alphanumerics → `-`, collapse repeats, trim, cap ~40 chars). Persist the resolved `slug` in the tracker `## Worktree` section so the path/branch are reproducible across compaction. Do NOT let `/nw` invent its own slug — pass it explicitly.
300
+ b. Pass all card IDs, their `group.parent` fields, AND the resolved `slug` to the skill's grouping logic.
301
+ c. The skill handles: grouping cards by `group.parent`, deriving branch names from `git_strategy.branch` (falling back to `feat/<PARENT-ID>-<slug>` using the slug you passed), creating the worktree in `.worktrees/`, installing dependencies, copying env files, assigning a free port, and verifying the build.
302
+ d. The skill updates `.worktrees/registry.json` with the worktree entry (including all card IDs in the `cards` field).
303
+ e. If build fails the skill STOPs and reports. Do NOT continue.
304
+ f. Record the worktree path, branch, slug, and port from the skill's output in the tracker.
305
+ 5. **Tracker already exists** the `/tmp/batch-tracker-<FIRST-CARD-ID>.md` file was created at batch start (before Phase 0, see "Context Tracking"). Here, backfill the `## Worktree` section with the worktree path, branch, slug, and port resolved in step 4, AND write `Created: <ISO-8601 now>` (`date -u +%Y-%m-%dT%H:%M:%SZ`) — this is the strategy-independent start anchor Phase 8's `cycle_time_mins` reads. Do NOT re-create the file (Phase 0 has already written to it).
306
+ 6. Create a task list to track progress across all cards.
295
307
 
296
308
  ---
297
309
 
298
310
  ## QA Profile Selector
299
311
 
300
- Before Phase 3.5, determine the review profile for each card.
312
+ Before Phase 3.5, determine the review profile for each card. **`/new` is a CONSUMER of `review_profile`, not the place where the decision criteria live.**
301
313
 
302
314
  **Primary path (deterministic — since v3.38.0): READ `review_profile` from the card YAML.**
303
- The PRD `prd-card-writer` computes this field at authoring time (`prd-card-writer.md §
304
- Rule C` — the SSOT) so the decision is made once, in the card, not re-interpreted here.
305
- If the card has a `review_profile` {`skip`, `light`, `balanced`, `deep`}, **use it
306
- verbatim** do NOT recompute. Log `profile=<x> (from card)` in the tracker.
307
-
308
- **Fallback path (legacy cards only): compute from the table below.** Cards authored before
309
- v3.38.0 (or manually without the field) have no `review_profile`. Only then, derive it using
310
- the **first matching rule** (priority order). Log `profile=<x> (computed review_profile absent)`.
311
-
312
- > ⚠️ This fallback table MUST stay in sync with `prd-card-writer.md § Rule C`, which is the
313
- > SSOT. If you edit the rules, edit both. The two differ only in *when* they run (authoring
314
- > time vs `/new` runtime), never in *what* they decide.
315
-
316
- | Profile | When to apply |
317
- |---------|--------------|
318
- | **SKIP** | Card type is `docs`, `chore`, or `config` — OR all changed paths are `.md`/`.yml` (non-API)/CSS with zero logic files OR title contains only cosmetic keywords (typo, rename, copy, wording, style) with no code areas |
319
- | **LIGHT** | Card type is `bugfix` OR `refactor` AND ≤3 files likely touched AND no HIGH-risk keywords in paths/areas/title. **NEVER applies to `feature` or `enhancement` cards.** |
320
- | **BALANCED** | Default for ALL `feature` / `enhancement` cards not matching DEEP rules — OR `bugfix`/`refactor` with >3 files or HIGH-risk keywords |
321
- | **DEEP** | ANY of: areas includes both `api` + `data` — OR paths/title contain `auth`, `payment`, `permission`, `schema`, `migration`, `cron`, `webhook`, `transaction` — OR >15 files likely touched — OR acceptance criteria count > 5 — OR DB indexes changed (`db_indexes` block present, or legacy `firestore_indexes`) — OR API contract changed — OR `data_fields` block has ≥3 entries with `status: new` or `status: modified` |
322
-
323
- **Critical**: `feature` and `enhancement` cards are ALWAYS BALANCED minimum — never LIGHT. The `files_likely_touched` field in YAML underestimates actual scope; ignore it for profile decisions on feature cards. When in doubt between BALANCED and DEEP, use DEEP.
315
+ The PRD `prd-card-writer` computes this field at authoring time the decision criteria
316
+ (how `skip`/`light`/`balanced`/`deep` is chosen, including the `>15-file deep` trigger and
317
+ the `feature`/`enhancement` balanced floor) are the **SSOT in `prd-card-writer.md § Rule C`**.
318
+ They are deliberately NOT reproduced here. If the card has a `review_profile` {`skip`,
319
+ `light`, `balanced`, `deep`}, **use it verbatim** — do NOT recompute. Log `profile=<x> (from card)`
320
+ in the tracker.
321
+
322
+ **Fallback path (legacy cards only — no `review_profile` field):** cards authored before v3.38.0
323
+ (or manually) lack the field. Only then, compute the profile by reading the decision criteria
324
+ from the SSOT: **`review_profile` is computed by prd-card-writer (Rule C); see
325
+ `framework/.claude/agents/prd-card-writer.md` for the criteria.** Apply those criteria here,
326
+ then log `profile=<x> (computed review_profile absent)`. There is no second copy of the
327
+ criteria in this file — keeping one authority avoids the drift that previously let the two tables
328
+ disagree.
329
+
330
+ > Note: the qa-sentinel agent keeps its OWN, different mapping (`profile SCOPED/FULL` test tier),
331
+ > which is legitimately qa-sentinel's; that is not the same thing as this profile-selection criteria.
324
332
 
325
333
  ---
326
334
 
327
335
  ## Agent Routing (Phase 2 dispatcher)
328
336
 
329
337
  Phase 2 reads the card's `owner_agent` field and dispatches to the matching
330
- specialist. The enum is defined by the PRD card writer
331
- (`framework/.claude/agents/prd.md:538` + `prd-card-writer.md § Agent
338
+ specialist. The enum's single source of truth is the **`owner_agent enum` block in
339
+ `framework/.claude/agents/REGISTRY.md`** (applied by `prd-card-writer.md § Agent
332
340
  Specialization Rules`) and validated by the PRD validation phase
333
341
  (`framework/.claude/skills/prd/references/validation-phase.md` Step 6 → 0).
334
342
 
@@ -339,15 +347,21 @@ Specialization Rules`) and validated by the PRD validation phase
339
347
  | `plan` | Fallback to `coder` + WARN | Standard coder briefing + log `"[ROUTER] plan-only briefing not yet implemented — using coder"` |
340
348
  | `visual-designer` | Fallback to `coder` + WARN | Standard coder briefing + same WARN format |
341
349
  | `motion-expert` | Fallback to `coder` + WARN | Standard coder briefing + same WARN format |
342
- | missing / `claude` / unknown | Fallback to `coder` + WARN | Standard coder briefing; for unknown value also log the literal value |
350
+ | missing / `""` / `claude` (legacy placeholders only) | Fallback to `coder` + WARN | Standard coder briefing + log `"[ROUTER] card <ID> has no valid owner_agent (got '<raw>'); defaulting to coder"` |
351
+ | any other unrecognized value (typo, unregistered agent) | **HALT + ask the user** | No briefing — the router stops and surfaces the literal value (see pseudocode step 6b) |
343
352
 
344
353
  The three `plan` / `visual-designer` / `motion-expert` stubs exist so the
345
354
  router never blocks a card; specialized briefings are tracked as a follow-up.
355
+ **Only the empty/missing/`claude` legacy placeholders fall back to `coder`** — a
356
+ genuinely unrecognized value (e.g. a typo `coder-agent`, or an agent not in the
357
+ enum) HALTs so a mis-routed card is never silently downgraded. The table and the
358
+ step-6b pseudocode are the same contract stated twice; keep them in lockstep.
346
359
 
347
- If you change which values are valid here, also update:
360
+ The valid values come from ONE place `REGISTRY.md`'s `owner_agent enum` block. Do not
361
+ re-list them here or maintain parallel copies; if the roster changes, edit the enum in
362
+ `REGISTRY.md` and these consumers read it:
348
363
 
349
- - `framework/.claude/agents/prd.md:538` (the canonical enum comment),
350
- - `framework/.claude/skills/prd/assets/card-template.yml` (the placeholder),
364
+ - `framework/.claude/skills/prd/assets/card-template.yml` (the placeholder cites the REGISTRY enum),
351
365
  - `framework/.claude/agents/prd-card-writer.md § Agent Specialization Rules`,
352
366
  - `framework/.claude/skills/prd/references/validation-phase.md` Step 6 → 0a,
353
367
  - `framework/.claude/agents/REGISTRY.md` decision-tree.
@@ -362,16 +376,19 @@ For each card, execute these phases in order:
362
376
 
363
377
  ### Phase 1 — Claim & Context
364
378
  1. **Update tracker**: set current card, phase = "1-claim".
365
- 2. Set the card status to `IN_PROGRESS` and assign yourself.
366
- 2b. **`depends_on` gate** — read the card's `depends_on` field. For each listed card ID:
379
+ 2. **`depends_on` gate (BEFORE claiming)** — read the card's `depends_on` field. For each listed card ID:
367
380
  - Read that card's backlog YAML and check its `status` field.
368
- - If NOT `DONE` → HALT: log in `## Issues & Flags` and ask the user: "Card <CARD-ID> depends on <DEP-ID> which is `<status>`. Proceed anyway, or wait?" Do not start implementation until the user responds explicitly.
369
- - If `DONE` → continue.
370
- 3. Update `${paths.references_dir}/project-status.md` Active Code Context (skip when the file does not exist in the project).
371
- 4. Invoke the **codebase-architect** agent (MUST per AGENTS.md) to understand the relevant codebase area, existing patterns, and architecture before any implementation. When `features.has_lsp_layer: true`, the architect uses LSP find-references for any identifier-shaped lookups inside the card (transitive via `agents/code-search-protocol.md`).
372
- 4b. **Plan-auditor grounding check**invoke the **plan-auditor** agent in QUICK mode with this prompt:
373
- ```
374
- Quick grounding check only (not a full audit). Verify this backlog card's requirements are grounded in the actual codebase.
381
+ - If NOT `DONE` → HALT: log in `## Issues & Flags` and ask the user: "Card <CARD-ID> depends on <DEP-ID> which is `<status>`. Proceed anyway, or wait?" Do not start implementation until the user responds explicitly. **The card status is NOT written until this gate passes** — so a HALT leaves the card untouched (no stale `IN_PROGRESS` for the next run to mis-read).
382
+ - If `DONE` (or the user chose "proceed anyway") → continue.
383
+ 2b. **Claim** only now set the card status to `IN_PROGRESS` and assign yourself.
384
+ 3. Invoke the **codebase-architect** agent (MUST per AGENTS.md) to understand the relevant codebase area, existing patterns, and architecture before any implementation. When `features.has_lsp_layer: true`, the architect uses LSP find-references for identifier-shaped lookups this needs NO handoff from the orchestrator: the architect reads `features.has_lsp_layer` from `baldart.config.yml` directly (the flag is ambient) per `agents/code-search-protocol.md`. The orchestrator does NOT propagate the flag. (Earlier doc versions numbered this step 4; the step that read project-status BEFORE the architect was removed because it persisted pre-analysis context — see step 3a.)
385
+ 3a. Update `${paths.references_dir}/project-status.md` Active Code Context (skip when the file does not exist in the project) do this AFTER the codebase-architect run (step 3) so the "Active Code Context" reflects the architect's findings (which files are actually in scope), not just the card YAML's `files_likely_touched`. Writing it before the architect run would persist pre-analysis claims that downstream agents (e.g. a parallel card) would then read as truth.
386
+ 4. **Plan-auditor grounding check** — invoke the **plan-auditor** agent in **QUICK mode** (pass `mode: QUICK`; its contract runs only the grounding checks, and because the codebase-architect findings are passed in below it does NOT re-spawn codebase-architect for this narrow check):
387
+ ```
388
+ mode: QUICK
389
+ Grounding check only (NOT a full audit — do not run the full AUDIT CHECKLIST A-H, and do not
390
+ re-invoke codebase-architect; the findings are pasted below). Verify this backlog card's
391
+ requirements are grounded in the actual codebase.
375
392
 
376
393
  Check:
377
394
  1. Do all paths in files_likely_touched actually exist?
@@ -383,14 +400,14 @@ For each card, execute these phases in order:
383
400
  [paste full card YAML]
384
401
 
385
402
  Codebase-architect findings:
386
- [paste key findings from step 4]
403
+ [paste key findings from step 3]
387
404
 
388
405
  Return: PASS | FIXES NEEDED (list exact corrections). No full audit report needed.
389
406
  ```
390
407
  - If **FIXES NEEDED**: apply corrections to the tracker notes for this card (do NOT modify the backlog YAML). Carry the corrected requirements into the Phase 2 briefing.
391
408
  - If **PASS**: proceed.
392
409
  5. **Update tracker**: phase = "1-claim DONE", log codebase-architect key findings (1-2 lines) and plan-auditor result (PASS or corrections applied).
393
- 5b. **Persist the architecture baseline for reuse (since v3.35.0)** — write the FULL codebase-architect findings (file paths, type signatures, patterns, high-risk paths) to `/tmp/arch-baseline-<CARD-ID>.md`. Phase 3.7's per-card `/codexreview` reuses this file instead of re-spawning `codebase-architect` (lean mode), so capture enough detail to ground a reviewer not just the 1-2 tracker lines.
410
+ 5b. **Persist the architecture baseline for reuse (since v3.35.0)** — write the **untruncated codebase-architect task result verbatim** (the full Task-tool output: file paths, type signatures, patterns, high-risk paths) to `/tmp/arch-baseline-<CARD-ID>.md`. This is NOT the 1-2-line tracker summary from step 5 — serialize the agent's complete output to disk BEFORE you summarize it for the tracker, so Phase 3.7's per-card `/codexreview` (lean mode, reuses this file instead of re-spawning `codebase-architect`) gets enough detail to ground a reviewer. If you summarize first and write the summary, the lean contract is defeated.
394
411
 
395
412
  ### Phase 2 — Implement (self-healing, up to 3 retries)
396
413
  6. **Update tracker**: phase = "2-implement".
@@ -398,6 +415,10 @@ For each card, execute these phases in order:
398
415
 
399
416
  ```
400
417
  Let raw = card.owner_agent (string, may be missing).
418
+ # Epic guard FIRST — an epic is a tracker, not implementable work.
419
+ If card is an epic (id ends "-00" OR filename ends "-epic.yml" OR review_profile == "skip"
420
+ with no requirements): SKIP — do NOT dispatch to any agent. Log
421
+ "[ROUTER] card <ID> is an epic (tracker) — skipped, not implemented" and move to the next card.
401
422
  Switch on raw:
402
423
  - "coder" → spawn = "coder", briefing = "coder"
403
424
  - "ui-expert" → spawn = "ui-expert", briefing = "ui-expert"
@@ -422,7 +443,7 @@ For each card, execute these phases in order:
422
443
 
423
444
  ### Card Specification (verbatim — do not paraphrase)
424
445
  Requirements:
425
- [copy full requirements list from card YAML, including any corrections from plan-auditor step 4b]
446
+ [copy full requirements list from card YAML, including any corrections from the plan-auditor grounding check (step 4)]
426
447
 
427
448
  Acceptance Criteria:
428
449
  [copy full acceptance_criteria list from card YAML]
@@ -571,9 +592,8 @@ For each card, execute these phases in order:
571
592
  ### Project Anti-Patterns & NFRs (ENFORCED)
572
593
  Violating these is a BLOCKER in code review.
573
594
 
574
- Deprecated patterns (from AGENTS.md / MEMORY.md adapt to your project):
575
- - List the project-specific anti-patterns documented in AGENTS.md / MEMORY.md
576
- - Example: deprecated permission shortcuts, deprecated client-state APIs, etc.
595
+ Deprecated patterns — the orchestrator MUST fill this from the project's own AGENTS.md / MEMORY.md / coding-standards.md before sending the briefing. If the project documents none, write "None documented." Do NOT pass placeholder example text to the coder (it wastes tokens and confuses the agent):
596
+ [resolved list of this project's deprecated/anti-patterns, or "None documented."]
577
597
 
578
598
  Performance MUST rules (adapt to your stack):
579
599
  - Bounded reads on every database query (`.limit()` / equivalent)
@@ -637,15 +657,15 @@ For each card, execute these phases in order:
637
657
  requirements:
638
658
  - id: 1
639
659
  text: "[verbatim requirement text]"
640
- status: done | partial | blocked
660
+ status: done | not_implemented
641
661
  evidence: "src/path/to/file.ts:LINE_NUMBER"
642
- notes: "[if partial or blocked: explain why]"
662
+ notes: "[if not_implemented: explain why]"
643
663
  - id: 2
644
664
  [repeat for each requirement]
645
665
  acceptance_criteria:
646
666
  - id: 1
647
667
  text: "[verbatim criterion text]"
648
- status: done | partial | blocked
668
+ status: done | not_implemented
649
669
  evidence: "src/path/to/file.ts:LINE_NUMBER"
650
670
  items_total: [N]
651
671
  items_done: [M]
@@ -657,31 +677,39 @@ For each card, execute these phases in order:
657
677
  notes: "[if B-missing: exact TODO location and text left; otherwise omit]"
658
678
  ```
659
679
 
680
+ Per-requirement / per-AC `status` is BINARY: `done` or `not_implemented` (the legacy
681
+ `partial`/`blocked` values are removed — see `coder.md`). The orchestrator re-derives the
682
+ finer Done/Partial/Missing classification itself in Phase 2.5 by reading the code; the coder
683
+ report is a starting signal, not ground truth.
660
684
  If `items_skipped` is not "none", you MUST explain why each was skipped.
661
- The orchestrator treats any skipped item as a gap requiring a fix agent.
685
+ The orchestrator treats any `not_implemented` or skipped item as a gap requiring a fix agent.
662
686
  `conditional_items` MUST list every binary-outcome item from the card. If the card has no
663
687
  conditional requirements, omit the field. `branch_taken: B-missing` MUST have a `notes`
664
688
  field with the exact file path and line where the TODO comment was written.
665
689
  ```
666
690
 
667
- 8. Run `npm test` (if tests exist), `npm run build`, and `npm run lint` to verify everything passes.
668
- 9. **If any check fails**: categorize the error (`lint | TypeScript | test | build`), log it in the tracker as `retry-cause: <category>`.
669
- **Stash recovery (MUST check before rewriting)**: before spawning a fix agent or rewriting code, check if a previous stash contains the needed changes:
691
+ 8. **Run the verification gates and CAPTURE their output** (so step 9 can pass it to a fix agent). Each is its own gate:
670
692
  ```bash
671
- git stash list
672
- ```
673
- If a named agent stash exists (e.g., `stash@{0}: On feat/...: agent-XXXXXXXXXX`), inspect it:
674
- ```bash
675
- git stash show -p stash@{0}
676
- ```
677
- If the stash contains edits that lint-staged removed (e.g., a field definition that was "unused" at commit time but is needed by subsequent code), recover with `git stash pop` instead of rewriting from scratch. Only spawn a fix agent if the stash does NOT contain the needed changes.
678
- When spawning a fix agent: scope it exclusively to this card's Edit-allowed files (from `## File Ownership Map`) — it MUST NOT touch files owned by other cards. Pass the fix agent: the error output, the error category, the explicit list of files it may edit, and the failing check output. Do NOT ask the user — just fix and re-run. Fix the code, not the tests (unless the test itself is wrong). Repeat up to **3 times**.
679
- 10. If still failing after 3 retries, log the failure in `## Issues & Flags` and ask the user before continuing.
693
+ cd <worktree-path>
694
+ npm run lint 2>&1 | tee /tmp/lint-<CARD-ID>.txt
695
+ # tsc is its OWN blocking gate run it ONLY when stack.language includes "typescript"
696
+ # (do NOT assume `npm run build` covers tsc — Next.js dev builds and many setups skip it):
697
+ npx tsc --noEmit 2>&1 | tee /tmp/tsc-<CARD-ID>.txt # guard: when stack.language includes "typescript"
698
+ npm test 2>&1 | tee /tmp/test-<CARD-ID>.txt # if tests exist
699
+ npm run build 2>&1 | tee /tmp/build-<CARD-ID>.txt
700
+ ```
701
+ When `stack.language` does NOT include `typescript`, skip the `tsc` line (no equivalent gate). Capturing to `/tmp/*-<CARD-ID>.txt` guarantees the failing output is available for step 9 — do NOT run the gates without capture.
702
+ 9. **If any check fails**: categorize the error (`lint | TypeScript | test | build`), log it in the tracker as `retry-cause: <category>`.
703
+ **Code-recovery check (MUST do before rewriting)**: before spawning a fix agent or rewriting code, check whether lint-staged or a pre-commit hook removed code that is actually needed (e.g. a field "unused" at commit time but consumed by later code). Inspect the captured diff and `git diff`/`git log -p` for the worktree branch and restore the needed code by an explicit file write. **Do NOT `git stash pop` inside the worktree** — `refs/stash` is globally shared across worktrees (`$GIT_COMMON_DIR`), so a stash created or popped in a worktree can corrupt another worktree's state (see Phase 4 WORKTREE COMMIT RULE). If you need to set work aside in a worktree, make a clearly-labelled WIP commit and record its hash in the tracker for later squash, never a stash.
704
+ When spawning a fix agent: scope it exclusively to this card's Edit-allowed files (from `## File Ownership Map`) — it MUST NOT touch files owned by other cards. Pass the fix agent: the error output (from the captured `/tmp/*-<CARD-ID>.txt` files), the error category, the explicit list of files it may edit, and the failing check output. Do NOT ask the user — just fix and re-run. Fix the code, not the tests (unless the test itself is wrong). Repeat up to **3 times**. **Stuck-loop guard**: compare the failing error's fingerprint (file:line + message) between retries; if the SAME error reproduces on 2 consecutive retries, the fix is not converging (often a constraint outside the card's ownership map) — stop early, log `[STUCK-LOOP] <error>` in `## Issues & Flags`, and escalate to the user rather than burning the 3rd retry on an identical failure.
705
+ 10. If still failing after 3 retries (or on a stuck loop), log the failure in `## Issues & Flags` and ask the user before continuing.
680
706
  11. **Update tracker**: phase = "2-implement DONE", log files changed (short list), retry count, retry causes (e.g., `"2 retries: TypeScript x1, lint x1"`), and test results (new + existing test count, pass/fail).
681
- 11b. **File diff gate** — verify the coder only touched its allowed files:
707
+ 11b. **File diff gate** — verify the coder only touched its allowed files. The coder commits its work (per `coder.md`), so a bare `git diff --name-only HEAD` is vacuous (clean tree). Detect what the card's commits actually changed against the trunk, with an uncommitted-fallback:
682
708
  ```bash
683
- cd <worktree-path> && git diff --name-only HEAD
709
+ cd <worktree-path>
710
+ git diff --name-only "$TRUNK...HEAD" 2>/dev/null || git diff --name-only HEAD~1..HEAD
684
711
  ```
712
+ (`$TRUNK` is the trunk branch resolved in Phase 0; if the worktree branched from a different base, the `HEAD~1..HEAD` fallback covers the last commit.)
685
713
  Compare against this card's allowed files in `## File Ownership Map`.
686
714
  - **All within allowed set** → proceed to Phase 2.5.
687
715
  - **Any file outside allowed set** → log the violation in `## Issues & Flags` (`"unauthorized file: <path>"`), then spawn a **targeted revert coder agent** with instruction: "Revert ONLY these files to their pre-commit state. Do not touch any other file: [list unauthorized files]". Re-run build + lint to confirm clean state after revert. Update tracker with revert outcome, then proceed to Phase 2.5.
@@ -705,6 +733,10 @@ Before triggering any review, you MUST verify that the coder agent implemented *
705
733
 
706
734
  If NEITHER branch artifact exists → classify as `Missing` (not Partial) and trigger the
707
735
  gap-resolution fix agent sub-loop immediately for these items (before continuing the checklist).
736
+ **Loop-counter scope**: the max-2 gap-resolution cap is tracked PER ITEM, not per phase. The
737
+ step-0 pre-scan and the step-5 main loop share the same per-item budget — an item that consumed
738
+ both retries here is NOT given two fresh retries at step 5; an item never touched here keeps its
739
+ full budget at step 5. This prevents both over-fixing (4 spawns) and starvation (0 retries left).
708
740
 
709
741
  Also check: if the card has conditional requirements but the completion report has no
710
742
  `conditional_items` section → flag as `Missing` (the coder must document the branch taken).
@@ -794,15 +826,17 @@ Before triggering any review, you MUST verify that the coder agent implemented *
794
826
  - The exact list of unimplemented items (copy the checklist rows)
795
827
  - The file-ownership restrictions from `## File Ownership Map`
796
828
  - The instruction: "Implement ONLY these missing items. Do not refactor or expand scope."
797
- - After the fix agent completes, re-run build + lint (`npm run build && npm run lint`).
829
+ - After the fix agent completes, re-run the static gates the fix could have broken — `npm run lint`, `npx tsc --noEmit` (when `stack.language` includes typescript), and `npm test` — not just build + lint (a gap-fix can introduce a type error or break a test that the earlier Phase 2 gate had passed).
798
830
  - Re-verify each fixed item against the code — do NOT trust the agent's self-report.
799
- - Repeat this sub-loop up to **2 times**. After 2 loops, if items remain Missing:
831
+ - Repeat this sub-loop up to **2 times** (per-item budget, shared with step 0 — see "Loop-counter scope"). After 2 loops, if items remain Partial or Missing:
800
832
  - Log in `## Issues & Flags`: list each unimplemented requirement.
801
833
  - Ask the user whether to proceed to review despite the gap or stop.
834
+ - **Handoff to Phase 2.5b**: any AC that is still `Partial` or `Missing` at this point (whether the user said "proceed" or not) MUST be seeded into the Phase 2.5b AC Closure Ledger as a `deferred` row with `User-approved? pending` — the user's "proceed to review" answer here does NOT auto-approve the deferral; Phase 2.5b Step 4 still asks per-AC. Do NOT let "proceed" silently mark these ACs `implemented`.
802
835
 
803
- 5b. **API contract check** (only for MODIFIED `route.ts` files — new routes are exempt):
804
- - From `git diff --name-only`, identify `route.ts` files that existed before this card.
805
- - For each modified route:
836
+ 5b. **API contract check**:
837
+ - From `git diff --name-only "$TRUNK...HEAD"`, identify `route.ts` files that existed before this card (MODIFIED) vs newly added (NEW).
838
+ - **NEW routes** are exempt from the breaking-change comparison (no prior contract to violate) BUT must still be checked for a **path collision**: read `${paths.references_dir}/api/index.md` and confirm the new route's URL path is not already documented under a different module. A collision → `[API-CONTRACT] PATH-COLLISION` in `## Issues & Flags` (BLOCKER — a second handler for a documented path).
839
+ - For each MODIFIED route:
806
840
  a. Derive the API module from path (e.g., `src/app/api/v1/booking/[id]/route.ts` → `booking`).
807
841
  b. Read `${paths.references_dir}/api/<module>.md` (or subdirectory match).
808
842
  c. Read the modified route, extract response shape from `NextResponse.json()` calls.
@@ -811,10 +845,10 @@ Before triggering any review, you MUST verify that the coder agent implemented *
811
845
  Added fields only → `[API-CONTRACT] ADDITIVE` (informational).
812
846
  No doc found → `[API-CONTRACT] NO-DOC` (informational).
813
847
 
814
- 5c. **Reference-aliasing mutation check** (MANDATORY — deterministic detector, BUG-0558 prevention).
848
+ 5c. **Reference-aliasing mutation check** (MANDATORY — deterministic detector, reference-aliasing mutation prevention).
815
849
 
816
850
  Detect call sites that pair a helper invocation with in-place mutation of an input array
817
- without an identity guard. Anti-pattern (the BUG-0558 family):
851
+ without an identity guard. Anti-pattern (the reference-aliasing mutation family):
818
852
 
819
853
  ```ts
820
854
  const result = filterHelper(input, ...);
@@ -822,12 +856,14 @@ input.length = 0; // ← wipes both input AND result when result === in
822
856
  input.push(...result); // ← pushes zero elements (result was just emptied)
823
857
  ```
824
858
 
825
- Run this exact bash block in the worktree:
859
+ This detector targets TypeScript/JavaScript array-mutation patterns. **Guard: run it only when `stack.language` includes `typescript` or `javascript`.** For other languages it does not apply — log `[ALIAS-MUTATION] skipped — stack.language not ts/js` and continue (do NOT report a non-TS diff as "clean", which would imply the check ran).
860
+
861
+ Run this exact bash block in the worktree (`$TRUNK` = the trunk branch resolved in Phase 0):
826
862
 
827
863
  ```bash
828
864
  cd <worktree-path>
829
- CHANGED_TS="$(git diff --name-only develop...HEAD -- '*.ts' '*.tsx' 2>/dev/null \
830
- || git diff --name-only HEAD~1 -- '*.ts' '*.tsx')"
865
+ CHANGED_TS="$(git diff --name-only "$TRUNK...HEAD" -- '*.ts' '*.tsx' '*.js' '*.jsx' 2>/dev/null \
866
+ || git diff --name-only HEAD~1..HEAD -- '*.ts' '*.tsx' '*.js' '*.jsx')"
831
867
 
832
868
  ALIAS_HITS=()
833
869
  for f in $CHANGED_TS; do
@@ -862,27 +898,27 @@ For each hit, classify by reading 10 lines of context around the match:
862
898
 
863
899
  For each `[ALIAS-MUTATION] BLOCKER`:
864
900
 
865
- - Spawn a targeted fix `coder` agent with the exact file:line and instruction: "Add identity guard `if (result !== input) { ... }` around the in-place reset, OR clone the input with `[...input]` before passing it to the helper. Add a regression test on the caller pattern (see `tests/booking/apply-orphan-protection-reference.test.ts` as reference). Read `agents/coding-standards.md § Reference-Aliasing Mutation Patterns` before implementing."
901
+ - Spawn a targeted fix `coder` agent with the exact file:line and instruction: "Add identity guard `if (result !== input) { ... }` around the in-place reset, OR clone the input with `[...input]` before passing it to the helper. Add a regression test on the caller pattern, co-located with the helper's existing tests following this project's test convention (e.g. a `*-reference` negative-control test next to the helper's test file). Read `agents/coding-standards.md § Reference-Aliasing Mutation Patterns` before implementing."
866
902
  - Re-run the detector after the fix. The hit MUST classify as Guarded, Cloned, or helper-immutable. Repeat **max 2 times**.
867
903
  - If still un-guarded after 2 loops → log in `## Issues & Flags` and ask the user.
868
904
 
869
- Rationale: BUG-0558 (2026-05-11) shipped to develop and collapsed availability to zero options for any store with protected Solo-Combo tables. Cause: `applyOrphanProtection()` returns the same input reference on 3 of 4 paths; the call site did `options.length = 0; push(...result)` without an identity guard. The helper passed unit tests in isolation; the bug only manifested end-to-end. This detector closes that detection gap.
905
+ Rationale (reference-aliasing incident class): a helper that returns its same input reference on some code paths, combined with a call site that did `arr.length = 0; arr.push(...result)` without an identity guard, silently wiped the result. The helper passed unit tests in isolation; the bug only manifested end-to-end. This detector closes that detection gap. Full policy: `agents/coding-standards.md § Reference-Aliasing Mutation Patterns`.
870
906
 
871
907
  5d. **Caller-pattern test coverage check** (MANDATORY — when card introduces or modifies an exported helper consumed by 1+ caller with in-place mutation).
872
908
 
873
- If the card's diff includes BOTH (a) an `export function` / `export const` declaration in a `src/lib/**` file AND (b) at least one call site in another file that mutates an input array in place (detected via the bash block in 5c), verify that a test file exists that exercises the **caller pattern** — not only the helper in isolation.
909
+ If the card's diff includes BOTH (a) an `export function` / `export const` declaration in a shared-library file (under `${paths.lib}` if defined, else the project's lib/util dir) AND (b) at least one call site in another file that mutates an input array in place (detected via the bash block in 5c), verify that a test file exists that exercises the **caller pattern** — not only the helper in isolation.
874
910
 
875
911
  Required test characteristics:
876
912
 
877
- - At least one case asserts the post-call state of the input array (e.g. `assert.strictEqual(options.length, 3)` after the helper invocation).
878
- - At least one **negative-control case** documents the failure mode that would occur if the identity guard / defensive clone were removed (e.g. `tests/booking/apply-orphan-protection-reference.test.ts:182-205`).
913
+ - At least one case asserts the post-call state of the input array (e.g. `assert.strictEqual(arr.length, N)` after the helper invocation).
914
+ - At least one **negative-control case** documents the failure mode that would occur if the identity guard / defensive clone were removed.
879
915
 
880
916
  If the test is missing:
881
917
 
882
- - Spawn a targeted fix `coder` agent to write the test. The test MUST live in a file co-located with similar helpers (e.g. `tests/booking/<helper-name>-reference.test.ts`).
883
- - This is BLOCKING for Phase 4 commit when the helper is on a high-risk path (auth, payments, booking-availability, DORE primitives). On non-high-risk paths it's a HIGH-severity flag but the orchestrator may proceed and defer to `/codexreview`.
918
+ - Spawn a targeted fix `coder` agent to write the test. The test MUST live in a file co-located with similar helpers, following this project's test convention (do NOT invent a directory — mirror where the helper's existing tests live).
919
+ - This is BLOCKING for Phase 4 commit when the helper is on a high-risk path (a path listed in `paths.high_risk_modules` in `baldart.config.yml`, e.g. auth / payments / a shared scoring-or-ranking primitive). On non-high-risk paths it's a HIGH-severity flag but the orchestrator may proceed and defer to `/codexreview`.
884
920
 
885
- Rationale: BUG-0558's introducing card (FEAT-0850-02) had its tests deferred to the later E2E card (FEAT-0850-07). The helper passed in isolation; the caller pattern was never tested. A caller-pattern test at the helper-card level would have caught the bug 2 days earlier.
921
+ Rationale: in the originating incident, the helper's caller pattern was never tested (tests were deferred to a later E2E card); the helper passed in isolation while the caller-side aliasing bug shipped. A caller-pattern test at the helper-card level catches this class earlier.
886
922
 
887
923
  6. **Update tracker**: phase = "2.5-completeness DONE", log result (PASS / GAPS REMAIN + count).
888
924
 
@@ -921,9 +957,9 @@ This gate enforces `framework/agents/workflows.md § Scope Closure Discipline` a
921
957
  For each `deferred` row whose `User-approved?` is `pending`, invoke `AskUserQuestion` with:
922
958
  - Question: `"AC-<N> of <CARD-ID> non implementata. Testo verbatim: '<AC text>'. Motivazione dell'implementer: '<reason or 'nessuna — silent skip'>'. Come procedo?"`
923
959
  - Options (max 4):
924
- 1. **"Implementa adesso"** — spawn a targeted fix `coder` agent scoped to this card's MAY EDIT files (from `## File Ownership Map`) with the instruction "Implement ONLY AC-<N>: '<AC text>'. Do not refactor or expand scope." Re-run Phase 2.5 verification on the resulting diff. On second failure of the same AC, return to this gate (do NOT loop indefinitely).
960
+ 1. **"Implementa adesso"** — spawn a targeted fix `coder` agent scoped to this card's MAY EDIT files (from `## File Ownership Map`) with the instruction "Implement ONLY AC-<N>: '<AC text>'. Do not refactor or expand scope." Re-run Phase 2.5 verification on the resulting diff — **including the mandatory sub-steps 5b (API contract), 5c (alias-mutation), 5d (caller-pattern test)** if the fix touched a route, an exported helper, or a call site (a single-AC fix can still introduce these). **Hard cap: 2 attempts on the same AC.** On the 2nd failure, do NOT re-offer "Implementa adesso" — re-invoke `AskUserQuestion` with only options 2/3/4 (approve deferral / follow-up card / stop), so the loop cannot recur unbounded.
925
961
  2. **"Approva il deferral"** — record `[USER-APPROVED DEFERRAL] <today>: <user-supplied reason>` on its own line in the card's `implementation_notes`. Mark the row `User-approved? yes`.
926
- 3. **"Sposta su follow-up card"** — create a backlog stub at `${paths.backlog_dir}/<CARD-ID>-followup-AC<N>.yml` with the AC text + minimal context + `status: TODO`. Mark the row `User-approved? yes (follow-up: <new-card-id>)`. Do NOT proceed to Phase 2.55 until the follow-up file exists on disk.
962
+ 3. **"Sposta su follow-up card"** — create a backlog stub at `${paths.backlog_dir}/<CARD-ID>-followup-AC<N>.yml` with `status: TODO` AND the minimum fields the Pre-flight gate (step 1b) requires, so a future `/new` run can pick it up without halting: a non-empty `requirements` (≥1, derived from the AC), a non-empty `acceptance_criteria` (≥1, the verbatim AC text), and a non-empty `files_likely_touched` (≥1, carried from the parent card's ownership map for this AC). Mark the row `User-approved? yes (follow-up: <new-card-id>)`. Do NOT proceed to Phase 2.55 until the follow-up file exists on disk and passes the step-1b field check.
927
963
  4. **"Ferma il batch"** — halt the orchestrator, leave the worktree intact, log the reason in `## Issues & Flags`. Do NOT commit.
928
964
 
929
965
  Do NOT batch the question across multiple ACs — one `AskUserQuestion` per AC, so the user sees each AC's text in isolation. Issue the questions sequentially.
@@ -935,9 +971,9 @@ This gate enforces `framework/agents/workflows.md § Scope Closure Discipline` a
935
971
  - `covered by` / `already handles` / `redundant`
936
972
  - `skipped` / `dropped`
937
973
 
938
- For each match, verify the line carries the `[USER-APPROVED DEFERRAL]` prefix AND the corresponding AC row in the ledger has `User-approved? yes`. Any mismatch is a silent deferral that escaped Step 4 — trigger the Step 4 `AskUserQuestion` for the AC referenced (or for "the unspecified AC implied by this note" if no AC is named, asking the user to clarify which AC the note refers to).
974
+ When scanning a line, FIRST check for the `[USER-APPROVED DEFERRAL]` prefix; if present AND the corresponding AC row has `User-approved? yes`, the line is RESOLVED — do NOT re-match the rationalization keywords inside the user-supplied reason text (otherwise a freeform reason like "approve deferral: existing component already handles this" would re-flag an already-approved AC). Only lines WITHOUT the prefix (or whose AC row is not `yes`) are silent deferrals that escaped Step 4 — for those, trigger the Step 4 `AskUserQuestion` for the AC referenced (or for "the unspecified AC implied by this note" if no AC is named, asking the user to clarify which AC the note refers to). **If Step 5 triggers any new Step 4 question, fully resolve it (loop back into Step 4) BEFORE running Step 6** — do NOT write `2.5b-ac-closure DONE` while a triggered question is still open.
939
975
 
940
- 6. **Update tracker**: phase = "2.5b-ac-closure DONE". Persist the AC Closure Ledger AND the Rationalization Verification (if any) in the tracker. Log counts on one line:
976
+ 6. **Update tracker** (only after Steps 3–5 have fully settled, including any Step-5-triggered Step-4 question): phase = "2.5b-ac-closure DONE". Persist the AC Closure Ledger AND the Rationalization Verification (if any) in the tracker. Log counts on one line:
941
977
 
942
978
  ```
943
979
  ac-closure: implemented=N | user-approved deferrals=M | follow-up cards created=K | rationalization-verifications passed=L
@@ -982,13 +1018,13 @@ Every fix-application decision in Phases 2.55 / 3 / 3.5 / 3.7 appends one row to
982
1018
 
983
1019
  Some fix domains are **never** safe for inline orchestrator apply, regardless of size — the orchestrator MUST delegate every fix in these domains to the **domain-owning agent**, even when the patch is a one-liner. The owning agent is the one whose system prompt + project overlay encodes that domain's invariants; an orchestrator inline edit (or a fix routed to the wrong agent) routinely breaks them.
984
1020
 
985
- Enumerated tassativamente:
1021
+ Enumerated exhaustively:
986
1022
 
987
1023
  | Domain | Owning agent | Match rule |
988
1024
  |---|---|---|
989
1025
  | `doc` | **`doc-reviewer`** (write mode) | File path matching `*.md` under `${paths.references_dir}`, `${paths.prd_dir}`, project root `CHANGELOG.md`, or any `ssot-registry.md`. |
990
- | `security` | `coder` | File path matching the Phase 3.7 detector Triggers #2 (auth/permissions: `src/lib/auth/middleware.ts`, `src/lib/permissions.ts`, anything matching `withAuth`) or #3 (payments: `^src/lib/payments/`, `^src/app/api/v1/billing/`). Also any SQL migration whose content matches `CREATE POLICY|ALTER POLICY|DROP POLICY` (RLS policy mutations). |
991
- | `migration` | `coder` | File path matching `supabase/migrations/*.sql` (or `${paths.migrations_dir}/*.sql` if defined in `baldart.config.yml`). |
1026
+ | `security` | `coder` | File path matching any entry in `paths.high_risk_modules` (`baldart.config.yml`) the same auth/permission/payment-class paths the Phase 3.7 Step A detector reads. Also any SQL migration whose content matches `CREATE POLICY|ALTER POLICY|DROP POLICY` (RLS policy mutations). If `paths.high_risk_modules` is absent, the security match rule emits a one-line diagnostic and matches nothing (no hardcoded default). |
1027
+ | `migration` | `coder` | File path matching `${paths.migrations_dir}/*.sql` if `paths.migrations_dir` is defined in `baldart.config.yml`; otherwise the project's migrations dir per convention (`migrations/`, `db/migrate/`, `supabase/migrations/`, `prisma/migrations/`). |
992
1028
 
993
1029
  **Why `doc` is owned by `doc-reviewer`, not `coder` (since v3.40.0)** — the doc invariants the orchestrator must not break (freshness markers, linking protocol, frontmatter standard, tabular formatting, SSOT/registry coverage, dependency-topological order, SCIP/code refs) are encoded in the **`doc-reviewer`** system prompt, NOT the coder's. The coder is a code-oriented agent that lacks the doc-invariant contract — routing doc fixes to it is the wrong agent doing work the auditing agent already has full context for. The agent that *audits* the docs is also the agent that *fixes* them (`doc-reviewer.md` § Constraints: "WRITE missing docs directly. You are fully responsible — do not defer to other agents"). NEVER route a `doc`-domain fix to `coder`.
994
1030
 
@@ -1011,13 +1047,14 @@ After completeness is verified, clean up the implementation before it reaches re
1011
1047
  - **Efficiency agent** — flag unnecessary work (redundant computations, duplicate API calls, N+1), missed concurrency, hot-path bloat, recurring no-op updates without change-detection guards, TOCTOU existence checks, memory issues (unbounded structures, missing cleanup), overly broad operations.
1012
1048
 
1013
1049
  4. Aggregate findings from all three agents. For each finding:
1014
- - **Valid** → fix directly (no coder agent spawn apply edits inline).
1015
- - **False positive / not worth addressing** → skip silently, do not surface in user output.
1050
+ - **Valid AND in a Domain-Override domain** (the finding's target file matches the `doc`, `security`, or `migration` match rule in "Domain-Override Domains") do NOT apply inline. Delegate to the domain owner: `doc` → `doc-reviewer` (write mode), `security`/`migration` → `coder`. Even a one-line efficiency fix in `paths.high_risk_modules` or a migration file goes to the owning agent — the orchestrator lacks that domain's invariant contract.
1051
+ - **Valid AND not in a Domain-Override domain** → fix directly (apply edits inline).
1052
+ - **False positive / not worth addressing** → skip, BUT record it (see telemetry). If the skip rests on a "covered by X" / "redundant" / "not needed" rationalization (the same family the AC-Closure Gate guards against), do NOT discard silently — verify the rationale by reading `X`, and if it does not hold, treat the finding as valid.
1016
1053
 
1017
- **Telemetry (Fix Application Log)** — for EVERY finding (valid OR skipped) append one row to the tracker's `## Fix Application Log` section per the schema above. Use `domain=simplify-{reuse|quality|efficiency}` matching the originating agent. Valid: `decision=inline | applied_by=orchestrator | est_lines=<n> | finding=<1-line>`. Skipped: `decision=skipped | applied_by=- | est_lines=0 | reason=<false-positive|not-worth-addressing>`.
1054
+ **Telemetry (Fix Application Log)** — for EVERY finding (valid OR skipped) append one row to the tracker's `## Fix Application Log` section per the schema above. Use `domain=simplify-{reuse|quality|efficiency}` matching the originating agent. Include the `severity` trailing key. Inline: `decision=inline | applied_by=orchestrator | est_lines=<n> | severity=<HIGH|MEDIUM> | finding=<1-line>`. Delegated (domain-override): `decision=<coder|doc-reviewer> | applied_by=<coder|doc-reviewer> | est_lines=<n> | severity=<...> | finding=<1-line>`. Skipped: `decision=skipped | applied_by=- | est_lines=0 | reason=<false-positive|not-worth-addressing>`.
1018
1055
 
1019
1056
  5. After all fixes, run `npm run lint` and `npx tsc --noEmit` to confirm nothing broke.
1020
- If either fails, fix the regression (up to 2 retries).
1057
+ If either fails, fix the regression (up to **2 retries**). **If it still fails after 2 retries**: do NOT silently continue to Phase 2.6 with a broken tree — log the failure in `## Issues & Flags` as `[SIMPLIFY-REGRESSION]` and invoke `AskUserQuestion` (revert the simplify fixes / keep and have me fix manually / stop the card), mirroring the Phase 3.5 escalation.
1021
1058
 
1022
1059
  6. **Update tracker**: phase = "2.55-simplify DONE", log count of fixes applied (or "clean — 0 fixes").
1023
1060
  If any valid finding revealed a reusable pattern or common mistake, append 1-line to `## Lessons Learned`:
@@ -1041,8 +1078,8 @@ auto-skips on backend-only cards, and BLOCKS the commit on gating findings.
1041
1078
  | Condition | Action |
1042
1079
  |-----------|--------|
1043
1080
  | `features.has_e2e_review: false` in `baldart.config.yml` | **SKIP** — log `"e2e-review: SKIP (feature disabled — run `npx baldart configure` to enable)"`. Preserves backwards compatibility for pre-3.18 consumers. |
1044
- | Card has no `links.design` AND `git diff --name-only` shows no file under `${paths.app_dir}` AND no file under `${paths.components_primitives}` AND no `*.tsx` / `*.css` / `*.scss` / `*.svelte` / `*.vue` anywhere | **SKIP** — log `"e2e-review: SKIP (backend-only card)"`. |
1045
- | Card type is `docs`, `chore`, or `config` | **SKIP** — same log. |
1081
+ | Card has no `links.design` AND the card's diff (`git diff --name-only "$TRUNK...HEAD"` for this card's committed files, falling back to `HEAD~1..HEAD`) shows no file under `${paths.app_dir}` AND no file under `${paths.components_primitives}` AND no `*.tsx` / `*.css` / `*.scss` / `*.svelte` / `*.vue` anywhere | **SKIP** — log `"e2e-review: SKIP (backend-only card)"`. (Diff against the trunk, NOT a bare `HEAD` — the coder already committed, so a working-tree diff is empty and would false-skip.) |
1082
+ | Card type is `backend`, `api`, `db`, `infra`, `docs`, `chore`, or `config` | **SKIP** — same log. (This list mirrors `/e2e-review`'s OWN Pre-flight skip set so `/new` does not invoke the skill only to have it immediately return `skipped`. A card with no `type` field falls through to the diff-based backend-only check above — do NOT assume a missing type means skip.) |
1046
1083
 
1047
1084
  #### Invocation contract
1048
1085
 
@@ -1056,12 +1093,12 @@ When the gate above passes, the orchestrator invokes `/e2e-review` in
1056
1093
  {
1057
1094
  "card_id": "<CARD-ID>",
1058
1095
  "worktree_path": "<absolute worktree path>",
1059
- "mode": "programmatic",
1060
- "tolerance": "<features.e2e_review.fidelity_tolerance>",
1061
- "max_self_heal_iterations": "<features.e2e_review.max_self_heal_iterations>"
1096
+ "mode": "programmatic"
1062
1097
  }
1063
1098
  ```
1064
1099
 
1100
+ Do NOT pass `tolerance` / `max_self_heal_iterations` in the payload — `/e2e-review` reads those from `features.e2e_review.*` in `baldart.config.yml` itself (they are NOT parsed from the invocation payload). Passing them here would be dead keys that silently diverge from the config the skill actually uses.
1101
+
1065
1102
  The skill is responsible for: reading the card YAML + `test_plan`, deriving
1066
1103
  routes from the diff, walking the 4-level mockup cascade (Figma MCP →
1067
1104
  local image → compliance-only → skip), generating the Playwright spec via
@@ -1089,7 +1126,7 @@ When the gate above passes, the orchestrator invokes `/e2e-review` in
1089
1126
  | `"skipped"` | Log skip reason. Proceed to Phase 3. |
1090
1127
  | `"overridden"` | Log override + reason in `## Issues & Flags` as `[E2E-OVERRIDE] <reason>`. Proceed to Phase 3. |
1091
1128
  | `"blocked"` | **STOP the card**. Log findings in tracker + `## Issues & Flags` as `[E2E-BLOCKED] <count> gating findings (<categories>)`. Ask the user whether to (a) override with reason, (b) escalate (open follow-up card), or (c) abandon this card and continue the batch. Do NOT proceed to Phase 3 silently. |
1092
- | `"error"` | Log error in `## Issues & Flags`. Ask the user whether to retry, skip, or abandon. Do NOT proceed silently. |
1129
+ | `"error"` | Log error in `## Issues & Flags`. Ask the user whether to retry, skip, or abandon. Do NOT proceed silently. **Retry re-entry (defined path, capped):** on "retry", re-enter at step 1 of this Invocation contract — reset the `.baldart/e2e-review/<CARD-ID>/` state dir, re-spawn `/e2e-review` with the SAME payload, and re-parse. **Cap: 2 error-retries per card** (central repair cap, consistent with the other loops). On the 2nd `error`, do NOT re-offer "retry" — re-invoke `AskUserQuestion` with only skip/abandon, so the loop cannot recur unbounded. |
1093
1130
 
1094
1131
  #### Re-run trigger (after Phase 3.7 `/codexreview`)
1095
1132
 
@@ -1132,9 +1169,9 @@ skill's Phase 1 falls back to deriving Gherkin scenarios from
1132
1169
  > **Note**: Code review is NOT performed in this phase — it is handled by the **mandatory unconditional `/codexreview` gate in Phase 3.7**, which runs per-card BEFORE the Phase 4 commit. The post-batch `/codexreview` in the Final review remains as a final FULL-diff sweep over the entire batch (since v3.37.0 — the guaranteed full-depth merge gate). This Phase 3 focuses exclusively on documentation sync, which must happen per-card (tied to the specific commit).
1133
1170
 
1134
1171
  12. **Update tracker**: phase = "3-doc-review".
1135
- 13. Build a **Doc Sync Context** block:
1172
+ 13. Build a **Doc Sync Context** block. The coder committed in Phase 2, so detect the card's changed files against the trunk (a bare `HEAD` diff would be empty post-commit):
1136
1173
  ```bash
1137
- git diff --name-only HEAD
1174
+ git diff --name-only "$TRUNK...HEAD" 2>/dev/null || git diff --name-only HEAD~1..HEAD
1138
1175
  ```
1139
1176
  Use the output to build this block:
1140
1177
  ```
@@ -1151,10 +1188,10 @@ skill's Phase 1 falls back to deriving Gherkin scenarios from
1151
1188
  - [ ] Backlog card set to DONE? → ${paths.references_dir}/ssot-registry.md entry
1152
1189
  - [ ] New external dependency? → agents/architecture.md External Dependencies list
1153
1190
  - [ ] Card has `documentation_impact` field? → verify each listed doc is updated
1154
- - [ ] New `process.env.VAR` added? → entry in `${paths.references_dir}/env-vars.md` (nome, scope, required, feature/card, default)
1155
- - [ ] Last usage of `process.env.VAR` removed? → mark `status: deprecated` in `${paths.references_dir}/env-vars.md` con data
1156
- - [ ] Default value changed in `src/lib/env.ts`?aggiornare colonna Default in `${paths.references_dir}/env-vars.md`
1157
- - [ ] Card ha campo `env_vars` popolato? → verificare che ogni entry sia tracciata in `${paths.references_dir}/env-vars.md`
1191
+ - [ ] New `process.env.VAR` added? → entry in `${paths.references_dir}/env-vars.md` (name, scope, required, feature/card, default)
1192
+ - [ ] Last usage of `process.env.VAR` removed? → mark `status: deprecated` in `${paths.references_dir}/env-vars.md` with the date
1193
+ - [ ] Default value changed in the project's env module (e.g. `src/lib/env.ts`)?update the Default column in `${paths.references_dir}/env-vars.md`
1194
+ - [ ] Card has an `env_vars` field populated? → verify each entry is tracked in `${paths.references_dir}/env-vars.md`
1158
1195
 
1159
1196
  ### Related docs to check (per convention map):
1160
1197
  - [derive from FRESHNESS_MAP: src/app/api/** → ${paths.references_dir}/api/; src/lib/booking/** → booking.md etc.]
@@ -1182,27 +1219,30 @@ skill's Phase 1 falls back to deriving Gherkin scenarios from
1182
1219
  doc-reviewer does NOT fix itself — report it with the conflicting code location + the doc
1183
1220
  it violates, and the orchestrator routes it to the `security`/code fix path as appropriate.
1184
1221
  ```
1185
- Doc-reviewer applies all doc-domain fixes itself. The orchestrator does NOT spawn a coder for doc fixes (since v3.40.0 — `doc` is owned by `doc-reviewer`, see "Domain-Override Domains"). The only doc-reviewer output that leaves this phase unfixed is a doc-drift→bug finding rooted in code, which follows the normal code fix path.
1186
- 14. **Obsidian Corpus Dispatch**: Parse section H from doc-reviewer findings. If `Trigger: YES`, dispatch the `obsidian-sync` agent (`.claude/agents/obsidian-sync.md`) with the listed paths after the doc fixes are applied. If `Trigger: NO`, skip. This is non-blocking -- do not wait for obsidian-sync to complete before proceeding.
1187
- 15. **Telemetry** — after doc-reviewer returns, append one row per doc finding to `## Fix Application Log`: `3 | doc | est_lines=<n> | decision=doc-reviewer | applied_by=doc-reviewer | finding=<1-line>`. If 0 findings, append one row: `3 | doc | est_lines=0 | decision=skipped | applied_by=- | reason=no-findings`.
1188
- 16. Run `npm run lint` and `npx tsc --noEmit` to verify nothing broke. If any check fails, apply the self-healing retry loop (up to 3 times, no user prompt).
1189
- 17. **Update tracker**: phase = "3-doc-review DONE", log doc findings count, fixes applied.
1222
+ Doc-reviewer applies all doc-domain fixes itself. The orchestrator does NOT spawn a coder for doc fixes (since v3.40.0 — `doc` is owned by `doc-reviewer`, see "Domain-Override Domains"). The only doc-reviewer output that leaves this phase unfixed is a **doc-drift→bug finding rooted in CODE** (the implementation contradicts a documented contract). Route it explicitly: if the conflicting code file matches the `security` Domain-Override match rule (`paths.high_risk_modules`) → spawn `coder` with the finding now, in this phase (a security-class code fix is not deferrable to a `light` Phase 3.7); otherwise carry the finding into the Phase 3.7 `/codexreview` input as a known code-drift bug and let the Phase 3.7 fix sub-loop apply it. Either way, append a Fix Application Log row with `domain=codex-correctness` (NOT `doc`) so telemetry attributes it as a code fix. Do NOT leave it accumulating in the tracker with no fix owner.
1223
+ 14. **Knowledge-corpus sync (OPTIONAL only if the project ships a corpus-sync agent)**: There is NO shipped `obsidian-sync` agent — do NOT dispatch one (a hard dispatch to a non-existent subagent fails silently). Only when the project provides its own knowledge-corpus sync agent (declared in `.baldart/overlays/new.md`) AND doc-reviewer's findings indicate a corpus impact, invoke that agent with the listed paths after the doc fixes are applied. Otherwise skip with a one-line notice (`knowledge-corpus sync: skipped (no corpus-sync agent configured)`). Non-blocking either way.
1224
+ 15. **Telemetry** — after doc-reviewer returns, append one row per doc finding to `## Fix Application Log`: `3 | doc | est_lines=<n> | decision=doc-reviewer | applied_by=doc-reviewer | finding=<1-line>`. If 0 findings, append one row: `3 | doc | est_lines=0 | decision=skipped | applied_by=- | reason=no-findings`. **Phase-8 producer (named counter)** — ALSO record the per-card doc-gap counts as a structured line in `## Current Card` (carried into `## Completed Cards` at Phase 5): `doc_gaps: found=<N> fixed=<M>` where `N` = total doc findings doc-reviewer raised and `M` = those it applied. This is the single named producer for Phase 8's `doc_gaps_found` / `doc_gaps_fixed` fields — without it those fields have no upstream write and Phase 8 would hard-code zeros. (D.4a is the team-mode producer of the same counter — see Phase 7 § D.4a.)
1225
+ 16. Run `npm run lint` and `npx tsc --noEmit` (when `stack.language` includes typescript) to verify nothing broke. If doc-reviewer touched any source-adjacent file (a `.ts`/`.tsx` helper, a co-located doc export), also run `npm run build`. If any check fails, apply the self-healing retry loop (up to 3 times, no user prompt). **If still failing after 3 retries**: do NOT fall through silently to Phase 3.5 — log `[DOC-PHASE-REGRESSION]` in `## Issues & Flags` and invoke `AskUserQuestion` (revert the doc-phase edits that broke the build / keep and fix manually / stop the card).
1226
+ 17. **Telemetry for the step-16 self-heal** — if the retry loop spawned any fix (a code edit to recover from a doc-phase regression), append a Fix Application Log row for it AFTER the loop settles (the step-15 doc telemetry row was written before this loop ran, so it does not capture step-16 fixes). Then update tracker: phase = "3-doc-review DONE", log doc findings count, fixes applied.
1190
1227
  If doc-reviewer found a recurring gap, append 1-line to `## Lessons Learned`:
1191
1228
  `DOC: <pattern>`
1192
1229
 
1193
1230
  ### Phase 3.5 — QA Validation
1194
1231
 
1195
1232
  18. **Update tracker**: phase = "3.5-qa".
1196
- 19. **Select QA profile**: READ the card's `review_profile` field (use verbatim); only compute from the QA Profile Selector table above when the field is absent (legacy card). Log the chosen profile and its source (`from card` | `computed`) in the tracker (1 line).
1233
+ 19. **Select QA profile**: READ the card's `review_profile` field (use verbatim); only compute via the QA Profile Selector above (whose criteria SSOT is `prd-card-writer.md § Rule C`) when the field is absent (legacy card). Log the chosen profile and its source (`from card` | `computed`) in the tracker (1 line).
1197
1234
  20. **If profile is SKIP**: log "QA skipped — [reason]" in the tracker. Proceed to Phase 4.
1198
- 21. **If profile is LIGHT**: SKIP qa-sentinel Phase 2 step 8 already ran lint/tsc/test/build gates. Log "QA LIGHT skipped — gates already passed in Phase 2" in the tracker. Proceed to Phase 4.
1235
+ 21. **If profile is LIGHT**: SKIP qa-sentinel. Phase 2 step 8 ran lint/tsc/test/build, AND the two code-mutating phases that follow it (Phase 2.55 Simplify, Phase 3 doc-reviewer) each re-run lint + tsc on their post-mutation output (so the static checks cover the committed code, not just the pre-mutation code). Log "QA LIGHT skipped — lint/tsc/test/build passed in Phase 2 and lint/tsc re-ran post-mutation in 2.55/3" in the tracker. Proceed to Phase 4.
1199
1236
  22. **If profile is BALANCED or DEEP**: invoke the **`qa-sentinel`** agent (subagent_type: `qa-sentinel`) via Task tool with the following context:
1200
1237
 
1201
1238
  ```
1202
- Run QA on card <CARD-ID> at the tier dictated by the QA profile below (do NOT default to FULL).
1239
+ Run QA gates on card <CARD-ID> at the tier dictated by the QA profile below (do NOT default to FULL).
1240
+ You are a MECHANICAL GATE-RUNNER: run the gates, return a PASS/FAIL gate table + confidence.
1241
+ Do NOT read source files to analyze logic, do NOT emit a BLOCKER/MAJOR/MINOR severity taxonomy,
1242
+ do NOT do collateral-impact analysis, and do NOT write tests.
1203
1243
 
1204
1244
  Context:
1205
- - Worktree path: <worktree-path>
1245
+ - Worktree path: <worktree-path> — `cd` into it before running any gate
1206
1246
  - Branch: <branch-name>
1207
1247
  - Changed files: <list from implementation phase>
1208
1248
  - QA profile: [balanced | deep]
@@ -1211,26 +1251,24 @@ skill's Phase 1 falls back to deriving Gherkin scenarios from
1211
1251
  Only path-based risk drift (auth/permission/payment/schema/migration/API contract in the diff)
1212
1252
  may escalate balanced→FULL. File count never escalates.
1213
1253
 
1214
- E2E: deep `npx playwright test --reporter=list` (BLOCKING);
1215
- balanced `npx playwright test --grep "<changed area>" --reporter=list` (ADVISORY).
1216
- Also run gates (lint, tsc, build, markdownlint) as sanity check.
1254
+ E2E is NOT your job (run by the /e2e-review skill in Phase 2.6) — do NOT run Playwright.
1255
+ Run the gates: lint, tsc (when stack.language includes typescript), test (per tier), build, markdownlint.
1217
1256
  Do NOT verify acceptance criteria (Phase 2.5 already did this).
1218
- Do NOT analyze code for bugs/patterns (deferred to /codexreview post-batch).
1219
- Do NOT write recommendations or follow-up actions.
1257
+ Do NOT analyze code for bugs/patterns (the per-card /codexreview gate in Phase 3.7 does this).
1220
1258
 
1221
1259
  Write the gate results + verdict to: /qa/<CARD-ID>.md
1222
1260
  Report should be under 40 lines.
1223
- Return verdict: PASS or FAIL.
1261
+ Return verdict: PASS or FAIL (+ the per-gate table).
1224
1262
  ```
1225
1263
 
1226
- 23. **Read qa-sentinel's output.** Verify the findings file was written to `/qa/<CARD-ID>.md`.
1227
- 24. **If QA verdict is FAIL**:
1228
- - Spawn the **coder** agent to fix all BLOCKER findings (pass it the findings file path + list of blockers). Do NOT ask the user.
1264
+ 23. **Read qa-sentinel's output.** Verify the findings file was written to `/qa/<CARD-ID>.md`. **If the file is absent** (qa-sentinel returned a verdict but did not write it): re-invoke qa-sentinel once asking it to (re-)write the file; if still absent, treat the verdict from the return message as authoritative, log `[QA] findings file missing — used return verdict` in `## Issues & Flags`, and pass the captured gate output (not a missing path) to any fix coder.
1265
+ 24. **If QA verdict is FAIL** (one or more gates failed):
1266
+ - Spawn the **coder** agent to fix the FAILING GATES (pass it the findings file path + the failing-gate output — NOT a severity-ranked finding list; qa-sentinel does not emit one). Do NOT ask the user.
1229
1267
  - After coder fixes, re-invoke `qa-sentinel` in the same mode to re-validate. Repeat up to **2 times**.
1230
1268
  - If still FAIL after 2 retries: log in `## Issues & Flags` and **ask the user** whether to proceed or stop.
1231
1269
  - The commit in Phase 4 MUST NOT happen until QA verdict is PASS (or user explicitly overrides).
1232
- - **Telemetry** — after each coder spawn, append one row per blocker to `## Fix Application Log`: `3.5 | qa-blocker | est_lines=<n> | decision=coder | applied_by=coder | retry=<0|1|2>`. After PASS without any spawn (initial verdict PASS), append one row: `3.5 | qa-none | est_lines=0 | decision=skipped | applied_by=- | reason=qa-pass`.
1233
- 25. **Update tracker**: phase = "3.5-qa DONE", log: profile used, tier run (SCOPED/FULL) + any risk-drift escalation, verdict (PASS/FAIL/SKIP), confidence %, findings count (blockers/majors/minors), findings file path.
1270
+ - **Telemetry** — after each coder spawn, append one row per failing-gate fix to `## Fix Application Log`: `3.5 | qa-blocker | est_lines=<n> | decision=coder | applied_by=coder | retry=<0|1|2>`. After PASS without any spawn (initial verdict PASS), append one row: `3.5 | qa-none | est_lines=0 | decision=skipped | applied_by=- | reason=qa-pass`.
1271
+ 25. **Update tracker**: phase = "3.5-qa DONE", log: profile used, tier run (SCOPED/FULL) + any risk-drift escalation, verdict (PASS/FAIL/SKIP), confidence %, failing gates (which of lint/tsc/test/build/markdownlint), findings file path. (qa-sentinel returns a per-gate PASS/FAIL table, not a blockers/majors/minors split — do not invent one.) **Phase-8 producer (named flag)** — ALSO record `qa_first_attempt: <pass|fail>` on the card's `## Completed Cards` QA line: `pass` ⟺ the FIRST qa-sentinel invocation (step 22, retry=0) returned PASS with no step-24 coder spawn; `fail` ⟺ a retry was needed (or the profile reached PASS only after a fix). For `SKIP`/`LIGHT` cards (qa-sentinel not run) record `qa_first_attempt: n/a`. This is the single named producer for Phase 8's `qa_pass_first_attempt_rate` — the tracker otherwise logs only the final verdict, so the first-attempt signal must be tagged here at the source.
1234
1272
 
1235
1273
  ### Phase 3.7 — Pre-Merge Codex Review Gate (MANDATORY — UNCONDITIONAL)
1236
1274
 
@@ -1242,46 +1280,47 @@ skill's Phase 1 falls back to deriving Gherkin scenarios from
1242
1280
 
1243
1281
  Run this exact bash block in the worktree. It is deterministic (grep + path match), not LLM-discretionary.
1244
1282
 
1283
+ `$TRUNK` is the trunk branch resolved in Phase 0 (`git.trunk_branch`, default `develop`). `$HIGH_RISK_RE` is an alternation built from `paths.high_risk_modules` in `baldart.config.yml` (e.g. `path1|path2|withAuth`). **If `paths.high_risk_modules` is absent, the path-based triggers (#1) emit a one-line diagnostic and match nothing — no hardcoded project path is baked in.**
1284
+
1245
1285
  ```bash
1246
1286
  cd <worktree-path>
1247
1287
  CARD_ID="<CARD-ID>"
1248
- CARD_FILE="$(find ../../backlog -name "${CARD_ID}*.yml" 2>/dev/null | head -1)"
1249
- CHANGED="$(git diff --name-only develop...HEAD 2>/dev/null || git diff --name-only HEAD~1)"
1288
+ CARD_FILE="$(find "${paths.backlog_dir:-../../backlog}" -name "${CARD_ID}*.yml" 2>/dev/null | head -1)"
1289
+ # Detect what THIS card's commits changed against the trunk (post-commit), with an uncommitted fallback:
1290
+ CHANGED="$(git diff --name-only "$TRUNK...HEAD" 2>/dev/null || git diff --name-only HEAD~1..HEAD)"
1291
+ # High-risk path alternation from config (empty when paths.high_risk_modules is unset):
1292
+ HIGH_RISK_RE="<alternation of paths.high_risk_modules entries, or empty>"
1250
1293
 
1251
1294
  TRIGGERS=()
1252
1295
 
1253
- # Trigger #1 — DORE shared scoring/ranking primitive
1254
- echo "$CHANGED" | grep -qE 'src/lib/booking/dore/(engine|reranking)\.ts$' \
1255
- && TRIGGERS+=("#1: DORE shared scoring/ranking primitive")
1256
-
1257
- # Trigger #2 Auth / permissions
1258
- echo "$CHANGED" | grep -qE 'src/lib/auth/middleware\.ts$|src/lib/permissions\.ts$|withAuth' \
1259
- && TRIGGERS+=("#2: Auth/permissions")
1260
-
1261
- # Trigger #3 — Payment / billing
1262
- echo "$CHANGED" | grep -qE '^src/lib/payments/|^src/app/api/v1/billing/' \
1263
- && TRIGGERS+=("#3: Payment/billing")
1264
-
1265
- # Trigger #4 — Dead-code resurrection (card text + commit messages)
1266
- { cat "$CARD_FILE" 2>/dev/null; git log --format=%B develop...HEAD 2>/dev/null; } \
1296
+ # Trigger #1 — High-risk module (auth / permission / payment / shared scoring-or-ranking primitive)
1297
+ # Sourced from paths.high_risk_modules NOT a hardcoded project path.
1298
+ if [ -n "$HIGH_RISK_RE" ]; then
1299
+ echo "$CHANGED" | grep -qE "$HIGH_RISK_RE" \
1300
+ && TRIGGERS+=("#1: high-risk module (paths.high_risk_modules)")
1301
+ else
1302
+ echo "[DETECTOR] paths.high_risk_modules absent — path-based high-risk trigger skipped" >&2
1303
+ fi
1304
+
1305
+ # Trigger #2 Dead-code resurrection (card text + commit messages)
1306
+ { cat "$CARD_FILE" 2>/dev/null; git log --format=%B "$TRUNK...HEAD" 2>/dev/null; } \
1267
1307
  | grep -qiE 'dead code|unreachable|resurrect' \
1268
- && TRIGGERS+=("#4: Dead-code resurrection")
1269
-
1270
- # Trigger #5Cross-card delta-baseline arithmetic
1271
- { cat "$CARD_FILE" 2>/dev/null; echo "$CHANGED" | xargs -I{} git show "HEAD:{}" 2>/dev/null; } \
1272
- | grep -qE 'score_new\s*-\s*score_current|delta-baseline|deltaBaseline' \
1273
- && TRIGGERS+=("#5: Cross-card delta-baseline arithmetic")
1274
-
1275
- # Trigger #6 — Reference-aliasing mutation pattern (BUG-0558 family)
1276
- # Card introduces or modifies an exported helper that returns an array/object AND a
1277
- # call site in the diff mutates an input array in place via `length = 0` + `push(...result)`.
1278
- # Even if the call site has an identity guard locally, the high-risk gate runs Codex
1279
- # adversarial review to confirm the contract is robust against future caller patterns.
1280
- echo "$CHANGED" | xargs -I{} sh -c 'git show "HEAD:{}" 2>/dev/null' \
1281
- | grep -qE '\.length\s*=\s*0' \
1282
- && echo "$CHANGED" | xargs -I{} sh -c 'git show "HEAD:{}" 2>/dev/null' \
1283
- | grep -qE '\.push\(\s*\.\.\.' \
1284
- && TRIGGERS+=("#6: Reference-aliasing mutation pattern (BUG-0558 family)")
1308
+ && TRIGGERS+=("#2: Dead-code resurrection")
1309
+
1310
+ # Trigger #3Reference-aliasing mutation pattern (in-place array reset + push)
1311
+ # Card introduces or modifies an exported helper AND a call site in the diff mutates an input
1312
+ # array in place via `length = 0` + `push(...result)`. Even with a local identity guard, the
1313
+ # high-risk gate runs adversarial review to confirm the contract is robust against future callers.
1314
+ # Iterate file-by-file safely (no unquoted xargs filename expansion):
1315
+ RESET=""; PUSH=""
1316
+ while IFS= read -r f; do
1317
+ [ -n "$f" ] || continue
1318
+ C="$(git show "HEAD:$f" 2>/dev/null)" || continue
1319
+ printf '%s' "$C" | grep -qE '\.length\s*=\s*0' && RESET=1
1320
+ printf '%s' "$C" | grep -qE '\.push\(\s*\.\.\.' && PUSH=1
1321
+ done <<< "$CHANGED"
1322
+ [ -n "$RESET" ] && [ -n "$PUSH" ] \
1323
+ && TRIGGERS+=("#3: Reference-aliasing mutation pattern")
1285
1324
 
1286
1325
  printf '%s\n' "${TRIGGERS[@]}"
1287
1326
  ```
@@ -1335,8 +1374,9 @@ For EVERY card (no conditional skip — the gate ALWAYS runs; only its DEPTH var
1335
1374
  Then dump the card diff (reviewer grounding) and write the lean contract `/codexreview` consumes:
1336
1375
  ```bash
1337
1376
  cd <worktree-path>
1338
- git diff develop...HEAD -- <card files from ownership map> > /tmp/diff-<CARD-ID>.txt 2>/dev/null \
1339
- || git diff HEAD~1 > /tmp/diff-<CARD-ID>.txt
1377
+ # $TRUNK = the trunk branch resolved in Phase 0 (git.trunk_branch)
1378
+ git diff "$TRUNK...HEAD" -- <card files from ownership map> > /tmp/diff-<CARD-ID>.txt 2>/dev/null \
1379
+ || git diff HEAD~1..HEAD > /tmp/diff-<CARD-ID>.txt
1340
1380
  ```
1341
1381
  Write `/tmp/codexreview-lean-<CARD-ID>.json`:
1342
1382
  ```json
@@ -1369,26 +1409,33 @@ For EVERY card (no conditional skip — the gate ALWAYS runs; only its DEPTH var
1369
1409
  (since v3.38.0). `code-reviewer` + Step 3 FP-gate + Step 4 report ALWAYS run. (`full` = standard
1370
1410
  pipeline incl. Codex adversarial, minus the duplicate doc-reviewer.)
1371
1411
 
1372
- 3. **Read the consolidated report** from `/tmp/codexreview-report-<TIMESTAMP>.md`. Extract:
1412
+ 3. **Read the consolidated report**. `/codexreview` writes it to `/tmp/codexreview-report-<TIMESTAMP>.md` and RETURNS that exact path in its Skill-tool result — use the returned path, do NOT guess the timestamp. If the return value did not surface the path, locate the newest report whose body names this `<CARD-ID>` (`grep -l "<CARD-ID>" /tmp/codexreview-report-*.md | xargs ls -t | head -1`), never just the newest file by mtime (a parallel card's report could be newer). Extract:
1373
1413
  - Verified BLOCKER count
1374
1414
  - Verified HIGH count
1375
- - List of `[ripple-expanded]` findings (Step 3.5 widened scope)
1415
+ - List of `[ripple-expanded]` findings (Step 3.5 widened scope). At `light` profile Step 3.5 CoVe does not run, so this list is structurally empty — log `ripple-expanded: 0 (CoVe not run at light)` rather than a bare `0`, so audits can tell "not attempted" apart from "attempted, none found".
1376
1416
 
1377
1417
  4. **Apply fix sub-loop** (mirror of Phase 3.5 retry pattern):
1378
- - If 0 BLOCKER and 0 HIGH → log `verdict: PASS — proceeding to Phase 4` in tracker. Done.
1379
- - If 1+ BLOCKER OR 1+ HIGH → spawn `coder` agent with the report path + list of VERIFIED bugs **+ the patch(es) suggested by Codex inline in the report**. The coder applies the suggested patches verbatim**does NOT re-do the analysis, does NOT re-grep, does NOT re-read files extensively** (since v3.28.3). Codex has already produced both the diagnosis and the patch; the coder is here to apply edits with the right system prompt (project conventions, naming, testing patterns), not to redo Codex's work. This saves 5-10k tokens of redundant security/correctness review per spawn. After coder fixes, re-invoke `/codexreview <CARD-ID>` to re-validate. Repeat **max 2 times**.
1380
- - If still BLOCKER/HIGH after 2 retries → log in `## Issues & Flags` and **ask the user** whether to proceed, escalate, or stop. The Phase 4 commit MUST NOT happen until High-Risk Gate verdict is PASS or user explicitly overrides.
1418
+ - If 0 BLOCKER and 0 HIGH → log `verdict: PASS — proceeding to Phase 4` in tracker. Done. (MEDIUM/LOW findings are advisory at this per-card gate; they are not silently lost — the post-batch **Final-review FULL gate** applies every VERIFIED finding ≥ MEDIUM. Log the MEDIUM count in the tracker so it is visible.)
1419
+ - If 1+ BLOCKER OR 1+ HIGH → spawn `coder` agent with the report path + list of VERIFIED bugs. **At `full` profile** the report contains Codex-suggested inline patches: pass them and have the coder **apply the suggested patches** with the right system prompt (project conventions, naming, testing patterns) it does NOT re-do the analysis or re-grep (since v3.28.3), BUT it MUST first confirm each patch still applies against the current file state (prior fix-loop iterations may have shifted line offsets); if a patch no longer applies cleanly, the coder re-locates the target by content and applies the equivalent edit rather than a stale-offset verbatim paste. **At `light` profile** no Codex adversarial patches exist (the pass was dropped) — the findings come from `code-reviewer`; brief the coder accordingly (apply the `code-reviewer` fix direction; there are no Codex patches to paste). After coder fixes, **re-write the lean contract `/tmp/codexreview-lean-<CARD-ID>.json` (it is consumed-once and deleted by `/codexreview`)** and re-invoke `/codexreview` via the Skill tool with `args: <CARD-ID>` (NOT a bare prose mention — the card ID MUST be passed so the retry reviews THIS card, not an inferred one). Repeat **max 2 times**.
1420
+ - If still BLOCKER/HIGH after 2 retries → log in `## Issues & Flags` and **ask the user** whether to proceed, escalate, or stop. The Phase 4 commit MUST NOT happen until the Pre-Merge Codex Review verdict is PASS or user explicitly overrides.
1381
1421
  - **Telemetry** — for EVERY codex finding processed (verified BLOCKER, verified HIGH, or false-positive-filtered), append one row to `## Fix Application Log`: `3.7 | codex-<security|correctness|other> | est_lines=<n> | decision=<coder|skipped> | applied_by=<coder|-> | severity=<BLOCKER|HIGH|FALSE-POSITIVE> | retry=<n>`. Classify domain: `security` for findings touching RLS / auth / permissions / payments; `correctness` for logic / data integrity / race conditions; `other` for everything else.
1382
1422
 
1383
- 5. **Update tracker**: phase = `3.7-highrisk DONE`, log final verdict, retry count, list of fixed findings, and the report path.
1423
+ 5. **Update tracker**: phase = `3.7-codexgate DONE` (the gate runs unconditionally for every card — the legacy `3.7-highrisk` name implied it only fired on high-risk cards, which is no longer true), log final verdict, retry count, list of fixed findings, and the report path.
1424
+
1425
+ 6. **E2E re-run obligation check (enforces the Phase 2.6 re-run trigger HERE)** — if this Phase 3.7 fix sub-loop modified any file under `${paths.app_dir}`, `${paths.components_primitives}`, or any `*.css` / `*.scss`, you MUST re-invoke `/e2e-review` (programmatic mode, same `.baldart/e2e-review/<CARD-ID>/` state dir) BEFORE the Phase 4 commit and resolve its verdict per the Phase 2.6 gating-logic table. Log `e2e-rerun: triggered` or `e2e-rerun: not-needed (no UI files touched in 3.7)` in the tracker so Phase 4 can assert it. Do NOT defer this to "it's documented in Phase 2.6" — the check fires here, where the fixes happen.
1384
1426
 
1385
1427
  #### Why deterministic detector + agent pipeline
1386
1428
 
1387
1429
  The detector (Step A) is bash + grep — guaranteed to run, no LLM skip. The downstream pipeline (Step C) is the existing `/codexreview` skill — fully tested, includes Codex adversarial review and Step 3.5 cross-agent CoVe. This combination ensures the AGENTS.md "MUST run BEFORE merge" rule is enforced reliably.
1388
1430
 
1431
+ **Crash handling**: if `/codexreview` (or its fix-loop `coder`) crashes mid-phase, apply the "Sub-agent failure protocol" (below). Treat this gate as a **mandatory gate, NOT a non-domain-override fallback** — `/codexreview` covers the `security` domain (auth/payment/permission findings), so a crash that cannot be recovered by one retry STOPS the pipeline and routes to `AskUserQuestion`; do NOT rationalize it as "non-domain-override" and inline-bypass the merge gate.
1432
+
1389
1433
  ### Phase 4 — Commit (in worktree, NO merge yet)
1390
- 25. **Update tracker**: phase = "4-commit".
1391
- 26. Stage and commit **all changes together** in the worktree using format `[CARD-ID] Brief description` (MUST per AGENTS.md). Include all relevant files implementation, review fixes, QA-driven fixes, and doc updates in a single commit. Do NOT merge or push yet — that happens post-batch.
1434
+
1435
+ > Sequential-mode global step numbering resumes here at 26 (Phase 3.5 ended at 25; Phase 3.7 used its own local C.0–C.6 counter). The tracker phase-string `4-commit` therefore maps to step 26, NOT a second step 25.
1436
+
1437
+ 26. **Update tracker**: phase = "4-commit". **Entry assertion** — before committing, verify the Phase 3.7 e2e re-run obligation was honored: read the tracker for `e2e-rerun: triggered` / `e2e-rerun: not-needed`. If Phase 3.7 touched UI files but no `e2e-rerun` entry exists, do NOT commit yet — go run the re-run per Phase 3.7 step 6 first. Also confirm Phase 3.5/3.7 fixes did not leave lint/tsc broken: if the Phase 3.7 fix sub-loop applied any patch, run `npm run lint` + `npx tsc --noEmit` (when typescript) once before committing.
1438
+ 27. Stage and commit **all changes together** in the worktree using format `[CARD-ID] Brief description` (MUST per AGENTS.md). Include all relevant files — implementation, review fixes, QA-driven fixes, and doc updates in a single commit. Do NOT merge or push yet — that happens post-batch.
1392
1439
  - **IMPORTANT — explicit staging**: NEVER use `git add -A` or `git add .`. Always stage files by explicit name:
1393
1440
  ```bash
1394
1441
  cd <worktree-path>
@@ -1401,20 +1448,22 @@ The detector (Step A) is bash + grep — guaranteed to run, no LLM skip. The dow
1401
1448
  git commit -m "[CARD-ID] Brief description"
1402
1449
  ```
1403
1450
  - **WORKTREE COMMIT RULE — NEVER use `git stash` in worktrees.** Stashes are globally shared across all worktrees (`refs/stash` via `git.commondir`). A stash created in one worktree can be popped by another, causing conflicts, data loss, and cascading merge failures. The file ownership map ensures no overlap between cards — explicit staging is sufficient isolation. The stash pattern in AGENTS.md applies ONLY to the main repo working tree.
1404
- - **If commit fails** (e.g., lint-staged or pre-commit hook error):
1451
+ - **If commit fails**:
1452
+ 0. **"Nothing to commit" is NOT a failure** — if the error is `nothing to commit, working tree clean`, all changes were already committed by a prior step (e.g. doc-reviewer committed its own docs, or a Phase 3.7 fix was already committed). Treat the card commit as already done, record the existing HEAD hash, and proceed to step 28 (Mark card DONE) — do NOT enter the COMMIT_LOCK / re-stage loop.
1405
1453
  1. **Check for stale COMMIT_LOCK first** — parallel agents and crashed processes leave stale lock files. Always clear before retry:
1406
1454
  ```bash
1407
1455
  rm -f <main-repo>/.git/worktrees/<worktree-name>/COMMIT_LOCK 2>/dev/null
1408
1456
  ```
1409
- 2. Re-stage the same files explicitly and retry. Never run `git commit` alone after a failure — the staging area may have been altered by lint-staged auto-fixes. If lint-staged removed an "unused" variable that IS used by code in a later card, check `git diff` to see what changed, restore the needed code, re-stage, and retry.
1457
+ 2. Re-stage the same files explicitly and retry. Never run `git commit` alone after a failure — the staging area may have been altered by lint-staged auto-fixes. If lint-staged removed an "unused" variable that IS used by code in a later card, check `git diff` to see what changed, restore the needed code (explicit file write — **never `git stash pop` in a worktree**), re-stage, and retry.
1458
+ 3. **Retry cap + escalation** — retry the commit at most **2 times**. If it still fails (e.g. a pre-commit / doc-freshness rule that cannot be satisfied without human input), log the failure in `## Issues & Flags` as `[COMMIT-BLOCKED] <error>` and invoke `AskUserQuestion`: (a) fix the blocking condition manually and let me retry, (b) hand off the worktree intact, (c) abandon this card and continue the batch. Do NOT loop indefinitely and do NOT silently proceed to step 28 with an uncommitted card.
1410
1459
  - The Claude Code pre-commit hook automatically skips for worktree commits (Husky handles checks natively in the worktree).
1411
- 27. **Mark card DONE (MANDATORY — do NOT skip)**:
1460
+ 28. **Mark card DONE (MANDATORY — do NOT skip)**:
1412
1461
  a. Edit the backlog YAML (`${paths.backlog_dir}/<CARD-ID>.yml`): set `status: DONE`, add `completed_date: <today>`.
1413
1462
  b. Add implementation notes summarizing what was built.
1414
1463
  c. **Update `${paths.references_dir}/ssot-registry.md`** — add/update the entry for this card's feature area. The pre-commit doc-freshness hook BLOCKS commits that touch `backlog/` without a corresponding ssot-registry update. Always include ssot-registry.md in the same commit as the backlog YAML.
1415
1464
  d. **Verify the write**: re-read the YAML file and confirm `status: DONE` is present. If not, retry the edit.
1416
- e. Stage BOTH the updated YAML AND ssot-registry.md, then commit (or as an immediate follow-up commit if Phase 4 commit already happened).
1417
- 28. **Update tracker**: move card to `## Completed Cards` with commit hash, summary, flags, **and `card_status: DONE (verified)`**.
1465
+ e. Stage BOTH the updated YAML AND ssot-registry.md, then commit (or as an immediate follow-up commit if the Phase 4 implementation commit already happened). When this produces a SECOND commit for the card, record BOTH hashes in the tracker (`commit: <impl-hash> + <done-hash>`) so traceability/bisect is unambiguous.
1466
+ 29. **Update tracker**: move card to `## Completed Cards` with commit hash(es), summary, flags, **and `card_status: DONE (verified)`**.
1418
1467
 
1419
1468
  ### Sub-agent failure protocol (since v3.28.3)
1420
1469
 
@@ -1428,16 +1477,16 @@ If any sub-agent **crashes or errors** during any phase, follow this 4-step prot
1428
1477
  Never block the pipeline indefinitely — recover and continue per the rules above. AskUserQuestion in step 3 is bounded by normal user response time.
1429
1478
 
1430
1479
  ### Phase 5 — Context Clean & Continue
1431
- 29. Archive the card from Active Code Context in `${paths.references_dir}/project-status.md`.
1432
- 30. **CONTEXT PURGE**: After updating the tracker, deliberately forget the implementation details of this card. From this point forward, you should NOT reference any code, file contents, or review details from this card — only the summary in the tracker. If you need to recall what happened, read the tracker file. This keeps your working context lean for the next card.
1433
- 31. **Update tracker**: clear `## Current Card`, move to next pending card.
1434
- 32. Move to the next card.
1480
+ 30. Archive the card from Active Code Context in `${paths.references_dir}/project-status.md`.
1481
+ 31. **CONTEXT PURGE**: After updating the tracker, deliberately forget the implementation details of this card. From this point forward, you should NOT reference any code, file contents, or review details from this card — only the summary in the tracker. If you need to recall what happened, read the tracker file. This keeps your working context lean for the next card.
1482
+ 32. **Update tracker**: clear `## Current Card`, move to next pending card.
1483
+ 33. **Move to the next card** — restart the per-card pipeline at **Phase 1 (Claim & Context)** for the next pending card in `## Card Queue`. Do NOT re-run Phase 0 (workspace hygiene runs once per batch) and do NOT re-run Pre-flight worktree setup (the worktree already exists). When all cards in the queue are done, proceed to **Final review**.
1435
1484
 
1436
1485
  ---
1437
1486
 
1438
1487
  ## Final review (after all cards)
1439
1488
 
1440
- > **Primary code reviewer: Codex (GPT-5.4)** — cross-model validation with built-in FP filtering.
1489
+ > **Primary code reviewer: Codex (a non-Anthropic frontier model, via `codex-companion.mjs`)** — cross-model validation with built-in FP filtering.
1441
1490
  > Claude `code-reviewer` is the automatic fallback if Codex is unavailable.
1442
1491
  > The `/codexreview` command remains available for standalone reviews on demand.
1443
1492
 
@@ -1479,23 +1528,36 @@ Once ALL cards are committed in the worktree:
1479
1528
 
1480
1529
  ### Step F.2 — Architecture baseline
1481
1530
 
1482
- 5. Invoke **codebase-architect** agent to map existing architecture, critical patterns, and
1483
- high-risk code paths for regression across the batch scope. This provides grounding context
1484
- for all downstream review agents.
1531
+ 5. **Reuse per-card baselines before re-spawning (dedup).** Phase 1 step 5b already persisted one
1532
+ `/tmp/arch-baseline-<CARD-ID>.md` per card. Before spawning `codebase-architect` for the batch
1533
+ scope, check whether every card in the batch already has a readable baseline file:
1534
+ ```bash
1535
+ MISSING=()
1536
+ for c in <ALL-CARD-IDS>; do [ -s "/tmp/arch-baseline-$c.md" ] || MISSING+=("$c"); done
1537
+ printf '%s\n' "${MISSING[@]}"
1538
+ ```
1539
+ - **All present** → do NOT re-spawn the architect for the full batch. Concatenate the per-card
1540
+ baselines into `${ARCH_BASELINE}` (they already cover every touched file across the batch) and
1541
+ proceed to F.3. Log `f.2-arch: reused N per-card baselines (no re-spawn)` in the tracker. This
1542
+ avoids the N+1 architect invocations the per-card persistence was designed to prevent.
1543
+ - **Any missing** (a card was added late, or a baseline was purged) → invoke **codebase-architect**
1544
+ ONCE over the batch scope to map existing architecture, critical patterns, and high-risk code
1545
+ paths for regression. Persist its output to `/tmp/arch-baseline-batch-<FIRST-CARD-ID>.md` and use
1546
+ that as `${ARCH_BASELINE}`. Log `f.2-arch: re-spawned (missing baselines: <list>)`. Do NOT spawn
1547
+ the architect more than once for the batch — it is the single grounding context for all
1548
+ downstream review agents.
1485
1549
 
1486
1550
  ### Step F.3 — Codex deep code review (primary) + Claude agents (support)
1487
1551
 
1488
- > **Primary reviewer: Codex (GPT-5.4)** — cross-model validation. Codex runs the full
1489
- > `/codexreview` protocol: scope resolution, architecture baseline, parallel deep review,
1490
- > and mandatory false-positive validation. Claude agents provide doc review and supplementary checks.
1552
+ > **Primary reviewer: Codex (a non-Anthropic frontier model, via `codex-companion.mjs`)** — cross-model validation. This is a SINGLE-MODEL Codex pass over the batch diff (scope + review + self-FP-check in one task) — it is NOT the full multi-agent `/codexreview` command pipeline (no sub-agent spawning, no Step 3.5 CoVe). The full per-card `/codexreview` already ran in Phase 3.7; this final pass is the cross-model batch-wide sweep. Claude agents (doc-reviewer, api-perf-cost-auditor) provide doc review and supplementary checks; qa-sentinel runs gates only.
1491
1553
 
1492
- 6. **Launch Codex code review** via `Bash` with `run_in_background: true` and `timeout: 600000` (10 min):
1554
+ 6. **Launch Codex code review** via `Bash` with `run_in_background: true` and `timeout: 600000` (10 min). **The companion script is invoked WITHOUT `--wait`** so the Bash call returns immediately and the orchestrator can fire the parallel Claude agents in step 7; poll `$REVIEW_FILE` for completion in F.4 (it is `tee`-d, so it fills as Codex runs and is complete when the background task finishes). Combining `run_in_background: true` with `--wait` would block the shell and defeat the parallelism — do NOT pass `--wait` here.
1493
1555
 
1494
1556
  ```bash
1495
- REVIEW_FILE="/tmp/codexreview-batch-$(date +%Y%m%d-%H%M%S).md" && \
1557
+ REVIEW_FILE="/tmp/codexreview-batch-<FIRST-CARD-ID>-<SESSION-ID>.md" && \
1496
1558
  CODEX_SCRIPT="$(ls -d ~/.claude/plugins/marketplaces/openai-codex/plugins/codex/scripts/codex-companion.mjs ~/.claude/plugins/cache/openai-codex/codex/*/scripts/codex-companion.mjs 2>/dev/null | sort -V | tail -1)" && \
1497
- [ -z "$CODEX_SCRIPT" ] && echo "CODEX_NOT_FOUND" && exit 1; \
1498
- node "$CODEX_SCRIPT" task --wait "
1559
+ [ -z "$CODEX_SCRIPT" ] && echo "CODEX_NOT_FOUND" | tee "$REVIEW_FILE" && exit 1; \
1560
+ node "$CODEX_SCRIPT" task "
1499
1561
  Run a deep multi-agent code review for these backlog cards. This is a post-implementation
1500
1562
  review — the code is already written and committed. Your job is to find bugs, regressions,
1501
1563
  security issues, and quality problems.
@@ -1538,45 +1600,66 @@ Once ALL cards are committed in the worktree:
1538
1600
 
1539
1601
  7. **In parallel with Codex**, launch Claude support agents (single message):
1540
1602
 
1541
- | Agent | `subagent_type` | Focus |
1542
- |-------|-----------------|-------|
1543
- | **doc-reviewer** | `doc-reviewer` | Cross-card doc consistency, ssot-registry completeness, invariants |
1544
- | **api-perf-cost-auditor** | `api-perf-cost-auditor` | API/data/performance/cost defects (skip if no API/data files in scope) |
1545
- | **qa-sentinel** | `qa-sentinel` | Edge cases, testing gaps, reproducibility |
1603
+ | Agent | `subagent_type` | Focus | Output |
1604
+ |-------|-----------------|-------|--------|
1605
+ | **doc-reviewer** | `doc-reviewer` | Cross-card doc consistency, ssot-registry completeness, invariants | Findings: `finding_id`, `title`, `severity`, `confidence`, `evidence`, `minimal_fix_direction` |
1606
+ | **api-perf-cost-auditor** | `api-perf-cost-auditor` | API/data/performance/cost defects (skip if no API/data files in scope) | Same findings schema |
1607
+ | **qa-sentinel** | `qa-sentinel` | **Mechanical gates ONLY** over the batch scope (lint, tsc, full test suite, build, `npm audit`, markdownlint) | A PASS/FAIL gate table — NOT a findings list. qa-sentinel does not read source files, does not emit severities, and does not do edge-case/reproducibility analysis (its system prompt forbids it). A gate FAILURE feeds the fix-loop the same way a VERIFIED finding does. |
1546
1608
 
1547
- Each agent receives: card IDs, YAML, `review_scope_files`, codebase-architect baseline.
1548
- Return findings with `finding_id`, `title`, `severity`, `confidence`, `evidence`, `minimal_fix_direction`.
1609
+ The two code-aware agents (doc-reviewer, api-perf-cost-auditor) receive: card IDs, YAML, `review_scope_files`, codebase-architect baseline, and a Budget Block per the `/codexreview` Step 2 contract (`framework/.claude/commands/codexreview.md`). qa-sentinel receives only the worktree path + the changed-file list and runs gates. Code-correctness/edge-case analysis is Codex's job (and the per-card `/codexreview` already ran) — do NOT ask qa-sentinel to produce code findings.
1610
+
1611
+ **Fan-out completion barrier (BLOCKING before F.4).** The three Claude agents write to a shared
1612
+ findings pool that F.4 step 9 fans in. Before F.4 reads ANY finding, you MUST have collected the
1613
+ return value of ALL THREE Task invocations from step 7 (doc-reviewer, api-perf-cost-auditor,
1614
+ qa-sentinel) — never start the merge while a Task is still in flight. Because step 7 launches all
1615
+ three in a single message, the harness returns when all three complete; do NOT proceed to step 9
1616
+ on a partial set. (The Codex background task has its OWN barrier — step 8 below polls `$REVIEW_FILE`
1617
+ for completion. The two barriers are independent: wait for BOTH the three Claude Tasks AND the
1618
+ Codex background task before merging.)
1549
1619
 
1550
1620
  ### Step F.4 — Collect & merge findings
1551
1621
 
1552
- 8. **Read Codex findings** from `$REVIEW_FILE` after the background command completes.
1622
+ 8. **Read Codex findings** from `$REVIEW_FILE` after the background command completes (the background
1623
+ task was launched with `timeout: 600000` in step 6 — that 10-min window is the max wait).
1553
1624
  - If file exists and contains findings → use as **primary code review source**.
1554
1625
  - If file is empty, missing, or contains `CODEX_NOT_FOUND` → **fallback**: spawn `code-reviewer`
1555
1626
  agent (subagent_type: `code-reviewer`) with the same scope and instructions. Log fallback
1556
1627
  reason in tracker: `"Codex unavailable — fallback to Claude code-reviewer"`.
1628
+ - **TIMED_OUT branch:** if the 10-min window elapses and `$REVIEW_FILE` still has no terminal
1629
+ output (no findings block and no `CODEX_NOT_FOUND`), treat the Codex pass as **TIMED_OUT** — do
1630
+ NOT block the merge gate waiting indefinitely and do NOT leave the task silently "in-progress".
1631
+ Log `f.3-codex: TIMED_OUT` in `## Issues & Flags` and take the SAME `code-reviewer` fallback as
1632
+ the unavailable case, so the final merge gate still gets a full code review.
1557
1633
 
1558
1634
  9. **Merge all findings** (Codex + Claude agents) into a consolidated list.
1559
1635
  - Codex findings are already FP-validated (Step F.3 protocol includes it).
1560
- - Claude agent findings with `confidence < 80` → cross-validate with a second agent.
1636
+ - Claude agent findings with `confidence < 80` → cross-validate by spawning **`code-reviewer`** as the static validator over the cited file:line; on disagreement between the originating agent and `code-reviewer`, classify the finding `NEEDS_MANUAL_CONFIRMATION` (do NOT silently drop it). (Naming the validator avoids the inconsistent "some second agent" interpretation.)
1561
1637
  - Classify: `VERIFIED` | `FALSE_POSITIVE` | `NEEDS_MANUAL_CONFIRMATION`.
1562
- - Only `VERIFIED` findings proceed to fixes.
1638
+ - `VERIFIED` findings proceed to fixes. **`NEEDS_MANUAL_CONFIRMATION` findings are NOT discarded** — list them in `## Issues & Flags` and surface them to the user via `AskUserQuestion` (treat as VERIFIED, treat as FALSE_POSITIVE, or hand off) before merge. Only `FALSE_POSITIVE` are dropped.
1563
1639
 
1564
1640
  ### Step F.5 — Apply fixes and final build
1565
1641
 
1566
1642
  10. **Persist verified findings** to `/tmp/batch-final-review-<FIRST-CARD-ID>.md`.
1567
- 11. If VERIFIED findings with severity >= MEDIUM exist, apply them by **domain owner** (since v3.40.0 — same Domain-Override routing as per-card phases). Partition the verified findings:
1568
- - **`doc`-domain findings** (file path matching the `doc` match rule in "Domain-Override Domains" — `*.md` under `${paths.references_dir}`/`${paths.prd_dir}`, `CHANGELOG.md`, `ssot-registry.md`) → invoke the **doc-reviewer** agent once in write mode to apply them. NEVER route doc fixes to coder.
1569
- - **All other findings** (code, perf, security, migration, test) → invoke the **coder** agent once to apply them in a single pass.
1570
- Run both in the order doc-reviewer coder (or skip either if its partition is empty). Pass only the verified findings, not false positives.
1643
+ 11. **Merge-blocking gate (mirrors the per-card Phase 3.7 gate this final pass backstops):** if any VERIFIED **BLOCKER or HIGH** finding exists, it MUST be resolved before Phase 6 merge. Apply fixes by **domain owner** (since v3.40.0 — same Domain-Override routing as the per-card phases), then re-verify; if a BLOCKER/HIGH cannot be resolved in a single apply + one retry, log it in `## Issues & Flags` and invoke `AskUserQuestion` (override with reason / escalate to a follow-up card / halt) — do NOT proceed to Phase 6 with an unresolved BLOCKER or HIGH. VERIFIED findings of severity MEDIUM are also applied (advisory below that). Partition the verified findings by the **Domain-Override match rules** ("Domain-Override Domains"):
1644
+ - **`doc`-domain findings** (file path matching the `doc` match rule — `*.md` under `${paths.references_dir}`/`${paths.prd_dir}`, `CHANGELOG.md`, `ssot-registry.md`) → invoke the **doc-reviewer** agent once in write mode to apply them. NEVER route doc fixes to coder.
1645
+ - **`security`-domain findings** (path in `paths.high_risk_modules`, or RLS-policy SQL) and **`migration`-domain findings** (SQL under the migrations dir) → route to **coder**, but apply the Sub-agent failure protocol's STOP-on-crash rule for these domains (never inline-fallback on a security/migration fix). These are NOT collapsed into a generic "everything else" bucket.
1646
+ - **All remaining findings** (other code, perf, test) invoke the **coder** agent once to apply them in a single pass.
1647
+ Run in the order doc-reviewer → coder (or skip either if its partition is empty). Pass only the verified findings, not false positives.
1571
1648
  12. Run final build: `npm run lint && npx tsc --noEmit && npm run build`.
1572
1649
  If any check fails, apply self-healing retry loop (up to 3 times).
1573
1650
  13. **Update tracker** with final review results:
1574
- - Review engine: Codex GPT-5.4 (primary) | Claude code-reviewer (fallback)
1651
+ - Review engine: Codex (a non-Anthropic frontier model, resolved at runtime by `codex-companion.mjs`) (primary) | Claude code-reviewer (fallback)
1575
1652
  - Total findings raised / verified / false positives / needs-manual
1576
1653
  - Fixes applied count
1577
1654
  - Build status (pass/fail + retry count)
1578
1655
  - Highest severity found
1579
- 7. **SSOT & Documentation Activity** (MANDATORYrun BEFORE merge):
1656
+ - **Phase-8 producer (named counters)** — ALSO write the structured line `review_findings: total=<R> verified=<V> blockers=<B>` to the tracker `## Final Review` section, where `R` = all findings raised (Codex + Claude agents, pre-FP), `V` = VERIFIED count, `B` = VERIFIED-BLOCKER count. These are the single named producers for Phase 8's batch-level `findings_total` / `findings_verified` / `blockers_count` Phase 8 reads THESE keys, never re-parses prose. (Per-card Phase 3.7 codex counts stay in `## Pre-Merge Codex Review`; Phase 8 uses the batch-level `review_findings` line here as the canonical total so the QA-findings and codex-findings counts are not conflated.)
1657
+
1658
+ ### Step F.6 — Post-review wrap-up (SSOT summary, sync, hand-off to Phase 6)
1659
+
1660
+ > This is a distinct sub-section from F.1–F.5 (its own list); the numbering below restarts and does NOT continue the F.5 counter.
1661
+
1662
+ 1. **SSOT & Documentation Activity** (MANDATORY — run BEFORE merge):
1580
1663
 
1581
1664
  Summarize all documentation and SSOT updates performed during the batch:
1582
1665
 
@@ -1594,34 +1677,36 @@ Once ALL cards are committed in the worktree:
1594
1677
  - `${paths.adrs_dir}/ADR-YYYYMMDD-<slug>.md` — [titolo] (o "Nessun ADR creato")
1595
1678
  ```
1596
1679
 
1597
- 8. **Knowledge Base Sync** (OPTIONAL — only if the project has an external knowledge corpus, e.g. an Obsidian vault, Confluence space, or Notion workspace):
1680
+ 2. **Knowledge Base Sync** (OPTIONAL — only if the project has an external knowledge corpus, e.g. an Obsidian vault, Confluence space, or Notion workspace):
1598
1681
 
1599
- If the project ships a `knowledge-sync` agent (or equivalent), invoke it after doc updates so the external corpus stays aligned. If no such agent exists, skip this step.
1682
+ If the project ships a `knowledge-sync` corpus agent (declared in `.baldart/overlays/new.md`), invoke it after doc updates so the external corpus stays aligned. If no such agent exists, skip this step with a one-line notice (do NOT dispatch a non-existent agent).
1600
1683
 
1601
- 9. **Proceed to Phase 6** (post-batch merge & cleanup).
1602
- 10. Present a **single summary report** to the user per card (and a batch summary at the end):
1684
+ 3. **Proceed to Phase 6** (post-batch merge & cleanup).
1685
+ 4. Present a **single summary report** to the user per card (and a batch summary at the end):
1603
1686
  - **Files changed** (short list per card)
1604
1687
  - **Test results** (new tests + existing tests count, pass rate at each iteration)
1605
1688
  - **Build/lint status** (pass + retry count if any)
1606
1689
  - **Fix cycles** (total number of self-healing retries across phases)
1607
1690
  - **Final review** (findings: N raised / M verified / K false positives | fixes applied: N | highest severity)
1608
1691
  - **UX testing** (PASS/FAIL/SKIP | test file path if written)
1609
- - **QA result** (profile: skip/light/balanced/deep | verdict: PASS/FAIL/SKIP | confidence % | findings: N blockers, N majors, N minors | E2E: PASS/FAIL/SKIP | findings file path)
1692
+ - **QA result** (profile: skip/light/balanced/deep | verdict: PASS/FAIL/SKIP | confidence % | failing gates: which of lint/tsc/test/build/markdownlint, or "none" | findings file path). E2E is reported separately (Phase 2.6 e2e-review), not by qa-sentinel.
1610
1693
  - **Issues needing user attention** (anything unresolved, partially wired, or flagged)
1611
1694
  - **Commit hashes** (from tracker)
1612
1695
  - **Merge commit hash** (from Phase 6)
1613
1696
  - **Card status reconciliation** (Phase 6b: N cards verified DONE, K force-updated)
1614
1697
  - **Worktree cleanup status** (success/failed)
1615
- - **Obsidian sync status** (from step 8COMPLETATO/FALLITO/PARZIALE)
1698
+ - **Knowledge-corpus sync status** (from Step F.6 item 2 "Knowledge Base Sync" COMPLETED/FAILED/PARTIAL, or SKIPPED if no corpus-sync agent is configured)
1616
1699
  - Overall implementation status
1617
1700
 
1618
- 10b. **Next Steps & Launch Command** (MANDATORY section — always present):
1701
+ 4b. **Next Steps & Launch Command** (MANDATORY section — always present):
1619
1702
 
1620
- Check for remaining TODO/READY cards in the same epic group:
1703
+ Check for remaining not-yet-DONE cards in the same epic group (match "not DONE",
1704
+ not a hard-coded status value — see the `status enum` SSOT in REGISTRY.md):
1621
1705
 
1622
1706
  ```bash
1623
- # Find parent epic from completed cards
1624
- grep -l "parent: <EPIC-ID>" backlog/*.yml | xargs grep -l "status: TODO\|status: READY"
1707
+ # Find parent epic's cards that are NOT yet DONE (TODO | READY | IN_PROGRESS | BLOCKED)
1708
+ grep -l "parent: <EPIC-ID>" ${paths.backlog_dir}/*.yml \
1709
+ | xargs grep -L "status: DONE"
1625
1710
  ```
1626
1711
 
1627
1712
  Present the section:
@@ -1652,10 +1737,10 @@ Once ALL cards are committed in the worktree:
1652
1737
  ## Prossimi Passi
1653
1738
 
1654
1739
  Tutte le card dell'epic **FEAT-XXXX** sono state completate.
1655
- Pronto per il deploy: `/deploy` o `git push origin develop`
1740
+ Pronto per il deploy: `/deploy` o `git push origin <trunk branch>` (il `git.trunk_branch` configurato)
1656
1741
  ```
1657
1742
 
1658
- 11. **Proceed to Phase 7** (production readiness checklist).
1743
+ 5. **Proceed to Phase 7** (production readiness checklist).
1659
1744
 
1660
1745
  ---
1661
1746
 
@@ -1673,10 +1758,10 @@ After the final review passes AND all cards are committed in the worktree, deleg
1673
1758
  - The worktree path and branch from the tracker
1674
1759
  - `checksAlreadyPassed: true` (final review + QA already validated the build)
1675
1760
  - All card IDs for the commit message
1676
- 3. The skill handles: safety commit of any remaining uncommitted files (step 3), rebasing onto latest develop (step 4b — auto-resolves doc conflicts, stops on code conflicts), merging into develop via the configured `git.merge_strategy` (step 4c — `pr` uses `gh pr merge`, `local-push` does a direct FF push to `origin/develop`; NEITHER runs `git checkout develop` on the main repo, respecting the absolute terminal-isolation rule), post-merge verification, worktree removal, registry cleanup, and remote branch deletion.
1761
+ 3. The skill handles: safety commit of any remaining uncommitted files (step 3), rebasing onto the latest trunk (`$TRUNK` = `git.trunk_branch`) (step 4b — auto-resolves doc conflicts, stops on code conflicts), merging into the trunk via the configured `git.merge_strategy` (step 4c — `pr` uses `gh pr merge`, `local-push` does a direct FF push to `origin/$TRUNK`; NEITHER runs `git checkout` of the trunk on the main repo, respecting the absolute terminal-isolation rule), post-merge verification, worktree removal, registry cleanup, and remote branch deletion.
1677
1762
  4. **If code merge conflicts** → the skill STOPs and reports. Doc-only conflicts (ssot-registry.md, project-status.md, etc.) are auto-resolved by keeping both sides.
1678
1763
  5. **If post-merge build fails** → the skill STOPs and keeps the worktree intact for investigation.
1679
- 6. Record the merge commit hash and result in the tracker.
1764
+ 6. Record the merge commit hash and result in the tracker. **Also record the merge timestamp** under `## Worktree Merges` (`merge_ts: <ISO-8601>`): for `local-push` use the local merge-commit time (`git log --format=%ci <hash>`); for `pr` (`gh pr merge`) use the PR merge completion time, or note `merge_ts: pending` if GitHub has not yet finalized the merge. Phase 8's `cycle_time_mins` reads THIS field as the strategy-independent end anchor (never a raw hash that may be the PR HEAD).
1680
1765
 
1681
1766
  ### Phase 6b — Backlog Card Status Reconciliation (MANDATORY — ZERO TOLERANCE)
1682
1767
 
@@ -1743,7 +1828,7 @@ The most common failure mode is leaving cards IN_PROGRESS after merge. This crea
1743
1828
 
1744
1829
  ### Phase 6c — Workspace Hygiene Post-merge (BLOCKING — non-skippable)
1745
1830
 
1746
- **Why this exists**: FEAT-0006 incident closed the batch leaving the main repo with an unpushed orphan commit (`c9d41f9`) and local `develop` diverged from `origin/develop` for a manual fix. This phase closes the loop opened by Phase 0: it verifies the main repo is clean and synchronized, parses the `[SYNC-DEFERRED]` marker emitted by `/mw` when HEAD ≠ develop, and restores any stash saved in Phase 0.
1831
+ **Why this exists**: FEAT-0006 incident closed the batch leaving the main repo with an unpushed orphan commit (`c9d41f9`) and local `develop` diverged from `origin/develop` for a manual fix. This phase closes the loop opened by Phase 0: it verifies the main repo is clean and synchronized, parses the `[SYNC-DEFERRED]` marker emitted by `/mw` when HEAD ≠ `$TRUNK`, and restores any stash saved in Phase 0.
1747
1832
 
1748
1833
  **Auto Mode does NOT override this phase.** Every `AskUserQuestion` below is non-bypassable.
1749
1834
 
@@ -1755,11 +1840,11 @@ The most common failure mode is leaving cards IN_PROGRESS after merge. This crea
1755
1840
  ```
1756
1841
 
1757
1842
  2. **Parse `mw-docs` sync markers** — scan the captured stdout from every `/mw` invocation in this batch (kept in the tracker under `## Worktree Merges`):
1758
- - **`[SYNC-DEFERRED] main repo HEAD=<branch>`** (main repo HEAD ≠ develop) — surface via `AskUserQuestion`:
1759
- - Question: `"Una o più merge worktree hanno deferito la sincronizzazione di local develop (main repo HEAD non era develop). Come riconcilio adesso?"`
1760
- - Options: `[Ora HEAD è develop → ff-pull adesso]` / `[Lascia deferred (riconcilio io manualmente)]` / `[Mostra dettagli e fammi decidere]`.
1761
- - **`[SYNC-NEEDS-DECISION] …`** (HEAD was develop but the ff was blocked by a **foreign** uncommitted file, or by a divergence/rebase conflict `/mw` would not auto-commit) — this is NEVER a passive note. Surface via `AskUserQuestion`:
1762
- - Question: `"Il develop locale non si è sincronizzato: <dettaglio dal marker>. Come chiudo?"`
1843
+ - **`[SYNC-DEFERRED] main repo HEAD=<branch>`** (main repo HEAD ≠ `$TRUNK`) — surface via `AskUserQuestion`:
1844
+ - Question: `"Una o più merge worktree hanno deferito la sincronizzazione di local $TRUNK (main repo HEAD non era $TRUNK). Come riconcilio adesso?"`
1845
+ - Options: `[Ora HEAD è $TRUNK → ff-pull adesso]` / `[Lascia deferred (riconcilio io manualmente)]` / `[Mostra dettagli e fammi decidere]`.
1846
+ - **`[SYNC-NEEDS-DECISION] …`** (HEAD was `$TRUNK` but the ff was blocked by a **foreign** uncommitted file, or by a divergence/rebase conflict `/mw` would not auto-commit) — this is NEVER a passive note. Surface via `AskUserQuestion`:
1847
+ - Question: `"Il $TRUNK locale non si è sincronizzato: <dettaglio dal marker>. Come chiudo?"`
1763
1848
  - Options: `[Committa tu adesso (descrivi cosa)]` / `[Lo gestisco io — chiudi senza sync locale]` / `[Mostrami il diff e decidiamo]`.
1764
1849
  - **No marker** — `/mw` fast-forwarded (or auto-reconciled the framework telemetry log); nothing to parse.
1765
1850
 
@@ -1771,27 +1856,29 @@ The most common failure mode is leaving cards IN_PROGRESS after merge. This crea
1771
1856
  - Question: `"Main repo ha modifiche non committate dopo il batch (non c'era stash di Phase 0). Cosa faccio?"`
1772
1857
  - Options: `[Mostrami il diff e fammi decidere]` / `[Stash adesso con label baldart-post-batch-<timestamp>]` / `[Lascia così (lo gestisco io)]` / `[Halt]`.
1773
1858
 
1774
- 4. **Divergence assertion (BLOCKING — this catches the FEAT-0006 orphan-commit pattern)**:
1859
+ 4. **Divergence assertion (BLOCKING — this catches the FEAT-0006 orphan-commit pattern)**. `$MAIN` and `$TRUNK` are read from the tracker (`## Worktree` section); HALT if either is absent/empty.
1775
1860
  ```bash
1776
- git -C "$MAIN" rev-list --left-right --count origin/develop...develop
1861
+ git -C "$MAIN" rev-list --left-right --count "origin/$TRUNK...$TRUNK"
1777
1862
  ```
1778
1863
  If the output is anything other than `0\t0`:
1779
1864
  - **Ahead only (M > 0, unpushed commits)** — invoke `AskUserQuestion`:
1780
- - Question: `"Local develop ha M commit non pushati su origin/develop al termine del batch. Lista: <git log --oneline origin/develop..develop>. Cosa faccio?"`
1781
- - Options: `[Push adesso (git push origin develop)]` / `[Cherry-pick selettivo su origin/develop]` / `[Reset --hard origin/develop (richiede conferma esplicita, DISTRUTTIVO)]` / `[Halt con stato preservato]`.
1782
- - On `Reset --hard origin/develop` re-ask `AskUserQuestion` to confirm: `"Confermi reset --hard? I commit <list> verranno persi se non pushati altrove."` Solo dopo conferma esplicita esegui il reset.
1865
+ - Question: `"Local $TRUNK ha M commit non pushati su origin/$TRUNK al termine del batch. Lista: <git log --oneline origin/$TRUNK..$TRUNK>. Cosa faccio?"`
1866
+ - Options: `[Push adesso (git push origin $TRUNK)]` / `[Cherry-pick selettivo su origin/$TRUNK]` / `[Reset --hard origin/$TRUNK (richiede conferma esplicita, DISTRUTTIVO)]` / `[Halt con stato preservato]`.
1867
+ - On `Reset --hard origin/$TRUNK` re-ask `AskUserQuestion` to confirm: `"Confermi reset --hard? I commit <list> verranno persi se non pushati altrove."` Solo dopo conferma esplicita esegui il reset.
1783
1868
  - **Behind only (N > 0)** — invoke `AskUserQuestion`:
1784
- - Question: `"Local develop è behind origin/develop di N commit (qualcuno ha pushato durante il batch). Faccio ff-pull adesso?"`
1869
+ - Question: `"Local $TRUNK è behind origin/$TRUNK di N commit (qualcuno ha pushato durante il batch). Faccio ff-pull adesso?"`
1785
1870
  - Options: `[Fast-forward pull]` / `[Lascia behind (lo gestisco io)]`.
1786
1871
  - **Diverged both ways** — invoke `AskUserQuestion`:
1787
- - Question: `"Local develop è diverged (N behind, M ahead). Servono entrambe le riconciliazioni. Come procedo?"`
1788
- - Options: `[Rebase local develop su origin/develop]` / `[Mostrami i commit locali e decidiamo]` / `[Halt]`.
1872
+ - Question: `"Local $TRUNK è diverged (N behind, M ahead). Servono entrambe le riconciliazioni. Come procedo?"`
1873
+ - Options: `[Rebase local $TRUNK su origin/$TRUNK]` / `[Mostrami i commit locali e decidiamo]` / `[Halt]`.
1789
1874
 
1790
- 5. **Restore Phase 0 stash (if any)** — read the tracker for `## Workspace Snapshot: <ref>`:
1791
- - If `<ref>` is `dirty-tree override` → skip restore, log "user retained dirty-tree responsibility".
1792
- - If `<ref>` is `stash@{N}` (or similar):
1875
+ 5. **Restore Phase 0 stash (if any)** — read the tracker for `## Workspace Snapshot: <message-label>`:
1876
+ - If the snapshot is `dirty-tree override` → skip restore, log "user retained dirty-tree responsibility".
1877
+ - If it is a stash MESSAGE label (e.g. `baldart-new-phase0-<FIRST-CARD-ID>-<timestamp>`) — resolve the stash by its message, NEVER by positional `stash@{N}` (other stashes may have shifted the index since Phase 0). This runs on `$MAIN` (the main checkout, not a worktree), so a stash op here is safe:
1793
1878
  ```bash
1794
- git -C "$MAIN" stash pop <ref>
1879
+ REF="$(git -C "$MAIN" stash list | grep -F "<message-label>" | head -1 | cut -d: -f1)"
1880
+ [ -n "$REF" ] && git -C "$MAIN" stash pop "$REF" \
1881
+ || echo "stash '<message-label>' not found — was it already restored?"
1795
1882
  ```
1796
1883
  On conflict, do NOT silent-drop — invoke `AskUserQuestion`:
1797
1884
  - Question: `"Restore dello stash di Phase 0 ha generato conflitti. Lo stash è ancora presente (NON eliminato). Come procedo?"`
@@ -1802,7 +1889,7 @@ The most common failure mode is leaving cards IN_PROGRESS after merge. This crea
1802
1889
  ## Phase 6c — Workspace Hygiene Post-merge
1803
1890
  Status: PASS
1804
1891
  Clean-tree: <yes | resolved: stashed | user-retained>
1805
- Divergence (local…origin/develop): <0\t0 | resolved: pushed/cherry-picked/ff-pulled/rebased>
1892
+ Divergence (local…origin/$TRUNK): <0\t0 | resolved: pushed/cherry-picked/ff-pulled/rebased>
1806
1893
  Sync-deferred markers: <none | reconciled | user-retained>
1807
1894
  Phase 0 snapshot restore: <n/a | popped clean | conflict-deferred-to-user>
1808
1895
  Completed: <timestamp>
@@ -1812,11 +1899,11 @@ The most common failure mode is leaving cards IN_PROGRESS after merge. This crea
1812
1899
  7. **Anti-bypass guard** — like Phase 0, before exiting Phase 6c re-read the tracker section you just wrote. Missing `## Phase 6c` → refuse to proceed to Phase 7. There is no `--skip-phase-6c` flag; treat any urge to skip as a self-correction trigger.
1813
1900
 
1814
1901
  ### Fail-safe rules (enforced by worktree-manager skill)
1815
- - **Never `git checkout`, `git switch`, `git checkout -b`, or `git branch` on the main repo** from inside this orchestrator. The main repo is shared across parallel terminals. Use the configured `git.merge_strategy` for develop merges and `git -C <main> pull --ff-only` (only when `HEAD = develop` already) for sync. See `/mw` step 4c.
1816
- - Never merge to `main` — only to `develop`, via the configured `git.merge_strategy` (NOT local checkout).
1817
- - Never force push to `main` or `develop`. `--force-with-lease` on feature branches after rebase is allowed.
1902
+ - **Never `git checkout`, `git switch`, `git checkout -b`, or `git branch` on the main repo** from inside this orchestrator. The main repo is shared across parallel terminals. Use the configured `git.merge_strategy` for trunk merges and `git -C <main> pull --ff-only` (only when `HEAD = $TRUNK` already) for sync. See `/mw` step 4c.
1903
+ - Never merge into a release/production branch — only into the integration trunk (`$TRUNK` = `git.trunk_branch`), via the configured `git.merge_strategy` (NOT local checkout).
1904
+ - Never force push to the trunk. `--force-with-lease` on feature branches after rebase is allowed.
1818
1905
  - Never delete a branch before successful merge verification.
1819
- - Never remove a worktree before confirming develop is stable post-merge.
1906
+ - Never remove a worktree before confirming the trunk is stable post-merge.
1820
1907
  - Stop execution immediately if any command fails.
1821
1908
 
1822
1909
  ---
@@ -2071,7 +2158,7 @@ If at ANY point you are unsure where you are in the batch:
2071
2158
 
2072
2159
  ### Team mode (for complex batches)
2073
2160
 
2074
- - Cards within the same `parallel_group` run in parallel via isolated coder agents.
2161
+ - Cards within the same `execution_strategy.groups` level (same `level`) run in parallel via isolated coder agents.
2075
2162
  - Groups execute sequentially (group 0 → group 1 → group 2...).
2076
2163
  - Review + QA run ONCE per group (combined), not per card.
2077
2164
  - The orchestrator holds ONLY coordination state, never implementation details.
@@ -2101,7 +2188,7 @@ It does NOT accumulate implementation details, codebase-architect findings, or r
2101
2188
 
2102
2189
  After the standard pre-flight (steps 1-7), add:
2103
2190
 
2104
- 1. Read all cards' `parallel_group` field and sort into execution layers.
2191
+ 1. Read the `execution_strategy.groups` (each with `level` + `cards`) and sort into execution layers by `level`. For legacy cards lacking the block, fall back to the on-the-fly topological-layer computation from step 3c.
2105
2192
  2. Update tracker with team mode section:
2106
2193
  ```
2107
2194
  ## Team Mode
@@ -2195,16 +2282,17 @@ Agent tool call:
2195
2282
  ### 7. Completion Report (MANDATORY)
2196
2283
  ```completion-report
2197
2284
  card: <CARD-ID>
2198
- status: done | partial | failed
2285
+ status: done | partial | blocked # card-level — matches coder.md four-state (coder never self-emits deferred-approved; the orchestrator sets that after the AC-Closure Gate)
2199
2286
  requirements:
2200
2287
  - id: 1
2201
2288
  text: "[text]"
2202
- status: done | partial | blocked
2289
+ status: done | not_implemented # per-requirement is BINARY (legacy row-level partial/blocked were silent-deferral channels — removed)
2203
2290
  evidence: "file:line"
2204
2291
  files_changed:
2205
2292
  - path/to/file.ts
2206
2293
  build_status: pass | fail
2207
2294
  lint_status: pass | fail
2295
+ tsc_status: pass | fail | n/a # n/a when stack.language excludes typescript
2208
2296
  retry_count: N
2209
2297
  ```
2210
2298
  ```
@@ -2218,10 +2306,11 @@ For each completed agent:
2218
2306
  2. Log to tracker: card ID, status, files changed, build/lint status.
2219
2307
  3. Do NOT read or store implementation details — only the verdict.
2220
2308
 
2221
- **If an agent fails** (status: failed after 3 retries):
2309
+ **If an agent fails** (status: failed after 3 retries — the central repair cap):
2222
2310
  - Log failure in tracker `## Issues & Flags`.
2223
2311
  - Other agents in the group continue unaffected.
2224
2312
  - After group completes, ask user: retry failed card or skip?
2313
+ - **Retry re-entry (defined path):** on "retry", re-run the failed card from **Step B** (re-spawn the autonomous coder with a fresh briefing) — NOT from Step A, because the Step A arch context for the group is already in hand and is reused. The retried card then rejoins the group's Step D pipeline and MUST pass the SAME mandatory per-card sub-steps as its peers (D.3a AC-Closure → D.3b Simplify → D.3c E2E → D.4 QA → D.4a doc → D.4b codex → D.5 commit → D.6 backlog); there is no shortcut. The Step D coverage assertion will block Step E until the retried card has all mandatory tracker entries — so a retried card cannot silently skip a gate. **Cap: one Step-B re-entry per card** (consistent with the central cap discipline); on a second failure, re-ask with only skip/abandon so the loop cannot recur unbounded.
2225
2314
 
2226
2315
  #### Step D: Post-group review + QA (ALL sub-steps MANDATORY)
2227
2316
 
@@ -2239,21 +2328,27 @@ After ALL agents in the group complete successfully:
2239
2328
 
2240
2329
  3b. **D.3b — Phase 2.55 Simplify (per-card)** — For EACH card in the group, invoke Phase 2.55 (Reuse / Quality / Efficiency parallel agents) on the per-card diff (`git diff --name-only` filtered by the card's File Ownership Map). Per-card (not group-aggregate) so findings are attributable. Apply fixes inline. After all cards: re-run `npm run lint` and `npx tsc --noEmit` on the worktree once.
2241
2330
 
2242
- 3c. **D.3c — Phase 2.6 E2E-Review (per-card)** — For EACH card in the group, honor the existing Gate table (skip when `features.has_e2e_review: false`, backend-only diff per the diff predicate documented in Phase 2.6, or card type `docs`/`chore`/`config`). For cards that pass the gate, invoke `/e2e-review` in programmatic mode with that card's payload. BLOCKING per-card: if `/e2e-review` returns `"blocked"` or `"error"`, surface to the user via the same `AskUserQuestion` documented in Phase 2.6 — do NOT proceed to D.4 with an unresolved card. Skips are logged with the documented gate reason, never with `"time budget"` or similar.
2331
+ 3c. **D.3c — Phase 2.6 E2E-Review (per-card)** — For EACH card in the group, honor the existing Gate table (skip when `features.has_e2e_review: false`, backend-only diff per the diff predicate documented in Phase 2.6, or card type in the Phase 2.6 skip set — `backend`/`api`/`db`/`infra`/`docs`/`chore`/`config`). For cards that pass the gate, invoke `/e2e-review` in programmatic mode with that card's payload. BLOCKING per-card: if `/e2e-review` returns `"blocked"` or `"error"`, surface to the user via the same `AskUserQuestion` documented in Phase 2.6 — do NOT proceed to D.4 with an unresolved card. Skips are logged with the documented gate reason, never with `"time budget"` or similar.
2243
2332
 
2244
- 4. **D.4 — QA gate (group)** — Read each card's `review_profile` field (fallback: compute via the QA Profile Selector table for legacy cards). Select the HIGHEST profile across all cards in the group (e.g., if one card is BALANCED and another is DEEP, use DEEP) and pass it as the `QA profile` to **qa-sentinel** (invoked once for the group) using the same prompt contract as Phase 3.5 step 22 (`do NOT default to FULL`; balanced → SCOPED, deep → FULL). Note: a mixed group containing any DEEP card runs the group at FULL — the max-profile intentionally overrides the SCOPED tier the BALANCED cards would get individually, because the combined diff must be validated together.
2333
+ 4. **D.4 — QA gate (group)** — Read each card's `review_profile` field (fallback for legacy cards only: compute via the QA Profile Selector, whose criteria are the SSOT in `prd-card-writer.md § Rule C`). Select the HIGHEST profile across all cards in the group (e.g., if one card is BALANCED and another is DEEP, use DEEP) and pass it as the `QA profile` to **qa-sentinel** (invoked once for the group) using the same prompt contract as Phase 3.5 step 22 (`do NOT default to FULL`; balanced → SCOPED, deep → FULL). Note: a mixed group containing any DEEP card runs the group at FULL — the max-profile intentionally overrides the SCOPED tier the BALANCED cards would get individually, because the combined diff must be validated together.
2245
2334
 
2246
- 4a. **D.4a — Per-card doc gate (consumes D.2, since v3.35.0; doc-reviewer-applied since v3.40.0)** — Do NOT re-run the doc AUDIT (D.2 already produced per-card-attributed findings). If any card in the group has doc findings, invoke the **doc-reviewer once in write mode** over the group, passing the per-card-attributed findings from D.2, to APPLY all doc fixes in a single pass (it is now alone — code-reviewer is done — so the D.2 parallel-safety constraint no longer applies). Do **NOT** spawn a fix-coder for doc findings: `doc` is owned by `doc-reviewer` (see "Domain-Override Domains"); a code-oriented coder lacks the doc-invariant contract. The only exception is a doc-drift→bug finding rooted in CODE — that follows the D.4b code fix path. (Previously D.4a spawned a fix-coder per card; the audit was already collapsed to a single attributed D.2 pass. D.4b's per-card `/codexreview` also skips its doc-reviewer via the lean contract, so the doc AUDIT runs once per group while the doc FIXES run once per group via doc-reviewer.) **Telemetry** — append one row per applied doc finding to `## Fix Application Log`: `D.4a | doc | est_lines=<n> | decision=doc-reviewer | applied_by=doc-reviewer | card=<ID> | finding=<1-line>`.
2335
+ 4a. **D.4a — Per-card doc gate (consumes D.2, since v3.35.0; doc-reviewer-applied since v3.40.0)** — Do NOT re-run the doc AUDIT (D.2 already produced per-card-attributed findings). If any card in the group has doc findings, invoke the **doc-reviewer once in write mode** over the group, passing the per-card-attributed findings from D.2, to APPLY all doc fixes in a single pass (it is now alone — code-reviewer is done — so the D.2 parallel-safety constraint no longer applies). Do **NOT** spawn a fix-coder for doc findings: `doc` is owned by `doc-reviewer` (see "Domain-Override Domains"); a code-oriented coder lacks the doc-invariant contract. The only exception is a doc-drift→bug finding rooted in CODE — that follows the D.4b code fix path. (Previously D.4a spawned a fix-coder per card; the audit was already collapsed to a single attributed D.2 pass. D.4b's per-card `/codexreview` also skips its doc-reviewer via the lean contract, so the doc AUDIT runs once per group while the doc FIXES run once per group via doc-reviewer.) **Telemetry** — append one row per applied doc finding to `## Fix Application Log`: `D.4a | doc | est_lines=<n> | decision=doc-reviewer | applied_by=doc-reviewer | card=<ID> | finding=<1-line>`. **Phase-8 producer (named counter)** — ALSO record per card the `doc_gaps: found=<N> fixed=<M>` line in that card's `## Completed Cards` entry (same named counter as sequential Phase 3 step 15), so Phase 8 reads `doc_gaps_found`/`doc_gaps_fixed` from a structured field, not a free-form row.
2247
2336
 
2248
- 4b. **D.4b — Pre-Merge Codex Review Gate (per-card, MANDATORY — UNCONDITIONAL)** — Invoke `/codexreview` for EACH card in the group, one at a time, BEFORE any commit. This mirrors Phase 3.7 of the sequential path and is non-skippable regardless of file paths or perceived risk. Apply the same fix sub-loop: if the consolidated report shows verified BLOCKER/HIGH findings, spawn a fix-coder and re-run `/codexreview <CARD-ID>` (max 2 retries per card). If still BLOCKER/HIGH after retries, ask the user before proceeding to D.5. The D.5 commits MUST NOT happen until every card in the group has a PASS verdict (or explicit user override via `AskUserQuestion`). Log results in the tracker under `## Pre-Merge Codex Review` per card. **Lean + profile (since v3.35.0)**: before each card's `/codexreview`, apply the same Review Profile Selector and write the same `/tmp/codexreview-lean-<CARD-ID>.json` contract as sequential Phase 3.7 Step C — `arch_baseline_path` pointing at `/tmp/arch-baseline-group-<FIRST-CARD-ID>.md`, `skip_doc_reviewer: true`, and `profile: light|full` derived from the card's `review_profile` (`skip`/`light` → `light`, `balanced`/`deep` → `full`; since v3.38.0 read from the card, not recomputed) — with the same **escalation-only** rule: a Step A high-risk trigger on the actual diff promotes `light` → `full`, never the reverse. LIGHT runs `code-reviewer` + FP-gate only — the per-card **Codex adversarial pass is dropped at `light` since v3.38.0** (prerogative of `full` + the final cross-check). The Review Profile Selector is **ACTIVE since v3.37.0** — same status as sequential Phase 3.7 Step C above: `light` is per-card early feedback, and the merge-gate safety is the post-batch **Final-review FULL gate** (a single FULL `/codexreview` over the entire batch diff, which team mode reaches via "Post-batch — same as sequential mode"). To make per-card always-full again, restore `profile = full`; the final full gate stays regardless.
2337
+ 4b. **D.4b — Pre-Merge Codex Review Gate (per-card, MANDATORY — UNCONDITIONAL)** — Invoke `/codexreview` for EACH card in the group, one at a time, BEFORE any commit. This mirrors Phase 3.7 of the sequential path and is non-skippable regardless of file paths or perceived risk. Apply the same fix sub-loop as sequential Phase 3.7 Step C.4: if the consolidated report shows verified BLOCKER/HIGH findings, spawn a fix-coder and — **re-writing the consumed-once lean contract first** — re-invoke `/codexreview` via the Skill tool with `args: <CARD-ID>` (max 2 retries per card; a bare prose mention or a missing card-ID would let the retry review the wrong card). If still BLOCKER/HIGH after retries, ask the user before proceeding to D.5. The D.5 commits MUST NOT happen until every card in the group has a PASS verdict (or explicit user override via `AskUserQuestion`). Log results in the tracker under `## Pre-Merge Codex Review` per card. **Lean + profile (since v3.35.0)**: before each card's `/codexreview`, apply the same Review Profile Selector and write the same `/tmp/codexreview-lean-<CARD-ID>.json` contract as sequential Phase 3.7 Step C — `arch_baseline_path` pointing at `/tmp/arch-baseline-group-<FIRST-CARD-ID>.md`, `skip_doc_reviewer: true`, and `profile: light|full` derived from the card's `review_profile` (`skip`/`light` → `light`, `balanced`/`deep` → `full`; since v3.38.0 read from the card, not recomputed) — with the same **escalation-only** rule: a Step A high-risk trigger on the actual diff promotes `light` → `full`, never the reverse. LIGHT runs `code-reviewer` + FP-gate only — the per-card **Codex adversarial pass is dropped at `light` since v3.38.0** (prerogative of `full` + the final cross-check). The Review Profile Selector is **ACTIVE since v3.37.0** — same status as sequential Phase 3.7 Step C above: `light` is per-card early feedback, and the merge-gate safety is the post-batch **Final-review FULL gate** (a single FULL `/codexreview` over the entire batch diff, which team mode reaches via "Post-batch — same as sequential mode"). To make per-card always-full again, restore `profile = full`; the final full gate stays regardless.
2249
2338
 
2250
2339
  5. **D.5 — Commit** — One commit per card using explicit staging from the ownership map. **NO stash in worktrees** (stashes are globally shared via `refs/stash` — see Phase 4 WORKTREE COMMIT RULE):
2251
2340
  ```bash
2252
2341
  cd <worktree-path>
2253
2342
  # For each card in the group:
2254
2343
  git add <card's-files-only>
2255
- git commit -m "[CARD-ID] Brief description"
2344
+ # Disambiguate "nothing to commit" from a hook rejection BEFORE committing:
2345
+ if [ -z "$(git status --porcelain)" ]; then
2346
+ echo "D.5 <CARD-ID>: nothing staged — already committed upstream; record HEAD and skip"
2347
+ else
2348
+ git commit -m "[CARD-ID] Brief description"
2349
+ fi
2256
2350
  ```
2351
+ If `git commit` itself fails with a non-empty staging area (a pre-commit / lint-staged hook rejected it), apply the SAME bounded handler as sequential Phase 4 step 27: clear a stale `COMMIT_LOCK`, re-stage explicitly, retry at most **2 times**, then log `[COMMIT-BLOCKED]` and escalate via `AskUserQuestion` — never loop unbounded and never silently leave a card uncommitted.
2257
2352
 
2258
2353
  6. **D.6 — Update backlog (MANDATORY — do NOT skip)** — For EACH card in the group:
2259
2354
  a. Edit the backlog YAML (`${paths.backlog_dir}/<CARD-ID>.yml`): set `status: DONE`, add `completed_date: <today>`, add implementation notes (NEVER include `[USER-APPROVED DEFERRAL]` lines that didn't actually pass through D.3a's gate).
@@ -2301,42 +2396,54 @@ After all groups are complete, run the same Final Review, Phase 6 (merge), and P
2301
2396
 
2302
2397
  ---
2303
2398
 
2304
- ## Phase 8 — Metrics Log (MANDATORY — runs after Phase 7)
2399
+ ## Phase 8 — Metrics Log (MONITORING SIGNAL non-blocking — runs after Phase 7)
2305
2400
 
2306
2401
  After the Production Readiness Checklist is complete, log this batch run to the skill
2307
2402
  effectiveness registry. This enables tracking of first-attempt success rate, actionability,
2308
- and QA quality over time.
2403
+ and QA quality over time. This is a telemetry write, NOT a gate — it never blocks the batch (see
2404
+ the fail-safe note at the end of the phase). It was previously labelled "MANDATORY", but since it
2405
+ has no blocking consequence the honest label is "monitoring signal".
2309
2406
 
2310
2407
  **Steps:**
2311
2408
 
2312
- 1. Read the batch tracker file (`/tmp/batch-tracker-<FIRST-CARD-ID>.md`) to extract:
2409
+ 1. Read the batch tracker file (`/tmp/batch-tracker-<FIRST-CARD-ID>.md`) to extract — every field below has a NAMED upstream producer; read the structured key, do NOT re-parse prose:
2313
2410
  - Start timestamp (from `Started:` field)
2411
+ - Worktree-creation timestamp (from `## Worktree` — the time Phase 0/Pre-flight created the worktree; the strategy-independent cycle-time anchor, see step 2)
2314
2412
  - Card list and total count
2315
- - For each completed card: fix_cycles, qa_profile, qa_verdict, findings_total, findings_verified, blockers_count
2316
- - Merge commit hash
2413
+ - For each completed card:
2414
+ - `fix_cycles` (from the per-phase retry counts logged in `## Completed Cards`)
2415
+ - `qa_profile`, `qa_verdict` (Phase 3.5 step 25 / D.4 QA line)
2416
+ - `qa_first_attempt` (Phase 3.5 step 25 named flag — `pass`/`fail`/`n/a`)
2417
+ - `doc_gaps: found=<N> fixed=<M>` (Phase 3 step 15 / D.4a named counter)
2418
+ - Batch-level `review_findings: total / verified / blockers` (Final Review F.5 step 13 named counters — the canonical source for `findings_total` / `findings_verified` / `blockers_count`)
2419
+ - Merge commit hash (and, when `git.merge_strategy: pr`, the PR merge timestamp recorded by Phase 6 — see step 2)
2420
+
2421
+ If any named field is absent (e.g. a legacy tracker), record `0`/`n/a` for that field and append `metrics_gaps: <field>` to the JSONL row's notes — do NOT invent a value by re-parsing free-form prose.
2317
2422
 
2318
2423
  2. Compute aggregate metrics:
2319
2424
  - `first_attempt_success_rate`: cards with fix_cycles == 0 / total_cards
2320
2425
  - `mean_fix_cycles`: mean of per-card fix cycle counts
2321
2426
  - `qa_profiles`: count of {skip, light, balanced, deep}
2322
- - `qa_pass_first_attempt_rate`: cards with qa_verdict PASS on first attempt / total
2323
- - `findings_total` / `findings_verified`: sum across all cards
2324
- - `actionability_rate`: findings_verified / findings_total (conservative proxy; 0 if no findings)
2427
+ - `qa_pass_first_attempt_rate`: cards with `qa_first_attempt == pass` / cards where qa ran (exclude `n/a` SKIP/LIGHT cards from the denominator) — read the named flag from step 1, do NOT re-infer from `## Fix Application Log` rows
2428
+ - `findings_total` / `findings_verified`: from the batch-level `review_findings` named counter (Final Review), NOT a per-card re-sum (which would conflate QA gate counts with codex review findings)
2429
+ - `actionability_rate`: findings_verified / findings_total (conservative proxy — a lower-bound on reviewer precision, since some VERIFIED findings end as NEEDS_MANUAL_CONFIRMATION and never produce a fix; 0 if no findings)
2325
2430
  - `severity_p0_pct`: blockers_count / findings_total (0 if no findings)
2326
- - `cycle_time_mins`: minutes from `Started:` timestamp to merge commit time (use `git log --format=%ci <hash>`)
2431
+ - `cycle_time_mins`: minutes from the **strategy-independent anchor** to batch completion. Use the **worktree-creation timestamp** (Phase 0/Pre-flight) as the start, NOT `Started:` (which precedes workspace hygiene) and NOT a merge-commit hash (which varies by `git.merge_strategy` — under `pr` the merge commit is created asynchronously by GitHub CI and may not exist locally at Phase 8 time). End anchor: read the `merge_ts` field Phase 6 step 6 recorded under `## Worktree Merges` (it is already strategy-resolved there — local merge-commit time for `local-push`, PR merge completion time for `pr`). If `merge_ts: pending` (a `pr` merge GitHub has not yet finalized), fall back to "now" (`date -u`). Never recompute from a raw merge-commit hash that may be the PR HEAD rather than the actual merge commit.
2432
+
2433
+ > `$METRICS` = `paths.metrics` from `baldart.config.yml` (default `docs/metrics`). All paths below resolve under `$METRICS` — never a hardcoded `docs/metrics`.
2327
2434
 
2328
- 3. Write ONE JSON line to `docs/metrics/skill-runs.jsonl` (append):
2435
+ 3. **Assemble the full JSONL record in memory** (single atomic append — JSONL is append-only, so the row must be COMPLETE before it touches the file). If `STATS=true` (step 6), the `"cost"` key is part of THIS record — do NOT write a baseline row now and rewrite it later (a mid-file rewrite corrupts the JSONL; there is no `sed -i`/`jq` rewrite step in this skill). When `STATS=true`, defer the single append to step 6 after the cost object is computed; when `STATS=false`, append now. The record shape:
2329
2436
 
2330
2437
  ```json
2331
2438
  {"ts":"<ISO-8601-UTC>","skill":"new","run_id":"batch-<FIRST-CARD-ID>","cards":["FEAT-XXX"],"total_cards":N,"first_attempt_success_rate":0.0,"mean_fix_cycles":0.0,"qa_profiles":{"skip":0,"light":0,"balanced":0,"deep":0},"qa_pass_first_attempt_rate":0.0,"findings_total":0,"findings_verified":0,"actionability_rate":0.0,"severity_p0_pct":0.0,"doc_gaps_found":0,"doc_gaps_fixed":0,"cycle_time_mins":0,"worktree_branch":"","merge_commit":""}
2332
2439
  ```
2333
2440
 
2334
- Use `date -u +%Y-%m-%dT%H:%M:%SZ` for the timestamp. Write with `echo '...' >> docs/metrics/skill-runs.jsonl` via Bash.
2441
+ Use `date -u +%Y-%m-%dT%H:%M:%SZ` for the timestamp. Append the assembled line ONCE with `echo '<full-record>' >> "$METRICS/skill-runs.jsonl"` via Bash — never append a partial row then mutate it.
2335
2442
 
2336
2443
  4. Copy the batch tracker to archive:
2337
2444
 
2338
2445
  ```bash
2339
- cp /tmp/batch-tracker-<FIRST-CARD-ID>.md docs/metrics/archive/
2446
+ cp /tmp/batch-tracker-<FIRST-CARD-ID>.md "$METRICS/archive/"
2340
2447
  ```
2341
2448
 
2342
2449
  5. Note in tracker: `## Metrics Log: WRITTEN (run_id: batch-<FIRST-CARD-ID>)`
@@ -2347,13 +2454,13 @@ and QA quality over time.
2347
2454
 
2348
2455
  ```bash
2349
2456
  node .framework/framework/scripts/analyze-session-tokens.js \
2350
- --skill new --run-id "batch-<FIRST-CARD-ID>" --out-dir docs/metrics
2457
+ --skill new --run-id "batch-<FIRST-CARD-ID>" --out-dir "$METRICS"
2351
2458
  ```
2352
2459
 
2353
- The script reads `$CLAUDE_CODE_SESSION_ID` from the environment and derives the transcript paths itself (do NOT pass `--session` / `--project-dir` — those are test-only overrides). It writes a human-readable breakdown to `docs/metrics/sessions/batch-<FIRST-CARD-ID>.md` and prints a summary plus a final `TELEMETRY_JSON=<json>` line.
2460
+ The script reads `$CLAUDE_CODE_SESSION_ID` from the environment and derives the transcript paths itself (do NOT pass `--session` / `--project-dir` — those are test-only overrides). It writes a human-readable breakdown to `$METRICS/sessions/batch-<FIRST-CARD-ID>.md` and prints a summary plus a final `TELEMETRY_JSON=<json>` line.
2354
2461
 
2355
- Parse the `TELEMETRY_JSON=` line and **enrich the same skill-runs.jsonl row** you wrote in step 3 by adding a `"cost"` key holding that JSON object (`by_role` + `totals` + `run_wall_ms`). If the script printed `stats: SKIPPED (...)` instead, add `"cost":{"skipped":"<reason>"}` and continue. Echo the script's summary to the user so they see the per-role breakdown.
2462
+ Parse the `TELEMETRY_JSON=` line and add a `"cost"` key (holding that JSON object: `by_role` + `totals` + `run_wall_ms`) to the in-memory record from step 3, then **append the complete record ONCE** to `$METRICS/skill-runs.jsonl` (this is the deferred single atomic append from step 3 — there is NO already-written row to rewrite, so JSONL semantics stay intact: exactly one line per `run_id`). If the script printed `stats: SKIPPED (...)` instead, set `"cost":{"skipped":"<reason>"}` and append the single record. Echo the script's summary to the user so they see the per-role breakdown.
2356
2463
 
2357
- **If `docs/metrics/skill-runs.jsonl` does not exist**: create it first with `touch docs/metrics/skill-runs.jsonl`.
2464
+ **If `$METRICS/skill-runs.jsonl` does not exist**: create it first with `mkdir -p "$METRICS" && touch "$METRICS/skill-runs.jsonl"`.
2358
2465
  **If batch tracker is missing or unreadable**: log "Metrics Log: SKIPPED (tracker not found)" and proceed without blocking.
2359
2466
  **This phase is NON-BLOCKING** — if it fails for any reason, do not abort the run. The `-stats` step in particular is best-effort: the script is fail-safe (always exits 0), so never let it block the commit.