baldart 3.24.0 → 3.26.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.
@@ -12,6 +12,8 @@ description: >
12
12
 
13
13
  > **SCOPE CLOSURE DISCIPLINE (BLOCKING)**: This orchestrator NEVER unilaterally defers an acceptance criterion. Silent deferral via `implementation_notes`, commit message, or final recap is a protocol violation per `framework/agents/workflows.md § Scope Closure Discipline`. Under context-window pressure, time pressure, or unforeseen complexity, the correct action is to STOP and route the decision back to the user via `AskUserQuestion` — Auto Mode's "bias toward proceeding" applies to routine decisions, NOT to scope reduction. **Phase 2.5b (AC-Closure Gate)** enforces this at the per-card level before commit and is non-skippable.
14
14
 
15
+ > **NO PHASE SKIP FOR PERCEIVED TIME, EVER (BLOCKING)**: MANDATORY phases are non-negotiable in BOTH sequential mode and team mode. The phrases `time budget`, `context budget`, `context pressure as override`, `for time`, `to save tokens`, `to save iterations`, or any model-invented constraint are NOT valid skip reasons. The only valid skip reasons are those explicitly enumerated in each phase's Gate table (e.g. `features.has_e2e_review: false`, backend-only diff, card type `docs`/`chore`/`config`). Documenting skipped phases at the end of a batch as `"saltati per time budget"` is a protocol violation per the FEAT-0006 incident — treat any such phrase in your own output as a self-correction trigger: stop, surface the situation via `AskUserQuestion`, and resume only after explicit user direction. Auto Mode does NOT override this. Team-mode orchestrators (L0/L1) propagate every MANDATORY phase per-card and never aggregate, reduce, or pre-filter the per-card pipeline.
16
+
15
17
  ## Project Context
16
18
 
17
19
  **Reads from `baldart.config.yml`:** `paths.backlog_dir`, `paths.references_dir`.
@@ -49,9 +51,16 @@ At batch start, create `/tmp/batch-tracker-<FIRST-CARD-ID>.md` with:
49
51
 
50
52
  ```markdown
51
53
  # Batch Run: [CARD-IDS]
54
+ <!-- MANDATORY phases are non-skippable in BOTH sequential and team mode.
55
+ Inventing reasons ("time budget", "context pressure", "out of scope of this card")
56
+ to skip phases or defer ACs is a protocol violation. See FEAT-0006 incident.
57
+ Only the user, via AskUserQuestion, can approve a deferral. -->
52
58
  Started: [timestamp]
53
59
  Total cards: [N]
54
60
 
61
+ ## Workspace Snapshot
62
+ (none — Phase 0 will populate if `git stash` was used on the main repo)
63
+
55
64
  ## Worktree
56
65
  Branch: [feat/FEAT-XXXX-slug]
57
66
  Path: [.worktrees/feat-FEAT-XXXX-slug]
@@ -106,6 +115,71 @@ Main repo: [/absolute/path/to/main/repo]
106
115
 
107
116
  ---
108
117
 
118
+ ## Phase 0 — Workspace Hygiene Pre-flight (BLOCKING — non-skippable)
119
+
120
+ **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.
121
+
122
+ **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.
123
+
124
+ **Steps:**
125
+
126
+ 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.
127
+
128
+ 2. **Fetch remote state**:
129
+ ```bash
130
+ git -C "$MAIN" fetch origin --quiet
131
+ ```
132
+
133
+ 3. **Dirty-tree gate (BLOCKING)** — capture the working tree state of the main repo:
134
+ ```bash
135
+ git -C "$MAIN" status --porcelain
136
+ ```
137
+ If the output is non-empty, invoke `AskUserQuestion`:
138
+ - Question: `"Main repo ($MAIN) ha modifiche non committate prima di partire con la batch. Come procedo?"`
139
+ - Options (max 4):
140
+ 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.
141
+ 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.
142
+ 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.
143
+ 4. **"Abort batch"** — halt the batch, leave the tracker file in place, exit.
144
+
145
+ Do NOT silently proceed. Do NOT auto-stash without the user picking option 1.
146
+
147
+ 4. **Divergence gate (BLOCKING)** — compare local `develop` against `origin/develop`:
148
+ ```bash
149
+ git -C "$MAIN" rev-list --left-right --count origin/develop...develop
150
+ ```
151
+ The output is `<behind>\t<ahead>`. Branch on the result:
152
+
153
+ - **`0\t0`** — synchronized. Proceed to step 5.
154
+ - **`N\t0`** (only behind, N > 0) — invoke `AskUserQuestion`:
155
+ - Question: `"Local develop è behind origin/develop di N commit. Come procedo?"`
156
+ - Options: `[Fast-forward pull adesso]` / `[Procedi senza pull]` / `[Abort]`.
157
+ - 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".
158
+ - **`0\tM`** (only ahead, M > 0 — UNPUSHED LOCAL COMMITS, this is the FEAT-0006 pattern) — invoke `AskUserQuestion`:
159
+ - Question: `"Local develop è ahead di origin/develop di M commit (commit locali non pushati). Cosa faccio?"`
160
+ - Show the commit list inline: `git -C "$MAIN" log --oneline origin/develop..develop`.
161
+ - Options: `[Push adesso (git push origin develop)]` / `[Cherry-pick a parte e continua]` / `[Lascia così e procedi (lo gestisco io)]` / `[Abort]`.
162
+ - **`N\tM`** (diverged both ways) — invoke `AskUserQuestion`:
163
+ - Question: `"Local develop è diverged da origin/develop: N behind, M ahead. Servono entrambe le riconciliazioni. Come procedo?"`
164
+ - Options: `[Rebase local develop su origin/develop]` / `[Mostrami i commit e fammi decidere]` / `[Abort]`.
165
+
166
+ 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`.
167
+
168
+ 5. **Log and proceed** — write to the tracker:
169
+ ```
170
+ ## Phase 0 — Workspace Hygiene Pre-flight
171
+ Status: PASS
172
+ Main repo: $MAIN
173
+ Dirty: <none | stashed: stash@{0} | user-override>
174
+ Divergence (local…origin/develop): <0\t0 | resolved: ff-pull | resolved: pushed | user-deferred>
175
+ Completed: <timestamp>
176
+ ```
177
+ If any step ended in HALT, set `Status: HALT` and stop — do NOT continue to `## Pre-flight (once)`.
178
+
179
+ 6. **Anti-bypass guard** — before exiting Phase 0, re-read the tracker section you just wrote. If `## Phase 0` is missing or shows `Status: HALT`, refuse to proceed. There is no `--skip-phase-0` flag; if you find yourself reaching for one, treat it as a self-correction trigger and stop.
180
+
181
+ ---
182
+
109
183
  ## Pre-flight (once)
110
184
 
111
185
  1. Read each backlog card from `${paths.backlog_dir}/*.yml` to understand scope and dependencies. Also read the project's canonical-docs registry — typically `${paths.references_dir}/ssot-registry.md` plus any linking-protocol guide — to understand which canonical docs exist for the feature area being implemented. Exact filenames are listed in `.baldart/overlays/new.md`; skip when absent.
@@ -1504,6 +1578,71 @@ The most common failure mode is leaving cards IN_PROGRESS after merge. This crea
1504
1578
 
1505
1579
  **Why this exists**: Agents frequently skip the DONE marking in Phase 4 (step 27) due to context compaction, commit failures that interrupt the flow, or team mode where Step D.6 gets lost. This gate is the safety net that catches ALL of these cases.
1506
1580
 
1581
+ ### Phase 6c — Workspace Hygiene Post-merge (BLOCKING — non-skippable)
1582
+
1583
+ **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.
1584
+
1585
+ **Auto Mode does NOT override this phase.** Every `AskUserQuestion` below is non-bypassable.
1586
+
1587
+ **Steps:**
1588
+
1589
+ 1. **Fetch remote state**:
1590
+ ```bash
1591
+ git -C "$MAIN" fetch origin --quiet
1592
+ ```
1593
+
1594
+ 2. **Parse `[SYNC-DEFERRED]` markers** — scan the captured stdout from every `/mw` invocation in this batch (kept in the tracker under `## Worktree Merges`) for `[SYNC-DEFERRED] main repo HEAD=<branch>` lines. If present, surface to the user via `AskUserQuestion`:
1595
+ - Question: `"Una o più merge worktree hanno deferito la sincronizzazione di local develop (main repo HEAD non era develop). Come riconcilio adesso?"`
1596
+ - Options: `[Ora HEAD è develop → ff-pull adesso]` / `[Lascia deferred (riconcilio io manualmente)]` / `[Mostra dettagli e fammi decidere]`.
1597
+
1598
+ 3. **Clean-tree assertion (BLOCKING)**:
1599
+ ```bash
1600
+ git -C "$MAIN" status --porcelain
1601
+ ```
1602
+ If non-empty AND no Phase 0 stash is pending → invoke `AskUserQuestion`:
1603
+ - Question: `"Main repo ha modifiche non committate dopo il batch (non c'era stash di Phase 0). Cosa faccio?"`
1604
+ - Options: `[Mostrami il diff e fammi decidere]` / `[Stash adesso con label baldart-post-batch-<timestamp>]` / `[Lascia così (lo gestisco io)]` / `[Halt]`.
1605
+
1606
+ 4. **Divergence assertion (BLOCKING — this catches the FEAT-0006 orphan-commit pattern)**:
1607
+ ```bash
1608
+ git -C "$MAIN" rev-list --left-right --count origin/develop...develop
1609
+ ```
1610
+ If the output is anything other than `0\t0`:
1611
+ - **Ahead only (M > 0, unpushed commits)** — invoke `AskUserQuestion`:
1612
+ - Question: `"Local develop ha M commit non pushati su origin/develop al termine del batch. Lista: <git log --oneline origin/develop..develop>. Cosa faccio?"`
1613
+ - 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]`.
1614
+ - 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.
1615
+ - **Behind only (N > 0)** — invoke `AskUserQuestion`:
1616
+ - Question: `"Local develop è behind origin/develop di N commit (qualcuno ha pushato durante il batch). Faccio ff-pull adesso?"`
1617
+ - Options: `[Fast-forward pull]` / `[Lascia behind (lo gestisco io)]`.
1618
+ - **Diverged both ways** — invoke `AskUserQuestion`:
1619
+ - Question: `"Local develop è diverged (N behind, M ahead). Servono entrambe le riconciliazioni. Come procedo?"`
1620
+ - Options: `[Rebase local develop su origin/develop]` / `[Mostrami i commit locali e decidiamo]` / `[Halt]`.
1621
+
1622
+ 5. **Restore Phase 0 stash (if any)** — read the tracker for `## Workspace Snapshot: <ref>`:
1623
+ - If `<ref>` is `dirty-tree override` → skip restore, log "user retained dirty-tree responsibility".
1624
+ - If `<ref>` is `stash@{N}` (or similar):
1625
+ ```bash
1626
+ git -C "$MAIN" stash pop <ref>
1627
+ ```
1628
+ On conflict, do NOT silent-drop — invoke `AskUserQuestion`:
1629
+ - Question: `"Restore dello stash di Phase 0 ha generato conflitti. Lo stash è ancora presente (NON eliminato). Come procedo?"`
1630
+ - Options: `[Lascia lo stash + apri istruzioni per merge manuale]` / `[Mostrami il conflitto inline]` / `[Halt]`.
1631
+
1632
+ 6. **Log and exit**:
1633
+ ```
1634
+ ## Phase 6c — Workspace Hygiene Post-merge
1635
+ Status: PASS
1636
+ Clean-tree: <yes | resolved: stashed | user-retained>
1637
+ Divergence (local…origin/develop): <0\t0 | resolved: pushed/cherry-picked/ff-pulled/rebased>
1638
+ Sync-deferred markers: <none | reconciled | user-retained>
1639
+ Phase 0 snapshot restore: <n/a | popped clean | conflict-deferred-to-user>
1640
+ Completed: <timestamp>
1641
+ ```
1642
+ If any step ended in HALT, set `Status: HALT` and report — Phase 7 must NOT start with an unclean main repo unless the user explicitly chose `[Lascia così]`.
1643
+
1644
+ 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.
1645
+
1507
1646
  ### Fail-safe rules (enforced by worktree-manager skill)
1508
1647
  - **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.
1509
1648
  - Never merge to `main` — only to `develop`, via the configured `git.merge_strategy` (NOT local checkout).
@@ -1911,21 +2050,31 @@ For each completed agent:
1911
2050
  - Other agents in the group continue unaffected.
1912
2051
  - After group completes, ask user: retry failed card or skip?
1913
2052
 
1914
- #### Step D: Post-group review + QA
2053
+ #### Step D: Post-group review + QA (ALL sub-steps MANDATORY)
2054
+
2055
+ > **The sub-steps below are ALL MANDATORY.** The previous lean Step D (build → combined review → qa → codex → commit) was an incident-causing shortcut (FEAT-0006: 10 sub-cards committed with 0 invocations of Phase 2.5b / 2.55 / 2.6 / 3 / 3.5 / 3.7) and is now FORBIDDEN. Every card in the group MUST complete every per-card sub-step (D.3a, D.3b, D.3c, D.4a) before the group reaches D.5 commit. Skipping any sub-step "to save time", "to save tokens", "because the diff is small", or for any model-invented reason is a protocol violation. Auto Mode does NOT override this.
1915
2056
 
1916
2057
  After ALL agents in the group complete successfully:
1917
2058
 
1918
- 1. **Build verification** — Run `npm run build` in the worktree to verify combined changes compile. If build fails, identify which card's changes broke it (from `git diff` per card), spawn a targeted fix-coder for those files only.
2059
+ 1. **D.1 — Build verification (group)** — Run `npm run build` in the worktree to verify combined changes compile. If build fails, identify which card's changes broke it (from `git diff` per card), spawn a targeted fix-coder for those files only.
2060
+
2061
+ 2. **D.2 — Combined static review (group)** — Invoke **code-reviewer** + **doc-reviewer** IN PARALLEL (read-only, same as Phase 3) across ALL changes in the group. This is ONE combined review, not per-card — efficient and catches cross-card issues. Per-card doc-review still runs in D.4a.
2062
+
2063
+ 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.
1919
2064
 
1920
- 2. **Combined review** Invoke **code-reviewer** + **doc-reviewer** IN PARALLEL (read-only, same as Phase 3) across ALL changes in the group. This is ONE combined review, not per-card more efficient and catches cross-card issues.
2065
+ 3a. **D.3aPhase 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.
1921
2066
 
1922
- 3. **Apply fixes** — If findings exist, spawn ONE fix-coder to apply all fixes in a single pass. Run build + lint after.
2067
+ 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.
1923
2068
 
1924
- 4. **QA gate** Select the HIGHEST QA profile across all cards in the group (e.g., if one card is BALANCED and another is DEEP, use DEEP). Invoke **qa-sentinel** once for the group.
2069
+ 3c. **D.3cPhase 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.
1925
2070
 
1926
- 4b. **Pre-Merge Codex Review Gate (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 step 5. The Phase 4-equivalent commits in step 5 MUST NOT happen until every card in the group has a PASS verdict (or explicit user override). Log results in the tracker under `## Pre-Merge Codex Review` per card.
2071
+ 4. **D.4 QA gate (group)** — Select the HIGHEST QA profile across all cards in the group (e.g., if one card is BALANCED and another is DEEP, use DEEP). Invoke **qa-sentinel** once for the group.
1927
2072
 
1928
- 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):
2073
+ 4a. **D.4aPhase 3 Doc Review per-card** — For EACH card in the group, invoke `doc-reviewer` scoped to that card's File Ownership Map + card YAML (mirrors Phase 3 of the sequential path). If findings exist, spawn a targeted fix-coder for that card only. This is in addition to the combined D.2 doc-reviewer passD.2 catches cross-card issues, D.4a verifies each card meets its own doc gate before commit.
2074
+
2075
+ 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.
2076
+
2077
+ 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):
1929
2078
  ```bash
1930
2079
  cd <worktree-path>
1931
2080
  # For each card in the group:
@@ -1933,13 +2082,26 @@ After ALL agents in the group complete successfully:
1933
2082
  git commit -m "[CARD-ID] Brief description"
1934
2083
  ```
1935
2084
 
1936
- 6. **Update backlog (MANDATORY — do NOT skip)** — For EACH card in the group:
1937
- a. Edit the backlog YAML (`${paths.backlog_dir}/<CARD-ID>.yml`): set `status: DONE`, add `completed_date: <today>`, add implementation notes.
2085
+ 6. **D.6 — Update backlog (MANDATORY — do NOT skip)** — For EACH card in the group:
2086
+ 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).
1938
2087
  b. **Verify the write**: re-read the YAML file and confirm `status: DONE` is present. If not, retry.
1939
2088
  c. Stage the updated YAML and include it in the card's commit (or as an immediate follow-up commit).
1940
2089
  d. Log in tracker: `card_status: DONE (verified)` for each card.
1941
2090
  Note: Phase 6b (Status Reconciliation) will catch any card missed here, but aim for zero misses.
1942
2091
 
2092
+ #### Step D coverage assertion (MANDATORY end-of-group check)
2093
+
2094
+ Before moving to Step E, the orchestrator MUST verify the tracker contains, for EACH card in the group, ALL of the following entries (or an explicit gate-skip log with the documented reason):
2095
+
2096
+ - `## AC Closure Ledger — <CARD-ID>` with `ac-closure: implemented=N | user-approved deferrals=M | follow-up cards created=K`
2097
+ - `simplify: <N fixes | clean — 0 fixes>` (per-card from D.3b)
2098
+ - `e2e-review: <status>` (with documented gate-skip reason if SKIP)
2099
+ - `doc-review: <status>` (from D.4a)
2100
+ - `qa: <profile=... | verdict=...>` (group-level from D.4 — same value cited per card)
2101
+ - `codex-review: <verdict>` (per-card from D.4b)
2102
+
2103
+ A missing entry means a sub-step was skipped. If any entry is missing, do NOT proceed to Step E — return to the missing sub-step and execute it. Documenting "skipped per time budget" or similar is a protocol violation per the top-of-file rigidity clause.
2104
+
1943
2105
  #### Step E: Context purge + next group
1944
2106
 
1945
2107
  After committing all cards in the group:
@@ -846,6 +846,10 @@ if [ "$CURRENT_BRANCH" = "develop" ]; then
846
846
  else
847
847
  echo "ℹ️ Main repo HEAD is '$CURRENT_BRANCH' — not switching. Next manual `git pull` on develop will sync."
848
848
  git -C "$MAIN" fetch origin develop --quiet
849
+ # Structured marker for upstream orchestrators (e.g. /new Phase 6c).
850
+ # `/mw` standalone keeps fetch-only behavior (terminal isolation); orchestrators
851
+ # parse this marker and route reconciliation through an explicit user gate.
852
+ echo "[SYNC-DEFERRED] main repo HEAD=${CURRENT_BRANCH}, local develop not fast-forwarded — orchestrator workspace-hygiene phase must reconcile."
849
853
  fi
850
854
  ```
851
855
 
@@ -61,6 +61,10 @@ When a card's `acceptance_criteria` cannot be fully implemented as written, the
61
61
 
62
62
  Any deferral travels through an explicit `AskUserQuestion` with the AC text quoted verbatim, one question per AC. For orchestrator skills (`/new`, `/prd`) this discipline is enforced in the per-card AC-closure gate before commit. Auto Mode's "bias toward proceeding" applies to routine decisions, NOT to scope reduction — silent deferral of a numbered AC is a violation even in Auto Mode.
63
63
 
64
+ ### Coder authority (no defer)
65
+
66
+ Coder agents report `done | failed` at card level. Per-requirement signals (`done | not_implemented`) are inputs to the orchestrator's AC-Closure Gate, never terminal verdicts. Coders have NO authority to declare an AC `deferred`, `out of scope`, `covered by X`, `partial`, `blocked`, or any equivalent. Only the user, via the gate's `AskUserQuestion`, can approve a deferral. Inventing constraints — `time budget`, `context budget`, `context pressure as override`, or any phrase not enumerated in a phase's documented Gate table — to skip phases or defer ACs is a protocol violation. The same rule applies to orchestrator agents (sequential and team-mode L0/L1/L2/L3): they relay coder signals into the gate; they do not aggregate, summarize, or pre-filter deferrals.
67
+
64
68
  ## Deploys and Publications
65
69
 
66
70
  - Production deploys and publications must follow `agents/deployment-protocol.md` if it exists.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "3.24.0",
3
+ "version": "3.26.0",
4
4
  "description": "Claude Agent Framework - Reusable framework for coordinating AI agents and humans in software projects",
5
5
  "bin": {
6
6
  "baldart": "./bin/baldart.js"
@@ -117,15 +117,22 @@ function overlayDrift(cwd, frameworkVersion) {
117
117
  }
118
118
 
119
119
  async function describeRemote(git, repo, offline) {
120
- // Best-effort remote drift detection. Returns { ahead, behind, fetched }.
121
- if (offline) return { fetched: false };
122
- try { await git.fetch(repo); }
123
- catch (_) { return { fetched: false }; }
124
- try {
125
- const out = await git.git.raw(['rev-list', '--left-right', '--count', 'HEAD...FETCH_HEAD']);
126
- const [ahead, behind] = out.trim().split('\t').map(Number);
127
- return { ahead, behind, fetched: true };
128
- } catch (_) { return { fetched: false }; }
120
+ // SSOT (v3.25.0+): version-compare authority + diagnostic counts.
121
+ // The legacy { ahead, behind, fetched } shape is preserved for downstream
122
+ // callers that read it; the new authoritative fields are `isAligned`,
123
+ // `installedVersion`, `remoteVersion`.
124
+ if (offline) return { fetched: false, isAligned: false, installedVersion: 'unknown', remoteVersion: 'unknown', aheadScoped: 0 };
125
+ const status = await git.getUpdateStatus(repo);
126
+ return {
127
+ fetched: status.fetched,
128
+ ahead: status.commitCount.ahead,
129
+ behind: status.commitCount.behind,
130
+ isAligned: status.isAligned,
131
+ installedVersion: status.installedVersion,
132
+ remoteVersion: status.remoteVersion,
133
+ aheadScoped: status.aheadScoped,
134
+ hasFetchHead: status.hasFetchHead,
135
+ };
129
136
  }
130
137
 
131
138
  async function localFrameworkChanges(git) {
@@ -137,11 +144,12 @@ async function localFrameworkChanges(git) {
137
144
  } catch (_) { return 0; }
138
145
  }
139
146
 
140
- async function commitsAheadOfRemote(git) {
141
- try {
142
- const out = await git.git.raw(['rev-list', '--count', 'FETCH_HEAD..HEAD', '--', FRAMEWORK_DIR]);
143
- return Number(out.trim()) || 0;
144
- } catch (_) { return 0; }
147
+ async function commitsAheadOfRemote(git, remote) {
148
+ // Pre-v3.25.0 this re-ran the git query; now we read from the SSOT
149
+ // result populated by describeRemote(). Kept as a helper for readability
150
+ // in detectState().
151
+ if (remote && typeof remote.aheadScoped === 'number') return remote.aheadScoped;
152
+ return 0;
145
153
  }
146
154
 
147
155
  async function detectState(cwd, opts = {}) {
@@ -206,7 +214,7 @@ async function detectState(cwd, opts = {}) {
206
214
  }
207
215
 
208
216
  state.remote = await describeRemote(git, ledger.framework_repo || REPO_DEFAULT, !!opts.offline);
209
- state.commitsAhead = await commitsAheadOfRemote(git);
217
+ state.commitsAhead = await commitsAheadOfRemote(git, state.remote);
210
218
  state.workingTreeDirty = await localFrameworkChanges(git);
211
219
 
212
220
  state.overlays = countOverlays(cwd);
@@ -577,14 +585,24 @@ function planActions(state) {
577
585
  });
578
586
  }
579
587
 
580
- const remoteAhead = state.remote.fetched && state.remote.behind > 0;
581
- const hasLocalWork = state.workingTreeDirty > 0 || state.commitsAhead > 0;
588
+ // v3.25.0+: drift detection is authoritative via VERSION compare (isAligned).
589
+ // The HEAD...FETCH_HEAD commit count is subtree-merge noise and never reaches
590
+ // 0, so we MUST NOT use it as the "needs update" signal.
591
+ const remoteAhead = state.remote.fetched && state.remote.isAligned === false;
592
+ // Local work detection: working-tree dirty is real; commitsAhead (aheadScoped)
593
+ // includes subtree-merge noise — only count it when NOT aligned (i.e. there's
594
+ // a real divergence to investigate). C2 adds classifier to filter noise.
595
+ const hasLocalWork = state.workingTreeDirty > 0 ||
596
+ (state.commitsAhead > 0 && state.remote.fetched && state.remote.isAligned === false);
582
597
 
583
598
  if (remoteAhead) {
599
+ const fromTo = (state.remote.installedVersion && state.remote.remoteVersion)
600
+ ? `v${state.remote.installedVersion} → v${state.remote.remoteVersion}`
601
+ : 'update available';
584
602
  actions.push({
585
603
  key: 'update',
586
- label: `Pull ${state.remote.behind} commit(s) from upstream`,
587
- why: 'Remote framework is ahead of your local copy. Updating first keeps your contributions on a fresh base.',
604
+ label: `Update framework (${fromTo})`,
605
+ why: 'Remote framework version differs from local. Updating first keeps your contributions on a fresh base.',
588
606
  autoOk: true, // update has its own internal "Proceed?" prompt + pre-flight stash
589
607
  run: () => require('./update')(),
590
608
  });
@@ -596,7 +614,7 @@ function planActions(state) {
596
614
  label: 'Push local framework improvements upstream',
597
615
  why: state.workingTreeDirty
598
616
  ? `${state.workingTreeDirty} uncommitted file(s) in .framework/ — commit them through \`baldart push\`.`
599
- : `${state.commitsAhead} commit(s) on .framework/ not yet pushed.`,
617
+ : `${state.commitsAhead} commit(s) on .framework/ not yet pushed (may include subtree-merge noise; \`baldart push\` will classify).`,
600
618
  autoOk: false, // push writes to remote and asks for version classification — keep explicit intent
601
619
  run: () => require('./push')(),
602
620
  });
@@ -685,25 +703,39 @@ function renderDiagnostic(state) {
685
703
  }
686
704
 
687
705
  if (state.remote.fetched) {
688
- const behind = state.remote.behind || 0;
689
- console.log(statusLine(
690
- 'Remote',
691
- behind > 0 ? `${behind} commit(s) ahead — update available` : 'up to date',
692
- behind > 0 ? 'warn' : 'ok'
693
- ));
706
+ // v3.25.0+: messaging based on isAligned (version-compare), not on
707
+ // commit count which is structurally noisy on subtree pulls.
708
+ if (state.remote.isAligned) {
709
+ console.log(statusLine(
710
+ 'Remote',
711
+ `v${state.remote.remoteVersion} (aligned)`,
712
+ 'ok'
713
+ ));
714
+ } else if (state.remote.remoteVersion && state.remote.remoteVersion !== 'unknown') {
715
+ console.log(statusLine(
716
+ 'Remote',
717
+ `v${state.remote.remoteVersion} — update available (installed v${state.remote.installedVersion})`,
718
+ 'warn'
719
+ ));
720
+ } else {
721
+ console.log(statusLine('Remote', 'upstream VERSION unreadable', 'warn'));
722
+ }
694
723
  } else {
695
724
  console.log(statusLine('Remote', 'offline / not fetched', 'warn'));
696
725
  }
697
726
 
698
- console.log(statusLine(
699
- 'Local changes',
700
- state.workingTreeDirty > 0
701
- ? `${state.workingTreeDirty} file(s) in .framework/`
702
- : state.commitsAhead > 0
703
- ? `${state.commitsAhead} unpushed commit(s)`
704
- : 'clean',
705
- (state.workingTreeDirty + state.commitsAhead) > 0 ? 'warn' : 'ok'
706
- ));
727
+ // Local changes: working-tree dirty is real signal. Commit count is shown
728
+ // ONLY if not aligned (otherwise it's subtree-merge noise — see Fix 2 for
729
+ // the classifier that further filters this).
730
+ const showCommitsAhead = state.commitsAhead > 0 &&
731
+ state.remote.fetched && state.remote.isAligned === false;
732
+ const localStatusText = state.workingTreeDirty > 0
733
+ ? `${state.workingTreeDirty} file(s) in .framework/`
734
+ : showCommitsAhead
735
+ ? `${state.commitsAhead} unpushed commit(s)`
736
+ : 'clean';
737
+ const localSeverity = (state.workingTreeDirty > 0 || showCommitsAhead) ? 'warn' : 'ok';
738
+ console.log(statusLine('Local changes', localStatusText, localSeverity));
707
739
 
708
740
  console.log(statusLine(
709
741
  'Overlays',