mandrel 2.9.0 → 2.10.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 (57) hide show
  1. package/.agents/agents/.markdownlint.json +4 -0
  2. package/.agents/agents/acceptance-critic.md +30 -5
  3. package/.agents/agents/auditor.md +36 -19
  4. package/.agents/agents/plan-critic.md +31 -5
  5. package/.agents/agents/story-worker.md +91 -100
  6. package/.agents/docs/configuration.md +16 -4
  7. package/.agents/docs/execution-reference.md +13 -0
  8. package/.agents/docs/workflows.md +1 -1
  9. package/.agents/instructions.md +131 -265
  10. package/.agents/rules/git-conventions.md +47 -83
  11. package/.agents/rules/orchestration-error-handling.md +28 -0
  12. package/.agents/schemas/agentrc.schema.json +24 -2
  13. package/.agents/schemas/validation-evidence.schema.json +3 -1
  14. package/.agents/scripts/acceptance-eval.js +1 -1
  15. package/.agents/scripts/apply-quality-bootstrap.js +1 -1
  16. package/.agents/scripts/check-test-temp-hygiene.js +438 -0
  17. package/.agents/scripts/deliver-recover.js +23 -6
  18. package/.agents/scripts/lib/audit-suite/index.js +5 -0
  19. package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +179 -0
  20. package/.agents/scripts/lib/audit-suite/selector.js +1 -1
  21. package/.agents/scripts/lib/config/temp-paths.js +121 -1
  22. package/.agents/scripts/lib/config-settings-schema-delivery.js +30 -0
  23. package/.agents/scripts/lib/config-settings-schema.js +1 -1
  24. package/.agents/scripts/lib/observability/metrics-ledger.js +217 -0
  25. package/.agents/scripts/lib/observability/runtime-friction.js +7 -0
  26. package/.agents/scripts/lib/orchestration/complexity-gate.js +113 -2
  27. package/.agents/scripts/lib/orchestration/deliver-recover.js +137 -10
  28. package/.agents/scripts/lib/orchestration/merge-block-class.js +36 -15
  29. package/.agents/scripts/lib/orchestration/merge-poll.js +213 -0
  30. package/.agents/scripts/lib/orchestration/plan-context.js +57 -0
  31. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +182 -9
  32. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +29 -2
  33. package/.agents/scripts/lib/orchestration/plan-metrics.js +31 -82
  34. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +102 -2
  35. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +215 -14
  36. package/.agents/scripts/lib/orchestration/resolve-stories.js +7 -0
  37. package/.agents/scripts/lib/orchestration/review-providers/native.js +34 -16
  38. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +8 -3
  39. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +230 -79
  40. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +89 -1
  41. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +73 -0
  42. package/.agents/scripts/lib/templates/decomposer-prompts.js +13 -6
  43. package/.agents/scripts/lib/test-env.js +65 -0
  44. package/.agents/scripts/plan-context.js +66 -9
  45. package/.agents/scripts/plan-critics.js +115 -3
  46. package/.agents/scripts/plan-persist.js +11 -1
  47. package/.agents/scripts/plan-run-epilogue.js +1 -1
  48. package/.agents/scripts/single-story-confirm-merge.js +65 -5
  49. package/.agents/scripts/stories-wave-tick.js +1 -1
  50. package/.agents/workflows/deliver.md +86 -230
  51. package/.agents/workflows/helpers/deliver-reference.md +167 -0
  52. package/.agents/workflows/helpers/deliver-story-reference.md +203 -0
  53. package/.agents/workflows/helpers/deliver-story.md +114 -432
  54. package/.agents/workflows/helpers/plan-reference.md +211 -0
  55. package/.agents/workflows/plan.md +107 -304
  56. package/docs/CHANGELOG.md +27 -0
  57. package/package.json +1 -1
@@ -1,64 +1,52 @@
1
1
  ---
2
2
  description:
3
3
  Unified planning entry point. Interrogate → author → persist. Emits one
4
- Story by default (folded Tech Spec in the Story body); splits into N>1
5
- only under the default-single split policy.
4
+ Story by default; splits into N>1 only under the default-single split
5
+ policy.
6
6
  ---
7
7
 
8
8
  # /plan --seed "<text>" | --seed-file <path> | --tickets <ids>
9
9
 
10
- ## Role
11
-
12
- Single planning path. `/plan` owns the full ceremony — there is no
13
- Epic/Story router, no scope-triage `epic|story` verdict, and no
14
- `deliveryShape`. Two operator modes only:
15
-
16
- 1. **Text** — seed from chat or a file.
17
- 2. **Tickets** — analyze existing issue(s) into proper Stories.
18
-
19
- Audit findings become Stories via [`/audit-to-stories`](audit-to-stories.md)
20
- (separate workflow), which hands off with `--emit-plan-seed` →
21
- `/plan --seed-file <path>`.
10
+ > **Lean spine.** Happy path + gate list; edge-case and reference detail
11
+ > lives in the on-demand
12
+ > [`helpers/plan-reference.md`](helpers/plan-reference.md).
22
13
 
23
14
  ## Inputs
24
15
 
16
+ Single planning path — there is no
17
+ Epic/Story router, no scope-triage `epic|story` verdict:
18
+
25
19
  | Invocation | Behavior |
26
20
  | --- | --- |
27
- | `/plan --seed "<text>"` | Ideation from chat text: interrogate → author **one Story by default** → persist. |
28
- | `/plan --seed-file <path>` | Author from on-disk notes / plan seed (e.g. audit-to-stories handoff). |
21
+ | `/plan --seed "<text>"` / `--seed-file <path>` | Ideation from chat text or on-disk notes: interrogate → author **one Story by default** → persist. |
29
22
  | `/plan --tickets 123[,456…]` | Fetch issue(s), analyze into proper Stories (prefer N=1 rewrite). |
30
23
 
31
- `--body` is **not** a `/plan` entry. Persist always goes through
32
- `plan-persist.js --stories …`.
24
+ `--body` is **not** a `/plan` entry; persist always goes through
25
+ `plan-persist.js`.
33
26
 
34
27
  ## Flags
35
28
 
36
29
  | Flag | Meaning |
37
30
  | --- | --- |
38
- | `--seed "<text>"` | Seed text for ideation. |
39
- | `--seed-file <path>` | Pre-authored notes / plan-seed path. |
40
- | `--tickets <ids>` | Comma-separated issue ids to analyze. Closed as superseded at persist (see below). |
41
- | `--no-close-superseded` | Keep the `--tickets` source issues openno supersede comment, no close. |
42
- | `--force-review` | STOP at gate #2 for operator review of the assembled plan. The only thing that gates review — there is no risk-derived routing (Story #4542). |
43
- | `--allow-over-budget` | Permit a plan that exceeds `maxTickets` (rare N>1). |
31
+ | `--seed "<text>"` / `--seed-file <path>` | Seed text / pre-authored notes path. |
32
+ | `--tickets <ids>` | Issue ids to analyze; closed as superseded at persist. |
33
+ | `--no-close-superseded` | Keep the source issues open no supersede comment, no close. |
34
+ | `--force-review` | STOP at gate #2 for operator review the only review gate (Story #4542). |
35
+ | `--route-downgrade-reason "<text>"` | Audited `full`→`lite` downgrade (Story #4707), ledgered per Story. |
36
+ | `--allow-over-budget` | Permit a plan exceeding `maxTickets`. |
44
37
  | `--yes` | Non-interactive: auto-proceed gate #1 and gate #2 HITL waits. |
45
- | `--dry-run` | Author + validate without GitHub writes. Run it as a pre-pass before every real persist (see below). |
38
+ | `--dry-run` | Author + validate without GitHub writes; run as a pre-pass. |
46
39
 
47
40
  ## Default-single split policy
48
41
 
49
- Author **one Story** unless:
50
-
51
- 1. the pieces have **near-zero overlap** (genuinely independent capabilities), or
52
- 2. there is an **architectural seam** (different deployables, migration vs consumer).
53
-
54
- Coupled work stays one Story decompose it inside `## Slicing` as
55
- intra-session checkpoints, not sibling tickets. When N>1, every acceptance
56
- criterion must belong to exactly one Story; `plan-persist` runs
57
- `assertAcceptancePartition` and refuses coupled splits.
58
-
59
- **N=1 is the lean path:** one authoring prompt, folded `## Spec` in the
60
- Story body, light risk/critic profile. Do not run Epic-scale decompose,
61
- clarity, or reconciler ceremony for a single Story.
42
+ Author **one Story** unless (1) the pieces have **near-zero overlap**
43
+ (genuinely independent capabilities), or (2) there is an **architectural
44
+ seam** (different deployables, migration vs consumer). Coupled work stays
45
+ one Story decompose it inside `## Slicing` as intra-session checkpoints,
46
+ not sibling tickets. When N>1, every acceptance criterion belongs to exactly
47
+ one Story (`assertAcceptancePartition` refuses coupled splits). **N=1 is the
48
+ lean path:** one authoring prompt, folded `## Spec`, light risk/critic
49
+ profile no Epic-scale ceremony.
62
50
 
63
51
  ## Procedure
64
52
 
@@ -71,315 +59,130 @@ node .agents/scripts/plan-context.js --seed "<seed>" \
71
59
  # or: --tickets 123,456
72
60
  ```
73
61
 
74
- **Always pass `--out temp/plan-<slug>/plan-context.json`.** The CLI writes the
75
- envelope there (creating parent dirs); persist auto-discovers that file from
76
- `--plan-dir` and derives the `--tickets` source ids from its `sourceTickets[]`.
77
- That is what makes superseding work without anyone re-typing ids
78
- (Story #4554). The envelope still goes to stdout too, so piping is unaffected.
79
-
80
- The envelope carries docs context, codebase snapshot, BDD probe, risk
81
- heuristics, the story-author system prompt, `sourceTickets[]` (`--tickets`
82
- mode), `duplicates[]` (open **Stories** whose title/body overlap the
83
- seed never Epics), and the `complexityRoute` ceremony-lite signal (below).
62
+ **Always pass `--out`.** Persist auto-discovers that envelope from
63
+ `--plan-dir` and derives source-ticket ids from its `sourceTickets[]`
64
+ (Story #4554); the CLI also writes **`stories.template.json`** the
65
+ authoring skeleton step 2 starts from.
66
+
67
+ The envelope carries docs context, codebase snapshot, the story-author
68
+ prompt, `sourceTickets[]`, `duplicates[]` (open **Stories** overlapping the
69
+ seed never Epics), and the `complexityRoute` signal:
70
+ `"lite"` (trivial single-artifact scope author one minimal Story, skip
71
+ fresh-critic / Tech-Spec ceremony; every close gate still runs) or `"full"`
72
+ (everything else; fails toward `full` on any doubt). Detail:
73
+ [`helpers/plan-reference.md` § Ceremony-lite gate](helpers/plan-reference.md).
84
74
  Under `--yes`, do not ask free-form operator questions — unresolved
85
75
  unknowns land in Key Assumptions.
86
76
 
87
- #### Ceremony-lite complexity gate (`complexityRoute`)
88
-
89
- The envelope's `complexityRoute` field is a **deterministic, conservative**
90
- plan-time gate (Story #4683) that routes a genuinely trivial single-artifact
91
- seed onto a collapsed path so it stops paying the full two-session
92
- plan/deliver ceremony that measurably buys no quality at that size:
93
-
94
- - **`route: "lite"`** — a trivial scope (seed ≤ `maxSeedWords` words **and** ≤
95
- `maxArtifacts` enumerated items). Collapse the ceremony: author **one minimal
96
- Story** and skip the fresh-critic / Tech-Spec ceremony a one-artifact scope
97
- does not earn. The lite route is **not** licence to drop a non-negotiable —
98
- its `preserves` field enumerates exactly what still holds: the Story ticket,
99
- the PR-to-`main` landing, every repo quality gate, and the security baseline.
100
- Those gates still run in `single-story-close.js` regardless of route.
101
- - **`route: "full"`** — everything else. The gate fails toward `full` on any
102
- doubt (empty seed, over the word ceiling, a multi-capability enumeration, or
103
- the gate disabled via `planning.complexityGate.enabled=false`), so a real
104
- capability slice never loses ceremony. Author normally under the split policy.
105
-
106
- The threshold and its override knob (`planning.complexityGate.{enabled,
107
- maxSeedWords, maxArtifacts}`) are documented in
108
- [`.agents/docs/configuration.md`](../docs/configuration.md) under `### planning`;
109
- the defaults live on `DEFAULT_COMPLEXITY_GATE` in
110
- [`lib/orchestration/complexity-gate.js`](../scripts/lib/orchestration/complexity-gate.js).
111
-
112
77
  **Gate #1** — STOP to confirm the sharpened plan intent and any
113
78
  duplicate-candidate review. Under `--yes`, auto-proceed.
114
79
 
115
80
  ### 2. Author
116
81
 
117
- Write artifacts under `temp/plan-<slug>/`:
118
-
119
- - `stories.json` array of Story tickets (**length 1 by default**). Each
120
- body uses the canonical `story-body` shape (`## Goal`, optional
121
- `## Slicing`, optional `## Spec`, `## Changes`, `## Acceptance`,
122
- `## Verify`, …). The Story is the single executable document: put lean
123
- approach prose in `## Spec`, binding criteria in top-level
124
- `acceptance[]` / `verify[]` (persist syncs them into the body). Do not
125
- restate Goal/Acceptance inside Spec. Over-budget Specs fail closed —
126
- split the Story or tighten Spec; never write Specs under `docs/`.
127
- - optional `techspec.md` — **N===1 only** convenience when Spec was authored
128
- outside the Story JSON; persist folds it into that Story's `## Spec`.
129
- Forbidden for N>1 (each Story must carry its own Spec).
130
- - optional `acceptance-manifest.json` — plan-level AC list (a JSON
131
- `string[]`) for partition coverage when N>1. Pass it to persist as
132
- `--plan-acceptance` or it is not read.
133
-
134
- For N=1, use the envelope `systemPrompts.story` and emit one cohesive
135
- Story. Split only under the policy above.
136
-
137
- **Tickets mode — author `supersedes[]` on every Story.** In `--tickets`
138
- mode each Story carries a top-level `supersedes` array claiming the source
139
- issues it replaces. It is bookkeeping, not part of the Story body, so it is
140
- never serialized into the markdown:
82
+ **One-shot authoring (Story #4707).** Start from `stories.template.json`
83
+ (or the skeleton below); author `stories.json` in one pass. `body` is a
84
+ markdown string **or** a structured object; persist parses either,
85
+ serializes the canonical markdown, and syncs the top-level `acceptance[]` /
86
+ `verify[]` into the body never dual-author those lists.
141
87
 
142
88
  ```jsonc
143
- {
144
- "slug": "close-superseded",
145
- "supersedes": [
146
- 4525,
147
- { "id": 4529, "note": "The filed `--changed-only` fix is provably inert; the correction is recorded here." }
148
- ]
149
- }
89
+ // temp/plan-<slug>/stories.json
90
+ [
91
+ {
92
+ "slug": "hyphen-case-slug", // ^[a-z0-9][a-z0-9-]*$
93
+ "type": "story",
94
+ "title": "Short descriptive title",
95
+ "body": {
96
+ "goal": "One sentence: why this Story exists.",
97
+ "spec": "Optional — contract and invariants.",
98
+ "changes": [{ "path": "path/to/file.ext", "assumption": "refactors-existing" }], // creates | refactors-existing | deletes
99
+ "non_goals": [],
100
+ "reason_to_exist": "One coherent reason this Story exists."
101
+ },
102
+ "acceptance": ["A testable, observable criterion"],
103
+ "verify": ["exact command (unit|contract|e2e|validate)"],
104
+ "depends_on": [] // sibling Story slugs, N>1 only
105
+ }
106
+ ]
150
107
  ```
151
108
 
152
- Entries are bare issue numbers, or `{ id, note }` when the plan has
153
- something to say about *that* source issuea correction to its analysis,
154
- or why it was folded in with others. The optional `note` is rendered into
155
- that issue's supersede comment, so planning that materially corrects a
156
- source issue records the correction on the ticket rather than emitting
157
- template-only prose.
109
+ Artifacts under `temp/plan-<slug>/`: `stories.json`
110
+ (**length 1 by default**; over-budget Specs fail closed split or tighten,
111
+ never write Specs under `docs/`); optional `techspec.md` (**N===1 only**
112
+ folded into `## Spec`); optional `acceptance-manifest.json` (N>1 partition
113
+ list pass as `--plan-acceptance` or it is not read). For N=1,
114
+ use the envelope `systemPrompts.story` and emit one cohesive Story.
115
+ Split only under the policy above.
158
116
 
159
- ### Supersede-map partition
160
-
161
- `plan-persist` refuses a partial supersede map **before** it creates any
162
- Story (mirroring `assertAcceptancePartition`): every id passed to
163
- `--tickets` must be claimed by **exactly one** Story, and no Story may
164
- claim an id that was not a source ticket. With N>1 the mapping is not
165
- total by default — an authored map is the only thing that can say
166
- `#4525-#4528 → #4530` while `#4529 → #4531`, which a blanket "superseded by
167
- this plan-run" reference could not.
117
+ **Tickets mode:** every Story authors a top-level `supersedes[]` claiming
118
+ the source issues it replaces; persist refuses a partial map (shape:
119
+ [`helpers/plan-reference.md` § Tickets mode](helpers/plan-reference.md)).
168
120
 
169
121
  ### 2.5 Critics
170
122
 
171
- Evaluate the critic-dispatch conditions against the authored draft — here,
172
- **before** persist, because this is the last point where a finding can still
173
- be folded into a re-author round rather than into live issues:
174
-
175
123
  ```bash
176
124
  node .agents/scripts/plan-critics.js \
177
125
  --stories temp/plan-<slug>/stories.json \
178
126
  [--tech-spec temp/plan-<slug>/techspec.md]
179
127
  ```
180
128
 
181
- It prints a verdict on stdout and exits 0 on **any** verdict — the verdict
182
- routes work, it does not gate the run. It exits **1** only on a usage/IO
183
- error (an unreadable or malformed `--stories` / `--tech-spec` path). That is
184
- not an advisory "proceed": no critic ran and no skip was ledgered, so **do
185
- not proceed to Persist** — fix the path and re-run:
186
-
187
- ```jsonc
188
- {
189
- "consolidation": { "critic": "consolidation", "dispatch": false, "reasons": ["…"] },
190
- "premortem": { "critic": "pre-mortem", "dispatch": true, "reasons": ["…"] },
191
- "textHygiene": { "critic": "text-hygiene", "findings": [] }
192
- }
193
- ```
129
+ Run **before** persist the last point a finding folds into a re-author
130
+ round. It exits 0 on **any** verdict (verdicts route work, they do not
131
+ gate) and exits **1** only on a usage/IO error no critic ran, no skip
132
+ ledgered: **do not proceed to Persist**; fix and re-run.
194
133
 
195
- The verdict's third entry, `textHygiene`, is advisory-only (Story #4599): it
196
- carries deterministic body lints (`dangling-citation` / `open-question` /
197
- `slicing-mass`) with no dispatch semantics it spawns nothing and never
198
- gates the run. Fold `textHygiene.findings[]` into the re-author round the
199
- same way critic findings fold in: fix each named defect in `stories.json`
200
- (anchor or inline the citation, resolve the question into a declarative
201
- assumption, thin the Slicing checkpoint) and re-run this step. Empty
202
- `findings` add nothing to the round.
203
-
204
- - **Both `dispatch: false`** — proceed straight to Persist. The conditions
205
- provably have nothing for a critic to find, and each skip is recorded on the
206
- plan-metrics ledger so under-firing stays auditable.
207
- - **Either `dispatch: true`** — dispatch **one fresh-context sub-agent per
208
- firing critic**, then fold its findings into the Gate #2 view or a re-author
209
- round before persist. When `delivery.routing.roleScopedAgents` is enabled
210
- (the **default**), dispatch each firing critic with `subagent_type:
211
- plan-critic` — it boots on the role-scoped
212
- [`plan-critic`](../agents/plan-critic.md) context (its own system prompt, no
213
- `CLAUDE.md` @-closure) that carries the maker-blind invariant, the
214
- `consolidation` and `pre-mortem` charters, and the output shape standalone.
215
- When the kill-switch is off (`roleScopedAgents: false`) or the host cannot
216
- spawn at this depth, fall back to a generic sub-agent and hand it the same
217
- charter (the `consolidation` / `pre-mortem` definitions in
218
- [`plan-critic.md`](../agents/plan-critic.md)). Either way the critic is
219
- **maker-blind**: hand it the draft artifacts (`stories.json`, and
220
- `techspec.md` when present) — never the authoring transcript or the reasons
221
- the planner believed its own draft is sound. A critic that reads the maker's
222
- case grades the case, not the draft.
223
-
224
- Fold what survives back into `stories.json` and re-run this step. Findings are
225
- advisory input to the operator's Gate #2 decision, not an automatic re-author
226
- mandate.
134
+ - **Both `dispatch: false`** proceed to Persist (each skip is ledgered).
135
+ - **Either `dispatch: true`** dispatch **one fresh-context, maker-blind
136
+ sub-agent per firing critic** (hand it only the draft artifacts,
137
+ never the authoring transcript), fold findings into Gate #2 or a
138
+ re-author round, re-run this step. Pre-mortem triggers (incl. the
139
+ external-dependency probe, #4700), folding the advisory-only
140
+ `textHygiene.findings[]` lints, and the role-scoped dispatch shape:
141
+ [`helpers/plan-reference.md` § Critic dispatch detail](helpers/plan-reference.md).
227
142
 
228
143
  ### 3. Persist
229
144
 
230
- **Gate #2** — when the operator passed `--force-review`, STOP for approval of
231
- the assembled plan before persist. Under `--yes`, auto-proceed.
232
-
233
- `--force-review` is the **only** thing that raises this gate — there is no
234
- risk-derived routing (Story #4542): a gate a plan can lower for itself is not a
235
- gate.
145
+ **Gate #2** — with `--force-review`, STOP for approval before persist (the
146
+ **only** trigger). Under `--yes`, auto-proceed.
236
147
 
237
- #### Dry-run pre-pass (always)
238
-
239
- Run persist with `--dry-run` **before** the real one. It is the same command
240
- with the same flags — only the GitHub writes are suppressed:
148
+ Run persist with `--dry-run` **first** — same command, GitHub writes
149
+ suppressed; every gate (ticket validator, body parse, DAG, capacity,
150
+ budget, reachability, split-policy and supersede partitions, Spec fold)
151
+ runs before the first `createIssue`. Then:
241
152
 
242
153
  ```bash
243
154
  node .agents/scripts/plan-persist.js \
244
155
  --stories temp/plan-<slug>/stories.json \
245
- [--plan-acceptance temp/plan-<slug>/acceptance-manifest.json] \
246
- [--tech-spec temp/plan-<slug>/techspec.md] \
247
156
  --plan-dir temp/plan-<slug> \
248
- --dry-run
249
- ```
250
-
251
- Every gate — ticket validator, body parse, subject-prefix, DAG, capacity,
252
- budget, reachability, split-policy and supersede partitions, Spec fold —
253
- runs **before** the first `createIssue`, so a dry-run exercises all of them
254
- write-free. An authoring mistake surfaces here, where the fix costs one
255
- re-author, instead of after `k` of `N` Stories are already live.
256
-
257
- #### The real persist
258
-
259
- ```bash
260
- node .agents/scripts/plan-persist.js \
261
- --stories temp/plan-<slug>/stories.json \
262
157
  [--plan-acceptance temp/plan-<slug>/acceptance-manifest.json] \
263
158
  [--tech-spec temp/plan-<slug>/techspec.md] \
264
- [--plan-dir temp/plan-<slug>] \
265
- [--plan-context temp/plan-<slug>/plan-context.json] \
266
- [--source-tickets 123,456] \
267
- [--no-close-superseded] \
268
- [--force-review] \
269
- [--allow-over-budget]
159
+ [--source-tickets 123,456] [...flags from the table above]
270
160
  ```
271
161
 
272
- Pass `--plan-acceptance` whenever step 2 wrote an `acceptance-manifest.json`
273
- it is what `assertAcceptancePartition` checks the N>1 split against.
274
-
275
- Persist creates Story issue(s) with `type::story` (plus any sanitized
276
- authored `labels[]`) and, when N>1, writes each authored `depends_on` edge
277
- into the sibling's body as a `blocked by #<id>` footer — the ordering
278
- `/deliver` resolves from. No batch label is applied (Story #4540 retired
279
- `plan-run::<id>`). Ends by naming the exact command:
280
- `/deliver <storyId> [<storyId> ...]`.
281
-
282
- stdout is a pure JSON result; all log lines go to stderr, so a headless
283
- driver can `JSON.parse` the stdout stream directly.
284
-
285
- #### Ready means fully persisted
286
-
287
- `agent::ready` is the **terminal** step, not part of the creating POST
288
- (Story #4541). The order is: create unlabelled → upsert `story-plan-state` on
289
- every Story → upsert `plan-summary` on the primary → flip every Story to
290
- `agent::ready`.
291
-
292
- This is what lets `/deliver` trust the label: a Story carrying
293
- `agent::ready` always has its persist receipt on the ticket, so nothing can
294
- pick it up mid-write and read a half-persisted plan.
295
-
296
- #### Resuming a failed persist
297
-
298
- Persist is **idempotent over the same authored artifacts**. Each created body
299
- carries an invisible plan fingerprint (derived from the Story's slug +
300
- title), and persist indexes the open `type::story` backlog by it before
301
- creating anything.
302
-
303
- So if a transient GitHub failure strands the run at Story `k` of `N`:
304
-
305
- | | Behaviour |
306
- | --- | --- |
307
- | The `1..k-1` Stories | Live, but **not** `agent::ready` — invisible to `/deliver`, not half-delivered. |
308
- | Re-running persist | Adopts them by fingerprint, creates only the missing ones, then flips the whole cohort ready. |
309
- | Editing `stories.json` first | Changing a slug or title changes the fingerprint — the old issue is orphaned rather than adopted. Close it by hand. |
310
-
311
- Just re-run the same command. Do not hand-delete the stranded issues first.
312
-
313
- #### Temp hygiene
314
-
315
- A terminal-success run deletes its own `--plan-dir`. Every persist also reaps
316
- abandoned `temp/plan-*` directories older than 7 days, so dry-runs, failed
317
- gates, and abandoned authoring sessions do not accumulate under `temp/`.
318
-
319
- ### How the source ids reach persist
320
-
321
- In `--tickets` mode persist needs to know which ids were fetched. It resolves
322
- them **envelope-first** (Story #4554):
323
-
324
- | Channel | When it wins |
325
- | --- | --- |
326
- | Envelope `sourceTickets[]` | **The normal path.** Written by step 1's `--out`, then read from `--plan-context <file>` or auto-discovered at `<plan-dir>/plan-context.json`. No ids to re-type. |
327
- | `--source-tickets <ids>` | Explicit **override** for hand-driven runs (no captured envelope, or deliberately narrowing the set). Wins over the envelope; a disagreement is warned about, not silently reconciled. |
328
-
329
- The result envelope's `supersede.sourceTicketOrigin` reports which channel was
330
- used (`envelope` \| `flag` \| `none`).
331
-
332
- Every path with no envelope is **audible** — persist cannot tell a legitimate
333
- `--seed` run from a `--tickets` run whose envelope was never captured, so it
334
- says so rather than deciding silently:
335
-
336
- | Situation | Behaviour |
337
- | --- | --- |
338
- | Neither `--plan-dir` nor `--plan-context` | **Warn** — nothing was read; only `--source-tickets` can supply ids. |
339
- | Auto-discovered `<plan-dir>/plan-context.json` absent | **Warn** — degrade to `--source-tickets`; a `--seed` run legitimately has none. |
340
- | Explicit `--plan-context` missing | **Fatal** — the operator named a file and meant it. |
341
- | Envelope present but unparseable | **Fatal** — a corrupt envelope is not "no source tickets"; treating it as such is how a `--tickets` run used to report success having superseded nothing. |
342
-
343
- Whichever channel supplies them, the supersede-map partition above still
344
- fail-closes: a `--tickets` run whose Stories forgot `supersedes[]` is now
345
- **caught** (`source ticket #N is not claimed by any Story`) instead of
346
- partitioning an empty set and passing vacuously.
347
-
348
- ### Closing superseded source tickets
349
-
350
- **Default on.** After the Stories exist, persist comments on each source
351
- issue naming the specific Story that claims it — plus that Story's optional
352
- per-supersede `note` — and closes it with reason **`not_planned`**
353
- (`state_reason`). Nothing has shipped at persist time and the issue will not
354
- be actioned in its own right, so `not_planned` is the honest reason;
355
- `completed` would be a lie. This is what keeps the tracker from asserting
356
- that already-planned work is still unowned, and it writes down the supersede
357
- link that makes the history readable.
358
-
359
- | Behaviour | Contract |
360
- | --- | --- |
361
- | Default | Comment + close every source ticket as `not_planned`. |
362
- | `--no-close-superseded` | Skips all commenting and closing. Story creation is unchanged. Use it for a genuinely partial supersede — when the plan folded in only *part* of an issue and the remainder must stay open. |
363
- | `--dry-run` | Posts no comment and closes nothing; reports what it would have done. |
364
- | Re-run | Idempotent — the comment is keyed off a `superseded-by` structured-comment marker, and an already-closed source is skipped. |
365
- | Already closed / deleted / inaccessible | Skipped and reported. Never throws. |
366
- | Close-phase failure | **Never fails the run.** Stories stay created; the result envelope's `supersede` report names which tickets were and were not closed so the operator can finish by hand. |
162
+ Persist creates Story issue(s) with `type::story` plus a `plan-run::<id>`
163
+ grouping label (**metadata only** never a delivery-resolution input, Story #4692); N>1 `depends_on` edges become `blocked by #<id>` body footers.
164
+ `agent::ready` is the **terminal** flip, after all receipts are upserted — a
165
+ ready Story is always fully persisted (Story #4541). stdout is pure JSON
166
+ (logs on stderr).
367
167
 
368
- `--seed` / `--seed-file` modes have no source tickets, so no close phase
369
- runs at all.
168
+ In `--tickets` mode persist resolves source ids **envelope-first** and
169
+ closes each superseded source as `not_planned` with a comment (default on).
170
+ Detail (channels, close contract, resume, temp hygiene):
171
+ [`helpers/plan-reference.md`](helpers/plan-reference.md) — on a stranded
172
+ persist, re-run the same command; never hand-delete issues.
370
173
 
371
174
  ## Constraints
372
175
 
373
- - `/plan` never starts delivery.
374
- - No Epic ticket is opened. No reconciler. No `delivery::single` marker.
176
+ - `/plan` never starts delivery. No Epic ticket, no reconciler, no
177
+ `delivery::single` marker.
375
178
  - Duplicate search targets open Stories (`type::story`), not Epics.
376
- - Deterministic gates (ticket validator, split policy, reachability, budget)
377
- still fail closed under `--yes`.
179
+ - Deterministic gates still fail closed under `--yes`.
378
180
 
379
181
  ## See also
380
182
 
381
- - [`/deliver`](deliver.md) — delivery entry point (`/deliver <storyId>`).
382
- - [`/audit-to-stories`](audit-to-stories.md) — audit findings → plan seed
383
- `/plan --seed-file`.
183
+ - [`/deliver`](deliver.md) — delivery entry point.
184
+ - [`/audit-to-stories`](audit-to-stories.md) — audit findings → plan seed.
185
+ - [`helpers/plan-reference.md`](helpers/plan-reference.md) — ceremony-lite,
186
+ supersede, critic, and persist-resume detail.
384
187
  - [`core/scope-triage`](../skills/core/scope-triage/SKILL.md) — optional
385
188
  split-advisory notes only (no routing verdict).
package/docs/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [2.10.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.9.0...mandrel-v2.10.0) (2026-07-23)
6
+
7
+
8
+ ### Added
9
+
10
+ * ceremony-proportional lite route: end-to-end lite delivery, one-shot authoring, contract-level Story prose ([#4707](https://github.com/dsj1984/mandrel/issues/4707)) ([#4709](https://github.com/dsj1984/mandrel/issues/4709)) ([d9d543a](https://github.com/dsj1984/mandrel/commit/d9d543a40ec55c063e8b3bff143782b053f8ef52))
11
+ * **ceremony:** findings-yield instrumentation + pre-eval full-suite discipline + lens diff-floor ([#4699](https://github.com/dsj1984/mandrel/issues/4699)) ([#4706](https://github.com/dsj1984/mandrel/issues/4706)) ([f7a6582](https://github.com/dsj1984/mandrel/commit/f7a658218f8a42997cb5bdbfe018c8b27e211b94))
12
+ * **close:** async merge-confirm mode — probe once, return pending, land via background confirm ([#4698](https://github.com/dsj1984/mandrel/issues/4698)) ([#4705](https://github.com/dsj1984/mandrel/issues/4705)) ([79360ce](https://github.com/dsj1984/mandrel/commit/79360ce224d48896050494e15b227c68d430e003))
13
+ * **plan:** external-dependency pre-mortem trigger so N=1 plans get a viability check ([#4700](https://github.com/dsj1984/mandrel/issues/4700)) ([#4702](https://github.com/dsj1984/mandrel/issues/4702)) ([4874890](https://github.com/dsj1984/mandrel/commit/4874890fab5ae346c2daf43b873ac374dc4a8df8))
14
+ * **plan:** reintroduce a per-plan-run grouping label on persisted Stories (metadata-only) ([#4692](https://github.com/dsj1984/mandrel/issues/4692)) ([#4693](https://github.com/dsj1984/mandrel/issues/4693)) ([1ca7d75](https://github.com/dsj1984/mandrel/commit/1ca7d7582972585a414268b242fb88cfcc94953e))
15
+ * residency & cache topology: always-on diet, shared-prefix subagent boots, lean-spine workflows ([#4708](https://github.com/dsj1984/mandrel/issues/4708)) ([#4718](https://github.com/dsj1984/mandrel/issues/4718)) ([8ba40d8](https://github.com/dsj1984/mandrel/commit/8ba40d84982a046a2f11e4be5e2a562b74c8f67e))
16
+
17
+
18
+ ### Fixed
19
+
20
+ * **close:** harden the merge wait — gh subprocess timeouts, extracted fail-fast tree, resume-CLI wait override, honest required-run evidence ([#4710](https://github.com/dsj1984/mandrel/issues/4710)) ([#4717](https://github.com/dsj1984/mandrel/issues/4717)) ([3edc9b5](https://github.com/dsj1984/mandrel/commit/3edc9b53b8176aa2386e0d12023bf69ab28bd87f))
21
+ * **close:** require head-anchored required-check evidence before the checks-failed fail-fast (refs [#4695](https://github.com/dsj1984/mandrel/issues/4695)) ([#4703](https://github.com/dsj1984/mandrel/issues/4703)) ([6b0b4db](https://github.com/dsj1984/mandrel/commit/6b0b4db021eb11ef46aa65fe6ed906f567ffef05))
22
+ * **evidence:** extend gateName enum to the real close-gate set (refs [#4697](https://github.com/dsj1984/mandrel/issues/4697)) ([#4701](https://github.com/dsj1984/mandrel/issues/4701)) ([49be21b](https://github.com/dsj1984/mandrel/commit/49be21bc9556de5db60871e5140e674e02bb9667))
23
+ * **observability:** isolate test-suite emissions from real telemetry streams and guard CI against fixture pollution ([#4696](https://github.com/dsj1984/mandrel/issues/4696)) ([#4704](https://github.com/dsj1984/mandrel/issues/4704)) ([992a5a2](https://github.com/dsj1984/mandrel/commit/992a5a2fa31091e9ae021c50c5ae8a7ec323ccd7))
24
+ * **observability:** make the test-temp hygiene guard airtight (refs [#4711](https://github.com/dsj1984/mandrel/issues/4711)) ([#4716](https://github.com/dsj1984/mandrel/issues/4716)) ([7520cfb](https://github.com/dsj1984/mandrel/commit/7520cfb66fae3aa5c6802af109becbc2b2795b56))
25
+ * **recover:** stability re-probe before trusting a mid-flight strand verdict ([#4719](https://github.com/dsj1984/mandrel/issues/4719)) ([a2edc9f](https://github.com/dsj1984/mandrel/commit/a2edc9f4465ca130b025deaaf12fb87174b242a4))
26
+
27
+
28
+ ### Changed
29
+
30
+ * **metrics:** single ledger-append implementation and clean close boundary (refs [#4712](https://github.com/dsj1984/mandrel/issues/4712)) ([#4715](https://github.com/dsj1984/mandrel/issues/4715)) ([a9b019b](https://github.com/dsj1984/mandrel/commit/a9b019b6c68e3874950728578e535e4614751553))
31
+
5
32
  ## [2.9.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.8.0...mandrel-v2.9.0) (2026-07-21)
6
33
 
7
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mandrel",
3
- "version": "2.9.0",
3
+ "version": "2.10.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/",