mandrel 2.9.0 → 2.11.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 (75) 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 +39 -25
  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 +36 -9
  13. package/.agents/schemas/validation-evidence.schema.json +3 -1
  14. package/.agents/scripts/acceptance-eval.js +10 -6
  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/baselines/env-overrides.js +33 -0
  22. package/.agents/scripts/lib/baselines/git-base.js +0 -0
  23. package/.agents/scripts/lib/baselines/preview-gates.js +5 -0
  24. package/.agents/scripts/lib/config/gates/maintainability.schema.js +10 -1
  25. package/.agents/scripts/lib/config/quality.js +13 -0
  26. package/.agents/scripts/lib/config/temp-paths.js +121 -1
  27. package/.agents/scripts/lib/config-settings-schema-delivery.js +30 -0
  28. package/.agents/scripts/lib/config-settings-schema.js +12 -16
  29. package/.agents/scripts/lib/observability/metrics-ledger.js +217 -0
  30. package/.agents/scripts/lib/observability/runtime-friction.js +7 -0
  31. package/.agents/scripts/lib/orchestration/ceremony-routing.js +45 -0
  32. package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +97 -4
  33. package/.agents/scripts/lib/orchestration/check-baselines/phases/parse-args.js +7 -0
  34. package/.agents/scripts/lib/orchestration/complexity-gate.js +533 -93
  35. package/.agents/scripts/lib/orchestration/deliver-recover.js +137 -10
  36. package/.agents/scripts/lib/orchestration/merge-block-class.js +36 -15
  37. package/.agents/scripts/lib/orchestration/merge-poll.js +213 -0
  38. package/.agents/scripts/lib/orchestration/plan-context.js +119 -3
  39. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +182 -9
  40. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +29 -2
  41. package/.agents/scripts/lib/orchestration/plan-metrics.js +31 -82
  42. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +153 -2
  43. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +221 -14
  44. package/.agents/scripts/lib/orchestration/resolve-stories.js +12 -1
  45. package/.agents/scripts/lib/orchestration/review-depth.js +9 -4
  46. package/.agents/scripts/lib/orchestration/review-providers/native.js +34 -16
  47. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +8 -3
  48. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +230 -79
  49. package/.agents/scripts/lib/orchestration/spec-budget.js +78 -0
  50. package/.agents/scripts/lib/orchestration/story-body-gate.js +72 -0
  51. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +89 -1
  52. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +73 -0
  53. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +6 -0
  54. package/.agents/scripts/lib/orchestration/ticket-validator.js +18 -62
  55. package/.agents/scripts/lib/templates/decomposer-prompts.js +13 -6
  56. package/.agents/scripts/lib/test-env.js +65 -0
  57. package/.agents/scripts/plan-context.js +84 -9
  58. package/.agents/scripts/plan-critics.js +115 -3
  59. package/.agents/scripts/plan-persist.js +11 -1
  60. package/.agents/scripts/plan-run-epilogue.js +1 -1
  61. package/.agents/scripts/resolve-stories.js +2 -0
  62. package/.agents/scripts/single-story-confirm-merge.js +65 -5
  63. package/.agents/scripts/stories-wave-tick.js +1 -1
  64. package/.agents/workflows/deliver.md +88 -230
  65. package/.agents/workflows/helpers/acceptance-self-eval.md +16 -5
  66. package/.agents/workflows/helpers/deliver-reference.md +171 -0
  67. package/.agents/workflows/helpers/deliver-story-reference.md +223 -0
  68. package/.agents/workflows/helpers/deliver-story.md +115 -432
  69. package/.agents/workflows/helpers/plan-reference.md +246 -0
  70. package/.agents/workflows/plan.md +108 -304
  71. package/docs/CHANGELOG.md +46 -0
  72. package/lib/cli/registry.js +31 -14
  73. package/lib/migrations/index.js +2 -0
  74. package/lib/migrations/steps/2.11.0-retire-max-seed-words.js +92 -0
  75. package/package.json +1 -1
@@ -0,0 +1,246 @@
1
+ # /plan — on-demand reference appendix
2
+
3
+ > **Applies when:** you are executing [`/plan`](../plan.md) and hit one of the
4
+ > situations below — shape-derived complexity routing, `--tickets` supersede
5
+ > authoring, critic dispatch detail, a failed persist, or source-id
6
+ > resolution. The spine stays resident; this file is read on demand.
7
+
8
+ ## Shape-derived complexity routing (`complexitySignals`)
9
+
10
+ Complexity routes on the **objective shape of the authored work**, never on
11
+ seed word count (Story #4722 — a detailed prompt can describe trivial work, a
12
+ terse one complex work; `maxSeedWords` is removed). The pipeline stages the
13
+ decision:
14
+
15
+ - **Signals, not routing.** The envelope's `complexitySignals` field is
16
+ advisory only (`routingAuthority: false`): enumerated-artifact count (with
17
+ the configured `maxArtifacts` threshold beside it as one input),
18
+ `planning.riskHeuristics` phrases present in the seed, the repo state of
19
+ predicted paths (existing paths predict refactors; missing predict
20
+ creates), and the `audit-rules.json` sensitive-path classes the predicted
21
+ footprint intersects.
22
+ - **You author the verdict.** Judge the signals: a genuinely trivial scope
23
+ (small additive footprint, no risk hits, no sensitive class) earns a `lite`
24
+ claim via `plan-persist.js --route-downgrade-reason "<why>"`. The reason is
25
+ recorded on every created Story's `story-plan-state` checkpoint, making the
26
+ judgment auditable; without a recorded reason the conservative default
27
+ (`full`) stands.
28
+ - **Persist backstops the claim deterministically.** After authoring, the
29
+ work has measurable shape, so persist validates the `lite` claim against
30
+ each Story's own shape — `changes[]` count, acceptance-criteria count,
31
+ creates-vs-refactors mix, glob-free footprint, and sensitive-path classes,
32
+ against the framework `STORY_SHAPE_CEILINGS` — and **fails closed to
33
+ `full`** when any Story exceeds them (the refusal is ledgered on the
34
+ checkpoint too). The lite route is **not** licence to drop a
35
+ non-negotiable — every decision's `preserves` field enumerates what still
36
+ holds: the Story ticket, the PR-to-`main` landing, every repo quality gate,
37
+ and the security baseline. Those gates run in `single-story-close.js`
38
+ regardless of route.
39
+
40
+ **The label is a hint; deliver re-derives (Story #4722).** Persist labels a
41
+ lite cohort's Stories with **`route::lite`** as a *human-visible hint only* —
42
+ `/deliver` computes the route from each fetched Story body via the same shape
43
+ function at dispatch, so neither a lost label nor an unread marker can
44
+ misroute delivery: a lite-shaped Story executes **inline** (no story-worker
45
+ or acceptance-critic sub-agent boots) even with the label absent, and a
46
+ sensitive-footprint Story routes `full` and keeps its fresh critic even with
47
+ the label present. The `route::*` axis stays runtime-derived: hand-authored
48
+ `route::*` entries in `labels[]` are dropped by persist.
49
+
50
+ The knobs (`planning.complexityGate.{enabled, maxArtifacts}`) are documented
51
+ in [`.agents/docs/configuration.md`](../../docs/configuration.md) under
52
+ `### planning`; the defaults live on `DEFAULT_COMPLEXITY_GATE` and the shape
53
+ ceilings on `STORY_SHAPE_CEILINGS` in
54
+ [`lib/orchestration/complexity-gate.js`](../../scripts/lib/orchestration/complexity-gate.js).
55
+
56
+ ## Correct-by-construction authoring template (Story #4723)
57
+
58
+ `plan-context.js --out` writes `stories.template.json` as a
59
+ **correct-by-construction** skeleton, built from the same repo snapshot the
60
+ `complexitySignals` probed:
61
+
62
+ - **`verify[]` placeholders already end with a valid `(tier)` tag.** Keep
63
+ every filled entry's trailing tag one of `(unit)` / `(contract)` /
64
+ `(e2e)` / `(validate)` (or use the `manual:<reason>` escape) — a tierless
65
+ entry is exactly the mechanical persist round-trip the template exists to
66
+ prevent.
67
+ - **`changes[]` arrive pre-resolved to creates-vs-refactors.** Every path
68
+ the seed predicted is probed against the repo: an existing path is
69
+ emitted with `assumption: "refactors-existing"`, a missing one with
70
+ `assumption: "creates"`. Trust the pre-resolved assumption — verify
71
+ against the repo before overriding one (authoring `creates` for a file
72
+ that exists at base is a validator rejection). The persist gates stay
73
+ authoritative: they probe the base branch ref, not the working tree.
74
+ - **Keep `## Spec` near contract-level prose.** Persist emits an
75
+ **advisory** warning past ~250 words (`SPEC_SOFT_WORD_BUDGET`) — it never
76
+ fails the persist, but it is the nudge toward the #4707 contract-level
77
+ Spec (interfaces, invariants, load-bearing constraints; no per-file
78
+ behavior narration). The hard fail-closed ceiling (~1500 tokens,
79
+ `spec-spill.js`) is unchanged.
80
+
81
+ A faithfully-filled skeleton — placeholders replaced, pre-resolved entries
82
+ kept, tags valid — passes the persist ticket validators with no
83
+ round-trip.
84
+
85
+ ## Tickets mode — authoring `supersedes[]`
86
+
87
+ In `--tickets` mode each Story carries a top-level `supersedes` array claiming
88
+ the source issues it replaces. It is bookkeeping, not part of the Story body,
89
+ so it is never serialized into the markdown:
90
+
91
+ ```jsonc
92
+ {
93
+ "slug": "close-superseded",
94
+ "supersedes": [
95
+ 4525,
96
+ { "id": 4529, "note": "The filed `--changed-only` fix is provably inert; the correction is recorded here." }
97
+ ]
98
+ }
99
+ ```
100
+
101
+ Entries are bare issue numbers, or `{ id, note }` when the plan has
102
+ something to say about *that* source issue — a correction to its analysis,
103
+ or why it was folded in with others. The optional `note` is rendered into
104
+ that issue's supersede comment, so planning that materially corrects a
105
+ source issue records the correction on the ticket rather than emitting
106
+ template-only prose.
107
+
108
+ ### Supersede-map partition
109
+
110
+ `plan-persist` refuses a partial supersede map **before** it creates any
111
+ Story (mirroring `assertAcceptancePartition`): every id passed to
112
+ `--tickets` must be claimed by **exactly one** Story, and no Story may
113
+ claim an id that was not a source ticket. With N>1 the mapping is not
114
+ total by default — an authored map is the only thing that can say
115
+ `#4525-#4528 → #4530` while `#4529 → #4531`, which a blanket "superseded by
116
+ this plan-run" reference could not.
117
+
118
+ ## Critic dispatch detail
119
+
120
+ The **pre-mortem** critic fires on any of three deterministic triggers: the
121
+ draft ticket count reaching half the reviewability budget, a
122
+ `planning.riskHeuristics` phrase matching the plan text, or the
123
+ **external-dependency** probe (Story #4700) finding an out-of-repo marker — a
124
+ scoped package the plan names that no repo manifest declares, a cross-repo
125
+ `github.com/<owner>/<repo>` reference, or an endpoint named as a service
126
+ prerequisite. That third trigger is what gives the default N=1 plan a cheap
127
+ viability check, since the size trigger is unreachable at one ticket and this
128
+ repo's resolved `riskHeuristics` is empty. The probe is conservative — explicit
129
+ markers only, so a plan naming no such artifact dispatches exactly as before.
130
+
131
+ ```jsonc
132
+ {
133
+ "consolidation": { "critic": "consolidation", "dispatch": false, "reasons": ["…"] },
134
+ "premortem": { "critic": "pre-mortem", "dispatch": true, "reasons": ["…"] },
135
+ "textHygiene": { "critic": "text-hygiene", "findings": [] }
136
+ }
137
+ ```
138
+
139
+ The verdict's third entry, `textHygiene`, is advisory-only (Story #4599): it
140
+ carries deterministic body lints (`dangling-citation` / `open-question` /
141
+ `slicing-mass`) with no dispatch semantics — it spawns nothing and never
142
+ gates the run. Fold `textHygiene.findings[]` into the re-author round the
143
+ same way critic findings fold in: fix each named defect in `stories.json`
144
+ (anchor or inline the citation, resolve the question into a declarative
145
+ assumption, thin the Slicing checkpoint) and re-run the critic step. Empty
146
+ `findings` add nothing to the round.
147
+
148
+ **Dispatch shape.** When `delivery.routing.roleScopedAgents` is enabled (the
149
+ **default**), dispatch each firing critic with `subagent_type: plan-critic` —
150
+ it boots on the role-scoped [`plan-critic`](../../agents/plan-critic.md)
151
+ context (its own system prompt, no `CLAUDE.md` @-closure) that carries the
152
+ maker-blind invariant, the `consolidation` and `pre-mortem` charters, and the
153
+ output shape standalone. When the kill-switch is off
154
+ (`roleScopedAgents: false`) or the host cannot spawn at this depth, fall back
155
+ to a generic sub-agent and hand it the same charter (the `consolidation` /
156
+ `pre-mortem` definitions in [`plan-critic.md`](../../agents/plan-critic.md)).
157
+ Either way the critic is **maker-blind**: hand it the draft artifacts
158
+ (`stories.json`, and `techspec.md` when present) — never the authoring
159
+ transcript or the reasons the planner believed its own draft is sound. A
160
+ critic that reads the maker's case grades the case, not the draft.
161
+
162
+ ## Ready means fully persisted
163
+
164
+ `agent::ready` is the **terminal** step, not part of the creating POST
165
+ (Story #4541). The order is: create unlabelled → upsert `story-plan-state` on
166
+ every Story → upsert `plan-summary` on the primary → flip every Story to
167
+ `agent::ready`.
168
+
169
+ This is what lets `/deliver` trust the label: a Story carrying
170
+ `agent::ready` always has its persist receipt on the ticket, so nothing can
171
+ pick it up mid-write and read a half-persisted plan.
172
+
173
+ ## Resuming a failed persist
174
+
175
+ Persist is **idempotent over the same authored artifacts**. Each created body
176
+ carries an invisible plan fingerprint (derived from the Story's slug +
177
+ title), and persist indexes the open `type::story` backlog by it before
178
+ creating anything.
179
+
180
+ So if a transient GitHub failure strands the run at Story `k` of `N`:
181
+
182
+ | | Behaviour |
183
+ | --- | --- |
184
+ | The `1..k-1` Stories | Live, but **not** `agent::ready` — invisible to `/deliver`, not half-delivered. |
185
+ | Re-running persist | Adopts them by fingerprint, creates only the missing ones, then flips the whole cohort ready. |
186
+ | Editing `stories.json` first | Changing a slug or title changes the fingerprint — the old issue is orphaned rather than adopted. Close it by hand. |
187
+
188
+ Just re-run the same command. Do not hand-delete the stranded issues first.
189
+
190
+ ## Temp hygiene
191
+
192
+ A terminal-success run deletes its own `--plan-dir`. Every persist also reaps
193
+ abandoned `temp/plan-*` directories older than 7 days, so dry-runs, failed
194
+ gates, and abandoned authoring sessions do not accumulate under `temp/`.
195
+
196
+ ## How the source ids reach persist
197
+
198
+ In `--tickets` mode persist needs to know which ids were fetched. It resolves
199
+ them **envelope-first** (Story #4554):
200
+
201
+ | Channel | When it wins |
202
+ | --- | --- |
203
+ | 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. |
204
+ | `--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. |
205
+
206
+ The result envelope's `supersede.sourceTicketOrigin` reports which channel was
207
+ used (`envelope` \| `flag` \| `none`).
208
+
209
+ Every path with no envelope is **audible** — persist cannot tell a legitimate
210
+ `--seed` run from a `--tickets` run whose envelope was never captured, so it
211
+ says so rather than deciding silently:
212
+
213
+ | Situation | Behaviour |
214
+ | --- | --- |
215
+ | Neither `--plan-dir` nor `--plan-context` | **Warn** — nothing was read; only `--source-tickets` can supply ids. |
216
+ | Auto-discovered `<plan-dir>/plan-context.json` absent | **Warn** — degrade to `--source-tickets`; a `--seed` run legitimately has none. |
217
+ | Explicit `--plan-context` missing | **Fatal** — the operator named a file and meant it. |
218
+ | 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. |
219
+
220
+ Whichever channel supplies them, the supersede-map partition above still
221
+ fail-closes: a `--tickets` run whose Stories forgot `supersedes[]` is now
222
+ **caught** (`source ticket #N is not claimed by any Story`) instead of
223
+ partitioning an empty set and passing vacuously.
224
+
225
+ ## Closing superseded source tickets
226
+
227
+ **Default on.** After the Stories exist, persist comments on each source
228
+ issue naming the specific Story that claims it — plus that Story's optional
229
+ per-supersede `note` — and closes it with reason **`not_planned`**
230
+ (`state_reason`). Nothing has shipped at persist time and the issue will not
231
+ be actioned in its own right, so `not_planned` is the honest reason;
232
+ `completed` would be a lie. This is what keeps the tracker from asserting
233
+ that already-planned work is still unowned, and it writes down the supersede
234
+ link that makes the history readable.
235
+
236
+ | Behaviour | Contract |
237
+ | --- | --- |
238
+ | Default | Comment + close every source ticket as `not_planned`. |
239
+ | `--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. |
240
+ | `--dry-run` | Posts no comment and closes nothing; reports what it would have done. |
241
+ | Re-run | Idempotent — the comment is keyed off a `superseded-by` structured-comment marker, and an already-closed source is skipped. |
242
+ | Already closed / deleted / inaccessible | Skipped and reported. Never throws. |
243
+ | 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. |
244
+
245
+ `--seed` / `--seed-file` modes have no source tickets, so no close phase
246
+ runs at all.