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
@@ -0,0 +1,211 @@
1
+ # /plan — on-demand reference appendix
2
+
3
+ > **Applies when:** you are executing [`/plan`](../plan.md) and hit one of the
4
+ > situations below — the ceremony-lite route, `--tickets` supersede authoring,
5
+ > critic dispatch detail, a failed persist, or source-id resolution. The spine
6
+ > stays resident; this file is read on demand.
7
+
8
+ ## Ceremony-lite complexity gate (`complexityRoute`)
9
+
10
+ The envelope's `complexityRoute` field is a **deterministic, conservative**
11
+ plan-time gate (Story #4683) that routes a genuinely trivial single-artifact
12
+ seed onto a collapsed path so it stops paying the full two-session
13
+ plan/deliver ceremony that measurably buys no quality at that size:
14
+
15
+ - **`route: "lite"`** — a trivial scope (seed ≤ `maxSeedWords` words **and** ≤
16
+ `maxArtifacts` enumerated items). Collapse the ceremony: author **one minimal
17
+ Story** and skip the fresh-critic / Tech-Spec ceremony a one-artifact scope
18
+ does not earn. The lite route is **not** licence to drop a non-negotiable —
19
+ its `preserves` field enumerates exactly what still holds: the Story ticket,
20
+ the PR-to-`main` landing, every repo quality gate, and the security baseline.
21
+ Those gates still run in `single-story-close.js` regardless of route.
22
+ - **`route: "full"`** — everything else. The gate fails toward `full` on any
23
+ doubt (empty seed, over the word ceiling, a multi-capability enumeration, or
24
+ the gate disabled via `planning.complexityGate.enabled=false`), so a real
25
+ capability slice never loses ceremony. Author normally under the split policy.
26
+
27
+ **Planner downgrade (audited, Story #4707).** Seed word count is a poor
28
+ complexity proxy, so a `full` verdict you judge genuinely trivial (one
29
+ artifact, one obvious change) may be downgraded to `lite` — but **only** by
30
+ passing `--route-downgrade-reason "<why>"` to persist. The reason is recorded
31
+ on every created Story's `story-plan-state` checkpoint, making the judgment
32
+ auditable; without a recorded reason the deterministic verdict stands, and the
33
+ gate itself is unchanged (it still fails toward `full`).
34
+
35
+ **The route persists with the Story (Story #4707).** Persist labels every
36
+ Story of a lite-routed plan with the **`route::lite`** marker and ledgers the
37
+ route (including any downgrade reason) on its `story-plan-state` checkpoint;
38
+ a full-routed Story carries no marker. `/deliver` reads the marker to execute
39
+ a lite Story **inline** — no story-worker or acceptance-critic sub-agent
40
+ boots — while every `single-story-close.js` gate runs unchanged. The
41
+ `route::*` axis is runtime-derived: hand-authored `route::*` entries in
42
+ `labels[]` are dropped by persist.
43
+
44
+ The threshold and its override knob (`planning.complexityGate.{enabled,
45
+ maxSeedWords, maxArtifacts}`) are documented in
46
+ [`.agents/docs/configuration.md`](../../docs/configuration.md) under
47
+ `### planning`; the defaults live on `DEFAULT_COMPLEXITY_GATE` in
48
+ [`lib/orchestration/complexity-gate.js`](../../scripts/lib/orchestration/complexity-gate.js).
49
+
50
+ ## Tickets mode — authoring `supersedes[]`
51
+
52
+ In `--tickets` mode each Story carries a top-level `supersedes` array claiming
53
+ the source issues it replaces. It is bookkeeping, not part of the Story body,
54
+ so it is never serialized into the markdown:
55
+
56
+ ```jsonc
57
+ {
58
+ "slug": "close-superseded",
59
+ "supersedes": [
60
+ 4525,
61
+ { "id": 4529, "note": "The filed `--changed-only` fix is provably inert; the correction is recorded here." }
62
+ ]
63
+ }
64
+ ```
65
+
66
+ Entries are bare issue numbers, or `{ id, note }` when the plan has
67
+ something to say about *that* source issue — a correction to its analysis,
68
+ or why it was folded in with others. The optional `note` is rendered into
69
+ that issue's supersede comment, so planning that materially corrects a
70
+ source issue records the correction on the ticket rather than emitting
71
+ template-only prose.
72
+
73
+ ### Supersede-map partition
74
+
75
+ `plan-persist` refuses a partial supersede map **before** it creates any
76
+ Story (mirroring `assertAcceptancePartition`): every id passed to
77
+ `--tickets` must be claimed by **exactly one** Story, and no Story may
78
+ claim an id that was not a source ticket. With N>1 the mapping is not
79
+ total by default — an authored map is the only thing that can say
80
+ `#4525-#4528 → #4530` while `#4529 → #4531`, which a blanket "superseded by
81
+ this plan-run" reference could not.
82
+
83
+ ## Critic dispatch detail
84
+
85
+ The **pre-mortem** critic fires on any of three deterministic triggers: the
86
+ draft ticket count reaching half the reviewability budget, a
87
+ `planning.riskHeuristics` phrase matching the plan text, or the
88
+ **external-dependency** probe (Story #4700) finding an out-of-repo marker — a
89
+ scoped package the plan names that no repo manifest declares, a cross-repo
90
+ `github.com/<owner>/<repo>` reference, or an endpoint named as a service
91
+ prerequisite. That third trigger is what gives the default N=1 plan a cheap
92
+ viability check, since the size trigger is unreachable at one ticket and this
93
+ repo's resolved `riskHeuristics` is empty. The probe is conservative — explicit
94
+ markers only, so a plan naming no such artifact dispatches exactly as before.
95
+
96
+ ```jsonc
97
+ {
98
+ "consolidation": { "critic": "consolidation", "dispatch": false, "reasons": ["…"] },
99
+ "premortem": { "critic": "pre-mortem", "dispatch": true, "reasons": ["…"] },
100
+ "textHygiene": { "critic": "text-hygiene", "findings": [] }
101
+ }
102
+ ```
103
+
104
+ The verdict's third entry, `textHygiene`, is advisory-only (Story #4599): it
105
+ carries deterministic body lints (`dangling-citation` / `open-question` /
106
+ `slicing-mass`) with no dispatch semantics — it spawns nothing and never
107
+ gates the run. Fold `textHygiene.findings[]` into the re-author round the
108
+ same way critic findings fold in: fix each named defect in `stories.json`
109
+ (anchor or inline the citation, resolve the question into a declarative
110
+ assumption, thin the Slicing checkpoint) and re-run the critic step. Empty
111
+ `findings` add nothing to the round.
112
+
113
+ **Dispatch shape.** When `delivery.routing.roleScopedAgents` is enabled (the
114
+ **default**), dispatch each firing critic with `subagent_type: plan-critic` —
115
+ it boots on the role-scoped [`plan-critic`](../../agents/plan-critic.md)
116
+ context (its own system prompt, no `CLAUDE.md` @-closure) that carries the
117
+ maker-blind invariant, the `consolidation` and `pre-mortem` charters, and the
118
+ output shape standalone. When the kill-switch is off
119
+ (`roleScopedAgents: false`) or the host cannot spawn at this depth, fall back
120
+ to a generic sub-agent and hand it the same charter (the `consolidation` /
121
+ `pre-mortem` definitions in [`plan-critic.md`](../../agents/plan-critic.md)).
122
+ Either way the critic is **maker-blind**: hand it the draft artifacts
123
+ (`stories.json`, and `techspec.md` when present) — never the authoring
124
+ transcript or the reasons the planner believed its own draft is sound. A
125
+ critic that reads the maker's case grades the case, not the draft.
126
+
127
+ ## Ready means fully persisted
128
+
129
+ `agent::ready` is the **terminal** step, not part of the creating POST
130
+ (Story #4541). The order is: create unlabelled → upsert `story-plan-state` on
131
+ every Story → upsert `plan-summary` on the primary → flip every Story to
132
+ `agent::ready`.
133
+
134
+ This is what lets `/deliver` trust the label: a Story carrying
135
+ `agent::ready` always has its persist receipt on the ticket, so nothing can
136
+ pick it up mid-write and read a half-persisted plan.
137
+
138
+ ## Resuming a failed persist
139
+
140
+ Persist is **idempotent over the same authored artifacts**. Each created body
141
+ carries an invisible plan fingerprint (derived from the Story's slug +
142
+ title), and persist indexes the open `type::story` backlog by it before
143
+ creating anything.
144
+
145
+ So if a transient GitHub failure strands the run at Story `k` of `N`:
146
+
147
+ | | Behaviour |
148
+ | --- | --- |
149
+ | The `1..k-1` Stories | Live, but **not** `agent::ready` — invisible to `/deliver`, not half-delivered. |
150
+ | Re-running persist | Adopts them by fingerprint, creates only the missing ones, then flips the whole cohort ready. |
151
+ | Editing `stories.json` first | Changing a slug or title changes the fingerprint — the old issue is orphaned rather than adopted. Close it by hand. |
152
+
153
+ Just re-run the same command. Do not hand-delete the stranded issues first.
154
+
155
+ ## Temp hygiene
156
+
157
+ A terminal-success run deletes its own `--plan-dir`. Every persist also reaps
158
+ abandoned `temp/plan-*` directories older than 7 days, so dry-runs, failed
159
+ gates, and abandoned authoring sessions do not accumulate under `temp/`.
160
+
161
+ ## How the source ids reach persist
162
+
163
+ In `--tickets` mode persist needs to know which ids were fetched. It resolves
164
+ them **envelope-first** (Story #4554):
165
+
166
+ | Channel | When it wins |
167
+ | --- | --- |
168
+ | 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. |
169
+ | `--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. |
170
+
171
+ The result envelope's `supersede.sourceTicketOrigin` reports which channel was
172
+ used (`envelope` \| `flag` \| `none`).
173
+
174
+ Every path with no envelope is **audible** — persist cannot tell a legitimate
175
+ `--seed` run from a `--tickets` run whose envelope was never captured, so it
176
+ says so rather than deciding silently:
177
+
178
+ | Situation | Behaviour |
179
+ | --- | --- |
180
+ | Neither `--plan-dir` nor `--plan-context` | **Warn** — nothing was read; only `--source-tickets` can supply ids. |
181
+ | Auto-discovered `<plan-dir>/plan-context.json` absent | **Warn** — degrade to `--source-tickets`; a `--seed` run legitimately has none. |
182
+ | Explicit `--plan-context` missing | **Fatal** — the operator named a file and meant it. |
183
+ | 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. |
184
+
185
+ Whichever channel supplies them, the supersede-map partition above still
186
+ fail-closes: a `--tickets` run whose Stories forgot `supersedes[]` is now
187
+ **caught** (`source ticket #N is not claimed by any Story`) instead of
188
+ partitioning an empty set and passing vacuously.
189
+
190
+ ## Closing superseded source tickets
191
+
192
+ **Default on.** After the Stories exist, persist comments on each source
193
+ issue naming the specific Story that claims it — plus that Story's optional
194
+ per-supersede `note` — and closes it with reason **`not_planned`**
195
+ (`state_reason`). Nothing has shipped at persist time and the issue will not
196
+ be actioned in its own right, so `not_planned` is the honest reason;
197
+ `completed` would be a lie. This is what keeps the tracker from asserting
198
+ that already-planned work is still unowned, and it writes down the supersede
199
+ link that makes the history readable.
200
+
201
+ | Behaviour | Contract |
202
+ | --- | --- |
203
+ | Default | Comment + close every source ticket as `not_planned`. |
204
+ | `--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. |
205
+ | `--dry-run` | Posts no comment and closes nothing; reports what it would have done. |
206
+ | Re-run | Idempotent — the comment is keyed off a `superseded-by` structured-comment marker, and an already-closed source is skipped. |
207
+ | Already closed / deleted / inaccessible | Skipped and reported. Never throws. |
208
+ | 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. |
209
+
210
+ `--seed` / `--seed-file` modes have no source tickets, so no close phase
211
+ runs at all.