baldart 4.58.0 → 4.60.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 (29) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +1 -1
  3. package/VERSION +1 -1
  4. package/framework/.claude/agents/REGISTRY.md +2 -0
  5. package/framework/.claude/agents/codebase-architect.md +9 -0
  6. package/framework/.claude/agents/doc-reviewer.md +11 -0
  7. package/framework/.claude/agents/plan-auditor.md +11 -0
  8. package/framework/.claude/agents/senior-researcher.md +10 -0
  9. package/framework/.claude/skills/new/SKILL.md +63 -3
  10. package/framework/.claude/skills/new/references/codex-gate.md +2 -2
  11. package/framework/.claude/skills/new/references/commit.md +2 -2
  12. package/framework/.claude/skills/new/references/completeness.md +5 -3
  13. package/framework/.claude/skills/new/references/final-review.md +37 -7
  14. package/framework/.claude/skills/new/references/implement.md +2 -2
  15. package/framework/.claude/skills/new/references/merge-cleanup.md +18 -1
  16. package/framework/.claude/skills/new/references/production-readiness.md +17 -2
  17. package/framework/.claude/skills/new/references/review-cycle.md +20 -7
  18. package/framework/.claude/skills/new/references/setup.md +11 -5
  19. package/framework/.claude/skills/new/references/team-mode.md +9 -7
  20. package/framework/.claude/workflows/new-card-review.js +75 -18
  21. package/framework/.claude/workflows/new-final-review.js +212 -15
  22. package/framework/agents/index.md +2 -0
  23. package/framework/agents/return-contract-protocol.md +130 -0
  24. package/framework/templates/agent-return-contract.snippet.md +30 -0
  25. package/framework/templates/baldart.config.template.yml +11 -0
  26. package/package.json +1 -1
  27. package/src/commands/configure.js +13 -0
  28. package/src/commands/doctor.js +33 -0
  29. package/src/commands/update.js +3 -0
@@ -67,7 +67,7 @@ so it surfaces in telemetry.
67
67
  CLASSIFICATION FIRST, then domain** (so a needs-manual doc finding reaches the human gate, not the
68
68
  automated doc re-review):
69
69
  - `classification == NEEDS_MANUAL_CONFIRMATION` (any domain) → `AskUserQuestion` — the human gate the
70
- workflow cannot run. (`summary.needsManual` counts these, doc included.)
70
+ workflow cannot run. (`summary.needsManual` counts these, doc included.) **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=blocker; recommended=none safe (a NEEDS_MANUAL finding has no automated resolution) → materialize a follow-up card carrying the finding (NEVER treat it as VERIFIED / silently drop it).**
71
71
  - else `domain == doc` residual → **the workflow's Doc stage (since v4.56.0) already ran the doc-reviewer in audit-and-apply mode** over the wave diff and applied the doc fixes in the worktree (uncommitted — you stage them at Phase 4 via `perCard.docFixesApplied`). A doc `residual` here means the workflow's doc-reviewer flagged a gap it could **not** resolve — carry ONLY that into a targeted Phase 3 doc pass; do NOT re-run a full per-wave doc review (it's done).
72
72
  - else `security` residual (a fix not converged in 2 retries) → spawn a targeted `security-reviewer`
73
73
  now over this card's `editableFiles` (it owns the security-invariant contract — never a coder).
@@ -76,7 +76,7 @@ so it surfaces in telemetry.
76
76
  - **QA gate (BLOCKING — mirror of inline Phase 3.5 step 24)**: if `gateTable` has any `status:"FAIL"`
77
77
  **OR** `summary.checksFailed` is true, the merge gate is NOT satisfied. Spawn a `coder` on the
78
78
  failing gates / post-fix regressions (≤2 retries, re-run via the workflow or inline qa-sentinel),
79
- then if still failing → `AskUserQuestion` (proceed/stop). **Do NOT proceed to Phase 4 commit until
79
+ then if still failing → `AskUserQuestion` (proceed/stop). **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=blocker; recommended=none safe (a red QA gate is a merge blocker) → materialize a follow-up card and do NOT commit this card (NEVER force-proceed past a FAIL gate).** **Do NOT proceed to Phase 4 commit until
80
80
  `gateTable` is all PASS/SKIP and `checksFailed` is false** — a delegated QA FAIL must block exactly
81
81
  as the inline path does. (`gateTable` is **wave-scoped**, not per-card — qa-sentinel runs once over
82
82
  the worktree; a FAIL applies to the whole wave's commit.)
@@ -137,7 +137,7 @@ After completeness is verified, clean up the implementation before it reaches re
137
137
  **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>`.
138
138
 
139
139
  5. After all fixes, run `npm run lint` and `npx tsc --noEmit` (when `has_toolchain`, the configured `toolchain.commands.{lint,typecheck}` verbatim — § "Toolchain gates") to confirm nothing broke (redirect to disk per § "Context economy"; surface only exit code + a bounded extract on failure).
140
- 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.
140
+ 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. **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=reversible; recommended=revert the simplify fixes (Simplify is quality-only — reverting loses no merge-gate coverage and restores a green tree), log `AUTO: simplify-regression → reverted`.**
141
141
 
142
142
  6. **Update tracker**: phase = "2.55-simplify DONE", log count of fixes applied (or "clean — 0 fixes").
143
143
  If any valid finding revealed a reusable pattern or common mistake, append 1-line to `## Lessons Learned`:
@@ -208,8 +208,8 @@ When the gate above passes, the orchestrator invokes `/e2e-review` in
208
208
  | `"passed"` | Log result in tracker. Proceed to Phase 3 (doc review + code review). |
209
209
  | `"skipped"` | Log skip reason. Proceed to Phase 3. |
210
210
  | `"overridden"` | Log override + reason in `## Issues & Flags` as `[E2E-OVERRIDE] <reason>`. Proceed to Phase 3. |
211
- | `"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. |
212
- | `"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. |
211
+ | `"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. **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=blocker; recommended=none safe (a blocked E2E is a real gating failure) → take (b) escalate to a follow-up card; NEVER take (a) override autonomously.** |
212
+ | `"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. **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=blocker; recommended=retry once (the capped re-entry above); if still `error` → skip the E2E for this card AND materialize a follow-up card to re-run it (NEVER abandon the card silently).** |
213
213
 
214
214
  #### Re-run trigger (after Phase 3.7 `/codexreview`)
215
215
 
@@ -308,7 +308,7 @@ skill's Phase 1 falls back to deriving Gherkin scenarios from
308
308
  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 `security-reviewer` with the finding now, in this phase (a security-class code fix is not deferrable to a `light` Phase 3.7, and security is owned by `security-reviewer` — never a coder); 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.
309
309
  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.
310
310
  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.)
311
- 16. Run `npm run lint` and `npx tsc --noEmit` (when `stack.language` includes typescript) — when `has_toolchain`, the configured `toolchain.commands.{lint,typecheck,build}` verbatim (§ "Toolchain gates") — to verify nothing broke (redirect to disk per § "Context economy"). 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).
311
+ 16. Run `npm run lint` and `npx tsc --noEmit` (when `stack.language` includes typescript) — when `has_toolchain`, the configured `toolchain.commands.{lint,typecheck,build}` verbatim (§ "Toolchain gates") — to verify nothing broke (redirect to disk per § "Context economy"). 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). **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=reversible; recommended=revert the doc-phase edits that broke the build (restores a green tree; the doc gap re-surfaces at the Final FULL doc-reviewer), log `AUTO: doc-phase-regression → reverted`.**
312
312
  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.
313
313
  If doc-reviewer found a recurring gap, append 1-line to `## Lessons Learned`:
314
314
  `DOC: <pattern>`
@@ -353,7 +353,20 @@ skill's Phase 1 falls back to deriving Gherkin scenarios from
353
353
  24. **If QA verdict is FAIL** (one or more gates failed):
354
354
  - 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.
355
355
  - After coder fixes, re-invoke `qa-sentinel` in the same mode to re-validate. Repeat up to **2 times**.
356
- - If still FAIL after 2 retries: log in `## Issues & Flags` and **ask the user** whether to proceed or stop.
356
+ - If still FAIL after 2 retries: log in `## Issues & Flags` and **ask the user** whether to proceed or stop. **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=blocker; recommended=none safe (a failing QA gate is a merge blocker) → materialize a follow-up card and stop this card's commit (NEVER force-proceed past a FAIL verdict).**
357
357
  - The commit in Phase 4 MUST NOT happen until QA verdict is PASS (or user explicitly overrides).
358
358
  - **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`.
359
359
  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.
360
+
361
+ #### Per-card review-cluster coverage assertion (MANDATORY before Phase 4 commit — mirror of team-mode Step D)
362
+
363
+ Before the Phase 4 commit, the orchestrator MUST verify the tracker contains, for THIS card, the review-cluster producer entries — **present, or an explicit skip with a documented enumerated reason** (exactly as team mode's "Step D coverage assertion" enforces for the parallel path). This closes the same FEAT-0006-class hole on the sequential path: a card whose cluster was silently inline-gated and committed with no review producer.
364
+
365
+ - `simplify: <N fixes | clean — 0 fixes | SKIPPED (trivial — non-source diff) | decision=workflow>` (Phase 2.55 / delegated workflow)
366
+ - `code-review: / codex-review: <verdict (light) | verdict (full) | SKIPPED (trivial — non-source diff; covered by Final FULL gate) | decision=workflow>` (Phase 3.7 / delegated workflow)
367
+ - `qa: <profile=… verdict=… | DEFERRED to Final FULL gate (light/balanced, no risk escalation) | decision=workflow>` (Phase 3.5 / delegated workflow)
368
+ - `doc-review: <status | DEFERRED to Final FULL gate (light + no-doc diff) | decision=workflow>` (Phase 3 / delegated workflow Doc stage)
369
+
370
+ A `decision=workflow` provenance row (the Phase 2.5x delegated path) is a **truthful** producer synthesized from the `new-card-review` return — never a placeholder. A missing entry, or a skip whose reason is `time budget` / `to save tokens` / any model-invented constraint, is a protocol violation: do NOT commit — return to the missing sub-step and run it.
371
+
372
+ > **Test-only diff is SOURCE, NOT trivial.** A card whose committed diff touches ONLY `*.test.*` / `*.spec.*` files is **source** (per `codex-gate.md`'s trivial definition, which already classifies `.test.ts` as source) — it is **NOT** `IS_TRIVIAL` and MUST run the full review cluster (delegate it per Phase 2.5x, or run the inline Phase 2.55→3.7 fallback). A test-only card therefore CANNOT take the trivial fast-lane's inline mechanical gates + commit; the assertion above blocks it until the cluster ran (or was delegated). This prevents a test-only change being silently inline-gated and committed without review.
@@ -6,7 +6,9 @@
6
6
 
7
7
  **Why this exists**: FEAT-0006 incident left the main repo with an unpushed orphan commit (`c9d41f9`) and `develop` diverged from `origin/develop` because `/new` started work on a dirty/diverged repo without surfacing the situation. This phase reconciles the main repo state BEFORE any worktree is created, and snapshots any in-progress user work so Phase 6c can restore it after the batch.
8
8
 
9
- **Auto Mode does NOT override this phase.** Every `AskUserQuestion` below is non-bypassable — the model has no authority to pick an option on the user's behalf.
9
+ **Auto Mode does NOT override this phase.** Every `AskUserQuestion` below is non-bypassable in the interactive path — the model has no authority to pick an option on the user's behalf.
10
+
11
+ > **When AUTONOMOUS** (per § AUTONOMOUS RESOLUTION RULE + § AUTONOMOUS SAFETY BOUNDARY, SKILL.md), the orchestrator still runs every Phase 0 gate — it does NOT skip workspace hygiene — but resolves each by the per-action disposition instead of `AskUserQuestion`: reversible/local choices (stash with a labeled message, ff-pull, push genuine unpushed commits) are AUTO-RUN; anything needing human judgment (a genuine divergence with no safe default, a missing required card field) materializes a follow-up card / HALTs rather than guessing; and the **Migration Gate's remote schema deploy is treated per § AUTONOMOUS SAFETY BOUNDARY** — see step 1b. Each auto-resolution is logged to `## Issues & Flags` as `AUTO: <gate> → <chosen> (<evidence>)`.
10
12
 
11
13
  **Steps:**
12
14
 
@@ -24,6 +26,7 @@
24
26
  3. **(declared) Verify the artifacts exist on disk.** For each path in `migration_plan.artifacts`, check it exists under `$MAIN`. If **any is missing** → do **NOT** apply or prompt; write `## Migration\ndeclared but artifact(s) missing: <paths> — degraded to deferred (author the migration first)` to the tracker and **proceed to step 2** (the migration falls back to the current end-of-batch owner-gated deferral — no regression). *(Auto-generating a missing artifact is out of scope; the migration must already be authored to be applied up-front.)*
25
27
  4. **(declared + artifacts present) Assemble the apply modalities**, in this source order, de-duplicating by `id`: (a) `migration_plan.apply_modalities` from the epic block; (b) a `## Migration modalities` section in `.baldart/overlays/new.md` if present; (c) any project-memory note on how this project applies migrations; (d) the built-in tail `["Già applicata — prosegui", "Abort"]`. Each modality is `{ id, label, command? }`.
26
28
  5. **`AskUserQuestion`** — `"La epic dichiara una migrazione DB (<summary>). Va applicata PRIMA del batch così le card downstream verificano contro lo schema reale. Come procedo?"` with up to 4 of the assembled modalities (always include "Già applicata — prosegui" and "Abort" as the last options). This is a legitimate Phase 0 question (Auto Mode does not override it).
29
+ **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE + § AUTONOMOUS SAFETY BOUNDARY (SKILL.md): a *command* modality is a remote schema deploy (PRE-AUTH action). category=outward; recommended=DEFER — do NOT execute the command modality. Degrade the front-load to the end-of-batch owner-gated deferral exactly as the missing-artifact / off-trunk-guard branches do: write `## Migration\ndeclared but AUTONOMOUS without AUTO_SHIP authorization — front-load deferred (apply after merge / from trunk)` and proceed to step 2. A "Già applicata — prosegui" disposition (no deploy) stays available — pick it only when a deterministic signal (the optional `verify` probe green) proves the schema is already live; otherwise DEFER. EXECUTE the command modality ONLY when `AUTO_SHIP` is set AND `git.auto_deploy` lists this deploy target/intent AND the `schema_deploy_from_trunk_only` guard (step 6) still passes — `git.auto_deploy` layers ON TOP of the branch invariant, never replaces it.**
27
30
  6. **Execute the choice in `$MAIN`** (project env):
28
31
  - **Schema-deploy-from-trunk-only guard** (when `stack.schema_deploy_from_trunk_only: true`): a **command** modality is a remote schema deploy, so it may run only from trunk. Before executing one, check `git -C "$MAIN" rev-parse --abbrev-ref HEAD`; if it is **not** `git.trunk_branch`, do **NOT** apply or prompt for a command modality — write `## Migration\ndeclared but $MAIN is on '<branch>' (not trunk '<trunk>') — front-load deferred per schema_deploy_from_trunk_only (apply from trunk, then re-run)` to the tracker and **proceed to step 2** (the migration falls back to the end-of-batch owner-gated deferral — no regression). "Già applicata — prosegui" stays available (it executes no deploy). When the key is `false`/unset this guard is a no-op.
29
32
  - a **command** modality → run it with output to disk (`<cmd> > /tmp/migration-<FIRST-CARD-ID>.log 2>&1`), surface only the exit code; on exit 0 run the optional `migration_plan.verify` probe and require it green too. On **failure** → surface a bounded extract (`tail -n 30`) and re-ask (re-offer the modalities + Abort); never silently proceed against a non-live schema. **Never run a command without the user having selected it.**
@@ -59,6 +62,7 @@
59
62
  **Why**: Phase 8 copies the batch tracker into `$METRICS/archive/` and appends to `$METRICS/skill-runs.jsonl` *after* the merge, and never commits them. And `baldart update`/`add` regenerate `.baldart/generated/*` + bump `.baldart/state.json` on every framework sync — left uncommitted until the next reconcile. If either class tripped this gate, **every batch would block on the previous update's own output** — a self-inflicted loop. They are not user work and the user is never asked about them.
60
63
  - If `$PORCELAIN` is non-empty but `$USER_DIRTY` is **empty** (only telemetry under `$METRICS` and/or BALDART-managed `.baldart/` artifacts are dirty) → do **NOT** gate. Write `Dirty: framework-managed-only (auto-ignored)` to the tracker and proceed to step 4. No `AskUserQuestion`.
61
64
  - If `$USER_DIRTY` is **non-empty** (genuine uncommitted user work) → invoke `AskUserQuestion`:
65
+ - **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=reversible; recommended=option 1 "Stash automatico (restore in Phase 6c)" — AUTO-RUN (reversible, the labeled stash is restored post-batch); log `AUTO: dirty-tree → stashed (<label>)`. NEVER option 3 "Procedi senza stash" autonomously (it accepts responsibility a human must own).**
62
66
  - Question: `"Main repo ($MAIN) ha modifiche non committate prima di partire con la batch. Come procedo?"`
63
67
  - Options (max 4):
64
68
  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}`.
@@ -95,6 +99,8 @@
95
99
  ```
96
100
  `$FW_SKIPPED` framework-management commits are auto-ignored (log them — see below). Branch on `$BEHIND` and the **genuine** ahead count `$GENUINE_AHEAD` (NOT the raw `$AHEAD`):
97
101
 
102
+ > **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE + § AUTONOMOUS SAFETY BOUNDARY (SKILL.md) at each branch below:** behind-only → ff-pull (AUTO-RUN, only when `$MAIN` HEAD == `$TRUNK`); ahead-only → push the genuine commits to `origin/$TRUNK` (AUTO-RUN, FF only) — `Cherry-pick` / `Reset --hard` are NOT auto-selected (reset-hard is HARD-NEVER); diverged-both-ways → no safe default → materialize a follow-up card and proceed without reconciling (NEVER auto-rebase/reset). Log `AUTO: divergence → <chosen> (<evidence>)`. `Abort` is never auto-selected (an unattended run does not silently abort a hygiene gate it can defer).
103
+
98
104
  - **`BEHIND=0` and `GENUINE_AHEAD=0`** — synchronized for `/new`'s purposes. If `$FW_SKIPPED > 0`, write `Divergence: $FW_SKIPPED framework-management commits ahead (auto-ignored — baldart update/subtree)` to the tracker. **No `AskUserQuestion`.** Proceed to step 5.
99
105
  - **`BEHIND>0` and `GENUINE_AHEAD=0`** (only behind) — invoke `AskUserQuestion`:
100
106
  - Question: `"Local $TRUNK è behind origin/$TRUNK di $BEHIND commit. Come procedo?"`
@@ -132,7 +138,7 @@
132
138
  **1b-i — HALT on non-derivable fields (ask the user).** Verify the fields that cannot be safely synthesized are present and non-empty:
133
139
  - `requirements` (>=1), `acceptance_criteria` (>=1), `files_likely_touched` (>=1 file), and `scope`.
134
140
  - `scope_boundaries` is **NOT** in this set (it is conditional — legitimately omitted for standalone cards with no siblings; see card-schema.md).
135
- If any card fails: log the specific missing fields in `## Issues & Flags`, ask the user to fill them in before proceeding with that card, and continue pre-flight for any remaining valid cards.
141
+ If any card fails: log the specific missing fields in `## Issues & Flags`, ask the user to fill them in before proceeding with that card, and continue pre-flight for any remaining valid cards. **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=blocker; recommended=none safe (non-derivable fields cannot be fabricated — SC-4 "missing fields → SKIP+report, never fabricate") → skip that card from the batch and log `AUTO: card-baseline → skipped <CARD-ID> (missing <fields>)`; continue with the valid cards. NEVER invent requirements/AC/scope.**
136
142
 
137
143
  **1b-ii — Back-fill deterministically-computable fields (compute → persist → log).** For each non-epic card missing a derivable field, compute it and **write it back to the card on disk in `$MAIN/${paths.backlog_dir}`** (the main repo path resolved at Phase 0 step 1 — NOT the worktree copy; same F-040 discipline `new2` uses for follow-ups):
138
144
  - `review_profile` absent → compute via `prd-card-writer.md § Rule C` (the SSOT), write it.
@@ -142,10 +148,10 @@
142
148
  - **Commit discipline:** after all cards are processed, if ANY back-fill was written, commit them together in `$MAIN` with the COMMIT_LOCK + doc-freshness precautions of `merge-cleanup.md` Phase 6b (clear stale `COMMIT_LOCK`; `git add` only the back-filled card YAMLs; if the doc-freshness hook blocks, stage `ssot-registry.md`; ≤2 retries): `git commit -m "chore(backlog): normalise card baseline [BACKFILL]"`. Do NOT leave `$MAIN` dirty for the batch duration — an uncommitted backlog edit would surface as noise in the Phase 3/4 git-diff gates.
143
149
  - `canonical_docs` / `links.prd` absent on a standalone/non-PRD card → **WARN** only (log, do not block, do not back-fill).
144
150
 
145
- **1b-iii — Conformance validation.** Run the framework validator `node .framework/framework/scripts/validate-card-baseline.js <card-yaml-path>` (or the path resolved for your install) against each card. It is profile-aware (epic/child/standalone) and catches what 1b-i/1b-ii could not — invalid enum values (`review_profile` ∉ `{skip,light,balanced,deep}`, `owner_agent` ∉ Rule A enum), or a REQUIRED field still missing. Exit 1 → display the per-field errors in `## Issues & Flags` and HALT that card (continue others). If the validator is not reachable in this install, skip with a one-line note (the 1b-i/1b-ii gates still applied).
151
+ **1b-iii — Conformance validation.** Run the framework validator `node .framework/framework/scripts/validate-card-baseline.js <card-yaml-path>` (or the path resolved for your install) against each card. It is profile-aware (epic/child/standalone) and catches what 1b-i/1b-ii could not — invalid enum values (`review_profile` ∉ `{skip,light,balanced,deep}`, `owner_agent` ∉ Rule A enum), or a REQUIRED field still missing. Exit 1 → display the per-field errors in `## Issues & Flags` and HALT that card (continue others). **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=blocker; recommended=none safe (a conformance error is a data defect the orchestrator must not fabricate around) → skip that card from the batch and log `AUTO: card-conformance → skipped <CARD-ID>`; continue with the valid cards.** If the validator is not reachable in this install, skip with a one-line note (the 1b-i/1b-ii gates still applied).
146
152
 
147
153
  1c. **Field Registry Validation (pre-flight gate)** — for each card with a `data_fields` block, run the project's field-validation tool if available (path listed in `.baldart/overlays/new.md`; typically `python3 tools/validate-card-fields.py <card-yaml-path>`).
148
- - If exit 1: display the field errors in `## Issues & Flags` and HALT — ask the user to fix the card before proceeding. Do not start implementation until the card passes validation.
154
+ - If exit 1: display the field errors in `## Issues & Flags` and HALT — ask the user to fix the card before proceeding. Do not start implementation until the card passes validation. **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=blocker; recommended=none safe (a field-registry error is a data defect the orchestrator must not fabricate around) → skip that card from the batch and log `AUTO: field-validation → skipped <CARD-ID>`; continue with the valid cards.**
149
155
  - If the card has DB-index signals (`db_indexes` — or legacy `firestore_indexes` — or `data.collections`/`data.tables`) but NO `data_fields` block: log WARNING in `## Issues & Flags` — "Card `<ID>` touches the persistence layer but has no `data_fields` block. Field/column names are unvalidated."
150
156
  - Cards with no `data_fields` and no storage signals: skip silently.
151
157
  - If the project does not ship a field-validation tool: skip this step.
@@ -259,7 +265,7 @@
259
265
  ```
260
266
  Parse PASS / `CODEX_NOT_FOUND` / conflict-findings from that filtered output only. When logging to the tracker, write the **distilled findings** (one line per conflict: cards + type + one-line fix) — never paste the raw block, and never the trace.
261
267
  - Read the exact `$AUDIT_FILE` path recorded in the tracker after the background command completes (always via the trace-stripping filter above).
262
- - 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.
268
+ - 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. **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=blocker; recommended=use the `code-reviewer` fallback (the deterministic safe default); only if BOTH Codex and `code-reviewer` are unavailable → proceed without cross-card detection but log `AUTO: cross-card-review → unavailable, proceeded (CAPABILITY_UNAVAILABLE)` to `## Issues & Flags` (an unattended run cannot halt indefinitely on a missing capability; the per-card + Final gates remain).**
263
269
  - If **PASS** or file empty: proceed normally.
264
270
  - If **conflicts found**: log in tracker under `## Cross-Card Conflicts (Codex)` and present to user. For each conflict:
265
271
  - `FILE_CONFLICT` / `ORDER_RISK` → force the conflicting cards sequential (update file-ownership map).
@@ -6,6 +6,8 @@
6
6
 
7
7
  When the complexity assessment (step 3c) selects team mode, the orchestrator changes role: instead of executing each card's pipeline sequentially, it coordinates parallel coder agents — each with its OWN isolated context window.
8
8
 
9
+ > **When AUTONOMOUS, team-mode is FORCED to sequential single-worktree** (per § AUTONOMOUS RESOLUTION RULE, SKILL.md — cost/predictability: a wide parallel fan-out saturates the shared API rate-limit pool and kills coders mid-work, paying twice; an unattended run cannot afford that non-determinism). So under `-auto`/`--auto`/`BALDART_AUTONOMOUS`/`CI`, step 3c's team-mode decision is overridden to `sequential` regardless of card count / group width, and the per-card pipeline (the rest of `/new`) runs instead of this section. Log `AUTO: execution-mode → sequential (AUTONOMOUS forces single-worktree)` in `## Execution Mode`. This section applies ONLY to the non-AUTONOMOUS interactive path.
10
+
9
11
  **Key principle**: the orchestrator stays LEAN. It holds only:
10
12
  - The tracker file path
11
13
  - Parallel group status (pending/active/done)
@@ -154,12 +156,12 @@ For each completed agent:
154
156
  `D="$(ls -dt ~/.claude/projects/*/${CLAUDE_SESSION_ID:-*}/subagents 2>/dev/null | head -1)"; f="$(grep -l '"<teammate-label>"' "$D"/*.meta.json 2>/dev/null | head -1 | sed 's/\.meta\.json$/.jsonl/')"; tail -c 4000 "$f"`
155
157
  (if `$CLAUDE_SESSION_ID` is unset, the most recent `subagents/` dir under this project is the live session's.) If the transcript truly cannot be located, fall back to whatever the `Agent` tool result surfaced — but do NOT default to "genuine empty-result" without having looked. **State the classification you reached + the evidence in your narration** (e.g. "03 last event = `Rate limited` → transient").
156
158
  - **Transient infra failure (the common cause of a silent empty rest in a WIDE parallel wave).** The agent's last event is an API error — signatures: `isApiErrorMessage:true`, `Rate limited`, `Server is temporarily limiting requests`, `overload`, `(not your usage limit)` — i.e. the background teammate was **killed mid-flight by API rate-limiting** (it had done real work — reads, a plan — then died), NOT a model fabrication or "decided nothing to do". The runtime does NOT auto-resume a dead teammate, so it rests with no report + no diff. **Re-spawn it, but STAGGERED with backoff — do NOT re-fire several transient-failed agents in the same parallel burst** (that re-saturates the API and kills them again). Space them out (one at a time, or after a brief pause). This still costs a fresh run (the killed agent's reads are lost — the runtime cannot resume it), so **prevention beats recovery**: if a wave repeatedly hits rate limits, NARROW the parallel fan-out width for that wave (spawn fewer coders at once) rather than re-firing the full wave. A transient failure does NOT consume the Step-B genuine-failure budget.
157
- - **Genuine empty-result / fabrication.** The agent rested CLEANLY (no error in its last event), with no completion report and no diff. THIS is the model-fault case: take the one Step-B re-spawn below (same cap), re-briefing the coder with an explicit "write your ownership files and confirm them on disk before reporting done" mandate. A second empty result → `AskUserQuestion` (skip/abandon) — do not re-spawn a third time.
159
+ - **Genuine empty-result / fabrication.** The agent rested CLEANLY (no error in its last event), with no completion report and no diff. THIS is the model-fault case: take the one Step-B re-spawn below (same cap), re-briefing the coder with an explicit "write your ownership files and confirm them on disk before reporting done" mandate. A second empty result → `AskUserQuestion` (skip/abandon) — do not re-spawn a third time. **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=blocker; recommended=none safe (a card whose coder produced nothing twice has no implementation) → skip the card AND materialize a follow-up card carrying its spec (NEVER mark it DONE / no-op).** (Classify the cause first — a transient rate-limit death is re-spawned staggered, not counted against this budget.)
158
160
 
159
161
  **If an agent fails** (status: failed after 3 retries — the central repair cap):
160
162
  - Log failure in tracker `## Issues & Flags`.
161
163
  - Other agents in the group continue unaffected.
162
- - After group completes, ask user: retry failed card or skip?
164
+ - After group completes, ask user: retry failed card or skip? **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=blocker; recommended=take the one Step-B re-entry below (the central re-try cap); if it fails again → skip the card AND materialize a follow-up card (NEVER drop it silently).**
163
165
  - **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.
164
166
 
165
167
  #### Step D: Post-group review + QA (ALL sub-steps MANDATORY)
@@ -228,7 +230,7 @@ After ALL agents in the group complete successfully:
228
230
  residual → targeted `security-reviewer`; else unconverged code/perf residual → targeted `coder`).
229
231
  Apply the **same BLOCKING QA-gate consumption**:
230
232
  `gateTable` with any `status:"FAIL"` OR `summary.checksFailed` → coder fix (≤2 retries) then
231
- `AskUserQuestion`; **D.5 commit MUST NOT happen until `gateTable` is PASS/SKIP and `checksFailed` is
233
+ `AskUserQuestion` (**when AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE, SKILL.md: category=blocker; recommended=none safe → follow-up card, do NOT commit the wave's affected card; NEVER force-proceed past a FAIL**); **D.5 commit MUST NOT happen until `gateTable` is PASS/SKIP and `checksFailed` is
232
234
  false** (a delegated QA FAIL blocks exactly as inline D.4 / Phase 3.5 would — `gateTable` is
233
235
  wave-scoped). Append the workflow `fixesApplied`/`residual` to `## Fix Application Log`
234
236
  (`decision=workflow`, with `card=<ID>`).
@@ -263,14 +265,14 @@ After ALL agents in the group complete successfully:
263
265
 
264
266
  3. **D.3 — Apply fixes (group)** — If D.2 findings exist, spawn ONE fix-coder to apply all fixes in a single pass. Run build + lint after.
265
267
 
266
- 3a. **D.3a — Phase 2.5b AC-Closure Gate (per-card, BLOCKING — non-skippable)** — For EACH card in the group, **sequentially**, invoke the full Phase 2.5b gate as documented in `### Phase 2.5b — AC-Closure Gate (BLOCKING — Scope Closure Discipline)`. This includes: build the AC Closure Ledger from the card YAML, run the rationalization scan, invoke `AskUserQuestion` one-per-deferred-AC, run the `implementation_notes` deferral audit, and persist the ledger in the tracker. Until EVERY card in the group exits PASS, do NOT proceed to D.3b. Cards exiting with `not_implemented` ACs that the user routes to "Implementa adesso" must finish their fix-coder loop and re-pass the gate before D.3b starts for the next card. Log under `## AC Closure Ledger — <CARD-ID>` per card.
268
+ 3a. **D.3a — Phase 2.5b AC-Closure Gate (per-card, BLOCKING — non-skippable)** — For EACH card in the group, **sequentially**, invoke the full Phase 2.5b gate as documented in `### Phase 2.5b — AC-Closure Gate (BLOCKING — Scope Closure Discipline)`. (Its Step-4 user gate carries the AUTONOMOUS pointer in `references/completeness.md` — under AUTONOMOUS a deferred AC becomes a follow-up card, never an auto-approved deferral.) This includes: build the AC Closure Ledger from the card YAML, run the rationalization scan, invoke `AskUserQuestion` one-per-deferred-AC, run the `implementation_notes` deferral audit, and persist the ledger in the tracker. Until EVERY card in the group exits PASS, do NOT proceed to D.3b. Cards exiting with `not_implemented` ACs that the user routes to "Implementa adesso" must finish their fix-coder loop and re-pass the gate before D.3b starts for the next card. Log under `## AC Closure Ledger — <CARD-ID>` per card.
267
269
 
268
270
  3b. **D.3b — Phase 2.55 Simplify (per-card, FANNED OUT across the group)** — The Simplify agents are **read-only analysis on file-disjoint per-card diffs** (the orchestrator applies the fixes afterward), so there is NO reason to run them one card at a time. **Spawn the per-card Simplify analysis for ALL eligible cards in PARALLEL** — in a SINGLE message, fire each card's Phase 2.55 trio (Reuse / Quality / Efficiency) against that card's diff captured to `/tmp/diff-<CARD-ID>.txt` (per Phase 2.55 step 2 — pass each trio the **path**, scoped to the card's File Ownership Map; never inline the diff). Per-card (not group-aggregate) so findings stay attributable. When all analyses return, **apply fixes per card** (file-disjoint → no write conflict), then re-run `npm run lint` and `npx tsc --noEmit` (when `has_toolchain`, the configured `toolchain.commands.{lint,typecheck}` verbatim — § "Toolchain gates") on the worktree ONCE for the whole group (redirect to disk per § "Context economy"). (Concurrency is capped by the platform; passing N cards is safe — excess agents queue.)
269
271
  - **Gate (enumerated, `TRIVIAL_CARDS`-driven)**: SKIP D.3b for a card in **`TRIVIAL_CARDS`** (the set already computed at D.1.5 — `review_profile == skip` AND 0 Step-A triggers AND **non-source diff**), aligning team mode with sequential Phase 2.55's `IS_TRIVIAL` re-confirmation on the ACTUAL diff and with team-mode's own D.1.5 SSOT. A trivial card has no substantive diff to simplify, and Simplify is quality-only (no merge-gate coverage to lose). Log `simplify: SKIPPED (trivial — non-source diff)`. **A card with `review_profile == skip` whose committed diff DID touch a source file is NOT in `TRIVIAL_CARDS` → run D.3b for it** (exactly as sequential 2.55 does — `skip` is the floor, the real diff is the deciding check). For `light`/`balanced`/`deep` cards D.3b runs unchanged. This is the ONLY enumerated skip — never skip D.3b "for time" on a non-trivial card. (Skipped cards are simply omitted from the parallel fan-out.)
270
272
 
271
273
  3c. **D.3c — Phase 2.6 E2E-Review (per-card)** — First, evaluate the existing Gate table for EVERY card at once (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`). In practice most cards in a group skip this gate (backend/db/api), so the eligible set is usually 0–1. For the cards that PASS the gate, invoke `/e2e-review` in programmatic mode with that card's payload. Each `/e2e-review` keeps its own isolated state dir (`.baldart/e2e-review/<CARD-ID>/`), so multiple runs do not clobber each other's artifacts.
272
274
  - **Parallel-when-safe**: if **two or more** cards pass the gate, you MAY fan them out in parallel ONLY when they hit **disjoint routes/pages** — the genuinely shared resource is the worktree's single dev-server port, and concurrent Playwright sessions against one server are fine for disjoint routes. If the eligible cards touch overlapping routes (or `/e2e-review` spins its own server and a port clash is possible), run those **sequentially** to avoid a flaky cross-test. Default to parallel for the common disjoint case; fall back to sequential on any contention.
273
- - 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.
275
+ - 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. **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md) exactly as the Phase 2.6 `blocked`/`error` rows do (`references/review-cycle.md`): category=blocker; `blocked` → escalate to a follow-up card (never auto-override); `error` → retry once then skip-E2E + follow-up card.**
274
276
 
275
277
  4. **D.4 — QA gate (group; since v4.7.0 — runs only for `deep` / risk-escalation, else deferred to Final)** — Read each card's `review_profile` field and the D.1.5 Step-A trigger result. **Run the group qa-sentinel (once, at FULL) iff** the group's MAX profile is `deep` **OR** any card in the group had a Step-A risk escalation. **Otherwise (max profile ≤ `balanced`, no escalation) → DEFER the group QA to the Final Review F.3 qa-sentinel** (FULL suite + build + audit over the entire batch — the unconditional merge gate); log `D.4 QA: DEFERRED to Final FULL gate (group max=balanced, no risk escalation)`. This mirrors sequential step 21b. When it DOES run, invoke qa-sentinel at FULL (the combined group diff must be validated together) using the same prompt contract as Phase 3.5 step 22. **Granularity (INTENTIONAL — not a drift)**: the predicate is identical to sequential 21b (`deep`/risk-escalation → FULL, else defer to the Final FULL gate), but it is evaluated **per-GROUP** here whereas sequential evaluates it per-CARD. This is by design — qa-sentinel runs once over the group's combined diff (the wave-batched model gives it no per-card granularity), so the gate is deliberately the more conservative side: a single `deep`/risk card escalates the WHOLE group to FULL rather than just itself. No correctness gap — every card still reaches a FULL suite no later than the unconditional Final gate. ⚠️ A balanced-only group runs its first suite at the Final gate; merge safety is preserved because Final is FULL over the whole batch.
276
278
 
@@ -280,7 +282,7 @@ After ALL agents in the group complete successfully:
280
282
  - **`LIGHT_CARDS \ TRIVIAL_CARDS` → run `/codexreview` at `profile: light`** (since v4.18.0): **Codex is the sole finder** + the Step 3 FP-gate (`code-reviewer` + `codebase-architect` validate); no api-perf/doc/CoVe. This is where these cards' per-card code review now lives (D.2 no longer code-reviews them). On Codex-unavailable, `light` degrades to `code-reviewer` finder. Log `codex-review: <verdict> (light — Codex finder + FP-gate)`.
281
283
  - **`TRIVIAL_CARDS` → SKIP D.4b** (non-source diff — nothing for Codex to review; covered by the doc-reviewer group pass + the Final FULL gate). Log `codex-review: SKIPPED (trivial — non-source diff; covered by Final FULL gate)`.
282
284
  - **`FULL_CARDS` → run `/codexreview` at `profile: full`** (Codex adversarial + `code-reviewer` finder + CoVe + FP-gate). Keep the sequential one-at-a-time loop (avoid N concurrent `/codexreview`, each of which fans out multiple sub-agents → rate-limit risk). ⚠️ Since light cards now also run here, the D.4b loop is longer per group — accepted cost of moving the diff sweep onto Codex.
283
- 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`, `skip_api_perf_auditor: true` (since v4.7.0 — api-perf deferred to the Final F.3 batch-wide auditor), and `profile` resolved from D.1.5 (`light` cards → `light`, `full` cards → `full`; do NOT recompute). 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") remains the cross-card safety net for every card including the trivial ones.
285
+ 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. **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=blocker; recommended=route the residual BLOCKER/HIGH to the writer (security-reviewer/coder); if still unresolved → follow-up card and do NOT commit that card (NEVER force a PASS verdict).** 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`, `skip_api_perf_auditor: true` (since v4.7.0 — api-perf deferred to the Final F.3 batch-wide auditor), and `profile` resolved from D.1.5 (`light` cards → `light`, `full` cards → `full`; do NOT recompute). 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") remains the cross-card safety net for every card including the trivial ones.
284
286
 
285
287
  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):
286
288
  ```bash
@@ -294,7 +296,7 @@ After ALL agents in the group complete successfully:
294
296
  git commit -m "[CARD-ID] Brief description"
295
297
  fi
296
298
  ```
297
- 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.
299
+ 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. (Under AUTONOMOUS this inherits the same § AUTONOMOUS RESOLUTION RULE disposition as sequential Phase 4 step 27 — see `references/commit.md`: a persistent hook rejection has no safe default → follow-up card, never force-commit / never mark DONE.)
298
300
 
299
301
  6. **D.6 — Update backlog (MANDATORY — do NOT skip)** — For EACH card in the group:
300
302
  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).
@@ -138,6 +138,19 @@ const parseFindingsArray = (s) => {
138
138
  if (typeof s !== 'string' || !s.trim()) return null
139
139
  try { const v = JSON.parse(s.trim()); return Array.isArray(v) ? v : null } catch { return null }
140
140
  }
141
+ // Collision-safe: map a writer-returned token to the canonical prefixed finding_id.
142
+ // Accept exact equality OR a UNIQUE colon-delimited tail match (id.endsWith(':'+token)).
143
+ // A bare suffix (e.g. '001' vs 'CR-001') must NOT match → only ':'+token. 0 or >1 → return
144
+ // the token unchanged (caller logs the mismatch; never silently mis-attribute across cards).
145
+ function makeResolveId(canonIds) {
146
+ return (returned) => {
147
+ const t = String(returned == null ? '' : returned).trim()
148
+ if (!t) return returned
149
+ if (canonIds.includes(t)) return t
150
+ const hits = canonIds.filter((id) => id === t || id.endsWith(':' + t))
151
+ return hits.length === 1 ? hits[0] : returned
152
+ }
153
+ }
141
154
  // SSOT for the sentinel-extraction awk — used by BOTH the relay prompt and the re-extract agent.
142
155
  const AWK_EXTRACT = `awk '/<<<FINDINGS_JSON>>>/{c=1;b="";next}/<<<END_FINDINGS_JSON>>>/{c=0;last=b;next}c{b=b $0 ORS}END{printf "%s",last}'`
143
156
  const reExtractPrompt = (file) =>
@@ -385,22 +398,37 @@ for (const item of findResults) {
385
398
  }
386
399
 
387
400
  // ONE deterministic fallback: pre-flight found no companion, or it ran but did not complete.
388
- // PROSE-ONLY salvage (v4.56.0): when Codex FINISHED but emitted prose (no sentinel JSON), seed the
389
- // fallback with that prose as UNVERIFIED leads. The relay never parses prose into findings; the
390
- // fallback code-reviewer (a real reviewer WITH code access) re-verifies each lead against the actual
391
- // diff and emits the schema itself so Codex's cross-model finds are recovered, not discarded, and
392
- // the trust model is unchanged (output is validated by a reviewer over real code). Net vs the old
393
- // cold fallback: same full pass, but seeded recovers the finds, typically cheaper (confirm vs hunt).
401
+ // Two sub-paths (SC-3):
402
+ // SEEDED (Codex FINISHED but emitted PROSE, no sentinel JSON): run a CHEAP CONVERTER that turns the
403
+ // prose into the finding schema WITHOUT an independent re-hunt Codex already did the cross-model
404
+ // review, re-hunting the whole diff is the wasted second pass. The converted findings are source
405
+ // 'codex' but NON-preValidated, so the existing Verify phase validates each over real code (trust
406
+ // model unchanged: Verify validates). Layered coverage stays per-card simplify/security + qa +
407
+ // Verify + the batch Final pass keep hunting; only the duplicate full re-hunt is removed.
408
+ // COLD (no prose at all): no Codex signal to convert → run today's FULL code-reviewer (preValidated).
394
409
  if (!codexRan) {
395
- codexEngine = codexProse ? 'code-reviewer (codex-seeded)' : 'code-reviewer (fallback)'
396
- const seedBlock = codexProse
397
- ? `\n\nCodex (a DIFFERENT model) reviewed this exact diff and reported the following findings in PROSE — it failed to emit machine-readable JSON, so these are UNVERIFIED leads, NOT validated findings. VERIFY each against the actual diff at its cited file:line, DROP any that don't hold, then run your own full independent review for anything Codex missed. Treat the prose as hints to check, never as ground truth:\n<codex-prose>\n${codexProse}\n</codex-prose>`
398
- : ''
399
- const fb = await agent(
400
- `Codex was unavailable for this wave's code review. Run the FULL code review yourself over the wave diff, per ${protocolRef} (Phase 3.7).\n\n${waveBrief}\n\n${baselineBrief}\n\nReturn findings using the schema fields, with a self false-positive check applied (your findings are treated as validated). An observation you VERIFIED needs NO change (fix direction "no fix required" / "acceptable as-is") is not work — set requires_action:false (recorded, never sent to a fixer), or do not emit it.${seedBlock}`,
401
- { label: codexProse ? 'code-reviewer (codex-seeded)' : 'code-reviewer (fallback)', phase: 'Discovery', agentType: 'code-reviewer', schema: FINDINGS_SCHEMA }
402
- )
403
- if (fb && Array.isArray(fb.findings)) raw.push(...fb.findings.map((f) => ({ ...f, source: 'code-reviewer', preValidated: true })))
410
+ if (codexProse) {
411
+ codexEngine = 'codex (prose-converted)'
412
+ const conv = await agent(
413
+ `Convert the Codex PROSE findings below into the structured finding schema. Codex (a DIFFERENT model) already reviewed this exact diff; your job is ONLY transcription, not review.\n\n` +
414
+ `For each finding in the prose: keep the cited file:line + severity + a one-line minimal_fix_direction. Do a LIGHT plausibility check — DROP a lead whose cited file:line is obviously wrong/empty/missing. Do NOT perform an independent code review, do NOT hunt for new issues, do NOT re-read the whole diff.\n\n` +
415
+ `${waveBrief}\n\n<codex-prose>\n${codexProse}\n</codex-prose>\n\n` +
416
+ `Return findings[] using the schema fields (domain in {doc, security, migration, code, perf, test}).`,
417
+ { label: 'codex (prose-converted)', phase: 'Discovery', agentType: 'code-reviewer', schema: FINDINGS_SCHEMA }
418
+ )
419
+ // NON-preValidated (no preValidated:true) → verifyFinding routes each to the domain specialist.
420
+ // Clamp confidence < 80: the converter transcribes Codex's (often high) self-confidence, and
421
+ // verifyFinding short-circuits to VERIFIED at confidence >= 80 — which would skip the very Verify
422
+ // pass SC-3 relies on. Capping forces the domain-specialist validation over real code.
423
+ if (conv && Array.isArray(conv.findings)) raw.push(...conv.findings.map((f) => ({ ...f, source: 'codex', confidence: Math.min(Number(f.confidence) || 0, 70) })))
424
+ } else {
425
+ codexEngine = 'code-reviewer (fallback)'
426
+ const fb = await agent(
427
+ `Codex was unavailable for this wave's code review. Run the FULL code review yourself over the wave diff, per ${protocolRef} (Phase 3.7).\n\n${waveBrief}\n\n${baselineBrief}\n\nReturn findings using the schema fields, with a self false-positive check applied (your findings are treated as validated). An observation you VERIFIED needs NO change (fix direction "no fix required" / "acceptable as-is") is not work — set requires_action:false (recorded, never sent to a fixer), or do not emit it.`,
428
+ { label: 'code-reviewer (fallback)', phase: 'Discovery', agentType: 'code-reviewer', schema: FINDINGS_SCHEMA }
429
+ )
430
+ if (fb && Array.isArray(fb.findings)) raw.push(...fb.findings.map((f) => ({ ...f, source: 'code-reviewer', preValidated: true })))
431
+ }
404
432
  }
405
433
 
406
434
  // Globally-unique finding_id at the fan-in. Finders number F### INDEPENDENTLY, so a Codex F003 and
@@ -526,8 +554,16 @@ if (!securityFix.length && !actionable.length) log('Fix: no actionable code/perf
526
554
  // Merge the two passes. A FAIL in EITHER pass fails the wave; PASS only when a pass actually ran it.
527
555
  const fixPasses = [secResult, codeFixResult]
528
556
  const allActionable = [...securityFix, ...actionable]
529
- const appliedIds = new Set(fixPasses.flatMap((p) => (p.applied || []).map((x) => x.finding_id)))
530
- const unresolvedIds = new Set(fixPasses.flatMap((p) => p.unresolved || []))
557
+ // Collision-safe id reconciliation (SC-1): a writer often echoes only the human-readable tail
558
+ // (e.g. FEAT-0040-04-CR-001) instead of the full prefixed finding_id, so an exact-Set match would
559
+ // drop an applied fix into residual. resolveId maps each echoed token back to its canonical id.
560
+ const canonIds = allActionable.map((f) => f.finding_id)
561
+ const resolveId = makeResolveId(canonIds)
562
+ const appliedIds = new Set(fixPasses.flatMap((p) => (p.applied || []).map((x) => resolveId(x.finding_id))))
563
+ const unresolvedIds = new Set(fixPasses.flatMap((p) => (p.unresolved || []).map(resolveId)))
564
+ const matched = [...appliedIds].filter((id) => canonIds.includes(id)).length
565
+ const claimed = fixPasses.reduce((n, p) => n + (p.applied || []).length, 0)
566
+ if (matched < claimed) log(`Fix: id-reconciliation — ${claimed - matched} applied id(s) did not map to a canonical finding_id (left as residual; check writer echo).`)
531
567
  const ranChecks = fixPasses.filter((p) => p.ran).map((p) => p.checks)
532
568
  const mergedChecks = ['lint', 'tsc', 'build'].reduce((acc, k) => {
533
569
  acc[k] = ranChecks.some((c) => c[k] === 'FAIL') ? 'FAIL' : (ranChecks.some((c) => c[k] === 'PASS') ? 'PASS' : 'SKIP')
@@ -537,6 +573,24 @@ const mergedChecks = ['lint', 'tsc', 'build'].reduce((acc, k) => {
537
573
  const codeResidual = allActionable.filter((f) => !appliedIds.has(f.finding_id) || unresolvedIds.has(f.finding_id))
538
574
  const checksFailed = ['lint', 'tsc', 'build'].some((k) => mergedChecks[k] === 'FAIL')
539
575
 
576
+ // Light-tier test-regression gate (P1-D). At light tier qa-sentinel was SKIPPED in Discovery and the
577
+ // Fix-phase writers run only lint/tsc/build — so a fix that breaks a test slips through (the full suite
578
+ // is deferred to the Final FULL gate, which runs only at the END of the batch). When the Fix phase
579
+ // ACTUALLY applied code fixes at light tier, run ONE diff-scoped qa-sentinel pass over just the tests
580
+ // that import the changed files (NOT the whole suite) and merge its gates into gateTable, so the
581
+ // skill's existing gateTable-OR gate catches a fix-introduced test regression at the wave boundary.
582
+ const fixPhaseTouchedCode = appliedIds.size > 0
583
+ if (maxQaTier !== 'full' && fixPhaseTouchedCode) {
584
+ const scopedQaPrompt = qaPrompt +
585
+ `\n\nDIFF-SCOPED RUN: do NOT run the whole test suite. Scope tests to the files that import the changed files above (the post-fix blast radius) — run only those, plus lint/type-check on the changed files. If the test runner cannot select a diff-scoped subset, SKIP the test gate gracefully (return status SKIP with a one-line reason) rather than running the full suite.`
586
+ const scopedQa = await agent(scopedQaPrompt, { label: 'qa-sentinel (light-tier post-fix)', phase: 'Fix', agentType: 'qa-sentinel', schema: GATES_SCHEMA })
587
+ const scopedGates = (scopedQa && Array.isArray(scopedQa.gates)) ? scopedQa.gates : []
588
+ if (scopedGates.length) {
589
+ gateTable = [...gateTable, ...scopedGates]
590
+ log(`Fix: light-tier diff-scoped qa-sentinel ran after applying code fixes — ${scopedGates.filter((g) => g.status === 'FAIL').length} failing gate(s) merged into gateTable.`)
591
+ }
592
+ }
593
+
540
594
  // ───────────────────────────────────────────────────────────────────────────
541
595
  // Phase Doc — doc-reviewer audit-and-apply on the wave diff (since v4.56.0).
542
596
  // MOVED OFF the orchestrator (was /new Phase 3) so doc-reviewer's analysis +
@@ -574,7 +628,10 @@ if ((docSurfaceTouched || docFindings.length)) {
574
628
  const dres = (dr && typeof dr === 'object') ? dr : {}
575
629
  docApplied = Array.isArray(dres.applied) ? dres.applied : []
576
630
  const docUnresolved = Array.isArray(dres.unresolved) ? dres.unresolved : []
577
- const docAppliedIds = new Set(docApplied.map((x) => x && x.finding_id).filter(Boolean))
631
+ // Collision-safe id reconciliation (SC-1) for doc echoes too. Synthetic ids (doc-audit#i) with no
632
+ // canonical match return unchanged — correct (they don't map to a routed finding).
633
+ const resolveDoc = makeResolveId(docFindings.map((f) => f.finding_id))
634
+ const docAppliedIds = new Set(docApplied.map((x) => resolveDoc(x && x.finding_id)).filter(Boolean))
578
635
  // Verified doc findings NOT applied → residual (skill runs a targeted Phase 3 doc pass / routes code-rooted drift).
579
636
  docResidualOut = docFindings.filter((f) => !docAppliedIds.has(f.finding_id))
580
637
  // doc-reviewer's own unresolved audit items (no matching finding) → slim residual notes.