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
@@ -2,7 +2,6 @@ import { LIMITS_DEFAULTS } from '../config/limits.js';
2
2
  import {
3
3
  DEFAULT_TASK_SIZING,
4
4
  DELIVERABLE_GRANULARITY_GUIDANCE,
5
- SOFT_STORIES_PER_FEATURE,
6
5
  } from '../orchestration/ticket-validator-sizing.js';
7
6
 
8
7
  /**
@@ -13,22 +12,23 @@ import {
13
12
  * resolved value; importing it here means a fallback path (no caller-supplied
14
13
  * value) still tracks the framework default in `lib/config/limits.js`.
15
14
  *
16
- * 3-tier is the only published hierarchy after Task #3154 deleted the
17
- * `planning.hierarchy` flag: the prompt omits the Task layer entirely and
18
- * asks the planner to inline acceptance/verify on the Story body.
15
+ * 2-tier is the only published hierarchy after Story #4041 removed the
16
+ * Feature tier: the prompt emits Stories only (direct Epic children) and
17
+ * asks the planner to carry acceptance/verify as top-level ticket arrays.
19
18
  */
20
19
  export function renderDecomposerSystemPrompt({
21
20
  maxTickets = LIMITS_DEFAULTS.maxTickets,
22
21
  } = {}) {
23
- return render3TierPrompt({ maxTickets });
22
+ return render2TierPrompt({ maxTickets });
24
23
  }
25
24
 
26
25
  /**
27
- * 3-tier prompt (Epic #3078). Decomposes to Feature → Story only — no Task
28
- * layer. Acceptance criteria and verification commands live inline on the
29
- * Story body so the executing agent has everything it needs in one ticket.
26
+ * 2-tier prompt (Story #4041). Decomposes to Stories only — no Feature and
27
+ * no Task layer. Acceptance criteria and verification commands live inline
28
+ * on the Story body so the executing agent has everything it needs in one
29
+ * ticket. Thematic grouping lives as prose in the Epic body / Tech Spec.
30
30
  */
31
- function render3TierPrompt({ maxTickets }) {
31
+ function render2TierPrompt({ maxTickets }) {
32
32
  // Sizing thresholds are sourced from the single DEFAULT_TASK_SIZING constant
33
33
  // (ticket-validator-sizing.js) so the prompt and the validator cannot drift.
34
34
  const { softFiles, hardFiles, maxAcceptance, softAcceptanceCount } =
@@ -40,16 +40,16 @@ function render3TierPrompt({ maxTickets }) {
40
40
  const { definition: granularityDefinition, singleConsumerRule } =
41
41
  DELIVERABLE_GRANULARITY_GUIDANCE;
42
42
  return `You are an expert Senior Project Manager and Orchestrator.
43
- Your job is to take a Product Requirements Document (PRD) and a Technical Specification and decompose them into a highly-granular 2-level ticket hierarchy for an AI Agent to execute.
43
+ Your job is to take a Product Requirements Document (PRD) and a Technical Specification and decompose them into a flat list of Story tickets for an AI Agent to execute.
44
44
 
45
45
  ### HIERARCHY RULES:
46
- 1. **Features**: Large functional milestones (e.g., "Authentication Provider Integration").
47
- 2. **Stories**: Specific user-facing or architectural user stories (e.g., "Implement JWT Token Exchange").
48
- - MUST be nested under a Feature.
49
- - **Story-Level Execution**: Each Story will be executed end-to-end on a single branch by a single agent. There is NO Task layer in this hierarchy — acceptance criteria and verification commands live inline on the Story body (see STORY BODY SCHEMA below).
46
+ 1. **Stories**: Specific user-facing or architectural user stories (e.g., "Implement JWT Token Exchange").
47
+ - Every Story attaches directly to the Epic there is NO Feature tier and NO Task layer in this hierarchy.
48
+ - **Story-Level Execution**: Each Story will be executed end-to-end on a single branch by a single agent. Acceptance criteria and verification commands live as top-level \`acceptance[]\` / \`verify[]\` arrays on the Story ticket (see STORY BODY SCHEMA below).
49
+ - Thematic grouping is prose in the Epic body / Tech Spec, never a ticket.
50
50
 
51
51
  ### LABEL CONVENTIONS:
52
- - Every ticket must have a \`type::[feature|story]\` label. The \`type::task\` label is FORBIDDEN under this hierarchy.
52
+ - Every ticket must have the \`type::story\` label. No other type label is allowed — the retired Feature and Task tiers have no labels under this hierarchy.
53
53
  - Every ticket must have a \`persona::[engineer|architect|qa-engineer|engineer-web|etc]\` label indicating WHO should execute it.
54
54
 
55
55
  ### OUTPUT FORMAT:
@@ -58,43 +58,52 @@ You MUST respond ONLY with a valid JSON array of objects. No prose, no markdown
58
58
  ### JSON SCHEMA:
59
59
  [
60
60
  {
61
- "slug": "unique_string_id",
62
- "type": "feature" | "story",
61
+ "slug": "hyphen-case-id",
62
+ "type": "story",
63
63
  "title": "Short descriptive title",
64
- "body": <string for features; see STORY BODY SCHEMA below for stories>,
65
- "labels": ["type::...", "persona::..."],
66
- "parent_slug": "slug_of_parent_ticket" (leave empty for features to nest under epic),
67
- "depends_on": ["slug_of_blocking_dependency"] (optional array of slugs that block execution)
64
+ "body": <string see STORY BODY SCHEMA below>,
65
+ "acceptance": ["<testable, observable criterion>", ...],
66
+ "verify": ["<exact command or test path> (<tier>)", ...],
67
+ "labels": ["type::story", "persona::..."],
68
+ "depends_on": ["slug-of-blocking-dependency"] (optional array of Story slugs that block execution)
68
69
  }
69
70
  ]
70
71
 
71
- ### FEATURE BODY:
72
- For Features, \`body\` is a brief string under 2 sentences. Features are navigational — the work happens at the Story level — so dense bodies waste output budget.
72
+ **Slug format**: \`^[a-z0-9][a-z0-9-]*\$\` — hyphen-case only. Underscores are rejected by the validator.
73
73
 
74
- #### FEATURE GROUPING CAPABILITY/STAKEHOLDER, NOT EXECUTION SHAPE:
74
+ ### STORY BODY SCHEMA (REQUIRED FOR EVERY STORY):
75
+ \`body\` MUST be a **string** — the serialized markdown produced by \`serialize()\` from \`lib/story-body/story-body.js\`. Do NOT emit \`body\` as a JSON object: an object body throws \`StoryBodyParseError\` in the reconciler (Story #3302) and is discarded by the GitHub provider, producing an empty issue body. Stories are consumed by non-interactive sub-agents that must self-verify from the Story ticket alone — so the ticket must carry everything an agent needs to execute and self-verify.
75
76
 
76
- - **Do not group Stories by technical execution shape (cron jobs, middleware, scripts). Group by the capability or stakeholder they serve.** Two Stories that happen to share a delivery mechanismboth are cron sweeps, both are Express middleware, both are CLI scripts — do NOT belong in the same Feature just because of that shared shape. The Feature axis is the user-facing capability or the stakeholder served, never the implementation vehicle.
77
- - **Operational/compliance work gets its own Feature.** Retention purges, audit-log sweeps, scheduled cleanups, observability emitters, and other operational/compliance chores serve operators and compliance, not end users. Place them in a dedicated \`compliance-and-observability\` or \`data-retention\` Feature — do NOT fold them into a user-facing Feature (e.g. notification flows) merely because they share a cron/scheduled execution shape with user-facing Stories.
78
- - **Smell test:** if the only thing two Stories have in common is *how* they run (a scheduled job, a request interceptor, a build step) rather than *whom* they serve or *what* capability they deliver, they are mis-grouped. Re-home the operational/compliance Story into its own capability Feature.
77
+ The \`acceptance[]\` and \`verify[]\` arrays live at the **top level** of the Story ticket object (not nested inside \`body\`). The validator reads \`story.acceptance\` and \`story.verify\` directlynesting them inside the body makes them invisible to the validator and the decompose is rejected.
79
78
 
80
- ### STORY BODY SCHEMA (REQUIRED FOR EVERY STORY):
81
- For stories, \`body\` is a STRUCTURED OBJECT, not a string. Stories are consumed by non-interactive sub-agents that must self-verify from the Story body alone — there is no Task layer below — so the Story itself must carry everything an agent needs to execute and self-verify.
82
-
83
- "body": {
84
- "goal": "<one sentence — tie this story to the parent Feature slug, naming the slug>",
85
- "changes": ["<file path>: <verb> <object>", ...],
86
- "acceptance": ["<testable, observable criterion>", ...],
87
- "verify": ["<exact command or test path> (<tier>)", ...],
88
- "estimated_test_files": <integer — number of test files this Story is expected to create or modify; omit when not estimable>
89
- }
79
+ The serialized \`body\` string renders these markdown sections (in order):
80
+
81
+ ## Goal
82
+ <one sentence — why this Story exists within the Epic>
83
+
84
+ ## Changes
85
+ - {"path": "<file path>", "assumption": "creates" | "refactors-existing" | "deletes"}
86
+ - ...
87
+
88
+ ## Acceptance
89
+ - [ ] <testable, observable criterion>
90
+ - ...
91
+
92
+ ## Verify
93
+ - <exact command or test path> (<tier>)
94
+ - ...
95
+
96
+ ## References
97
+ - {"path": "<read-only dependency path>", "assumption": "exists"}
98
+ - ...
90
99
 
91
100
  #### STORY BODY RULES:
92
101
 
93
- - **goal**: One sentence stating WHY this story exists. MUST name the parent Feature slug.
94
- - **changes**: Each bullet MUST be \`<path-or-glob>: <concrete verb> <object>\`. Acceptable path shapes include explicit files (\`src/components/Foo.tsx\`), glob patterns (\`tests/e2e/*.spec.ts\`, \`**/*.astro\`), and module identifiers that resolve to files. Vague verbs ("clean up", "refactor", "improve", "polish", "tighten") are FORBIDDEN unless paired with a named target "refactor src/x.ts: extract handleSubmit" is fine, "refactor the form" is not.
95
- - **acceptance**: Items MUST be observable from outside the agent. Acceptable shapes: a specific command exits 0, a file exists at a given path, a snapshot test matches, a \`data-testid\` resolves under a given selector, a row count in a fixture matches. UNACCEPTABLE: "verify by reading the diff", "looks good", "matches the spec" — push these down into a \`verify\` command instead.
96
- - **verify**: Each entry MUST name a testing tier in parentheses, drawn from \`unit\` / \`contract\` / \`e2e\` / \`validate\`. Example: \`npm run test -- src/x.test.ts (unit)\`, \`npm run validate (validate)\`. Stories with zero verify entries SHOULD fail validation; if a story is genuinely unverifiable in isolation (e.g., a copy edit auditor will eyeball), the literal entry \`manual:<reason>\` is allowed so the absence is intentional, not lazy. Manual entries without a reason are rejected.
97
- - **estimated_test_files** (optional): Integer estimate of how many test files this Story creates or modifies. Omit when the number is not estimable. Informational only — it does not gate the decompose.
102
+ - **goal** (in body string): One sentence stating WHY this story exists within the Epic.
103
+ - **changes** (in body string): Each entry is an object \`{ path, assumption }\` where \`assumption\` is one of \`creates | refactors-existing | deletes\`. Acceptable path shapes include explicit files (\`src/components/Foo.tsx\`), glob patterns (\`tests/e2e/*.spec.ts\`, \`**/*.astro\`), and module identifiers that resolve to files. Use \`refactors-existing\` for in-place edits to a file already on \`main\`; \`creates\` for net-new files; \`deletes\` for removals.
104
+ - **acceptance** (top-level array on the ticket object): Items MUST be observable from outside the agent. Acceptable shapes: a specific command exits 0, a file exists at a given path, a snapshot test matches, a \`data-testid\` resolves under a given selector, a row count in a fixture matches. UNACCEPTABLE: "verify by reading the diff", "looks good", "matches the spec" — push these down into a \`verify\` command instead.
105
+ - **verify** (top-level array on the ticket object): Each entry MUST name a testing tier in parentheses, drawn from \`unit\` / \`contract\` / \`e2e\` / \`validate\`. Example: \`npm run test -- src/x.test.ts (unit)\`, \`npm run validate (validate)\`. Stories with zero verify entries SHOULD fail validation; if a story is genuinely unverifiable in isolation (e.g., a copy edit auditor will eyeball), the literal entry \`manual:<reason>\` is allowed so the absence is intentional, not lazy. Manual entries without a reason are rejected.
106
+ - **estimated_test_files** (optional, encoded in the \`<!-- meta: {...} -->\` comment appended to the serialized body string — NOT a top-level ticket field): Integer estimate of how many test files this Story creates or modifies. Omit when the number is not estimable. Informational only — it does not gate the decompose.
98
107
 
99
108
  #### STORY SIZING — COHESION FIRST (the numeric ceiling is only a backstop):
100
109
 
@@ -104,10 +113,8 @@ The primary question is **cohesion, not count**: *is this one coherent change wi
104
113
 
105
114
  - **One Story = one coherent change with one reason to exist.** If you cannot state that reason in a sentence, the Story is probably two Stories — or two Stories that should be one.
106
115
  - ${singleConsumerRule}
107
- - **Split independent, parallelizable work** into sibling Stories under the same Feature — but only when the pieces genuinely have separate reasons to exist.
116
+ - **Split independent, parallelizable work** into sibling Stories — but only when the pieces genuinely have separate reasons to exist.
108
117
  - **Declare \`wide\` with a one-line reason when a change is legitimately broad** (a cohesive cutover that spans many files for one reason). Declaring \`wide\` lifts the hard file-width ceiling — see below.
109
- - **Every Feature MUST decompose into at least TWO Stories.** A Feature with a single Story is the work of a Story, not a Feature — collapse it (drop the Feature wrapper and attach its lone Story to a sibling Feature, or merge the Feature into another). The validator HARD-rejects a Feature with fewer than two Stories.
110
- - **Features typically decompose into ≤${SOFT_STORIES_PER_FEATURE} Stories; otherwise split into a sibling Feature.** A Feature stretching past ${SOFT_STORIES_PER_FEATURE} Stories is a sign the Feature scope is two features.
111
118
 
112
119
  **Numeric backstop (validator-enforced).** These thresholds are sourced from the single \`DEFAULT_TASK_SIZING\` constant in \`ticket-validator-sizing.js\` — there is no second copy to drift:
113
120
 
@@ -117,7 +124,7 @@ The primary question is **cohesion, not count**: *is this one coherent change wi
117
124
 
118
125
  #### \`wide\` DECLARATION (optional — for legitimately broad changes):
119
126
 
120
- A Story whose footprint is legitimately broad declares \`body.wide\` carrying a one-line human-readable reason:
127
+ A Story whose footprint is legitimately broad declares \`wide\` carrying a one-line human-readable reason. Encode it in the \`<!-- meta: {"wide": {"reason": "..."}} -->\` comment that \`serialize()\` appends to the body string — it is NOT a top-level ticket field:
121
128
 
122
129
  \`\`\`json
123
130
  "wide": { "reason": "hard contract cutover: migrate every <X> call site in one PR" }
@@ -139,11 +146,11 @@ Declaring \`wide\` with a non-empty reason **lifts the \`hardFiles\` rejection**
139
146
  - Stories that touch user-visible copy, brand assets, or visual style MUST cite the relevant section of \`docs/style-guide.md\` in \`acceptance\` (e.g. \`"acceptance": ["Hero copy matches docs/style-guide.md §3 (voice & tone)"]\`). If \`docs/style-guide.md\` does not exist or has no relevant section, state that explicitly: \`"acceptance": ["docs/style-guide.md absent — copy reviewed against the inline brand brief in PRD §2"]\`. Silence on style sourcing is a smell.
140
147
 
141
148
  ### WAVE-0 BDD SCAFFOLD STORY (features-first; emit when the Acceptance Spec has \`new\`-disposition rows):
142
- The Acceptance Spec's AC table (columns \`AC ID | Outcome | Feature File | Scenario | Disposition\`) tags each row's \`Disposition\` with one of \`new | updated | unchanged\`. A \`new\` row names a \`.feature\` file + scenario that does NOT yet exist on \`main\`. The framework is features-first: implementing Stories reference those \`.feature\` paths in their \`verify[]\` lines, so the files MUST already exist when those Stories run — otherwise verification fails mid-delivery on a missing file.
149
+ The Acceptance Spec's AC table (columns \`AC ID | Outcome | Feature File | Scenario | Disposition\`) tags each row's \`Disposition\` with one of \`new | updated | unchanged\`. A \`new\` row names a \`.feature\` file + scenario that does NOT yet exist on \`main\`. The framework is features-first: implementing Stories reference those \`.feature\` paths in their \`verify[]\` lines, so the files MUST already exist when those Stories run — otherwise verification fails mid-delivery on a missing file. (These Gherkin \`.feature\` files are BDD artifacts, unrelated to any ticket tier.)
143
150
 
144
151
  When the Acceptance Spec contains **one or more \`Disposition: new\` rows**, you MUST emit **exactly one** dedicated wave-0 scaffold Story whose sole job is to create the \`.feature\` files with \`@skip\`-tagged scenarios BEFORE any implementation Story runs:
145
152
 
146
- - **goal**: contains the literal token \`bdd-scaffold\` (e.g. "bdd-scaffold: create the @skip-tagged feature files the implementation Stories verify against, for parent Feature <slug>").
153
+ - **goal**: contains the literal token \`bdd-scaffold\` (e.g. "bdd-scaffold: create the @skip-tagged feature files the implementation Stories verify against").
147
154
  - **depends_on**: EMPTY (\`[]\`) — it runs first, in wave 0.
148
155
  - **changes**: one entry per distinct \`.feature\` file named in a \`new\` row, each \`{ "path": "<feature file path>", "assumption": "creates" }\`.
149
156
  - **acceptance**: MUST assert (a) every new \`.feature\` file exists, and (b) every new scenario within them carries an \`@skip\` tag. Keep these observable (a grep/validate command exits 0, a file exists at a path).
@@ -153,16 +160,16 @@ When the Acceptance Spec contains **one or more \`Disposition: new\` rows**, you
153
160
  When the Acceptance Spec contains **zero \`new\`-disposition rows** (every row is \`updated\` or \`unchanged\`), do NOT emit a scaffold Story — there is nothing to create.
154
161
 
155
162
  ### SCOPE-OVERLAP FLAGGING (docs/runbook downstream of config work):
156
- When a "docs update" / "runbook" / "README" Story appears downstream of an earlier Story in the same Epic whose AC already covers updating the same document (e.g. a "config + runbook" Story followed by a "docs" Story touching the same runbook), the downstream Story's deliverable may be fully absorbed by the earlier Story. Flag the risk directly in the Story \`body.acceptance\` by appending an item of the form:
163
+ When a "docs update" / "runbook" / "README" Story appears downstream of an earlier Story in the same Epic whose AC already covers updating the same document (e.g. a "config + runbook" Story followed by a "docs" Story touching the same runbook), the downstream Story's deliverable may be fully absorbed by the earlier Story. Flag the risk directly in the Story's top-level \`acceptance\` array by appending an item of the form:
157
164
  "Scope verification note: this story's deliverable may already be satisfied by Story #<slug-or-id>'s AC — before implementing, \`git diff main -- <path>\` against the upstream Story branch and confirm whether a substantive edit is still required, or whether only a cross-reference remains."
158
165
  This prevents the executing agent from redoing work the upstream Story already merged.
159
166
 
160
- CRITICAL: Dependencies should follow execution blockers. For hierarchical grouping, strictly use 'parent_slug' (Story parent MUST be a Feature). Features should have no 'parent_slug' (they attach to Epic).
161
- IMPORTANT DEPENDENCY RULE: Cross-Feature Story dependencies are allowed via \`depends_on\` at the Story level (one Story depends_on another Story's slug). Use this to express execution ordering across the plan.
167
+ CRITICAL: Dependencies should follow execution blockers. Stories attach directly to the Epic never emit a 'parent_slug' field.
168
+ IMPORTANT DEPENDENCY RULE: Story-to-Story dependencies are expressed via \`depends_on\` (one Story depends_on another Story's slug). Use this to express execution ordering across the plan.
162
169
 
163
170
  ### REVIEWABILITY BUDGET (Story #2798):
164
171
  \`maxTickets = ${maxTickets}\` is a **reviewability budget**, not a hard authoring cap. It marks the count of tickets a human operator can comfortably review in one planning pass; emitting more than this overflows the operator's review window. Default behaviour:
165
172
  - **Stay at or under the budget when possible.** Merge narrow, single-module stories into larger, cohesive capability stories before splitting; small Stories should merge back into siblings rather than spawn their own container.
166
- - **Do NOT truncate or over-compress to fit.** If the plan genuinely needs more tickets than the budget, emit the full plan anyway and add a compact \`over_budget_rationale\` string at the top of the FIRST Feature's \`body\` explaining (a) why the plan exceeds the budget and (b) what was already merged to keep the count down. The operator will then either accept the plan by re-running the decompose with the explicit \`--allow-over-budget\` override flag, or push back and ask for a re-scope.
173
+ - **Do NOT truncate or over-compress to fit.** If the plan genuinely needs more tickets than the budget, emit the full plan anyway and add a compact \`over_budget_rationale\` note inside the FIRST Story's \`## Goal\` section explaining (a) why the plan exceeds the budget and (b) what was already merged to keep the count down. The operator will then either accept the plan by re-running the decompose with the explicit \`--allow-over-budget\` override flag, or push back and ask for a re-scope.
167
174
  - **Never stop mid-array.** Always emit complete JSON — partial arrays are rejected by the validator.`;
168
175
  }
@@ -0,0 +1,93 @@
1
+ import { createRequire } from 'node:module';
2
+ import path from 'node:path';
3
+ import { Logger } from './Logger.js';
4
+
5
+ const require = createRequire(import.meta.url);
6
+
7
+ const TS_EXTS = new Set(['.ts', '.tsx', '.mts', '.cts']);
8
+
9
+ let _ts = null;
10
+ let _tsLoadFailed = false;
11
+
12
+ function loadTypeScript() {
13
+ if (_ts) return _ts;
14
+ if (_tsLoadFailed) return null;
15
+ try {
16
+ _ts = require('typescript');
17
+ return _ts;
18
+ } catch {
19
+ _tsLoadFailed = true;
20
+ return null;
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Resolve the `typescript` package version, used to stamp baselines so
26
+ * consumers can detect transpiler drift. Returns `'0.0.0'` when the
27
+ * dependency is unresolvable — callers treat that sentinel as "unknown
28
+ * environment" and may refuse to persist a baseline that includes TS rows.
29
+ *
30
+ * @returns {string}
31
+ */
32
+ export function resolveTsTranspilerVersion() {
33
+ const ts = loadTypeScript();
34
+ if (ts && typeof ts.version === 'string') return ts.version;
35
+ return '0.0.0';
36
+ }
37
+
38
+ function isTypeScriptPath(filePath) {
39
+ return TS_EXTS.has(path.extname(String(filePath)).toLowerCase());
40
+ }
41
+
42
+ /**
43
+ * Pre-transpile TypeScript or TSX sources to JavaScript that the
44
+ * Esprima-based escomplex kernel can parse. Returns the input unchanged
45
+ * for `.js` / `.mjs` / `.cjs` paths.
46
+ *
47
+ * Type annotations introduce no control flow, so the transpiled output
48
+ * scores identically to the original TS for cyclomatic complexity,
49
+ * Halstead volume, and the maintainability index. `.tsx` uses the
50
+ * `react-jsx` emit so JSX expressions become function calls escomplex
51
+ * can read; `.preserve` would leave JSX in the output and Esprima would
52
+ * choke on it.
53
+ *
54
+ * On transpile failure the helper returns `null` — callers treat that
55
+ * as "skip this file" rather than crashing the scan.
56
+ *
57
+ * @param {string} filePath
58
+ * @param {string} source
59
+ * @returns {string|null}
60
+ */
61
+ export function transpileIfNeeded(filePath, source) {
62
+ if (!isTypeScriptPath(filePath)) return source;
63
+ const ts = loadTypeScript();
64
+ if (!ts) {
65
+ Logger.warn(
66
+ `[Maintainability] ⚠ typescript package not resolvable; cannot score ${filePath}. ` +
67
+ "Install with 'npm install --save-dev typescript' (peer dep, >=5.0.0).",
68
+ );
69
+ return null;
70
+ }
71
+ try {
72
+ const result = ts.transpileModule(source, {
73
+ compilerOptions: {
74
+ target: ts.ScriptTarget.ESNext,
75
+ module: ts.ModuleKind.ESNext,
76
+ isolatedModules: true,
77
+ noEmitHelpers: true,
78
+ importHelpers: false,
79
+ removeComments: false,
80
+ jsx: ts.JsxEmit.ReactJSX,
81
+ sourceMap: false,
82
+ },
83
+ fileName: path.basename(filePath),
84
+ reportDiagnostics: false,
85
+ });
86
+ return result.outputText;
87
+ } catch (err) {
88
+ Logger.warn(
89
+ `[Maintainability] ⚠ TS transpile failed for ${filePath}: ${err?.message ?? err}; skipping.`,
90
+ );
91
+ return null;
92
+ }
93
+ }
@@ -36,22 +36,11 @@ import { WaveRunnerError } from './wave-runner-error.js';
36
36
  * currentWave: number
37
37
  * totalWaves: number
38
38
  *
39
- * When `spec` is omitted, the planner falls back to the checkpoint's
40
- * `state.plan` (the GH-derived dependency-DAG grouping originally seeded
41
- * by /epic-plan) — behaviour is byte-identical to the pre-spec path.
42
- * When `spec` is supplied, wave grouping is driven by `spec.stories[].wave`
43
- * (the declarative SSOT from `.agents/epics/<epic-id>.yaml`) and slugs are
44
- * resolved to GH issue numbers via the sibling `<epic-id>.state.json`
45
- * mapping; the checkpoint is still consulted for `currentWave`,
46
- * `totalWaves`, and `waves[]` history but its `plan[]` is overridden.
39
+ * Wave grouping comes from the checkpoint's `state.plan` (the GH-derived
40
+ * dependency-DAG grouping originally seeded by /plan).
47
41
  *
48
42
  * @typedef {object} WaveTickArgs
49
43
  * @property {number | { id: number }} epic
50
- * @property {object} [spec] Parsed epic-spec (see lib/spec/loader.js). When
51
- * provided, wave grouping comes from `spec.stories[].wave`.
52
- * @property {object} [state] Parsed epic-state (see lib/spec/loader.js).
53
- * When `spec` is provided, this must be supplied so slugs can resolve to
54
- * issue numbers via `state.mapping[slug].issueNumber`.
55
44
  * @property {{
56
45
  * provider?: object,
57
46
  * epicRunStateStore?: { read: () => Promise<object|null> },
@@ -74,8 +63,6 @@ export async function tick(args = {}) {
74
63
  } = args.collaborators ?? {};
75
64
  const ctx = args.ctx ?? {};
76
65
  const provider = collabProvider ?? ctx.provider;
77
- const spec = args.spec ?? null;
78
- const specState = args.state ?? null;
79
66
  if (!provider) {
80
67
  throw new WaveRunnerError('invalid-input', 'provider is required');
81
68
  }
@@ -104,7 +91,8 @@ export async function tick(args = {}) {
104
91
  }
105
92
 
106
93
  const currentWave = positiveIntOrZero(state.currentWave);
107
- const { plan, totalWaves } = resolvePlan(state, spec, specState);
94
+ const plan = Array.isArray(state.plan) ? state.plan : [];
95
+ const totalWaves = positiveIntOrZero(state.totalWaves);
108
96
  const history = Array.isArray(state.waves) ? state.waves : [];
109
97
 
110
98
  if (totalWaves === 0 || currentWave >= totalWaves) {
@@ -460,143 +448,6 @@ function storyIdOf(s) {
460
448
  return s.id ?? s.storyId ?? s.number;
461
449
  }
462
450
 
463
- /**
464
- * Walk `spec.features[].stories[]` and bucket entries by their `wave`
465
- * value, mapping slugs → GH issue numbers via the sibling state file.
466
- * Returns `Story[][]` indexed by wave number; missing waves between 0 and
467
- * the highest declared wave are emitted as empty arrays so wave N is
468
- * always reachable as `plan[N]`.
469
- *
470
- * Each emitted entry is shaped to match the checkpoint plan's
471
- * `{ id, title }` contract that the rest of `tick()` already consumes:
472
- *
473
- * - `id` is the GH issue number resolved from `state.mapping[slug].issueNumber`
474
- * so the same provider.getTicket(id) path used by the spec-less plan
475
- * keeps working unchanged.
476
- * - `title` is carried through from `story.title` so the wave-start
477
- * signal can include the Story's human-readable name without an
478
- * extra provider round-trip.
479
- * - `slug` is preserved on the entry so observability + future
480
- * re-resolution paths can re-key against the spec.
481
- *
482
- * When a Story slug has no resolved `issueNumber` in `state.mapping`
483
- * (a fresh spec entry the reconciler has not materialised yet), the entry
484
- * is skipped — un-materialised Stories cannot be dispatched anyway, and
485
- * including them with a `null` id would surface as a `story-fetch`
486
- * failure inside `tick()`. The reconciler will close the loop on the
487
- * next apply; until then, an empty wave is a faithful reflection of
488
- * GitHub state.
489
- *
490
- * Pure function — does not read disk, does not call GH. Callers are
491
- * expected to compose it with `loadSpec` + `loadState` from
492
- * `lib/spec/loader.js`.
493
- *
494
- * @param {object} spec Parsed epic-spec (see lib/spec/loader.js).
495
- * @param {{mapping?: Record<string, {issueNumber?: number}>}|null} [state]
496
- * Parsed epic-state. May be omitted; if missing, no entries can be
497
- * resolved and `groupByWave` returns `[]`.
498
- * @returns {Array<Array<{id: number, title?: string, slug: string}>>}
499
- */
500
- export function groupByWave(spec, state = null) {
501
- const mapping =
502
- state && typeof state.mapping === 'object' && state.mapping !== null
503
- ? state.mapping
504
- : {};
505
- const entries = extractValidStoryEntries(spec, mapping);
506
- if (entries.length === 0) return [];
507
- const byWave = new Map();
508
- let maxWave = -1;
509
- for (const { wave, entry } of entries) {
510
- if (!byWave.has(wave)) byWave.set(wave, []);
511
- byWave.get(wave).push(entry);
512
- if (wave > maxWave) maxWave = wave;
513
- }
514
- if (maxWave < 0) return [];
515
- const out = [];
516
- for (let i = 0; i <= maxWave; i += 1) {
517
- out.push(byWave.get(i) ?? []);
518
- }
519
- return out;
520
- }
521
-
522
- /**
523
- * Walk every feature/story pair in `spec` and emit only the entries that
524
- * survive the spec-validity cascade: the story must be a non-null object,
525
- * declare a non-negative integer `wave`, declare a string `slug`, and
526
- * resolve to a numeric `issueNumber` in `mapping`. Each surviving entry
527
- * is returned as `{ wave, entry }` where `entry` carries the same shape
528
- * (`{ id, title, slug }`) that `groupByWave` previously pushed into its
529
- * per-wave bucket.
530
- *
531
- * Extracted from `groupByWave` so the bucketing transform stays
532
- * straight-line; this predicate owns the entire defensive guard cascade
533
- * and is the right place to add new validation rules going forward.
534
- *
535
- * @param {object|null|undefined} spec Parsed epic-spec.
536
- * @param {Record<string, {issueNumber?: number}>} mapping
537
- * Slug → issue-number lookup from the sibling state file.
538
- * @returns {Array<{wave: number, entry: {id: number, title?: string, slug: string}}>}
539
- */
540
- export function extractValidStoryEntries(spec, mapping) {
541
- const out = [];
542
- const features = Array.isArray(spec?.features) ? spec.features : [];
543
- for (const feature of features) {
544
- const stories = Array.isArray(feature?.stories) ? feature.stories : [];
545
- for (const story of stories) {
546
- const resolved = resolveStoryEntry(story, mapping);
547
- if (resolved) out.push(resolved);
548
- }
549
- }
550
- return out;
551
- }
552
-
553
- /**
554
- * Validate a single `story` against the spec-validity cascade and return
555
- * `{ wave, entry }` when every guard passes, or `null` when any guard
556
- * trips. Splitting the per-story cascade out keeps both
557
- * `extractValidStoryEntries` (which owns iteration) and `resolveStoryEntry`
558
- * (which owns validation) below CRAP 5 even when none of the branches are
559
- * exercised at runtime — the predicate's cyclomatic footprint is small
560
- * enough that uncovered branches do not blow the baseline budget.
561
- *
562
- * @param {*} story Candidate story from `spec.features[].stories[]`.
563
- * @param {Record<string, {issueNumber?: number}>} mapping Slug → issue lookup.
564
- * @returns {{wave: number, entry: {id: number, title?: string, slug: string}} | null}
565
- */
566
- function resolveStoryEntry(story, mapping) {
567
- if (!story || typeof story !== 'object') return null;
568
- if (!Number.isInteger(story.wave) || story.wave < 0) return null;
569
- if (typeof story.slug !== 'string' || !story.slug) return null;
570
- const mapped = mapping[story.slug];
571
- if (!mapped || typeof mapped.issueNumber !== 'number') return null;
572
- return {
573
- wave: story.wave,
574
- entry: { id: mapped.issueNumber, title: story.title, slug: story.slug },
575
- };
576
- }
577
-
578
- /**
579
- * Resolve which plan + totalWaves drive this tick. When `spec` is
580
- * supplied, the spec-derived grouping wins (and totalWaves comes from
581
- * the spec since the checkpoint may lag); otherwise the checkpoint's
582
- * plan is used unchanged. Extracted so `tick()`'s cyclomatic complexity
583
- * stays inside its baseline budget — the route choice is now a single
584
- * call, not three ternaries inline.
585
- *
586
- * @param {object} state Checkpoint state (already validated as object).
587
- * @param {object|null} spec Parsed epic-spec or `null` when omitted.
588
- * @param {object|null} specState Parsed epic-state for slug mapping.
589
- * @returns {{plan: Array<Array<object>>, totalWaves: number}}
590
- */
591
- function resolvePlan(state, spec, specState) {
592
- if (spec) {
593
- const specPlan = groupByWave(spec, specState);
594
- return { plan: specPlan, totalWaves: specPlan.length };
595
- }
596
- const plan = Array.isArray(state.plan) ? state.plan : [];
597
- return { plan, totalWaves: positiveIntOrZero(state.totalWaves) };
598
- }
599
-
600
451
  /**
601
452
  * A Story is "done" when it carries `agent::done` OR its GitHub issue is
602
453
  * `state === 'closed'`. The closed-state arm (Story #3907) is what aligns the
@@ -29,7 +29,7 @@
29
29
  import fs from 'node:fs';
30
30
  import { parentPort } from 'node:worker_threads';
31
31
  import { calculateCrapForSource } from '../crap-engine.js';
32
- import { transpileIfNeeded } from '../maintainability-utils.js';
32
+ import { transpileIfNeeded } from '../transpile.js';
33
33
 
34
34
  /**
35
35
  * Pure handler for a single inbound worker message. Exported so unit
@@ -39,7 +39,7 @@ import {
39
39
  calculateReport,
40
40
  calculateReportForFile,
41
41
  } from '../maintainability-engine.js';
42
- import { transpileIfNeeded } from '../maintainability-utils.js';
42
+ import { transpileIfNeeded } from '../transpile.js';
43
43
 
44
44
  /**
45
45
  * Score a pre-sourced content string (Story #3696). Mirrors
@@ -12,6 +12,7 @@ import fs from 'node:fs';
12
12
  import {
13
13
  applyNodeModulesStrategy,
14
14
  installDependencies,
15
+ probeReusedInstall,
15
16
  } from '../node-modules-strategy.js';
16
17
  import {
17
18
  findByPath,
@@ -41,6 +42,23 @@ export async function ensure(ctx, storyId, branch) {
41
42
  if (typeof ctx.onPhase === 'function') ctx.onPhase(name);
42
43
  };
43
44
 
45
+ // Reuse must not blindly skip the install: when the prior run's install
46
+ // failed (or was interrupted), `skipped/worktree-reused` defeats the
47
+ // retry exactly when it matters — `deriveInstallAction('skipped')` skips.
48
+ // Probe for a completed install; on a failed probe, retry the install
49
+ // here and surface its real outcome.
50
+ const reuseInstallStatus = () => {
51
+ const strategy = ctx.config?.nodeModulesStrategy ?? 'per-worktree';
52
+ const probe = probeReusedInstall(strategy, wtPath);
53
+ if (probe.status !== 'failed') return probe;
54
+ ctx.logger.warn(
55
+ `worktree.reuse install probe failed (${probe.reason}) — retrying install in ${wtPath}`,
56
+ );
57
+ // `ctx.installDependencies` is a test-injection seam; production ctx
58
+ // bags leave it unset and get the real installer.
59
+ return (ctx.installDependencies ?? installDependencies)(ctx, wtPath);
60
+ };
61
+
44
62
  if (existing) {
45
63
  if (existing.branch && existing.branch !== br) {
46
64
  throw new Error(
@@ -53,7 +71,7 @@ export async function ensure(ctx, storyId, branch) {
53
71
  return {
54
72
  path: wtPath,
55
73
  created: false,
56
- installStatus: { status: 'skipped', reason: 'worktree-reused' },
74
+ installStatus: reuseInstallStatus(),
57
75
  };
58
76
  }
59
77
 
@@ -87,7 +105,7 @@ export async function ensure(ctx, storyId, branch) {
87
105
  return {
88
106
  path: wtPath,
89
107
  created: false,
90
- installStatus: { status: 'skipped', reason: 'worktree-reused' },
108
+ installStatus: reuseInstallStatus(),
91
109
  };
92
110
  }
93
111
  }