mandrel 1.71.0 → 1.72.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.
@@ -26,6 +26,15 @@
26
26
  * "clean sprint" retro trailer), emit `epic.merge.ready`. Otherwise emit
27
27
  * `epic.merge.blocked` with a non-empty reason.
28
28
  *
29
+ * Code-review parse-miss policy (Story #4222): a code-review comment that is
30
+ * present but whose severity bullets cannot be parsed is treated as a DISTINCT
31
+ * condition — surfaced via the `codeReviewUnparseable` signal — and FAILS OPEN
32
+ * rather than blocking. Failing closed on a format miss is indistinguishable,
33
+ * to the operator and to downstream telemetry, from a real disqualifying
34
+ * finding; a parser miss must never masquerade as "the signal said no" inside
35
+ * a generic `epic.merge.blocked`. Genuine critical/high findings still block,
36
+ * because those require the counts to have parsed.
37
+ *
29
38
  * Critical contract:
30
39
  * - The verdict for any given input set is byte-identical to the
31
40
  * pre-inlining legacy module's output — this file is its
@@ -229,11 +238,30 @@ function evaluateCodeReviewSignals(codeReview, reasons) {
229
238
  : { critical: null, high: null, medium: null, suggestion: null };
230
239
  if (!codeReviewFound) {
231
240
  reasons.push('code-review structured comment not found on Epic');
232
- return { codeReviewFound, severity };
241
+ return { codeReviewFound, codeReviewUnparseable: false, severity };
233
242
  }
234
- if (severity.critical === null || severity.high === null) {
235
- reasons.push('code-review severity bullets could not be parsed');
236
- return { codeReviewFound, severity };
243
+ // "Present but unparseable" is a DISTINCT condition from "present and says
244
+ // no" (Story #4222). The canonical renderer
245
+ // (`review-providers/findings-renderer.js`) always emits all four severity
246
+ // bullets, so a body whose critical/high counts we cannot extract is a
247
+ // FORMAT MISS, not a disqualifying signal. Failing closed here — pushing a
248
+ // generic block reason — is indistinguishable, to the operator and to
249
+ // downstream telemetry (the mandrel-bench Autonomy dimension), from a real
250
+ // critical finding: it stalls an otherwise-clean unattended run for a
251
+ // non-reason.
252
+ //
253
+ // Chosen policy: FAIL OPEN on an unparseable code-review body. We surface
254
+ // the condition explicitly via the `codeReviewUnparseable` signal so
255
+ // telemetry can tell a parser miss from a true HITL hand-off, but we do NOT
256
+ // add a disqualifying `reasons[]` entry — the absence of a parseable
257
+ // critical/high count cannot, on its own, block a run whose other signals
258
+ // are clean. Genuine disqualifying review findings (critical > 0 /
259
+ // high > 0) still block below, because those require the counts to have
260
+ // parsed successfully.
261
+ const codeReviewUnparseable =
262
+ severity.critical === null || severity.high === null;
263
+ if (codeReviewUnparseable) {
264
+ return { codeReviewFound, codeReviewUnparseable, severity };
237
265
  }
238
266
  if (severity.critical > 0) {
239
267
  reasons.push(`code-review has ${severity.critical} 🔴 Critical Blocker(s)`);
@@ -241,7 +269,7 @@ function evaluateCodeReviewSignals(codeReview, reasons) {
241
269
  if (severity.high > 0) {
242
270
  reasons.push(`code-review has ${severity.high} 🟠 High Risk finding(s)`);
243
271
  }
244
- return { codeReviewFound, severity };
272
+ return { codeReviewFound, codeReviewUnparseable, severity };
245
273
  }
246
274
 
247
275
  function evaluateRetroSignals(retro, reasons) {
@@ -287,6 +315,7 @@ function evaluateRetroSignals(retro, reasons) {
287
315
  * storyStatuses: string[],
288
316
  * storyBlockers: number,
289
317
  * severity: { critical: number|null, high: number|null, medium: number|null, suggestion: number|null },
318
+ * codeReviewUnparseable: boolean,
290
319
  * retroCompact: boolean,
291
320
  * codeReviewFound: boolean,
292
321
  * retroFound: boolean,
@@ -308,6 +337,7 @@ export function deriveAutoMergeVerdict({ state, codeReview, retro }) {
308
337
  storyStatuses: stateSig.storyStatuses,
309
338
  storyBlockers: stateSig.storyBlockers,
310
339
  severity: reviewSig.severity,
340
+ codeReviewUnparseable: reviewSig.codeReviewUnparseable,
311
341
  retroCompact: retroSig.retroCompact,
312
342
  codeReviewFound: reviewSig.codeReviewFound,
313
343
  retroFound: retroSig.retroFound,
@@ -74,14 +74,22 @@ Phase 5 (Re-Plan Detection).
74
74
  one-pager feeds the scope-triage gate below, whose verdict folds into
75
75
  the **same** Phase 1 HITL confirmation. Do not stop twice.
76
76
 
77
- 3. **HITL stop — confirm the sharpened one-pager**: Display the one-pager
78
- to the operator and **STOP**. Do not proceed to Phase 2 until the
79
- user explicitly confirms the direction. This is the same gate the
80
- skill's own Phase 3 enforces; surfacing it here makes the wait
77
+ 3. **HITL stop — confirm the sharpened one-pager** (**gate #1**): Display
78
+ the one-pager to the operator and **STOP**. Do not proceed to Phase 2
79
+ until the user explicitly confirms the direction. This is the same gate
80
+ the skill's own Phase 3 enforces; surfacing it here makes the wait
81
81
  contract visible to `/plan` callers. When the Phase 1.5 verdict is
82
82
  `story` or `borderline`, this stop carries the three-way choice the
83
83
  triage gate defines (below) instead of a plain confirm.
84
84
 
85
+ > **`--yes` (headless) auto-proceed.** When `/plan` was invoked with
86
+ > `--yes`, this gate does **not** STOP: the one-pager confirm resolves as
87
+ > **approved** and the run continues to Phase 2. A `story` / `borderline`
88
+ > triage verdict resolves to its **Recommended** branch (below) rather
89
+ > than prompting the three-way choice. Display the one-pager and the
90
+ > verdict line for the record, then proceed without waiting. See
91
+ > [`plan.md` § Headless / non-interactive mode](../plan.md#headless--non-interactive-mode---yes).
92
+
85
93
  ## Phase 1.5: Scope Triage (ideation path only)
86
94
 
87
95
  This phase runs **only** on the ideation path, immediately after Phase 1
@@ -124,6 +132,15 @@ skill states once).
124
132
  avoid the ceremony tax of pushing a story-sized scope through the full Epic
125
133
  pipeline.
126
134
 
135
+ > **`--yes` (headless) exception.** "Never auto-route" is the interactive
136
+ > contract. Under `--yes` the operator has *pre-authorized* the
137
+ > recommendation: the three-way choice resolves to its **Recommended**
138
+ > branch deterministically — `single Story` hands off to
139
+ > `/plan --from-notes <path>` (carrying `--yes` so the receiving story
140
+ > path also auto-proceeds), and an `epic` verdict simply continues to
141
+ > Phase 2. No operator wait. This is the only sanctioned auto-route, and it
142
+ > exists solely to make `/plan` driveable headlessly.
143
+
127
144
  ## Phase 2: Cross-Epic Duplicate Search
128
145
 
129
146
  Runs immediately after Phase 1 (and only on the s-plan-ideation path).
@@ -396,6 +413,19 @@ for the scoring logic.
396
413
  permission") is honored — no `gh issue edit` call until the
397
414
  operator confirms.
398
415
 
416
+ > **`--yes` (headless) auto-proceed.** This refinement-diff confirm is the
417
+ > clarity-gate face of `/plan`'s **gate #1** on the existing-Epic
418
+ > (`/plan <epicId>`) path — it is an operator *wait*, not a deterministic
419
+ > validator (the deterministic half is the section-presence *scoring* in
420
+ > step 1, which always runs). When `/plan` was invoked with `--yes`, this
421
+ > confirm does **not** STOP: the sharpened body is auto-**approved** and the
422
+ > run proceeds to step 6 (persist). The blast-radius note is still displayed
423
+ > for the record; only the operator wait is suppressed. This keeps
424
+ > `/plan <epicId> --yes` driveable headlessly even when the Epic body needs
425
+ > refinement (`gh issue edit` still runs only via the step 6 persist call,
426
+ > which the auto-approval authorizes). See
427
+ > [`plan.md` § Headless / non-interactive mode](../plan.md#headless--non-interactive-mode---yes).
428
+
399
429
  6. **Persist**: On approval, run the persist mode:
400
430
 
401
431
  ```bash
@@ -556,18 +586,31 @@ for the scoring logic.
556
586
  `planningRisk.requiresReview` unless the operator passed
557
587
  `--force-review`:
558
588
  - **High risk** (`requiresReview === true`) or **operator override**
559
- (`--force-review`): **STOP**. Ask the USER to review the generated
560
- PRD, Tech Spec, and Acceptance Spec on GitHub. Approval is the
561
- user's verbal OK in this session — the three context tickets stay
589
+ (`--force-review`) **gate #2**: **STOP**. Ask the USER to review the
590
+ generated PRD, Tech Spec, and Acceptance Spec on GitHub. Approval is
591
+ the user's verbal OK in this session — the three context tickets stay
562
592
  **open** through delivery and are closed automatically by
563
593
  `/deliver` when the Epic PR opens. Do NOT proceed
564
594
  to decomposition until the user confirms the plan is accurate.
595
+
596
+ > **`--yes` (headless) auto-proceed.** When `/plan` was invoked with
597
+ > `--yes`, this review gate does **not** STOP, even when
598
+ > `requiresReview === true` or `--force-review` was also passed: the
599
+ > review resolves as **approved** and the run **continues directly to
600
+ > Phase 8**, exactly as the low-risk auto-proceed branch below. The
601
+ > three context tickets stay **open** through delivery as usual; only
602
+ > the operator *wait* is suppressed. This is `/plan`'s **gate #2** —
603
+ > the second and last HITL STOP `--yes` suppresses. `--yes` does
604
+ > **not** alter risk routing or the review criteria themselves; it
605
+ > only forces a proceed where this gate would otherwise STOP. See
606
+ > [`plan.md` § Headless / non-interactive mode](../plan.md#headless--non-interactive-mode---yes).
565
607
  - **Low risk** (`requiresReview === false` and no `--force-review`):
566
608
  Emit the auto-proceed message from the persist stdout
567
609
  (`reviewRouting.operatorMessage`) and **continue directly to Phase 8**
568
610
  without an extra review stop. The Epic still carries
569
611
  `agent::review-spec` until decomposition completes; the routing
570
- decision is recorded in the `epic-plan-state` checkpoint.
612
+ decision is recorded in the `epic-plan-state` checkpoint. (`--yes` is
613
+ a no-op on this branch — there is no STOP to suppress.)
571
614
 
572
615
  5. **Tech Spec freshness check (advisory)**: After the Tech Spec issue
573
616
  is created, `epic-plan-spec.js` runs
@@ -68,6 +68,9 @@ to authoring the standalone Story body from the handed-off one-pager.
68
68
 
69
69
  # Inspect the draft body without creating an Issue:
70
70
  /plan --dry-run --body temp/single-story-draft.md
71
+
72
+ # Headless / non-interactive (auto-proceeds the draft-confirm gate):
73
+ /plan --idea "rip out the unused TaskBodyMigrator export" --yes
71
74
  ```
72
75
 
73
76
  ## Phase 1 — Emit Context
@@ -153,8 +156,18 @@ add a second stop.
153
156
 
154
157
  Display the draft to the operator and **STOP**. Do not call the persist phase
155
158
  until the operator explicitly confirms the draft. This mirrors the HITL gate
156
- `/plan` Phase 3 enforces before opening the Epic Issue. The scope-triage
157
- verdict folds into this same stop:
159
+ `/plan` Phase 3 enforces before opening the Epic Issue. This is the
160
+ story-path face of `/plan`'s **gate #1** (the ideation one-pager /
161
+ scope-triage confirm). The scope-triage verdict folds into this same stop:
162
+
163
+ > **`--yes` (headless) auto-proceed.** When `/plan` was invoked with `--yes`,
164
+ > this gate does **not** STOP: the draft confirm resolves as **approved** and
165
+ > the run proceeds to Phase 3 (persist). An `epic` verdict resolves to its
166
+ > **Recommended** branch — escalate to `/plan --idea` (carrying `--yes`),
167
+ > abandoning the draft — rather than prompting the three-way choice. Display
168
+ > the draft and the verdict line for the record, then proceed without
169
+ > waiting. See
170
+ > [`plan.md` § Headless / non-interactive mode](../plan.md#headless--non-interactive-mode---yes).
158
171
 
159
172
  - **`story` verdict (or gate skipped via handoff)** → no extra prompt. The
160
173
  operator confirms the draft as usual and the run proceeds to Phase 3
@@ -177,6 +190,9 @@ verdict folds into this same stop:
177
190
 
178
191
  **Never auto-route.** The verdict is advisory; the operator always decides,
179
192
  and no `agent::*` / label transition happens on either side of the choice.
193
+ (**`--yes` exception:** headless mode pre-authorizes the **Recommended**
194
+ branch deterministically — see the `--yes` note above — the only sanctioned
195
+ auto-route, present solely to make `/plan` driveable without an operator.)
180
196
 
181
197
  ## Phase 3 — Persist (`gh issue create`)
182
198
 
@@ -44,6 +44,7 @@ forwards them to the active path helper:
44
44
  | `--force` | Epic | Close + recreate an existing ticket tree on re-plan. |
45
45
  | `--force-review` | Epic | Force the operator review gate even when risk routing would skip it. |
46
46
  | `--allow-over-budget` | Epic | Permit a decomposition that exceeds the framework `maxTickets` reviewability budget. |
47
+ | `--yes` | both | **Non-interactive / headless mode.** Deterministically auto-proceeds the two `/plan` HITL STOP gates — the ideation one-pager / scope-triage confirm (gate #1) and the Phase-7 Epic operator review gate (gate #2) — without waiting for operator input. Parallel to [`/deliver --yes`](deliver.md). Composes with `--allow-over-budget` and with the risk-routed Phase-7 skip (it forces a proceed where those do not apply). Default (flag absent) behavior is unchanged: both gates still STOP for interactive use. |
47
48
  | `--steal` | Epic | Forcibly transfer a foreign Epic-lease. |
48
49
  | `--dry-run` | both | Author + validate without GitHub writes. |
49
50
  | `--body <path>` | Story | Pre-authored Story body file; validate (and create, unless `--dry-run`) without re-authoring. |
@@ -58,6 +59,71 @@ commands (story-sized Epic ↘ Story; epic-sized Story draft ↗ Epic) is now
58
59
  an **internal branch switch** inside this router: same skills, same
59
60
  helpers, no command hop and no operator re-entry.
60
61
 
62
+ ### Headless / non-interactive mode (`--yes`)
63
+
64
+ `--yes` is the headless escape hatch for an unattended driver (CI, a
65
+ benchmark harness, or any `claude -p` run with no human at the keyboard). It
66
+ is the `/plan`-side parallel of [`/deliver --yes`](deliver.md), which
67
+ suppresses that command's segment-plan confirmation. `/plan` has exactly
68
+ **two** HITL STOP gates, and `--yes` deterministically auto-proceeds **both**
69
+ without waiting for operator input:
70
+
71
+ 1. **Gate #1 — the ideation one-pager / scope-triage / clarity confirm.**
72
+ This single conceptual gate has three faces depending on the entry form,
73
+ and `--yes` auto-proceeds **all three**:
74
+ - On the `--idea` Epic path,
75
+ [`helpers/plan-epic.md`](helpers/plan-epic.md) Phase 1 (folding in the
76
+ Phase 1.5 scope-triage verdict) STOPs to confirm the sharpened one-pager.
77
+ - On the `--idea` Story path,
78
+ [`helpers/plan-story.md`](helpers/plan-story.md) Phase 2 STOPs to
79
+ confirm the drafted Story body.
80
+ - On the existing-Epic (`/plan <epicId>`) path, the Phase 6 Epic Clarity
81
+ Gate's **needs-refinement** branch STOPs to confirm the refined-body
82
+ diff before persisting it.
83
+
84
+ Under `--yes` each auto-proceeds: the one-pager / draft / refined-body
85
+ confirm resolves as **approved**, and a `story` / `borderline` triage
86
+ verdict resolves to its **recommended** branch (the one the gate prose
87
+ marks "Recommended") rather than prompting the three-way choice. The
88
+ verdict / clarity scoring is still recorded in chat (one line); only the
89
+ *wait* is suppressed. The clarity-gate *scoring* itself (deterministic
90
+ section-presence in Phase 6 step 1) still runs — `--yes` suppresses only
91
+ the operator confirm of the proposed refinement.
92
+ 2. **Gate #2 — the Phase-7 Epic operator review gate.** When risk routing
93
+ forces a review (`planningRisk.requiresReview === true`, or the operator
94
+ also passed `--force-review`),
95
+ [`helpers/plan-epic.md`](helpers/plan-epic.md) Phase 7 STOPs for operator
96
+ approval of the PRD / Tech Spec / Acceptance Spec before decomposition.
97
+ Under `--yes` this review auto-proceeds straight to Phase 8 — the three
98
+ context tickets stay **open** through delivery exactly as on the low-risk
99
+ auto-proceed path.
100
+
101
+ **Composition.** `--yes` is orthogonal to the other planning flags and
102
+ composes cleanly:
103
+
104
+ - With **`--allow-over-budget`**: `--yes` suppresses the gate *waits* while
105
+ `--allow-over-budget` still governs the `maxTickets` over-budget persist —
106
+ passing `--yes` alone does **not** waive the budget gate.
107
+ - With the **risk-routed Phase-7 skip**: when risk routing already skips the
108
+ Phase-7 review (low-risk, no `--force-review`), `--yes` is a no-op for that
109
+ gate — it only *forces a proceed where the gate would otherwise STOP*, it
110
+ never *adds* a stop or relaxes any non-HITL validator.
111
+
112
+ **`--yes` suppresses only the HITL operator *waits* above.** It does **not**
113
+ relax any deterministic gate — the Phase 6 Epic Clarity Gate **scoring**
114
+ (section-presence), the Phase 7.5 Tech Spec Section Gate, the file-assumption
115
+ / DAG validators, the Phase 10 readiness healthcheck, and the `agent::blocked`
116
+ runtime pause all behave exactly as without the flag. A `--yes` run that hits
117
+ one of those still fails closed; the flag is an operator-input suppressor, not
118
+ a validation override. The other operator-input *waits* in the pipeline also
119
+ auto-proceed under `--yes` — the non-blocking Phase 2 duplicate-search pause
120
+ (distinct-Epic confirmed), the Phase 6 clarity refinement-diff confirm
121
+ (refined body approved), and the advisory Phase 8.3/8.4/8.5
122
+ consolidation/critic diffs (consolidated/critic output applied) — since they
123
+ are operator-input waits, not validators. The two *named* HITL STOP gates the
124
+ Story tracks (gate #1, gate #2) are the load-bearing pair; these additional
125
+ waits are auto-proceeded for the same headless reason.
126
+
61
127
  ## First-run preflight
62
128
 
63
129
  Before routing to a path helper, run a **first-run preflight** to catch
@@ -109,9 +175,11 @@ stubbed docs, or an unready doctor verdict).
109
175
  [`core/scope-triage`](../skills/core/scope-triage/SKILL.md) skill on the
110
176
  seed. Record the verdict in chat (one line).
111
177
  4. **Delegate.** Read the selected path helper **in full** and execute it
112
- from its entry phase, forwarding the absorbed flags. The helper's phase
113
- numbering, HITL gates, and scripts are unchanged — this router adds no
114
- phase content.
178
+ from its entry phase, forwarding the absorbed flags (including `--yes`).
179
+ The helper's phase numbering, HITL gates, and scripts are unchanged — this
180
+ router adds no phase content. When `--yes` is present, the two HITL STOP
181
+ gates auto-proceed per [Headless / non-interactive mode](#headless--non-interactive-mode---yes)
182
+ above; every deterministic gate still runs.
115
183
  5. **Internal returns.** When a path helper would historically have handed
116
184
  off to the other planning command, switch helpers in-place and continue;
117
185
  surface the switch to the operator as a one-line note.
package/docs/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.72.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.71.0...mandrel-v1.72.0) (2026-06-17)
6
+
7
+
8
+ ### Added
9
+
10
+ * **plan:** add a headless/non-interactive flag to suppress the ideation one-pager + Phase-7 review gate ([#4223](https://github.com/dsj1984/mandrel/issues/4223)) ([#4226](https://github.com/dsj1984/mandrel/issues/4226)) ([9ffa355](https://github.com/dsj1984/mandrel/commit/9ffa35543f897199c096ad45a568de39b0424ba7))
11
+
12
+
13
+ ### Fixed
14
+
15
+ * **deliver:** fail open on unparseable code-review severity bullets in auto-merge gate (refs [#4222](https://github.com/dsj1984/mandrel/issues/4222)) ([#4224](https://github.com/dsj1984/mandrel/issues/4224)) ([ca949bd](https://github.com/dsj1984/mandrel/commit/ca949bd7d98eea440571db6636f8be2f2128c3b9))
16
+
5
17
  ## [1.71.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.70.0...mandrel-v1.71.0) (2026-06-16)
6
18
 
7
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mandrel",
3
- "version": "1.71.0",
3
+ "version": "1.72.0",
4
4
  "description": "Claude Code-first opinionated workflow framework: instructions, personas, skills, and SDLC workflows that govern AI coding assistants.",
5
5
  "files": [
6
6
  ".agents/",