mandrel 2.0.0 → 2.1.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 (311) hide show
  1. package/.agents/README.md +59 -28
  2. package/.agents/agents/acceptance-critic.md +9 -7
  3. package/.agents/agents/story-worker.md +41 -46
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +51 -44
  6. package/.agents/docs/agentrc-reference.json +8 -13
  7. package/.agents/docs/configuration.md +32 -56
  8. package/.agents/docs/execution-reference.md +39 -10
  9. package/.agents/docs/quality-gates.md +14 -16
  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/git-conventions-reference.md +42 -51
  14. package/.agents/schemas/agentrc.schema.json +34 -45
  15. package/.agents/schemas/audit-rules.json +59 -1
  16. package/.agents/schemas/audit-rules.schema.json +33 -1
  17. package/.agents/schemas/lifecycle/README.md +1 -2
  18. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  19. package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
  20. package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
  21. package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
  22. package/.agents/schemas/signal-event.schema.json +3 -3
  23. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  24. package/.agents/schemas/validation-evidence.schema.json +1 -1
  25. package/.agents/scripts/acceptance-eval.js +22 -66
  26. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  27. package/.agents/scripts/bootstrap.js +3 -3
  28. package/.agents/scripts/check-dead-exports.js +43 -104
  29. package/.agents/scripts/check-doc-links.js +2 -2
  30. package/.agents/scripts/check-lifecycle-lint.js +1 -1
  31. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  32. package/.agents/scripts/deliver-recover.js +122 -0
  33. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  34. package/.agents/scripts/evidence-gate.js +20 -50
  35. package/.agents/scripts/generate-skills-index.js +17 -1
  36. package/.agents/scripts/generate-workflows-doc.js +4 -4
  37. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  38. package/.agents/scripts/lib/audit-suite/selector.js +323 -23
  39. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
  40. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  41. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  42. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  43. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  44. package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
  45. package/.agents/scripts/lib/checks/index.js +1 -1
  46. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  47. package/.agents/scripts/lib/checks/state.js +17 -248
  48. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
  49. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  50. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  51. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  52. package/.agents/scripts/lib/cli-args.js +23 -2
  53. package/.agents/scripts/lib/close-validation/gates.js +13 -13
  54. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  55. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  56. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  57. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  58. package/.agents/scripts/lib/config/delivery-routing.js +7 -6
  59. package/.agents/scripts/lib/config/explain.js +10 -16
  60. package/.agents/scripts/lib/config/github.js +7 -5
  61. package/.agents/scripts/lib/config/limits.js +15 -25
  62. package/.agents/scripts/lib/config/quality.js +11 -14
  63. package/.agents/scripts/lib/config/runners.js +8 -21
  64. package/.agents/scripts/lib/config/temp-paths.js +18 -56
  65. package/.agents/scripts/lib/config-settings-schema-delivery.js +31 -13
  66. package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
  67. package/.agents/scripts/lib/config-settings-schema.js +48 -22
  68. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  69. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  70. package/.agents/scripts/lib/duplicate-search.js +38 -7
  71. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  72. package/.agents/scripts/lib/format-generated-json.js +97 -0
  73. package/.agents/scripts/lib/framework-version.js +19 -189
  74. package/.agents/scripts/lib/gh-exec.js +8 -0
  75. package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
  76. package/.agents/scripts/lib/git-utils.js +0 -14
  77. package/.agents/scripts/lib/json-utils.js +1 -2
  78. package/.agents/scripts/lib/label-constants.js +0 -15
  79. package/.agents/scripts/lib/label-taxonomy.js +1 -12
  80. package/.agents/scripts/lib/observability/active-story-env.js +42 -163
  81. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  82. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  83. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  84. package/.agents/scripts/lib/observability/source-classifier.js +2 -2
  85. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  86. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  87. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  88. package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
  89. package/.agents/scripts/lib/orchestration/code-review.js +58 -168
  90. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
  91. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  92. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  93. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  94. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  95. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  96. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
  97. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  98. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  99. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
  100. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  101. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  102. package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
  103. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  104. package/.agents/scripts/lib/orchestration/plan-context.js +114 -24
  105. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
  106. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +3 -7
  107. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  108. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
  109. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
  110. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  111. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -75
  112. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
  113. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
  114. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  115. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
  116. package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
  117. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  118. package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
  119. package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
  120. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  121. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  122. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  123. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  124. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  125. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  126. package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
  127. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
  128. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  129. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  130. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +3 -12
  131. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  132. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
  133. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  134. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  135. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
  136. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  137. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  138. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
  139. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  140. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  141. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -32
  142. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  143. package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
  144. package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
  145. package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
  146. package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
  147. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  148. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
  149. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
  150. package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
  151. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
  152. package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
  153. package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
  154. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  155. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  156. package/.agents/scripts/lib/planning-corpus.js +12 -286
  157. package/.agents/scripts/lib/preflight-runner.js +2 -2
  158. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  159. package/.agents/scripts/lib/signals/index.js +4 -17
  160. package/.agents/scripts/lib/signals/read.js +35 -35
  161. package/.agents/scripts/lib/signals/schema.js +8 -11
  162. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  163. package/.agents/scripts/lib/signals/write.js +0 -1
  164. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  165. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  166. package/.agents/scripts/lib/story-adjacency.js +8 -7
  167. package/.agents/scripts/lib/story-body/story-body.js +6 -5
  168. package/.agents/scripts/lib/templates/decomposer-prompts.js +7 -3
  169. package/.agents/scripts/lib/test-env.js +14 -1
  170. package/.agents/scripts/lib/test-tiers.js +0 -3
  171. package/.agents/scripts/lib/ticket-body-sections.js +0 -14
  172. package/.agents/scripts/lib/validation-evidence.js +31 -59
  173. package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
  174. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  175. package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
  176. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  177. package/.agents/scripts/plan-context.js +38 -6
  178. package/.agents/scripts/plan-persist.js +145 -35
  179. package/.agents/scripts/plan-run-epilogue.js +83 -38
  180. package/.agents/scripts/post-structured-comment.js +0 -38
  181. package/.agents/scripts/pr-watch-with-update.js +43 -22
  182. package/.agents/scripts/providers/github/compose.js +0 -1
  183. package/.agents/scripts/providers/github/errors.js +0 -19
  184. package/.agents/scripts/providers/github/issues.js +1 -11
  185. package/.agents/scripts/providers/github/mappers.js +5 -0
  186. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  187. package/.agents/scripts/providers/github/tickets.js +33 -153
  188. package/.agents/scripts/providers/github.js +17 -6
  189. package/.agents/scripts/resolve-stories.js +236 -0
  190. package/.agents/scripts/run-coverage.js +4 -1
  191. package/.agents/scripts/run-lint.js +2 -2
  192. package/.agents/scripts/run-verify.js +31 -2
  193. package/.agents/scripts/signals-view.js +9 -10
  194. package/.agents/scripts/single-story-close.js +173 -18
  195. package/.agents/scripts/single-story-confirm-merge.js +288 -15
  196. package/.agents/scripts/single-story-init.js +6 -10
  197. package/.agents/scripts/stories-wave-tick.js +79 -4
  198. package/.agents/scripts/story-plan.js +3 -3
  199. package/.agents/scripts/update-ticket-state.js +8 -50
  200. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  201. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  202. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  203. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  204. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  205. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  206. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  207. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  208. package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
  209. package/.agents/skills/core/scope-triage/SKILL.md +3 -0
  210. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  211. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  212. package/.agents/skills/skills.index.json +2 -12
  213. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  214. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  215. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  216. package/.agents/workflows/audit-architecture.md +3 -4
  217. package/.agents/workflows/audit-clean-code.md +4 -4
  218. package/.agents/workflows/audit-documentation.md +4 -5
  219. package/.agents/workflows/audit-lighthouse.md +8 -0
  220. package/.agents/workflows/audit-navigability.md +10 -0
  221. package/.agents/workflows/audit-performance.md +2 -3
  222. package/.agents/workflows/audit-quality.md +8 -9
  223. package/.agents/workflows/audit-security.md +1 -2
  224. package/.agents/workflows/audit-seo.md +10 -0
  225. package/.agents/workflows/audit-ux-ui.md +7 -0
  226. package/.agents/workflows/deliver.md +98 -45
  227. package/.agents/workflows/git-cleanup.md +2 -2
  228. package/.agents/workflows/git-deliver.md +1 -1
  229. package/.agents/workflows/helpers/acceptance-self-eval.md +21 -13
  230. package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
  231. package/.agents/workflows/helpers/code-review.md +12 -10
  232. package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
  233. package/.agents/workflows/helpers/deliver-story.md +193 -118
  234. package/.agents/workflows/helpers/parallel-tooling.md +2 -2
  235. package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
  236. package/.agents/workflows/plan.md +184 -19
  237. package/.agents/workflows/qa-assist.md +6 -6
  238. package/.agents/workflows/qa-explore.md +3 -3
  239. package/.agents/workflows/qa-run.md +1 -5
  240. package/bin/mandrel.js +12 -1
  241. package/docs/CHANGELOG.md +40 -0
  242. package/lib/cli/registry.js +262 -19
  243. package/lib/cli/sync-agents.js +157 -0
  244. package/lib/cli/sync-commands.js +115 -6
  245. package/lib/cli/sync.js +168 -6
  246. package/lib/cli/update.js +105 -8
  247. package/lib/cli/version-helpers.js +131 -0
  248. package/lib/migrations/README.md +7 -5
  249. package/lib/migrations/index.js +12 -9
  250. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  251. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  252. package/package.json +1 -1
  253. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  254. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  255. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  256. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  257. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  258. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  259. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  260. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  261. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  262. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  263. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  264. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  265. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  266. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  267. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  268. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  269. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  270. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  271. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  272. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  273. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  274. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  275. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  276. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  277. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  278. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  279. package/.agents/schemas/risk-verdict.schema.json +0 -53
  280. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  281. package/.agents/scripts/analyze-execution.js +0 -444
  282. package/.agents/scripts/check-prepush-recovery.js +0 -90
  283. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  284. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  285. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
  286. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  287. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  288. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  289. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
  290. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
  291. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  292. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  293. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  294. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  295. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  296. package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
  297. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  298. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  299. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  300. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
  301. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  302. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  303. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  304. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
  305. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  306. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
  307. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  308. package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
  309. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
  310. package/.agents/scripts/resolve-plan-run.js +0 -117
  311. package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
@@ -4,15 +4,31 @@
4
4
  *
5
5
  * Ordered, fail-closed pipeline:
6
6
  *
7
- * 1. Risk-verdict presence (schema validation is CLI-owned)
8
- * 2. Ticket validator + file-assumption + DAG + capacity + budget
9
- * 3. Draft reachability (named soft failure, exit 3)
10
- * 4. Split-policy partition (`assertAcceptancePartition`) + spec fold/spill
11
- * 5. Create Story issues (`type::story` + `agent::ready`; `plan-run::`
12
- * label when N>1)
13
- * 6. Upsert `risk-verdict` + `story-plan-state` on every created Story;
14
- * upsert `plan-summary` on the primary Story
15
- * 7. Temp cleanup at terminal success only
7
+ * 1. Ticket validator + file-assumption + DAG + capacity + budget
8
+ * 2. Draft reachability (named soft failure, exit 3)
9
+ * 3. Split-policy partition (`assertAcceptancePartition`) + spec fold/spill
10
+ * 4. Create Story issues (`type::story` + sanitized authored labels —
11
+ * deliberately NOT `agent::ready`), resumably via a plan fingerprint
12
+ * 5. Upsert `story-plan-state` on every created Story; upsert `plan-summary`
13
+ * on the primary Story
14
+ * 6. Flip every Story to `agent::ready` the terminal step, so `ready`
15
+ * always implies "checkpoints written"
16
+ * 7. Comment on + close the superseded `--tickets` source issues
17
+ * (Story #4535) — bookkeeping only; never fails the run
18
+ * 8. Temp cleanup at terminal success + a stale-plan-dir reap
19
+ *
20
+ * **Why `agent::ready` moved to the end (Story #4541).** Issues used to be
21
+ * born `agent::ready` in the creating POST while the checkpoints were
22
+ * written afterwards. Anything picking a Story up in that window — or after
23
+ * a comment failure aborted the loop — read the checkpoint as `null`
24
+ * (`story-plan-state.js` degrades missing/malformed to `null`). Creating
25
+ * unlabelled, writing checkpoints, then flipping closes that race.
26
+ *
27
+ * **No authored risk artifact (Story #4542).** Persist neither requires nor
28
+ * accepts a risk verdict, derives no envelope from one, and computes no
29
+ * review routing. Review depth and the acceptance-critic mode are derived from
30
+ * the diff at close time (`review-depth.js#deriveChangeLevel`); `--force-review`
31
+ * is an explicit operator flag, recorded here as a receipt and never inferred.
16
32
  *
17
33
  * Hard cutover: no Epic parent, no reconciler, no `deliveryShape`, no
18
34
  * `--amend` tree cascades. Those surfaces die with Stages 4–5 for any
@@ -21,8 +37,10 @@
21
37
  * @module lib/orchestration/plan-persist/run-plan-persist
22
38
  */
23
39
 
24
- import { rm } from 'node:fs/promises';
40
+ import { readdir, rm, stat } from 'node:fs/promises';
41
+ import path from 'node:path';
25
42
 
43
+ import { anchorTempRoot, tempRootFrom } from '../../config/temp-paths.js';
26
44
  import { getLimits, PROJECT_ROOT } from '../../config-resolver.js';
27
45
  import { gitSpawn } from '../../git-utils.js';
28
46
  import { Logger } from '../../Logger.js';
@@ -37,20 +55,23 @@ import {
37
55
  evaluateDraftReachability,
38
56
  renderReachabilityOrphans,
39
57
  } from '../plan-reachability.js';
40
- import { resolveReviewRouting } from '../plan-review-routing.js';
41
- import { deriveRiskEnvelope } from '../planning-risk.js';
42
58
  import { upsertStructuredComment } from '../ticketing.js';
43
59
  import {
44
60
  enforceFanOutGate,
45
61
  surfaceSoftConflictFindings,
46
62
  } from './fan-out-gate.js';
47
- import { validateTickets } from './persist-helpers.js';
48
- import { assemblePlanStories, createStoryIssues } from './story-ops.js';
63
+ import { resolveBaseBranchRef, validateTickets } from './persist-helpers.js';
64
+ import {
65
+ assemblePlanStories,
66
+ createStoryIssues,
67
+ markStoriesReady,
68
+ } from './story-ops.js';
49
69
  import {
50
70
  buildPlanSummaryCommentBody,
51
71
  buildWaveTable,
52
72
  PLAN_SUMMARY_COMMENT_TYPE,
53
73
  } from './summary.js';
74
+ import { closeSupersededTickets } from './supersede-ops.js';
54
75
 
55
76
  /** Checkpoint schema version written on each Story's story-plan-state. */
56
77
  const PLAN_CHECKPOINT_SCHEMA_VERSION_V2 = 2;
@@ -88,6 +109,25 @@ export async function writeCheckpointV2(provider, storyId, state) {
88
109
  return state;
89
110
  }
90
111
 
112
+ /**
113
+ * Enforce the ticket validator's findings and report what the file-assumption
114
+ * gate actually concluded.
115
+ *
116
+ * The return value feeds the posted `plan-summary`'s freshness line, which
117
+ * used to be hard-coded `{ stale: 0, ambiguous: 0 }` — so the comment read
118
+ * "Spec freshness: clean" even on the one run where the gate had *given up*
119
+ * (Story #4541's unresolvable-base-ref downgrade). The summary asserted a
120
+ * clean result precisely when it had the least evidence for one.
121
+ *
122
+ * The mapping is deliberate. A confirmed mismatch is never reported here at
123
+ * all — it throws, and the run posts no summary. The only findings that
124
+ * survive to the summary are ones the gate could not *verify*, because the
125
+ * base ref they were computed against does not resolve; unverifiable is
126
+ * `ambiguous`, not `stale`.
127
+ *
128
+ * @returns {{ stale: number, ambiguous: number }} Freshness counts for the
129
+ * posted summary.
130
+ */
91
131
  function enforceTicketValidation(validated, { config, settings, cwd }) {
92
132
  const validationErrors = validated.errors ?? [];
93
133
  const assumptionFailures = validationErrors.filter((error) =>
@@ -102,8 +142,15 @@ function enforceTicketValidation(validated, { config, settings, cwd }) {
102
142
  `hard error(s):\n${blockingErrors.map((error) => ` - ${error}`).join('\n')}`,
103
143
  );
104
144
  }
105
- if (assumptionFailures.length === 0) return;
106
- const gateBaseRef = config?.baseBranch ?? settings?.baseBranch ?? 'main';
145
+ if (assumptionFailures.length === 0) return { stale: 0, ambiguous: 0 };
146
+ // Story #4541: resolve through the canonical `project.baseBranch` (the
147
+ // shape `config-resolver` actually emits) with the legacy settings bag as
148
+ // a fallback — reading a bare `config.baseBranch` meant this probe always
149
+ // targeted the literal `main`.
150
+ const gateBaseRef =
151
+ config?.project?.baseBranch ??
152
+ settings?.baseBranch ??
153
+ resolveBaseBranchRef(config);
107
154
  const refResolves =
108
155
  gitSpawn(
109
156
  cwd ?? process.cwd(),
@@ -122,16 +169,155 @@ function enforceTicketValidation(validated, { config, settings, cwd }) {
122
169
  `[plan-persist] file-assumption gate skipped: base ref '${gateBaseRef}' ` +
123
170
  `does not resolve — ${assumptionFailures.length} finding(s) downgraded.`,
124
171
  );
172
+ return { stale: 0, ambiguous: assumptionFailures.length };
125
173
  }
126
174
 
127
- function riskVerdictCommentBody(riskVerdict) {
128
- return [
129
- '### risk-verdict',
130
- '',
131
- '```json',
132
- JSON.stringify(riskVerdict, null, 2),
133
- '```',
134
- ].join('\n');
175
+ /**
176
+ * Run the supersede close phase behind a belt-and-braces guard.
177
+ *
178
+ * `closeSupersededTickets` already swallows per-ticket failures, but this
179
+ * phase runs *after* `createIssue`, so an unexpected throw anywhere in it
180
+ * would leave the run half-done with Stories already live. Degrade to a
181
+ * reported failure instead.
182
+ *
183
+ * @returns {Promise<import('./supersede-ops.js').SupersedeReport>}
184
+ */
185
+ async function runSupersedePhase(args) {
186
+ try {
187
+ return await closeSupersededTickets(args);
188
+ } catch (err) {
189
+ Logger.warn(
190
+ `[plan-persist] supersede close phase failed: ${err.message} — ` +
191
+ 'Stories were created; close the source tickets by hand.',
192
+ );
193
+ return {
194
+ enabled: true,
195
+ dryRun: args.dryRun === true,
196
+ reason: `phase-error: ${err.message}`,
197
+ closed: [],
198
+ planned: [],
199
+ skipped: [],
200
+ failed: (args.sourceTicketIds ?? []).map((ticket) => ({
201
+ ticket,
202
+ reason: err.message,
203
+ })),
204
+ };
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Render the plan-metrics line for the **posted** `plan-summary` comment,
210
+ * scoped to this invocation.
211
+ *
212
+ * The ordering hazard this closes: the ledger record for the current run is
213
+ * appended by `recordPlanInvocation`'s `finally`, which by construction only
214
+ * fires once `runPlanPersist` has *resolved* — long after this comment body
215
+ * is composed. A plain `since`-filtered ledger read therefore summarized
216
+ * every run except the one being summarized, and on an otherwise-quiet
217
+ * ledger it rendered "plan-metrics: no invocations recorded" onto the very
218
+ * comment reporting the run. (The stdout envelope was always correct: its
219
+ * `attachPlanMetrics` read runs after the wrapper returns.)
220
+ *
221
+ * **The `finally` stays where it is.** It is what guarantees a persist that
222
+ * throws is still recorded, so moving the append earlier — or moving this
223
+ * read later, past the GitHub writes it feeds — would trade a cosmetic bug
224
+ * for a real one. Instead of racing it, fold in a synthetic record standing
225
+ * for the in-flight invocation. It is the same record the wrapper is about to
226
+ * write, minus a final duration; it cannot double-count, because the real one
227
+ * does not exist yet at this point in the pipeline.
228
+ *
229
+ * `ok: true` is honest here: this line is only ever composed on the success
230
+ * path, after `createStoryIssues` has returned.
231
+ *
232
+ * @param {{ config: object, since: string, startedAt: string, mode: string }} args
233
+ * @returns {Promise<string|null>}
234
+ */
235
+ async function renderRunScopedPlanMetricsLine({
236
+ config,
237
+ since,
238
+ startedAt,
239
+ mode,
240
+ }) {
241
+ try {
242
+ const ledger = await readPlanMetrics(null, config);
243
+ const endedAt = new Date().toISOString();
244
+ const inFlight = {
245
+ v: 1,
246
+ cli: 'plan-persist',
247
+ mode,
248
+ epicId: null,
249
+ startedAt,
250
+ endedAt,
251
+ durationMs: Math.max(0, Date.parse(endedAt) - Date.parse(startedAt)) || 0,
252
+ ok: true,
253
+ };
254
+ const summary = summarizePlanMetrics(
255
+ { ...ledger, entries: [...(ledger.entries ?? []), inFlight] },
256
+ { since },
257
+ );
258
+ return renderPlanMetricsSummaryLine(summary);
259
+ } catch (err) {
260
+ Logger.warn(`[plan-persist] plan-metrics summary skipped: ${err.message}`);
261
+ return null;
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Age after which an abandoned `temp/plan-*` directory is reaped. A plan run
267
+ * that is still being authored is minutes-to-hours old; a week is far past
268
+ * any live run and comfortably past an operator returning to a paused one.
269
+ */
270
+ const STALE_PLAN_DIR_MS = 7 * 24 * 60 * 60 * 1000;
271
+
272
+ /**
273
+ * Reap abandoned `plan-*` directories under the temp root (Story #4541).
274
+ *
275
+ * Terminal-success cleanup only ever removed the *current* run's `planDir`,
276
+ * so every plan that failed a gate, was abandoned mid-authoring, or ran
277
+ * `--dry-run` left its directory behind forever. This sweeps the stragglers
278
+ * on each persist.
279
+ *
280
+ * Best-effort throughout: this is hygiene, never a reason to fail a run that
281
+ * has already created Stories. The current run's own `planDir` is always
282
+ * excluded — its cleanup is the caller's decision.
283
+ *
284
+ * @param {{ config?: object, keepDir?: string|null, now?: number }} args
285
+ * @returns {Promise<{ reaped: string[] }>}
286
+ */
287
+ export async function reapStalePlanDirs({
288
+ config = {},
289
+ keepDir = null,
290
+ now = Date.now(),
291
+ } = {}) {
292
+ const reaped = [];
293
+ const tempRoot = anchorTempRoot(tempRootFrom(config));
294
+ let entries;
295
+ try {
296
+ entries = await readdir(tempRoot, { withFileTypes: true });
297
+ } catch {
298
+ return { reaped }; // No temp root yet — nothing to reap.
299
+ }
300
+ const keep = keepDir ? path.resolve(keepDir) : null;
301
+ for (const entry of entries) {
302
+ if (!entry.isDirectory() || !entry.name.startsWith('plan-')) continue;
303
+ const dir = path.resolve(tempRoot, entry.name);
304
+ if (keep !== null && dir === keep) continue;
305
+ try {
306
+ const { mtimeMs } = await stat(dir);
307
+ if (now - mtimeMs < STALE_PLAN_DIR_MS) continue;
308
+ await rm(dir, { recursive: true, force: true });
309
+ reaped.push(dir);
310
+ } catch {
311
+ // A racing writer or a permission error: leave it for the next run.
312
+ }
313
+ }
314
+ if (reaped.length > 0) {
315
+ Logger.info(
316
+ `[plan-persist] reaped ${reaped.length} abandoned plan director(ies) ` +
317
+ `older than 7d under ${tempRoot}.`,
318
+ );
319
+ }
320
+ return { reaped };
135
321
  }
136
322
 
137
323
  /**
@@ -141,7 +327,6 @@ function riskVerdictCommentBody(riskVerdict) {
141
327
  * provider: object,
142
328
  * artifacts: {
143
329
  * stories: Array<object>,
144
- * riskVerdict: object,
145
330
  * techSpecContent?: string|null,
146
331
  * planAcceptance?: string[]|null,
147
332
  * },
@@ -153,10 +338,12 @@ function riskVerdictCommentBody(riskVerdict) {
153
338
  * allowLargeFanOut?: boolean,
154
339
  * skipCleanup?: boolean,
155
340
  * dryRun?: boolean,
156
- * planRunId?: string,
157
341
  * planDir?: string,
158
342
  * fanOutCounter?: Function,
159
343
  * cwd?: string,
344
+ * sourceTicketIds?: number[],
345
+ * sourceTicketOrigin?: 'flag'|'envelope'|'none',
346
+ * closeSuperseded?: boolean,
160
347
  * },
161
348
  * }} input
162
349
  */
@@ -169,7 +356,6 @@ export async function runPlanPersist({
169
356
  }) {
170
357
  const {
171
358
  stories: rawStories = null,
172
- riskVerdict,
173
359
  techSpecContent = null,
174
360
  planAcceptance = null,
175
361
  } = artifacts ?? {};
@@ -179,24 +365,20 @@ export async function runPlanPersist({
179
365
  allowLargeFanOut = false,
180
366
  skipCleanup = false,
181
367
  dryRun = false,
182
- planRunId,
183
368
  planDir = null,
184
369
  fanOutCounter = undefined,
185
370
  cwd = PROJECT_ROOT,
371
+ sourceTicketIds = [],
372
+ sourceTicketOrigin = 'none',
373
+ closeSuperseded = true,
186
374
  } = opts;
187
375
 
188
- if (!riskVerdict || !Array.isArray(riskVerdict.axes)) {
189
- throw new Error(
190
- '[plan-persist] risk verdict is required author risk-verdict.json ' +
191
- 'and pass it with --risk-verdict.',
192
- );
193
- }
194
- if ('deliveryShape' in (riskVerdict ?? {})) {
195
- throw new Error(
196
- '[plan-persist] risk-verdict.deliveryShape was removed in v2 Stage 3 — ' +
197
- 'delete the field; persist always creates Story issue(s).',
198
- );
199
- }
376
+ // Boundary for the plan-metrics summary below: everything this invocation
377
+ // appends to the ledger is stamped at or after this instant, so filtering
378
+ // on it scopes the counts to *this* run rather than every plan ever run
379
+ // through the shared standalone ledger (Story #4541).
380
+ const runStartedAt = opts.metricsSince ?? new Date().toISOString();
381
+
200
382
  if (!Array.isArray(rawStories) || rawStories.length === 0) {
201
383
  throw new Error(
202
384
  '[plan-persist] stories payload must be a non-empty array ' +
@@ -228,7 +410,11 @@ export async function runPlanPersist({
228
410
  });
229
411
  enforceFanOutGate(validated.findings, allowLargeFanOut, 'plan-persist');
230
412
  surfaceSoftConflictFindings(validated.findings, 'plan-persist');
231
- enforceTicketValidation(validated, { config, settings, cwd });
413
+ const freshness = enforceTicketValidation(validated, {
414
+ config,
415
+ settings,
416
+ cwd,
417
+ });
232
418
 
233
419
  const reachability = evaluateDraftReachability({
234
420
  tickets: rawStories,
@@ -254,7 +440,6 @@ export async function runPlanPersist({
254
440
 
255
441
  const critics = evaluatePlanCritics({
256
442
  techSpecContent: techSpecContent ?? '',
257
- riskVerdict,
258
443
  tickets: rawStories,
259
444
  config,
260
445
  });
@@ -280,18 +465,13 @@ export async function runPlanPersist({
280
465
  const { stories } = assemblePlanStories(rawStories, {
281
466
  sharedSpec: techSpecContent,
282
467
  planAcceptance: planAcceptance ?? undefined,
468
+ sourceTicketIds,
283
469
  });
284
470
 
285
- const planningRisk = deriveRiskEnvelope(riskVerdict);
286
- const reviewRouting = resolveReviewRouting({
287
- planningRisk,
288
- forceReview,
289
- });
290
-
291
- const { created, planRunLabel } = await createStoryIssues({
471
+ const { created } = await createStoryIssues({
292
472
  provider,
293
473
  stories,
294
- opts: { planRunId, dryRun },
474
+ opts: { dryRun },
295
475
  });
296
476
 
297
477
  const primary = created[0];
@@ -303,44 +483,39 @@ export async function runPlanPersist({
303
483
  })),
304
484
  );
305
485
 
306
- let planMetricsLine = null;
307
- try {
308
- const metrics = summarizePlanMetrics(await readPlanMetrics(config));
309
- planMetricsLine = renderPlanMetricsSummaryLine(metrics);
310
- } catch {
311
- planMetricsLine = null;
312
- }
486
+ // Story #4541: `readPlanMetrics` is declared `(epicId, config)` but was
487
+ // called with `config` first, so the ledger path resolver received the
488
+ // config object as an `epicId` and threw its guard on every single run —
489
+ // a throw this try/catch then swallowed into a silently absent summary.
490
+ // v2 persist is always Epic-less, hence the explicit `null`. The `since`
491
+ // filter keeps the counts about this invocation, and the in-flight record
492
+ // is folded in because the wrapper has not written it yet — see
493
+ // `renderRunScopedPlanMetricsLine`.
494
+ const planMetricsLine = await renderRunScopedPlanMetricsLine({
495
+ config,
496
+ since: runStartedAt,
497
+ startedAt: runStartedAt,
498
+ mode: dryRun ? 'dry-run' : 'persist',
499
+ });
313
500
 
314
501
  const summaryBody = buildPlanSummaryCommentBody({
315
502
  epicId: primary.id,
316
503
  ticketCount: created.length,
317
- planningRisk,
318
- reviewRouting,
319
- freshness: { stale: 0, ambiguous: 0 },
504
+ forceReview,
505
+ freshness,
320
506
  healthcheck: { skipped: true },
321
507
  waveTable,
322
508
  mode: 'stories',
323
509
  planMetricsLine,
324
510
  stories: created,
325
- planRunLabel,
326
511
  });
327
512
 
328
513
  if (!dryRun) {
329
514
  for (const story of created) {
330
- await upsertStructuredComment(
331
- provider,
332
- story.id,
333
- 'risk-verdict',
334
- riskVerdictCommentBody(riskVerdict),
335
- );
336
515
  await writeCheckpointV2(provider, story.id, {
337
- planningRisk,
338
- riskVerdict,
339
- reviewRouting,
340
516
  persist: {
341
517
  completedAt: new Date().toISOString(),
342
518
  storyCount: created.length,
343
- planRunLabel,
344
519
  primaryStoryId: primary.id,
345
520
  stories: created.map((createdStory) => ({
346
521
  slug: createdStory.slug,
@@ -355,8 +530,26 @@ export async function runPlanPersist({
355
530
  PLAN_SUMMARY_COMMENT_TYPE,
356
531
  summaryBody,
357
532
  );
533
+
534
+ // Terminal step: every checkpoint above is now on every Story, so
535
+ // `agent::ready` can honestly mean "fully persisted" (Story #4541).
536
+ // Anything that picks a Story up from here reads a real checkpoint.
537
+ await markStoriesReady({ provider, created });
358
538
  }
359
539
 
540
+ const supersede = await runSupersedePhase({
541
+ provider,
542
+ stories,
543
+ created,
544
+ sourceTicketIds,
545
+ dryRun,
546
+ closeSuperseded,
547
+ });
548
+ // Record which channel the ids came from so a run that superseded nothing
549
+ // says *why* (`none` = neither the envelope nor --source-tickets carried
550
+ // any) rather than reading as a clean no-op — Story #4554.
551
+ supersede.sourceTicketOrigin = sourceTicketOrigin;
552
+
360
553
  if (!skipCleanup && planDir) {
361
554
  try {
362
555
  await rm(planDir, { recursive: true, force: true });
@@ -364,21 +557,33 @@ export async function runPlanPersist({
364
557
  Logger.warn(`[plan-persist] temp cleanup skipped: ${err.message}`);
365
558
  }
366
559
  }
560
+ // Terminal-success cleanup only ever removes *this* run's planDir, so
561
+ // abandoned ones accumulated forever. Sweep them (Story #4541).
562
+ await reapStalePlanDirs({ config, keepDir: skipCleanup ? planDir : null });
367
563
 
564
+ const adopted = created.filter((story) => story.adopted);
565
+ if (adopted.length > 0) {
566
+ Logger.info(
567
+ `[plan-persist] resumed ${adopted.length} of ${created.length} Story(ies) ` +
568
+ `from a previous persist: ${adopted.map((s2) => `#${s2.id}`).join(', ')}.`,
569
+ );
570
+ }
368
571
  Logger.info(
369
572
  `[plan-persist] Persisted ${created.length} Story(ies)` +
370
- (planRunLabel ? ` under ${planRunLabel}` : '') +
371
573
  `; primary #${primary.id} is agent::ready.`,
372
574
  );
575
+ Logger.info(
576
+ `[plan-persist] Deliver with: /deliver ${created.map((s2) => s2.id).join(' ')}`,
577
+ );
373
578
 
374
579
  return {
375
580
  stories: created,
376
581
  primaryStoryId: primary.id,
377
- planRunLabel,
378
- planningRisk,
379
- reviewRouting,
582
+ forceReview,
380
583
  critics,
381
584
  reachability,
585
+ freshness,
382
586
  waveTable,
587
+ supersede,
383
588
  };
384
589
  }