mandrel 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/.agents/README.md +59 -28
  2. package/.agents/agents/acceptance-critic.md +9 -7
  3. package/.agents/agents/story-worker.md +41 -46
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +51 -44
  6. package/.agents/docs/agentrc-reference.json +8 -13
  7. package/.agents/docs/configuration.md +32 -56
  8. package/.agents/docs/execution-reference.md +39 -10
  9. package/.agents/docs/quality-gates.md +14 -16
  10. package/.agents/docs/workflows.md +6 -6
  11. package/.agents/instructions.md +64 -79
  12. package/.agents/rules/ci-remediation.md +3 -3
  13. package/.agents/rules/git-conventions-reference.md +42 -51
  14. package/.agents/schemas/agentrc.schema.json +34 -45
  15. package/.agents/schemas/audit-rules.json +59 -1
  16. package/.agents/schemas/audit-rules.schema.json +33 -1
  17. package/.agents/schemas/lifecycle/README.md +1 -2
  18. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  19. package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
  20. package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
  21. package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
  22. package/.agents/schemas/signal-event.schema.json +3 -3
  23. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  24. package/.agents/schemas/validation-evidence.schema.json +1 -1
  25. package/.agents/scripts/acceptance-eval.js +22 -66
  26. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  27. package/.agents/scripts/bootstrap.js +3 -3
  28. package/.agents/scripts/check-dead-exports.js +43 -104
  29. package/.agents/scripts/check-doc-links.js +2 -2
  30. package/.agents/scripts/check-lifecycle-lint.js +1 -1
  31. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  32. package/.agents/scripts/deliver-recover.js +122 -0
  33. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  34. package/.agents/scripts/evidence-gate.js +20 -50
  35. package/.agents/scripts/generate-skills-index.js +17 -1
  36. package/.agents/scripts/generate-workflows-doc.js +4 -4
  37. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  38. package/.agents/scripts/lib/audit-suite/selector.js +323 -23
  39. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
  40. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  41. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  42. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  43. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  44. package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
  45. package/.agents/scripts/lib/checks/index.js +1 -1
  46. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  47. package/.agents/scripts/lib/checks/state.js +17 -248
  48. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
  49. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  50. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  51. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  52. package/.agents/scripts/lib/cli-args.js +23 -2
  53. package/.agents/scripts/lib/close-validation/gates.js +13 -13
  54. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  55. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  56. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  57. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  58. package/.agents/scripts/lib/config/delivery-routing.js +7 -6
  59. package/.agents/scripts/lib/config/explain.js +10 -16
  60. package/.agents/scripts/lib/config/github.js +7 -5
  61. package/.agents/scripts/lib/config/limits.js +15 -25
  62. package/.agents/scripts/lib/config/quality.js +11 -14
  63. package/.agents/scripts/lib/config/runners.js +8 -21
  64. package/.agents/scripts/lib/config/temp-paths.js +18 -56
  65. package/.agents/scripts/lib/config-settings-schema-delivery.js +31 -13
  66. package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
  67. package/.agents/scripts/lib/config-settings-schema.js +48 -22
  68. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  69. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  70. package/.agents/scripts/lib/duplicate-search.js +38 -7
  71. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  72. package/.agents/scripts/lib/format-generated-json.js +97 -0
  73. package/.agents/scripts/lib/framework-version.js +19 -189
  74. package/.agents/scripts/lib/gh-exec.js +8 -0
  75. package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
  76. package/.agents/scripts/lib/git-utils.js +0 -14
  77. package/.agents/scripts/lib/json-utils.js +1 -2
  78. package/.agents/scripts/lib/label-constants.js +0 -15
  79. package/.agents/scripts/lib/label-taxonomy.js +1 -12
  80. package/.agents/scripts/lib/observability/active-story-env.js +42 -163
  81. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  82. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  83. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  84. package/.agents/scripts/lib/observability/source-classifier.js +2 -2
  85. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  86. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  87. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  88. package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
  89. package/.agents/scripts/lib/orchestration/code-review.js +58 -168
  90. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
  91. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  92. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  93. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  94. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  95. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  96. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
  97. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  98. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  99. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
  100. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  101. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  102. package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
  103. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  104. package/.agents/scripts/lib/orchestration/plan-context.js +114 -24
  105. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
  106. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +3 -7
  107. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  108. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
  109. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
  110. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  111. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -75
  112. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
  113. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
  114. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  115. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
  116. package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
  117. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  118. package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
  119. package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
  120. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  121. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  122. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  123. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  124. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  125. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  126. package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
  127. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
  128. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  129. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  130. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +3 -12
  131. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  132. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
  133. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  134. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  135. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
  136. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  137. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  138. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
  139. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  140. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  141. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -32
  142. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  143. package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
  144. package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
  145. package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
  146. package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
  147. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  148. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
  149. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
  150. package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
  151. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
  152. package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
  153. package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
  154. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  155. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  156. package/.agents/scripts/lib/planning-corpus.js +12 -286
  157. package/.agents/scripts/lib/preflight-runner.js +2 -2
  158. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  159. package/.agents/scripts/lib/signals/index.js +4 -17
  160. package/.agents/scripts/lib/signals/read.js +35 -35
  161. package/.agents/scripts/lib/signals/schema.js +8 -11
  162. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  163. package/.agents/scripts/lib/signals/write.js +0 -1
  164. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  165. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  166. package/.agents/scripts/lib/story-adjacency.js +8 -7
  167. package/.agents/scripts/lib/story-body/story-body.js +6 -5
  168. package/.agents/scripts/lib/templates/decomposer-prompts.js +7 -3
  169. package/.agents/scripts/lib/test-env.js +14 -1
  170. package/.agents/scripts/lib/test-tiers.js +0 -3
  171. package/.agents/scripts/lib/ticket-body-sections.js +0 -14
  172. package/.agents/scripts/lib/validation-evidence.js +31 -59
  173. package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
  174. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  175. package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
  176. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  177. package/.agents/scripts/plan-context.js +38 -6
  178. package/.agents/scripts/plan-persist.js +145 -35
  179. package/.agents/scripts/plan-run-epilogue.js +83 -38
  180. package/.agents/scripts/post-structured-comment.js +0 -38
  181. package/.agents/scripts/pr-watch-with-update.js +43 -22
  182. package/.agents/scripts/providers/github/compose.js +0 -1
  183. package/.agents/scripts/providers/github/errors.js +0 -19
  184. package/.agents/scripts/providers/github/issues.js +1 -11
  185. package/.agents/scripts/providers/github/mappers.js +5 -0
  186. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  187. package/.agents/scripts/providers/github/tickets.js +33 -153
  188. package/.agents/scripts/providers/github.js +17 -6
  189. package/.agents/scripts/resolve-stories.js +236 -0
  190. package/.agents/scripts/run-coverage.js +4 -1
  191. package/.agents/scripts/run-lint.js +2 -2
  192. package/.agents/scripts/run-verify.js +31 -2
  193. package/.agents/scripts/signals-view.js +9 -10
  194. package/.agents/scripts/single-story-close.js +173 -18
  195. package/.agents/scripts/single-story-confirm-merge.js +288 -15
  196. package/.agents/scripts/single-story-init.js +6 -10
  197. package/.agents/scripts/stories-wave-tick.js +79 -4
  198. package/.agents/scripts/story-plan.js +3 -3
  199. package/.agents/scripts/update-ticket-state.js +8 -50
  200. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  201. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  202. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  203. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  204. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  205. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  206. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  207. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  208. package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
  209. package/.agents/skills/core/scope-triage/SKILL.md +3 -0
  210. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  211. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  212. package/.agents/skills/skills.index.json +2 -12
  213. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  214. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  215. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  216. package/.agents/workflows/audit-architecture.md +3 -4
  217. package/.agents/workflows/audit-clean-code.md +4 -4
  218. package/.agents/workflows/audit-documentation.md +4 -5
  219. package/.agents/workflows/audit-lighthouse.md +8 -0
  220. package/.agents/workflows/audit-navigability.md +10 -0
  221. package/.agents/workflows/audit-performance.md +2 -3
  222. package/.agents/workflows/audit-quality.md +8 -9
  223. package/.agents/workflows/audit-security.md +1 -2
  224. package/.agents/workflows/audit-seo.md +10 -0
  225. package/.agents/workflows/audit-ux-ui.md +7 -0
  226. package/.agents/workflows/deliver.md +98 -45
  227. package/.agents/workflows/git-cleanup.md +2 -2
  228. package/.agents/workflows/git-deliver.md +1 -1
  229. package/.agents/workflows/helpers/acceptance-self-eval.md +21 -13
  230. package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
  231. package/.agents/workflows/helpers/code-review.md +12 -10
  232. package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
  233. package/.agents/workflows/helpers/deliver-story.md +193 -118
  234. package/.agents/workflows/helpers/parallel-tooling.md +2 -2
  235. package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
  236. package/.agents/workflows/plan.md +184 -19
  237. package/.agents/workflows/qa-assist.md +6 -6
  238. package/.agents/workflows/qa-explore.md +3 -3
  239. package/.agents/workflows/qa-run.md +1 -5
  240. package/bin/mandrel.js +12 -1
  241. package/docs/CHANGELOG.md +40 -0
  242. package/lib/cli/registry.js +262 -19
  243. package/lib/cli/sync-agents.js +157 -0
  244. package/lib/cli/sync-commands.js +115 -6
  245. package/lib/cli/sync.js +168 -6
  246. package/lib/cli/update.js +105 -8
  247. package/lib/cli/version-helpers.js +131 -0
  248. package/lib/migrations/README.md +7 -5
  249. package/lib/migrations/index.js +12 -9
  250. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  251. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  252. package/package.json +1 -1
  253. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  254. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  255. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  256. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  257. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  258. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  259. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  260. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  261. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  262. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  263. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  264. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  265. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  266. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  267. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  268. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  269. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  270. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  271. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  272. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  273. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  274. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  275. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  276. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  277. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  278. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  279. package/.agents/schemas/risk-verdict.schema.json +0 -53
  280. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  281. package/.agents/scripts/analyze-execution.js +0 -444
  282. package/.agents/scripts/check-prepush-recovery.js +0 -90
  283. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  284. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  285. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
  286. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  287. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  288. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  289. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
  290. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
  291. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  292. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  293. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  294. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  295. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  296. package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
  297. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  298. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  299. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  300. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
  301. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  302. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  303. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  304. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
  305. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  306. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
  307. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  308. package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
  309. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
  310. package/.agents/scripts/resolve-plan-run.js +0 -117
  311. package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
@@ -9,12 +9,18 @@
9
9
  * misconfigured knip installation cannot block CI when we have no current
10
10
  * snapshot to compare against.
11
11
  *
12
+ * Story #4575 adds the **production pass** (`--production`), the test-only-
13
+ * importer discount: default mode counts a test as an importer, so
14
+ * production-dead code hides behind its own tests. See
15
+ * `lib/dead-exports-mode.js` for why the two passes carry separate baselines.
16
+ *
12
17
  * Contract:
13
- * - Reads the committed baseline at `baselines/dead-exports.json`
14
- * (override with `--baseline <path>`). Envelope shape:
18
+ * - Reads the committed baseline at `baselines/dead-exports.json` — or
19
+ * `baselines/dead-exports-production.json` under `--production`
20
+ * (override either with `--baseline <path>`). Envelope shape:
15
21
  * { $schema, kernelVersion, generatedAt, rows: [{ file, symbol }] }
16
- * - Spawns `npx knip --reporter json --no-progress`, parses stdout,
17
- * extracts `{ file, symbol }` rows from `issues[].exports[]`.
22
+ * - Spawns `npx knip --reporter json --no-progress` (plus `--production`),
23
+ * parses stdout, extracts `{ file, symbol }` rows from `issues[].exports[]`.
18
24
  * - Diffs current vs. baseline by `(file, symbol)` identity.
19
25
  * - Prints `+ <file>: <symbol>` for each added dead export and
20
26
  * `- <file>: <symbol>` for each removed one, then a summary line.
@@ -25,24 +31,30 @@
25
31
  * Knip spawn/parse failure exits 0 (advisory) with a stderr warning.
26
32
  */
27
33
 
28
- import { spawnSync } from 'node:child_process';
29
34
  import fs from 'node:fs';
30
35
  import path from 'node:path';
31
36
  import process from 'node:process';
32
37
  import { runAsCli } from './lib/cli-utils.js';
38
+ import {
39
+ extractRowsFromKnip,
40
+ readKnipOutput,
41
+ runKnip,
42
+ } from './lib/dead-exports-knip.js';
43
+ import { resolveDeadExportsMode } from './lib/dead-exports-mode.js';
33
44
 
34
45
  /**
35
- * Parse argv for `--baseline <path>`, `--json`, and `--knip-output <path>`.
36
- * `--knip-output` is a test seam: pass a pre-captured knip JSON file instead
37
- * of spawning knip. Exported so unit tests can pin the parser.
46
+ * Parse argv for `--baseline <path>`, `--json`, `--knip-output <path>`, and
47
+ * `--production`. `--knip-output` is a test seam: pass a pre-captured knip JSON
48
+ * file instead of spawning knip. Exported so unit tests can pin the parser.
38
49
  *
39
50
  * @param {string[]} argv
40
- * @returns {{ baselinePath: string | null, json: boolean, knipOutputPath: string | null }}
51
+ * @returns {{ baselinePath: string | null, json: boolean, knipOutputPath: string | null, production: boolean }}
41
52
  */
42
53
  export function parseArgv(argv = []) {
43
54
  let baselinePath = null;
44
55
  let json = false;
45
56
  let knipOutputPath = null;
57
+ let production = false;
46
58
  for (let i = 0; i < argv.length; i += 1) {
47
59
  const a = argv[i];
48
60
  if (a === '--baseline') {
@@ -53,6 +65,8 @@ export function parseArgv(argv = []) {
53
65
  }
54
66
  } else if (a === '--json') {
55
67
  json = true;
68
+ } else if (a === '--production') {
69
+ production = true;
56
70
  } else if (a === '--knip-output') {
57
71
  const next = argv[i + 1];
58
72
  if (next && !next.startsWith('--')) {
@@ -61,7 +75,7 @@ export function parseArgv(argv = []) {
61
75
  }
62
76
  }
63
77
  }
64
- return { baselinePath, json, knipOutputPath };
78
+ return { baselinePath, json, knipOutputPath, production };
65
79
  }
66
80
 
67
81
  /**
@@ -84,36 +98,6 @@ export function loadBaseline(baselinePath) {
84
98
  }
85
99
  }
86
100
 
87
- /**
88
- * Pure helper: normalize knip's `--reporter json` output into a flat array of
89
- * `{ file, symbol }` rows. Knip emits `{ issues: [{ file, exports: [{ name, ... }], ... }, ...] }`.
90
- * Only `exports` rows are mapped — the dead-export ratchet ignores file-level,
91
- * dependency-level, and duplicate-level issues (knip surfaces those via
92
- * separate `rules` keys).
93
- *
94
- * @param {unknown} knipEnvelope The parsed knip JSON report.
95
- * @returns {Array<{ file: string, symbol: string }>}
96
- */
97
- export function extractRowsFromKnip(knipEnvelope) {
98
- const rows = [];
99
- if (!knipEnvelope || typeof knipEnvelope !== 'object') return rows;
100
- const issues = Array.isArray(knipEnvelope.issues) ? knipEnvelope.issues : [];
101
- for (const issue of issues) {
102
- const file = issue?.file;
103
- if (typeof file !== 'string' || file.length === 0) continue;
104
- const exports_ = Array.isArray(issue.exports) ? issue.exports : [];
105
- for (const e of exports_) {
106
- const symbol =
107
- (e && typeof e.name === 'string' && e.name) ||
108
- (e && typeof e.symbol === 'string' && e.symbol) ||
109
- null;
110
- if (!symbol) continue;
111
- rows.push({ file, symbol });
112
- }
113
- }
114
- return rows;
115
- }
116
-
117
101
  /**
118
102
  * Pure helper: diff two `{ file, symbol }` row sets. Returns `added` (in
119
103
  * current but not baseline) and `removed` (in baseline but not current).
@@ -151,75 +135,24 @@ export function diffRows(baselineRows, currentRows) {
151
135
  * "no drift" signal. When added rows are present the summary includes a
152
136
  * "(gate fail)" marker so the ratchet violation is visible in CI output.
153
137
  *
138
+ * `label` distinguishes the two passes in CI logs, which run back to back and
139
+ * would otherwise emit two identical-looking summaries.
140
+ *
154
141
  * @param {{ added: Array, removed: Array }} diff
142
+ * @param {string} [label='dead-exports']
155
143
  * @returns {string}
156
144
  */
157
- export function renderDiff(diff) {
145
+ export function renderDiff(diff, label = 'dead-exports') {
158
146
  const lines = [];
159
147
  for (const r of diff.added) lines.push(`+ ${r.file}: ${r.symbol}`);
160
148
  for (const r of diff.removed) lines.push(`- ${r.file}: ${r.symbol}`);
161
149
  const tag = diff.added.length > 0 ? '(gate fail)' : '(ok)';
162
150
  lines.push(
163
- `[dead-exports] added=${diff.added.length} removed=${diff.removed.length} ${tag}`,
151
+ `[${label}] added=${diff.added.length} removed=${diff.removed.length} ${tag}`,
164
152
  );
165
153
  return lines.join('\n');
166
154
  }
167
155
 
168
- /**
169
- * Spawn `npx knip --reporter json --no-progress` and return the parsed
170
- * envelope. Returns `null` on spawn / parse failure — the caller logs the
171
- * underlying error and falls back to treating current rows as empty (which
172
- * surfaces every baseline row as "removed", a loud-but-safe signal).
173
- *
174
- * Exported as a hook so tests can stub the spawn without setting up a
175
- * functioning knip workspace.
176
- *
177
- * @param {{ cwd?: string, spawn?: typeof spawnSync }} [opts]
178
- * @returns {{ ok: true, envelope: unknown } | { ok: false, error: string }}
179
- */
180
- export function runKnip({ cwd = process.cwd(), spawn = spawnSync } = {}) {
181
- const result = spawn(
182
- process.platform === 'win32' ? 'npx.cmd' : 'npx',
183
- ['knip', '--reporter', 'json', '--no-progress'],
184
- {
185
- cwd,
186
- stdio: ['ignore', 'pipe', 'pipe'],
187
- encoding: 'utf-8',
188
- shell: process.platform === 'win32',
189
- },
190
- );
191
- if (result.error) {
192
- return { ok: false, error: `spawn failed: ${result.error.message}` };
193
- }
194
- const stdout = typeof result.stdout === 'string' ? result.stdout : '';
195
- if (stdout.trim().length === 0) {
196
- return { ok: false, error: 'knip produced empty stdout' };
197
- }
198
- try {
199
- return { ok: true, envelope: JSON.parse(stdout) };
200
- } catch (err) {
201
- return {
202
- ok: false,
203
- error: `knip JSON parse failed: ${err?.message ?? err}`,
204
- };
205
- }
206
- }
207
-
208
- /**
209
- * Read a pre-captured knip JSON envelope from disk (for the `--knip-output`
210
- * test seam). Returns the parsed envelope or `null` on failure.
211
- *
212
- * @param {string} filePath
213
- * @returns {unknown}
214
- */
215
- function readKnipOutput(filePath) {
216
- try {
217
- return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
218
- } catch {
219
- return null;
220
- }
221
- }
222
-
223
156
  /**
224
157
  * Top-level CLI entry. Exported so tests can drive the full pipeline through
225
158
  * injected hooks without spawning knip.
@@ -242,10 +175,15 @@ export async function runCli({
242
175
  runKnipImpl = runKnip,
243
176
  loadBaselineImpl = loadBaseline,
244
177
  } = {}) {
245
- const { baselinePath, json, knipOutputPath } = parseArgv(argv);
178
+ const { baselinePath, json, knipOutputPath, production } = parseArgv(argv);
179
+ const {
180
+ mode,
181
+ label,
182
+ baseline: defaultBaseline,
183
+ } = resolveDeadExportsMode(production);
246
184
  const resolvedBaselinePath = path.resolve(
247
185
  cwd,
248
- baselinePath ?? path.join('baselines', 'dead-exports.json'),
186
+ baselinePath ?? defaultBaseline,
249
187
  );
250
188
  const baseline = loadBaselineImpl(resolvedBaselinePath);
251
189
  const baselineRows = Array.isArray(baseline?.rows) ? baseline.rows : [];
@@ -256,7 +194,7 @@ export async function runCli({
256
194
  knipEnvelope = readKnipOutput(path.resolve(cwd, knipOutputPath));
257
195
  if (!knipEnvelope) knipError = `failed to read ${knipOutputPath}`;
258
196
  } else {
259
- const result = runKnipImpl({ cwd });
197
+ const result = runKnipImpl({ cwd, production });
260
198
  if (result.ok) {
261
199
  knipEnvelope = result.envelope;
262
200
  } else {
@@ -275,6 +213,7 @@ export async function runCli({
275
213
  if (json) {
276
214
  const envelope = {
277
215
  kind: 'dead-exports-report',
216
+ mode,
278
217
  baselinePath: resolvedBaselinePath,
279
218
  baselineRows,
280
219
  currentRows,
@@ -287,14 +226,14 @@ export async function runCli({
287
226
  } else {
288
227
  if (!baseline) {
289
228
  stderr.write(
290
- `[dead-exports] ⚠ baseline not found at ${resolvedBaselinePath} — treating as empty\n`,
229
+ `[${label}] ⚠ baseline not found at ${resolvedBaselinePath} — treating as empty\n`,
291
230
  );
292
231
  }
293
232
  if (knipError) {
294
- stderr.write(`[dead-exports] ⚠ knip run failed: ${knipError}\n`);
233
+ stderr.write(`[${label}] ⚠ knip run failed: ${knipError}\n`);
295
234
  }
296
- stdout.write(`\n--- dead-exports preview ---\n`);
297
- stdout.write(`${renderDiff(diff)}\n`);
235
+ stdout.write(`\n--- ${label} preview ---\n`);
236
+ stdout.write(`${renderDiff(diff, label)}\n`);
298
237
  }
299
238
 
300
239
  return exitCode;
@@ -257,8 +257,8 @@ function stripAnchorAndQuery(target) {
257
257
  // A slash token is `/<name>` where:
258
258
  // - the preceding char is NOT a word char, `/`, `:`, `.`, `>`, `]`, `)`,
259
259
  // so we don't match URL path segments or fragments embedded in paths
260
- // like `temp/epic-<id>/lifecycle.ndjson` (preceded by `>`) or
261
- // `temp/epic-[ID]/tickets.json` (preceded by `]`).
260
+ // like `temp/run-<id>/lifecycle.ndjson` (preceded by `>`) or
261
+ // `temp/run-[ID]/tickets.json` (preceded by `]`).
262
262
  // - the following char is NOT a word char, `-`, or `.`, so file
263
263
  // extensions like `/tickets.json` and identifier suffixes don't match.
264
264
  // The optional `(?::[a-z][a-z0-9-]*)?` tail captures the namespaced
@@ -17,7 +17,7 @@
17
17
  * Any module under `.agents/scripts/lib/orchestration/lifecycle/listeners/**`
18
18
  * that calls `bus.on('*', …)` MUST NOT import a side-effecting module.
19
19
  * The static blocklist is small (the modules that mutate GitHub state,
20
- * the worktree, or write outside `temp/epic-<id>/`); we match by module
20
+ * the worktree, or write outside `temp/run-<id>/`); we match by module
21
21
  * specifier suffix to keep the rule simple and stable.
22
22
  *
23
23
  * Rule 3 — "Auto-merge lockout" (Story #2253 / Task #2255, Epic #2172
@@ -61,6 +61,19 @@ const BACKTICK_CALL_RE = /`[A-Za-z_$][\w$]*\s*\(/;
61
61
  const EXPORTED_FROM_RE = /\bexported from\b/;
62
62
  const LIB_IMPORT_RE = /\b(?:import|require)\(\s*['"`][^'"`]*scripts\/lib\//;
63
63
 
64
+ /** A markdown table row (leading pipe, optionally indented). */
65
+ const TABLE_ROW_RE = /^\s*\|/;
66
+ /** A markdown heading of any level — the lookback boundary for Rule 4. */
67
+ const HEADING_RE = /^\s*#{1,6}\s/;
68
+ /** A backticked `--flag` occupying a table row's first cell. */
69
+ const FLAG_CELL_RE = /^\s*\|\s*`?--[\w-]+/;
70
+ /** A `.js` script filename — bare or path-qualified. */
71
+ const SCRIPT_NAME_RE = /([\w./-]*[\w-]+\.js)\b/;
72
+ /** A markdown table's delimiter row (`| --- | --- |`). */
73
+ const TABLE_DELIMITER_RE = /^\s*\|[\s|:-]+\|?\s*$/;
74
+ /** Minimum `--flag` rows before a table counts as a flag *table*. */
75
+ export const MIN_FLAG_ROWS = 2;
76
+
64
77
  /**
65
78
  * Strip fenced code blocks (``` / ~~~), replacing their lines with empty
66
79
  * strings so line numbers stay stable. Complete runnable commands live in
@@ -106,6 +119,82 @@ export function toParagraphs(lines) {
106
119
  return paragraphs;
107
120
  }
108
121
 
122
+ /**
123
+ * Rule 4 — CLI flag table. Find markdown tables that enumerate the `--flags`
124
+ * of a script the repo already owns, and flag them as duplicated surface.
125
+ *
126
+ * The script's own argument parser is the source of truth for its flags, and
127
+ * it prints them itself; a copy in prose is drift waiting to happen (the very
128
+ * drift Story #4546 removed). Prose must point at the command instead.
129
+ *
130
+ * The signal is deliberately narrow, to stay at zero false positives:
131
+ *
132
+ * 1. The table has at least `MIN_FLAG_ROWS` data rows whose **first cell**
133
+ * is a `--flag`. One flag row among prose rows is a contract/behaviour
134
+ * table (e.g. "Default | …", "`--dry-run` | …"), not a flag enumeration.
135
+ * 2. A `.js` script is named between the table and the nearest preceding
136
+ * heading (inclusive) — i.e. the section is *about* that script, so the
137
+ * table is restating its surface.
138
+ *
139
+ * A slash command's own argument table (`/plan`, `/deliver`) has no script
140
+ * behind it — nothing owns those flags but the workflow prose itself — so
141
+ * condition 2 leaves it alone by design.
142
+ *
143
+ * @param {string[]} rawLines unstripped source lines (fenced commands are the
144
+ * usual place a script is named, so this rule reads the original text).
145
+ * @returns {Array<{ rule: string, line: number, hint: string }>}
146
+ */
147
+ export function lintFlagTables(rawLines) {
148
+ const violations = [];
149
+ let inFence = false;
150
+
151
+ for (let i = 0; i < rawLines.length; i++) {
152
+ if (/^\s*(```|~~~)/.test(rawLines[i])) {
153
+ inFence = !inFence;
154
+ continue;
155
+ }
156
+ if (inFence || !TABLE_ROW_RE.test(rawLines[i])) continue;
157
+
158
+ // Collect the contiguous table block starting here.
159
+ const start = i;
160
+ let end = i;
161
+ while (end + 1 < rawLines.length && TABLE_ROW_RE.test(rawLines[end + 1])) {
162
+ end += 1;
163
+ }
164
+ i = end; // continue scanning after this table
165
+
166
+ const flagRows = rawLines
167
+ .slice(start, end + 1)
168
+ .filter((l) => !TABLE_DELIMITER_RE.test(l) && FLAG_CELL_RE.test(l));
169
+ if (flagRows.length < MIN_FLAG_ROWS) continue;
170
+
171
+ // Look back to the nearest heading (inclusive) for a named script.
172
+ let script = null;
173
+ for (let j = start - 1; j >= 0; j--) {
174
+ const m = SCRIPT_NAME_RE.exec(rawLines[j]);
175
+ if (m) {
176
+ script = m[1];
177
+ break;
178
+ }
179
+ if (HEADING_RE.test(rawLines[j])) break;
180
+ }
181
+ if (!script) continue;
182
+
183
+ const base = script.split('/').pop();
184
+ violations.push({
185
+ rule: 'no-cli-flag-table',
186
+ line: start + 1,
187
+ hint:
188
+ `Workflow prose restates the flag surface of \`${base}\` as a table (${flagRows.length} flag rows). ` +
189
+ "The script's argument parser owns those flags, so a prose copy is drift waiting to happen. " +
190
+ `Delete the table and point at the command (\`node .agents/scripts/${base} …\`), keeping only the ` +
191
+ 'judgement a reader cannot get from the command itself. If the script has no help output to point ' +
192
+ 'at, add one — do not re-inline the table.',
193
+ });
194
+ }
195
+ return violations;
196
+ }
197
+
109
198
  /**
110
199
  * Lint one markdown source. Returns violations
111
200
  * `{ rule, line, hint }[]` (empty when clean).
@@ -115,7 +204,9 @@ export function toParagraphs(lines) {
115
204
  */
116
205
  export function lintWorkflowSource(source) {
117
206
  const violations = [];
207
+ const rawLines = source.split('\n');
118
208
  const lines = stripFences(source);
209
+ violations.push(...lintFlagTables(rawLines));
119
210
  for (const para of toParagraphs(lines)) {
120
211
  if (IMPERATIVE_CALL_RE.test(para.text)) {
121
212
  violations.push({
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * deliver-recover.js — probe a stranded Story's live state and print the ONE
5
+ * command that resumes it (Story #4543).
6
+ *
7
+ * Read-only by construction. This CLI mutates nothing: it reads the ticket's
8
+ * labels and lease, the Story branch locally and via its tracking ref, the
9
+ * worktree, and the PR (state + checks), then walks a fixed decision table
10
+ * and prints one command with the evidence it was derived from. It never
11
+ * prints a menu — a menu of options is what the operator already has, and it
12
+ * is the thing they cannot act on.
13
+ *
14
+ * The strand this exists for above all others is the **merged-but-label-stale**
15
+ * Story: a `/deliver` re-run refuses it outright, because
16
+ * `single-story-init.js` hard-errors on an already-closed Story. Before this
17
+ * surface, that Story had no automated way back.
18
+ *
19
+ * Usage:
20
+ * node .agents/scripts/deliver-recover.js --story <STORY_ID> [--cwd <main-repo>]
21
+ * [--json]
22
+ *
23
+ * Exit codes:
24
+ * 0 — a recovery shape was resolved and its next command printed (including
25
+ * the "nothing to recover" shapes). Reading state is not a failure.
26
+ * 1 — the probe itself could not run (unreadable ticket, bad input).
27
+ *
28
+ * @see .agents/scripts/lib/orchestration/deliver-recover.js
29
+ * @see .agents/schemas/story-deliver-terminal.schema.json
30
+ */
31
+
32
+ import { parseArgs } from 'node:util';
33
+ import { runAsCli } from './lib/cli-utils.js';
34
+ import { resolveConfig } from './lib/config-resolver.js';
35
+ import { Logger } from './lib/Logger.js';
36
+ import {
37
+ recoverStory,
38
+ renderRecovery,
39
+ } from './lib/orchestration/deliver-recover.js';
40
+ import { PROJECT_ROOT } from './lib/project-root.js';
41
+ import { createProvider } from './lib/provider-factory.js';
42
+
43
+ const HELP = `Usage: node .agents/scripts/deliver-recover.js --story <id> [--cwd <main-repo>] [--json]
44
+
45
+ Probes a Story's live delivery state — labels, lease, branch, worktree, PR
46
+ state and checks — and prints the single next command that resumes it, with
47
+ the evidence it was derived from. Read-only: mutates nothing.
48
+
49
+ Flags:
50
+ --story GitHub issue number of the Story (required).
51
+ --cwd Main-repo checkout to probe (default: project root).
52
+ --json Emit the full recovery envelope as JSON instead of prose.
53
+ --help Show this message.
54
+ `;
55
+
56
+ export function parseArgv(argv) {
57
+ const { values } = parseArgs({
58
+ args: argv,
59
+ options: {
60
+ story: { type: 'string' },
61
+ cwd: { type: 'string' },
62
+ json: { type: 'boolean', default: false },
63
+ help: { type: 'boolean', default: false },
64
+ },
65
+ strict: false,
66
+ });
67
+ return {
68
+ storyId: Number.parseInt(String(values.story ?? ''), 10),
69
+ cwd: values.cwd ?? null,
70
+ json: Boolean(values.json),
71
+ help: Boolean(values.help),
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Probe and report. Exported for testing.
77
+ */
78
+ export async function runDeliverRecover({
79
+ storyId: storyIdParam,
80
+ cwd: cwdParam,
81
+ json: jsonParam,
82
+ argv,
83
+ injectedProvider,
84
+ injectedConfig,
85
+ injectedGh,
86
+ injectedGitSpawn,
87
+ } = {}) {
88
+ const parsed =
89
+ storyIdParam !== undefined
90
+ ? { storyId: storyIdParam, cwd: cwdParam ?? null, json: !!jsonParam }
91
+ : parseArgv(argv ?? process.argv.slice(2));
92
+
93
+ if (parsed.help) {
94
+ Logger.info(HELP);
95
+ return { success: true, result: null };
96
+ }
97
+ if (!Number.isInteger(parsed.storyId) || parsed.storyId <= 0) {
98
+ throw new Error(
99
+ 'Usage: node deliver-recover.js --story <STORY_ID> [--cwd <main-repo>] [--json]',
100
+ );
101
+ }
102
+
103
+ const cwd = parsed.cwd ?? PROJECT_ROOT;
104
+ const config = injectedConfig || resolveConfig({ cwd });
105
+ const provider = injectedProvider || createProvider(config);
106
+
107
+ const recovery = await recoverStory({
108
+ storyId: parsed.storyId,
109
+ cwd,
110
+ provider,
111
+ config,
112
+ gh: injectedGh,
113
+ gitSpawnFn: injectedGitSpawn,
114
+ });
115
+
116
+ Logger.info(
117
+ parsed.json ? JSON.stringify(recovery, null, 2) : renderRecovery(recovery),
118
+ );
119
+ return { success: true, result: recovery };
120
+ }
121
+
122
+ runAsCli(import.meta.url, runDeliverRecover, { source: 'deliver-recover' });
@@ -8,7 +8,7 @@
8
8
  * any still-stuck entries by enumerating the processes holding handles
9
9
  * inside the worktree path and terminating them.
10
10
  *
11
- * Invoked by `/deliver`, `/epic-plan-spec` / `/epic-plan-decompose`
11
+ * Invoked by `/deliver` and `/plan`
12
12
  * (via `drainPendingCleanupAtBoot` → `worktree-sweep.js`), and
13
13
  * `story-close` so the pending-cleanup ledger drains automatically
14
14
  * across the sprint lifecycle. Operators can also run it standalone:
@@ -13,42 +13,22 @@
13
13
  *
14
14
  * Usage:
15
15
  * node .agents/scripts/evidence-gate.js \
16
- * (--epic-id <epicId> | --standalone) --scope-id <storyOrEpicId> --gate <name> \
16
+ * --standalone --scope-id <storyId> --gate <name> \
17
17
  * [--worktree <path>] [--no-evidence] -- <cmd> [args...]
18
18
  *
19
19
  * Examples:
20
- * node .agents/scripts/evidence-gate.js --epic-id 802 --scope-id 817 --gate lint -- npm run lint
21
- * node .agents/scripts/evidence-gate.js --epic-id 1030 --scope-id 1030 --gate test -- npm test
22
- * node .agents/scripts/evidence-gate.js --epic-id 1114 --scope-id 1120 --gate test \
23
- * --worktree .worktrees/story-1120 -- npm test
24
20
  * node .agents/scripts/evidence-gate.js --standalone --scope-id 4250 --gate lint \
25
21
  * --worktree .worktrees/story-4250 -- npm run lint
22
+ * node .agents/scripts/evidence-gate.js --standalone --scope-id 4250 --gate test \
23
+ * --worktree .worktrees/story-4250 -- npm test
26
24
  *
27
- * Either `--epic-id` or `--standalone` is required. When `scope-id ===
28
- * epic-id` the evidence file is Epic-scoped
29
- * (`<tempRoot>/epic-<eid>/validation-evidence.json`); when `scope-id !==
30
- * epic-id` it is Story-scoped under
31
- * `<tempRoot>/epic-<eid>/story-<sid>/validation-evidence.json`. With
32
- * `--standalone` (Story #4250) the file is anchored on the Story id alone at
25
+ * `--standalone` (Story #4250) is required: the evidence file is anchored on
26
+ * the Story id at
33
27
  * `<tempRoot>/standalone/stories/story-<sid>/validation-evidence.json` — the
34
28
  * same keyspace the standalone close consults, so the acceptance-self-eval
35
29
  * critic's verify[] runs (lint / typecheck) are shared with the close.
36
- *
37
- * **Worktree-aware spawn (Story #1120).** `--cwd` (default `PROJECT_ROOT`)
38
- * is the *evidence cwd* — it locates the per-Epic temp tree under the main
39
- * checkout's `.git/`. `--worktree` is the *spawn cwd* — when supplied, the
40
- * gate runner is launched with `cwd: <worktree>` and the HEAD-SHA used as
41
- * the evidence key is read from the worktree (so caching keys to the Story
42
- * branch's HEAD, not the main branch's). When `--worktree` is omitted,
43
- * spawn cwd falls back to `--cwd` (legacy single-tree behaviour).
44
- *
45
- * Exit codes:
46
- * 0 — gate passed (or skipped via evidence)
47
- * N — gate failed (passes through the runner's exit code)
48
- *
49
- * `--no-evidence` forces the runner regardless of recorded state. The
50
- * evidence file is gitignored — evidence is a perf optimization, not a
51
- * trust boundary; pre-push hooks and CI continue to verify independently.
30
+ * v2.0.0 removed the Epic tier along with the `--epic-id` Epic-keyed
31
+ * keyspace.
52
32
  */
53
33
 
54
34
  import { spawnSync } from 'node:child_process';
@@ -88,7 +68,6 @@ export function parseWrapperArgs(argv) {
88
68
  args: argv,
89
69
  options: {
90
70
  'scope-id': { type: 'string' },
91
- 'epic-id': { type: 'string' },
92
71
  gate: { type: 'string' },
93
72
  'no-evidence': { type: 'boolean', default: false },
94
73
  standalone: { type: 'boolean', default: false },
@@ -98,10 +77,8 @@ export function parseWrapperArgs(argv) {
98
77
  strict: false,
99
78
  });
100
79
  const scopeId = Number.parseInt(values['scope-id'] ?? '', 10);
101
- const epicId = Number.parseInt(values['epic-id'] ?? '', 10);
102
80
  return {
103
81
  scopeId: Number.isNaN(scopeId) || scopeId <= 0 ? null : scopeId,
104
- epicId: Number.isNaN(epicId) || epicId <= 0 ? null : epicId,
105
82
  standalone: values.standalone === true,
106
83
  gate: values.gate ?? null,
107
84
  useEvidence: values['no-evidence'] !== true,
@@ -126,15 +103,13 @@ function resolveHeadShaDefault(cwd, gitSpawnFn) {
126
103
  * Exported for tests + the CLI `main()`.
127
104
  *
128
105
  * @param {object} params
129
- * @param {number} params.scopeId — Story / Epic ID (positive integer).
130
- * @param {number|null} [params.epicId] Parent Epic id (Epic-keyed path).
131
- * @param {boolean} [params.standalone] When true, route to the
132
- * storyId-anchored standalone keyspace instead of the Epic-keyed path
133
- * (Story #4250). Substitutes for `epicId`.
106
+ * @param {number} params.scopeId — Story ID (positive integer).
107
+ * @param {boolean} [params.standalone] Required. Routes to the
108
+ * storyId-anchored standalone keyspace (Story #4250).
134
109
  * @param {string} params.gate — Logical gate name (`lint`, `typecheck`, …).
135
110
  * @param {boolean} params.useEvidence — When false, force the runner.
136
- * @param {string} params.cwd — Evidence cwd (locates the per-Epic
137
- * temp tree). The runner is spawned in `worktreePath` when set, else `cwd`.
111
+ * @param {string} params.cwd — Evidence cwd (locates the temp
112
+ * tree). The runner is spawned in `worktreePath` when set, else `cwd`.
138
113
  * @param {string|null} [params.worktreePath] — Spawn cwd override (Story #1120).
139
114
  * When set, the runner runs in the Story worktree and the HEAD-SHA used as
140
115
  * the evidence cache key is read from the worktree, not from `cwd`.
@@ -159,7 +134,6 @@ export async function runEvidenceGate(params, deps = {}) {
159
134
  } = deps;
160
135
  const {
161
136
  scopeId,
162
- epicId,
163
137
  standalone = false,
164
138
  gate,
165
139
  useEvidence,
@@ -168,31 +142,27 @@ export async function runEvidenceGate(params, deps = {}) {
168
142
  runnerArgs,
169
143
  } = params ?? {};
170
144
 
171
- // `--epic-id` is required for the Epic-keyed path; `--standalone` (Story
172
- // #4250) substitutes for it and routes the evidence file to the
173
- // storyId-anchored standalone keyspace so the acceptance-self-eval critic
174
- // can record verify[] evidence into the same keyspace the standalone close
175
- // consults.
145
+ // `--standalone` (Story #4250) routes the evidence file to the
146
+ // storyId-anchored keyspace so the acceptance-self-eval critic can record
147
+ // verify[] evidence into the same keyspace the standalone close consults.
176
148
  if (
177
149
  !scopeId ||
178
- (!epicId && !standalone) ||
150
+ !standalone ||
179
151
  !gate ||
180
152
  !runnerArgs ||
181
153
  runnerArgs.length === 0
182
154
  ) {
183
155
  logger.fatal(
184
- 'Usage: node evidence-gate.js (--epic-id <epicId> | --standalone) --scope-id <id> --gate <name> [--worktree <path>] [--no-evidence] -- <cmd> [args...]',
156
+ 'Usage: node evidence-gate.js --standalone --scope-id <id> --gate <name> [--worktree <path>] [--no-evidence] -- <cmd> [args...]',
185
157
  );
186
158
  return { status: 1, skipped: false };
187
159
  }
188
- // Evidence-store opts shared by shouldSkip + recordPass below. `standalone`
189
- // routes to the storyId-anchored keyspace; otherwise the Epic-keyed path
190
- // resolves under `epicId`.
191
- const evidenceStoreOpts = { cwd, epicId, standalone };
160
+ // Evidence-store opts shared by shouldSkip + recordPass below.
161
+ const evidenceStoreOpts = { cwd, standalone };
192
162
 
193
163
  // Spawn cwd is the worktree when supplied — every gate command sees the
194
164
  // Story branch's tree, not the main checkout. Evidence cwd stays anchored
195
- // to the main checkout so the per-Epic temp tree resolves under the main
165
+ // to the main checkout so the temp tree resolves under the main
196
166
  // `.git/`. The HEAD-SHA used as the cache key is read from the spawn cwd
197
167
  // (the worktree), so cache entries key against the Story branch's HEAD.
198
168
  const spawnCwd = worktreePath ?? cwd;