mandrel 1.58.0 → 1.60.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 (319) hide show
  1. package/.agents/README.md +100 -98
  2. package/.agents/docs/SDLC.md +140 -141
  3. package/.agents/docs/configuration.md +16 -16
  4. package/.agents/docs/workflows.md +7 -8
  5. package/.agents/instructions.md +12 -11
  6. package/.agents/personas/architect.md +1 -1
  7. package/.agents/personas/product.md +1 -1
  8. package/.agents/personas/project-manager.md +14 -14
  9. package/.agents/personas/technical-writer.md +1 -1
  10. package/.agents/rules/changelog-style.md +5 -5
  11. package/.agents/rules/git-conventions.md +3 -3
  12. package/.agents/schemas/agentrc.schema.json +3 -3
  13. package/.agents/schemas/audit-rules.json +20 -0
  14. package/.agents/schemas/dispatch-manifest.json +4 -4
  15. package/.agents/schemas/epic-spec.schema.json +15 -45
  16. package/.agents/schemas/lifecycle/README.md +1 -1
  17. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +1 -1
  18. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +1 -1
  19. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +1 -1
  20. package/.agents/schemas/validation-evidence.schema.json +1 -1
  21. package/.agents/scripts/README.md +1 -1
  22. package/.agents/scripts/acceptance-eval.js +21 -4
  23. package/.agents/scripts/acceptance-spec-reconciler.js +2 -2
  24. package/.agents/scripts/analyze-execution.js +2 -2
  25. package/.agents/scripts/assert-branch.js +1 -3
  26. package/.agents/scripts/audit-to-stories.js +1 -1
  27. package/.agents/scripts/bootstrap.js +1 -1
  28. package/.agents/scripts/check-arch-cycles.js +360 -0
  29. package/.agents/scripts/check-doc-links.js +2 -3
  30. package/.agents/scripts/coverage-capture.js +24 -3
  31. package/.agents/scripts/diagnose-friction.js +1 -1
  32. package/.agents/scripts/dispatcher.js +2 -2
  33. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  34. package/.agents/scripts/epic-audit-prepare.js +3 -3
  35. package/.agents/scripts/epic-deliver-note-intervention.js +2 -2
  36. package/.agents/scripts/epic-deliver-preflight.js +11 -9
  37. package/.agents/scripts/epic-deliver-prepare.js +13 -5
  38. package/.agents/scripts/epic-execute-record-wave.js +5 -5
  39. package/.agents/scripts/epic-plan-healthcheck.js +6 -10
  40. package/.agents/scripts/epic-plan-spec-validate.js +1 -1
  41. package/.agents/scripts/epic-reconcile.js +11 -29
  42. package/.agents/scripts/evidence-gate.js +2 -2
  43. package/.agents/scripts/generate-workflows-doc.js +1 -1
  44. package/.agents/scripts/git-rebase-and-resolve.js +1 -1
  45. package/.agents/scripts/hierarchy-gate.js +40 -24
  46. package/.agents/scripts/lib/ITicketingProvider.js +1 -1
  47. package/.agents/scripts/lib/audit-suite/selector.js +1 -1
  48. package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +2 -2
  49. package/.agents/scripts/lib/baseline-snapshot.js +7 -7
  50. package/.agents/scripts/lib/baselines/kinds/coverage.js +33 -149
  51. package/.agents/scripts/lib/baselines/kinds/duplication.js +27 -116
  52. package/.agents/scripts/lib/baselines/kinds/kind-factory.js +192 -0
  53. package/.agents/scripts/lib/baselines/kinds/lighthouse.js +34 -133
  54. package/.agents/scripts/lib/baselines/kinds/maintainability.js +31 -124
  55. package/.agents/scripts/lib/baselines/kinds/mutation.js +25 -111
  56. package/.agents/scripts/lib/baselines/maintainability-baseline-io.js +59 -0
  57. package/.agents/scripts/lib/baselines/maintainability-baseline-save.js +37 -0
  58. package/.agents/scripts/lib/baselines/writer.js +1 -1
  59. package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
  60. package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
  61. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +1 -1
  62. package/.agents/scripts/lib/bootstrap/branch-protection.js +1 -1
  63. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +1 -1
  64. package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
  65. package/.agents/scripts/lib/close-validation/commands.js +188 -0
  66. package/.agents/scripts/lib/close-validation/gates.js +235 -0
  67. package/.agents/scripts/lib/close-validation/process.js +101 -0
  68. package/.agents/scripts/lib/close-validation/projections/maintainability.js +1 -1
  69. package/.agents/scripts/lib/close-validation/runner.js +325 -0
  70. package/.agents/scripts/lib/close-validation/telemetry.js +70 -0
  71. package/.agents/scripts/lib/codebase-snapshot.js +1 -1
  72. package/.agents/scripts/lib/config/explain.js +1 -1
  73. package/.agents/scripts/lib/config/quality.js +6 -6
  74. package/.agents/scripts/lib/config/runners.js +2 -2
  75. package/.agents/scripts/lib/config/runtime.js +1 -1
  76. package/.agents/scripts/lib/config/temp-paths.js +2 -2
  77. package/.agents/scripts/lib/config-resolver.js +2 -5
  78. package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
  79. package/.agents/scripts/lib/config-settings-schema-quality.js +1 -1
  80. package/.agents/scripts/lib/config-settings-schema.js +3 -3
  81. package/.agents/scripts/lib/coverage-capture.js +147 -4
  82. package/.agents/scripts/lib/cpu-pool.js +14 -0
  83. package/.agents/scripts/lib/crap-utils.js +6 -11
  84. package/.agents/scripts/lib/duplicate-search.js +1 -1
  85. package/.agents/scripts/lib/dynamic-workflow/capability.js +1 -1
  86. package/.agents/scripts/lib/dynamic-workflow/documentation-report-contract.js +87 -0
  87. package/.agents/scripts/lib/epic-plan-clarity.js +1 -1
  88. package/.agents/scripts/lib/epic-plan-ideation.js +1 -1
  89. package/.agents/scripts/lib/feedback-loop/memory-freshness.js +1 -1
  90. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +1 -1
  91. package/.agents/scripts/lib/findings/classify-finding.js +1 -1
  92. package/.agents/scripts/lib/findings/promote-finding.js +10 -10
  93. package/.agents/scripts/lib/git-utils.js +24 -22
  94. package/.agents/scripts/lib/label-constants.js +3 -4
  95. package/.agents/scripts/lib/label-taxonomy.js +3 -8
  96. package/.agents/scripts/lib/maintainability-engine.js +1 -1
  97. package/.agents/scripts/lib/maintainability-utils.js +4 -187
  98. package/.agents/scripts/lib/observability/perf-report-readers.js +32 -23
  99. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +81 -7
  100. package/.agents/scripts/lib/orchestration/code-review.js +95 -82
  101. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +8 -9
  102. package/.agents/scripts/lib/orchestration/dependency-analyzer.js +3 -3
  103. package/.agents/scripts/lib/orchestration/detectors-phase.js +2 -2
  104. package/.agents/scripts/lib/orchestration/dispatch-engine.js +30 -38
  105. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +14 -37
  106. package/.agents/scripts/lib/orchestration/epic-cleanup.js +1 -1
  107. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +22 -22
  108. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +1 -1
  109. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +7 -21
  110. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +3 -3
  111. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +2 -2
  112. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +206 -58
  113. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +1 -1
  114. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +27 -3
  115. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +1 -1
  116. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +28 -8
  117. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +1 -1
  118. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +3 -3
  119. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +4 -4
  120. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +3 -3
  121. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +13 -41
  122. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +7 -7
  123. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +2 -3
  124. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +2 -8
  125. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +4 -4
  126. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +103 -0
  127. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +22 -64
  128. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +38 -76
  129. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +4 -4
  130. package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +10 -10
  131. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +8 -20
  132. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +7 -15
  133. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +72 -41
  134. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +2 -4
  135. package/.agents/scripts/lib/orchestration/file-assumptions.js +6 -5
  136. package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +1 -1
  137. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +2 -2
  138. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +1 -1
  139. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +144 -0
  140. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +1 -1
  141. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +3 -3
  142. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +1 -1
  143. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +1 -1
  144. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +1 -1
  145. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +1 -1
  146. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +1 -1
  147. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
  148. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +1 -1
  149. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +1 -1
  150. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +8 -8
  151. package/.agents/scripts/lib/orchestration/manifest-builder.js +5 -5
  152. package/.agents/scripts/lib/orchestration/parked-follow-ons.js +2 -2
  153. package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +5 -5
  154. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +3 -3
  155. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +3 -3
  156. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +7 -7
  157. package/.agents/scripts/lib/orchestration/preflight-cache.js +36 -13
  158. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +1 -1
  159. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +1 -1
  160. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +2 -2
  161. package/.agents/scripts/lib/orchestration/retro-runner.js +3 -3
  162. package/.agents/scripts/lib/orchestration/review-depth.js +1 -1
  163. package/.agents/scripts/lib/orchestration/review-providers/codex.js +5 -60
  164. package/.agents/scripts/lib/orchestration/review-providers/native.js +7 -6
  165. package/.agents/scripts/lib/orchestration/review-providers/parse-findings.js +105 -0
  166. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +7 -59
  167. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +2 -4
  168. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +1 -1
  169. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +1 -1
  170. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +2 -4
  171. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +32 -35
  172. package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +1 -2
  173. package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
  174. package/.agents/scripts/lib/orchestration/spec-renderer.js +36 -73
  175. package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
  176. package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +451 -503
  177. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +8 -2
  178. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +47 -2
  179. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +2 -2
  180. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +1 -1
  181. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +358 -54
  182. package/.agents/scripts/lib/orchestration/story-close/phases/close.js +1 -1
  183. package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +3 -2
  184. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +32 -5
  185. package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +5 -18
  186. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +3 -3
  187. package/.agents/scripts/lib/orchestration/story-close-recovery.js +33 -16
  188. package/.agents/scripts/lib/orchestration/story-reachability.js +47 -0
  189. package/.agents/scripts/lib/orchestration/task-body-validator.js +6 -6
  190. package/.agents/scripts/lib/orchestration/ticket-lease.js +1 -1
  191. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +4 -35
  192. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -10
  193. package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -70
  194. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +44 -73
  195. package/.agents/scripts/lib/orchestration/ticketing/reads.js +16 -7
  196. package/.agents/scripts/lib/orchestration/ticketing/state.js +53 -439
  197. package/.agents/scripts/lib/orchestration/ticketing/transition.js +471 -0
  198. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  199. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +3 -3
  200. package/.agents/scripts/lib/orchestration/wave-record-projection.js +2 -8
  201. package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
  202. package/.agents/scripts/lib/preflight-runner.js +1 -1
  203. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +4 -5
  204. package/.agents/scripts/lib/presentation/manifest-builder.js +28 -34
  205. package/.agents/scripts/lib/presentation/manifest-formatter.js +3 -4
  206. package/.agents/scripts/lib/presentation/manifest-helpers.js +1 -1
  207. package/.agents/scripts/lib/presentation/manifest-procedures.js +4 -4
  208. package/.agents/scripts/lib/presentation/manifest-render-waves.js +4 -23
  209. package/.agents/scripts/lib/presentation/manifest-renderer.js +1 -1
  210. package/.agents/scripts/lib/presentation/manifest-story-views.js +2 -11
  211. package/.agents/scripts/lib/project-root.js +17 -0
  212. package/.agents/scripts/lib/signals/schema.js +1 -1
  213. package/.agents/scripts/lib/spec/index.js +1 -1
  214. package/.agents/scripts/lib/spec/loader.js +2 -2
  215. package/.agents/scripts/lib/spec/state.js +7 -16
  216. package/.agents/scripts/lib/story-adjacency.js +76 -0
  217. package/.agents/scripts/lib/story-init/context-resolver.js +3 -3
  218. package/.agents/scripts/lib/story-init/state-transitioner.js +2 -2
  219. package/.agents/scripts/lib/story-init/task-graph-builder.js +7 -7
  220. package/.agents/scripts/lib/story-lifecycle.js +9 -9
  221. package/.agents/scripts/lib/story-plan.js +1 -1
  222. package/.agents/scripts/lib/templates/decomposer-prompts.js +59 -52
  223. package/.agents/scripts/lib/transpile.js +93 -0
  224. package/.agents/scripts/lib/wave-runner/tick.js +4 -153
  225. package/.agents/scripts/lib/workers/crap-worker.js +1 -1
  226. package/.agents/scripts/lib/workers/maintainability-report-worker.js +1 -1
  227. package/.agents/scripts/lib/worktree/lifecycle/creation.js +20 -2
  228. package/.agents/scripts/lib/worktree/lifecycle/force-drain.js +90 -0
  229. package/.agents/scripts/lib/worktree/lifecycle/reap.js +26 -8
  230. package/.agents/scripts/lib/worktree/node-modules-strategy.js +74 -0
  231. package/.agents/scripts/lifecycle-emit-story-dispatch.js +1 -1
  232. package/.agents/scripts/lifecycle-emit.js +1 -1
  233. package/.agents/scripts/providers/github/board-add.js +1 -1
  234. package/.agents/scripts/providers/github/errors.js +1 -1
  235. package/.agents/scripts/providers/github/mappers.js +2 -2
  236. package/.agents/scripts/providers/github/tickets.js +114 -10
  237. package/.agents/scripts/resync-status-column.js +1 -1
  238. package/.agents/scripts/retro-run.js +2 -2
  239. package/.agents/scripts/run-lint.js +10 -1
  240. package/.agents/scripts/run-tests.js +24 -4
  241. package/.agents/scripts/single-story-init.js +1 -1
  242. package/.agents/scripts/stories-wave-tick.js +13 -10
  243. package/.agents/scripts/story-close.js +1 -1
  244. package/.agents/scripts/story-init.js +162 -26
  245. package/.agents/scripts/story-phase.js +5 -5
  246. package/.agents/scripts/story-plan.js +3 -3
  247. package/.agents/scripts/sync-branch-from-base.js +2 -2
  248. package/.agents/scripts/validate-docs-freshness.js +1 -1
  249. package/.agents/scripts/wave-tick.js +1 -1
  250. package/.agents/skills/core/analyze-execution/SKILL.md +2 -2
  251. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +21 -26
  252. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +23 -56
  253. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +4 -4
  254. package/.agents/skills/core/hydrate-context/SKILL.md +2 -2
  255. package/.agents/skills/core/idea-refinement/SKILL.md +4 -4
  256. package/.agents/skills/core/knowledge-transfer/SKILL.md +2 -2
  257. package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +1 -1
  258. package/.agents/skills/core/scope-triage/SKILL.md +9 -10
  259. package/.agents/skills/core/using-agent-skills/SKILL.md +1 -1
  260. package/.agents/skills/skills.index.json +7 -7
  261. package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +1 -1
  262. package/.agents/templates/agent-protocol.md +2 -2
  263. package/.agents/workflows/agents-update.md +2 -2
  264. package/.agents/workflows/audit-architecture.md +2 -2
  265. package/.agents/workflows/audit-clean-code.md +2 -2
  266. package/.agents/workflows/audit-dependencies.md +1 -1
  267. package/.agents/workflows/audit-devops.md +1 -1
  268. package/.agents/workflows/audit-documentation.md +226 -0
  269. package/.agents/workflows/audit-lighthouse.md +1 -1
  270. package/.agents/workflows/audit-performance.md +2 -2
  271. package/.agents/workflows/audit-privacy.md +1 -1
  272. package/.agents/workflows/audit-quality.md +2 -2
  273. package/.agents/workflows/audit-security.md +2 -2
  274. package/.agents/workflows/audit-seo.md +1 -1
  275. package/.agents/workflows/audit-sre.md +1 -1
  276. package/.agents/workflows/audit-to-stories.md +10 -10
  277. package/.agents/workflows/audit-ux-ui.md +1 -1
  278. package/.agents/workflows/deliver.md +85 -0
  279. package/.agents/workflows/explain.md +3 -3
  280. package/.agents/workflows/git-merge-pr.md +1 -1
  281. package/.agents/workflows/git-pr-all.md +13 -10
  282. package/.agents/workflows/git-push.md +6 -3
  283. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  284. package/.agents/workflows/helpers/acceptance-self-eval.md +1 -1
  285. package/.agents/workflows/helpers/code-review.md +5 -5
  286. package/.agents/workflows/{epic-deliver.md → helpers/deliver-epic.md} +59 -66
  287. package/.agents/workflows/{story-deliver.md → helpers/deliver-stories.md} +25 -25
  288. package/.agents/workflows/helpers/diagnose.md +1 -1
  289. package/.agents/workflows/helpers/epic-audit.md +6 -6
  290. package/.agents/workflows/helpers/epic-deliver-story.md +28 -39
  291. package/.agents/workflows/helpers/epic-plan-decompose.md +23 -23
  292. package/.agents/workflows/helpers/epic-plan-spec.md +6 -6
  293. package/.agents/workflows/helpers/epic-testing.md +3 -3
  294. package/.agents/workflows/helpers/parallel-tooling.md +1 -1
  295. package/.agents/workflows/{epic-plan.md → helpers/plan-epic.md} +84 -84
  296. package/.agents/workflows/{story-plan.md → helpers/plan-story.md} +43 -43
  297. package/.agents/workflows/helpers/signals.md +1 -1
  298. package/.agents/workflows/helpers/single-story-deliver.md +12 -11
  299. package/.agents/workflows/helpers/worktree-lifecycle.md +18 -18
  300. package/.agents/workflows/onboard.md +21 -20
  301. package/.agents/workflows/plan.md +89 -0
  302. package/.agents/workflows/qa-explore.md +1 -1
  303. package/.agents/workflows/qa-run-harness.md +1 -1
  304. package/README.md +17 -20
  305. package/docs/CHANGELOG.md +1149 -0
  306. package/lib/cli/__tests__/update-changelog-surface.test.js +357 -0
  307. package/lib/cli/__tests__/update-reexec.test.js +513 -0
  308. package/lib/cli/init.js +338 -0
  309. package/lib/cli/update.js +413 -52
  310. package/package.json +3 -1
  311. package/.agents/scripts/lib/auto-refresh-baselines.js +0 -308
  312. package/.agents/scripts/lib/close-validation.js +0 -897
  313. package/.agents/scripts/lib/orchestration/cascade-grouping.js +0 -275
  314. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter.js +0 -69
  315. package/.agents/scripts/lib/orchestration/reconciler.js +0 -137
  316. package/.agents/scripts/lib/orchestration/story-close/format-autofix-scoped.js +0 -221
  317. package/.agents/scripts/lib/orchestration/story-close/format-autofix-shared.js +0 -123
  318. package/.agents/scripts/lib/task-utils.js +0 -26
  319. package/.agents/scripts/story-deliver-prepare.js +0 -267
@@ -1,119 +1,24 @@
1
1
  import fs from 'node:fs';
2
- import { createRequire } from 'node:module';
3
2
  import path from 'node:path';
4
3
  import { minimatch } from 'minimatch';
5
4
  import { canonicalise as canonicalisePath } from './baselines/path-canon.js';
6
- import {
7
- write as writeBaselineEnvelope,
8
- writeFile as writeBaselineFile,
9
- } from './baselines/writer.js';
10
- import { runOnPool } from './cpu-pool.js';
5
+ import { POOL_SERIAL_THRESHOLD, runOnPool } from './cpu-pool.js';
11
6
  import { Logger } from './Logger.js';
12
7
  import { calculateForFile } from './maintainability-engine.js';
13
8
 
14
- const require = createRequire(import.meta.url);
15
-
16
9
  const MAINTAINABILITY_WORKER_URL = new URL(
17
10
  './workers/maintainability-worker.js',
18
11
  import.meta.url,
19
12
  );
20
13
 
21
- // Below this batch size the pool's spawn overhead dominates — fall back
22
- // to in-process serial scoring for `--changed-since` runs that touch
23
- // only a handful of files. Tuned against the test suite's tmpdir
24
- // fixtures (n=2 stays serial; the full repo n≈470 takes the pool path).
25
- const SERIAL_THRESHOLD = 8;
14
+ // Pool-vs-serial cutover single-sourced in cpu-pool.js (see the
15
+ // POOL_SERIAL_THRESHOLD docstring for the tuning rationale).
16
+ const SERIAL_THRESHOLD = POOL_SERIAL_THRESHOLD;
26
17
 
27
18
  const JS_EXTS = new Set(['.js', '.mjs', '.cjs']);
28
19
  const TS_EXTS = new Set(['.ts', '.tsx', '.mts', '.cts']);
29
20
  const SUPPORTED_EXTS = new Set([...JS_EXTS, ...TS_EXTS]);
30
21
 
31
- let _ts = null;
32
- let _tsLoadFailed = false;
33
-
34
- function loadTypeScript() {
35
- if (_ts) return _ts;
36
- if (_tsLoadFailed) return null;
37
- try {
38
- _ts = require('typescript');
39
- return _ts;
40
- } catch {
41
- _tsLoadFailed = true;
42
- return null;
43
- }
44
- }
45
-
46
- /**
47
- * Resolve the `typescript` package version, used to stamp baselines so
48
- * consumers can detect transpiler drift. Returns `'0.0.0'` when the
49
- * dependency is unresolvable — callers treat that sentinel as "unknown
50
- * environment" and may refuse to persist a baseline that includes TS rows.
51
- *
52
- * @returns {string}
53
- */
54
- export function resolveTsTranspilerVersion() {
55
- const ts = loadTypeScript();
56
- if (ts && typeof ts.version === 'string') return ts.version;
57
- return '0.0.0';
58
- }
59
-
60
- function isTypeScriptPath(filePath) {
61
- return TS_EXTS.has(path.extname(String(filePath)).toLowerCase());
62
- }
63
-
64
- /**
65
- * Pre-transpile TypeScript or TSX sources to JavaScript that the
66
- * Esprima-based escomplex kernel can parse. Returns the input unchanged
67
- * for `.js` / `.mjs` / `.cjs` paths.
68
- *
69
- * Type annotations introduce no control flow, so the transpiled output
70
- * scores identically to the original TS for cyclomatic complexity,
71
- * Halstead volume, and the maintainability index. `.tsx` uses the
72
- * `react-jsx` emit so JSX expressions become function calls escomplex
73
- * can read; `.preserve` would leave JSX in the output and Esprima would
74
- * choke on it.
75
- *
76
- * On transpile failure the helper returns `null` — callers treat that
77
- * as "skip this file" rather than crashing the scan.
78
- *
79
- * @param {string} filePath
80
- * @param {string} source
81
- * @returns {string|null}
82
- */
83
- export function transpileIfNeeded(filePath, source) {
84
- if (!isTypeScriptPath(filePath)) return source;
85
- const ts = loadTypeScript();
86
- if (!ts) {
87
- Logger.warn(
88
- `[Maintainability] ⚠ typescript package not resolvable; cannot score ${filePath}. ` +
89
- "Install with 'npm install --save-dev typescript' (peer dep, >=5.0.0).",
90
- );
91
- return null;
92
- }
93
- try {
94
- const result = ts.transpileModule(source, {
95
- compilerOptions: {
96
- target: ts.ScriptTarget.ESNext,
97
- module: ts.ModuleKind.ESNext,
98
- isolatedModules: true,
99
- noEmitHelpers: true,
100
- importHelpers: false,
101
- removeComments: false,
102
- jsx: ts.JsxEmit.ReactJSX,
103
- sourceMap: false,
104
- },
105
- fileName: path.basename(filePath),
106
- reportDiagnostics: false,
107
- });
108
- return result.outputText;
109
- } catch (err) {
110
- Logger.warn(
111
- `[Maintainability] ⚠ TS transpile failed for ${filePath}: ${err?.message ?? err}; skipping.`,
112
- );
113
- return null;
114
- }
115
- }
116
-
117
22
  /**
118
23
  * @returns {boolean} True when the path's extension is one the engines score.
119
24
  */
@@ -121,94 +26,6 @@ function isSupportedSourceFile(filePath) {
121
26
  return SUPPORTED_EXTS.has(path.extname(String(filePath)).toLowerCase());
122
27
  }
123
28
 
124
- /**
125
- * Loads the current maintainability baseline from disk. The on-disk path is
126
- * resolved by the caller via {@link getBaselines}; passing it explicitly
127
- * removes the silent-default behaviour the framework dropped in Epic #730
128
- * Story 5.5.
129
- *
130
- * @param {string} baselinePath Repo-relative or absolute path to the baseline
131
- * JSON. Required.
132
- * @returns {Record<string, number>}
133
- */
134
- /**
135
- * Story #1895: project the canonical maintainability envelope back to the
136
- * legacy flat `{ path: mi }` map so existing gate consumers keep working
137
- * without churn — Story #1912 will replace this shim with the shared
138
- * reader. Returns the parsed input unchanged when it doesn't look like an
139
- * envelope (legacy flat shape stays flat).
140
- */
141
- function projectMaintainabilityEnvelopeToFlat(parsed) {
142
- if (
143
- !parsed ||
144
- typeof parsed !== 'object' ||
145
- Array.isArray(parsed) ||
146
- !Array.isArray(parsed.rows) ||
147
- typeof parsed.$schema !== 'string'
148
- ) {
149
- return parsed;
150
- }
151
- const flat = {};
152
- for (const row of parsed.rows) {
153
- if (row && typeof row.path === 'string' && typeof row.mi === 'number') {
154
- flat[row.path] = row.mi;
155
- }
156
- }
157
- return flat;
158
- }
159
-
160
- export function getBaseline(baselinePath) {
161
- if (typeof baselinePath !== 'string' || baselinePath.length === 0) {
162
- throw new TypeError(
163
- 'maintainability-utils.getBaseline: baselinePath is required (Epic #730 ' +
164
- 'Story 5.5 — callers resolve the path via getBaselines(config).maintainability.path).',
165
- );
166
- }
167
- const abs = path.isAbsolute(baselinePath)
168
- ? baselinePath
169
- : path.resolve(process.cwd(), baselinePath);
170
- if (!fs.existsSync(abs)) return {};
171
- try {
172
- const parsed = JSON.parse(fs.readFileSync(abs, 'utf-8'));
173
- return projectMaintainabilityEnvelopeToFlat(parsed);
174
- } catch (err) {
175
- Logger.warn(`[Maintainability] Failed to parse baseline: ${err.message}`);
176
- return {};
177
- }
178
- }
179
-
180
- /**
181
- * Saves a new maintainability baseline to disk at `baselinePath`.
182
- *
183
- * Accepts the legacy flat `{ path: mi }` shape for backwards compatibility
184
- * with existing callers (`regenerateMainFromTree`, refresh helpers). The
185
- * map is transformed into the canonical envelope shape (`$schema`,
186
- * `kernelVersion`, `generatedAt`, `rollup`, `rows`) via the shared
187
- * `lib/baselines/writer.js` pipeline before being persisted, so every
188
- * write produces a file that round-trips through `lib/baselines/reader.js`
189
- * without schema errors.
190
- *
191
- * @param {Record<string, number>} baseline path→MI flat map.
192
- * @param {string} baselinePath Required — caller supplies via getBaselines().
193
- */
194
- export function saveBaseline(baseline, baselinePath) {
195
- if (typeof baselinePath !== 'string' || baselinePath.length === 0) {
196
- throw new TypeError(
197
- 'maintainability-utils.saveBaseline: baselinePath is required.',
198
- );
199
- }
200
- const abs = path.isAbsolute(baselinePath)
201
- ? baselinePath
202
- : path.resolve(process.cwd(), baselinePath);
203
-
204
- const rows = Object.entries(baseline ?? {}).map(([p, mi]) => ({
205
- path: p,
206
- mi,
207
- }));
208
- const envelope = writeBaselineEnvelope({ kind: 'maintainability', rows });
209
- writeBaselineFile(abs, envelope);
210
- }
211
-
212
29
  const IGNORED_DIRS = new Set([
213
30
  'node_modules',
214
31
  '.git',
@@ -16,6 +16,7 @@ import { storyArtifactPath } from '../config/temp-paths.js';
16
16
  import { gitSpawn } from '../git-utils.js';
17
17
  import { Logger } from '../Logger.js';
18
18
  import { read as readSignals } from '../signals/read.js';
19
+ import { concurrentMap } from '../util/concurrent-map.js';
19
20
  import { extractStoryPerfSummaryFromComment } from './perf-report-render.js';
20
21
  import { forEachLine } from './signals-writer.js';
21
22
 
@@ -154,30 +155,38 @@ export async function collectStorySummaries(provider, epicId, logger) {
154
155
  ),
155
156
  );
156
157
 
157
- const summaries = [];
158
- for (const ticket of storyTickets) {
159
- const id = Number(ticket.id ?? ticket.number);
160
- if (!Number.isInteger(id) || id < 1) continue;
161
- let comments;
162
- try {
163
- comments = (await provider.getTicketComments(id)) ?? [];
164
- } catch (err) {
165
- logger.warn?.(
166
- `[analyze-execution] getTicketComments(${id}) failed: ${
167
- err instanceof Error ? err.message : String(err)
168
- }`,
169
- );
170
- continue;
171
- }
172
- for (const c of comments) {
173
- const parsed = extractStoryPerfSummaryFromComment(c?.body);
174
- if (parsed) {
175
- summaries.push(parsed);
176
- break;
158
+ // Bounded-parallel comment fetch (Story #3990). Each Story's comment
159
+ // thread is an independent paginated REST read through a fresh `gh`
160
+ // spawn, so a serial loop pays N sequential round-trips. concurrentMap
161
+ // preserves input→output index, keeping summaries in Story order; the
162
+ // per-Story try/catch stays inside the mapper so one failed fetch
163
+ // degrades to warn-and-skip without aborting the batch (mirrors
164
+ // verifySingleResult in lib/orchestration/wave-record-io.js, #3024).
165
+ const perStory = await concurrentMap(
166
+ storyTickets,
167
+ async (ticket) => {
168
+ const id = Number(ticket.id ?? ticket.number);
169
+ if (!Number.isInteger(id) || id < 1) return null;
170
+ let comments;
171
+ try {
172
+ comments = (await provider.getTicketComments(id)) ?? [];
173
+ } catch (err) {
174
+ logger.warn?.(
175
+ `[analyze-execution] getTicketComments(${id}) failed: ${
176
+ err instanceof Error ? err.message : String(err)
177
+ }`,
178
+ );
179
+ return null;
177
180
  }
178
- }
179
- }
180
- return summaries;
181
+ for (const c of comments) {
182
+ const parsed = extractStoryPerfSummaryFromComment(c?.body);
183
+ if (parsed) return parsed;
184
+ }
185
+ return null;
186
+ },
187
+ { concurrency: 4 },
188
+ );
189
+ return perStory.filter((s) => s !== null);
181
190
  }
182
191
 
183
192
  /**
@@ -1,12 +1,22 @@
1
1
  /**
2
2
  * Acceptance self-eval decision core (Story #3819).
3
3
  *
4
- * Pure, I/O-free reducer that turns one round's critic verdict plus the
5
- * resolved round cap into the loop's next action. The CLI wrapper
4
+ * Pure reducer that turns one round's critic verdict plus the resolved
5
+ * round cap into the loop's next action. The CLI wrapper
6
6
  * (`acceptance-eval.js`) owns the file reads, schema validation, signal
7
7
  * emission, and ticket transitions; this module owns the *decision* so it
8
8
  * can be unit-tested in isolation.
9
9
  *
10
+ * ## Round derivation (Story #4019)
11
+ *
12
+ * The round number is **derived from the signals ledger**, not from the
13
+ * critic's self-reported `verdict.round`: every prior round appended one
14
+ * `acceptance-eval` signal to the Story's `signals.ndjson`, so the
15
+ * current round is `count(prior signals) + 1`. This survives a subagent
16
+ * restart (the ledger is on disk) and removes the critic's scratch value
17
+ * from the cap enforcement path — a critic that always reports `round: 1`
18
+ * can no longer defeat the bounded-loop guarantee.
19
+ *
10
20
  * ## The three terminal actions
11
21
  *
12
22
  * - `proceed` — every criterion is `met`. The Story may flip to
@@ -29,6 +39,10 @@
29
39
  * possible action is `block`.
30
40
  */
31
41
 
42
+ import { readFileSync } from 'node:fs';
43
+
44
+ import { signalsFile } from '../config/temp-paths.js';
45
+
32
46
  /**
33
47
  * Verdicts that clear a criterion. Anything else (`partial`, `unmet`, or
34
48
  * an unrecognised value) is treated as not-yet-met and triggers rework.
@@ -87,11 +101,16 @@ function partitionCriteria(criteria) {
87
101
  * Decide the next loop action from a single round's verdict.
88
102
  *
89
103
  * @param {object} args
90
- * @param {{ round?: number, criteria?: Array<object> }} args.verdict
104
+ * @param {{ criteria?: Array<object> }} args.verdict
91
105
  * A verdict already validated against the acceptance-eval-verdict schema.
106
+ * Its `round` field, when present, is ignored — the round is supplied by
107
+ * the caller (derived from the signals ledger; Story #4019).
92
108
  * @param {number} args.maxRounds
93
109
  * The resolved (already-clamped) redraft ceiling from
94
110
  * `getAcceptanceEval(config).maxRounds`.
111
+ * @param {number} [args.round]
112
+ * The current round number, derived via `deriveAcceptanceEvalRound`.
113
+ * Defaults to 1 when absent or invalid.
95
114
  * @returns {{
96
115
  * decision: 'proceed' | 'redraft' | 'block',
97
116
  * round: number,
@@ -102,10 +121,9 @@ function partitionCriteria(criteria) {
102
121
  * capReached: boolean,
103
122
  * }}
104
123
  */
105
- export function decideAcceptanceEval({ verdict, maxRounds }) {
124
+ export function decideAcceptanceEval({ verdict, maxRounds, round: roundIn }) {
106
125
  const cap = effectiveCap(maxRounds);
107
- const round =
108
- Number.isInteger(verdict?.round) && verdict.round >= 1 ? verdict.round : 1;
126
+ const round = Number.isInteger(roundIn) && roundIn >= 1 ? roundIn : 1;
109
127
  const { metCount, notMet } = partitionCriteria(verdict?.criteria);
110
128
  const totalCriteria = metCount + notMet.length;
111
129
  const allMet = notMet.length === 0;
@@ -134,7 +152,7 @@ export function decideAcceptanceEval({ verdict, maxRounds }) {
134
152
  /**
135
153
  * Build the per-criterion acceptance-eval signal payload for the retro /
136
154
  * feedback substrate. Carries which acceptance items needed rework and the
137
- * round count so `/epic-plan` Phase 0 feedback fetch and the retro can
155
+ * round count so `/plan` Phase 0 feedback fetch and the retro can
138
156
  * surface acceptance churn. PII-free by construction — it carries only
139
157
  * acceptance-item indices, verdicts, and the terminal decision.
140
158
  *
@@ -171,3 +189,59 @@ export function buildAcceptanceEvalSignal({
171
189
  },
172
190
  };
173
191
  }
192
+
193
+ /**
194
+ * Derive the current acceptance-eval round for a Story by counting the
195
+ * `acceptance-eval` signals already appended to the Story's
196
+ * `signals.ndjson` (Story #4019). Round = prior-signal count + 1, so the
197
+ * first run reports round 1 and each completed round (which appends one
198
+ * signal via `acceptance-eval.js`) advances the derived round by one.
199
+ *
200
+ * The derivation is restart-safe: the ledger lives on disk, so a subagent
201
+ * that dies mid-loop and restarts still observes every prior round. A
202
+ * missing or malformed ledger degrades to round 1 (no prior rounds), and
203
+ * malformed lines are skipped — observability corruption never wedges the
204
+ * gate.
205
+ *
206
+ * @param {object} args
207
+ * @param {number|null} args.epicId Parent Epic ID, or `null` for a
208
+ * standalone Story (routes to `<tempRoot>/standalone/stories/...`).
209
+ * @param {number} args.storyId
210
+ * @param {object} [args.config] Resolved config (tempRoot resolution).
211
+ * @param {(p: string) => string} [args.readFile] Injectable reader (tests).
212
+ * @param {(eid: number|null, sid: number, config?: object) => string} [args.signalsPathResolver]
213
+ * Injectable path resolver (tests). Defaults to `signalsFile`.
214
+ * @returns {number} The 1-based current round.
215
+ */
216
+ export function deriveAcceptanceEvalRound({
217
+ epicId,
218
+ storyId,
219
+ config,
220
+ readFile = (p) => readFileSync(p, 'utf8'),
221
+ signalsPathResolver = signalsFile,
222
+ }) {
223
+ let text;
224
+ try {
225
+ text = readFile(signalsPathResolver(epicId ?? null, storyId, config));
226
+ } catch (_err) {
227
+ // No ledger yet → no prior rounds.
228
+ return 1;
229
+ }
230
+
231
+ let priorRounds = 0;
232
+ for (const line of String(text).split('\n')) {
233
+ const trimmed = line.trim();
234
+ if (trimmed === '') continue;
235
+ let record;
236
+ try {
237
+ record = JSON.parse(trimmed);
238
+ } catch (_err) {
239
+ continue; // Malformed line — skip, never throw.
240
+ }
241
+ if (!record || typeof record !== 'object') continue;
242
+ if (record.kind !== 'acceptance-eval') continue;
243
+ if (record.storyId !== storyId) continue;
244
+ priorRounds += 1;
245
+ }
246
+ return priorRounds + 1;
247
+ }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Story #1155 (Epic #1142, 5.40.0) — extracted the helper-driven
5
5
  * `epic-code-review` invocation into a callable module so the
6
- * `/epic-deliver` runner can run Phase D without spawning a child
6
+ * `/deliver` runner can run Phase D without spawning a child
7
7
  * process or routing through an LLM-driven helper.
8
8
  *
9
9
  * Story #2831 (Epic #2815, Pluggable Code Review) — refactored to load
@@ -16,7 +16,7 @@
16
16
  * chain are unchanged.
17
17
  *
18
18
  * Public API:
19
- * - `runCodeReview({ epicId, provider, logger, bus, ... })` →
19
+ * - `runCodeReview({ scope, ticketId, provider, logger, bus, ... })` →
20
20
  * `{ status, severity, posted, report, halted, blockerReason }`.
21
21
  *
22
22
  * Behaviour:
@@ -25,11 +25,11 @@
25
25
  * - Always posts the structured `code-review` comment on the Epic
26
26
  * issue (the adapter never posts; the orchestrator owns persistence).
27
27
  * - Treats severity.critical > 0 as a halting blocker — the merged
28
- * `/epic-deliver` runner consults `halted` and refuses to advance
28
+ * `/deliver` runner consults `halted` and refuses to advance
29
29
  * to Phase E (retro) when set.
30
30
  *
31
31
  * Halting on critical findings is the in-process replacement for the
32
- * helper's "operator must remediate before /epic-deliver" gate.
32
+ * helper's "operator must remediate before /deliver" gate.
33
33
  */
34
34
 
35
35
  import { resolveConfig } from '../config-resolver.js';
@@ -116,7 +116,7 @@ function resolveTaskSizing(config) {
116
116
  *
117
117
  * Best-effort and total: a missing/unparseable checkpoint, an absent
118
118
  * `planningRisk` field, a read failure, or a malformed `overallLevel` all
119
- * degrade to `standard` (never throws). So an Epic that skipped `/epic-plan`
119
+ * degrade to `standard` (never throws). So an Epic that skipped `/plan`
120
120
  * (no checkpoint) still gets a passing `standard` review with no new failure
121
121
  * mode. The producer resolves depth from the judged risk alone (the diff is
122
122
  * not yet enumerated at this point); the full risk + diff-width combination is
@@ -302,90 +302,106 @@ function buildCodeReviewEndPayload({ epicId, result, durationMs }) {
302
302
  }
303
303
 
304
304
  /**
305
- * Resolve the scope envelope from the (legacy `epicId` + optional
306
- * `baseBranch`) shape OR the (new `scope`/`ticketId`/`headRef`/
307
- * `commentTargetId`) shape into a single normalized record. Extracted to
308
- * keep `runCodeReview` body below the CRAP-cyclomatic ceiling.
305
+ * Resolve the project base branch fallback used when a caller omits
306
+ * `baseRef`.
307
+ */
308
+ function resolveConfigBase(config) {
309
+ return (
310
+ config?.project?.baseBranch ?? config?.agentSettings?.baseBranch ?? 'main'
311
+ );
312
+ }
313
+
314
+ /** Positive-integer override, else the supplied default. */
315
+ function resolveCommentTargetId(commentTargetId, fallback) {
316
+ return Number.isInteger(commentTargetId) && commentTargetId > 0
317
+ ? commentTargetId
318
+ : fallback;
319
+ }
320
+
321
+ /**
322
+ * Resolve the Story-scope envelope from the parameterized
323
+ * `{ scope: 'story', ticketId, baseRef, headRef, commentTargetId }` shape.
309
324
  *
310
- * @param {{
311
- * epicId?: number,
312
- * scope?: 'epic'|'story',
313
- * ticketId?: number,
314
- * baseBranch?: string|null,
315
- * baseRef?: string|null,
316
- * headRef?: string|null,
317
- * commentTargetId?: number|null,
318
- * }} opts
319
- * @param {object} config
320
325
  * @returns {{
321
- * scope: 'epic'|'story',
326
+ * scope: 'story',
322
327
  * ticketId: number,
323
328
  * baseRef: string,
324
329
  * headRef: string,
325
330
  * commentTargetId: number,
326
- * epicIdForLedger: number|null,
331
+ * epicIdForLedger: null,
327
332
  * }}
328
333
  */
329
- function resolveScopeEnvelope(opts, config) {
330
- const explicitScope = opts.scope;
331
- const epicIdLegacy = opts.epicId;
332
- const configBase =
333
- config?.project?.baseBranch ?? config?.agentSettings?.baseBranch ?? 'main';
334
-
335
- if (explicitScope === 'story') {
336
- if (!Number.isInteger(opts.ticketId) || opts.ticketId <= 0) {
337
- throw new TypeError(
338
- 'runCodeReview: ticketId is required (positive integer) when scope="story".',
339
- );
340
- }
341
- if (typeof opts.headRef !== 'string' || opts.headRef.length === 0) {
342
- throw new TypeError(
343
- 'runCodeReview: headRef is required (non-empty string) when scope="story".',
344
- );
345
- }
346
- const baseRef = opts.baseRef ?? opts.baseBranch ?? configBase;
347
- const commentTargetId =
348
- Number.isInteger(opts.commentTargetId) && opts.commentTargetId > 0
349
- ? opts.commentTargetId
350
- : opts.ticketId;
351
- return {
352
- scope: 'story',
353
- ticketId: opts.ticketId,
354
- baseRef,
355
- headRef: opts.headRef,
356
- commentTargetId,
357
- epicIdForLedger: null,
358
- };
334
+ function resolveStoryScope(opts, config) {
335
+ if (!Number.isInteger(opts.ticketId) || opts.ticketId <= 0) {
336
+ throw new TypeError(
337
+ 'runCodeReview: ticketId is required (positive integer) when scope="story".',
338
+ );
339
+ }
340
+ if (typeof opts.headRef !== 'string' || opts.headRef.length === 0) {
341
+ throw new TypeError(
342
+ 'runCodeReview: headRef is required (non-empty string) when scope="story".',
343
+ );
359
344
  }
345
+ return {
346
+ scope: 'story',
347
+ ticketId: opts.ticketId,
348
+ baseRef: opts.baseRef ?? resolveConfigBase(config),
349
+ headRef: opts.headRef,
350
+ commentTargetId: resolveCommentTargetId(
351
+ opts.commentTargetId,
352
+ opts.ticketId,
353
+ ),
354
+ epicIdForLedger: null,
355
+ };
356
+ }
360
357
 
361
- // Epic scope (default + legacy `epicId` callers).
362
- const effectiveEpicId =
363
- Number.isInteger(opts.ticketId) && opts.ticketId > 0
364
- ? opts.ticketId
365
- : epicIdLegacy;
366
- if (!Number.isInteger(effectiveEpicId) || effectiveEpicId <= 0) {
358
+ /**
359
+ * Resolve the Epic-scope envelope from the parameterized
360
+ * `{ scope: 'epic', ticketId, baseRef, headRef, commentTargetId }` shape.
361
+ * `headRef` defaults to `epic/<ticketId>` and `baseRef` to the project
362
+ * base branch.
363
+ *
364
+ * @returns {{
365
+ * scope: 'epic',
366
+ * ticketId: number,
367
+ * baseRef: string,
368
+ * headRef: string,
369
+ * commentTargetId: number,
370
+ * epicIdForLedger: number,
371
+ * }}
372
+ */
373
+ function resolveEpicScope(opts, config) {
374
+ if (!Number.isInteger(opts.ticketId) || opts.ticketId <= 0) {
367
375
  throw new TypeError(
368
- 'runCodeReview: epicId is required (positive integer).',
376
+ 'runCodeReview: ticketId is required (positive integer) when scope="epic".',
369
377
  );
370
378
  }
371
- const baseRef = opts.baseRef ?? opts.baseBranch ?? configBase;
372
- const headRef = opts.headRef ?? `epic/${effectiveEpicId}`;
373
- const commentTargetId =
374
- Number.isInteger(opts.commentTargetId) && opts.commentTargetId > 0
375
- ? opts.commentTargetId
376
- : effectiveEpicId;
377
379
  return {
378
380
  scope: 'epic',
379
- ticketId: effectiveEpicId,
380
- baseRef,
381
- headRef,
382
- commentTargetId,
383
- epicIdForLedger: effectiveEpicId,
381
+ ticketId: opts.ticketId,
382
+ baseRef: opts.baseRef ?? resolveConfigBase(config),
383
+ headRef: opts.headRef ?? `epic/${opts.ticketId}`,
384
+ commentTargetId: resolveCommentTargetId(
385
+ opts.commentTargetId,
386
+ opts.ticketId,
387
+ ),
388
+ epicIdForLedger: opts.ticketId,
384
389
  };
385
390
  }
386
391
 
387
392
  /**
388
- * In-process wrapper that the `/epic-deliver` runner and the
393
+ * Dispatch the parameterized scope envelope
394
+ * (`{ scope, ticketId, baseRef, headRef, commentTargetId }`) to the
395
+ * matching pure resolver. `scope` defaults to `'epic'`.
396
+ */
397
+ function resolveScopeEnvelope(opts, config) {
398
+ return opts.scope === 'story'
399
+ ? resolveStoryScope(opts, config)
400
+ : resolveEpicScope(opts, config);
401
+ }
402
+
403
+ /**
404
+ * In-process wrapper that the `/deliver` runner and the
389
405
  * `/single-story-deliver` close path consume.
390
406
  *
391
407
  * Story #2252 — emits `code-review.start` immediately on entry and
@@ -407,26 +423,23 @@ function resolveScopeEnvelope(opts, config) {
407
423
  * `scope: 'epic'` because the `code-review.end` schema requires
408
424
  * `epicId` and the ledger only spans Epic lifecycles.
409
425
  *
410
- * Argument shapes:
411
- * - Legacy (Epic):
412
- * `{ epicId, provider, bus, [baseBranch] }`
413
- * - Parameterized (Epic or Story):
414
- * `{ scope, ticketId, baseRef, headRef, [commentTargetId],
415
- * provider, bus }`
416
- * For `scope === 'story'`, `commentTargetId` overrides the post
417
- * target (e.g. PR number) while `ticketId` continues to label the
418
- * rendered header ("Story #N").
426
+ * Argument shape (parameterized, Epic or Story):
427
+ * `{ scope, ticketId, baseRef, headRef, [commentTargetId],
428
+ * provider, bus }`
429
+ * `scope` defaults to `'epic'`; `baseRef` defaults to the project base
430
+ * branch and (Epic scope only) `headRef` defaults to `epic/<ticketId>`.
431
+ * For `scope === 'story'`, `commentTargetId` overrides the post
432
+ * target (e.g. PR number) while `ticketId` continues to label the
433
+ * rendered header ("Story #N").
419
434
  *
420
435
  * @param {{
421
- * epicId?: number,
422
436
  * scope?: 'epic'|'story',
423
- * ticketId?: number,
437
+ * ticketId: number,
424
438
  * baseRef?: string|null,
425
439
  * headRef?: string|null,
426
440
  * commentTargetId?: number|null,
427
441
  * provider: object,
428
442
  * logger?: { info?: Function, warn?: Function, error?: Function, fatal?: Function, createProgress?: Function },
429
- * baseBranch?: string|null,
430
443
  * planningRisk?: { overallLevel?: ('low'|'medium'|'high'), axes?: Array<{ axis?: string, level?: string }> }|null,
431
444
  * changedFileCount?: number|null,
432
445
  * storyId?: number|null,