mandrel 2.38.0 → 2.40.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 (41) hide show
  1. package/.agents/README.md +51 -11
  2. package/.agents/agents/auditor.md +5 -0
  3. package/.agents/docs/SDLC.md +21 -12
  4. package/.agents/docs/agentrc-reference.json +1 -4
  5. package/.agents/docs/configuration.md +2 -2
  6. package/.agents/instructions.md +17 -16
  7. package/.agents/schemas/agentrc.schema.json +6 -7
  8. package/.agents/scripts/audit-to-stories.js +510 -66
  9. package/.agents/scripts/generate-skills-index.js +158 -75
  10. package/.agents/scripts/lib/audit-to-stories/epic-grouping-directive.js +39 -0
  11. package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +290 -0
  12. package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +94 -3
  13. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +10 -0
  14. package/.agents/scripts/lib/changed-files.js +100 -9
  15. package/.agents/scripts/lib/config-settings-schema.js +25 -7
  16. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  17. package/.agents/scripts/lib/label-constants.js +18 -0
  18. package/.agents/scripts/lib/label-taxonomy.js +18 -5
  19. package/.agents/scripts/lib/orchestration/epic-container.js +186 -0
  20. package/.agents/scripts/lib/orchestration/epic-expansion.js +148 -0
  21. package/.agents/scripts/lib/orchestration/plan-persist/epic-ops.js +320 -0
  22. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +18 -0
  23. package/.agents/scripts/lib/orchestration/run-epilogue.js +130 -1
  24. package/.agents/scripts/lib/qa/resolve-qa-contract.js +58 -6
  25. package/.agents/scripts/lib/skills/skills-index.js +168 -0
  26. package/.agents/scripts/lib/skills/walk-skill-files.js +133 -9
  27. package/.agents/scripts/plan-persist.js +39 -1
  28. package/.agents/scripts/providers/github/sub-issue-add.js +218 -0
  29. package/.agents/scripts/quality-preview.js +50 -9
  30. package/.agents/scripts/resolve-stories.js +42 -2
  31. package/.agents/scripts/validate-skills.js +53 -66
  32. package/.agents/templates/docs/audit-sweep-runbook.md +169 -0
  33. package/.agents/workflows/audit-to-stories.md +85 -7
  34. package/.agents/workflows/helpers/audit-lens-core.md +24 -4
  35. package/.agents/workflows/helpers/deliver-reference.md +8 -0
  36. package/.agents/workflows/helpers/plan-reference.md +28 -0
  37. package/.agents/workflows/mandrel-deliver.md +47 -43
  38. package/.agents/workflows/mandrel-plan.md +44 -38
  39. package/.agents/workflows/qa-run.md +13 -5
  40. package/docs/CHANGELOG.md +28 -0
  41. package/package.json +1 -1
@@ -51,7 +51,8 @@ path). The report MUST include every section its lens template mandates — writ
51
51
  `_No findings._` rather than omitting a section — and always an
52
52
  `## Executive Summary` and a `## Detailed Findings` section. The Executive
53
53
  Summary carries the self-cross-check `kept <k> / dropped <d>` line
54
- ([below](#self-cross-check)).
54
+ ([below](#self-cross-check)) and, beside it, the machine-readable severity
55
+ tally ([below](#severity-tally)).
55
56
 
56
57
  Every finding under `## Detailed Findings` uses the shared 7-field skeleton
57
58
  below. A lens may **add** fields (e.g. a WCAG success criterion, a CWE ID, a
@@ -64,9 +65,11 @@ drops or renames a shared field.
64
65
  ```markdown
65
66
  ## Executive Summary
66
67
 
67
- [The lens's headline read plus the self-cross-check `kept <k> / dropped <d>`
68
- line. A lens may mandate additional report sections between here and the
69
- findings — its own body names them.]
68
+ [The lens's headline read, the self-cross-check `kept <k> / dropped <d>` line,
69
+ and the severity tally line below. A lens may mandate additional report
70
+ sections between here and the findings — its own body names them.]
71
+
72
+ Severity tally: Critical <n> / High <n> / Medium <n> / Low <n>
70
73
 
71
74
  ## Detailed Findings
72
75
 
@@ -188,6 +191,23 @@ that rests on one of them:
188
191
  A lens that keeps every finding still records `dropped 0` — the line's absence
189
192
  is itself a defect (it means the pass did not run).
190
193
 
194
+ ## Severity tally (mandatory, machine-readable) {#severity-tally}
195
+
196
+ The Executive Summary MUST also carry exactly one tally line, counting the
197
+ findings you kept:
198
+
199
+ ```text
200
+ Severity tally: Critical <n> / High <n> / Medium <n> / Low <n>
201
+ ```
202
+
203
+ Write every bucket, zeros included, and never count `Info` — the scale already
204
+ excludes it from scheduled work. This line is the report's own checksum:
205
+ `audit-to-stories --scan` cross-checks it against the findings its parser
206
+ extracted and names any disagreement as a **report failure**
207
+ (`summary.reportFailures[]`), and `--auto` refuses to file anything from a
208
+ report whose line is missing or wrong. A parse that silently drops findings is
209
+ otherwise indistinguishable from a clean audit.
210
+
191
211
  ## Execution strategy {#execution-strategy}
192
212
 
193
213
  A lens is a self-contained, read-only unit of work — exactly the shape a
@@ -292,6 +292,14 @@ This executes, in order:
292
292
  (files issues when auto-file is on; posts `follow-ups`).
293
293
  - `sibling-coherence` — Spec/Acceptance coherence check across sibling bodies
294
294
  (`plan-run-sibling-coherence`).
295
+ - `epic-close` — closes a container Epic once **every** child Story is
296
+ `agent::done`, as `completed`. This is the only completion cascade v2 has:
297
+ it closes the container and nothing else — no child status roll-up, no label
298
+ inheritance, no reopening. Because linkage is parent→child only, the parent
299
+ is found by scanning open `type::epic` issues, and only an Epic containing
300
+ one of *this run's* Stories is considered, so an unrelated container is
301
+ never swept. An Epic with an outstanding child is reported `pending` and
302
+ left open.
295
303
 
296
304
  A single-Story run skips the epilogue — follow-ups are captured on merge
297
305
  confirm instead (`captureStoryFollowUps`).
@@ -414,6 +414,34 @@ That is the whole point of running it first: a dry run that comes back clean
414
414
  has already paid for every deterministic refusal, so the real persist has
415
415
  nothing left to discover except network failure.
416
416
 
417
+ ## The container Epic (Gate #3, N>2)
418
+
419
+ Above two Stories, `/mandrel-plan` offers to group them under one `type::epic`
420
+ container. Confirmed, persist opens it **after** the Stories — its body embeds
421
+ their issue numbers and its sub-issue edges need their database ids — and
422
+ links every created Story both ways it can: a `- [ ] #N` body checklist and a
423
+ native GitHub sub-issue edge. Both are written because each survives what the
424
+ other does not; the delivery-side reader unions them.
425
+
426
+ What the Epic must never carry: an `agent::*` label (that absence keeps it out
427
+ of the bare `/mandrel-deliver` ready list and outside the `type::story`-scoped
428
+ body lint), a `## Spec`, an `acceptance[]` / `verify[]`, or any path, finding
429
+ or rationale a child does not already hold. It is a container; unique content
430
+ here is content no delivering agent reads.
431
+
432
+ What the **Stories** never gain is an `Epic: #N` footer. Linkage is
433
+ parent→child only, which is exactly why every existing refusal of that footer
434
+ still stands and each Story stays independently deliverable (ADR
435
+ `20260905-5139`).
436
+
437
+ Degradation is deliberate: an unensurable `type::epic` label skips the Epic
438
+ entirely (an unlabelled container is not a container), while a failed
439
+ sub-issue edge only warns — the checklist still lists every child. Either way
440
+ the Stories are untouched and deliver by id. A resumed persist adopts an
441
+ existing Epic carrying the same fingerprint, which is keyed on the title **and
442
+ the exact child set**, so a run grouping different Stories never adopts the
443
+ wrong container.
444
+
417
445
  ## Ready means fully persisted
418
446
 
419
447
  `agent::ready` is the **terminal** step, not part of the creating POST.
@@ -7,8 +7,8 @@ description:
7
7
 
8
8
  # /mandrel-deliver
9
9
 
10
- > **Lean spine.** Happy path + gate list. Sequencing, dispatch mechanics,
11
- > intent phrases, ceremony and the epilogue live in on-demand
10
+ > **Lean spine.** Happy path + gate list. Sequencing, dispatch mechanics, intent
11
+ > phrases, ceremony and the epilogue live in on-demand
12
12
  > [`helpers/deliver-reference.md`](helpers/deliver-reference.md) ("reference"
13
13
  > below); the unplanned path in
14
14
  > [`helpers/deliver-light.md`](helpers/deliver-light.md). Every delivery reads
@@ -21,9 +21,8 @@ close-and-land tail; Stories are implemented via
21
21
  [`helpers/deliver-story.md`](helpers/deliver-story.md).
22
22
 
23
23
  The dependency graph is **discovered, not declared** — `resolve-stories.js`
24
- reads it from live state, so you can deliver Stories **across plan runs and
25
- over time**. `plan-run::<id>` is filter metadata, never a resolution input;
26
- `route::lite` a body-derived hint only.
24
+ reads it from live state, so you can deliver Stories **across plan runs and over
25
+ time**. `plan-run::<id>` is filter metadata, never a resolution input.
27
26
 
28
27
  ## Inputs
29
28
 
@@ -36,25 +35,28 @@ you read:
36
35
  | `/mandrel-deliver 4712` | ids | One Story via `helpers/deliver-story.md`, **inline in this session** — no `story-worker` spawn. |
37
36
  | `/mandrel-deliver 4712 4713 …` | ids | Resolve the set, sequence by the discovered graph via `stories-wave-tick.js`, dispatch sub-agents. |
38
37
  | `/mandrel-deliver 4712 - 4716` | ids | A **range** — every id in the inclusive span. |
38
+ | `/mandrel-deliver 4700` (a `type::epic`) | ids | The Epic's **open** child Stories. Mixes with Story ids. |
39
39
  | `/mandrel-deliver add a --json flag to doctor` | prompt | Unplanned work: gate, author a receipt Story, land it — [`helpers/deliver-light.md`](helpers/deliver-light.md). |
40
40
 
41
41
  **The discriminator is lexical and total.** An argument matching `^#?\d+$` is an
42
42
  id, and `^#?\d+\s*[-–—]\s*#?\d+$` an inclusive **range** — pass one on as a
43
- single unspaced token, never hand-expanded (reference § Ranges). Either shape
43
+ single unspaced token, never hand-expanded (reference). Either shape
44
44
  means ids; anything else means a prompt. A **mixed** invocation (ids *and*
45
- prose) is a **hard error** — refuse it and ask which was meant. A ticket not
46
- `type::story`, or carrying an `Epic: #N` footer, is a hard error too.
45
+ prose) is a **hard error** — refuse it and ask which was meant. A ticket that
46
+ is neither `type::story` nor `type::epic`, or that carries an `Epic: #N`
47
+ footer, is a hard error too — container Epics link parent→child only, so that
48
+ footer stays refused.
47
49
 
48
50
  ## Saying what you want
49
51
 
50
- No flags to remember: state intent — *"…but I'll merge it myself"*, *"…one at
51
- a time"* — and announce what you read. Phrasings and the flags they fill in:
52
+ No flags to remember: state intent — *"…but I'll merge it myself"*, *"…one at a
53
+ time"* — and announce what you read. Phrasings and the flags they fill in:
52
54
  reference § Intent phrases.
53
55
 
54
56
  `--yes` is **runner-set, never operator-typed**: cron, `/loop` and headless
55
57
  dispatch set it to mean *nobody is at the keyboard*, which fails the unplanned
56
- path's over-scope stop closed to an envelope instead of a question. Never offer
57
- it to an operator or add it to an attended run.
58
+ path's over-scope stop closed to an envelope instead of a question. Never add it
59
+ to an attended run.
58
60
 
59
61
  ## Procedure
60
62
 
@@ -66,9 +68,11 @@ it to an operator or add it to an attended run.
66
68
  `node .agents/scripts/resolve-stories.js --ids <id,id,...>`. It validates the
67
69
  set and shows what will run: read `stories[]`, `dag[]` and `done[]` to
68
70
  present the order in step 2, but do **not** thread them into step 3 — the
69
- tick re-resolves the graph every beat. It hard-errors (exit 1) on an id that
70
- is not a Story, carries an `Epic: #N` footer, or whose edges cannot be
71
- read a missing gate would co-dispatch against an unlanded blocker.
71
+ tick re-resolves the graph every beat. An Epic id expands to its open child
72
+ Stories first **announce it**. It hard-errors (exit 1) on an id that is
73
+ neither a Story nor an Epic, on an `Epic: #N` footer, on an Epic with no
74
+ open children, or on edges it cannot read — a missing gate would co-dispatch
75
+ against an unlanded blocker.
72
76
 
73
77
  2. **Confirm (N>1).** Present the order; wait unless `--yes`.
74
78
 
@@ -87,62 +91,62 @@ it to an operator or add it to an attended run.
87
91
  Each beat re-probes live state to derive done / in-flight itself; you never
88
92
  compute them. `--dispatched` is the one thing you must supply — the
89
93
  append-only list of every id you spawned this run. Cross-run de-confliction
90
- via the assignee lease is automatic (reference §§ Sequencing edge cases,
91
- Dispatch mechanics). Branch on the exit code:
94
+ via the assignee lease is automatic. Branch on the exit code:
92
95
  - **0** — dispatch each `ready` id (already capped and overlap-free); an
93
96
  empty `ready` with work in flight means "waiting", so keep looping;
94
97
  `epilogueDue: true` means every Story is done — step 4.
95
98
  - **2 / 3 / 4** — `cycleError` / `wedged` / `blocked`: stop the loop and
96
- route per reference § Sequencing edge cases. **4** is the protocol's HITL
97
- pause ([`instructions.md` § 1.J](../instructions.md)) — surface it and
98
- wait for the operator; never poll.
99
+ route per reference. **4** is the protocol's HITL pause
100
+ ([`instructions.md` § 1.J](../instructions.md)) — surface it and wait for
101
+ the operator; never poll.
99
102
 
100
103
  4. **Close each hand-off** (§ Closing what the workers hand back), then, with
101
104
  every Story landed, run the **per-run epilogue (N>1)**:
102
- `node .agents/scripts/plan-run-epilogue.js --stories 101,102`. N=1 skips it
103
- ([reference § Per-run epilogue](helpers/deliver-reference.md)).
105
+ `node .agents/scripts/plan-run-epilogue.js --stories 101,102`, which also
106
+ closes a container Epic whose children all landed. N=1 skips it
107
+ ([reference](helpers/deliver-reference.md)).
104
108
 
105
109
  5. **Correct what the change invalidated.** If a memory you recalled this
106
- session is now wrong — a trap this landed, a budget it moved — fix that
107
- entry now, while both the old belief and the new fact are in context, and
108
- say so when you report. No memory substrate → skip silently. Sweeping the
109
- whole pool is [`/memory-consolidate`](memory-consolidate.md), not this step.
110
+ session is now wrong — a trap this landed, a budget it moved — fix that entry
111
+ now, while both the old belief and the new fact are in context, and say so
112
+ when you report. No memory substrate → skip silently. Sweeping the whole pool
113
+ is [`/memory-consolidate`](memory-consolidate.md), not this step.
110
114
 
111
115
  ## Closing what the workers hand back {#tail}
112
116
 
113
117
  **The tail is the orchestrator's, not the worker's.** A dispatched
114
118
  `story-worker` stops at a pushed branch and returns a hand-off; **you** run
115
119
  [`helpers/deliver-story.md`](helpers/deliver-story.md) Step 3
116
- (`single-story-close.js`) for it, foreground, and relay the envelope.
120
+ (`single-story-close.js`) for it, foreground, and relay its envelope.
117
121
 
118
- **Serialize the tail.** Implementation runs in parallel; closing does not. Close
119
- one Story at a time — closes contend on the base branch, the merge queue and
120
- the checkout. A worker handing back mid-close waits its turn.
122
+ **Serialize the tail.** Implementation runs in parallel; closing does not.
123
+ Close one Story at a time — closes contend on the base branch, the merge queue
124
+ and the checkout. A worker handing back mid-close waits its turn.
121
125
 
122
- **A worker returning no terminal envelope is the expected shape**, not a failure
123
- to answer with a re-dispatch: only close mints one. Close the pushed branch, or
124
- probe read-only with `node .agents/scripts/deliver-recover.js --story <storyId>`
125
- and resume the worker or close it names.
126
+ **A worker returning no terminal envelope is expected**, not a failure to answer
127
+ with a re-dispatch: only close mints one. Close the pushed branch, or probe
128
+ read-only with `node .agents/scripts/deliver-recover.js --story <storyId>` and
129
+ resume what it names.
126
130
 
127
131
  **Reading the outcome.** Each close ends the Story in one schema-validated
128
132
  envelope — `landed` | `pending` | `blocked` | `failed`; statuses, exits and
129
- fields are digest § 5. `pending` is **not** a failure — `nextCommand` resumes
130
- it; run that, do not re-dispatch.
133
+ fields are digest § 5. `pending` is **not** a failure — run its `nextCommand`.
131
134
 
132
135
  **Branch model (authoritative).** `story-<id>` → PR → `main` (squash +
133
136
  required checks), per digest § 2; dependent Stories land sequentially. Ceremony
134
- depth (profiles + derived level via `ceremony-routing.js`, review depth reading
135
- it): reference § Ceremony.
137
+ depth (profiles + the derived level via `ceremony-routing.js`, which review
138
+ depth reads): reference § Ceremony.
136
139
 
137
140
  ## Constraints
138
141
 
139
142
  - **Land or block — never a silent local build** (digest § 2). Attended delivers
140
143
  default to close-and-land (`delivery.routing.closeAndLand: true`); rest at
141
- `agent::closing` only when a human owns it.
142
- - **`/mandrel-deliver` never plans.** Planned tickets come from [`/mandrel-plan`](mandrel-plan.md), and
143
- an over-scope prompt **escalates and ends** — never invoke `/mandrel-plan` in this
144
- session to rescue it ([`helpers/deliver-light.md`](helpers/deliver-light.md)
145
- § Escalation is terminal).
144
+ `agent::closing` only when a human owns the merge.
145
+ - **`/mandrel-deliver` never plans.** Planned tickets come from
146
+ [`/mandrel-plan`](mandrel-plan.md), and an over-scope prompt **escalates and
147
+ ends** — never invoke `/mandrel-plan` in this session to rescue it
148
+ ([`helpers/deliver-light.md`](helpers/deliver-light.md) § Escalation is
149
+ terminal).
146
150
 
147
151
  ## See also
148
152
 
@@ -7,14 +7,14 @@ description:
7
7
 
8
8
  # /mandrel-plan
9
9
 
10
- > **Lean spine.** Happy path + gate list; edge-case detail lives on demand in
10
+ > **Lean spine.** Happy path + gate list; edge-case detail is on demand in
11
11
  > [`helpers/plan-reference.md`](helpers/plan-reference.md).
12
12
 
13
13
  ## Inputs
14
14
 
15
15
  Single planning path — there is no Epic/Story router, no scope-triage
16
- `epic|story` verdict. **Derive the mode from what the operator typed, announce
17
- it, act**:
16
+ `epic|story` verdict (Gate #3's container groups, never routes). **Derive the
17
+ mode from what the operator typed, announce it, act**:
18
18
 
19
19
  | Invocation | Mode | Behavior |
20
20
  | --- | --- | --- |
@@ -25,9 +25,9 @@ it, act**:
25
25
  | `/mandrel-plan 4712`, already delivered | amends | Amend a shipped Story from a **delta envelope**, not a re-interrogation. |
26
26
 
27
27
  **Resolving a bare id.** Read live state rather than asking: `agent::done` can
28
- only be amended, an open unplanned issue can only be planned. **Announce the
29
- derivation** — "4712 is `agent::done` → amending". Ask **only** for an open
30
- Story already at `agent::ready`.
28
+ only be amended, an open unplanned issue only planned. **Announce the derivation** —
29
+ "4712 is `agent::done` → amending". Ask **only** for an open Story already at
30
+ `agent::ready`.
31
31
 
32
32
  ## Saying what you want
33
33
 
@@ -36,7 +36,7 @@ No flags to remember — state intent; the workflow fills in the CLI
36
36
 
37
37
  `--yes` is **runner-set, never operator-typed** — cron, `/loop`, and headless
38
38
  dispatch set it to mean *nobody is at the keyboard*, which auto-proceeds the
39
- gates below (#1 and #2). Never offer it to an operator or an attended run.
39
+ gates below (#1 and #2) and skips #3. Never offer it to an operator.
40
40
 
41
41
  ## Default-single split policy
42
42
 
@@ -62,46 +62,44 @@ and derives source ids from its `sourceTickets[]`; the CLI also writes
62
62
  The envelope carries docs context, the story-author prompt, `sourceTickets[]`,
63
63
  `duplicates[]` (open **Stories**, never Epics) and advisory `complexitySignals`
64
64
  (**no routing authority**). A trivial scope can claim the lite route at
65
- persist — shape-validated, failing closed to `full`
66
- ([detail](helpers/plan-reference.md)).
65
+ persist — shape-validated, failing closed to `full` ([detail](helpers/plan-reference.md)).
67
66
 
68
67
  **Triage each unknown by resolver** ([detail](helpers/plan-reference.md)): an
69
68
  **AFK** unknown (research settles it) is resolved before authoring, never
70
- assumed; a **HITL** unknown (an operator call) goes to Gate #1 as "needs your
71
- decision". Under `--yes` do not ask free-form operator questions AFK unknowns
72
- are still researched; only HITL unknowns land in Key Assumptions, each marked a
73
- decision-made-by-default.
69
+ assumed; a **HITL** unknown goes to Gate #1. Under `--yes` do not ask free-form
70
+ operator questions AFK unknowns are still researched; only HITL unknowns land
71
+ in Key Assumptions, each a decision-made-by-default.
74
72
 
75
73
  **Gate #1** — STOP to confirm the sharpened plan intent and any
76
74
  duplicate-candidate review. Under `--yes`, auto-proceed.
77
75
 
78
- On a truthy `memoryPoolAdvisory.recommend`, name [`/memory-consolidate`](memory-consolidate.md)
79
- as an operator option, quoting its `reasons[]` — advisory, never invoked here.
76
+ On a truthy `memoryPoolAdvisory.recommend`, name
77
+ [`/memory-consolidate`](memory-consolidate.md) quoting its `reasons[]` —
78
+ advisory, never invoked here.
80
79
 
81
80
  On a truthy `deliverLightSuggestion.suggested`, offer — advisory, never an
82
- automatic reroute — to deliver the seed instead; on confirm, route **in this
81
+ automatic reroute — to deliver the seed instead; on confirm route **in this
83
82
  session** into [`helpers/deliver-light.md`](helpers/deliver-light.md), its gate
84
- filled from this envelope. A truthy `complexitySignals.uiSurface` marks a
85
- UI-touching plan: name [`/prototype`](prototype.md) as an operator option,
86
- never invoke it here. [Both offers](helpers/plan-reference.md).
83
+ filled from this envelope. A truthy `complexitySignals.uiSurface` names
84
+ [`/prototype`](prototype.md) as an option; never invoke it here.
85
+ [Both offers](helpers/plan-reference.md).
87
86
 
88
87
  ### 2. Author
89
88
 
90
89
  **One-shot authoring.** Start from `stories.template.json`; author
91
90
  `stories.json` in one pass. `body` is markdown **or** a structured object;
92
91
  persist parses either, serializes canonical markdown and syncs top-level
93
- `acceptance[]` / `verify[]` into it — never dual-author those lists.
92
+ `acceptance[]` / `verify[]` in — never dual-author those lists.
94
93
 
95
94
  **Grounding = your reads + Phase 8.** Nothing inventories the repo: read each
96
- file you cite; persist's file-assumption gate hard-errors on any
97
- `{path, assumption}` absent from the tree. Entry fields:
98
- [reference](helpers/plan-reference.md).
95
+ file you cite; persist hard-errors on any `{path, assumption}` absent from
96
+ the tree. Fields: [reference](helpers/plan-reference.md).
99
97
 
100
98
  Artifacts under `temp/plan-<slug>/`: `stories.json` (**length 1 by default**;
101
99
  over-budget Specs fail closed — split or tighten, never under `docs/`); optional
102
- `techspec.md` (**N===1 only** folded into `## Spec`); optional
103
- `acceptance-manifest.json` (N>1 partition — `--plan-acceptance`). For N=1 use
104
- the envelope `systemPrompts.story`; split only under the policy above.
100
+ `techspec.md` (**N===1 only**, folded into `## Spec`) and
101
+ `acceptance-manifest.json` (N>1 — `--plan-acceptance`). Use the envelope
102
+ `systemPrompts.story`; split only under the policy above.
105
103
 
106
104
  **Tickets mode:** every Story authors a top-level `supersedes[]`; persist
107
105
  refuses a partial map ([shape](helpers/plan-reference.md)).
@@ -115,9 +113,9 @@ node .agents/scripts/plan-critics.js \
115
113
  ```
116
114
 
117
115
  Run **before** persist — the last point a finding folds into a re-author.
118
- It exits 0 on **any** verdict (verdicts route work, they do not gate) and exits
119
- **1** only on a usage/IO error — no critic ran, no skip ledgered: **do not
120
- proceed to Persist**, fix and re-run.
116
+ It exits 0 on **any** verdict (verdicts route work, they do not gate) and
117
+ exits **1** only on a usage/IO error — no critic ran: **do not proceed to
118
+ Persist**, fix and re-run.
121
119
 
122
120
  - **Both `dispatch: false`** — proceed to Persist (each skip is ledgered).
123
121
  - **Either `dispatch: true`** — dispatch **one fresh-context, maker-blind
@@ -125,16 +123,19 @@ proceed to Persist**, fix and re-run.
125
123
  authoring transcript), fold findings into Gate #2 or a re-author round, re-run
126
124
  this step. Pre-mortem triggers (incl. the external-dependency probe), the
127
125
  advisory-only `textHygiene.findings[]` lints and dispatch shape:
128
- [reference § Critic dispatch detail](helpers/plan-reference.md).
126
+ [reference](helpers/plan-reference.md).
129
127
 
130
128
  ### 3. Persist
131
129
 
132
130
  **Gate #2** — STOP for approval before persist **only** when the operator asked
133
131
  to review (`--force-review`). Under `--yes`, auto-proceed.
134
132
 
135
- Run persist with `--dry-run` **first**same command, writes suppressed;
136
- every gate runs before the first `createIssue`
137
- ([the list](helpers/plan-reference.md)):
133
+ **Gate #3 (N>2 only)** — offer a **container Epic**; on a yes add
134
+ `--epic-title` / `--epic-goal` below. It carries nothing a child does not
135
+ ([shape](helpers/plan-reference.md)). Never below 3 Stories, never unasked.
136
+
137
+ Run persist with `--dry-run` **first** — same command, writes suppressed; every
138
+ gate runs before the first `createIssue` ([list](helpers/plan-reference.md)):
138
139
 
139
140
  ```bash
140
141
  node .agents/scripts/plan-persist.js \
@@ -142,24 +143,29 @@ node .agents/scripts/plan-persist.js \
142
143
  --plan-dir temp/plan-<slug> \
143
144
  [--plan-acceptance temp/plan-<slug>/acceptance-manifest.json] \
144
145
  [--tech-spec temp/plan-<slug>/techspec.md] \
145
- [--source-tickets 123,456]
146
+ [--source-tickets 123,456] \
147
+ [--epic-title "<name>" --epic-goal "<one paragraph>"]
146
148
  ```
147
149
 
148
- At lite shape, `--chain-on-clean` folds a clean dry-run into the real persist;
150
+ At lite shape `--chain-on-clean` folds a clean dry-run into the real persist;
149
151
  a full plan keeps its review trip.
150
152
 
151
153
  Persist creates `type::story` issue(s), a **metadata-only** `plan-run::<id>`
152
- label, and `blocked by #<id>` footers for N>1 `depends_on` edges.
154
+ label, `blocked by #<id>` footers for N>1 `depends_on` edges, and on a confirmed
155
+ Gate #3 a `type::epic` container linking every created Story.
153
156
  `agent::ready` is the **terminal** flip after receipts land; stdout is pure
154
157
  JSON. Tickets mode also comments on and closes each source id
155
158
  ([detail](helpers/plan-reference.md)).
156
159
 
157
160
  ## Constraints
158
161
 
159
- - `/mandrel-plan` starts delivery **only** through a confirmed Gate #1 light route —
160
- never off its own authored Stories, which land via [`/mandrel-deliver`](mandrel-deliver.md).
162
+ - `/mandrel-plan` starts delivery **only** through a confirmed Gate #1 light
163
+ route — never off its own authored Stories, which land via
164
+ [`/mandrel-deliver`](mandrel-deliver.md).
161
165
  - Duplicate search targets open Stories (`type::story`), not Epics; and
162
166
  deterministic gates still fail closed under `--yes`.
167
+ - A container Epic is never a work item, and no Story body gains an `Epic: #N`
168
+ footer: linkage is parent→child only.
163
169
 
164
170
  ## See also
165
171
 
@@ -106,7 +106,11 @@ browser surface") and stop. Never attempt a headless fallback.
106
106
  runs against via
107
107
  [`resolveQaEnvironment`](../scripts/lib/qa/resolve-qa-contract.js), yielding
108
108
  `{ name, baseUrl, signInSeam, allowWrites }` (`allowWrites` defaults to an
109
- explicit boolean — `true` only for the conventional `local` environment). When
109
+ explicit boolean — `true` only for the conventional `local` environment;
110
+ `signInSeam` is `null` when the environment declares none). A `{ skill }` seam
111
+ is resolved here too: the resolver throws when the id resolves to no
112
+ `SKILL.md`, so a dangling seam fails before the browser is driven, never
113
+ mid-sweep. When
110
114
  `<env>` is supplied, pass it straight through (an exact name wins; a raw URL
111
115
  matches by origin). When it is omitted (bare `/qa-run`), **prompt** the
112
116
  operator, enumerating every environment as `name → baseUrl` (marking
@@ -152,10 +156,14 @@ resolved environment's discriminated-union seam (anchored on `baseUrl`):
152
156
  input; under a `urlTemplate` seam the contract is authored as a plain name
153
157
  array and no per-persona auth material is read.
154
158
  - **`kind: 'skill'`** — invoke the named consumer sign-in skill (procedural /
155
- non-URL sign-in). Real auth uses **only `credentialRef`-indirected material**
156
- the skill dereferences; raw passwords, tokens, or API keys are never inlined
157
- into the contract, the workflow, or chat, and captured evidence is redacted
158
- per [`helpers/qa-core.md`](helpers/qa-core.md) before persistence.
159
+ non-URL sign-in), reading the `SKILL.md` at the seam's resolved `skillPath`.
160
+ Real auth uses **only `credentialRef`-indirected material** the skill
161
+ dereferences; raw passwords, tokens, or API keys are never inlined into the
162
+ contract, the workflow, or chat, and captured evidence is redacted per
163
+ [`helpers/qa-core.md`](helpers/qa-core.md) before persistence.
164
+ - **No seam (`signInSeam` absent → `null`)** — a declarable state, not a
165
+ defect. Drive only the unauthenticated surface and record the gap in the
166
+ envelope; never fabricate a session or enter real credentials by hand.
159
167
 
160
168
  **Verification (the envelope's proof).** After sign-in, confirm the
161
169
  authenticated state with a `take_snapshot` showing the persona badge (the user
package/docs/CHANGELOG.md CHANGED
@@ -15,6 +15,34 @@ All notable changes to this project will be documented in this file.
15
15
  -->
16
16
  <!-- markdownlint-disable-file MD004 MD012 MD037 -->
17
17
 
18
+ ## [2.40.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.39.0...mandrel-v2.40.0) (2026-09-06)
19
+
20
+
21
+ ### Added
22
+
23
+ * audit-to-stories --auto: persist the cross-run ledger via a `--ledger-commit` PR and ship the audit-sweep runbook template ([#5145](https://github.com/dsj1984/mandrel/issues/5145)) ([#5151](https://github.com/dsj1984/mandrel/issues/5151)) ([7274e6a](https://github.com/dsj1984/mandrel/commit/7274e6abe1bbf4fca6e1ed09abc8b4ebc9598d89))
24
+ * audit-to-stories: parse `####` findings under grouping headers and fail closed on a missing or mismatched `Severity tally:` line ([#5144](https://github.com/dsj1984/mandrel/issues/5144)) ([#5149](https://github.com/dsj1984/mandrel/issues/5149)) ([d9d8275](https://github.com/dsj1984/mandrel/commit/d9d827584989406e85d3e22110e6b741dab837e9))
25
+ * container Epic: /mandrel-plan offers one above 2 Stories, /mandrel-deliver expands an Epic id, /audit-to-stories requests one by default ([#5139](https://github.com/dsj1984/mandrel/issues/5139)) ([#5147](https://github.com/dsj1984/mandrel/issues/5147)) ([60dd912](https://github.com/dsj1984/mandrel/commit/60dd912f9d00dbf2fb7c17f8c98639b4008b5892))
26
+
27
+
28
+ ### Fixed
29
+
30
+ * audit-to-stories --wire-edges: carry the live provider's write ports through the dedup adapter ([#5143](https://github.com/dsj1984/mandrel/issues/5143)) ([#5146](https://github.com/dsj1984/mandrel/issues/5146)) ([380577e](https://github.com/dsj1984/mandrel/commit/380577e5e8f215bf28bb15ef8f2ac03c5958f0b8))
31
+ * **tests:** assert the report path separator-agnostically for Windows ([#5153](https://github.com/dsj1984/mandrel/issues/5153)) ([afe7fda](https://github.com/dsj1984/mandrel/commit/afe7fda7aa257bfb5e0745160363189ed84c2ad1))
32
+ * **tests:** import the provider fixture as a file URL for Windows ([#5150](https://github.com/dsj1984/mandrel/issues/5150)) ([797af46](https://github.com/dsj1984/mandrel/commit/797af46269e22e878fbd13293e5e5df6a044a1d3))
33
+
34
+ ## [2.39.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.38.0...mandrel-v2.39.0) (2026-09-05)
35
+
36
+
37
+ ### Added
38
+
39
+ * qa: make the `signInSeam` `{ skill }` arm authorable via a consumer-writable `.agents/local/skills/` root, and declarable-absent ([#5135](https://github.com/dsj1984/mandrel/issues/5135)) ([#5136](https://github.com/dsj1984/mandrel/issues/5136)) ([4a7c89e](https://github.com/dsj1984/mandrel/commit/4a7c89e55b83c20637c518950e3cdee5620a4442))
40
+
41
+
42
+ ### Fixed
43
+
44
+ * quality-preview --staged: re-base the staged scope to MERGE_HEAD so a base-sync merge commit is not scored for the base branch's work ([#5131](https://github.com/dsj1984/mandrel/issues/5131)) ([#5132](https://github.com/dsj1984/mandrel/issues/5132)) ([d6fccc1](https://github.com/dsj1984/mandrel/commit/d6fccc15738fe22965ff8e9c819a55fe69b39815))
45
+
18
46
  ## [2.38.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.37.0...mandrel-v2.38.0) (2026-09-04)
19
47
 
20
48
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mandrel",
3
- "version": "2.38.0",
3
+ "version": "2.40.0",
4
4
  "description": "Claude Code-first opinionated workflow framework: instructions, skills, rules, and SDLC workflows that govern AI coding assistants.",
5
5
  "files": [
6
6
  ".agents/",