mandrel 2.23.0 → 2.25.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 (101) hide show
  1. package/.agents/agents/acceptance-critic.md +13 -19
  2. package/.agents/agents/auditor.md +16 -19
  3. package/.agents/agents/story-worker.md +41 -54
  4. package/.agents/audit-checklists/architecture.md +5 -1
  5. package/.agents/audit-checklists/quality.md +3 -0
  6. package/.agents/docs/agentrc-reference.json +2 -1
  7. package/.agents/docs/configuration.md +2 -1
  8. package/.agents/docs/workflows.md +1 -1
  9. package/.agents/rules/ci-remediation.md +68 -3
  10. package/.agents/schemas/agentrc.schema.json +6 -1
  11. package/.agents/schemas/baselines/baseline-envelope.schema.json +4 -0
  12. package/.agents/schemas/baselines/crap.schema.json +5 -1
  13. package/.agents/schemas/crap-report.schema.json +37 -0
  14. package/.agents/schemas/story-deliver-terminal.schema.json +6 -1
  15. package/.agents/scripts/acceptance-eval.js +35 -9
  16. package/.agents/scripts/audit-labels-bootstrap.js +16 -95
  17. package/.agents/scripts/audit-to-stories.js +33 -3
  18. package/.agents/scripts/check-context-budget.js +95 -14
  19. package/.agents/scripts/check-test-temp-hygiene.js +83 -0
  20. package/.agents/scripts/cleanup-repo-test-temp.js +7 -5
  21. package/.agents/scripts/deliver-light.js +22 -45
  22. package/.agents/scripts/deliver-recover.js +13 -0
  23. package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +10 -25
  24. package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +177 -0
  25. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +35 -3
  26. package/.agents/scripts/lib/audit-to-stories/group-findings.js +20 -11
  27. package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +23 -9
  28. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +9 -2
  29. package/.agents/scripts/lib/baseline-loader.js +0 -0
  30. package/.agents/scripts/lib/baselines/git-base.js +0 -0
  31. package/.agents/scripts/lib/baselines/kinds/crap.js +396 -51
  32. package/.agents/scripts/lib/baselines/kinds/maintainability.js +20 -32
  33. package/.agents/scripts/lib/baselines/preview-gates.js +58 -15
  34. package/.agents/scripts/lib/baselines/reader.js +10 -0
  35. package/.agents/scripts/lib/baselines/writer.js +7 -3
  36. package/.agents/scripts/lib/config-settings-schema-delivery.js +5 -0
  37. package/.agents/scripts/lib/crap-engine.js +97 -19
  38. package/.agents/scripts/lib/crap-utils.js +73 -13
  39. package/.agents/scripts/lib/env-loader.js +46 -16
  40. package/.agents/scripts/lib/escomplex-ast-compat.js +360 -0
  41. package/.agents/scripts/lib/findings/route-finding.js +123 -17
  42. package/.agents/scripts/lib/findings/severity.js +80 -2
  43. package/.agents/scripts/lib/git/cached-fetch.js +0 -0
  44. package/.agents/scripts/lib/label-constants.js +17 -0
  45. package/.agents/scripts/lib/maintainability-engine.js +83 -11
  46. package/.agents/scripts/lib/maintainability-unscorable.js +60 -0
  47. package/.agents/scripts/lib/maintainability-utils.js +14 -5
  48. package/.agents/scripts/lib/observability/runtime-friction.js +8 -0
  49. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +110 -17
  50. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +548 -0
  51. package/.agents/scripts/lib/orchestration/deliver-recover.js +2 -2
  52. package/.agents/scripts/lib/orchestration/diff-magnitude.js +283 -0
  53. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +71 -20
  54. package/.agents/scripts/lib/orchestration/light-backstop.js +124 -0
  55. package/.agents/scripts/lib/orchestration/light-escalation.js +248 -0
  56. package/.agents/scripts/lib/orchestration/light-suitability.js +254 -51
  57. package/.agents/scripts/lib/orchestration/plan-context.js +12 -13
  58. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +7 -0
  59. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +13 -1
  60. package/.agents/scripts/lib/orchestration/retro-proposals.js +0 -0
  61. package/.agents/scripts/lib/orchestration/run-scoped-config.js +276 -0
  62. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +41 -10
  63. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +96 -34
  64. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +70 -2
  65. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +129 -21
  66. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +73 -18
  67. package/.agents/scripts/lib/reserved-test-ids.js +77 -0
  68. package/.agents/scripts/lib/story-plan.js +137 -42
  69. package/.agents/scripts/lib/templates/decomposer-prompts.js +4 -4
  70. package/.agents/scripts/lib/ticket-body-sections.js +1 -1
  71. package/.agents/scripts/lib/util/poll-loop.js +18 -2
  72. package/.agents/scripts/lib/wave-runner/live-probe.js +7 -1
  73. package/.agents/scripts/lib/wave-runner/ready-set.js +73 -9
  74. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +6 -3
  75. package/.agents/scripts/lib/workers/crap-worker.js +6 -3
  76. package/.agents/scripts/lib/workers/maintainability-worker.js +14 -9
  77. package/.agents/scripts/pr-watch-with-update.js +568 -213
  78. package/.agents/scripts/quality-preview.js +25 -0
  79. package/.agents/scripts/run-tests.js +10 -0
  80. package/.agents/scripts/single-story-close.js +6 -0
  81. package/.agents/scripts/single-story-init.js +7 -0
  82. package/.agents/scripts/stories-wave-tick.js +95 -10
  83. package/.agents/scripts/story-plan.js +65 -9
  84. package/.agents/scripts/update-crap-baseline.js +42 -4
  85. package/.agents/scripts/update-maintainability-baseline.js +31 -4
  86. package/.agents/templates/single-story-body.md +16 -8
  87. package/.agents/workflows/audit-architecture.md +69 -13
  88. package/.agents/workflows/audit-quality.md +26 -0
  89. package/.agents/workflows/audit-to-stories.md +30 -4
  90. package/.agents/workflows/deliver.md +81 -95
  91. package/.agents/workflows/helpers/audit-lens-core.md +25 -4
  92. package/.agents/workflows/helpers/deliver-light.md +21 -4
  93. package/.agents/workflows/helpers/deliver-reference.md +18 -3
  94. package/.agents/workflows/helpers/deliver-story-reference.md +127 -46
  95. package/.agents/workflows/helpers/deliver-story.md +104 -119
  96. package/.agents/workflows/helpers/plan-reference.md +34 -0
  97. package/.agents/workflows/plan.md +60 -76
  98. package/docs/CHANGELOG.md +38 -0
  99. package/lib/cli/update.js +14 -11
  100. package/lib/cli/version-check.js +9 -1
  101. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Audit architectural boundaries, module coupling, and layering violations; emit a structured findings report keyed to High/Medium/Low severity.
2
+ description: Audit architectural boundaries, module coupling, layering violations, and shipped-but-uncalled seams; emit a structured findings report keyed to the canonical severity scale.
3
3
  ---
4
4
 
5
5
  # Architecture & Clean Code Audit
@@ -57,9 +57,14 @@ the failure mode this lens exists to prevent.
57
57
  node .agents/scripts/check-dead-exports.js
58
58
  ```
59
59
 
60
- Each unreferenced export is a grounded candidate. **Cede it** to
61
- audit-clean-code's Dead Code dimension rather than re-deriving it here (see
62
- the deferral in Step 2). When the shipped checker is unavailable, fall back
60
+ Each unreferenced export is a grounded candidate. **Cede the
61
+ merely-unreferenced ones** to audit-clean-code's Dead Code dimension rather
62
+ than re-deriving them here (see the deferral in Step 2); keep the ones that
63
+ are dead *wiring* — a seam a delivery shipped that no live production path
64
+ reaches — for this lens's Shipped-But-Never-Wired dimension. Note that this
65
+ checker only sees unreferenced symbols, so it cannot surface the worst shape
66
+ (a writer with no reader, both fully referenced); that one you must trace by
67
+ hand. When the shipped checker is unavailable, fall back
63
68
  to `npx knip --production` — and heed the `!`-suffix entry-pattern caveat
64
69
  that [`audit-clean-code`](audit-clean-code.md) documents, since
65
70
  `knip --production` is a silent no-op without it.
@@ -116,21 +121,35 @@ legitimately have no layered architecture to guard.
116
121
 
117
122
  ## Step 2: Analysis Dimensions
118
123
 
119
- For every finding you surface, grade **Impact** on a High / Medium / Low axis
120
- reflecting the severity of the architectural risk (how much correctness,
121
- maintainability, or testability the gap erodes), independent of the
122
- **Category** effort axis a Quick Win can still be High Impact, and a
123
- Structural Change can be Medium. As a loose default, Quick Wins typically land
124
- High (cheap to fix, real payoff) and Structural Changes Medium/High, but grade
125
- Impact on the risk itself rather than deriving it mechanically from Category.
124
+ For every finding you surface, grade **Impact** on the **canonical severity
125
+ scale** `Critical | High | Medium | Low | Info`, defined in the core's
126
+ [Severity scale](helpers/audit-lens-core.md#severity-scale) and owned by
127
+ `lib/findings/severity.js`. This lens relabels the axis `Impact` (it reflects how
128
+ much correctness, maintainability, or testability the gap erodes), but the
129
+ *levels* are the shared five and nothing else: a narrower or invented vocabulary
130
+ resolves to no severity and the finding is dropped from every severity-filtered
131
+ run. In particular an architectural defect that is actively data-losing or
132
+ release-blocking grades **Critical**, and a grounded observation that asks for no
133
+ scheduled work grades **Info** — neither collapses into `High` or `Low`.
134
+
135
+ Impact is independent of the **Category** effort axis — a Quick Win can still be
136
+ High Impact, and a Structural Change can be Medium. As a loose default, Quick
137
+ Wins typically land High (cheap to fix, real payoff) and Structural Changes
138
+ Medium/High, but grade Impact on the risk itself rather than deriving it
139
+ mechanically from Category.
126
140
 
127
141
  > **Boundary with `audit-clean-code`.** The clean-code-overlapping smells
128
142
  > (over-engineering & abstractions, cognitive load & nesting, dead code &
129
143
  > redundancy, naming & self-documentation, coupling & cohesion) are owned by
130
144
  > [`audit-clean-code`](audit-clean-code.md); the Step 0 dead-export candidates
131
145
  > flow into its Dead Code dimension. Do **not** duplicate them here. This lens
132
- > keeps only the two structural dimensions no other lens owns — the
133
- > testable-surface boundary and the automated-guardrail maturity.
146
+ > keeps only the three structural dimensions no other lens owns — the
147
+ > testable-surface boundary, the automated-guardrail maturity, and
148
+ > shipped-but-never-wired seams. The split with clean-code's Dead Code dimension
149
+ > is by *question asked*: clean-code asks whether a symbol is referenced at all,
150
+ > this lens asks whether a **live production path** reaches it. An unreferenced
151
+ > helper is clean-code's; a fully-referenced writer whose reader was never built
152
+ > is this lens's.
134
153
 
135
154
  Evaluate the gathered context against the following architecture dimensions:
136
155
 
@@ -208,6 +227,43 @@ Evaluate the gathered context against the following architecture dimensions:
208
227
  full maturity assessment runs only in codebase-wide mode (when
209
228
  `{{changedFiles}}` renders as the literal substitution token).
210
229
 
230
+ 3. **Shipped-But-Never-Wired Seams (mandatory).** Report code this repository
231
+ ships that **no live production caller ever reaches**. This is the failure
232
+ mode the per-Story suite structurally cannot catch: every piece passes its own
233
+ unit tests, the diff looks complete, and the assembled path is dead — so it is
234
+ found only after delivery, if at all. A green suite is not evidence of a live
235
+ path; only a caller is.
236
+
237
+ Walk the seam **from the consumer backwards**, not from the producer forwards.
238
+ For each candidate, name the production entry point you traced to — or state
239
+ that you could not reach one, which is the finding. Cover at least:
240
+
241
+ - **A produced-but-never-consumed artifact.** Something the code computes,
242
+ stamps, writes, or returns that nothing downstream ever reads: an envelope
243
+ field no consumer parses, a file or log written and never opened, a
244
+ provenance marker stamped by the writer with no reader. Both halves must
245
+ exist for the feature to work, and shipping only the writer looks exactly
246
+ like shipping the feature.
247
+ - **An optional field nothing populates.** The mirror image: a parameter,
248
+ config key, or schema property a consumer reads and branches on that no
249
+ caller ever sets. The branch is unreachable, so the behaviour it guards has
250
+ never once run, and the default silently *is* the behaviour.
251
+ - **An exported seam with no in-tree caller** that the core's exclusion list
252
+ does not bless — not a test seam, not a CLI/`exports` entry point, not
253
+ dynamically reached. Cite the exclusion and drop it when it is one of those;
254
+ the Step 0 dead-export reading is the grounding instrument here, and the
255
+ candidates it surfaces that are genuinely dead **internal** wiring belong in
256
+ this dimension rather than ceded to clean-code's Dead Code dimension.
257
+
258
+ Grade Impact by what the dead wiring was supposed to do: a dead *guard, gate,
259
+ or enforcement path* is **High** or **Critical** — the protection it was
260
+ shipped to provide has never been in force, and the gate reads green because
261
+ it never runs. Dead reporting or convenience wiring is **Medium**. Each
262
+ finding's **Acceptance signal** must be the observable that proves the path is
263
+ live: a test that fails when the caller is removed, or a trace from the
264
+ production entry point to the seam. "Added a unit test for the seam" is not
265
+ that signal — the seam already had one.
266
+
211
267
  ### Maturity Rubric
212
268
 
213
269
  Use these definitions to classify the project's `Architecture Guardrail
@@ -107,6 +107,32 @@ Evaluate the gathered context against the following test quality dimensions:
107
107
  criteria to ensure they have corresponding and complete test coverage.
108
108
  Verify that the implementation found in the codebase correctly matches the
109
109
  architectural requirements and highlight any inconsistencies or gaps.
110
+ 7. **Unwired Seams — Coverage Without a Caller (mandatory).** Report code the
111
+ suite **covers** but no live production path **calls**. This is the blind spot
112
+ this dimension exists for, and it is a property of the suite, not of the code:
113
+ a seam with its own passing unit test reports as covered, contributes to the
114
+ coverage number, and is never exercised in assembly — so the suite reads green
115
+ over wiring that has never run once. High coverage is therefore not evidence
116
+ of a live path; it is what conceals a dead one. Cover at least:
117
+
118
+ - **A produced-but-never-consumed artifact** — a field, file, or marker the
119
+ code writes that no test and no consumer ever reads back. A writer test that
120
+ asserts on the writer's own output is not a reader.
121
+ `assert(written === expected)` proves the write, never the round-trip.
122
+ - **An optional field nothing populates** — a parameter or config key a
123
+ consumer branches on that only *tests* ever set. The suite covers both
124
+ branches; production has only ever taken the default.
125
+
126
+ For each, name the missing test rather than the missing caller: the gap is
127
+ that **no test would fail if the wiring were deleted**. That is the assertion
128
+ to recommend — an integration-tier test that drives the real production entry
129
+ point and fails when the seam is unwired. Grade a dead guard/gate **High** or
130
+ **Critical** (the enforcement has never been in force and the bar reads green
131
+ because it never runs), other dead wiring **Medium**. The core's exclusion
132
+ list still applies: a sanctioned test seam or a declared entry point is not a
133
+ finding here. Route the *architectural* framing of the same defect to
134
+ [`audit-architecture`](audit-architecture.md)'s Shipped-But-Never-Wired
135
+ dimension; this lens owns the **missing-test** framing.
110
136
 
111
137
  ## Constraint (lens-specific carve-out)
112
138
 
@@ -143,9 +143,24 @@ Chain into the existing planning entrypoint:
143
143
 
144
144
  (`/plan --seed "$(cat <path>)"` also works for small seeds). `/plan`
145
145
  then runs its author → persist path, as documented in its workflow.
146
- Each Story it spawns from the seed carries `context::audit:
147
- <reportLink>` and `audit-fingerprint: <sha>` in its body so future
148
- Phase 6 idempotency works on the next run.
146
+
147
+ **Dedup provenance is carried mechanically do not hand-copy it.** The seed's
148
+ MVP Scope bullets carry each group's `audit-fingerprints` and
149
+ `audit-semantic-keys` footers as HTML comments (invisible in the rendered
150
+ one-pager). `plan-persist` harvests them out of the seed on the
151
+ `plan-context.json` envelope and appends them to **every** Story body it
152
+ persists, via `carryProvenanceFooters`
153
+ ([`lib/findings/route-finding.js`](../scripts/lib/findings/route-finding.js)).
154
+ The carry is additive, union-preserving and idempotent, so a resumed persist
155
+ cannot stack footers and a hand-authored fingerprint is never dropped.
156
+
157
+ This is deliberately not an authoring step. It used to be: the footers reached
158
+ the seed and stopped there, leaving the authoring agent to notice HTML comments
159
+ in a one-pager and copy them forward — a remembered step, which is to say no
160
+ step at all. Stories filed on the recommended path were therefore invisible to
161
+ the next sweep's Phase 6 dedup, which re-filed work it had already planned. If
162
+ you find yourself copying a footer by hand, the carry is broken — fix it there
163
+ rather than papering over it in the body.
149
164
 
150
165
  ## Phase 5b — Standalone-Stories path
151
166
 
@@ -261,7 +276,18 @@ When the single-plan path ran, link the Story (or plan-run) the chained
261
276
  dedup/route implementation, shared with `qa-explore`.
262
277
  - **Always** stamp the fingerprint footer (via the helper's
263
278
  `fingerprintFooter`) in the body of every created Story. Without it,
264
- the next run cannot dedupe.
279
+ the next run cannot dedupe. On the Single-plan path this is mechanical
280
+ (`carryProvenanceFooters`, Phase 5a) — never an authoring step.
281
+ - **Never** re-mint a finding fingerprint while normalising a finding.
282
+ `severity` and `labels` are identity fields folded into the sha, so
283
+ normalising either without holding the hash stable silently breaks
284
+ dedup for every finding already filed. The projection that keeps the
285
+ fingerprint invariant under normalisation is
286
+ `severity.js#fingerprintSeverity`; the contract is pinned by
287
+ `tests/lib/findings/route-finding.contract.test.js`.
288
+ - **Always** grade findings on the canonical five-level scale
289
+ (`lib/findings/severity.js`). A level outside it parses as no severity
290
+ and the finding is dropped by every severity-filtered run.
265
291
  - **Always** present the Phase 2, 3, and 4 HITL gates. Do not bypass —
266
292
  even when "obvious" — because the severity / grouping / mode picks
267
293
  are operator decisions that the workflow's UX contract relies on.
@@ -8,73 +8,64 @@ description:
8
8
  # /deliver
9
9
 
10
10
  > **Lean spine.** Happy path + gate list. Sequencing, dispatch mechanics,
11
- > intent phrases, ceremony, and the epilogue live in the on-demand
12
- > [`helpers/deliver-reference.md`](helpers/deliver-reference.md); the unplanned
13
- > path in [`helpers/deliver-light.md`](helpers/deliver-light.md). What every
14
- > delivery always needs is one read:
15
- > [`helpers/deliver-digest.md`](helpers/deliver-digest.md).
11
+ > intent phrases, ceremony and the epilogue live in on-demand
12
+ > [`helpers/deliver-reference.md`](helpers/deliver-reference.md) ("reference"
13
+ > below); the unplanned path in
14
+ > [`helpers/deliver-light.md`](helpers/deliver-light.md). Every delivery reads
15
+ > [`helpers/deliver-digest.md`](helpers/deliver-digest.md) once.
16
16
 
17
17
  ## Role
18
18
 
19
- One delivery door. `/deliver` owns input resolution and sequencing only — every
20
- Story lands through [`helpers/deliver-story.md`](helpers/deliver-story.md).
19
+ One delivery door. `/deliver` owns input resolution, sequencing and the
20
+ close-and-land tail; Stories are implemented via
21
+ [`helpers/deliver-story.md`](helpers/deliver-story.md).
21
22
 
22
- Nothing about the route is declared at the invocation; it is **derived, then
23
- announced, then acted on**. The dependency graph is **discovered, not
24
- declared** `resolve-stories.js` reads it from live state (body edges ∪ native
25
- `blocked_by` edges, each blocker resolved against its real issue state), so
26
- there is no graph to hand it and no batch label, which is what lets you deliver
27
- Stories **across plan runs and over time**.
28
- `plan-run::<id>` is filter metadata, never a resolution input; `route::lite` is
29
- a body-derived hint only. Ahead of all of it, a **single-Story run runs the
30
- engine inline** whatever the shape — sub-agent isolation only earns its cost
31
- against a concurrent sibling.
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.
32
27
 
33
28
  ## Inputs
34
29
 
35
- Classify what the operator typed **before** doing anything else, and say which
36
- shape you read it as:
30
+ Classify what the operator typed **before** anything else, and say which shape
31
+ you read:
37
32
 
38
33
  | Invocation | Shape | Behavior |
39
34
  | --- | --- | --- |
40
35
  | `/deliver` | bare | List the open `agent::ready` Stories and ask which to deliver. Deliver nothing until answered. |
41
36
  | `/deliver 4712` | ids | One Story via `helpers/deliver-story.md`, **inline in this session** — no `story-worker` spawn. |
42
- | `/deliver 4712 4713 …` | ids | Resolve the set, then sequence by the discovered graph via `stories-wave-tick.js`, dispatching role-scoped sub-agents. |
37
+ | `/deliver 4712 4713 …` | ids | Resolve the set, sequence by the discovered graph via `stories-wave-tick.js`, dispatch sub-agents. |
43
38
  | `/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). |
44
39
 
45
40
  **The discriminator is lexical and total.** Every positional argument matching
46
41
  `^#?\d+$` means ids; anything else means a prompt. A **mixed** invocation (ids
47
- *and* prose) is a **hard error** — refuse it and ask which was meant, the way
48
- resolution refuses a whole set rather than under-delivering. A named ticket
49
- that is not `type::story`, or carries an `Epic: #N` footer, is a hard error too.
42
+ *and* prose) is a **hard error** — refuse it and ask which was meant. A ticket
43
+ not `type::story`, or carrying an `Epic: #N` footer, is a hard error too.
50
44
 
51
45
  ## Saying what you want
52
46
 
53
- No flags to remember: state intent — *"…but I'll merge it myself"*, *"…take the
54
- lease"*, *"…one at a time"* — and announce what you read before acting.
55
- Phrasings and the flag each fills in:
56
- [`helpers/deliver-reference.md` § Intent phrases](helpers/deliver-reference.md).
47
+ No flags to remember: state intent — *"…but I'll merge it myself"*, *"…one at
48
+ a time"* — and announce what you read. Phrasings and the flags they fill in:
49
+ reference § Intent phrases.
57
50
 
58
- `--yes` is **runner-set, never operator-typed**: cron, `/loop`, and headless
59
- dispatch set it to mean *nobody is at the keyboard*, which is what makes the
60
- unplanned path's over-scope stop fail closed to a terminal envelope instead of
61
- a question. Never offer it to an operator or add it to an attended run.
51
+ `--yes` is **runner-set, never operator-typed**: cron, `/loop` and headless
52
+ dispatch set it to mean *nobody is at the keyboard*, which fails the unplanned
53
+ path's over-scope stop closed to an envelope instead of a question. Never offer
54
+ it to an operator or add it to an attended run.
62
55
 
63
56
  ## Procedure
64
57
 
65
58
  0. **Classify and announce.** Read the invocation per § Inputs and state the
66
- shape you derived. A prompt leaves for
67
- [`helpers/deliver-light.md`](helpers/deliver-light.md); bare asks; ids
68
- continue below.
69
-
70
- 1. **Resolve the set.** One command, for one Story or many:
71
- `node .agents/scripts/resolve-stories.js --ids <id,id,...>`. It validates
72
- the set and shows what will run: read `stories[]`, `dag[]`, and `done[]` to
73
- present the order in step 2. You do **not** thread them into step 3 — the
74
- tick re-resolves the graph itself every beat. Resolution hard-errors
75
- (exit 1) on a named id that is not a Story, carries an `Epic: #N` footer, or
76
- whose native edges cannot be read — a missing gate would co-dispatch against
77
- an unlanded blocker.
59
+ shape. A prompt leaves for
60
+ [`helpers/deliver-light.md`](helpers/deliver-light.md); bare asks; ids go on.
61
+
62
+ 1. **Resolve the set.** One command, one Story or many:
63
+ `node .agents/scripts/resolve-stories.js --ids <id,id,...>`. It validates the
64
+ set and shows what will run: read `stories[]`, `dag[]` and `done[]` to
65
+ present the order in step 2, but do **not** thread them into step 3 — the
66
+ tick re-resolves the graph every beat. It hard-errors (exit 1) on an id that
67
+ is not a Story, carries an `Epic: #N` footer, or whose edges cannot be
68
+ read a missing gate would co-dispatch against an unlanded blocker.
78
69
 
79
70
  2. **Confirm (N>1).** Present the order; wait unless `--yes`.
80
71
 
@@ -87,74 +78,69 @@ a question. Never offer it to an operator or add it to an attended run.
87
78
  ```
88
79
 
89
80
  **Do not add `--concurrency` unless the operator explicitly asked for a
90
- per-run cap** — an explicit value wins over config, so a filled-in literal
91
- silently defeats a `.agentrc.local.json` override.
81
+ per-run cap** — an explicit value wins over config, so a literal defeats a
82
+ `.agentrc.local.json` override.
92
83
 
93
84
  Each beat re-probes live state to derive done / in-flight itself; you never
94
85
  compute them. `--dispatched` is the one thing you must supply — the
95
- append-only list of every id you spawned this run and cross-run
96
- de-confliction via the assignee lease is automatic
97
- ([`helpers/deliver-reference.md`](helpers/deliver-reference.md) §§ Sequencing
98
- edge cases, Dispatch mechanics).
99
-
100
- Branch on the exit code:
101
- - **0** — dispatch each `ready` id (already capped and overlap-free). Empty
102
- `ready` with work in flight means "waiting"; keep looping.
103
- `epilogueDue: true` means every Story is done go to step 4.
104
- - **2** — `cycleError`: the graph is self-referential. Fix the `depends_on`
105
- declarations; do not retry.
106
- - **3** — `wedged`: nothing dispatchable, nothing in flight, undone Stories
107
- waiting on unmet blockers both named in the envelope. Land the blocker
108
- or include it in `--ids`; do not retry unchanged.
109
- - **4** — `blocked`: a Story carries `agent::blocked`, named in `blocked[]`
110
- with `blockedReason` the protocol's HITL pause
111
- ([`instructions.md` § 1.J](../instructions.md)). **Stop the loop and
112
- surface it; do not poll.** Read the friction comment
113
- (`gh issue view <id> --comments`) and resume only once the operator
114
- unblocks it (`update-ticket-state.js --ticket <id> --state agent::ready`).
115
- Blocked outranks a wedge but not a cycle (fix the graph first).
116
-
117
- 4. **Per-run epilogue (N>1).** Once step 3 reports `epilogueDue: true`, run
118
- `node .agents/scripts/plan-run-epilogue.js --stories 101,102` — audit
119
- roster, follow-up roll-up, sibling coherence. A single-Story run skips it
86
+ append-only list of every id you spawned this run. Cross-run de-confliction
87
+ via the assignee lease is automatic (reference §§ Sequencing edge cases,
88
+ Dispatch mechanics). Branch on the exit code:
89
+ - **0** dispatch each `ready` id (already capped and overlap-free); an
90
+ empty `ready` with work in flight means "waiting", so keep looping;
91
+ `epilogueDue: true` means every Story is done — step 4.
92
+ - **2** — `cycleError`: the graph is self-referential; fix `depends_on`, do
93
+ not retry. **3** — `wedged`: nothing dispatchable, nothing in flight, the
94
+ undone Stories and their unmet blockers named; land a blocker or add it to
95
+ `--ids`. **4** — `blocked`: a Story carries `agent::blocked` with
96
+ `blockedReason`, the protocol's HITL pause
97
+ ([`instructions.md` § 1.J](../instructions.md)) **stop the loop and
98
+ surface it; do not poll**, resuming once the operator unblocks it. Blocked
99
+ outranks a wedge, not a cycle.
100
+
101
+ 4. **Close each hand-off** (§ Closing what the workers hand back), then, with
102
+ every Story landed, run the **per-run epilogue (N>1)**:
103
+ `node .agents/scripts/plan-run-epilogue.js --stories 101,102`. N=1 skips it
120
104
  ([reference § Per-run epilogue](helpers/deliver-reference.md)).
121
105
 
122
- ## Branch model (authoritative)
106
+ ## Closing what the workers hand back {#tail}
123
107
 
124
- `story-<id>` PR `main` (squash + required checks), per digest § 2.
125
- Dependent Stories land sequentially so each builds on the previous merge.
126
- Ceremony depth (profiles + derived level via `ceremony-routing.js`, review
127
- depth reading the same level):
128
- [`helpers/deliver-reference.md` § Ceremony](helpers/deliver-reference.md).
108
+ **The tail is the orchestrator's, not the worker's.** A dispatched
109
+ `story-worker` stops at a pushed branch and returns a hand-off; **you** run
110
+ [`helpers/deliver-story.md`](helpers/deliver-story.md) Step 3
111
+ (`single-story-close.js`) for it, foreground, and relay the envelope.
129
112
 
130
- ## Reading a Story's outcome
113
+ **Serialize the tail.** Implementation runs in parallel; closing does not. Close
114
+ one Story at a time — closes contend on the base branch, the merge queue and
115
+ the checkout. A worker handing back mid-close waits its turn.
131
116
 
132
- Each Story ends in exactly one schema-validated terminal envelope `landed` |
133
- `pending` | `blocked` | `failed`. Statuses, exits, and fields:
134
- [`helpers/deliver-digest.md`](helpers/deliver-digest.md) § 5.
117
+ **A worker returning no terminal envelope is the expected shape**, not a failure
118
+ to answer with a re-dispatch: only close mints one. Close the pushed branch, or
119
+ probe read-only with `node .agents/scripts/deliver-recover.js --story <storyId>`
120
+ and resume the worker or close it names.
135
121
 
136
- `pending` is **not** a failure: the bounded wait expired with the PR healthy
137
- (or a human owns the merge), nothing was mutated, and `nextCommand` resumes it
138
- run that rather than re-dispatching.
122
+ **Reading the outcome.** Each close ends the Story in one schema-validated
123
+ envelope `landed` | `pending` | `blocked` | `failed`; statuses, exits and
124
+ fields are digest § 5. `pending` is **not** a failure — `nextCommand` resumes
125
+ it; run that, do not re-dispatch.
139
126
 
140
- For a Story in an unclear state including the merged-but-label-stale one a
141
- re-run refuses outright probe it read-only with
142
- `node .agents/scripts/deliver-recover.js --story <storyId>`.
127
+ **Branch model (authoritative).** `story-<id>` PR `main` (squash +
128
+ required checks), per digest § 2; dependent Stories land sequentially. Ceremony
129
+ depth (profiles + derived level via `ceremony-routing.js`, review depth reading
130
+ it): reference § Ceremony.
143
131
 
144
132
  ## Constraints
145
133
 
146
- - **Land or block — never a silent local build** (digest § 2). Attended
147
- delivers default to close-and-land (`delivery.routing.closeAndLand: true`);
148
- rest at `agent::closing` only when a human owns the merge.
134
+ - **Land or block — never a silent local build** (digest § 2). Attended delivers
135
+ default to close-and-land (`delivery.routing.closeAndLand: true`); rest at
136
+ `agent::closing` only when a human owns it.
149
137
  - **`/deliver` never plans.** Planned tickets come from [`/plan`](plan.md), and
150
138
  an over-scope prompt **escalates and ends** — never invoke `/plan` in this
151
139
  session to rescue it ([`helpers/deliver-light.md`](helpers/deliver-light.md)
152
- § Escalation is terminal). The router performs no git/label mutations;
153
- `deliver-story` owns every script.
140
+ § Escalation is terminal).
154
141
 
155
142
  ## See also
156
143
 
157
- - [`/plan`](plan.md) unified planning entry point.
158
- - [`helpers/deliver-story.md`](helpers/deliver-story.md) the one engine.
159
- - [`helpers/deliver-light.md`](helpers/deliver-light.md) the unplanned
160
- prompt path, shared with `/plan` Gate #1.
144
+ [`/plan`](plan.md), [`helpers/deliver-story.md`](helpers/deliver-story.md) (the
145
+ engine), [`helpers/deliver-light.md`](helpers/deliver-light.md) (the unplanned
146
+ prompt path, shared with `/plan` Gate #1).
@@ -76,7 +76,7 @@ the primary file the finding lives in:]
76
76
  ### `path/to/primary-file.ext` — [Short title of the issue]
77
77
 
78
78
  - **Dimension:** [the lens-specific dimension — see the lens's own list]
79
- - **Severity:** [Critical | High | Medium | Low]
79
+ - **Severity:** [Critical | High | Medium | Low | Info]
80
80
  - **Location:** `path/to/primary-file.ext:line`
81
81
  - **Current State:** [the specific file/line/module and why it is problematic]
82
82
  - **Recommendation & Rationale:** [how to remediate and why it matters]
@@ -89,11 +89,18 @@ the primary file the finding lives in:]
89
89
 
90
90
  ## Severity scale {#severity-scale}
91
91
 
92
- Every finding grades its severity (labelled `Severity` or `Impact`) on this
93
- ordered scale. `parse-audit-md.js` recognizes every level, and a surviving
94
- **Critical** finding halts the delivery gate
92
+ Every finding grades its severity (labelled `Severity` or `Impact`) on the
93
+ ordered scale below. **The code owns this vocabulary**: it is defined in
94
+ [`lib/findings/severity.js`](../../scripts/lib/findings/severity.js)
95
+ (`SEVERITIES`), and every level here — including its accepted spellings — comes
96
+ from that module. A surviving **Critical** finding halts the delivery gate
95
97
  (`lib/audit-suite/findings.js#hasSurvivingCritical`).
96
98
 
99
+ Grade on **exactly** these five levels. A level of your own invention does not
100
+ parse: it resolves to no severity, tallies as `unknown`, and the finding is
101
+ dropped by every severity-filtered run — including the most permissive one. A
102
+ dropped finding is indistinguishable from a finding you never wrote.
103
+
97
104
  - **Critical** — an active, exploitable, or data-losing defect that must be
98
105
  fixed before the change can ship (e.g. a leaked secret, an auth bypass, a
99
106
  guaranteed production outage or data-loss path).
@@ -102,6 +109,11 @@ ordered scale. `parse-audit-md.js` recognizes every level, and a surviving
102
109
  - **Medium** — a real problem worth scheduling; contained blast radius, or a
103
110
  reasonable workaround exists.
104
111
  - **Low** — minor or cosmetic; fix opportunistically.
112
+ - **Info** — the canonical floor: a real, grounded observation worth recording
113
+ that asks for no scheduled work (a documented deviation worth noting, a
114
+ measurement that is fine today and worth watching). Accepts `Informational`.
115
+ Use it instead of inventing a below-`Low` word of your own; a finding that
116
+ cannot clear the evidence bar below is **dropped**, not filed as `Info`.
105
117
 
106
118
  ## Self-cross-check (mandatory — filter false positives before you finalize) {#self-cross-check}
107
119
 
@@ -149,6 +161,15 @@ that rests on one of them:
149
161
  - **Style-only nits already enforced by a formatter/linter** — do not
150
162
  re-litigate what the committed tooling already governs.
151
163
 
164
+ > **Boundary with the dead-wiring mandate.** The architecture and quality lenses
165
+ > are required to report shipped seams with no live production caller (their own
166
+ > bodies carry the mandate). The exclusions above **bound** that mandate rather
167
+ > than cancelling it: a test seam, a CLI entry point, a declared `exports`
168
+ > surface, or a dynamically-reached symbol is still never a finding. What the
169
+ > mandate targets is the case none of those cover — an **internal** seam that a
170
+ > delivery shipped and nothing in production ever calls. When a candidate is
171
+ > genuinely one of the exclusions, cite the exclusion and drop it.
172
+
152
173
  ### Final re-open-and-drop pass (mandatory)
153
174
 
154
175
  1. Walk your Detailed Findings once more, applying the bar and the exclusion
@@ -48,6 +48,11 @@ Because the predicted footprint is a *declaration* — a guess, and a gameable o
48
48
  multi-capability enumeration). Size is enforced where ground truth is available:
49
49
  the diff backstop in step 4. Do not talk yourself past that one.
50
50
 
51
+ **The backstop counts by the same principle.** It reads magnitude — changed
52
+ lines over implementation files — not artifacts, and exempts the test and doc
53
+ companions the framework itself mandates. A ceiling that punishes a repo for
54
+ obeying its own test-first rule is a ceiling that over-fires.
55
+
51
56
  Sensitivity is the exception and stays absolute: a footprint touching an auth,
52
57
  crypto, billing, or migration class routes `full` however small or mechanical —
53
58
  and unlike a ceiling, it is **not overridable** (§ Recording a proceed-light
@@ -136,10 +141,22 @@ answer).
136
141
  node .agents/scripts/deliver-light.js --backstop --story <storyId>
137
142
  ```
138
143
 
139
- Exit `3` (`blocked: true`) means the landed diff exceeds the light ceilings
140
- (file count or a sensitive-path class). STOP, flip `agent::blocked`, and
141
- escalate to `/plan` do not land. This is the pass that actually bounds
142
- size, which is why the prediction gate above can afford to be coarse.
144
+ This is the pass that actually bounds size, which is why the prediction gate
145
+ above can afford to be coarse. It measures **magnitude on the change's
146
+ implementation half**changed lines (additions + deletions) plus a file
147
+ sprawl tripwire never raw artifact count. Tests, `docs/**`, `**/*.md`,
148
+ `baselines/**`, and lockfiles are exempt from the counts, because the
149
+ framework mandates those companions and obeying it must not inflate the
150
+ number that then rejects the change. They are **not** exempt from
151
+ sensitive-path matching, which runs over the full change set.
152
+
153
+ Exit `3` (`blocked: true`) means the diff exceeds a light ceiling or touches a
154
+ sensitive-path class. STOP, flip `agent::blocked`, and **recycle the receipt**
155
+ through the envelope's `nextCommand` (`/plan <storyId>`) — tickets mode
156
+ rewrites it into properly-planned Stories and closes it as superseded. Do not
157
+ land, and do not leave the receipt open with no successor: it already carries
158
+ the branch, the worktree, and the implementation, all of which are evidence
159
+ the plan should read.
143
160
 
144
161
  5. **Close and land (same engine).** Exactly [`/deliver`](../deliver.md)'s close:
145
162
 
@@ -14,6 +14,20 @@ it is the mechanics an operator consults when the matching lever is engaged.
14
14
 
15
15
  ## Sequencing edge cases (`stories-wave-tick.js`)
16
16
 
17
+ **What "discovered, not declared" means concretely.** `resolve-stories.js` reads
18
+ the graph from live state as the union of the Story bodies' `depends_on` edges
19
+ and GitHub's native `blocked_by` edges, resolving each blocker against its real
20
+ issue state rather than against anything you hand it. That is why there is no
21
+ batch label to pass and why a blocker that landed in an unrelated run is simply
22
+ seen as done.
23
+
24
+ **Resuming an exit-4 `blocked`.** Read the friction comment with
25
+ `gh issue view <id> --comments`, and resume only once the operator has
26
+ unblocked it:
27
+ `node .agents/scripts/update-ticket-state.js --ticket <id> --state agent::ready`.
28
+ Do not poll the label yourself while waiting — the HITL pause is the operator's
29
+ turn, not a slow beat.
30
+
17
31
  Each beat re-probes live state: it re-resolves the graph, classifies **done**
18
32
  (`agent::done` or a closed issue — including foreign blockers that landed in
19
33
  another run), and derives **in-flight** from live `agent::executing` /
@@ -88,9 +102,10 @@ exposes agent dispatch, spawn each ready Story as its own
88
102
  `subagent_type: story-worker` sub-agent — it boots on the role-scoped
89
103
  [`story-worker`](../../agents/story-worker.md) context (its own system prompt, no
90
104
  `CLAUDE.md` @-closure) carrying the load-bearing delivery MUSTs standalone. The
91
- sub-agent executes [`deliver-story.md`](deliver-story.md) end to end
92
- (init → implement → acceptance self-eval → close-and-land). Thread into its
93
- prompt: `storyId`; `docsDigestPath` (the per-run docs digest, null when
105
+ sub-agent executes [`deliver-story.md`](deliver-story.md) Steps 0–2.5
106
+ (init → implement → acceptance self-eval → **push**) and stops there; **you**
107
+ own Step 3, serialized — see `/deliver` § Closing what the workers hand back.
108
+ Thread into its prompt: `storyId`; `docsDigestPath` (the per-run docs digest, null when
94
109
  `project.docsContextFiles` is unset); `checklistPath` (the footprint-matched
95
110
  write-time audit checklist, produced at dispatch, below); and the
96
111
  **change-set discipline** — the worker computes the change set once with