mandrel 2.31.0 → 2.33.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 (267) hide show
  1. package/.agents/README.md +13 -17
  2. package/.agents/agents/acceptance-critic.md +1 -2
  3. package/.agents/docs/SDLC.md +12 -9
  4. package/.agents/docs/agentrc-reference.json +63 -58
  5. package/.agents/docs/configuration.md +275 -227
  6. package/.agents/docs/execution-reference.md +13 -14
  7. package/.agents/docs/quality-gates.md +195 -23
  8. package/.agents/instructions.md +2 -5
  9. package/.agents/rules/git-conventions-reference.md +27 -27
  10. package/.agents/rules/git-conventions.md +4 -2
  11. package/.agents/rules/known-tooling-behavior.md +66 -30
  12. package/.agents/rules/testing-standards.md +35 -71
  13. package/.agents/runtime-deps.json +2 -2
  14. package/.agents/schemas/agentrc.schema.json +1955 -1410
  15. package/.agents/schemas/lifecycle/README.md +21 -14
  16. package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
  17. package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
  18. package/.agents/scripts/README.md +16 -29
  19. package/.agents/scripts/apply-quality-bootstrap.js +27 -34
  20. package/.agents/scripts/audit-to-stories.js +160 -41
  21. package/.agents/scripts/bootstrap.js +28 -26
  22. package/.agents/scripts/check-baseline-drift.js +73 -13
  23. package/.agents/scripts/check-baseline-scope.js +362 -0
  24. package/.agents/scripts/check-dead-exports.js +9 -1
  25. package/.agents/scripts/check-gherkin-corpus.js +508 -0
  26. package/.agents/scripts/check-knip-entries.js +159 -0
  27. package/.agents/scripts/check-lifecycle-lint.js +108 -124
  28. package/.agents/scripts/check-schema-references.js +1 -1
  29. package/.agents/scripts/diagnose-friction.js +7 -4
  30. package/.agents/scripts/generate-config-docs.js +263 -171
  31. package/.agents/scripts/install-matrix-assert.js +0 -1
  32. package/.agents/scripts/lib/ITicketingProvider.js +0 -58
  33. package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
  34. package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
  35. package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
  36. package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
  37. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +81 -35
  38. package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +185 -0
  39. package/.agents/scripts/lib/baselines/envelope.js +41 -60
  40. package/.agents/scripts/lib/baselines/git-base.js +30 -37
  41. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
  42. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
  43. package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
  44. package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
  45. package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
  46. package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
  47. package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
  48. package/.agents/scripts/lib/bdd-step-index.js +326 -0
  49. package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
  50. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
  51. package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
  52. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
  53. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
  54. package/.agents/scripts/lib/checks/loop-health.js +9 -37
  55. package/.agents/scripts/lib/child-exec.js +193 -0
  56. package/.agents/scripts/lib/cli/standard-args.js +1 -1
  57. package/.agents/scripts/lib/cli-args.js +64 -0
  58. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  59. package/.agents/scripts/lib/close-validation/runner.js +3 -3
  60. package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
  61. package/.agents/scripts/lib/config/commands.js +3 -5
  62. package/.agents/scripts/lib/config/explain.js +5 -7
  63. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
  64. package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
  65. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
  66. package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
  67. package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
  68. package/.agents/scripts/lib/config/gates/index.js +5 -2
  69. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
  70. package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
  71. package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
  72. package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
  73. package/.agents/scripts/lib/config/gates/shared.js +58 -9
  74. package/.agents/scripts/lib/config/github.js +0 -1
  75. package/.agents/scripts/lib/config/limits.js +3 -48
  76. package/.agents/scripts/lib/config/qa.js +105 -0
  77. package/.agents/scripts/lib/config/runners.js +38 -16
  78. package/.agents/scripts/lib/config/temp-paths.js +6 -5
  79. package/.agents/scripts/lib/config-settings-schema-delivery.js +245 -56
  80. package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
  81. package/.agents/scripts/lib/config-settings-schema.js +386 -39
  82. package/.agents/scripts/lib/crap-baseline-join.js +126 -9
  83. package/.agents/scripts/lib/crap-utils.js +84 -520
  84. package/.agents/scripts/lib/dead-exports-knip.js +79 -10
  85. package/.agents/scripts/lib/degraded-mode.js +2 -2
  86. package/.agents/scripts/lib/dependency-parser.js +20 -7
  87. package/.agents/scripts/lib/doc-tiers.js +3 -3
  88. package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
  89. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
  90. package/.agents/scripts/lib/findings/provenance-field.js +135 -0
  91. package/.agents/scripts/lib/findings/route-finding.js +57 -8
  92. package/.agents/scripts/lib/fs-walk.js +52 -0
  93. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  94. package/.agents/scripts/lib/git-utils.js +16 -36
  95. package/.agents/scripts/lib/knip-config-resolver.js +181 -0
  96. package/.agents/scripts/lib/knip-entry-sync.js +508 -0
  97. package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
  98. package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
  99. package/.agents/scripts/lib/observability/signal-validator.js +5 -85
  100. package/.agents/scripts/lib/observability/signals-writer.js +19 -62
  101. package/.agents/scripts/lib/observability/source-classifier.js +5 -7
  102. package/.agents/scripts/lib/observability/terse-result.js +3 -3
  103. package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
  104. package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
  105. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
  106. package/.agents/scripts/lib/orchestration/code-review.js +2 -2
  107. package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
  108. package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
  109. package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
  110. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
  111. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
  112. package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
  113. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +7 -26
  114. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +69 -5
  115. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +69 -13
  116. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +49 -0
  117. package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
  118. package/.agents/scripts/lib/orchestration/resolve-stories.js +77 -50
  119. package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
  120. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
  121. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
  122. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
  123. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
  124. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
  125. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
  126. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
  127. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
  128. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
  129. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
  130. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
  131. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
  132. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
  133. package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
  134. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
  135. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
  136. package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
  137. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
  138. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +116 -1
  139. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
  140. package/.agents/scripts/lib/orchestration/ticket-validator.js +38 -0
  141. package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
  142. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
  143. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
  144. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
  145. package/.agents/scripts/lib/signals/detectors/common.js +1 -1
  146. package/.agents/scripts/lib/signals/index.js +8 -6
  147. package/.agents/scripts/lib/signals/schema.js +20 -25
  148. package/.agents/scripts/lib/signals/write.js +8 -8
  149. package/.agents/scripts/lib/story-body/footer-block.js +97 -0
  150. package/.agents/scripts/lib/story-body/story-body.js +18 -81
  151. package/.agents/scripts/lib/temp-retention.js +1 -1
  152. package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
  153. package/.agents/scripts/lib/ticket-body-sections.js +4 -5
  154. package/.agents/scripts/lib/wave-runner/footprint.js +306 -0
  155. package/.agents/scripts/lib/wave-runner/ready-set.js +198 -181
  156. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
  157. package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
  158. package/.agents/scripts/lib/worktree-manager.js +2 -3
  159. package/.agents/scripts/lint-label-vocabulary.js +2 -24
  160. package/.agents/scripts/pr-watch-with-update.js +7 -5
  161. package/.agents/scripts/providers/github/blocked-by-add.js +25 -10
  162. package/.agents/scripts/providers/github/cache.js +2 -2
  163. package/.agents/scripts/providers/github/comments.js +6 -28
  164. package/.agents/scripts/providers/github/compose.js +0 -15
  165. package/.agents/scripts/providers/github/errors.js +10 -27
  166. package/.agents/scripts/providers/github/request-helpers.js +1 -2
  167. package/.agents/scripts/providers/github/sub-issues.js +10 -218
  168. package/.agents/scripts/providers/github.js +4 -7
  169. package/.agents/scripts/prune-baseline-orphans.js +181 -0
  170. package/.agents/scripts/resolve-stories.js +21 -7
  171. package/.agents/scripts/run-lint.js +61 -61
  172. package/.agents/scripts/run-test-profile.js +6 -6
  173. package/.agents/scripts/run-verify.js +48 -30
  174. package/.agents/scripts/single-story-close.js +20 -0
  175. package/.agents/scripts/single-story-init.js +12 -35
  176. package/.agents/scripts/stories-wave-tick.js +192 -9
  177. package/.agents/scripts/update-dead-exports-baseline.js +321 -0
  178. package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
  179. package/.agents/skills/skills.index.json +1 -11
  180. package/.agents/workflows/audit-documentation.md +5 -6
  181. package/.agents/workflows/audit-to-stories.md +28 -2
  182. package/.agents/workflows/helpers/audit-lens-core.md +11 -12
  183. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
  184. package/.agents/workflows/helpers/code-review.md +3 -8
  185. package/.agents/workflows/helpers/deliver-reference.md +30 -2
  186. package/.agents/workflows/helpers/deliver-story-reference.md +84 -16
  187. package/.agents/workflows/helpers/plan-reference.md +76 -0
  188. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
  189. package/.agents/workflows/mandrel-update.md +10 -10
  190. package/.agents/workflows/qa-assist.md +15 -20
  191. package/.agents/workflows/qa-explore.md +9 -8
  192. package/README.md +1 -1
  193. package/docs/CHANGELOG.md +58 -0
  194. package/lib/migrations/index.js +2 -0
  195. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
  196. package/package.json +14 -5
  197. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
  198. package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
  199. package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
  200. package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
  201. package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
  202. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
  203. package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
  204. package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
  205. package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
  206. package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
  207. package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
  208. package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
  209. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
  210. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
  211. package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
  212. package/.agents/scripts/check-gherkin-placeholders.js +0 -663
  213. package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
  214. package/.agents/scripts/lib/audit-suite/cli.js +0 -64
  215. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
  216. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
  217. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
  218. package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
  219. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
  220. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
  221. package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
  222. package/.agents/scripts/lib/crap-baseline-index.js +0 -46
  223. package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
  224. package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
  225. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
  226. package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
  227. package/.agents/scripts/lib/mutation/config-detector.js +0 -119
  228. package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
  229. package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
  230. package/.agents/scripts/lib/observability/active-story-env.js +0 -170
  231. package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
  232. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
  233. package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
  234. package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
  235. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
  236. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
  237. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
  238. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
  239. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
  240. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
  241. package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
  242. package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
  243. package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
  244. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
  245. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
  246. package/.agents/scripts/lib/planning-corpus.js +0 -37
  247. package/.agents/scripts/lib/qa/coverage-report.js +0 -181
  248. package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
  249. package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
  250. package/.agents/scripts/lib/signals/detectors/index.js +0 -14
  251. package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
  252. package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
  253. package/.agents/scripts/lib/signals/read.js +0 -268
  254. package/.agents/scripts/lib/signals/span-tree.js +0 -291
  255. package/.agents/scripts/lib/story-lifecycle.js +0 -194
  256. package/.agents/scripts/lib/story-plan.js +0 -379
  257. package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
  258. package/.agents/scripts/lib/util/phase-timer.js +0 -163
  259. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
  260. package/.agents/scripts/lint-baseline.js +0 -507
  261. package/.agents/scripts/providers/github/prs.js +0 -103
  262. package/.agents/scripts/signals-view.js +0 -309
  263. package/.agents/scripts/story-plan.js +0 -370
  264. package/.agents/scripts/sync-branch-from-base.js +0 -149
  265. package/.agents/scripts/validate-docs-freshness.js +0 -314
  266. package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
  267. package/.agents/workflows/helpers/signals.md +0 -112
@@ -1,18 +1,25 @@
1
1
  # Lifecycle event schemas
2
2
 
3
- JSON Schemas for the lifecycle event taxonomy consumed by the
4
- `/deliver` lifecycle bus
5
- (`lib/orchestration/lifecycle/bus.js`). The bus validates every
6
- emit payload against one of these schemas before invoking
7
- listeners; a schema mismatch fails the emit and propagates the
8
- throw.
3
+ JSON Schemas for the two events a delivery run can append to its per-run
4
+ lifecycle ledger. `appendLedgerEvent`
5
+ (`lib/orchestration/lifecycle/emit-ledger-event.js`) validates every payload
6
+ against one of these before the write; a schema mismatch throws and nothing is
7
+ appended.
9
8
 
10
- Each event in the Tech Spec taxonomy has a `<event>.schema.json`
11
- file. The ledger record (`emitted | completed | failed` union)
12
- lives in `ledger-record.schema.json` and is consumed by
13
- `ledger-writer.js`.
9
+ `ledger-record.schema.json` is the NDJSON record envelope
10
+ (`emitted | completed | failed`), not an event. Only `emitted` has a writer —
11
+ see the schema's own description for why the other two kinds remain.
14
12
 
15
- Schemas are intentionally permissive (`additionalProperties: true`)
16
- on inner objects whose shape is dictated by upstream tooling (e.g.
17
- `gh pr view` JSON, `checkOutcomes`). The required-key set is the
18
- contract.
13
+ **A schema belongs here only while code emits its event.** Story #4545 applied
14
+ that rule to the `epic.*` and `acceptance.reconcile.*` families; Story #5024
15
+ applied it to the remaining fifteen when it retired the lifecycle bus that had
16
+ been their only publish path. `tests/lifecycle/schema-registry.test.js` enforces
17
+ it in both directions, so a schema file for an event nobody emits fails the
18
+ suite rather than reading green.
19
+
20
+ Schemas are intentionally permissive (`additionalProperties: true`) on inner
21
+ objects whose shape is dictated by upstream tooling (e.g. `gh pr view` JSON).
22
+ The required-key set is the contract.
23
+
24
+ Full reference:
25
+ [`docs/LIFECYCLE.md`](https://github.com/dsj1984/mandrel/blob/main/docs/LIFECYCLE.md).
@@ -2,22 +2,41 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/dsj1984/mandrel/blob/main/.agents/schemas/lifecycle/ledger-record.schema.json",
4
4
  "title": "Lifecycle ledger record (emitted | completed | failed)",
5
- "description": "Append-only NDJSON record shape for temp/run-<id>/lifecycle.ndjson. Three discriminated kinds; consumers (LedgerWriter, TraceLogger) discriminate on `kind`.",
5
+ "description": "Append-only NDJSON record shape for the per-run lifecycle ledger. Three discriminated kinds keyed on `kind`. Only `emitted` has a writer: Story #5024 retired the lifecycle bus, so the `completed` / `failed` kinds are a READ contract for ledgers archived from the bus era (`failed` requires a `listener`, which only a listener chain could supply). Reading a ledger never consults this schema; validation happens on the write path only.",
6
6
  "oneOf": [
7
- { "$ref": "#/$defs/emitted" },
8
- { "$ref": "#/$defs/completed" },
9
- { "$ref": "#/$defs/failed" }
7
+ {
8
+ "$ref": "#/$defs/emitted"
9
+ },
10
+ {
11
+ "$ref": "#/$defs/completed"
12
+ },
13
+ {
14
+ "$ref": "#/$defs/failed"
15
+ }
10
16
  ],
11
17
  "$defs": {
12
18
  "emitted": {
13
19
  "type": "object",
14
20
  "required": ["kind", "seqId", "ts", "event", "payload"],
15
21
  "properties": {
16
- "kind": { "const": "emitted" },
17
- "seqId": { "type": "integer", "minimum": 1 },
18
- "ts": { "type": "string", "format": "date-time" },
19
- "event": { "type": "string", "minLength": 1 },
20
- "payload": { "type": "object" }
22
+ "kind": {
23
+ "const": "emitted"
24
+ },
25
+ "seqId": {
26
+ "type": "integer",
27
+ "minimum": 1
28
+ },
29
+ "ts": {
30
+ "type": "string",
31
+ "format": "date-time"
32
+ },
33
+ "event": {
34
+ "type": "string",
35
+ "minLength": 1
36
+ },
37
+ "payload": {
38
+ "type": "object"
39
+ }
21
40
  },
22
41
  "additionalProperties": false
23
42
  },
@@ -25,11 +44,25 @@
25
44
  "type": "object",
26
45
  "required": ["kind", "seqId", "ts", "event"],
27
46
  "properties": {
28
- "kind": { "const": "completed" },
29
- "seqId": { "type": "integer", "minimum": 1 },
30
- "ts": { "type": "string", "format": "date-time" },
31
- "event": { "type": "string", "minLength": 1 },
32
- "listener": { "type": "string", "minLength": 1 }
47
+ "kind": {
48
+ "const": "completed"
49
+ },
50
+ "seqId": {
51
+ "type": "integer",
52
+ "minimum": 1
53
+ },
54
+ "ts": {
55
+ "type": "string",
56
+ "format": "date-time"
57
+ },
58
+ "event": {
59
+ "type": "string",
60
+ "minLength": 1
61
+ },
62
+ "listener": {
63
+ "type": "string",
64
+ "minLength": 1
65
+ }
33
66
  },
34
67
  "additionalProperties": false
35
68
  },
@@ -37,18 +70,39 @@
37
70
  "type": "object",
38
71
  "required": ["kind", "seqId", "ts", "event", "listener", "error"],
39
72
  "properties": {
40
- "kind": { "const": "failed" },
41
- "seqId": { "type": "integer", "minimum": 1 },
42
- "ts": { "type": "string", "format": "date-time" },
43
- "event": { "type": "string", "minLength": 1 },
44
- "listener": { "type": "string", "minLength": 1 },
73
+ "kind": {
74
+ "const": "failed"
75
+ },
76
+ "seqId": {
77
+ "type": "integer",
78
+ "minimum": 1
79
+ },
80
+ "ts": {
81
+ "type": "string",
82
+ "format": "date-time"
83
+ },
84
+ "event": {
85
+ "type": "string",
86
+ "minLength": 1
87
+ },
88
+ "listener": {
89
+ "type": "string",
90
+ "minLength": 1
91
+ },
45
92
  "error": {
46
93
  "type": "object",
47
94
  "required": ["name", "message"],
48
95
  "properties": {
49
- "name": { "type": "string", "minLength": 1 },
50
- "message": { "type": "string" },
51
- "stack": { "type": "string" }
96
+ "name": {
97
+ "type": "string",
98
+ "minLength": 1
99
+ },
100
+ "message": {
101
+ "type": "string"
102
+ },
103
+ "stack": {
104
+ "type": "string"
105
+ }
52
106
  },
53
107
  "additionalProperties": false
54
108
  }
@@ -73,10 +73,10 @@
73
73
  },
74
74
  "gates": {
75
75
  "type": "object",
76
- "description": "Outcome of each close gate this invocation was responsible for. A gate the run skipped (--skip-validation, or a phase it never reached) reports \"skipped\" rather than being omitted, so a missing gate is never mistaken for a passing one.",
76
+ "description": "Outcome of each close gate this invocation was responsible for. A gate the run skipped (--skip-validation, or a phase it never reached) reports \"skipped\" rather than being omitted, so a missing gate is never mistaken for a passing one. \"overridden\" is distinct from both: the gate ran, it FAILED, and an operator authorized delivery anyway via --override-review-block with a recorded reason. Reporting that as \"passed\" would erase the one fact a reader of this envelope most needs, and reporting it as \"skipped\" would claim the gate never ran.",
77
77
  "additionalProperties": {
78
78
  "type": "string",
79
- "enum": ["passed", "failed", "skipped"]
79
+ "enum": ["passed", "failed", "skipped", "overridden"]
80
80
  }
81
81
  },
82
82
  "tail": {
@@ -5,35 +5,22 @@ invoked indirectly by `npm run …`, slash-command workflows
5
5
  (`.agents/workflows/*.md`), or Husky / GitHub Actions hooks; you rarely
6
6
  need to call them by hand.
7
7
 
8
- This file is **not** an exhaustive index of the ~90 top-level entrypoints.
9
- It documents the **operator-facing scripts** that operators may want to
10
- run by hand and that are **not** wired into the standard quality / CI
11
- surface. For everything else, search `package.json` scripts and
12
- `.agents/workflows/` first.
13
-
14
- ## Operator Scripts
15
-
16
- These scripts are kept in the distributed product but are intentionally
17
- not invoked by `npm test`, `npm run verify`, CI, or any Husky hook. They
18
- are optional operator tools; run them by hand when you need them.
19
-
20
- ### `validate-docs-freshness.js`
21
-
22
- **Purpose.** Per-Epic documentation freshness gate. For each doc in
23
- `delivery.docsFreshness.paths` + `project.docsContextFiles`, asserts
24
- that the file was meaningfully updated during this Epic's lifecycle
25
- (commit message references `#<epicId>` or the file body does).
26
-
27
- **When to run.** Optional. Useful as a pre-merge spot check when an
28
- Epic should have produced documentation updates; the standard
29
- `/deliver` flow does **not** invoke this gate today.
30
-
31
- **Usage.**
32
-
33
- ```bash
34
- node .agents/scripts/validate-docs-freshness.js --epic <id> \
35
- [--base main] [--docs <comma-separated>] [--json]
36
- ```
8
+ This file is **not** an exhaustive index of the ~90 top-level entrypoints
9
+ it is the orientation pointer for the directory. Every script documents
10
+ its own flags under `--help`, and each is reachable from a real caller:
11
+ search `package.json` scripts, `.agents/workflows/`, and the Husky /
12
+ GitHub Actions surfaces first. `check-knip-entries.js` derives that
13
+ caller set mechanically, so a CLI no invoker names is dead, not
14
+ operator-only.
15
+
16
+ It reads the entry list from whatever configuration knip itself would
17
+ load `knip.json`, `knip.jsonc`, `.knip.json(c)`, `knip.ts`, `knip.js`,
18
+ `knip.config.ts`, `knip.config.js`, or `package.json#knip` evaluating
19
+ TS/JS modules rather than parsing them, and counting entries declared
20
+ per-workspace as well as at the top level. A project with no knip
21
+ configuration at all exits 0 with a skip line, so the gate is safe to
22
+ wire everywhere; a configuration that exists but cannot be resolved
23
+ still exits 2.
37
24
 
38
25
  ## See Also
39
26
 
@@ -11,63 +11,56 @@
11
11
  * had no test so it silently drifted when the two helper signatures moved, and
12
12
  * it could not be invoked or dry-run independently.
13
13
  *
14
- * This script runs the two Epic #1386 quality-gate installs in order against
15
- * the consumer repo root:
14
+ * The script runs one install against the consumer repo root:
15
+ * `applyQualityBootstrap` — copies the code-quality-guardrails helper,
16
+ * installs the `.husky/pre-commit` quality:preview line, backfills the
17
+ * `quality:preview` / `quality:watch` npm scripts, seeds the
18
+ * `delivery.quality.{codingGuardrails,autoRefresh}` defaults, and prunes a
19
+ * committed pre-v2 `baselines/epic/` tree.
16
20
  *
17
- * 1. `applyQualityBootstrap` copies the code-quality-guardrails helper,
18
- * installs the `.husky/pre-commit` quality:preview line, backfills the
19
- * `quality:preview` / `quality:watch` npm scripts, and seeds the
20
- * `delivery.quality.{codingGuardrails,autoRefresh}` defaults.
21
- * 2. `migrateBaselinesLayout` relocates per-Epic baseline snapshots into
22
- * the `temp/epic/<id>/baselines/` namespace.
21
+ * Story #5007 retired the second step. `migrateBaselinesLayout` relocated
22
+ * per-Epic ratchet snapshots into `temp/epic/<id>/baselines/` on the contract
23
+ * that `/deliver` reaps that namespace on merge — a mechanism the Story-only
24
+ * v2 model deleted, so the migration moved dead data into a namespace no code
25
+ * path writes, reads, or reaps. Its one residual hygiene value (getting the
26
+ * committed `baselines/epic/` tree out of version control) survives as
27
+ * `pruneLegacyEpicBaselines`, the quality install's fifth step.
23
28
  *
24
- * Both helpers are idempotent by contract — a second run reports `no-change`
25
- * on every install path — so this wrapper is safe to re-run. It prints the
26
- * **same JSON result shape** the heredoc did: `{ quality, baselines }` to
27
- * stdout, so any tooling that parsed the old output keeps working.
29
+ * The helper is idempotent by contract — a second run reports `no-change` /
30
+ * `already-present` / `absent` on every install path — so this wrapper is safe
31
+ * to re-run. It prints `{ quality }` JSON to stdout.
28
32
  *
29
33
  * The effectful work is a thin pure function (`applyBootstrapAndMigration`)
30
- * that takes the two helpers and the project root, so the test suite can
31
- * drive it against a tmp directory without spawning a child process. The CLI
32
- * wrapper wires the real helpers and `process.cwd()`.
34
+ * that takes the helper and the project root, so the test suite can drive it
35
+ * against a tmp directory without spawning a child process. The CLI wrapper
36
+ * wires the real helper and `process.cwd()`.
33
37
  */
34
38
 
35
- import path from 'node:path';
36
- import { migrateBaselinesLayout } from './lib/bootstrap/baselines-layout-migration.js';
37
39
  import { applyQualityBootstrap } from './lib/bootstrap/quality-bootstrap.js';
38
40
  import { runAsCli } from './lib/cli-utils.js';
39
41
 
40
42
  /**
41
- * Run the quality-bootstrap install and the baselines-layout migration
42
- * against `projectRoot`, returning the combined `{ quality, baselines }`
43
- * envelope. Pure relative to its injected helpers: the default helpers touch
44
- * the filesystem under `projectRoot`, but tests can pass stubs to exercise
45
- * the composition in isolation.
43
+ * Run the quality-bootstrap install against `projectRoot`, returning the
44
+ * `{ quality }` envelope. Pure relative to its injected helper: the default
45
+ * helper touches the filesystem under `projectRoot`, but tests can pass a stub
46
+ * to exercise the composition in isolation.
46
47
  *
47
48
  * @param {object} options
48
49
  * @param {string} options.projectRoot Absolute consumer repo root.
49
50
  * @param {typeof applyQualityBootstrap} [options.applyQualityBootstrap]
50
- * @param {typeof migrateBaselinesLayout} [options.migrateBaselinesLayout]
51
- * @returns {{ quality: object, baselines: object }}
51
+ * @returns {{ quality: object }}
52
52
  */
53
53
  export function applyBootstrapAndMigration({
54
54
  projectRoot,
55
55
  applyQualityBootstrap: applyQuality = applyQualityBootstrap,
56
- migrateBaselinesLayout: migrateBaselines = migrateBaselinesLayout,
57
56
  }) {
58
- const quality = applyQuality({ projectRoot });
59
- const baselines = migrateBaselines({
60
- baselinesDir: path.join(projectRoot, 'baselines'),
61
- repoRoot: projectRoot,
62
- });
63
- return { quality, baselines };
57
+ return { quality: applyQuality({ projectRoot }) };
64
58
  }
65
59
 
66
60
  async function main() {
67
61
  const projectRoot = process.cwd();
68
62
  const result = applyBootstrapAndMigration({ projectRoot });
69
- // Mirror the retired heredoc's output: pretty-printed `{ quality, baselines }`
70
- // to stdout. Use process.stdout.write (not console.log) per the no-console
63
+ // Use process.stdout.write (not console.log) per the no-console
71
64
  // enforcement boundary.
72
65
  process.stdout.write(`${JSON.stringify(result)}\n`);
73
66
  return 0;
@@ -79,7 +72,7 @@ runAsCli(import.meta.url, main, {
79
72
  usage: {
80
73
  invocation: 'node .agents/scripts/apply-quality-bootstrap.js',
81
74
  summary:
82
- 'Install the quality-gate surface into the consumer repo (guardrails helper, pre-commit line, npm scripts, config defaults) and migrate the baselines layout. Idempotent; prints { quality, baselines } JSON to stdout.',
75
+ 'Install the quality-gate surface into the consumer repo (guardrails helper, pre-commit line, npm scripts, config defaults, legacy baselines/epic prune). Idempotent; prints { quality } JSON to stdout.',
83
76
  flags: [],
84
77
  },
85
78
  });
@@ -45,6 +45,7 @@ import {
45
45
  } from './lib/audit-to-stories/ledger.js';
46
46
  import { parseAuditReports } from './lib/audit-to-stories/parse-audit-md.js';
47
47
  import { buildPlanSeedMarkdown } from './lib/audit-to-stories/seed-from-findings.js';
48
+ import { wireAuditStoryEdges } from './lib/audit-to-stories/wire-dependencies.js';
48
49
  import { runAsCli } from './lib/cli-utils.js';
49
50
  import { searchSemanticCandidates } from './lib/findings/semantic-issue-search.js';
50
51
  import { SEVERITIES, SEVERITY_RANK } from './lib/findings/severity.js';
@@ -583,6 +584,81 @@ function buildAndGateStories(eligible, edges) {
583
584
  return built;
584
585
  }
585
586
 
587
+ /**
588
+ * The `--wire-edges` pass: hand the opened issue numbers back so the cohort's
589
+ * detected group edges become declared ordering (Story #5044).
590
+ *
591
+ * This is the second half of the two-pass crossing `--emit-stories` starts. The
592
+ * host opens one Issue per group from the emitted drafts, then replays the
593
+ * `groupKey → issueNumber` map here; each Story whose blockers now exist is
594
+ * re-rendered with a canonical `blocked by #N` footer and the same edges are
595
+ * mirrored as native `blocked_by` relations.
596
+ *
597
+ * @param {object} params
598
+ * @param {object} params.plan A `--scan` plan envelope.
599
+ * @param {Record<string, number>} params.issueByGroupKey
600
+ * @param {object} [deps]
601
+ * @param {Function} [deps.loadProviderImpl]
602
+ * @param {Function} [deps.wireImpl]
603
+ * @returns {Promise<object>} the wiring summary.
604
+ */
605
+ async function wireEdges({ plan, issueByGroupKey }, deps = {}) {
606
+ const { loadProviderImpl = loadProvider, wireImpl = wireAuditStoryEdges } =
607
+ deps;
608
+ const groups = (plan.classifications ?? [])
609
+ .filter((c) => c.action === 'create')
610
+ .map((c) => c.group);
611
+ const provider = await loadProviderImpl();
612
+ if (typeof provider?.updateTicket !== 'function') {
613
+ throw new Error(
614
+ '--wire-edges needs a provider exposing updateTicket to rewrite the ' +
615
+ 'Story bodies with their `blocked by #N` footers. Configure ' +
616
+ 'github.owner/repo (and auth), or wire the edges by hand.',
617
+ );
618
+ }
619
+ return wireImpl({
620
+ groups,
621
+ edges: plan.edges ?? [],
622
+ issueByGroupKey,
623
+ provider,
624
+ updateBody: (issueNumber, body) =>
625
+ provider.updateTicket(issueNumber, { body }),
626
+ });
627
+ }
628
+
629
+ /**
630
+ * Parse the `--ids` argument: a JSON object mapping group key → issue number,
631
+ * or a path to a file containing one.
632
+ *
633
+ * @param {string|undefined} raw
634
+ * @returns {Record<string, number>}
635
+ */
636
+ function parseIssueMap(raw) {
637
+ if (!raw) {
638
+ throw new Error(
639
+ '--wire-edges requires --ids \'{"<groupKey>": <issueNumber>, ...}\' ' +
640
+ '(or a path to a JSON file with that shape) — the issue numbers the ' +
641
+ 'create pass opened. Without them there is nothing to resolve the ' +
642
+ 'group edges against.',
643
+ );
644
+ }
645
+ const text = raw.trimStart().startsWith('{')
646
+ ? raw
647
+ : fs.readFileSync(raw, 'utf8');
648
+ const parsed = JSON.parse(text);
649
+ const out = {};
650
+ for (const [key, value] of Object.entries(parsed)) {
651
+ const n = Number(value);
652
+ if (!Number.isInteger(n) || n <= 0) {
653
+ throw new Error(
654
+ `--ids: "${key}" maps to ${JSON.stringify(value)}, which is not a positive issue number.`,
655
+ );
656
+ }
657
+ out[key] = n;
658
+ }
659
+ return out;
660
+ }
661
+
586
662
  function persist(text, outPath) {
587
663
  if (!outPath) {
588
664
  process.stdout.write(text);
@@ -604,6 +680,8 @@ export const __testing = {
604
680
  resolveSeverityFloor,
605
681
  reconcileScanLedger,
606
682
  issueStatesFromClassifications,
683
+ wireEdges,
684
+ parseIssueMap,
607
685
  };
608
686
 
609
687
  /**
@@ -637,6 +715,8 @@ export async function runAuditToStories(
637
715
  loadPlanImpl = loadPlan,
638
716
  buildAndGateStoriesImpl = buildAndGateStories,
639
717
  buildPlanSeedMarkdownImpl = buildPlanSeedMarkdown,
718
+ wireEdgesImpl = wireEdges,
719
+ parseIssueMapImpl = parseIssueMap,
640
720
  persistImpl = persist,
641
721
  stdout = process.stdout,
642
722
  } = deps;
@@ -648,6 +728,8 @@ export async function runAuditToStories(
648
728
  'dry-run': { type: 'boolean' },
649
729
  'emit-plan-seed': { type: 'boolean' },
650
730
  'emit-stories': { type: 'boolean' },
731
+ 'wire-edges': { type: 'boolean' },
732
+ ids: { type: 'string' },
651
733
  glob: { type: 'string' },
652
734
  severity: { type: 'string' },
653
735
  ledger: { type: 'string' },
@@ -659,64 +741,93 @@ export async function runAuditToStories(
659
741
  strict: false,
660
742
  });
661
743
 
662
- if (values.auto) {
663
- const { summary } = await runAutoImpl({
664
- glob: values.glob,
665
- severity: values.severity,
666
- dryRun: values['dry-run'],
667
- useProvider: !values['no-provider'],
668
- ledgerPath: values.ledger,
669
- });
670
- persistImpl(JSON.stringify(summary, null, 2), values.out);
671
- if (!values.out) stdout.write('\n');
672
- return;
673
- }
674
-
675
- if (values.scan) {
676
- const plan = await buildPlanImpl({
744
+ const json = (value) => JSON.stringify(value, null, 2);
745
+
746
+ const runAutoSummary = async () =>
747
+ (
748
+ await runAutoImpl({
749
+ glob: values.glob,
750
+ severity: values.severity,
751
+ dryRun: values['dry-run'],
752
+ useProvider: !values['no-provider'],
753
+ ledgerPath: values.ledger,
754
+ })
755
+ ).summary;
756
+
757
+ const scanPlan = () =>
758
+ buildPlanImpl({
677
759
  glob: values.glob,
678
760
  severity: values.severity,
679
761
  useProvider: !values['no-provider'],
680
762
  });
681
- const out = JSON.stringify(plan, null, 2);
682
- persistImpl(out, values.out);
683
- if (!values.out) stdout.write('\n');
684
- return;
685
- }
686
763
 
687
- if (values['emit-plan-seed']) {
764
+ const seedMarkdown = () => {
688
765
  const plan = loadPlanImpl(values.plan);
689
- const md = buildPlanSeedMarkdownImpl({
766
+ return buildPlanSeedMarkdownImpl({
690
767
  groups: plan.groups ?? [],
691
768
  findings: plan.findings ?? [],
692
769
  sourceReports: plan.sourceReports ?? [],
693
770
  });
694
- persistImpl(md, values.out);
695
- return;
696
- }
771
+ };
697
772
 
698
- if (values['emit-stories']) {
773
+ const emittedStories = () => {
699
774
  const plan = loadPlanImpl(values.plan);
700
775
  const eligible = (plan.classifications ?? [])
701
776
  .filter((c) => c.action === 'create')
702
777
  .map((c) => c.group);
703
778
  const built = buildAndGateStoriesImpl(eligible, plan.edges ?? []);
704
- const out = values.json
705
- ? JSON.stringify(built, null, 2)
706
- : built
707
- .map(
708
- (s, i) =>
709
- `--- story ${i + 1} ---\nTitle: ${s.title}\nLabels: ${s.labels.join(', ')}\n\n${s.body}\n`,
710
- )
711
- .join('\n');
712
- persistImpl(out, values.out);
713
- if (!values.out) stdout.write('\n');
714
- return;
779
+ return values.json ? json(built) : renderStoryDrafts(built);
780
+ };
781
+
782
+ const wiredEdges = () =>
783
+ wireEdgesImpl({
784
+ plan: loadPlanImpl(values.plan),
785
+ issueByGroupKey: parseIssueMapImpl(values.ids),
786
+ });
787
+
788
+ // One table, not a chain of `if (values.X) { …; return; }`. Each entry
789
+ // renders its sub-command's output; persisting it — and the stdout newline a
790
+ // piped run needs — happens once, below. The chain restated that tail in
791
+ // every arm, so each new sub-command paid for it twice: once in the branch
792
+ // and once in the complexity budget.
793
+ const subcommands = [
794
+ ['auto', async () => json(await runAutoSummary()), true],
795
+ ['scan', async () => json(await scanPlan()), true],
796
+ ['emit-plan-seed', () => seedMarkdown(), false],
797
+ ['emit-stories', () => emittedStories(), true],
798
+ ['wire-edges', async () => json(await wiredEdges()), true],
799
+ ];
800
+
801
+ const entry = subcommands.find(([flag]) => values[flag]);
802
+ if (!entry) {
803
+ throw new Error(
804
+ 'Usage: node audit-to-stories.js (--scan | --emit-plan-seed | --emit-stories | --wire-edges) [options]',
805
+ );
715
806
  }
807
+ const [, render, newlineOnStdout] = entry;
808
+ persistImpl(await render(), values.out);
809
+ if (newlineOnStdout && !values.out) stdout.write('\n');
810
+ }
716
811
 
717
- throw new Error(
718
- 'Usage: node audit-to-stories.js (--scan | --emit-plan-seed | --emit-stories) [options]',
719
- );
812
+ /**
813
+ * Render the Story drafts as the human-readable `--emit-stories` transcript
814
+ * (the `--json` form is the machine one). `dependsOn` is surfaced because the
815
+ * group edges no longer ride the body at emit time — the blockers have no issue
816
+ * numbers yet — so this is where a human driving the create pass by hand sees
817
+ * the ordering they will replay through `--wire-edges` (Story #5044).
818
+ *
819
+ * @param {Array<{ title: string, labels: string[], body: string, groupKey?: string, dependsOn?: string[] }>} built
820
+ * @returns {string}
821
+ */
822
+ function renderStoryDrafts(built) {
823
+ return built
824
+ .map((s, i) => {
825
+ const deps = (s.dependsOn ?? []).length
826
+ ? `\nDepends on group(s): ${s.dependsOn.join(', ')}`
827
+ : '';
828
+ return `--- story ${i + 1} ---\nTitle: ${s.title}\nLabels: ${s.labels.join(', ')}\nGroup key: ${s.groupKey}${deps}\n\n${s.body}\n`;
829
+ })
830
+ .join('\n');
720
831
  }
721
832
 
722
833
  async function main() {
@@ -727,7 +838,7 @@ runAsCli(import.meta.url, main, {
727
838
  source: 'audit-to-stories',
728
839
  usage: {
729
840
  invocation:
730
- 'node .agents/scripts/audit-to-stories.js (--scan | --auto | --emit-plan-seed | --emit-stories) [options]',
841
+ 'node .agents/scripts/audit-to-stories.js (--scan | --auto | --emit-plan-seed | --emit-stories | --wire-edges) [options]',
731
842
  summary:
732
843
  'Turn audit-lens findings under temp/audits/ into a dedup-checked plan seed or standalone Stories.',
733
844
  flags: [
@@ -735,6 +846,14 @@ runAsCli(import.meta.url, main, {
735
846
  ['--auto', 'Run the full scan → file pipeline and print the summary.'],
736
847
  ['--emit-plan-seed', 'Emit a /plan --seed-file document.'],
737
848
  ['--emit-stories', 'Emit the Story drafts as JSON.'],
849
+ [
850
+ '--wire-edges',
851
+ 'Second pass: resolve the detected group edges to blocked by #N footers plus native blocked_by relations. Needs --plan and --ids.',
852
+ ],
853
+ [
854
+ '--ids <json|path>',
855
+ 'Group key → opened issue number, as JSON or a path to a JSON file. Required by --wire-edges.',
856
+ ],
738
857
  ['--glob <pattern>', 'Override the audit-results glob.'],
739
858
  ['--severity <level>', 'Lowest severity to include (high|medium|low).'],
740
859
  ['--ledger <path>', 'Path to the dedup ledger.'],