mandrel 1.94.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 (560) hide show
  1. package/.agents/README.md +116 -99
  2. package/.agents/agents/acceptance-critic.md +9 -7
  3. package/.agents/agents/story-worker.md +45 -51
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +498 -1287
  6. package/.agents/docs/agentrc-reference.json +185 -80
  7. package/.agents/docs/configuration.md +132 -190
  8. package/.agents/docs/execution-reference.md +51 -25
  9. package/.agents/docs/quality-gates.md +25 -33
  10. package/.agents/docs/workflows.md +8 -8
  11. package/.agents/instructions.md +113 -139
  12. package/.agents/rules/ci-remediation.md +11 -15
  13. package/.agents/rules/git-conventions-reference.md +48 -58
  14. package/.agents/rules/git-conventions.md +16 -22
  15. package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
  16. package/.agents/schemas/agentrc.schema.json +83 -254
  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/retro.end.schema.json +1 -1
  24. package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
  25. package/.agents/schemas/signal-event.schema.json +3 -3
  26. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  27. package/.agents/schemas/validation-evidence.schema.json +1 -1
  28. package/.agents/scripts/acceptance-eval.js +22 -66
  29. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  30. package/.agents/scripts/audit-to-stories.js +7 -7
  31. package/.agents/scripts/boot-sweep.js +1 -1
  32. package/.agents/scripts/bootstrap.js +3 -3
  33. package/.agents/scripts/check-dead-exports.js +43 -104
  34. package/.agents/scripts/check-doc-links.js +2 -2
  35. package/.agents/scripts/check-lifecycle-lint.js +7 -10
  36. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  37. package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
  38. package/.agents/scripts/deliver-recover.js +122 -0
  39. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  40. package/.agents/scripts/evidence-gate.js +20 -50
  41. package/.agents/scripts/generate-skills-index.js +17 -1
  42. package/.agents/scripts/generate-workflows-doc.js +4 -4
  43. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  44. package/.agents/scripts/lib/Logger.js +6 -10
  45. package/.agents/scripts/lib/audit-suite/runner.js +2 -2
  46. package/.agents/scripts/lib/audit-suite/selector.js +328 -28
  47. package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
  48. package/.agents/scripts/lib/baselines/kernel.js +206 -18
  49. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -15
  50. package/.agents/scripts/lib/baselines/reader.js +1 -6
  51. package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
  52. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  53. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
  54. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  55. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  56. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  57. package/.agents/scripts/lib/checks/core-bare-clean.js +6 -3
  58. package/.agents/scripts/lib/checks/index.js +3 -2
  59. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  60. package/.agents/scripts/lib/checks/state.js +17 -248
  61. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +26 -24
  62. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  63. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  64. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  65. package/.agents/scripts/lib/cli/standard-args.js +13 -22
  66. package/.agents/scripts/lib/cli-args.js +39 -9
  67. package/.agents/scripts/lib/close-validation/gates.js +15 -15
  68. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  69. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  70. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  71. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  72. package/.agents/scripts/lib/config/ci.js +6 -31
  73. package/.agents/scripts/lib/config/delivery-routing.js +52 -35
  74. package/.agents/scripts/lib/config/explain.js +61 -48
  75. package/.agents/scripts/lib/config/github.js +7 -5
  76. package/.agents/scripts/lib/config/limits.js +29 -80
  77. package/.agents/scripts/lib/config/paths.js +0 -2
  78. package/.agents/scripts/lib/config/quality.js +12 -15
  79. package/.agents/scripts/lib/config/runners.js +20 -66
  80. package/.agents/scripts/lib/config/temp-paths.js +30 -63
  81. package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
  82. package/.agents/scripts/lib/config-resolver.js +2 -7
  83. package/.agents/scripts/lib/config-settings-schema-delivery.js +55 -161
  84. package/.agents/scripts/lib/config-settings-schema-quality.js +17 -16
  85. package/.agents/scripts/lib/config-settings-schema.js +100 -60
  86. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  87. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  88. package/.agents/scripts/lib/dependency-parser.js +3 -2
  89. package/.agents/scripts/lib/doc-tiers.js +2 -2
  90. package/.agents/scripts/lib/duplicate-search.js +242 -41
  91. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
  92. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  93. package/.agents/scripts/lib/format-generated-json.js +97 -0
  94. package/.agents/scripts/lib/framework-version.js +19 -190
  95. package/.agents/scripts/lib/gh-exec.js +8 -0
  96. package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
  97. package/.agents/scripts/lib/git-branch-lifecycle.js +17 -180
  98. package/.agents/scripts/lib/git-utils.js +32 -20
  99. package/.agents/scripts/lib/github/framework-repo.js +6 -0
  100. package/.agents/scripts/lib/json-utils.js +1 -2
  101. package/.agents/scripts/lib/label-constants.js +10 -38
  102. package/.agents/scripts/lib/label-taxonomy.js +10 -55
  103. package/.agents/scripts/lib/observability/active-story-env.js +44 -165
  104. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  105. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  106. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  107. package/.agents/scripts/lib/observability/source-classifier.js +5 -5
  108. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  109. package/.agents/scripts/lib/onboard/init-tail.js +1 -3
  110. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  111. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  112. package/.agents/scripts/lib/orchestration/ceremony-routing.js +105 -44
  113. package/.agents/scripts/lib/orchestration/code-review.js +78 -436
  114. package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
  115. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +4 -4
  116. package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
  117. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  118. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  119. package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
  120. package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
  121. package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
  122. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  123. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  124. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  125. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +17 -19
  126. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  127. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  128. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
  129. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +21 -43
  130. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  131. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  132. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
  133. package/.agents/scripts/lib/orchestration/merge-block-class.js +77 -21
  134. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  135. package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
  136. package/.agents/scripts/lib/orchestration/plan-context.js +354 -282
  137. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
  138. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +4 -8
  139. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  140. package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
  141. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +71 -0
  142. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +245 -0
  143. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  144. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +432 -858
  145. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +778 -0
  146. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +47 -115
  147. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  148. package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
  149. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
  150. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +52 -51
  151. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
  152. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +1 -1
  153. package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
  154. package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
  155. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  156. package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
  157. package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
  158. package/.agents/scripts/lib/orchestration/review-depth.js +126 -47
  159. package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
  160. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  161. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  162. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  163. package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
  164. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  165. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  166. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  167. package/.agents/scripts/lib/orchestration/run-epilogue.js +784 -0
  168. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +25 -1
  169. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  170. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  171. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +86 -41
  172. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  173. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +77 -32
  174. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  175. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  176. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +44 -0
  177. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  178. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  179. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +275 -46
  180. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  181. package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
  182. package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
  183. package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
  184. package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
  185. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
  186. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  187. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +28 -42
  188. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  189. package/.agents/scripts/lib/orchestration/story-follow-ups.js +298 -0
  190. package/.agents/scripts/lib/orchestration/story-init-remote.js +51 -0
  191. package/.agents/scripts/lib/orchestration/story-plan-state.js +33 -0
  192. package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
  193. package/.agents/scripts/lib/orchestration/task-body-validator.js +60 -25
  194. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  195. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +130 -75
  196. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +192 -377
  197. package/.agents/scripts/lib/orchestration/ticket-validator.js +123 -25
  198. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -72
  199. package/.agents/scripts/lib/orchestration/ticketing/reads.js +45 -55
  200. package/.agents/scripts/lib/orchestration/ticketing/transition.js +66 -6
  201. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  202. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  203. package/.agents/scripts/lib/planning-corpus.js +12 -281
  204. package/.agents/scripts/lib/preflight-runner.js +4 -4
  205. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
  206. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  207. package/.agents/scripts/lib/signals/index.js +4 -17
  208. package/.agents/scripts/lib/signals/read.js +35 -35
  209. package/.agents/scripts/lib/signals/schema.js +8 -11
  210. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  211. package/.agents/scripts/lib/signals/write.js +0 -1
  212. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  213. package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
  214. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  215. package/.agents/scripts/lib/story-adjacency.js +17 -19
  216. package/.agents/scripts/lib/story-body/story-body.js +130 -75
  217. package/.agents/scripts/lib/story-plan.js +2 -4
  218. package/.agents/scripts/lib/templates/decomposer-prompts.js +51 -46
  219. package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
  220. package/.agents/scripts/lib/test-env.js +14 -1
  221. package/.agents/scripts/lib/test-tiers.js +0 -3
  222. package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +25 -39
  223. package/.agents/scripts/lib/validation-evidence.js +32 -60
  224. package/.agents/scripts/lib/wave-runner/ready-set.js +38 -12
  225. package/.agents/scripts/lib/workspace-provisioner.js +1 -1
  226. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  227. package/.agents/scripts/lib/worktree/lifecycle/reap.js +72 -25
  228. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  229. package/.agents/scripts/lint-issue-body.js +71 -40
  230. package/.agents/scripts/mandrel-update-preflight.js +1 -1
  231. package/.agents/scripts/notify.js +4 -3
  232. package/.agents/scripts/plan-context.js +102 -80
  233. package/.agents/scripts/plan-persist.js +230 -279
  234. package/.agents/scripts/plan-run-epilogue.js +142 -0
  235. package/.agents/scripts/post-structured-comment.js +0 -38
  236. package/.agents/scripts/pr-watch-with-update.js +43 -22
  237. package/.agents/scripts/providers/github/compose.js +0 -1
  238. package/.agents/scripts/providers/github/errors.js +0 -19
  239. package/.agents/scripts/providers/github/issues.js +13 -39
  240. package/.agents/scripts/providers/github/mappers.js +5 -12
  241. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  242. package/.agents/scripts/providers/github/tickets.js +33 -156
  243. package/.agents/scripts/providers/github.js +17 -6
  244. package/.agents/scripts/resolve-stories.js +236 -0
  245. package/.agents/scripts/run-coverage.js +4 -1
  246. package/.agents/scripts/run-lint.js +2 -2
  247. package/.agents/scripts/run-verify.js +31 -2
  248. package/.agents/scripts/signals-view.js +25 -21
  249. package/.agents/scripts/single-story-close.js +178 -26
  250. package/.agents/scripts/single-story-confirm-merge.js +313 -24
  251. package/.agents/scripts/single-story-init.js +35 -30
  252. package/.agents/scripts/stories-wave-tick.js +85 -10
  253. package/.agents/scripts/story-plan.js +28 -49
  254. package/.agents/scripts/update-ticket-state.js +14 -65
  255. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  256. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  257. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  258. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  259. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  260. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  261. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  262. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  263. package/.agents/skills/core/idea-refinement/SKILL.md +9 -9
  264. package/.agents/skills/core/scope-triage/SKILL.md +31 -172
  265. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  266. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  267. package/.agents/skills/skills.index.json +3 -53
  268. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  269. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  270. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  271. package/.agents/starter-agentrc.json +0 -5
  272. package/.agents/templates/agent-protocol.md +9 -10
  273. package/.agents/workflows/audit-architecture.md +6 -7
  274. package/.agents/workflows/audit-clean-code.md +7 -7
  275. package/.agents/workflows/audit-dependencies.md +3 -3
  276. package/.agents/workflows/audit-devops.md +3 -3
  277. package/.agents/workflows/audit-documentation.md +9 -10
  278. package/.agents/workflows/audit-lighthouse.md +11 -3
  279. package/.agents/workflows/audit-navigability.md +13 -2
  280. package/.agents/workflows/audit-performance.md +5 -6
  281. package/.agents/workflows/audit-privacy.md +3 -3
  282. package/.agents/workflows/audit-quality.md +11 -12
  283. package/.agents/workflows/audit-security.md +4 -5
  284. package/.agents/workflows/audit-seo.md +13 -3
  285. package/.agents/workflows/audit-sre.md +3 -3
  286. package/.agents/workflows/audit-to-stories.md +20 -20
  287. package/.agents/workflows/audit-ux-ui.md +10 -3
  288. package/.agents/workflows/deliver.md +177 -176
  289. package/.agents/workflows/git-cleanup.md +5 -6
  290. package/.agents/workflows/git-deliver.md +1 -1
  291. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  292. package/.agents/workflows/helpers/acceptance-self-eval.md +35 -40
  293. package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
  294. package/.agents/workflows/helpers/code-review.md +75 -196
  295. package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +83 -44
  296. package/.agents/workflows/helpers/deliver-story.md +606 -0
  297. package/.agents/workflows/helpers/diagnose.md +10 -10
  298. package/.agents/workflows/helpers/parallel-tooling.md +3 -3
  299. package/.agents/workflows/helpers/signals.md +16 -16
  300. package/.agents/workflows/helpers/worktree-lifecycle.md +66 -86
  301. package/.agents/workflows/mandrel-update.md +2 -1
  302. package/.agents/workflows/plan.md +277 -145
  303. package/.agents/workflows/qa-assist.md +27 -33
  304. package/.agents/workflows/qa-explore.md +29 -38
  305. package/.agents/workflows/qa-run.md +2 -6
  306. package/README.md +9 -8
  307. package/bin/mandrel.js +12 -1
  308. package/docs/CHANGELOG.md +70 -0
  309. package/lib/cli/registry.js +262 -19
  310. package/lib/cli/sync-agents.js +157 -0
  311. package/lib/cli/sync-commands.js +115 -6
  312. package/lib/cli/sync.js +168 -6
  313. package/lib/cli/update.js +105 -8
  314. package/lib/cli/version-helpers.js +131 -0
  315. package/lib/migrations/README.md +7 -5
  316. package/lib/migrations/index.js +12 -8
  317. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  318. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  319. package/package.json +2 -2
  320. package/.agents/agents/retro.md +0 -42
  321. package/.agents/personas/architect.md +0 -113
  322. package/.agents/personas/devops-engineer.md +0 -38
  323. package/.agents/personas/engineer.md +0 -33
  324. package/.agents/personas/project-manager.md +0 -114
  325. package/.agents/personas/qa-engineer.md +0 -95
  326. package/.agents/personas/security-engineer.md +0 -111
  327. package/.agents/personas/technical-writer.md +0 -101
  328. package/.agents/schemas/dispatch-manifest.json +0 -232
  329. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  330. package/.agents/schemas/epic-spec.schema.json +0 -153
  331. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  332. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  333. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  334. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  335. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  336. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  337. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  338. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  339. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  340. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  341. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  342. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  343. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  344. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  345. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  346. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  347. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  348. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  349. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  350. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  351. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  352. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  353. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  354. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  355. package/.agents/schemas/lifecycle/slice.end.schema.json +0 -21
  356. package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +0 -20
  357. package/.agents/schemas/lifecycle/slice.start.schema.json +0 -17
  358. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  359. package/.agents/schemas/risk-verdict.schema.json +0 -66
  360. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  361. package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
  362. package/.agents/scripts/analyze-execution.js +0 -444
  363. package/.agents/scripts/bookkeeping-reconcile.js +0 -117
  364. package/.agents/scripts/check-prepush-recovery.js +0 -90
  365. package/.agents/scripts/dispatcher.js +0 -295
  366. package/.agents/scripts/epic-audit-prepare.js +0 -497
  367. package/.agents/scripts/epic-audit-recheck.js +0 -274
  368. package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
  369. package/.agents/scripts/epic-deliver-preflight.js +0 -462
  370. package/.agents/scripts/epic-deliver-prepare.js +0 -852
  371. package/.agents/scripts/epic-execute-record-wave.js +0 -449
  372. package/.agents/scripts/epic-plan-clarity.js +0 -211
  373. package/.agents/scripts/epic-plan-healthcheck.js +0 -581
  374. package/.agents/scripts/epic-reconcile.js +0 -625
  375. package/.agents/scripts/lib/baseline-snapshot.js +0 -979
  376. package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
  377. package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
  378. package/.agents/scripts/lib/config/lifecycle.js +0 -40
  379. package/.agents/scripts/lib/config/preflight.js +0 -58
  380. package/.agents/scripts/lib/config/retro.js +0 -77
  381. package/.agents/scripts/lib/epic-merge-lock.js +0 -322
  382. package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
  383. package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
  384. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  385. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  386. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -219
  387. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  388. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  389. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  390. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -270
  391. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -539
  392. package/.agents/scripts/lib/orchestration/deliver-route.js +0 -173
  393. package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
  394. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
  395. package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
  396. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
  397. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
  398. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
  399. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
  400. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
  401. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
  402. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
  403. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
  404. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
  405. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
  406. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
  407. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
  408. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/risk-verdict.js +0 -105
  409. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
  410. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
  411. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
  412. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -621
  413. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
  414. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
  415. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
  416. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
  417. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
  418. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
  419. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
  420. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
  421. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
  422. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
  423. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
  424. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
  425. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
  426. package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +0 -400
  427. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
  428. package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
  429. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
  430. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
  431. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
  432. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
  433. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
  434. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  435. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
  436. package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
  437. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
  438. package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +0 -270
  439. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
  440. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  441. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  442. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -465
  443. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
  444. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
  445. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
  446. package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -284
  447. package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
  448. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
  449. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
  450. package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
  451. package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
  452. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  453. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
  454. package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
  455. package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
  456. package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
  457. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  458. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  459. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  460. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  461. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -33
  462. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  463. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  464. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  465. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -396
  466. package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
  467. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  468. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
  469. package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
  470. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
  471. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
  472. package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
  473. package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
  474. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  475. package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
  476. package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
  477. package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
  478. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
  479. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
  480. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
  481. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
  482. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
  483. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
  484. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
  485. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
  486. package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
  487. package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
  488. package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
  489. package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
  490. package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
  491. package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
  492. package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
  493. package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
  494. package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
  495. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
  496. package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
  497. package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
  498. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
  499. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
  500. package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
  501. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -302
  502. package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
  503. package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
  504. package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
  505. package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
  506. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
  507. package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
  508. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
  509. package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
  510. package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
  511. package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
  512. package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
  513. package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
  514. package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
  515. package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
  516. package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
  517. package/.agents/scripts/lib/push-epic-retry.js +0 -209
  518. package/.agents/scripts/lib/spec/index.js +0 -36
  519. package/.agents/scripts/lib/spec/loader.js +0 -425
  520. package/.agents/scripts/lib/spec/state.js +0 -208
  521. package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
  522. package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
  523. package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
  524. package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
  525. package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
  526. package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
  527. package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
  528. package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
  529. package/.agents/scripts/lib/wave-runner/tick.js +0 -754
  530. package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
  531. package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
  532. package/.agents/scripts/lifecycle-emit.js +0 -510
  533. package/.agents/scripts/retro-run.js +0 -218
  534. package/.agents/scripts/slice-phase.js +0 -361
  535. package/.agents/scripts/standalone-feedback-rollup.js +0 -188
  536. package/.agents/scripts/story-close.js +0 -294
  537. package/.agents/scripts/story-init.js +0 -599
  538. package/.agents/scripts/story-phase.js +0 -369
  539. package/.agents/scripts/wave-tick.js +0 -464
  540. package/.agents/skills/core/analyze-execution/SKILL.md +0 -98
  541. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
  542. package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
  543. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
  544. package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
  545. package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
  546. package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
  547. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -383
  548. package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
  549. package/.agents/workflows/helpers/deliver-epic-reference.md +0 -547
  550. package/.agents/workflows/helpers/deliver-epic-single.md +0 -331
  551. package/.agents/workflows/helpers/deliver-epic.md +0 -998
  552. package/.agents/workflows/helpers/deliver-stories.md +0 -450
  553. package/.agents/workflows/helpers/epic-audit.md +0 -189
  554. package/.agents/workflows/helpers/epic-deliver-story.md +0 -436
  555. package/.agents/workflows/helpers/epic-testing.md +0 -125
  556. package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
  557. package/.agents/workflows/helpers/plan-epic.md +0 -353
  558. package/.agents/workflows/helpers/plan-story.md +0 -251
  559. package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
  560. package/.agents/workflows/helpers/single-story-deliver.md +0 -557
@@ -0,0 +1,298 @@
1
+ /**
2
+ * story-follow-ups.js — capture actionable follow-ups from a landed Story.
3
+ *
4
+ * Replaces the unwired Epic retro as the default closeout for v2: after a
5
+ * Story merges, read its standalone `signals.ndjson` friction stream, compose
6
+ * routed proposals, auto-file follow-up issues (when enabled), and upsert a
7
+ * structured `follow-ups` comment on the Story.
8
+ *
9
+ * @module lib/orchestration/story-follow-ups
10
+ */
11
+
12
+ import { graduateRetroProposals } from '../feedback-loop/retro-proposals-graduator.js';
13
+ import { DEFAULT_FRAMEWORK_REPO } from '../github/framework-repo.js';
14
+ import { Logger } from '../Logger.js';
15
+ import { forEachLine } from '../observability/signals-writer.js';
16
+ import { composeRoutedProposals } from './retro-proposals.js';
17
+ import { upsertStructuredComment } from './ticketing.js';
18
+
19
+ export const FOLLOW_UPS_COMMENT_TYPE = 'follow-ups';
20
+
21
+ /**
22
+ * @param {object} [config]
23
+ * @returns {{ frameworkRepo: string, consumerRepo: string, currentRepo: { owner: string, repo: string } }}
24
+ */
25
+ export function resolveFollowUpRepos(config) {
26
+ const owner =
27
+ typeof config?.github?.owner === 'string' ? config.github.owner.trim() : '';
28
+ const repo =
29
+ typeof config?.github?.repo === 'string' ? config.github.repo.trim() : '';
30
+ const consumerRepo =
31
+ owner && repo ? `${owner}/${repo}` : DEFAULT_FRAMEWORK_REPO;
32
+ const frameworkRepo =
33
+ typeof config?.github?.frameworkRepo === 'string' &&
34
+ config.github.frameworkRepo.trim()
35
+ ? config.github.frameworkRepo.trim()
36
+ : DEFAULT_FRAMEWORK_REPO;
37
+ const [cOwner, cRepo] = consumerRepo.split('/');
38
+ return {
39
+ frameworkRepo,
40
+ consumerRepo,
41
+ currentRepo: {
42
+ owner: cOwner || 'unknown',
43
+ repo: cRepo || 'unknown',
44
+ },
45
+ };
46
+ }
47
+
48
+ /**
49
+ * @param {number} storyId
50
+ * @param {object} [config]
51
+ * @returns {Promise<Array<{ category: string, source: 'framework'|'consumer' }>>}
52
+ */
53
+ export async function gatherStoryFrictionSignals(storyId, config) {
54
+ const signals = [];
55
+ await forEachLine(
56
+ null,
57
+ storyId,
58
+ (parsed) => {
59
+ if (!parsed || typeof parsed !== 'object') return;
60
+ const kind = parsed.kind;
61
+ if (kind !== 'friction' && kind !== undefined) {
62
+ // Prefer friction records; also accept category-bearing rows.
63
+ }
64
+ const category =
65
+ typeof parsed.category === 'string' ? parsed.category.trim() : '';
66
+ if (!category) return;
67
+ const source = parsed.source === 'framework' ? 'framework' : 'consumer';
68
+ signals.push({ category, source });
69
+ },
70
+ config,
71
+ );
72
+ return signals;
73
+ }
74
+
75
+ /**
76
+ * Render the empty-roll-up line.
77
+ *
78
+ * Story #4578 — an empty roll-up over a multi-Story run must NOT read as
79
+ * success. The pre-#4578 text ("No friction signals — nothing to follow up")
80
+ * was *truthful* about the stream and *false* about the run: a 7-Story
81
+ * delivery containing a mid-run git outage, a parked worker, and a
82
+ * four-round acceptance critic rendered byte-identically to a genuinely
83
+ * clean run. An operator cannot tell "nothing went wrong" from "the
84
+ * telemetry never fired", and the second is likeliest exactly when the run
85
+ * went worst.
86
+ *
87
+ * So the line is a function of `storyCount`:
88
+ * - `storyCount <= 1` → the honest, quiet reading is retained. A single
89
+ * Story that emitted nothing plausibly *was* clean, and crying wolf on
90
+ * every clean Story is how a warning channel gets tuned out.
91
+ * - `storyCount > 1` → zero signals across N Stories is a **claim**, and
92
+ * the surrounding text says so and names the two readings, rather than
93
+ * asserting the flattering one.
94
+ *
95
+ * This mirrors the sibling precedent in `run-epilogue.js`'s
96
+ * `renderDiffLines`, which refuses to let an unresolvable base diff render
97
+ * as "0 changed files".
98
+ *
99
+ * @param {number} storyCount
100
+ * @returns {string[]}
101
+ */
102
+ function renderEmptyRollupLines(storyCount) {
103
+ if (storyCount <= 1) {
104
+ return ['_No friction signals — nothing to follow up._'];
105
+ }
106
+ return [
107
+ `> ⚠️ **0 friction signals across ${storyCount} Stories — this is a claim, not a clean bill of health.**`,
108
+ '> Either the run was genuinely friction-free, or telemetry never fired.',
109
+ '> An empty stream is indistinguishable from a clean run, and it is least',
110
+ '> likely to fill exactly when a run is going badly and the agent is busy.',
111
+ '> The runtime emits friction from its own observables (`agent::blocked`',
112
+ '> transitions, failed closes, exhausted merge waits) — so zero here also',
113
+ '> means none of those fired. If the run had friction you can name, that',
114
+ '> gap is itself the follow-up worth filing.',
115
+ ];
116
+ }
117
+
118
+ /**
119
+ * @param {{
120
+ * storyId: number,
121
+ * proposals: object,
122
+ * graduated: object,
123
+ * storyCount?: number,
124
+ * }} args - `storyCount` (default 1) is how many Stories the roll-up spans;
125
+ * it decides whether an empty result reads as quiet or as a flagged claim.
126
+ * @returns {string}
127
+ */
128
+ export function buildFollowUpsCommentBody({
129
+ storyId,
130
+ proposals,
131
+ graduated,
132
+ storyCount = 1,
133
+ }) {
134
+ const filed = Array.isArray(graduated?.filed) ? graduated.filed : [];
135
+ const framework = proposals?.framework ?? [];
136
+ const consumer = proposals?.consumer ?? [];
137
+ const discarded = proposals?.discarded ?? [];
138
+ const lines = [
139
+ '### follow-ups',
140
+ '',
141
+ `Actionable follow-ups captured from Story #${storyId} after merge.`,
142
+ '',
143
+ ];
144
+ if (filed.length > 0) {
145
+ lines.push('**Filed**');
146
+ for (const item of filed) {
147
+ lines.push(
148
+ `- ${item.source}: ${item.title}${item.url ? ` — ${item.url}` : ''}`,
149
+ );
150
+ }
151
+ lines.push('');
152
+ }
153
+ if (framework.length + consumer.length > 0 && filed.length === 0) {
154
+ lines.push('**Actionable (not auto-filed)**');
155
+ for (const item of [...framework, ...consumer]) {
156
+ lines.push(`- ${item.source}: ${item.title}`);
157
+ lines.push('');
158
+ lines.push('```bash');
159
+ lines.push(item.command);
160
+ lines.push('```');
161
+ }
162
+ lines.push('');
163
+ }
164
+ if (discarded.length > 0) {
165
+ lines.push('**Single-occurrence (not filed)**');
166
+ for (const item of discarded) {
167
+ lines.push(`- ${item.source}: \`${item.category}\` ×${item.occurrences}`);
168
+ }
169
+ lines.push('');
170
+ }
171
+ if (
172
+ filed.length === 0 &&
173
+ framework.length === 0 &&
174
+ consumer.length === 0 &&
175
+ discarded.length === 0
176
+ ) {
177
+ lines.push(...renderEmptyRollupLines(storyCount));
178
+ lines.push('');
179
+ }
180
+ lines.push('```json');
181
+ lines.push(
182
+ JSON.stringify(
183
+ {
184
+ storyId,
185
+ storyCount,
186
+ framework: framework.map((i) => i.category),
187
+ consumer: consumer.map((i) => i.category),
188
+ discarded: discarded.map((i) => i.category),
189
+ filed: filed.map((i) => ({
190
+ category: i.category,
191
+ url: i.url ?? null,
192
+ })),
193
+ // Story #4578 — an empty roll-up over N>1 Stories is a claim worth
194
+ // flagging, not a success. Machine-readable twin of the warning
195
+ // prose so a caller need not regex the body.
196
+ emptyRollupSuspect:
197
+ storyCount > 1 &&
198
+ filed.length === 0 &&
199
+ framework.length === 0 &&
200
+ consumer.length === 0 &&
201
+ discarded.length === 0,
202
+ },
203
+ null,
204
+ 2,
205
+ ),
206
+ );
207
+ lines.push('```');
208
+ return lines.join('\n');
209
+ }
210
+
211
+ /**
212
+ * Capture and persist Story follow-ups. Never throws — the land must not
213
+ * fail because follow-up filing flaked.
214
+ *
215
+ * Story #4543 retired the `captureFollowUpsAfterConfirm` action-gate wrapper
216
+ * (and its `withConfirmFollowUps` sibling) that used to front this function.
217
+ * Re-deriving "did the merge land?" from a confirmation envelope's `action`
218
+ * field was the coupling that made close-and-land — the DEFAULT path — skip
219
+ * capture entirely: the gate only opened on the standalone CLI's `done`, and
220
+ * a belated manual confirm could not backfill because the Story was already
221
+ * `agent::done` (confirm returns `noop`, the gate never opens). The shared
222
+ * land tail (`single-story-close/phases/post-land.js`) now calls this
223
+ * directly, after the merge is already confirmed.
224
+ *
225
+ * @param {object} args
226
+ * @param {number} args.storyId
227
+ * @param {object} args.provider
228
+ * @param {object} [args.config]
229
+ * @param {string} [args.cwd]
230
+ * @param {(tag: string, msg: string) => void} [args.progress]
231
+ * @returns {Promise<object>}
232
+ */
233
+ export async function captureStoryFollowUps({
234
+ storyId,
235
+ provider,
236
+ config,
237
+ cwd,
238
+ progress,
239
+ }) {
240
+ const sid = Number(storyId);
241
+ if (!Number.isInteger(sid) || sid <= 0) {
242
+ return { ok: false, reason: 'invalid-story-id' };
243
+ }
244
+ try {
245
+ const signals = await gatherStoryFrictionSignals(sid, config);
246
+ const repos = resolveFollowUpRepos(config);
247
+ const proposals = composeRoutedProposals({
248
+ anchorId: sid,
249
+ anchorKind: 'story',
250
+ frameworkRepo: repos.frameworkRepo,
251
+ consumerRepo: repos.consumerRepo,
252
+ signals,
253
+ unresolvedBlockedEvents: [],
254
+ });
255
+ const graduated = await graduateRetroProposals({
256
+ epicId: sid,
257
+ provider,
258
+ config,
259
+ currentRepo: repos.currentRepo,
260
+ frameworkRepo: (() => {
261
+ const [owner, repo] = repos.frameworkRepo.split('/');
262
+ return { owner, repo };
263
+ })(),
264
+ routedProposals: proposals,
265
+ cwd,
266
+ });
267
+ const body = buildFollowUpsCommentBody({
268
+ storyId: sid,
269
+ proposals,
270
+ graduated,
271
+ });
272
+ await upsertStructuredComment(provider, sid, FOLLOW_UPS_COMMENT_TYPE, body);
273
+ progress?.(
274
+ 'FOLLOW-UPS',
275
+ `Captured follow-ups for Story #${sid} (filed=${graduated.filed?.length ?? 0}).`,
276
+ );
277
+ return {
278
+ ok: true,
279
+ storyId: sid,
280
+ proposals,
281
+ graduated,
282
+ signalCount: signals.length,
283
+ };
284
+ } catch (err) {
285
+ Logger.warn(
286
+ `[story-follow-ups] capture failed for #${sid}: ${err?.message ?? err}`,
287
+ );
288
+ progress?.(
289
+ 'FOLLOW-UPS',
290
+ `⚠️ Follow-up capture failed (close continues): ${err?.message ?? err}`,
291
+ );
292
+ return {
293
+ ok: false,
294
+ reason: 'capture-failed',
295
+ error: String(err?.message ?? err),
296
+ };
297
+ }
298
+ }
@@ -0,0 +1,51 @@
1
+ import { Logger } from '../Logger.js';
2
+ import {
3
+ STATE_LABELS,
4
+ transitionTicketState,
5
+ upsertStructuredComment,
6
+ } from './ticketing.js';
7
+
8
+ /**
9
+ * Fail closed when the repository remote cannot be verified. The Story is
10
+ * blocked before lease/branch/worktree mutation so a host that misses the
11
+ * result envelope cannot strand an executing Story.
12
+ */
13
+ export async function handleRemoteVerificationFailure({
14
+ provider,
15
+ storyId,
16
+ remote,
17
+ dryRun = false,
18
+ }) {
19
+ if (remote?.remoteVerified) return;
20
+ const message =
21
+ `[single-story-init] remote verification failed for Story #${storyId}: ` +
22
+ `${remote?.detail ?? 'origin is unavailable'}`;
23
+ if (!dryRun) {
24
+ try {
25
+ await upsertStructuredComment(
26
+ provider,
27
+ storyId,
28
+ 'friction',
29
+ `### Remote verification blocked delivery\n\n${message}`,
30
+ );
31
+ } catch (err) {
32
+ Logger.warn(
33
+ `[single-story-init] failed to post remote-verification friction: ${err?.message ?? err}`,
34
+ );
35
+ }
36
+ // Story #4539 — the canonical mutator. This is the path where the
37
+ // skipped Projects v2 column sync (Story #2548) visibly drifts: the
38
+ // Story is still agent::ready (To Do) when the remote probe fails, so
39
+ // a direct label write leaves the board reading To Do for a blocked
40
+ // Story. single-story-init.js's own comment explains exactly why this
41
+ // must not bypass the mutator.
42
+ try {
43
+ await transitionTicketState(provider, storyId, STATE_LABELS.BLOCKED, {});
44
+ } catch (err) {
45
+ Logger.warn(
46
+ `[single-story-init] failed to block Story after remote verification: ${err?.message ?? err}`,
47
+ );
48
+ }
49
+ }
50
+ throw new Error(message);
51
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * story-plan-state.js — read the v2 Story planning checkpoint.
3
+ *
4
+ * `plan-persist.js` upserts one `story-plan-state` structured comment per
5
+ * created Story carrying the persist receipt (when the plan completed, how many
6
+ * Stories it created, and their ids). Story #4542 removed the risk fields it
7
+ * used to carry: the planner-authored verdict, the envelope derived from it,
8
+ * and the review routing computed from that envelope. Nothing read any of them
9
+ * back — review depth is now derived from the diff at close time
10
+ * (`review-depth.js#deriveChangeLevel`), so no checkpoint read sits on the
11
+ * delivery path at all.
12
+ */
13
+
14
+ import { parseFencedJsonComment } from './structured-comment-parser.js';
15
+ import { findStructuredComment } from './ticketing.js';
16
+
17
+ /**
18
+ * Read the v2 Story planning checkpoint. Missing/malformed comments degrade
19
+ * to null — an unplanned Story simply has no persist receipt.
20
+ */
21
+ export async function readStoryPlanState({
22
+ provider,
23
+ storyId,
24
+ findCommentFn = findStructuredComment,
25
+ }) {
26
+ const comment = await findCommentFn(
27
+ provider,
28
+ Number(storyId),
29
+ 'story-plan-state',
30
+ );
31
+ const state = parseFencedJsonComment(comment);
32
+ return state && typeof state === 'object' ? state : null;
33
+ }
@@ -2,7 +2,7 @@
2
2
  * structured-comment-parser.js — shared parser for fenced-JSON structured
3
3
  * comments.
4
4
  *
5
- * Several callers (epic-runner Checkpointer, ProgressReporter
5
+ * Several callers (the deleted epic-runner Checkpointer/ProgressReporter,
6
6
  * `parseStoryRunProgressComment` / `parsePhaseTimingsComment`, wave-gate's
7
7
  * local `extractJsonBlock`) need to extract the `{...}` payload from the
8
8
  * fenced ```json``` block of a structured comment body. They had each open-
@@ -24,16 +24,23 @@
24
24
  * name at least one path-shaped token so vague verbs ("clean up",
25
25
  * "refactor") can't slip through.
26
26
  *
27
- * `body.changes` items may be either:
28
- * 1. A string bullet (legacy shape, e.g. `"src/foo.ts: extract handler"`).
29
- * 2. An object `{ path: string, assumption: enum }` (Story #2636 shape).
27
+ * `acceptance` / `verify` are the **top-level machine contract** (Story
28
+ * #4541). The decomposer prompt tells authors to write those lists once at
29
+ * the ticket's top level and omit the matching body sections; persist syncs
30
+ * them into the body at assemble time. Validation runs *before* that sync,
31
+ * so this validator resolves each contract field from the parsed body and
32
+ * falls back to the ticket's top-level array when the body section is
33
+ * absent. Without that fallback the validator rejected the very shape its
34
+ * own prompt prescribes.
35
+ *
36
+ * `body.changes` items must be object-form `{ path: string, assumption: enum }`
37
+ * entries (Story #2636 shape). Plain string bullets are rejected at parse
38
+ * time and by this validator.
30
39
  *
31
40
  * Object-form items must declare an `assumption` ∈ `creates |
32
41
  * refactors-existing | exists | deletes`. The optional `body.references`
33
42
  * array uses the same object shape and is the home for paths the Story
34
43
  * reads but does not modify (test fixtures, sibling modules, etc.).
35
- * String-form `changes` items remain legal so legacy plans keep parsing,
36
- * but they emit a deprecation signal via `validateStoryFileAssumptions`.
37
44
  *
38
45
  * `body.verify` entries must either name a testing tier in parentheses
39
46
  * drawn from `VERIFY_TIER_VALUES` (e.g. `npm run test (unit)`) or be the
@@ -53,8 +60,8 @@ import { FILE_ASSUMPTION_VALUES } from './file-assumption-enum.js';
53
60
 
54
61
  /**
55
62
  * Canonical testing-tier labels that a `verify[]` entry must name (in
56
- * parentheses) to pass plan-time validation. Mirrors the skill contract in
57
- * `core/epic-plan-decompose-author/SKILL.md § verify rules`.
63
+ * parentheses) to pass plan-time validation. Mirrors the verify-rules contract
64
+ * in `.agents/scripts/lib/templates/decomposer-prompts.js`.
58
65
  *
59
66
  * Entries that do not end with `(<tier>)` and are not `manual:<reason>` are
60
67
  * rejected by `collectVerifyErrors`.
@@ -166,6 +173,39 @@ function resolveStructuredBody(ticket) {
166
173
  }
167
174
  }
168
175
 
176
+ /**
177
+ * The two contract fields that live at the ticket's top level and are
178
+ * synced into the body by `plan-persist` at assemble time.
179
+ */
180
+ const CONTRACT_FIELDS = Object.freeze(['acceptance', 'verify']);
181
+
182
+ /**
183
+ * Resolve the body's contract fields against the ticket's top-level arrays
184
+ * (Story #4541). The decomposer prompt prescribes authoring `acceptance[]`
185
+ * / `verify[]` **once** at top level and omitting the matching body
186
+ * sections; `assemblePlanStories#syncContractFieldFromTopLevel` performs
187
+ * the sync, but it runs *after* validation. So an absent body section is
188
+ * not a violation when the ticket carries the list at top level — it is the
189
+ * preferred shape. A body section that is present and disagrees with the
190
+ * top level is left alone here: the sync itself fails closed on that
191
+ * mismatch, and duplicating the check would report it twice.
192
+ *
193
+ * @param {object} ticket
194
+ * @param {object} bodyObject Parsed / structured body.
195
+ * @returns {object} A copy of `bodyObject` with the contract fields resolved.
196
+ */
197
+ function resolveContractFieldsFromTopLevel(ticket, bodyObject) {
198
+ const resolved = { ...bodyObject };
199
+ for (const field of CONTRACT_FIELDS) {
200
+ const bodyValue = Array.isArray(resolved[field]) ? resolved[field] : [];
201
+ if (bodyValue.length > 0) continue;
202
+ const topLevel = Array.isArray(ticket?.[field]) ? ticket[field] : [];
203
+ if (topLevel.length === 0) continue;
204
+ resolved[field] = topLevel.map(String);
205
+ }
206
+ return resolved;
207
+ }
208
+
169
209
  /**
170
210
  * Validate one Story body and return every violation it exhibits. Empty
171
211
  * array means clean. Splits the per-ticket cascade out of
@@ -184,13 +224,14 @@ function resolveStructuredBody(ticket) {
184
224
  */
185
225
  export function validateTaskBodyShape(ticket) {
186
226
  const prefix = `Story "${ticket.title}" (${ticket.slug})`;
187
- const { body, error } = resolveStructuredBody(ticket);
227
+ const { body: parsed, error } = resolveStructuredBody(ticket);
188
228
  if (error !== null) {
189
229
  return [error];
190
230
  }
191
- if (body === null || typeof body !== 'object') {
192
- return [`${prefix}: body must be an object, got ${typeof body}.`];
231
+ if (parsed === null || typeof parsed !== 'object') {
232
+ return [`${prefix}: body must be an object, got ${typeof parsed}.`];
193
233
  }
234
+ const body = resolveContractFieldsFromTopLevel(ticket, parsed);
194
235
  const errors = [];
195
236
  if (typeof body.goal !== 'string' || body.goal.trim() === '') {
196
237
  errors.push(`${prefix}: body.goal must be a non-empty string.`);
@@ -248,25 +289,17 @@ function collectChangesErrors(prefix, rawChanges) {
248
289
  return [`${prefix}: body.changes must list at least one bullet.`];
249
290
  }
250
291
  const errors = [];
251
- // An entry "names a path" when it is either a path-shaped bullet
252
- // string OR an object-form entry that passed the assumption schema.
253
- const namesPath = (c) => {
254
- if (typeof c === 'string') return bulletNamesPath(c);
255
- return isObjectPathEntry(c);
256
- };
292
+ const namesPath = (c) => isObjectPathEntry(c);
257
293
  if (changes.every((c) => !namesPath(c))) {
258
294
  errors.push(
259
- `${prefix}: body.changes bullets name no path-shaped token. Use "<path>: <verb> <object>" — e.g. "src/components/Foo.tsx: extract handleSubmit". Object-form entries may also declare { path, assumption } directly.`,
295
+ `${prefix}: body.changes must declare at least one { path, assumption } object entry.`,
260
296
  );
261
297
  }
262
298
  for (const entry of changes) {
263
299
  if (typeof entry === 'string') {
264
- const verb = vagueVerbWithoutTarget(entry);
265
- if (verb) {
266
- errors.push(
267
- `${prefix}: body.changes bullet uses vague verb "${verb}" without a named target: "${entry}".`,
268
- );
269
- }
300
+ errors.push(
301
+ `${prefix}: body.changes entry must be a { path, assumption } object; plain string bullets are no longer accepted: "${entry}".`,
302
+ );
270
303
  continue;
271
304
  }
272
305
  if (isMalformedObjectPathEntry(entry)) {
@@ -310,7 +343,9 @@ function collectReferencesErrors(prefix, rawReferences) {
310
343
  function collectAcceptanceErrors(prefix, rawAcceptance) {
311
344
  const acceptance = Array.isArray(rawAcceptance) ? rawAcceptance : [];
312
345
  if (acceptance.length === 0) {
313
- return [`${prefix}: body.acceptance must list at least one criterion.`];
346
+ return [
347
+ `${prefix}: acceptance must list at least one criterion — author it at the ticket's top level (preferred) or in the body's ## Acceptance section.`,
348
+ ];
314
349
  }
315
350
  return [];
316
351
  }
@@ -333,7 +368,7 @@ function collectVerifyErrors(prefix, rawVerify) {
333
368
  const verify = Array.isArray(rawVerify) ? rawVerify : [];
334
369
  if (verify.length === 0) {
335
370
  return [
336
- `${prefix}: body.verify must list at least one entry. Use "manual:<reason>" only when truly unverifiable in isolation.`,
371
+ `${prefix}: verify must list at least one entry — author it at the ticket's top level (preferred) or in the body's ## Verify section. Use "manual:<reason>" only when truly unverifiable in isolation.`,
337
372
  ];
338
373
  }
339
374
  const errors = [];
@@ -5,9 +5,19 @@
5
5
  * an exclusive, time-bounded claim on a ticket so two concurrent runs do not
6
6
  * both drive the same Story. Rather than invent a new state column, the lease
7
7
  * rides the ticket's existing **assignees** surface: the single assignee *is*
8
- * the lease owner. Liveness is decided by the owner's most-recent
9
- * `story.heartbeat` timestamp (the `operator` field added in this Story links
10
- * a heartbeat back to the claimant) compared against a configured TTL.
8
+ * the lease owner. Liveness is decided by the owner's last-heartbeat epoch-ms
9
+ * compared against a configured TTL.
10
+ *
11
+ * **There is no live heartbeat source.** The `story.heartbeat` emitter this
12
+ * module was designed against was structurally inert (it demanded an
13
+ * `epicId >= 1` that v2, which has no Epics, never supplies) and has been
14
+ * deleted. Every caller now reaches `acquireLease` through
15
+ * `lease-guard-shared.acquireLeaseFailClosed` with `anchorHeartbeatToNow`,
16
+ * which pins `heartbeatAt` to `now`: the lease **fails closed**, so any
17
+ * foreign claim reads as live and refuses. A stranded claim is cleared with
18
+ * `--steal`, never by TTL expiry. The TTL and the stale-reclaim branch below
19
+ * are therefore reachable only via an explicit caller-supplied `heartbeatAt`
20
+ * — the seam is kept, the automatic expiry is not real.
11
21
  *
12
22
  * The three exported operations are deliberately thin and provider-agnostic:
13
23
  *
@@ -31,19 +41,16 @@
31
41
  * Liveness seam: callers supply the owner's last-heartbeat epoch-ms via the
32
42
  * `heartbeatAt` option (a number, or `null`/`undefined` when no heartbeat has
33
43
  * ever been recorded for the current owner). Threading the timestamp in keeps
34
- * this module pure and trivially unit-testable — it does not read the
35
- * lifecycle ledger itself. A claim with no heartbeat is treated as stale
36
- * (reclaimable) so an abandoned assignment never wedges the ticket.
44
+ * this module pure and trivially unit-testable — it does not read any ledger
45
+ * itself. A claim with no heartbeat is treated as stale (reclaimable) by
46
+ * `isClaimLive`; note the live guards never take that branch, per the
47
+ * fail-closed anchoring described above.
37
48
  *
38
49
  * `now` is injectable (epoch ms) for deterministic tests; it defaults to
39
50
  * `Date.now()`.
40
51
  */
41
52
 
42
- import { readFileSync } from 'node:fs';
43
-
44
53
  import { resolveLeaseTtlMs } from '../config/limits.js';
45
- import { epicLedgerPath } from '../config/temp-paths.js';
46
- import { parseLedger } from './lifecycle/trace-logger.js';
47
54
 
48
55
  /**
49
56
  * The shipped, non-personal operator-identity placeholder (and its bare,
@@ -87,76 +94,22 @@ export function normalizeOperatorHandle(raw) {
87
94
  return trimmed;
88
95
  }
89
96
 
90
- /**
91
- * Read the most-recent `story.heartbeat` epoch-ms recorded for a given lease
92
- * owner from an Epic lifecycle ledger. Returns `null` when the ledger is
93
- * absent, unreadable, or carries no heartbeat for that owner — which the lease
94
- * primitive treats as a stale (reclaimable) claim.
95
- *
96
- * The ledger is NDJSON; each `story.heartbeat` record carries
97
- * `payload.operator` (Story #3480) and `payload.timestamp` (ISO-8601). A
98
- * malformed ledger downgrades to `null` rather than throwing so a corrupt
99
- * observability artifact never wedges the lease preflight.
100
- *
101
- * This is the shared liveness source the lease guards thread into
102
- * `acquireLease` via `heartbeatAt`; `/plan` and `/deliver` both
103
- * reuse it so a live foreign claim actually refuses.
104
- *
105
- * @param {object} args
106
- * @param {number} args.epicId
107
- * @param {string} args.owner Lease owner whose heartbeat to find.
108
- * @param {object} [args.config] Resolved config (for ledger path).
109
- * @param {string} [args.ledgerPath] Explicit path override (tests).
110
- * @param {(eid: number, config?: object) => string} [args.ledgerPathResolver]
111
- * Injectable resolver (tests). Defaults to `epicLedgerPath`.
112
- * @param {(p: string) => string} [args.readFile] Injectable reader (tests).
113
- * @returns {number|null}
114
- */
115
- export function latestHeartbeatForOwner({
116
- epicId,
117
- owner,
118
- config,
119
- ledgerPath,
120
- ledgerPathResolver = epicLedgerPath,
121
- readFile = (p) => readFileSync(p, 'utf8'),
122
- }) {
123
- if (typeof owner !== 'string' || owner.length === 0) return null;
124
- const resolvedPath = ledgerPath ?? ledgerPathResolver(epicId, config);
125
-
126
- let text;
127
- try {
128
- text = readFile(resolvedPath);
129
- } catch (_err) {
130
- // No ledger yet (fresh Epic) → no heartbeat → reclaimable.
131
- return null;
132
- }
133
-
134
- let records;
135
- try {
136
- records = parseLedger(text);
137
- } catch (_err) {
138
- // Corrupt ledger is an observability problem, not a coordination blocker.
139
- return null;
140
- }
141
-
142
- let latest = null;
143
- for (const record of records) {
144
- const payload = record?.payload;
145
- if (!payload || payload.event !== 'story.heartbeat') continue;
146
- if (payload.operator !== owner) continue;
147
- const ts = Date.parse(payload.timestamp ?? '');
148
- if (!Number.isFinite(ts)) continue;
149
- if (latest === null || ts > latest) latest = ts;
150
- }
151
- return latest;
152
- }
153
-
154
97
  /**
155
98
  * Decide whether a foreign claim is still "live" given the owner's last
156
99
  * heartbeat and the configured TTL. A claim is live when a heartbeat exists
157
100
  * and is no older than `ttlMs`. A missing heartbeat (`null`/`undefined`) or a
158
101
  * heartbeat older than the TTL is stale and therefore reclaimable.
159
102
  *
103
+ * The `heartbeatAt` seam is retained, but there is no longer any in-repo
104
+ * heartbeat *source*: the `story.heartbeat` emitter was structurally inert
105
+ * (it required an `epicId >= 1` that v2 never sets) and was deleted along
106
+ * with the ledger reader that scanned for it. Every live caller reaches this
107
+ * through `lease-guard-shared.acquireLeaseFailClosed` with
108
+ * `anchorHeartbeatToNow`, which pins `heartbeatAt` to `now` so ANY foreign
109
+ * claim reads live and the guard fails closed — a stranded claim is cleared
110
+ * with `--steal`, not by TTL expiry. The parameter stays because that
111
+ * anchoring is expressed through it.
112
+ *
160
113
  * @param {object} args
161
114
  * @param {number|null|undefined} args.heartbeatAt Owner's last heartbeat (epoch ms).
162
115
  * @param {number} args.ttlMs Lease TTL in milliseconds.