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
@@ -3,15 +3,12 @@
3
3
  *
4
4
  * Tech Spec #819 §"Evidence record (Story 7)". Each successful gate run
5
5
  * writes a record keyed by `{ gateName, commitSha, commandConfigHash }` to
6
- * a per-Epic-tree path under the resolved `tempRoot`:
6
+ * a storyId-anchored path under the resolved `tempRoot`:
7
7
  *
8
- * - Epic-scoped (scopeId === epicId):
9
- * `<tempRoot>/epic-<epicId>/validation-evidence.json`
10
- * - Story-scoped (scopeId === storyId):
11
- * `<tempRoot>/epic-<epicId>/story-<storyId>/validation-evidence.json`
8
+ * `<tempRoot>/standalone/stories/story-<storyId>/validation-evidence.json`
12
9
  *
13
- * Both paths sit inside the per-Epic durable workspace (Epic #1030, Stories
14
- * #1039 + #1054) and are gitignored via `temp/`.
10
+ * The path is gitignored via `temp/`. v2.0.0 removed the Epic tier along
11
+ * with the Epic-keyed `<tempRoot>/epic-<epicId>/…` keyspace.
15
12
  *
16
13
  * A subsequent caller can `shouldSkip(...)` to learn whether the same gate
17
14
  * has already passed against the current HEAD with an identical
@@ -34,7 +31,7 @@ import path from 'node:path';
34
31
  import { fileURLToPath } from 'node:url';
35
32
  import Ajv from 'ajv/dist/2020.js';
36
33
  import addFormats from 'ajv-formats';
37
- import { epicTempDir, storyTempDir } from './config/temp-paths.js';
34
+ import { storyTempDir } from './config/temp-paths.js';
38
35
 
39
36
  export const SCHEMA_VERSION = 1;
40
37
  const DEFAULT_TEMP_DIR = 'temp';
@@ -97,37 +94,28 @@ function requirePositiveInt(value, label) {
97
94
 
98
95
  /**
99
96
  * Compute the absolute path of the evidence file for `scopeId` under the
100
- * per-Epic temp tree.
97
+ * storyId-anchored temp tree.
101
98
  *
102
- * The resolution rule mirrors `lib/config/temp-paths.js`:
103
- * - `scopeId === epicId` → `<tempRoot>/epic-<epicId>/validation-evidence.json`
104
- * - `scopeId !== epicId` → treated as a Story id → `<tempRoot>/epic-<epicId>/story-<scopeId>/validation-evidence.json`
105
- *
106
- * **Standalone keyspace (Story #4250).** When `opts.standalone === true`,
99
+ * **Standalone keyspace (Story #4250).** `opts.standalone` must be `true`:
107
100
  * the Story has no parent Epic, so the evidence file is anchored on the
108
101
  * Story id alone at
109
102
  * `<tempRoot>/standalone/stories/story-<scopeId>/validation-evidence.json`
110
- * (the `storyTempDir(null, sid)` layout from Story #2874). In this mode
111
- * `epicId` is ignored — callers MUST NOT feed a `0`/`null` epicId into the
112
- * Epic-keyed branch (the historical bug this keyspace replaces). Outside
113
- * standalone mode `epicId` remains required.
103
+ * (the `storyTempDir(null, sid)` layout from Story #2874).
114
104
  *
115
- * The legacy flat `temp/validation-evidence-<scopeId>.json` layout is no
116
- * longer supported — Epic-scoped callers must thread the Epic id through
117
- * (Epic #1030 follow-up to Story #1054). The synthetic config bag passed to
118
- * `epicTempDir` / `storyTempDir` keeps the resolver from doing a disk-bound
119
- * `.agentrc.json` lookup; bare callers can pass `tempDir` via `opts` to
120
- * override the default `'temp'`.
105
+ * The legacy flat `temp/validation-evidence-<scopeId>.json` layout and the
106
+ * Epic-keyed `<tempRoot>/epic-<epicId>/…` layout are both gone. The
107
+ * synthetic config bag passed to `storyTempDir` keeps the resolver from
108
+ * doing a disk-bound `.agentrc.json` lookup; bare callers can pass
109
+ * `tempDir` via `opts` to override the default `'temp'`.
121
110
  *
122
111
  * @param {number|string} scopeId
123
- * @param {{ cwd?: string, tempDir?: string, epicId?: number|string|null, standalone?: boolean }} opts
112
+ * @param {{ cwd?: string, tempDir?: string, standalone?: boolean }} opts
124
113
  * @returns {string}
125
114
  */
126
115
  export function evidencePath(scopeId, opts = {}) {
127
- const standalone = opts.standalone === true;
128
- if (!standalone && opts.epicId == null) {
116
+ if (opts.standalone !== true) {
129
117
  throw new Error(
130
- '[validation-evidence] evidencePath requires opts.epicId (Epic-scoped path resolution) unless opts.standalone is set.',
118
+ '[validation-evidence] evidencePath requires opts.standalone (the storyId-anchored keyspace is the only keyspace).',
131
119
  );
132
120
  }
133
121
  const { cwd, tempDir } = resolveOpts(opts);
@@ -143,18 +131,9 @@ export function evidencePath(scopeId, opts = {}) {
143
131
  ? tempDir
144
132
  : path.join(cwd, tempDir);
145
133
  const configBag = { project: { paths: { tempRoot: absTempRoot } } };
146
- let dir;
147
- if (standalone) {
148
- // Story #4250 — storyId-anchored standalone keyspace. `null` is the
149
- // standalone-story sentinel `storyTempDir` accepts (Story #2874).
150
- dir = storyTempDir(null, scope, configBag);
151
- } else {
152
- const epicId = requirePositiveInt(opts.epicId, 'epicId');
153
- dir =
154
- scope === epicId
155
- ? epicTempDir(epicId, configBag)
156
- : storyTempDir(epicId, scope, configBag);
157
- }
134
+ // Story #4250 — storyId-anchored standalone keyspace. `null` is the
135
+ // standalone-story sentinel `storyTempDir` accepts (Story #2874).
136
+ const dir = storyTempDir(null, scope, configBag);
158
137
  return path.join(dir, EVIDENCE_FILENAME);
159
138
  }
160
139
 
@@ -189,19 +168,17 @@ function emptyDoc(scopeId) {
189
168
  * cross-scopeId cases — callers don't have to branch on those failure
190
169
  * modes; they manifest as `shouldSkip()` returning `skip: false`.
191
170
  *
192
- * `opts.epicId` is required so the per-Epic-tree path can be resolved,
193
- * unless `opts.standalone === true` (Story #4250) routes to the
194
- * storyId-anchored standalone keyspace.
171
+ * `opts.standalone` is required (Story #4250) it routes to the
172
+ * storyId-anchored keyspace.
195
173
  *
196
174
  * @param {number|string} scopeId
197
- * @param {{ cwd?: string, tempDir?: string, epicId?: number|string|null, standalone?: boolean, fs?: object }} opts
175
+ * @param {{ cwd?: string, tempDir?: string, standalone?: boolean, fs?: object }} opts
198
176
  * @returns {{ storyId: number, schemaVersion: number, records: object[] }}
199
177
  */
200
178
  export function loadEvidence(scopeId, opts = {}) {
201
179
  const resolved = resolveOpts(opts);
202
180
  const file = evidencePath(scopeId, {
203
181
  ...resolved,
204
- epicId: opts.epicId,
205
182
  standalone: opts.standalone,
206
183
  });
207
184
  if (!resolved.fs.existsSync(file)) return emptyDoc(scopeId);
@@ -223,9 +200,8 @@ export function loadEvidence(scopeId, opts = {}) {
223
200
  * Validates the resulting document against the schema before writing — a
224
201
  * malformed write throws so the bug surfaces immediately.
225
202
  *
226
- * `opts.epicId` is required so the per-Epic-tree path can be resolved,
227
- * unless `opts.standalone === true` (Story #4250) routes to the
228
- * storyId-anchored standalone keyspace.
203
+ * `opts.standalone` is required (Story #4250) it routes to the
204
+ * storyId-anchored keyspace.
229
205
  *
230
206
  * @param {{
231
207
  * storyId: number|string,
@@ -235,7 +211,7 @@ export function loadEvidence(scopeId, opts = {}) {
235
211
  * exitCode?: number,
236
212
  * durationMs?: number|null,
237
213
  * }} input
238
- * @param {{ cwd?: string, tempDir?: string, epicId?: number|string|null, standalone?: boolean, fs?: object, now?: Function }} opts
214
+ * @param {{ cwd?: string, tempDir?: string, standalone?: boolean, fs?: object, now?: Function }} opts
239
215
  * @returns {object} The persisted record.
240
216
  */
241
217
  export function recordPass(
@@ -258,7 +234,6 @@ export function recordPass(
258
234
  const resolved = resolveOpts(opts);
259
235
  const evidenceOpts = {
260
236
  ...resolved,
261
- epicId: opts.epicId,
262
237
  standalone: opts.standalone,
263
238
  };
264
239
  const doc = loadEvidence(storyId, evidenceOpts);
@@ -297,13 +272,12 @@ export function recordPass(
297
272
  * with a machine-readable `reason` so callers can log why the skip didn't
298
273
  * fire.
299
274
  *
300
- * `opts.epicId` is required so the per-Epic-tree path can be resolved,
301
- * unless `opts.standalone === true` (Story #4250) routes to the
302
- * storyId-anchored standalone keyspace. `opts` is forwarded verbatim to
275
+ * `opts.standalone` is required (Story #4250) it routes to the
276
+ * storyId-anchored keyspace. `opts` is forwarded verbatim to
303
277
  * `loadEvidence`, so `standalone` flows through unchanged.
304
278
  *
305
279
  * @param {{ storyId: number|string, gateName: string, currentSha: string, configHash: string }} input
306
- * @param {{ cwd?: string, tempDir?: string, epicId?: number|string|null, standalone?: boolean, fs?: object }} opts
280
+ * @param {{ cwd?: string, tempDir?: string, standalone?: boolean, fs?: object }} opts
307
281
  * @returns {{ skip: boolean, reason: string, record?: object }}
308
282
  */
309
283
  export function shouldSkip(
@@ -336,23 +310,21 @@ export function shouldSkip(
336
310
  }
337
311
 
338
312
  /**
339
- * Delete the evidence file for `scopeId`. Called by `story-init.js` at the
313
+ * Delete the evidence file for `scopeId`. Called by `single-story-init.js` at the
340
314
  * start of each Story so a re-run always starts clean. Idempotent —
341
315
  * absent file is not an error.
342
316
  *
343
- * `opts.epicId` is required so the per-Epic-tree path can be resolved,
344
- * unless `opts.standalone === true` (Story #4250) routes to the
345
- * storyId-anchored standalone keyspace.
317
+ * `opts.standalone` is required (Story #4250) it routes to the
318
+ * storyId-anchored keyspace.
346
319
  *
347
320
  * @param {number|string} scopeId
348
- * @param {{ cwd?: string, tempDir?: string, epicId?: number|string|null, standalone?: boolean, fs?: object }} opts
321
+ * @param {{ cwd?: string, tempDir?: string, standalone?: boolean, fs?: object }} opts
349
322
  * @returns {{ cleared: boolean, path: string }}
350
323
  */
351
324
  export function forceClear(scopeId, opts = {}) {
352
325
  const resolved = resolveOpts(opts);
353
326
  const file = evidencePath(scopeId, {
354
327
  ...resolved,
355
- epicId: opts.epicId,
356
328
  standalone: opts.standalone,
357
329
  });
358
330
  if (!resolved.fs.existsSync(file)) return { cleared: false, path: file };
@@ -2,8 +2,8 @@
2
2
  * lib/wave-runner/ready-set.js — the path-agnostic ready-set scheduling
3
3
  * core.
4
4
  *
5
- * This module is the scheduling kernel both the Epic and standalone
6
- * delivery paths dispatch through. It replaces wave-*batch* selection
5
+ * This module is the scheduling kernel the v2 `/deliver` multi-Story path
6
+ * dispatches through (`stories-wave-tick.js`). It replaces wave-*batch* selection
7
7
  * (group N must fully drain before group N+1 opens) with *continuous*,
8
8
  * dependency-driven selection: a Story becomes dispatchable the instant
9
9
  * **its own** dependencies are satisfied, regardless of whether unrelated
@@ -15,14 +15,14 @@
15
15
  * reads GitHub, the lifecycle ledger, nor a checkpoint, and it dispatches
16
16
  * nothing. Callers supply the live Story records (already fetched), the
17
17
  * resolved `inFlight` count, and the `globalCap`, and receive back the set
18
- * of Stories that are safe to dispatch on this beat. Later Stories wire the
19
- * Epic / standalone adapters on top of this core; this Story ships the core
20
- * alone and does not modify `tick.js` or `stories-wave-tick.js`.
18
+ * of Stories that are safe to dispatch on this beat. The
19
+ * `stories-wave-tick.js` adapter wires this core; this module does not
20
+ * modify that CLI surface.
21
21
  *
22
22
  * Three exports:
23
23
  * - `classifyStory(story)` — live-label classifier mapping a Story
24
24
  * record's labels + issue state to one of `done | blocked | executing |
25
- * ready`. Mirrors the done-predicate `tick.js` already uses
25
+ * ready`. Mirrors the done-predicate this module uses
26
26
  * (`agent::done` OR closed issue) so a Story closed manually through
27
27
  * the GitHub UI is recognised as done.
28
28
  * - `storiesOverlap(a, b)` — the file-overlap co-dispatch guard: true
@@ -156,15 +156,37 @@ export function storyFootprint(story) {
156
156
  return out;
157
157
  }
158
158
 
159
+ /**
160
+ * Does a declared path contain a glob metacharacter? Mirrors the detection
161
+ * in `story-body.js#extractChangePaths`, whose `isGlob` flag documents an
162
+ * "unknown-width footprint" policy that was never implemented downstream.
163
+ *
164
+ * @param {string} path
165
+ * @returns {boolean}
166
+ */
167
+ function isGlobPath(path) {
168
+ return path.includes('*') || path.includes('?') || path.includes('{');
169
+ }
170
+
159
171
  /**
160
172
  * File-overlap co-dispatch guard. Returns `true` when two Stories' declared
161
- * file footprints intersect on at least one path — meaning they would race
162
- * the same file if dispatched onto parallel `story-<id>` branches in the
163
- * same beat. Two Stories that overlap MUST NOT both appear in one dispatch
164
- * set; one is withheld until the other clears.
173
+ * file footprints intersect — meaning they would race the same file if
174
+ * dispatched onto parallel `story-<id>` branches in the same beat. Two
175
+ * Stories that overlap MUST NOT both appear in one dispatch set; one is
176
+ * withheld until the other clears.
165
177
  *
166
- * An empty footprint on either side means "no known overlap" → `false`. A
167
- * Story that declares no files is therefore never withheld by this guard.
178
+ * Two deliberate asymmetries:
179
+ *
180
+ * - **An empty footprint means "no known overlap"** → `false`. A Story that
181
+ * declares no files is never withheld. This is permissive by necessity:
182
+ * an undeclared footprint carries no information, and withholding on
183
+ * absence would serialize every run.
184
+ * - **A glob footprint overlaps EVERYTHING** → `true` (Story #4539/#4540).
185
+ * Comparison is exact-string, so a Story declaring
186
+ * `.agents/scripts/lib/**` would not match another declaring
187
+ * `.agents/scripts/lib/story-adjacency.js` — the guard would silently
188
+ * pass two Stories that genuinely race. Unknown width is not the same as
189
+ * no width: fail safe by serializing.
168
190
  *
169
191
  * @param {StoryRecord} a
170
192
  * @param {StoryRecord} b
@@ -176,8 +198,12 @@ export function storiesOverlap(a, b) {
176
198
  const fb = storyFootprint(b);
177
199
  if (fb.size === 0) return false;
178
200
  for (const path of fa) {
201
+ if (isGlobPath(path)) return true;
179
202
  if (fb.has(path)) return true;
180
203
  }
204
+ for (const path of fb) {
205
+ if (isGlobPath(path)) return true;
206
+ }
181
207
  return false;
182
208
  }
183
209
 
@@ -145,7 +145,7 @@ export function provision({
145
145
  /**
146
146
  * Throw if any required workspace file is missing from `worktree`. Used as a
147
147
  * runtime guard (called right after every worktree create in
148
- * `story-init.js`) and as a test oracle.
148
+ * `single-story-init.js`) and as a test oracle.
149
149
  *
150
150
  * When `sourceRoot` is supplied, the thrown error carries the missing target
151
151
  * path and an exact `cp`/`copy` command the operator can paste to remediate.
@@ -7,7 +7,7 @@
7
7
  * on a Windows lock-class failure, the entry is appended to
8
8
  * `.worktrees/.pending-cleanup.json`. The plan-time `worktree-sweep.js`
9
9
  * reader (shipped in Epic #349) picks up the manifest on the next
10
- * `/epic-plan-spec` / `/epic-plan-decompose` runs and retries removal
10
+ * `/plan` run and retries removal
11
11
  * (`git worktree remove` then `fs.rm`) — by then the live file handles from Node / AV / the Windows
12
12
  * Search indexer are almost always gone. If `MAX_SWEEP_ATTEMPTS` elapses
13
13
  * without clearing, an `OPERATOR ACTION REQUIRED: persistent-lock` line
@@ -15,7 +15,7 @@
15
15
 
16
16
  import fs from 'node:fs';
17
17
  import { rm as fsPromisesRm } from 'node:fs/promises';
18
- import { isInsideWorktree, samePath, storyIdFromPath } from '../inspector.js';
18
+ import { isInsideWorktree, samePath } from '../inspector.js';
19
19
  import { sleepSync } from '../node-modules-strategy.js';
20
20
  import { checkMergeReachability } from './merge-reachability.js';
21
21
  import { recordPendingCleanup } from './pending-cleanup.js';
@@ -52,14 +52,12 @@ const WINDOWS_CWD_RE =
52
52
  * proves the Story branch was integrated even though the current HEAD
53
53
  * has diverged, so the worktree is still safe to reap.
54
54
  *
55
- * `opts.epicRef` is the canonical option name (e.g. `epic/1114`).
56
- * `opts.epicBranch` is accepted as a back-compat alias so existing call
57
- * sites that thread `{ epicBranch }` through `reap()` keep working until
58
- * they migrate.
55
+ * `opts.epicBranch` is the integration / base ref the Story must already
56
+ * be merged into (e.g. `main` or a plan-run branch).
59
57
  *
60
58
  * @param {object} ctx
61
59
  * @param {string} wtPath
62
- * @param {{ epicRef?: string|null, epicBranch?: string|null }} [opts]
60
+ * @param {{ epicBranch?: string|null }} [opts]
63
61
  * @returns {Promise<{ safe: boolean, reason?: string }>}
64
62
  */
65
63
  export async function isSafeToRemove(ctx, wtPath, opts = {}) {
@@ -67,28 +65,65 @@ export async function isSafeToRemove(ctx, wtPath, opts = {}) {
67
65
  if (!local.safe) return local;
68
66
  if (local.reason === 'path-missing') return local;
69
67
 
70
- const epicRef = opts.epicRef ?? opts.epicBranch ?? null;
68
+ const epicRef = opts.epicBranch ?? opts.epicRef ?? null;
71
69
  if (!epicRef) return { safe: true };
72
70
 
73
71
  return checkMergeReachability(ctx, wtPath, local.branch, epicRef);
74
72
  }
75
73
 
76
74
  /**
77
- * Returns true iff `branch` is already fully merged into `epicBranch`
78
- * (i.e. `merge-base --is-ancestor branch epicBranch` exits 0). A missing
79
- * epicBranch or a git failure both yield false so callers default to the
80
- * safe, non-forcing behavior.
75
+ * Returns true iff `branch`'s work is demonstrably already integrated into
76
+ * `baseRef`. Used only to license discarding a **dirty** tree, so it must
77
+ * err toward `false`.
78
+ *
79
+ * Two-phase:
80
+ * 1. `merge-base --is-ancestor` — cheap SHA reachability; true for
81
+ * fast-forward and merge-commit integration.
82
+ * 2. `git cherry <baseRef> <branch>` — compares **patch-ids** rather than
83
+ * SHAs, marking a commit `-` when an equivalent change already exists
84
+ * upstream. All-`-` (or empty) means every commit on the branch is
85
+ * present in the base under some SHA.
86
+ *
87
+ * **Known limit — this is not a general squash detector.** A squash collapses
88
+ * N commits into ONE new commit whose patch equals their *combined* diff, so
89
+ * no individual original commit has an upstream patch-id equivalent and
90
+ * `git cherry` marks them all `+`. Phase 2 therefore only recognises a
91
+ * squash-landed branch when the branch had a **single** commit (verified
92
+ * empirically, Story #4539). A multi-commit squash still reads as unmerged
93
+ * and the dirty tree is refused — the safe direction, and the same answer
94
+ * the previous ancestor-only check gave. Detecting the general case needs
95
+ * the PR's merged state, which this module has no client for.
96
+ *
97
+ * A missing ref or a git failure yields false, so callers default to the
98
+ * safe, non-forcing behavior. Module-private: `ensureSafeOrForceDiscard` is
99
+ * its only caller, and the symbol it replaced was exported with no consumer
100
+ * and then baselined as a dead export — repeating that would just hide a
101
+ * new corpse.
102
+ *
103
+ * @param {object} ctx
104
+ * @param {string} branch
105
+ * @param {string} baseRef
106
+ * @returns {boolean}
81
107
  */
82
- export function isStoryAlreadyMergedIntoEpic(ctx, branch, epicBranch) {
83
- if (!branch || !epicBranch) return false;
84
- const res = ctx.git.gitSpawn(
108
+ function isBranchMergedIntoBase(ctx, branch, baseRef) {
109
+ if (!branch || !baseRef) return false;
110
+ const ancestor = ctx.git.gitSpawn(
85
111
  ctx.repoRoot,
86
112
  'merge-base',
87
113
  '--is-ancestor',
88
114
  branch,
89
- epicBranch,
115
+ baseRef,
90
116
  );
91
- return res.status === 0;
117
+ if (ancestor.status === 0) return true;
118
+
119
+ const cherry = ctx.git.gitSpawn(ctx.repoRoot, 'cherry', baseRef, branch);
120
+ if (cherry.status !== 0) return false;
121
+ const lines = (cherry.stdout ?? '')
122
+ .split('\n')
123
+ .map((l) => l.trim())
124
+ .filter(Boolean);
125
+ // Every line starting with '+' is a commit with no upstream equivalent.
126
+ return lines.every((line) => line.startsWith('-'));
92
127
  }
93
128
 
94
129
  /**
@@ -472,28 +507,40 @@ function checkReapPreconditions(ctx, _storyId, opts, wtPath) {
472
507
  ok: false,
473
508
  result: { removed: false, reason: 'not-a-worktree', path: wtPath },
474
509
  };
475
- if (storyIdFromPath(wtPath, ctx.worktreeRoot) !== null && !opts.epicBranch) {
476
- return {
477
- ok: false,
478
- result: { removed: false, reason: 'epic-branch-required', path: wtPath },
479
- };
480
- }
510
+ // Story #4539 removed an `epic-branch-required` gate here: a
511
+ // `story-<id>` worktree used to be unreapable unless the caller supplied
512
+ // an Epic integration branch. v2 has no Epic branch, and the only v2
513
+ // caller (the close path) never passed one — so EVERY close silently
514
+ // failed to reap while reporting success, and cleanup fell to the next
515
+ // boot-sweep.
516
+ //
517
+ // Nothing is lost by dropping it. The close path reaps AFTER pushing
518
+ // `story-<id>` to origin and opening the PR, so the work is durable
519
+ // off-machine; and the real safety net is unchanged — `isSafeToRemove`
520
+ // still refuses a dirty tree (`uncommitted-changes`), which is what
521
+ // actually protects unsaved work.
481
522
  return { ok: true };
482
523
  }
483
524
 
484
525
  async function ensureSafeOrForceDiscard(ctx, storyId, wtPath, opts) {
526
+ const baseRef = opts.baseRef ?? opts.epicBranch ?? null;
485
527
  const safety = await isSafeToRemove(ctx, wtPath, {
486
- epicRef: opts.epicBranch ?? opts.epicRef ?? null,
528
+ epicBranch: baseRef,
487
529
  });
488
530
  if (safety.safe) return { ok: true, discardedPaths: null };
489
531
 
490
532
  const discardAfterMerge = opts.discardAfterMerge !== false;
491
533
  const branchName = `story-${validateStoryId(storyId)}`;
534
+ // Discarding a dirty tree is only permissible when the branch's work is
535
+ // demonstrably already integrated. See `isBranchMergedIntoBase` for what
536
+ // that can and cannot prove — notably a multi-commit squash reads as
537
+ // unmerged, so the discard is refused and the tree survives. Refusing is
538
+ // the safe direction: the cost is a stale worktree, not lost work.
492
539
  const canForceReap =
493
540
  discardAfterMerge &&
494
541
  safety.reason === 'uncommitted-changes' &&
495
- opts.epicBranch &&
496
- isStoryAlreadyMergedIntoEpic(ctx, branchName, opts.epicBranch);
542
+ baseRef &&
543
+ isBranchMergedIntoBase(ctx, branchName, baseRef);
497
544
  if (!canForceReap) {
498
545
  ctx.logger.warn(
499
546
  `reap-skipped storyId=${storyId} reason=${safety.reason} path=${wtPath}`,
@@ -7,7 +7,7 @@
7
7
  * - `creation.js` — `ensure`
8
8
  * - `registry-sync.js` — `pathFor`, `list`, `prune`, `getWorktreeList`,
9
9
  * `invalidateWorktreeCache`, `findByPath`
10
- * - `reap.js` — `isSafeToRemove`, `isStoryAlreadyMergedIntoEpic`,
10
+ * - `reap.js` — `isSafeToRemove`,
11
11
  * `removeWorktreeWithRecovery`, `reap`
12
12
  * - `gc.js` — `gc`
13
13
  * - `drift-detection.js` — `sweepStaleLocks`
@@ -26,7 +26,6 @@ export { sweepStaleLocks } from './lifecycle/drift-detection.js';
26
26
  export { gc } from './lifecycle/gc.js';
27
27
  export {
28
28
  isSafeToRemove,
29
- isStoryAlreadyMergedIntoEpic,
30
29
  reap,
31
30
  removeWorktreeWithRecovery,
32
31
  } from './lifecycle/reap.js';
@@ -4,7 +4,7 @@
4
4
  * Issue-body conformance lint (Story #4227).
5
5
  *
6
6
  * Runs the canonical `story-body.parse()` against a human-opened
7
- * `type::story` / `type::epic` issue body and reports whether the body
7
+ * `type::story` issue body and reports whether the body
8
8
  * round-trips. This is the drift guard between the generated GitHub Issue
9
9
  * Forms (`lib/bootstrap/issue-forms-template.js`) and the parser: if a
10
10
  * human files a ticket whose body `parse()` rejects (or which lacks the
@@ -114,12 +114,12 @@ export function evaluateIssueBody(body) {
114
114
 
115
115
  const problems = [];
116
116
 
117
- // A legacy string body parses but carries no structured sections — that
117
+ // An unstructured body parses but carries no structured sections — that
118
118
  // is exactly the human-filed shape this lint exists to catch.
119
- if (result.info.isLegacyStringBody) {
119
+ if (result.info.isUnstructuredBody) {
120
120
  problems.push(
121
121
  'The body has no recognised `## Goal` / `## Acceptance` / `## Verify` sections. ' +
122
- 'File via the Story/Epic issue form so it round-trips through the parser.',
122
+ 'File via the Story issue form so it round-trips through the parser.',
123
123
  );
124
124
  }
125
125
 
@@ -162,7 +162,7 @@ export function renderConformanceComment(verdict) {
162
162
  '',
163
163
  ...verdict.problems.map((p) => `- ${p}`),
164
164
  '',
165
- 'The quickest fix is to refile using the **Story** or **Epic** issue form ' +
165
+ 'The quickest fix is to refile using the **Story** issue form ' +
166
166
  '(New issue → pick the template), which lays out the required sections.',
167
167
  ];
168
168
  if (verdict.warnings.length > 0) {
@@ -196,32 +196,40 @@ function gh(args) {
196
196
  }
197
197
 
198
198
  /**
199
- * CLI entry. Reads the target issue (number from `--issue` or the
200
- * `ISSUE_NUMBER` env), fetches its body + labels, and — when the body is
201
- * non-conformant — upserts a single marker comment. Always exits 0 (the
202
- * lint *informs*, it does not block), unless an unexpected I/O error occurs.
199
+ * Stage 5: only `type::story` tickets are in scope (type::epic retired).
203
200
  *
204
- * Flags:
205
- * --issue <n> Issue number (defaults to env ISSUE_NUMBER).
206
- * --repo <o/r> owner/repo (defaults to env GITHUB_REPOSITORY).
207
- * --dry-run Evaluate + print the verdict, never touch GitHub.
201
+ * @param {string[]} labelNames
202
+ * @returns {boolean}
208
203
  */
209
- async function main() {
210
- const args = process.argv.slice(2);
211
- const get = (flag) => {
212
- const i = args.indexOf(flag);
213
- return i >= 0 ? args[i + 1] : undefined;
214
- };
215
- const dryRun = args.includes('--dry-run');
216
- const issue = get('--issue') ?? process.env.ISSUE_NUMBER;
217
- const repo = get('--repo') ?? process.env.GITHUB_REPOSITORY;
218
-
219
- if (!issue) {
220
- throw new Error('lint-issue-body: --issue <n> or ISSUE_NUMBER is required');
221
- }
204
+ export function isStoryTicket(labelNames) {
205
+ return Array.isArray(labelNames) && labelNames.includes('type::story');
206
+ }
222
207
 
208
+ /**
209
+ * Evaluate an issue payload and optionally post a conformance comment.
210
+ * Pure with respect to GitHub when `ghFn` is injected — the CLI `main`
211
+ * is a thin argv wrapper around this helper.
212
+ *
213
+ * @param {{
214
+ * issue: string|number,
215
+ * repo?: string,
216
+ * dryRun?: boolean,
217
+ * ghFn?: (args: string[]) => string,
218
+ * write?: (s: string) => void,
219
+ * }} opts
220
+ * @returns {{ skipped?: string, conformant?: boolean, problems?: string[] }}
221
+ */
222
+ export function runLintIssueBody({
223
+ issue,
224
+ repo,
225
+ dryRun = false,
226
+ ghFn = gh,
227
+ write = (s) => {
228
+ process.stdout.write(s);
229
+ },
230
+ }) {
223
231
  const repoArgs = repo ? ['--repo', repo] : [];
224
- const raw = gh([
232
+ const raw = ghFn([
225
233
  'issue',
226
234
  'view',
227
235
  String(issue),
@@ -231,20 +239,14 @@ async function main() {
231
239
  ]);
232
240
  const { body, labels } = JSON.parse(raw);
233
241
  const labelNames = (labels ?? []).map((l) => l.name);
234
- const isTicket =
235
- labelNames.includes('type::story') || labelNames.includes('type::epic');
236
-
237
- if (!isTicket) {
238
- // Machine-parsable JSON envelope → process.stdout.write (not console.log),
239
- // per the .agents/scripts logging contract (tests/enforcement/no-console).
240
- process.stdout.write(
241
- `${JSON.stringify({ issue: Number(issue), skipped: 'not-a-ticket' })}\n`,
242
- );
243
- return;
242
+ if (!isStoryTicket(labelNames)) {
243
+ const envelope = { issue: Number(issue), skipped: 'not-a-ticket' };
244
+ write(`${JSON.stringify(envelope)}\n`);
245
+ return envelope;
244
246
  }
245
247
 
246
248
  const verdict = evaluateIssueBody(body ?? '');
247
- process.stdout.write(
249
+ write(
248
250
  `${JSON.stringify({
249
251
  issue: Number(issue),
250
252
  conformant: verdict.conformant,
@@ -252,10 +254,39 @@ async function main() {
252
254
  })}\n`,
253
255
  );
254
256
 
255
- if (verdict.conformant || dryRun) return;
257
+ if (verdict.conformant || dryRun) {
258
+ return {
259
+ conformant: verdict.conformant,
260
+ problems: verdict.problems,
261
+ };
262
+ }
256
263
 
257
264
  const comment = renderConformanceComment(verdict);
258
- gh(['issue', 'comment', String(issue), ...repoArgs, '--body', comment]);
265
+ ghFn(['issue', 'comment', String(issue), ...repoArgs, '--body', comment]);
266
+ return { conformant: false, problems: verdict.problems };
267
+ }
268
+
269
+ /**
270
+ * CLI entry. Reads the target issue (number from `--issue` or the
271
+ * `ISSUE_NUMBER` env), fetches its body + labels, and — when the body is
272
+ * non-conformant — upserts a single marker comment. Always exits 0 (the
273
+ * lint *informs*, it does not block), unless an unexpected I/O error occurs.
274
+ */
275
+ async function main() {
276
+ const args = process.argv.slice(2);
277
+ const get = (flag) => {
278
+ const i = args.indexOf(flag);
279
+ return i >= 0 ? args[i + 1] : undefined;
280
+ };
281
+ const issue = get('--issue') ?? process.env.ISSUE_NUMBER;
282
+ if (!issue) {
283
+ throw new Error('lint-issue-body: --issue <n> or ISSUE_NUMBER is required');
284
+ }
285
+ runLintIssueBody({
286
+ issue,
287
+ repo: get('--repo') ?? process.env.GITHUB_REPOSITORY,
288
+ dryRun: args.includes('--dry-run'),
289
+ });
259
290
  }
260
291
 
261
292
  runAsCli(import.meta.url, main, { source: 'lint-issue-body' });
@@ -25,7 +25,7 @@
25
25
  * friendlier "you're offline" signal before any version probe.
26
26
  *
27
27
  * Severity is consistent with framework preflight conventions
28
- * (cf. `story-close.js` runStoryClosePreflight, `epic-deliver-preflight.js`):
28
+ * (cf. `single-story-close.js` runStoryClosePreflight, `mandrel-update-preflight.js`):
29
29
  * the consumer-shape check is a hard stop (blocker), dirty-index and offline
30
30
  * are warn-only and never block the run.
31
31
  *