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
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * plan-critics.js — the /plan critic-dispatch verdict CLI (Story #4592).
5
+ *
6
+ * `/plan` step 2.5 (between Author and Persist) runs this against the draft
7
+ * `stories.json`. It evaluates the consolidation + pre-mortem dispatch
8
+ * conditions and prints the verdict as JSON on stdout so the workflow can
9
+ * act on it — dispatching a fresh-context critic sub-agent and folding its
10
+ * findings into a re-author round **before** the plan is persisted.
11
+ *
12
+ * Why here and nowhere else. The evaluation used to run inside
13
+ * `run-plan-persist.js`, after authoring was finished and immediately before
14
+ * `createStoryIssues` — the one point in the flow where nothing can act on a
15
+ * `dispatch: true` verdict, because the artifacts are about to become live
16
+ * issues. It logged the verdict and moved on. This CLI is now the **single**
17
+ * evaluation point, sited where a re-author loop actually exists.
18
+ *
19
+ * Advisory by contract: a `dispatch: true` verdict routes work to the
20
+ * workflow, it does not gate the run. This CLI exits 0 on any verdict; only a
21
+ * usage/IO error is a failure. Every `dispatch: false` decision is recorded to
22
+ * the plan-metrics ledger (`appendCriticSkip`) so under-firing stays auditable.
23
+ *
24
+ * CLI:
25
+ * --stories <file> Required. The draft Story ticket array (JSON).
26
+ * --tech-spec <file> Optional. Shared Tech Spec carrying the
27
+ * `## Delivery Slicing` table the consolidation
28
+ * precondition reads.
29
+ *
30
+ * stdout is reserved for the verdict JSON (Story #2278 discipline):
31
+ *
32
+ * {
33
+ * "consolidation": { "critic": "consolidation", "dispatch": false, "reasons": [...] },
34
+ * "premortem": { "critic": "pre-mortem", "dispatch": true, "reasons": [...] },
35
+ * "textHygiene": { "critic": "text-hygiene", "findings": [...] }
36
+ * }
37
+ *
38
+ * `textHygiene` (Story #4599) is advisory-only: deterministic body lints with
39
+ * no dispatch semantics — its findings fold into the re-author round.
40
+ *
41
+ * Human-readable log lines go to stderr, matching the sibling `plan-persist`.
42
+ *
43
+ * Exit codes: 0 success (any verdict); 1 usage/IO error.
44
+ */
45
+
46
+ import { readFile } from 'node:fs/promises';
47
+ import path from 'node:path';
48
+ import { parseArgs } from 'node:util';
49
+
50
+ import { runAsCli } from './lib/cli-utils.js';
51
+ import { resolveConfig } from './lib/config-resolver.js';
52
+ import { Logger, routeAllOutputToStderr } from './lib/Logger.js';
53
+ import { evaluatePlanCritics } from './lib/orchestration/plan-critics-evaluate.js';
54
+ import { appendCriticSkip } from './lib/orchestration/plan-metrics.js';
55
+
56
+ const CLI_OPTIONS = {
57
+ stories: { type: 'string' },
58
+ 'tech-spec': { type: 'string' },
59
+ };
60
+
61
+ const USAGE = 'Usage: plan-critics.js --stories <file> [--tech-spec <file>]';
62
+
63
+ /** The `cli` discriminator every ledger record from this surface carries. */
64
+ export const PLAN_CRITICS_CLI = 'plan-critics';
65
+
66
+ /**
67
+ * Read the draft artifacts the critics evaluate.
68
+ *
69
+ * @param {{ storiesPath: string, techSpecPath?: string|null }} paths
70
+ * @returns {Promise<{ tickets: object[], techSpecContent: string }>}
71
+ */
72
+ export async function loadCriticArtifacts({
73
+ storiesPath,
74
+ techSpecPath = null,
75
+ }) {
76
+ const raw = await readFile(storiesPath, 'utf8');
77
+ let tickets;
78
+ try {
79
+ tickets = JSON.parse(raw);
80
+ } catch (err) {
81
+ throw new Error(
82
+ `Failed to parse stories file "${storiesPath}" as JSON: ${err.message}`,
83
+ );
84
+ }
85
+ if (!Array.isArray(tickets)) {
86
+ throw new Error(`Stories file "${storiesPath}" must contain a JSON array.`);
87
+ }
88
+ const techSpecContent = techSpecPath
89
+ ? await readFile(techSpecPath, 'utf8')
90
+ : '';
91
+ return { tickets, techSpecContent };
92
+ }
93
+
94
+ /**
95
+ * Log each decision and record every skip on the plan-metrics ledger. The
96
+ * ledger write is best-effort by `appendCriticSkip`'s own contract — it can
97
+ * never fail the plan step.
98
+ *
99
+ * @param {{ consolidation: object, premortem: object, textHygiene?: object }} verdict
100
+ * @param {object} config
101
+ * @param {{ append?: typeof appendCriticSkip }} [deps]
102
+ * @returns {Promise<void>}
103
+ */
104
+ export async function recordCriticSkips(
105
+ verdict,
106
+ config,
107
+ { append = appendCriticSkip } = {},
108
+ ) {
109
+ for (const decision of [verdict.consolidation, verdict.premortem]) {
110
+ Logger.info(
111
+ `[plan-critics] critic ${decision.critic}: ` +
112
+ `${decision.dispatch ? 'dispatch' : 'skip'} — ` +
113
+ decision.reasons.join('; '),
114
+ );
115
+ if (!decision.dispatch) {
116
+ await append(
117
+ {
118
+ critic: decision.critic,
119
+ reasons: decision.reasons,
120
+ cli: PLAN_CRITICS_CLI,
121
+ },
122
+ config,
123
+ );
124
+ }
125
+ }
126
+
127
+ // Text hygiene (Story #4599) is advisory-only — no dispatch semantics, so
128
+ // "skip" here means "zero findings". Recording that keeps the lint's
129
+ // fire/skip accounting on the same ledger as the dispatching critics.
130
+ const hygiene = verdict.textHygiene;
131
+ if (hygiene) {
132
+ const count = hygiene.findings.length;
133
+ Logger.info(
134
+ `[plan-critics] critic ${hygiene.critic}: ${count} finding(s) (advisory).`,
135
+ );
136
+ if (count === 0) {
137
+ await append(
138
+ {
139
+ critic: hygiene.critic,
140
+ reasons: ['No text-hygiene findings over the draft stories.'],
141
+ cli: PLAN_CRITICS_CLI,
142
+ },
143
+ config,
144
+ );
145
+ }
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Load the artifacts, evaluate both critics, record the skips, and return the
151
+ * verdict. Exported as the CLI's whole body so tests drive it in-process with
152
+ * an explicit config and ledger seam.
153
+ *
154
+ * @param {{
155
+ * storiesPath: string,
156
+ * techSpecPath?: string|null,
157
+ * config?: object,
158
+ * append?: typeof appendCriticSkip,
159
+ * }} args
160
+ * @returns {Promise<{ consolidation: object, premortem: object, textHygiene: object }>}
161
+ */
162
+ export async function evaluateCriticArtifacts({
163
+ storiesPath,
164
+ techSpecPath = null,
165
+ config = {},
166
+ append = appendCriticSkip,
167
+ }) {
168
+ const { tickets, techSpecContent } = await loadCriticArtifacts({
169
+ storiesPath,
170
+ techSpecPath,
171
+ });
172
+ const verdict = evaluatePlanCritics({ techSpecContent, tickets, config });
173
+ await recordCriticSkips(verdict, config, { append });
174
+ return verdict;
175
+ }
176
+
177
+ async function main() {
178
+ const { values } = parseArgs({ options: CLI_OPTIONS });
179
+
180
+ if (!values.stories) {
181
+ throw new Error(USAGE);
182
+ }
183
+
184
+ // stdout is reserved for the verdict JSON — flip every Logger sink that
185
+ // could land on stdout to stderr before any evaluation runs.
186
+ routeAllOutputToStderr();
187
+
188
+ const verdict = await evaluateCriticArtifacts({
189
+ storiesPath: path.resolve(values.stories),
190
+ techSpecPath: values['tech-spec']
191
+ ? path.resolve(values['tech-spec'])
192
+ : null,
193
+ config: resolveConfig(),
194
+ });
195
+
196
+ process.stdout.write(`${JSON.stringify(verdict, null, 2)}\n`);
197
+ return 0;
198
+ }
199
+
200
+ runAsCli(import.meta.url, main, {
201
+ source: 'plan-critics',
202
+ propagateExitCode: true,
203
+ });
@@ -4,27 +4,51 @@
4
4
  * plan-persist.js — flat Story GitHub-write surface for v2 `/plan`
5
5
  * (Stage 3 — `docs/roadmap.md`).
6
6
  *
7
- * Given the author-written planning artifacts (`stories.json` +
8
- * `risk-verdict.json`, optional shared Tech Spec), this CLI validates and
9
- * creates Story issue(s) directly:
7
+ * Given the author-written planning artifacts (`stories.json`, optional shared
8
+ * Tech Spec), this CLI validates and creates Story issue(s) directly:
10
9
  *
11
- * risk-verdict → ticket validator / DAG / capacity → reachability →
10
+ * ticket validator / DAG / capacity → reachability →
12
11
  * split-policy partition → fold/spill Spec into each Story body →
13
- * createIssue(s) with type::story + agent::ready risk-verdict +
14
- * story-plan-state on every Story; plan-summary on the primary → temp
15
- * cleanup.
12
+ * createIssue(s) with type::story, resumably by plan fingerprint (NOT
13
+ * agent::ready) → story-plan-state on every Story;
14
+ * plan-summary on the primary → flip every Story to agent::ready →
15
+ * comment + close superseded source tickets → temp cleanup + stale reap.
16
+ *
17
+ * Story #4542 retired the authored risk verdict: persist neither requires nor
18
+ * accepts one, and no plan-time step produces one. Review depth and the
19
+ * acceptance-critic mode are derived from the diff at close time
20
+ * (`review-depth.js#deriveChangeLevel`). `--force-review` is the only review
21
+ * gate the planner still carries, and it is an explicit operator flag.
16
22
  *
17
23
  * CLI:
18
- * --stories <file> Required Story ticket array (default length 1)
19
- * --risk-verdict <file> Required risk verdict (no deliveryShape)
20
- * --tech-spec <file> Optional shared Tech Spec folded into each Story
21
- * --plan-dir <dir> Optional temp dir deleted at terminal success
22
- * --plan-acceptance <file> Optional JSON string[] for partition coverage
23
- * --plan-run-id <id> Optional plan-run token when N>1
24
- * --dry-run Assemble + validate without GitHub writes
25
- * --force-review Record operator-forced review routing
24
+ * --stories <file> Required Story ticket array (default length 1)
25
+ * --tech-spec <file> Optional shared Tech Spec folded into each Story
26
+ * --plan-dir <dir> Optional temp dir deleted at terminal success.
27
+ * Also where the `plan-context.json` envelope is
28
+ * auto-discovered from (see --plan-context)
29
+ * --plan-context <file> Optional explicit path to the `plan-context.js`
30
+ * envelope. Its `sourceTickets[]` is what makes
31
+ * `--tickets` superseding work without a flag
32
+ * --plan-acceptance <file> Optional JSON string[] for partition coverage
33
+ * --source-tickets <ids> Explicit OVERRIDE of the envelope-derived source
34
+ * ids, for hand-driven runs. Each id must be
35
+ * claimed by exactly one Story's `supersedes[]`;
36
+ * they are commented on and closed as superseded
37
+ * --no-close-superseded Keep the source tickets open (no comment, no
38
+ * close) — for a genuinely partial supersede
39
+ * --dry-run Assemble + validate without GitHub writes
40
+ * --force-review Operator-forced review stop before persist lands
26
41
  * --allow-over-budget / --allow-large-fan-out
27
42
  *
43
+ * Run `--dry-run` first. It exercises every gate — validator, DAG, capacity,
44
+ * budget, reachability, split/supersede partition, Spec fold — write-free, so
45
+ * an authoring mistake surfaces before a single issue exists.
46
+ *
47
+ * stdout is reserved for the JSON result (Story #2278 discipline, extended to
48
+ * this CLI by Story #4541): `routeAllOutputToStderr()` runs before any
49
+ * pipeline code so a headless driver can `JSON.parse` stdout unconditionally.
50
+ * Human-readable log lines go to stderr, matching the sibling `plan-context`.
51
+ *
28
52
  * Exit codes: 0 success; 1 fatal; 3 reachability orphans (nothing mutated).
29
53
  */
30
54
 
@@ -38,13 +62,17 @@ import {
38
62
  resolveConfig,
39
63
  validateOrchestrationConfig,
40
64
  } from './lib/config-resolver.js';
41
- import { Logger } from './lib/Logger.js';
65
+ import { Logger, routeAllOutputToStderr } from './lib/Logger.js';
42
66
  import {
43
67
  readPlanMetrics,
44
68
  recordPlanInvocation,
45
69
  renderPlanMetricsSummaryLine,
46
70
  summarizePlanMetrics,
47
71
  } from './lib/orchestration/plan-metrics.js';
72
+ import {
73
+ loadPlanContextEnvelope,
74
+ resolvePlanContextPath,
75
+ } from './lib/orchestration/plan-persist/plan-context-source.js';
48
76
  import {
49
77
  runPlanPersist,
50
78
  writeCheckpointV2,
@@ -54,7 +82,7 @@ import {
54
82
  buildWaveTable,
55
83
  PLAN_SUMMARY_COMMENT_TYPE,
56
84
  } from './lib/orchestration/plan-persist/summary.js';
57
- import { loadRiskVerdict } from './lib/orchestration/planning/risk-verdict.js';
85
+ import { resolveSourceTicketIds } from './lib/orchestration/plan-persist/supersede-ops.js';
58
86
  import { createProvider } from './lib/provider-factory.js';
59
87
 
60
88
  export {
@@ -67,11 +95,13 @@ export {
67
95
 
68
96
  const CLI_OPTIONS = {
69
97
  stories: { type: 'string' },
70
- 'risk-verdict': { type: 'string' },
71
98
  'tech-spec': { type: 'string' },
72
99
  'plan-dir': { type: 'string' },
100
+ 'plan-context': { type: 'string' },
73
101
  'plan-acceptance': { type: 'string' },
74
- 'plan-run-id': { type: 'string' },
102
+ 'source-tickets': { type: 'string' },
103
+ 'close-superseded': { type: 'boolean', default: true },
104
+ 'no-close-superseded': { type: 'boolean', default: false },
75
105
  'dry-run': { type: 'boolean', default: false },
76
106
  'force-review': { type: 'boolean', default: false },
77
107
  'allow-over-budget': { type: 'boolean', default: false },
@@ -79,9 +109,11 @@ const CLI_OPTIONS = {
79
109
  };
80
110
 
81
111
  const USAGE =
82
- 'Usage: plan-persist.js --stories <file> --risk-verdict <file> ' +
83
- '[--tech-spec <file>] [--plan-dir <dir>] [--plan-acceptance <file>] ' +
84
- '[--plan-run-id <id>] [--dry-run] [--force-review] ' +
112
+ 'Usage: plan-persist.js --stories <file> ' +
113
+ '[--tech-spec <file>] [--plan-dir <dir>] [--plan-context <file>] ' +
114
+ '[--plan-acceptance <file>] ' +
115
+ '[--source-tickets <ids>] [--no-close-superseded] ' +
116
+ '[--dry-run] [--force-review] ' +
85
117
  '[--allow-over-budget] [--allow-large-fan-out]';
86
118
 
87
119
  async function readOptional(filePath, { required }) {
@@ -104,22 +136,28 @@ async function readJsonFile(filePath, label) {
104
136
  }
105
137
  }
106
138
 
107
- function resolveInputPaths(values) {
139
+ /**
140
+ * Resolve every input path the CLI accepts, including where the
141
+ * `plan-context.js` envelope is discovered from. Exported for tests.
142
+ *
143
+ * @param {object} values Parsed `parseArgs` values.
144
+ */
145
+ export function resolveInputPaths(values) {
146
+ const planDir = values['plan-dir'] ? path.resolve(values['plan-dir']) : null;
108
147
  return {
109
148
  storiesPath: path.resolve(values.stories),
110
- riskVerdictPath: path.resolve(values['risk-verdict']),
111
149
  techSpecPath: values['tech-spec']
112
150
  ? path.resolve(values['tech-spec'])
113
151
  : null,
114
152
  planAcceptancePath: values['plan-acceptance']
115
153
  ? path.resolve(values['plan-acceptance'])
116
154
  : null,
117
- planDir: values['plan-dir'] ? path.resolve(values['plan-dir']) : null,
155
+ planDir,
156
+ planContextPath: resolvePlanContextPath(values['plan-context'], planDir),
118
157
  };
119
158
  }
120
159
 
121
160
  async function loadArtifacts(paths) {
122
- const riskVerdict = loadRiskVerdict(paths.riskVerdictPath);
123
161
  const stories = await readJsonFile(paths.storiesPath, 'stories');
124
162
  const techSpecContent = paths.techSpecPath
125
163
  ? await readOptional(paths.techSpecPath, { required: true })
@@ -127,23 +165,61 @@ async function loadArtifacts(paths) {
127
165
  const planAcceptance = paths.planAcceptancePath
128
166
  ? await readJsonFile(paths.planAcceptancePath, 'plan-acceptance')
129
167
  : null;
168
+ const planContextEnvelope = await loadPlanContextEnvelope(
169
+ paths.planContextPath,
170
+ );
130
171
 
131
- return { stories, riskVerdict, techSpecContent, planAcceptance };
172
+ return {
173
+ stories,
174
+ techSpecContent,
175
+ planAcceptance,
176
+ planContextEnvelope,
177
+ };
132
178
  }
133
179
 
134
- function buildPersistOptions(values, paths) {
180
+ /**
181
+ * Assemble the `runPlanPersist` opts bag from parsed CLI values.
182
+ *
183
+ * Exported for tests: this is the join where the envelope-derived source ids
184
+ * meet the persist engine, so a regression here silently un-wires
185
+ * `/plan --tickets` superseding (Story #4554).
186
+ *
187
+ * @param {object} values Parsed `parseArgs` values.
188
+ * @param {ReturnType<typeof resolveInputPaths>} paths
189
+ * @param {object|null} planContextEnvelope
190
+ * @returns {object} opts for `runPlanPersist`.
191
+ */
192
+ export function buildPersistOptions(values, paths, planContextEnvelope) {
193
+ const source = resolveSourceTicketIds({
194
+ explicitIds: values['source-tickets'],
195
+ envelope: planContextEnvelope,
196
+ });
197
+
135
198
  return {
136
199
  forceReview: values['force-review'],
137
200
  allowOverBudget: values['allow-over-budget'],
138
201
  allowLargeFanOut: values['allow-large-fan-out'],
139
202
  dryRun: values['dry-run'],
140
- planRunId: values['plan-run-id'],
141
203
  planDir: paths.planDir,
142
204
  skipCleanup: values['dry-run'],
205
+ sourceTicketIds: source.ids,
206
+ sourceTicketOrigin: source.origin,
207
+ // Default-on: `--no-close-superseded` is the explicit escape and always
208
+ // wins over the (default `true`) `--close-superseded`.
209
+ closeSuperseded:
210
+ values['no-close-superseded'] === true
211
+ ? false
212
+ : values['close-superseded'] !== false,
143
213
  };
144
214
  }
145
215
 
146
- async function runPersistInvocation({ values, config, provider, artifacts }) {
216
+ async function runPersistInvocation({
217
+ values,
218
+ config,
219
+ provider,
220
+ artifacts,
221
+ metricsSince,
222
+ }) {
147
223
  const paths = resolveInputPaths(values);
148
224
  const settings = {
149
225
  baseBranch: config.project?.baseBranch,
@@ -164,14 +240,37 @@ async function runPersistInvocation({ values, config, provider, artifacts }) {
164
240
  artifacts,
165
241
  config,
166
242
  settings,
167
- opts: buildPersistOptions(values, paths),
243
+ opts: {
244
+ ...buildPersistOptions(values, paths, artifacts.planContextEnvelope),
245
+ metricsSince,
246
+ },
168
247
  }),
169
248
  );
170
249
  }
171
250
 
172
- async function attachPlanMetrics(result, config) {
251
+ /**
252
+ * Attach the plan-metrics roll-up for **this** invocation.
253
+ *
254
+ * Two Story #4541 fixes meet here. `readPlanMetrics` is declared
255
+ * `(epicId, config)` but was called with `config` first, so it threw its
256
+ * `epicId` guard on every run and the catch below turned that into a
257
+ * silently missing summary — v2 persist is always Epic-less, hence the
258
+ * explicit `null`. And the Epic-less ledger is shared across every plan the
259
+ * repo has ever run, so `since` scopes the counts to the current invocation
260
+ * instead of reporting lifetime totals under an invocation-shaped line.
261
+ *
262
+ * This runs *after* `recordPlanInvocation` has appended this run's own
263
+ * record, so the summary always has at least that one entry to report.
264
+ *
265
+ * @param {object} result Mutated in place with `planMetrics`.
266
+ * @param {object} config
267
+ * @param {string} since ISO-8601 instant this invocation started.
268
+ */
269
+ async function attachPlanMetrics(result, config, since) {
173
270
  try {
174
- const summary = summarizePlanMetrics(await readPlanMetrics(config));
271
+ const summary = summarizePlanMetrics(await readPlanMetrics(null, config), {
272
+ since,
273
+ });
175
274
  if (summary) {
176
275
  result.planMetrics = summary;
177
276
  Logger.info(`[plan-persist] ${renderPlanMetricsSummaryLine(summary)}`);
@@ -184,10 +283,20 @@ async function attachPlanMetrics(result, config) {
184
283
  async function main() {
185
284
  const { values } = parseArgs({ options: CLI_OPTIONS });
186
285
 
187
- if (!values.stories || !values['risk-verdict']) {
286
+ if (!values.stories) {
188
287
  throw new Error(USAGE);
189
288
  }
190
289
 
290
+ // stdout is reserved for the JSON result: flip every Logger sink that could
291
+ // land on stdout to stderr BEFORE any pipeline code runs (Story #2278
292
+ // discipline, extended here by Story #4541 — this CLI interleaved Logger
293
+ // lines with its own JSON, so a headless driver could not parse stdout).
294
+ routeAllOutputToStderr();
295
+
296
+ // Boundary for this invocation's plan-metrics roll-up — stamped before any
297
+ // ledger-writing work so every record this run appends falls inside it.
298
+ const metricsSince = new Date().toISOString();
299
+
191
300
  let config;
192
301
  try {
193
302
  config = resolveConfig();
@@ -206,6 +315,7 @@ async function main() {
206
315
  config,
207
316
  provider,
208
317
  artifacts,
318
+ metricsSince,
209
319
  });
210
320
  } catch (err) {
211
321
  if (err?.code === 'PLAN_REACHABILITY_ORPHANS') {
@@ -216,7 +326,7 @@ async function main() {
216
326
  throw err;
217
327
  }
218
328
 
219
- await attachPlanMetrics(result, config);
329
+ await attachPlanMetrics(result, config, metricsSince);
220
330
 
221
331
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
222
332
  }
@@ -1,17 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * plan-run-epilogue.js — execute the real per-run closeout for
4
- * `/deliver --run <planRunId>` and positional multi-Story delivers.
3
+ * plan-run-epilogue.js — execute the real per-run closeout for a
4
+ * multi-Story `/deliver`.
5
5
  *
6
6
  * Usage:
7
- * node .agents/scripts/plan-run-epilogue.js --run <planRunId>
8
- * node .agents/scripts/plan-run-epilogue.js --run <planRunId> --stories 1,2,3
9
7
  * node .agents/scripts/plan-run-epilogue.js --stories 1,2,3
10
8
  *
11
- * When `--stories` is omitted, `--run` is required and the set is resolved
12
- * via plan-run labels (`state=all` so landed Stories are included).
13
- * When `--stories` is supplied without `--run`, an adhoc planRunId is
14
- * synthesized from the sorted Story ids.
9
+ * Keyed on the delivered id set: an `adhoc-<sorted-ids>` run id is
10
+ * synthesized from `--stories`. Story #4540 retired the `--run <planRunId>`
11
+ * label-resolution branch along with the `plan-run::<id>` label itself.
15
12
  */
16
13
 
17
14
  import './lib/runtime-deps/ensure-installed.js';
@@ -20,16 +17,10 @@ import { parseArgs } from 'node:util';
20
17
  import { runAsCli } from './lib/cli-utils.js';
21
18
  import { resolveConfig } from './lib/config-resolver.js';
22
19
  import { Logger } from './lib/Logger.js';
23
- import {
24
- fetchPlanRunIssues,
25
- normalizePlanRunLabel,
26
- resolvePlanRunFromIssues,
27
- } from './lib/orchestration/resolve-plan-run.js';
28
20
  import { runPlanRunEpilogue } from './lib/orchestration/run-epilogue.js';
29
21
  import { createProvider } from './lib/provider-factory.js';
30
22
 
31
23
  const CLI_OPTIONS = {
32
- run: { type: 'string' },
33
24
  stories: { type: 'string' },
34
25
  cwd: { type: 'string' },
35
26
  };
@@ -44,13 +35,10 @@ export async function main(argv = process.argv.slice(2)) {
44
35
  options: CLI_OPTIONS,
45
36
  strict: false,
46
37
  });
47
- const runFlag = typeof values.run === 'string' ? values.run.trim() : '';
48
38
  const hasStoriesFlag =
49
39
  typeof values.stories === 'string' && values.stories.trim().length > 0;
50
- if (!runFlag && !hasStoriesFlag) {
51
- throw new Error(
52
- 'Usage: node plan-run-epilogue.js (--run <planRunId> | --stories 1,2,3) [--stories 1,2,3]',
53
- );
40
+ if (!hasStoriesFlag) {
41
+ throw new Error('Usage: node plan-run-epilogue.js --stories 1,2,3');
54
42
  }
55
43
  const cwd =
56
44
  typeof values.cwd === 'string' && values.cwd.trim()
@@ -59,26 +47,16 @@ export async function main(argv = process.argv.slice(2)) {
59
47
  const config = resolveConfig({ cwd });
60
48
  const provider = createProvider(config);
61
49
 
62
- let stories = [];
63
- if (hasStoriesFlag) {
64
- stories = values.stories
65
- .split(',')
66
- .map((s) => Number(s.trim()))
67
- .filter((n) => Number.isInteger(n) && n > 0);
68
- } else {
69
- const planRunLabel = normalizePlanRunLabel(runFlag);
70
- const issues = await fetchPlanRunIssues(provider, {
71
- planRunLabel,
72
- state: 'all',
73
- });
74
- const envelope = resolvePlanRunFromIssues({ run: runFlag, issues });
75
- stories = (envelope.stories ?? [])
76
- .map((s) => Number(s?.id ?? s))
77
- .filter((n) => Number.isInteger(n) && n > 0);
78
- }
50
+ // Story #4540 retired the `--run <planRunId>` label-resolution branch
51
+ // along with the label itself. The epilogue is keyed on the delivered id
52
+ // set, and the synthesized `adhoc-<ids>` id it already used for positional
53
+ // runs is now the only id it needs.
54
+ const stories = values.stories
55
+ .split(',')
56
+ .map((s) => Number(s.trim()))
57
+ .filter((n) => Number.isInteger(n) && n > 0);
79
58
 
80
- const planRunId =
81
- runFlag || `adhoc-${[...stories].sort((a, b) => a - b).join('-')}`;
59
+ const planRunId = `adhoc-${[...stories].sort((a, b) => a - b).join('-')}`;
82
60
 
83
61
  const result = await runPlanRunEpilogue({
84
62
  planRunId,
@@ -87,6 +65,8 @@ export async function main(argv = process.argv.slice(2)) {
87
65
  config,
88
66
  cwd,
89
67
  });
68
+ warnOnUnresolvedBase(result);
69
+ warnOnEmptyRollup(result);
90
70
  Logger.info(JSON.stringify(result, null, 2));
91
71
  if (result.errors?.length) {
92
72
  process.exitCode = 1;
@@ -94,4 +74,69 @@ export async function main(argv = process.argv.slice(2)) {
94
74
  return result;
95
75
  }
96
76
 
77
+ /**
78
+ * Surface an unresolvable combined landed diff as a loud operator warning.
79
+ *
80
+ * The roster's changed-file set is the input the host walks its audit lenses
81
+ * against; a silent absence would read as "nothing changed" and the lens walk
82
+ * would look complete while covering nothing. Not fatal — with no diff the
83
+ * selector degrades to keyword-only lens selection, so the rest of the
84
+ * roster is still useful.
85
+ *
86
+ * @param {object} result - `runPlanRunEpilogue` envelope.
87
+ * @returns {void}
88
+ */
89
+ function warnOnUnresolvedBase(result) {
90
+ const roster = (result?.results ?? []).find(
91
+ (r) => r?.kind === 'audit-roster',
92
+ );
93
+ const base = roster?.baseResolution;
94
+ if (base?.resolved !== false) return;
95
+ Logger.warn(
96
+ `⚠️ Combined landed diff unavailable — the pre-run base sha could not be ` +
97
+ `resolved against \`${base.baseRef}\`: ${base.reason}\n` +
98
+ ` changedFiles is null (NOT an empty set). Determine the run diff by ` +
99
+ `hand before walking the selected lenses.`,
100
+ );
101
+ }
102
+
103
+ /**
104
+ * Surface a zero-signal roll-up over a multi-Story run as a loud operator
105
+ * warning (Story #4578).
106
+ *
107
+ * The failure this exists to prevent is a *reassuring* one. The roll-up read
108
+ * "No friction signals — nothing to follow up" for a 7-Story run that
109
+ * contained a mid-run git outage, a parked worker needing an operator
110
+ * resume, and an acceptance critic that needed four rounds. Nothing was
111
+ * broken in the roll-up — the stream really was empty — but the report was
112
+ * indistinguishable from a clean run, so the retro loop that exists to learn
113
+ * from a run was silently blind to that run's pain.
114
+ *
115
+ * Not fatal: a genuinely friction-free multi-Story run is possible, and this
116
+ * cannot tell the two apart — which is precisely why it asks the operator
117
+ * rather than asserting either reading.
118
+ *
119
+ * @param {object} result - `runPlanRunEpilogue` envelope.
120
+ * @returns {void}
121
+ */
122
+ function warnOnEmptyRollup(result) {
123
+ const rollup = (result?.results ?? []).find(
124
+ (r) => r?.kind === 'follow-up-rollup',
125
+ );
126
+ if (!rollup?.emptyRollupSuspect) return;
127
+ Logger.warn(
128
+ `⚠️ 0 friction signals across ${rollup.storyCount} Stories — telemetry may not ` +
129
+ `have fired.\n` +
130
+ ` An empty roll-up is NOT evidence of a clean run: it is the same output a ` +
131
+ `run with\n` +
132
+ ` heavy friction produces when nothing recorded it. The runtime emits ` +
133
+ `friction from its\n` +
134
+ ` own observables (agent::blocked transitions, failed closes, exhausted ` +
135
+ `merge waits), so\n` +
136
+ ` zero here also means none of those fired. If this run had friction you ` +
137
+ `can name, that\n` +
138
+ ` telemetry gap is itself worth filing.`,
139
+ );
140
+ }
141
+
97
142
  await runAsCli(import.meta.url, main);