mandrel 2.0.0 → 2.2.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 (323) hide show
  1. package/.agents/README.md +59 -28
  2. package/.agents/agents/acceptance-critic.md +20 -9
  3. package/.agents/agents/story-worker.md +45 -48
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +60 -46
  6. package/.agents/docs/agentrc-reference.json +8 -13
  7. package/.agents/docs/configuration.md +33 -57
  8. package/.agents/docs/execution-reference.md +39 -10
  9. package/.agents/docs/quality-gates.md +17 -19
  10. package/.agents/docs/workflows.md +6 -6
  11. package/.agents/instructions.md +64 -79
  12. package/.agents/rules/ci-remediation.md +3 -3
  13. package/.agents/rules/gherkin-standards.md +10 -0
  14. package/.agents/rules/git-conventions-reference.md +42 -51
  15. package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
  16. package/.agents/schemas/agentrc.schema.json +35 -46
  17. package/.agents/schemas/audit-rules.json +59 -1
  18. package/.agents/schemas/audit-rules.schema.json +33 -1
  19. package/.agents/schemas/lifecycle/README.md +1 -2
  20. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  21. package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
  22. package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
  23. package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
  24. package/.agents/schemas/signal-event.schema.json +3 -3
  25. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  26. package/.agents/schemas/validation-evidence.schema.json +1 -1
  27. package/.agents/scripts/acceptance-eval.js +24 -68
  28. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  29. package/.agents/scripts/bootstrap.js +3 -3
  30. package/.agents/scripts/check-dead-exports.js +43 -104
  31. package/.agents/scripts/check-doc-links.js +2 -2
  32. package/.agents/scripts/check-lifecycle-lint.js +1 -1
  33. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  34. package/.agents/scripts/deliver-recover.js +122 -0
  35. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  36. package/.agents/scripts/evidence-gate.js +20 -50
  37. package/.agents/scripts/generate-skills-index.js +17 -1
  38. package/.agents/scripts/generate-workflows-doc.js +4 -4
  39. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  40. package/.agents/scripts/lib/audit-suite/selector.js +323 -23
  41. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
  42. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  43. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  44. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  45. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  46. package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
  47. package/.agents/scripts/lib/checks/index.js +1 -1
  48. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  49. package/.agents/scripts/lib/checks/state.js +17 -248
  50. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
  51. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  52. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  53. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  54. package/.agents/scripts/lib/cli-args.js +23 -2
  55. package/.agents/scripts/lib/close-validation/gates.js +13 -13
  56. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  57. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  58. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  59. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  60. package/.agents/scripts/lib/config/acceptance-eval.js +2 -2
  61. package/.agents/scripts/lib/config/delivery-routing.js +7 -6
  62. package/.agents/scripts/lib/config/explain.js +10 -16
  63. package/.agents/scripts/lib/config/github.js +7 -5
  64. package/.agents/scripts/lib/config/limits.js +15 -25
  65. package/.agents/scripts/lib/config/quality.js +11 -14
  66. package/.agents/scripts/lib/config/runners.js +8 -21
  67. package/.agents/scripts/lib/config/temp-paths.js +18 -56
  68. package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
  69. package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
  70. package/.agents/scripts/lib/config-settings-schema.js +48 -22
  71. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  72. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  73. package/.agents/scripts/lib/duplicate-search.js +38 -7
  74. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  75. package/.agents/scripts/lib/format-generated-json.js +97 -0
  76. package/.agents/scripts/lib/framework-version.js +19 -189
  77. package/.agents/scripts/lib/gh-exec.js +8 -0
  78. package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
  79. package/.agents/scripts/lib/git-utils.js +0 -14
  80. package/.agents/scripts/lib/json-utils.js +1 -2
  81. package/.agents/scripts/lib/label-constants.js +0 -15
  82. package/.agents/scripts/lib/label-taxonomy.js +1 -12
  83. package/.agents/scripts/lib/observability/active-story-env.js +42 -163
  84. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  85. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  86. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  87. package/.agents/scripts/lib/observability/source-classifier.js +2 -2
  88. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  89. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  90. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  91. package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
  92. package/.agents/scripts/lib/orchestration/change-set.js +103 -0
  93. package/.agents/scripts/lib/orchestration/code-review.js +70 -191
  94. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
  95. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  96. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  97. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  98. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  99. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  100. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
  101. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  102. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  103. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
  104. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  105. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  106. package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
  107. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  108. package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
  109. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
  110. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
  111. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  112. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
  113. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
  114. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  115. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
  116. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
  117. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
  118. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  119. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
  120. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
  121. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
  122. package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
  123. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  124. package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
  125. package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
  126. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  127. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  128. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  129. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  130. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  131. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  132. package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
  133. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
  134. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  135. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  136. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +4 -13
  137. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  138. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
  139. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  140. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  141. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
  142. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  143. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  144. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
  145. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  146. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  147. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
  148. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
  149. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
  150. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  151. package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
  152. package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
  153. package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
  154. package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
  155. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  156. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
  157. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
  158. package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
  159. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
  160. package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
  161. package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
  162. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  163. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  164. package/.agents/scripts/lib/planning-corpus.js +12 -286
  165. package/.agents/scripts/lib/preflight-runner.js +2 -2
  166. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  167. package/.agents/scripts/lib/signals/index.js +4 -17
  168. package/.agents/scripts/lib/signals/read.js +35 -35
  169. package/.agents/scripts/lib/signals/schema.js +8 -11
  170. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  171. package/.agents/scripts/lib/signals/write.js +0 -1
  172. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  173. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  174. package/.agents/scripts/lib/story-adjacency.js +8 -7
  175. package/.agents/scripts/lib/story-body/story-body.js +81 -13
  176. package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
  177. package/.agents/scripts/lib/test-env.js +14 -1
  178. package/.agents/scripts/lib/test-tiers.js +0 -3
  179. package/.agents/scripts/lib/ticket-body-sections.js +0 -14
  180. package/.agents/scripts/lib/validation-evidence.js +31 -59
  181. package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
  182. package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
  183. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  184. package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
  185. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  186. package/.agents/scripts/plan-context.js +38 -7
  187. package/.agents/scripts/plan-critics.js +203 -0
  188. package/.agents/scripts/plan-persist.js +145 -35
  189. package/.agents/scripts/plan-run-epilogue.js +83 -38
  190. package/.agents/scripts/post-structured-comment.js +0 -38
  191. package/.agents/scripts/pr-watch-with-update.js +43 -22
  192. package/.agents/scripts/providers/github/compose.js +0 -1
  193. package/.agents/scripts/providers/github/errors.js +0 -19
  194. package/.agents/scripts/providers/github/issues.js +1 -11
  195. package/.agents/scripts/providers/github/mappers.js +5 -0
  196. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  197. package/.agents/scripts/providers/github/tickets.js +33 -153
  198. package/.agents/scripts/providers/github.js +17 -6
  199. package/.agents/scripts/quality-preview.js +13 -6
  200. package/.agents/scripts/resolve-stories.js +236 -0
  201. package/.agents/scripts/run-coverage.js +4 -1
  202. package/.agents/scripts/run-lint.js +2 -2
  203. package/.agents/scripts/run-verify.js +31 -2
  204. package/.agents/scripts/signals-view.js +9 -10
  205. package/.agents/scripts/single-story-close.js +173 -18
  206. package/.agents/scripts/single-story-confirm-merge.js +288 -15
  207. package/.agents/scripts/single-story-init.js +6 -10
  208. package/.agents/scripts/stories-wave-tick.js +380 -53
  209. package/.agents/scripts/story-plan.js +3 -3
  210. package/.agents/scripts/update-ticket-state.js +8 -50
  211. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  212. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  213. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  214. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  215. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  216. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  217. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  218. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  219. package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
  220. package/.agents/skills/core/scope-triage/SKILL.md +3 -0
  221. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  222. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  223. package/.agents/skills/skills.index.json +2 -12
  224. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  225. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  226. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  227. package/.agents/workflows/audit-architecture.md +3 -4
  228. package/.agents/workflows/audit-clean-code.md +4 -4
  229. package/.agents/workflows/audit-documentation.md +4 -5
  230. package/.agents/workflows/audit-lighthouse.md +8 -0
  231. package/.agents/workflows/audit-navigability.md +10 -0
  232. package/.agents/workflows/audit-performance.md +2 -3
  233. package/.agents/workflows/audit-quality.md +8 -9
  234. package/.agents/workflows/audit-security.md +1 -2
  235. package/.agents/workflows/audit-seo.md +10 -0
  236. package/.agents/workflows/audit-ux-ui.md +7 -0
  237. package/.agents/workflows/deliver.md +133 -45
  238. package/.agents/workflows/git-cleanup.md +2 -2
  239. package/.agents/workflows/git-deliver.md +1 -1
  240. package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
  241. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
  242. package/.agents/workflows/helpers/code-review.md +14 -12
  243. package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
  244. package/.agents/workflows/helpers/deliver-story.md +209 -118
  245. package/.agents/workflows/helpers/parallel-tooling.md +2 -2
  246. package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
  247. package/.agents/workflows/plan.md +239 -19
  248. package/.agents/workflows/qa-assist.md +6 -6
  249. package/.agents/workflows/qa-explore.md +3 -3
  250. package/.agents/workflows/qa-run.md +1 -5
  251. package/bin/mandrel.js +12 -1
  252. package/docs/CHANGELOG.md +62 -0
  253. package/lib/cli/registry.js +262 -19
  254. package/lib/cli/sync-agents.js +157 -0
  255. package/lib/cli/sync-commands.js +115 -6
  256. package/lib/cli/sync.js +168 -6
  257. package/lib/cli/update.js +105 -8
  258. package/lib/cli/version-helpers.js +131 -0
  259. package/lib/migrations/README.md +7 -5
  260. package/lib/migrations/index.js +17 -9
  261. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  262. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  263. package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
  264. package/package.json +2 -2
  265. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  266. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  267. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  268. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  269. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  270. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  271. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  272. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  273. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  274. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  275. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  276. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  277. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  278. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  279. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  280. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  281. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  282. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  283. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  284. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  285. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  286. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  287. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  288. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  289. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  290. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  291. package/.agents/schemas/risk-verdict.schema.json +0 -53
  292. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  293. package/.agents/scripts/analyze-execution.js +0 -444
  294. package/.agents/scripts/check-prepush-recovery.js +0 -90
  295. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  296. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  297. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
  298. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  299. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  300. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  301. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
  302. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
  303. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  304. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  305. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  306. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  307. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  308. package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
  309. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  310. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  311. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  312. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
  313. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  314. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  315. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  316. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
  317. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  318. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
  319. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  320. package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
  321. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
  322. package/.agents/scripts/resolve-plan-run.js +0 -117
  323. package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
@@ -366,14 +366,19 @@ function resolveCoverageGate(userBlock) {
366
366
  }
367
367
 
368
368
  /**
369
- * Framework defaults for `delivery.quality.codingGuardrails`. The legacy
370
- * field name `miDropRefactor` was renamed to `miDropMustRefactor` in
371
- * Story 1 to avoid semantic collision with `autoRefresh.miDropCap`.
369
+ * Framework defaults for `delivery.quality.codingGuardrails`.
370
+ *
371
+ * `miDropMustRefactor` was retired in Story #4531: schema-validated,
372
+ * defaulted, and resolved, but never consumed — the gate it named
373
+ * (`quality-preview.js`'s `computeExitCode`) short-circuits on `miExit`
374
+ * (derived from the already-consumed `gates.maintainability.tolerance`)
375
+ * before this value is ever read. `maintainability.tolerance` is the one
376
+ * documented MI-drop control now; see `lib/migrations/index.js` for the
377
+ * consumer-config migration that strips a leftover key on upgrade.
372
378
  */
373
379
  export const CODING_GUARDRAILS_DEFAULTS = Object.freeze({
374
380
  cyclomaticFlag: 8,
375
381
  cyclomaticMustFix: 12,
376
- miDropMustRefactor: 1.5,
377
382
  requireSiblingTest: false,
378
383
  });
379
384
 
@@ -393,8 +398,6 @@ export function resolveCodingGuardrails(userBlock) {
393
398
  cyclomaticFlag: userBlock.cyclomaticFlag ?? defaults.cyclomaticFlag,
394
399
  cyclomaticMustFix:
395
400
  userBlock.cyclomaticMustFix ?? defaults.cyclomaticMustFix,
396
- miDropMustRefactor:
397
- userBlock.miDropMustRefactor ?? defaults.miDropMustRefactor,
398
401
  requireSiblingTest:
399
402
  typeof userBlock.requireSiblingTest === 'boolean'
400
403
  ? userBlock.requireSiblingTest
@@ -402,9 +405,10 @@ export function resolveCodingGuardrails(userBlock) {
402
405
  };
403
406
  }
404
407
 
408
+ // autoRefresh.miDropCap was retired alongside codingGuardrails.
409
+ // miDropMustRefactor in Story #4531 — same unconsumed-knob shape, same fix.
405
410
  const AUTO_REFRESH_DEFAULTS = Object.freeze({
406
411
  enabled: true,
407
- miDropCap: 1.5,
408
412
  crapJumpCap: 5,
409
413
  scope: 'diff',
410
414
  });
@@ -416,7 +420,6 @@ function resolveAutoRefresh(userBlock) {
416
420
  if (userBlock == null || typeof userBlock !== 'object') {
417
421
  return {
418
422
  enabled: defaults.enabled,
419
- miDropCap: defaults.miDropCap,
420
423
  crapJumpCap: defaults.crapJumpCap,
421
424
  scope: defaults.scope,
422
425
  };
@@ -429,12 +432,6 @@ function resolveAutoRefresh(userBlock) {
429
432
  typeof userBlock.enabled === 'boolean'
430
433
  ? userBlock.enabled
431
434
  : defaults.enabled,
432
- miDropCap:
433
- typeof userBlock.miDropCap === 'number' &&
434
- Number.isFinite(userBlock.miDropCap) &&
435
- userBlock.miDropCap >= 0
436
- ? userBlock.miDropCap
437
- : defaults.miDropCap,
438
435
  crapJumpCap:
439
436
  typeof userBlock.crapJumpCap === 'number' &&
440
437
  Number.isFinite(userBlock.crapJumpCap) &&
@@ -3,18 +3,11 @@
3
3
  *
4
4
  * Post-reshape, only `delivery.deliverRunner` and `delivery.codeReview` are
5
5
  * configurable via this accessor; everything else lives in framework-internal
6
- * constants exported alongside (`DEFAULT_STORY_MERGE_RETRY`,
7
- * `DEFAULT_DECOMPOSER`). `delivery.epicAudit` was removed on v2 (Story-only
8
- * delivery — no epic-audit runner; remediation policy lives on
9
- * `delivery.codeReview`).
6
+ * constants exported alongside (`DEFAULT_DECOMPOSER`).
7
+ * `delivery.epicAudit` was removed on v2 (Story-only delivery — no
8
+ * epic-audit runner; remediation policy lives on `delivery.codeReview`).
10
9
  */
11
10
 
12
- /** Hardcoded story-merge retry policy (was `orchestration.runners.storyMergeRetry`). */
13
- export const DEFAULT_STORY_MERGE_RETRY = Object.freeze({
14
- maxAttempts: 3,
15
- backoffMs: Object.freeze([250, 500, 1000]),
16
- });
17
-
18
11
  /** Hardcoded decomposer concurrency cap (was `orchestration.runners.decomposer.concurrencyCap`). */
19
12
  export const DEFAULT_DECOMPOSER = Object.freeze({
20
13
  concurrencyCap: 3,
@@ -33,14 +26,13 @@ export const DEFAULT_DECOMPOSER = Object.freeze({
33
26
  * reduces wall-clock time where dependencies allow. See `deliver.md` and
34
27
  * `agentrc-reference.json` `delivery.deliverRunner.concurrencyCap`.
35
28
  *
36
- * **`verifyConcurrencyCap`** (Epic #3019 Tech Spec §1.4 / Story #3024) is a
37
- * separate knob that bounds the `verifyWaveResults` loop independently of
38
- * Story-dispatch concurrency, so operators can tune ticket-verify parallelism
39
- * without raising the wave fan-out. Default 4.
29
+ * Story #4545 removed the sibling `verifyConcurrencyCap`: the
30
+ * `verifyWaveResults` loop it claimed to bound never existed in the tree, and
31
+ * its only reader was the retired execution-analysis CLI, which echoed the
32
+ * number into a report rather than bounding anything.
40
33
  */
41
34
  const DEFAULT_DELIVER_RUNNER = Object.freeze({
42
35
  concurrencyCap: 3,
43
- verifyConcurrencyCap: 4,
44
36
  });
45
37
 
46
38
  /**
@@ -59,9 +51,8 @@ export const DEFAULT_CODE_REVIEW = Object.freeze({
59
51
  *
60
52
  * @param {object | null | undefined} config
61
53
  * @returns {{
62
- * deliverRunner: { concurrencyCap: number, verifyConcurrencyCap: number },
54
+ * deliverRunner: { concurrencyCap: number },
63
55
  * codeReview: { maxFixAttempts: number, maxFixScopeFiles: number, autoFixSeverity: 'high'|'medium' },
64
- * storyMergeRetry: { maxAttempts: number, backoffMs: readonly number[] },
65
56
  * decomposer: { concurrencyCap: number },
66
57
  * }}
67
58
  */
@@ -73,9 +64,6 @@ export function getRunners(config) {
73
64
  concurrencyCap:
74
65
  deliverRunnerUser.concurrencyCap ??
75
66
  DEFAULT_DELIVER_RUNNER.concurrencyCap,
76
- verifyConcurrencyCap:
77
- deliverRunnerUser.verifyConcurrencyCap ??
78
- DEFAULT_DELIVER_RUNNER.verifyConcurrencyCap,
79
67
  },
80
68
  codeReview: {
81
69
  maxFixAttempts:
@@ -85,7 +73,6 @@ export function getRunners(config) {
85
73
  autoFixSeverity:
86
74
  codeReviewUser.autoFixSeverity ?? DEFAULT_CODE_REVIEW.autoFixSeverity,
87
75
  },
88
- storyMergeRetry: DEFAULT_STORY_MERGE_RETRY,
89
76
  decomposer: DEFAULT_DECOMPOSER,
90
77
  };
91
78
  }
@@ -13,15 +13,13 @@
13
13
  * ├─ techspec.md
14
14
  * ├─ manifest.md (dispatch manifest)
15
15
  * ├─ retro.md (mirror of GitHub retro at Epic close)
16
- * ├─ perf-report.md (analyzer output, Epic-level)
17
16
  * ├─ lifecycle.ndjson (lifecycle bus ledger)
18
17
  * ├─ checkpoints/... (pre-v2 epic-runner state store; retained layout)
19
- * ├─ <name> (epicArtifactPath escape hatch)
18
+ * ├─ <name> (runArtifactPath escape hatch)
20
19
  * └─ stories/
21
20
  * └─ story-<sid>/
22
21
  * ├─ manifest.md (story dispatch manifest)
23
22
  * ├─ signals.ndjson (append-only signals writer)
24
- * ├─ perf-summary.md
25
23
  * └─ <name> (storyArtifactPath escape hatch)
26
24
  *
27
25
  * Standalone Stories (no parent Epic) follow the same shape under
@@ -42,11 +40,12 @@
42
40
  * resolve a *relative* `tempRoot` against the **main checkout root** (the
43
41
  * parent of `git rev-parse --git-common-dir`) rather than `process.cwd()`.
44
42
  * Without this, a story child that `cd`s into `.worktrees/story-<id>/` before
45
- * calling `story-phase.js` would append `story.heartbeat` records to
43
+ * emitting a lifecycle record would append it to
46
44
  * `<worktree>/temp/run-N/lifecycle.ndjson`, while the `/deliver` host
47
45
  * (running from the main checkout) reads the main-checkout copy — so the
48
- * idle-watchdog never sees heartbeats and the Epic-lease guard silently
49
- * reclaims live foreign claims (the audit-#3513 bug class). Anchoring the
46
+ * host never sees the child's records (the audit-#3513 bug class; its
47
+ * original `story.heartbeat` instance is gone with that emitter, but the
48
+ * divergence applies to every ledger writer). Anchoring the
50
49
  * ledger to the git common dir makes the worktree child writer and the
51
50
  * main-checkout host reader converge on a single file regardless of cwd. An
52
51
  * absolute `tempRoot` is honoured verbatim; only relative roots are anchored.
@@ -155,11 +154,9 @@ export function tempRootFrom(config) {
155
154
  : 'temp';
156
155
  }
157
156
 
158
- const epicId = (id) => {
157
+ const runId = (id) => {
159
158
  if (!Number.isInteger(id) || id <= 0) {
160
- throw new Error(
161
- `[temp-paths] epicId must be a positive integer; got ${id}`,
162
- );
159
+ throw new Error(`[temp-paths] runId must be a positive integer; got ${id}`);
163
160
  }
164
161
  return id;
165
162
  };
@@ -206,19 +203,14 @@ const artifactName = (name) => {
206
203
  };
207
204
 
208
205
  /**
209
- * `temp/run-<eid>/` — every Epic-scoped artifact lives under here.
206
+ * `temp/run-<id>/` — every run-scoped artifact lives under here.
210
207
  *
211
- * @param {number} eid
208
+ * @param {number} rid
212
209
  * @param {object} [config]
213
210
  * @returns {string}
214
211
  */
215
212
  export function runTempDir(rid, config) {
216
- return path.join(anchorTempRoot(tempRootFrom(config)), `run-${epicId(rid)}`);
217
- }
218
-
219
- /** @deprecated Use {@link runTempDir}. */
220
- export function epicTempDir(eid, config) {
221
- return runTempDir(eid, config);
213
+ return path.join(anchorTempRoot(tempRootFrom(config)), `run-${runId(rid)}`);
222
214
  }
223
215
 
224
216
  /**
@@ -246,7 +238,7 @@ export function storyTempDir(eid, sid, config) {
246
238
  const parent =
247
239
  checkedEid === null
248
240
  ? path.join(anchorTempRoot(tempRootFrom(config)), 'standalone')
249
- : epicTempDir(checkedEid, config);
241
+ : runTempDir(checkedEid, config);
250
242
  return path.join(parent, 'stories', `story-${storyId(sid)}`);
251
243
  }
252
244
 
@@ -272,7 +264,7 @@ export function signalsFile(eid, sid, config) {
272
264
  * needs an on-disk home even though there is no `run-<id>/` directory to
273
265
  * anchor the event to.
274
266
  *
275
- * Mirrors `epicLedgerPath` exactly, one level down: `eid === null` routes
267
+ * Mirrors `runLedgerPath` exactly, one level down: `eid === null` routes
276
268
  * through `storyTempDir`'s standalone branch to
277
269
  * `<tempRoot>/standalone/stories/story-<sid>/lifecycle.ndjson`; a real
278
270
  * `eid` routes to `<tempRoot>/run-<eid>/stories/story-<sid>/lifecycle.ndjson`,
@@ -298,14 +290,13 @@ export const storyLedgerPath = (eid, sid, config) =>
298
290
  * @param {object} [config]
299
291
  * @returns {string}
300
292
  */
301
- export function epicArtifactPath(eid, name, config) {
302
- return path.join(epicTempDir(eid, config), artifactName(name));
293
+ export function runArtifactPath(eid, name, config) {
294
+ return path.join(runTempDir(eid, config), artifactName(name));
303
295
  }
304
296
 
305
297
  /**
306
298
  * Escape hatch for a Story-level artifact whose name isn't part of the
307
- * canonical layout (signals.ndjson + perf-summary.md + manifest.md ship
308
- * named helpers).
299
+ * canonical layout (signals.ndjson + manifest.md ship named helpers).
309
300
  *
310
301
  * @param {number} eid
311
302
  * @param {number} sid
@@ -313,37 +304,10 @@ export function epicArtifactPath(eid, name, config) {
313
304
  * @param {object} [config]
314
305
  * @returns {string}
315
306
  */
316
- export function storyArtifactPath(eid, sid, name, config) {
307
+ function storyArtifactPath(eid, sid, name, config) {
317
308
  return path.join(storyTempDir(eid, sid, config), artifactName(name));
318
309
  }
319
310
 
320
- // --- Canonical Epic-level filenames (Tech Spec #1032 §tempRoot) ---
321
-
322
- export const epicTechSpecPath = (eid, config) =>
323
- epicArtifactPath(eid, 'techspec.md', config);
324
- export const epicManifestPath = (eid, config) =>
325
- epicArtifactPath(eid, 'manifest.md', config);
326
- export const epicRetroMirrorPath = (eid, config) =>
327
- epicArtifactPath(eid, 'retro.md', config);
328
- export const epicPerfReportPath = (eid, config) =>
329
- epicArtifactPath(eid, 'perf-report.md', config);
330
-
331
- /**
332
- * `temp/run-<eid>/epic-perf-report.json` — canonical JSON snapshot of
333
- * the `epic-perf-report` payload persisted at /deliver close
334
- * (Epic #3019 / Story #3029 / Task #3040). When present alongside the
335
- * `epic-perf-report` structured comment, the report is discoverable
336
- * from the file system without round-tripping the ticketing provider,
337
- * and the `epic-handoff` structured close comment links it by relative
338
- * path.
339
- *
340
- * @param {number} eid
341
- * @param {object} [config]
342
- * @returns {string}
343
- */
344
- export const epicPerfReportJsonPath = (eid, config) =>
345
- epicArtifactPath(eid, 'epic-perf-report.json', config);
346
-
347
311
  /**
348
312
  * `temp/run-<eid>/lifecycle.ndjson` — append-only lifecycle bus ledger
349
313
  * (Story #2510). The LedgerWriter persists every emitted/completed/failed
@@ -357,12 +321,10 @@ export const epicPerfReportJsonPath = (eid, config) =>
357
321
  * @param {object} [config]
358
322
  * @returns {string}
359
323
  */
360
- export const epicLedgerPath = (eid, config) =>
361
- epicArtifactPath(eid, 'lifecycle.ndjson', config);
324
+ export const runLedgerPath = (eid, config) =>
325
+ runArtifactPath(eid, 'lifecycle.ndjson', config);
362
326
 
363
327
  // --- Canonical Story-level filenames ---
364
328
 
365
329
  export const storyManifestPath = (eid, sid, config) =>
366
330
  storyArtifactPath(eid, sid, 'manifest.md', config);
367
- export const storyPerfSummaryPath = (eid, sid, config) =>
368
- storyArtifactPath(eid, sid, 'perf-summary.md', config);
@@ -32,9 +32,11 @@ const EXECUTION_SCHEMA = {
32
32
 
33
33
  /**
34
34
  * `delivery.lease` — assignee-as-lease primitive (Story #3480). `ttlMs` is
35
- * the staleness window: a ticket claim whose owner has not emitted a
36
- * `story.heartbeat` within this many milliseconds is reclaimable by another
37
- * operator. Defaults to 900000 (15 min) in `lib/config/limits.js`.
35
+ * the staleness window: a ticket claim whose owner's last heartbeat is older
36
+ * than this many milliseconds is reclaimable by another operator. Defaults to
37
+ * 900000 (15 min) in `lib/config/limits.js`. Note the shipped guards fail
38
+ * closed (no live heartbeat source since A22 removed the inert emitter), so
39
+ * a stranded claim is cleared with `--steal` rather than by TTL expiry.
38
40
  */
39
41
  const LEASE_SCHEMA = {
40
42
  type: 'object',
@@ -64,7 +66,6 @@ const DELIVER_RUNNER_SCHEMA = {
64
66
  type: 'object',
65
67
  properties: {
66
68
  concurrencyCap: { type: 'integer', minimum: 1 },
67
- verifyConcurrencyCap: { type: 'integer', minimum: 1 },
68
69
  },
69
70
  additionalProperties: false,
70
71
  };
@@ -137,19 +138,36 @@ const SIGNALS_SCHEMA = {
137
138
  };
138
139
 
139
140
  /**
140
- * `delivery.mergeWatch` — knobs consumed by the MergeWatcher lifecycle
141
- * listener (Story #2896, Epic #2880). `intervalSeconds` is the poll
142
- * cadence between `gh pr view --json mergeCommit,mergedAt` probes after
143
- * `epic.merge.armed`; `maxBudgetSeconds` is the total wall-clock budget
144
- * before the watcher surfaces `agent::blocked` with reason
145
- * `budget-exceeded`. Both keys default in the listener when omitted
146
- * (30s / 3600s).
141
+ * `delivery.mergeWatch` — knobs consumed by the close-and-land merge wait
142
+ * listener (Story #2896, Epic #2880) and by the close-and-land merge wait
143
+ * (`single-story-close/phases/confirm-merge.js`). `intervalSeconds` is the
144
+ * poll cadence between `gh pr view` probes after the arm.
145
+ *
146
+ * The two budgets are deliberately separate axes (Story #4543):
147
+ *
148
+ * - `maxWaitSeconds` bounds **one invocation** of the merge wait. Its
149
+ * default (300s) fits inside a single host tool invocation, whose
150
+ * ceiling is ~10 minutes; the gates that run before the wait already
151
+ * consume minutes of that. Expiry is NOT a block — the wait returns a
152
+ * resumable `pending` terminal with no label mutation. A headless caller
153
+ * with no such ceiling raises it to keep land-in-one-block semantics.
154
+ * - `maxBudgetSeconds` bounds the **cumulative** wait across resumes,
155
+ * anchored at the PR's `createdAt` so re-entering the wait does not
156
+ * restart the clock. Exhausting *this* is the genuine give-up condition
157
+ * that classifies and blocks.
158
+ *
159
+ * `updateAttempts` caps how many times the wait will bring a
160
+ * behind-the-base PR up to date before giving the branch up as unwinnable,
161
+ * rather than waiting out the budget behind a base it could have merged.
162
+ * All keys default in the consumer when omitted (30s / 300s / 3600s / 3).
147
163
  */
148
164
  const MERGE_WATCH_SCHEMA = {
149
165
  type: 'object',
150
166
  properties: {
151
167
  intervalSeconds: { type: 'integer', minimum: 1 },
168
+ maxWaitSeconds: { type: 'integer', minimum: 1 },
152
169
  maxBudgetSeconds: { type: 'integer', minimum: 1 },
170
+ updateAttempts: { type: 'integer', minimum: 0 },
153
171
  },
154
172
  additionalProperties: false,
155
173
  };
@@ -167,8 +185,8 @@ const MERGE_WATCH_SCHEMA = {
167
185
  // context; false falls back to `subagent_type: general-purpose` (the instant
168
186
  // per-consumer revert + the escape for hosts that ignore `.claude/agents/`).
169
187
  // `delivery.routing.freshCriticSampleRate` (default 0.2, clamped [0, 1]) is the
170
- // maker-checker sampling floor forcing a fraction of low-risk acceptance
171
- // clusters through a fresh critic.
188
+ // maker-checker sampling floor forcing a fraction of low-derived-level
189
+ // acceptance clusters through a fresh critic.
172
190
  const ROUTING_SCHEMA = {
173
191
  type: 'object',
174
192
  properties: {
@@ -231,9 +249,9 @@ const REFACTOR_STAGE_SCHEMA = {
231
249
  * `delivery.acceptanceEval` — bounded per-Story acceptance self-eval loop
232
250
  * (Story #3819). After the implementation commits land and before the
233
251
  * Story-implementation phase flips to `closing`, an independent
234
- * (fresh-context) critic pass scores the working diff against each inline
235
- * `acceptance[]` item, redrafts the unmet items, and re-evaluates — capped
236
- * at `maxRounds` redraft rounds.
252
+ * (fresh-context) critic pass scores the caller-injected change set against
253
+ * each inline `acceptance[]` item, redrafts the unmet items, and
254
+ * re-evaluates — capped at `maxRounds` redraft rounds.
237
255
  *
238
256
  * `maxRounds` is the operator-tunable redraft ceiling (default 2 via
239
257
  * `lib/config/acceptance-eval.js`). It is a soft knob inside an
@@ -14,12 +14,20 @@
14
14
  // aggregate into per-gate files under `config/gates/`.
15
15
  import { GATES_SCHEMA } from './config/gates/index.js';
16
16
 
17
+ // Story #4531: miDropMustRefactor (here) and autoRefresh.miDropCap (below)
18
+ // were retired. Both were schema-validated, defaulted, and resolved, but
19
+ // never consumed by the gate they were named for — quality-preview.js's
20
+ // computeExitCode short-circuits on miExit (derived from the ALREADY-
21
+ // consumed delivery.quality.gates.maintainability.tolerance) before either
22
+ // knob is ever read. maintainability.tolerance is now the single documented
23
+ // MI-drop control. See lib/migrations/index.js for the consumer-config
24
+ // migration that strips these keys on upgrade (additionalProperties: false
25
+ // below means a leftover key is a hard AJV failure, not a silent no-op).
17
26
  const CODING_GUARDRAILS_SCHEMA = {
18
27
  type: 'object',
19
28
  properties: {
20
29
  cyclomaticFlag: { type: 'integer', minimum: 1 },
21
30
  cyclomaticMustFix: { type: 'integer', minimum: 1 },
22
- miDropMustRefactor: { type: 'number', minimum: 0 },
23
31
  requireSiblingTest: { type: 'boolean' },
24
32
  },
25
33
  additionalProperties: false,
@@ -29,7 +37,6 @@ const AUTO_REFRESH_SCHEMA = {
29
37
  type: 'object',
30
38
  properties: {
31
39
  enabled: { type: 'boolean' },
32
- miDropCap: { type: 'number', minimum: 0 },
33
40
  crapJumpCap: { type: 'number', minimum: 0 },
34
41
  scope: { type: 'string', enum: ['diff', 'full'] },
35
42
  },
@@ -109,25 +109,57 @@ const PROJECT_SCHEMA = {
109
109
 
110
110
  /**
111
111
  * Curated webhook event vocabulary. The webhook channel is gated by an
112
- * explicit allowlist of event names — the webhook narrative is "epic %
113
- * progress + blockers", not the firehose of per-story transitions that the
114
- * GitHub-comment channel still receives.
112
+ * explicit allowlist of event names — the vocabulary mirrors the events the
113
+ * v2 runtime actually emits through `notify()` (Story transitions, merge
114
+ * outcomes, loop lifecycle beats).
115
+ *
116
+ * `story.heartbeat` was retired here (A22): the vocabulary's contract is
117
+ * "events the runtime actually emits", and nothing could emit this one. Its
118
+ * emitter (`emit-story-heartbeat.js`) demanded an `epicId >= 1` while the
119
+ * sole call path (`single-story-init.js` → `setActiveStoryEnv`) passed
120
+ * `epicId: null`, so `CC_EPIC_ID` was never set and the hook that would have
121
+ * fired the beat always short-circuited. Emitter, hook, and schema are all
122
+ * deleted; keeping the name allowlistable would let an operator subscribe to
123
+ * a channel that can never deliver. Removing it from the enum makes a
124
+ * resurrection fail loudly at config-validation time rather than silently
125
+ * never firing.
115
126
  */
116
127
  export const WEBHOOK_EVENT_NAMES = Object.freeze([
117
- 'epic-started',
118
- 'epic-progress',
119
- 'epic-blocked',
120
- 'epic-unblocked',
121
- 'epic-complete',
128
+ 'state-transition',
129
+ 'story-merged',
130
+ 'story-closing',
131
+ 'operator-message',
132
+ 'merge.unlanded',
133
+ 'merge.flip-failed',
134
+ 'loop.tick',
122
135
  ]);
123
136
 
124
137
  /**
125
138
  * Curated GitHub-comment event vocabulary. The comment channel is gated by
126
139
  * an explicit allowlist of event names — same model as `webhookEvents`.
140
+ *
141
+ * **Deliberately narrower than {@link WEBHOOK_EVENT_NAMES}**, and the axis
142
+ * is ticket scope, not importance. A comment is written *onto a Story
143
+ * issue*, so only events that are about one Story, and whose message reads
144
+ * as narrative an operator wants durably on the ticket, belong here. The
145
+ * webhook-only remainder — `merge.unlanded`, `merge.flip-failed`,
146
+ * `loop.tick` — are run-scoped or firehose beats;
147
+ * mirroring them onto the ticket would bury the narrative under machine
148
+ * chatter, and `notify()` drops a comment for any dispatch without a
149
+ * resolvable ticket id regardless.
150
+ *
151
+ * `story-closing` IS in scope by that rule (Story-scoped, `level: 'story'`,
152
+ * human-readable — the same shape as `story-merged`) and its earlier
153
+ * absence was an oversight: the event was emittable to webhooks but could
154
+ * not be allowlisted for comments at all. It is in the vocabulary but NOT
155
+ * in the shipped default (`config/github.js` `NOTIFICATIONS_DEFAULTS`) —
156
+ * opting in is an operator choice, not a behaviour change forced on every
157
+ * consumer.
127
158
  */
128
159
  export const COMMENT_EVENT_NAMES = Object.freeze([
129
160
  'state-transition',
130
161
  'story-merged',
162
+ 'story-closing',
131
163
  'operator-message',
132
164
  ]);
133
165
 
@@ -208,19 +240,14 @@ const GITHUB_SCHEMA = {
208
240
  // planning.* — inputs to /plan
209
241
  // ---------------------------------------------------------------------------
210
242
 
211
- /**
212
- * `planning.context` bounded planning-context budget for `--emit-context`
213
- * payloads. When the full payload would exceed `maxBytes`, planners switch
214
- * to a summary representation.
215
- */
216
- const PLANNING_CONTEXT_SCHEMA = {
217
- type: 'object',
218
- properties: {
219
- maxBytes: { type: 'integer', minimum: 1024 },
220
- summaryMode: { type: 'string', enum: ['auto', 'always', 'never'] },
221
- },
222
- additionalProperties: false,
223
- };
243
+ // Story #4541: `planning.context.{maxBytes, summaryMode}` was retired. The
244
+ // `applyBudget` pass it fed lost its last caller in the v2 cutover, and it
245
+ // bounded a field the envelope builders discarded before shipping the raw seed
246
+ // anyway so the key resolved but capped nothing. The live bound on
247
+ // planner-context size is the fixed `PLAN_CONTEXT_ENVELOPE_BYTE_CEILING` in
248
+ // `lib/orchestration/plan-context.js`. Setting `planning.context` is now
249
+ // rejected as an additional property, so a resurrected key fails loudly rather
250
+ // than silently doing nothing.
224
251
 
225
252
  /**
226
253
  * Story #2634 — `planning.codebaseSnapshot` controls the structural
@@ -250,7 +277,6 @@ const PLANNING_SCHEMA = {
250
277
  type: 'object',
251
278
  properties: {
252
279
  riskHeuristics: LIST_OR_EXTENDER_OF_STRINGS,
253
- context: PLANNING_CONTEXT_SCHEMA,
254
280
  codebaseSnapshot: CODEBASE_SNAPSHOT_SCHEMA,
255
281
  // Cross-Story conflict-finding severity gates. Off by default so
256
282
  // existing repos keep advisory-only behaviour; flipping either to
@@ -0,0 +1,105 @@
1
+ /**
2
+ * dead-exports-knip.js — the knip driver behind the dead-export ratchet.
3
+ *
4
+ * Owns everything about talking to knip and normalising what comes back:
5
+ * spawning it, reading a pre-captured report, and flattening its report into
6
+ * `{ file, symbol }` rows. `check-dead-exports.js` stays a thin CLI over this.
7
+ *
8
+ * @module lib/dead-exports-knip
9
+ */
10
+
11
+ import { spawnSync } from 'node:child_process';
12
+ import fs from 'node:fs';
13
+ import process from 'node:process';
14
+
15
+ /**
16
+ * Spawn `npx knip --reporter json --no-progress` and return the parsed
17
+ * envelope. Never throws — the caller logs the error and treats current rows as
18
+ * empty, which surfaces every baseline row as "removed": loud, but safe.
19
+ *
20
+ * `production` adds knip's `--production` flag, which restricts analysis to
21
+ * entry/project patterns carrying the `!` suffix in `knip.json`. The test globs
22
+ * deliberately lack that suffix, so production mode drops them as entry points
23
+ * and an export reachable only from a test reads as dead. Without those
24
+ * suffixes production mode has no entry patterns at all and reports nothing —
25
+ * `knip.json` and this flag are a matched pair.
26
+ *
27
+ * Exported as a hook so tests can stub the spawn without a working knip
28
+ * workspace.
29
+ *
30
+ * @param {{ cwd?: string, spawn?: typeof spawnSync, production?: boolean }} [opts]
31
+ * @returns {{ ok: true, envelope: unknown } | { ok: false, error: string }}
32
+ */
33
+ export function runKnip({
34
+ cwd = process.cwd(),
35
+ spawn = spawnSync,
36
+ production = false,
37
+ } = {}) {
38
+ const args = ['knip', '--reporter', 'json', '--no-progress'];
39
+ if (production) args.push('--production');
40
+ const result = spawn(process.platform === 'win32' ? 'npx.cmd' : 'npx', args, {
41
+ cwd,
42
+ stdio: ['ignore', 'pipe', 'pipe'],
43
+ encoding: 'utf-8',
44
+ shell: process.platform === 'win32',
45
+ });
46
+ if (result.error) {
47
+ return { ok: false, error: `spawn failed: ${result.error.message}` };
48
+ }
49
+ const stdout = typeof result.stdout === 'string' ? result.stdout : '';
50
+ if (stdout.trim().length === 0) {
51
+ return { ok: false, error: 'knip produced empty stdout' };
52
+ }
53
+ try {
54
+ return { ok: true, envelope: JSON.parse(stdout) };
55
+ } catch (err) {
56
+ return {
57
+ ok: false,
58
+ error: `knip JSON parse failed: ${err?.message ?? err}`,
59
+ };
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Read a pre-captured knip JSON envelope from disk (the `--knip-output` test
65
+ * seam). Returns the parsed envelope or `null` on failure.
66
+ *
67
+ * @param {string} filePath
68
+ * @returns {unknown}
69
+ */
70
+ export function readKnipOutput(filePath) {
71
+ try {
72
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Flatten knip's `--reporter json` output into `{ file, symbol }` rows. Knip
80
+ * emits `{ issues: [{ file, exports: [{ name, ... }], ... }, ...] }`. Only
81
+ * `exports` rows are mapped — the ratchet ignores file-, dependency- and
82
+ * duplicate-level issues, which knip surfaces under separate `rules` keys.
83
+ *
84
+ * @param {unknown} knipEnvelope The parsed knip JSON report.
85
+ * @returns {Array<{ file: string, symbol: string }>}
86
+ */
87
+ export function extractRowsFromKnip(knipEnvelope) {
88
+ const rows = [];
89
+ if (!knipEnvelope || typeof knipEnvelope !== 'object') return rows;
90
+ const issues = Array.isArray(knipEnvelope.issues) ? knipEnvelope.issues : [];
91
+ for (const issue of issues) {
92
+ const file = issue?.file;
93
+ if (typeof file !== 'string' || file.length === 0) continue;
94
+ const exports_ = Array.isArray(issue.exports) ? issue.exports : [];
95
+ for (const e of exports_) {
96
+ const symbol =
97
+ (e && typeof e.name === 'string' && e.name) ||
98
+ (e && typeof e.symbol === 'string' && e.symbol) ||
99
+ null;
100
+ if (!symbol) continue;
101
+ rows.push({ file, symbol });
102
+ }
103
+ }
104
+ return rows;
105
+ }