mandrel 1.94.0 → 2.0.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 (393) hide show
  1. package/.agents/README.md +59 -73
  2. package/.agents/agents/story-worker.md +12 -13
  3. package/.agents/docs/SDLC.md +489 -1285
  4. package/.agents/docs/agentrc-reference.json +177 -67
  5. package/.agents/docs/configuration.md +104 -138
  6. package/.agents/docs/execution-reference.md +17 -20
  7. package/.agents/docs/quality-gates.md +13 -19
  8. package/.agents/docs/workflows.md +3 -3
  9. package/.agents/instructions.md +70 -81
  10. package/.agents/rules/ci-remediation.md +8 -12
  11. package/.agents/rules/git-conventions-reference.md +6 -7
  12. package/.agents/rules/git-conventions.md +16 -22
  13. package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
  14. package/.agents/schemas/agentrc.schema.json +54 -214
  15. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  16. package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
  17. package/.agents/schemas/risk-verdict.schema.json +0 -13
  18. package/.agents/scripts/analyze-execution.js +1 -1
  19. package/.agents/scripts/audit-to-stories.js +7 -7
  20. package/.agents/scripts/boot-sweep.js +1 -1
  21. package/.agents/scripts/check-lifecycle-lint.js +6 -9
  22. package/.agents/scripts/check-prepush-recovery.js +1 -1
  23. package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
  24. package/.agents/scripts/lib/Logger.js +6 -10
  25. package/.agents/scripts/lib/audit-suite/runner.js +2 -2
  26. package/.agents/scripts/lib/audit-suite/selector.js +5 -5
  27. package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
  28. package/.agents/scripts/lib/baselines/kernel.js +206 -18
  29. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -4
  30. package/.agents/scripts/lib/baselines/reader.js +1 -6
  31. package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
  32. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
  33. package/.agents/scripts/lib/checks/core-bare-clean.js +2 -2
  34. package/.agents/scripts/lib/checks/index.js +2 -1
  35. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +23 -21
  36. package/.agents/scripts/lib/cli/standard-args.js +13 -22
  37. package/.agents/scripts/lib/cli-args.js +16 -7
  38. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  39. package/.agents/scripts/lib/config/ci.js +6 -31
  40. package/.agents/scripts/lib/config/delivery-routing.js +45 -29
  41. package/.agents/scripts/lib/config/explain.js +55 -36
  42. package/.agents/scripts/lib/config/limits.js +17 -58
  43. package/.agents/scripts/lib/config/paths.js +0 -2
  44. package/.agents/scripts/lib/config/quality.js +1 -1
  45. package/.agents/scripts/lib/config/runners.js +17 -50
  46. package/.agents/scripts/lib/config/temp-paths.js +19 -14
  47. package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
  48. package/.agents/scripts/lib/config-resolver.js +2 -7
  49. package/.agents/scripts/lib/config-settings-schema-delivery.js +24 -148
  50. package/.agents/scripts/lib/config-settings-schema-quality.js +8 -14
  51. package/.agents/scripts/lib/config-settings-schema.js +52 -38
  52. package/.agents/scripts/lib/dependency-parser.js +3 -2
  53. package/.agents/scripts/lib/doc-tiers.js +2 -2
  54. package/.agents/scripts/lib/duplicate-search.js +211 -41
  55. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
  56. package/.agents/scripts/lib/findings/promote-finding.js +5 -5
  57. package/.agents/scripts/lib/framework-version.js +2 -3
  58. package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
  59. package/.agents/scripts/lib/git-branch-lifecycle.js +17 -22
  60. package/.agents/scripts/lib/git-utils.js +32 -6
  61. package/.agents/scripts/lib/github/framework-repo.js +6 -0
  62. package/.agents/scripts/lib/label-constants.js +10 -23
  63. package/.agents/scripts/lib/label-taxonomy.js +9 -43
  64. package/.agents/scripts/lib/observability/active-story-env.js +1 -1
  65. package/.agents/scripts/lib/observability/hook-heartbeat.js +20 -52
  66. package/.agents/scripts/lib/observability/source-classifier.js +3 -3
  67. package/.agents/scripts/lib/onboard/init-tail.js +1 -3
  68. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +128 -0
  69. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +8 -5
  70. package/.agents/scripts/lib/orchestration/ceremony-routing.js +83 -20
  71. package/.agents/scripts/lib/orchestration/code-review.js +20 -268
  72. package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
  73. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +1 -1
  74. package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
  75. package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
  76. package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
  77. package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
  78. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +8 -8
  79. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +2 -2
  80. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
  81. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +17 -43
  82. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
  83. package/.agents/scripts/lib/orchestration/merge-block-class.js +1 -1
  84. package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
  85. package/.agents/scripts/lib/orchestration/plan-context.js +248 -266
  86. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +1 -1
  87. package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
  88. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +61 -0
  89. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +97 -0
  90. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +223 -854
  91. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +361 -0
  92. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +35 -108
  93. package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
  94. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
  95. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +13 -13
  96. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
  97. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/risk-verdict.js +3 -4
  98. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +2 -2
  99. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +8 -20
  100. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +3 -2
  101. package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
  102. package/.agents/scripts/lib/orchestration/preflight-cache.js +5 -5
  103. package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
  104. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +155 -0
  105. package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
  106. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +8 -8
  107. package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
  108. package/.agents/scripts/lib/orchestration/review-depth.js +26 -12
  109. package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
  110. package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
  111. package/.agents/scripts/lib/orchestration/run-epilogue.js +426 -0
  112. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +1 -1
  113. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +95 -41
  114. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +16 -13
  115. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +40 -0
  116. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +11 -3
  117. package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
  118. package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
  119. package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
  120. package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
  121. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
  122. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -12
  123. package/.agents/scripts/lib/orchestration/story-follow-ups.js +237 -0
  124. package/.agents/scripts/lib/orchestration/story-init-remote.js +47 -0
  125. package/.agents/scripts/lib/orchestration/story-plan-state.js +48 -0
  126. package/.agents/scripts/lib/orchestration/{epic-runner → story-progress}/story-run-progress-writer.js +3 -3
  127. package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
  128. package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -18
  129. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +11 -61
  130. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +189 -373
  131. package/.agents/scripts/lib/orchestration/ticket-validator.js +2 -7
  132. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +0 -25
  133. package/.agents/scripts/lib/orchestration/ticketing/reads.js +29 -26
  134. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -5
  135. package/.agents/scripts/lib/planning-corpus.js +16 -11
  136. package/.agents/scripts/lib/preflight-runner.js +2 -2
  137. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
  138. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +36 -0
  139. package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
  140. package/.agents/scripts/lib/story-adjacency.js +11 -14
  141. package/.agents/scripts/lib/story-body/story-body.js +124 -70
  142. package/.agents/scripts/lib/story-plan.js +2 -4
  143. package/.agents/scripts/lib/templates/decomposer-prompts.js +46 -45
  144. package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
  145. package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +26 -26
  146. package/.agents/scripts/lib/validation-evidence.js +1 -1
  147. package/.agents/scripts/lib/wave-runner/ready-set.js +6 -6
  148. package/.agents/scripts/lib/workspace-provisioner.js +1 -1
  149. package/.agents/scripts/lib/worktree/lifecycle/reap.js +5 -7
  150. package/.agents/scripts/lint-issue-body.js +71 -40
  151. package/.agents/scripts/mandrel-update-preflight.js +1 -1
  152. package/.agents/scripts/notify.js +4 -3
  153. package/.agents/scripts/plan-context.js +64 -74
  154. package/.agents/scripts/plan-persist.js +121 -280
  155. package/.agents/scripts/plan-run-epilogue.js +97 -0
  156. package/.agents/scripts/providers/github/issues.js +17 -33
  157. package/.agents/scripts/providers/github/mappers.js +0 -12
  158. package/.agents/scripts/providers/github/tickets.js +2 -5
  159. package/.agents/scripts/resolve-plan-run.js +117 -0
  160. package/.agents/scripts/signals-view.js +24 -19
  161. package/.agents/scripts/single-story-close.js +11 -14
  162. package/.agents/scripts/single-story-confirm-merge.js +39 -23
  163. package/.agents/scripts/single-story-init.js +29 -20
  164. package/.agents/scripts/stories-wave-tick.js +6 -6
  165. package/.agents/scripts/story-plan.js +26 -47
  166. package/.agents/scripts/update-ticket-state.js +6 -15
  167. package/.agents/skills/core/analyze-execution/SKILL.md +21 -18
  168. package/.agents/skills/core/idea-refinement/SKILL.md +6 -6
  169. package/.agents/skills/core/scope-triage/SKILL.md +28 -172
  170. package/.agents/skills/skills.index.json +3 -43
  171. package/.agents/starter-agentrc.json +0 -5
  172. package/.agents/templates/agent-protocol.md +9 -10
  173. package/.agents/workflows/audit-architecture.md +3 -3
  174. package/.agents/workflows/audit-clean-code.md +3 -3
  175. package/.agents/workflows/audit-dependencies.md +3 -3
  176. package/.agents/workflows/audit-devops.md +3 -3
  177. package/.agents/workflows/audit-documentation.md +5 -5
  178. package/.agents/workflows/audit-lighthouse.md +3 -3
  179. package/.agents/workflows/audit-navigability.md +3 -2
  180. package/.agents/workflows/audit-performance.md +3 -3
  181. package/.agents/workflows/audit-privacy.md +3 -3
  182. package/.agents/workflows/audit-quality.md +3 -3
  183. package/.agents/workflows/audit-security.md +3 -3
  184. package/.agents/workflows/audit-seo.md +3 -3
  185. package/.agents/workflows/audit-sre.md +3 -3
  186. package/.agents/workflows/audit-to-stories.md +20 -20
  187. package/.agents/workflows/audit-ux-ui.md +3 -3
  188. package/.agents/workflows/deliver.md +122 -174
  189. package/.agents/workflows/git-cleanup.md +3 -4
  190. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  191. package/.agents/workflows/helpers/acceptance-self-eval.md +16 -29
  192. package/.agents/workflows/helpers/code-review.md +70 -193
  193. package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +12 -14
  194. package/.agents/workflows/helpers/{single-story-deliver.md → deliver-story.md} +113 -139
  195. package/.agents/workflows/helpers/diagnose.md +10 -10
  196. package/.agents/workflows/helpers/parallel-tooling.md +1 -1
  197. package/.agents/workflows/helpers/signals.md +16 -16
  198. package/.agents/workflows/helpers/worktree-lifecycle.md +48 -64
  199. package/.agents/workflows/mandrel-update.md +2 -1
  200. package/.agents/workflows/plan.md +112 -145
  201. package/.agents/workflows/qa-assist.md +24 -30
  202. package/.agents/workflows/qa-explore.md +29 -38
  203. package/.agents/workflows/qa-run.md +2 -2
  204. package/README.md +9 -8
  205. package/docs/CHANGELOG.md +30 -0
  206. package/lib/migrations/index.js +6 -5
  207. package/package.json +2 -2
  208. package/.agents/agents/retro.md +0 -42
  209. package/.agents/personas/architect.md +0 -113
  210. package/.agents/personas/devops-engineer.md +0 -38
  211. package/.agents/personas/engineer.md +0 -33
  212. package/.agents/personas/project-manager.md +0 -114
  213. package/.agents/personas/qa-engineer.md +0 -95
  214. package/.agents/personas/security-engineer.md +0 -111
  215. package/.agents/personas/technical-writer.md +0 -101
  216. package/.agents/schemas/dispatch-manifest.json +0 -232
  217. package/.agents/schemas/epic-spec.schema.json +0 -153
  218. package/.agents/schemas/lifecycle/slice.end.schema.json +0 -21
  219. package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +0 -20
  220. package/.agents/schemas/lifecycle/slice.start.schema.json +0 -17
  221. package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
  222. package/.agents/scripts/bookkeeping-reconcile.js +0 -117
  223. package/.agents/scripts/dispatcher.js +0 -295
  224. package/.agents/scripts/epic-audit-prepare.js +0 -497
  225. package/.agents/scripts/epic-audit-recheck.js +0 -274
  226. package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
  227. package/.agents/scripts/epic-deliver-preflight.js +0 -462
  228. package/.agents/scripts/epic-deliver-prepare.js +0 -852
  229. package/.agents/scripts/epic-execute-record-wave.js +0 -449
  230. package/.agents/scripts/epic-plan-clarity.js +0 -211
  231. package/.agents/scripts/epic-plan-healthcheck.js +0 -581
  232. package/.agents/scripts/epic-reconcile.js +0 -625
  233. package/.agents/scripts/lib/baseline-snapshot.js +0 -979
  234. package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
  235. package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
  236. package/.agents/scripts/lib/config/lifecycle.js +0 -40
  237. package/.agents/scripts/lib/config/preflight.js +0 -58
  238. package/.agents/scripts/lib/config/retro.js +0 -77
  239. package/.agents/scripts/lib/epic-merge-lock.js +0 -322
  240. package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
  241. package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
  242. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -539
  243. package/.agents/scripts/lib/orchestration/deliver-route.js +0 -173
  244. package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
  245. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
  246. package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
  247. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
  248. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
  249. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
  250. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
  251. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
  252. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
  253. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
  254. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
  255. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
  256. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
  257. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
  258. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
  259. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
  260. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
  261. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
  262. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -621
  263. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
  264. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
  265. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
  266. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
  267. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
  268. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
  269. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
  270. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
  271. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
  272. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
  273. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
  274. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
  275. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
  276. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
  277. package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
  278. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
  279. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
  280. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
  281. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
  282. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
  283. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
  284. package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
  285. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
  286. package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +0 -270
  287. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
  288. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -465
  289. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
  290. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
  291. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
  292. package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -284
  293. package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
  294. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
  295. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
  296. package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
  297. package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
  298. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
  299. package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
  300. package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
  301. package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
  302. package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
  303. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
  304. package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
  305. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
  306. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
  307. package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
  308. package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
  309. package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
  310. package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
  311. package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
  312. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
  313. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
  314. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
  315. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
  316. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
  317. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
  318. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
  319. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
  320. package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
  321. package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
  322. package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
  323. package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
  324. package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
  325. package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
  326. package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
  327. package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
  328. package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
  329. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
  330. package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
  331. package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
  332. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
  333. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
  334. package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
  335. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -302
  336. package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
  337. package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
  338. package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
  339. package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
  340. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
  341. package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
  342. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
  343. package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
  344. package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
  345. package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
  346. package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
  347. package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
  348. package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
  349. package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
  350. package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
  351. package/.agents/scripts/lib/push-epic-retry.js +0 -209
  352. package/.agents/scripts/lib/spec/index.js +0 -36
  353. package/.agents/scripts/lib/spec/loader.js +0 -425
  354. package/.agents/scripts/lib/spec/state.js +0 -208
  355. package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
  356. package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
  357. package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
  358. package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
  359. package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
  360. package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
  361. package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
  362. package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
  363. package/.agents/scripts/lib/wave-runner/tick.js +0 -754
  364. package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
  365. package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
  366. package/.agents/scripts/lifecycle-emit.js +0 -510
  367. package/.agents/scripts/retro-run.js +0 -218
  368. package/.agents/scripts/slice-phase.js +0 -361
  369. package/.agents/scripts/standalone-feedback-rollup.js +0 -188
  370. package/.agents/scripts/story-close.js +0 -294
  371. package/.agents/scripts/story-init.js +0 -599
  372. package/.agents/scripts/story-phase.js +0 -369
  373. package/.agents/scripts/wave-tick.js +0 -464
  374. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
  375. package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
  376. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
  377. package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
  378. package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
  379. package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
  380. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -383
  381. package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
  382. package/.agents/workflows/helpers/deliver-epic-reference.md +0 -547
  383. package/.agents/workflows/helpers/deliver-epic-single.md +0 -331
  384. package/.agents/workflows/helpers/deliver-epic.md +0 -998
  385. package/.agents/workflows/helpers/deliver-stories.md +0 -450
  386. package/.agents/workflows/helpers/epic-audit.md +0 -189
  387. package/.agents/workflows/helpers/epic-deliver-story.md +0 -436
  388. package/.agents/workflows/helpers/epic-testing.md +0 -125
  389. package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
  390. package/.agents/workflows/helpers/plan-epic.md +0 -353
  391. package/.agents/workflows/helpers/plan-story.md +0 -251
  392. package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
  393. /package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +0 -0
@@ -1,238 +0,0 @@
1
- /**
2
- * progress-reporter/transport.js — outbound I/O for the
3
- * `/deliver` progress narrative.
4
- *
5
- * Extracted from the parent `progress-reporter.js` so the
6
- * GitHub-comment posting surface (in `composition.js`) and the
7
- * progress-signal aggregation (in `signals.js`) can be reasoned about
8
- * independently of the webhook fan-out.
9
- *
10
- * Each `emit*` helper dispatches a curated event-shaped payload through
11
- * the caller-supplied `notify` function. The `notify` contract is owned
12
- * by `lib/notifications/*` and ultimately reaches the configured webhook
13
- * via gh-exec (the retry/backoff loop lives there, not here — this
14
- * module's role is the boundary). Failures from `notify` are swallowed
15
- * by design: a flaky webhook URL must never crash the runner mid-wave.
16
- *
17
- * The webhook events emitted from this module are:
18
- *
19
- * - `epic-started` — fired once at /deliver kickoff
20
- * - `epic-progress` — fired at wave boundaries / blocker transitions
21
- * - `epic-blocked` — wave aggregated to blocked/failed outside halt path
22
- * - `epic-unblocked` — operator flipped back to executing
23
- *
24
- * Each fire passes `skipComment: true` so the operator-facing GitHub
25
- * comments stay owned by `composition.js` (`upsertEpicRunProgress` and
26
- * `ProgressReporter.fire()`) — there is exactly one comment writer and
27
- * exactly one webhook writer per event.
28
- */
29
-
30
- /**
31
- * Webhook event name for the curated epic-progress rollup. Distinct from
32
- * the `epic-run-progress` structured-comment kind in `signals.js` — the
33
- * comment is the operator-facing per-poll snapshot on the Epic ticket,
34
- * the webhook event is the coarse-grained rollup that fires at wave
35
- * boundaries and after blocker transitions.
36
- */
37
- export const EPIC_PROGRESS_EVENT = 'epic-progress';
38
-
39
- /**
40
- * Fire a curated `epic-progress` webhook event. Event-driven only — called
41
- * per recorder beat and after blocker raise/clear transitions. Carries the
42
- * rollup payload `{ pct, done, total, phase, openBlockers }`, which Slack
43
- * consumers and downstream subscribers use to track epic progress without
44
- * subscribing to per-story chatter.
45
- *
46
- * The dispatch passes `skipComment: true` — the operator-facing GitHub
47
- * comment is owned by `ProgressReporter.fire()` and `upsertEpicRunProgress`,
48
- * not by this webhook fire.
49
- *
50
- * Failures are swallowed by design: the runner must keep moving even if
51
- * the webhook URL is misconfigured or the network is flaky.
52
- *
53
- * Story #4155 — the Epic `/deliver` runtime cut over to the continuous
54
- * ready-set scheduler, which has **no wave index**. The wave segment was
55
- * dropped from the message and the rollup payload entirely (rather than
56
- * rendered as `Wave undefined/undefined`); the sole live caller
57
- * (`wave-record-notifications.js`) never supplied wave coordinates.
58
- *
59
- * @param {{
60
- * notify: Function|null,
61
- * epicId: number,
62
- * done: number,
63
- * total: number,
64
- * phase?: string,
65
- * openBlockers?: Array<{ reason: string, storyId?: number }>,
66
- * logger?: { warn?: Function },
67
- * }} args
68
- * @returns {Promise<{ payload: object } | null>}
69
- */
70
- export async function emitEpicProgress({
71
- notify,
72
- epicId,
73
- done,
74
- total,
75
- phase,
76
- openBlockers = [],
77
- logger,
78
- }) {
79
- if (typeof notify !== 'function') return null;
80
- const epicIdNum = Number(epicId);
81
- if (!Number.isInteger(epicIdNum) || epicIdNum <= 0) return null;
82
- const totalN = Math.max(0, Number(total) || 0);
83
- const doneN = Math.max(0, Math.min(totalN, Number(done) || 0));
84
- const pct = totalN === 0 ? 0 : Math.round((doneN / totalN) * 100);
85
- const blockerCount = Array.isArray(openBlockers) ? openBlockers.length : 0;
86
- const blockerSuffix =
87
- blockerCount > 0
88
- ? ` · 🚧 ${blockerCount} blocker${blockerCount === 1 ? '' : 's'}`
89
- : '';
90
- const message = `Epic #${epicIdNum} progress · ${doneN}/${totalN} stories done (${pct}%)${blockerSuffix}`;
91
-
92
- const payload = {
93
- severity: blockerCount > 0 ? 'high' : 'medium',
94
- message,
95
- event: EPIC_PROGRESS_EVENT,
96
- level: 'epic',
97
- epicId: epicIdNum,
98
- };
99
- if (phase) payload.phase = phase;
100
-
101
- try {
102
- await notify(epicIdNum, payload, { skipComment: true });
103
- } catch (err) {
104
- logger?.warn?.(
105
- `[emitEpicProgress] notify dispatch failed (swallowed): ${err?.message ?? err}`,
106
- );
107
- return null;
108
- }
109
- return {
110
- payload: {
111
- pct,
112
- done: doneN,
113
- total: totalN,
114
- phase,
115
- openBlockers: openBlockers ?? [],
116
- },
117
- };
118
- }
119
-
120
- /**
121
- * Fire a curated `epic-started` webhook event at /deliver kickoff.
122
- * The Slack consumer anchors the rest of the epic narrative to this fire.
123
- * Failures are swallowed.
124
- *
125
- * Story #4155 — the ready-set runtime has no wave count, so the wave
126
- * segment was dropped from the message (rather than rendered as
127
- * `undefined wave(s)`); the sole live caller never supplied one.
128
- */
129
- export async function emitEpicStarted({
130
- notify,
131
- epicId,
132
- totalStories,
133
- title,
134
- logger,
135
- }) {
136
- if (typeof notify !== 'function') return null;
137
- const epicIdNum = Number(epicId);
138
- if (!Number.isInteger(epicIdNum) || epicIdNum <= 0) return null;
139
- const message = `Epic #${epicIdNum} started · ${totalStories} stor${totalStories === 1 ? 'y' : 'ies'}${title ? ` — ${title}` : ''}`;
140
- try {
141
- await notify(
142
- epicIdNum,
143
- {
144
- severity: 'medium',
145
- message,
146
- event: 'epic-started',
147
- level: 'epic',
148
- epicId: epicIdNum,
149
- },
150
- { skipComment: true },
151
- );
152
- } catch (err) {
153
- logger?.warn?.(
154
- `[emitEpicStarted] notify dispatch failed (swallowed): ${err?.message ?? err}`,
155
- );
156
- }
157
- return null;
158
- }
159
-
160
- /**
161
- * Fire a curated `epic-blocked` webhook event when a wave aggregates to
162
- * `blocked` or `failed` outside the `BlockerHandler.halt` code path (the
163
- * /deliver host-LLM loop has no handler instance — it calls this
164
- * helper directly from `epic-execute-record-wave.js`). The payload shape
165
- * matches the inline emit in `BlockerHandler.halt` so downstream consumers
166
- * see one canonical envelope regardless of which entry point fired.
167
- * Failures are swallowed.
168
- */
169
- export async function emitEpicBlocked({
170
- notify,
171
- epicId,
172
- reason,
173
- storyId,
174
- logger,
175
- }) {
176
- if (typeof notify !== 'function') return null;
177
- const epicIdNum = Number(epicId);
178
- if (!Number.isInteger(epicIdNum) || epicIdNum <= 0) return null;
179
- const storyPart = storyId ? ` (story #${storyId})` : '';
180
- const message = `🚨 Action Required: Epic #${epicIdNum}${storyPart} blocked: ${reason}`;
181
- try {
182
- await notify(
183
- epicIdNum,
184
- {
185
- severity: 'high',
186
- message,
187
- event: 'epic-blocked',
188
- level: 'epic',
189
- epicId: epicIdNum,
190
- },
191
- { skipComment: true },
192
- );
193
- } catch (err) {
194
- logger?.warn?.(
195
- `[emitEpicBlocked] notify dispatch failed (swallowed): ${err?.message ?? err}`,
196
- );
197
- }
198
- return null;
199
- }
200
-
201
- /**
202
- * Fire a curated `epic-unblocked` webhook event after the operator flips
203
- * the Epic label back to `agent::executing`. Paired with `epic-blocked` so
204
- * downstream consumers can track open-blocker lifecycle. Failures are
205
- * swallowed.
206
- */
207
- export async function emitEpicUnblocked({
208
- notify,
209
- epicId,
210
- resolvedBlocker,
211
- logger,
212
- }) {
213
- if (typeof notify !== 'function') return null;
214
- const epicIdNum = Number(epicId);
215
- if (!Number.isInteger(epicIdNum) || epicIdNum <= 0) return null;
216
- const reasonPart = resolvedBlocker?.reason
217
- ? ` (${resolvedBlocker.reason})`
218
- : '';
219
- const message = `Epic #${epicIdNum} unblocked${reasonPart} · resuming.`;
220
- try {
221
- await notify(
222
- epicIdNum,
223
- {
224
- severity: 'medium',
225
- message,
226
- event: 'epic-unblocked',
227
- level: 'epic',
228
- epicId: epicIdNum,
229
- },
230
- { skipComment: true },
231
- );
232
- } catch (err) {
233
- logger?.warn?.(
234
- `[emitEpicUnblocked] notify dispatch failed (swallowed): ${err?.message ?? err}`,
235
- );
236
- }
237
- return null;
238
- }
@@ -1,32 +0,0 @@
1
- /**
2
- * Shared bullet-formatting helpers for progress-signal detectors.
3
- *
4
- * Both crap-drift and maintainability-drift produce component-level
5
- * regression bullets and need the same two pure utilities — hoisted here
6
- * so they are maintained in one place (Story #3631, refs #3631).
7
- */
8
-
9
- /**
10
- * Comparator that sorts the wildcard component `'*'` before all named
11
- * components, with named components in locale order.
12
- *
13
- * @param {string} a
14
- * @param {string} b
15
- * @returns {number}
16
- */
17
- export function componentOrder(a, b) {
18
- if (a === '*') return -1;
19
- if (b === '*') return 1;
20
- return a.localeCompare(b);
21
- }
22
-
23
- /**
24
- * Formats a numeric gate value for display: integers render without a
25
- * decimal point; non-integers are fixed to two decimal places.
26
- *
27
- * @param {number} value
28
- * @returns {string}
29
- */
30
- export function formatNumber(value) {
31
- return Number.isInteger(value) ? String(value) : value.toFixed(2);
32
- }
@@ -1,203 +0,0 @@
1
- import nodeFs from 'node:fs';
2
- import path from 'node:path';
3
- import { resolveComponents } from '../../../baselines/components.js';
4
- import { componentOrder } from './_bullet-format.js';
5
-
6
- /**
7
- * Shared component-regression walk for the progress-signal drift detectors
8
- * (Story #3984). `crap-drift.js` and `maintainability-drift.js` previously
9
- * duplicated this baseline-rollup → per-component compare → bullet-list
10
- * shape verbatim; the only divergence was the breach comparator (CRAP is
11
- * "lower is better", maintainability is "higher is better") and the bullet
12
- * text. Both detectors now parameterize this walk.
13
- *
14
- * Pure — the caller loads the baseline via `lib/baselines/reader.js#load(...)`
15
- * and passes the resulting `{ rollup }` plus the gate config block.
16
- *
17
- * Only components whose rollup breaches the configured floor surface — a
18
- * breach in a component-scoped floor does NOT report against `*` unless
19
- * `*` itself breaches. This keeps the rollout narrowly targeted: when an
20
- * operator wires up a per-component floor for `api`, regressing `api`
21
- * names `api` — not `*`.
22
- *
23
- * @param {{
24
- * rollup?: Record<string, Record<string, number>>,
25
- * gateConfig?: { floors?: Record<string, Record<string, number>> } & object,
26
- * }} params
27
- * @param {{
28
- * isBreach: (value: number, floor: number) => boolean,
29
- * formatBullet: (name: string, axis: string, value: number, floor: number) => string,
30
- * }} spec
31
- * @returns {string[]}
32
- */
33
- export function walkComponentRegressions(params = {}, spec) {
34
- const rollup = params.rollup ?? {};
35
- const gateConfig = params.gateConfig ?? {};
36
- const floors = gateConfig.floors ?? {};
37
- const components = resolveComponents(gateConfig);
38
- const names = new Set([
39
- ...Object.keys(components),
40
- ...Object.keys(floors),
41
- ...Object.keys(rollup),
42
- ]);
43
- const bullets = [];
44
- for (const name of [...names].sort(componentOrder)) {
45
- const aggregate = rollup[name];
46
- if (!aggregate || typeof aggregate !== 'object') continue;
47
- const floor = floors[name] ?? floors['*'];
48
- if (!floor || typeof floor !== 'object') continue;
49
- for (const axis of Object.keys(floor).sort()) {
50
- const target = floor[axis];
51
- const value = aggregate[axis];
52
- if (typeof target !== 'number' || !Number.isFinite(target)) continue;
53
- if (typeof value !== 'number' || !Number.isFinite(value)) continue;
54
- if (!spec.isBreach(value, target)) continue;
55
- bullets.push(spec.formatBullet(name, axis, value, target));
56
- }
57
- }
58
- return bullets;
59
- }
60
-
61
- /**
62
- * Shared wave-start snapshot persistence for the drift detectors. Both
63
- * detectors persist a `{ capturedAt, ...metadata, scores }` JSON document
64
- * under `<cwd>/<baselineDir>/<filename>` so a resumed epic run can re-read
65
- * the wave-start anchor rather than lose it, and both treat persistence as
66
- * best-effort (the in-memory baseline still works when a write fails).
67
- *
68
- * @param {{
69
- * fs: { readFileSync?: Function, writeFileSync?: Function, mkdirSync?: Function },
70
- * baselinePath: string,
71
- * metadata?: Record<string, unknown>,
72
- * }} opts
73
- */
74
- function createSnapshotStore({ fs, baselinePath, metadata = {} }) {
75
- return {
76
- persist(scores) {
77
- if (!fs.writeFileSync) return;
78
- try {
79
- fs.mkdirSync?.(path.dirname(baselinePath), { recursive: true });
80
- fs.writeFileSync(
81
- baselinePath,
82
- JSON.stringify(
83
- { capturedAt: new Date().toISOString(), ...metadata, scores },
84
- null,
85
- 2,
86
- ),
87
- );
88
- } catch {
89
- // persistence is best-effort; the in-memory baseline still works
90
- }
91
- },
92
-
93
- load() {
94
- if (!fs.readFileSync) return null;
95
- try {
96
- const raw = fs.readFileSync(baselinePath, 'utf-8');
97
- const parsed = JSON.parse(raw);
98
- return parsed?.scores ?? null;
99
- } catch {
100
- return null;
101
- }
102
- },
103
- };
104
- }
105
-
106
- /**
107
- * Shared per-file/per-method drift-detector skeleton for the progress-signal
108
- * detectors (Story #4076). `crap-drift.js` and `maintainability-drift.js`
109
- * previously duplicated the same `{ baselinePath, captureBaseline,
110
- * loadBaseline, detect }` shape — identical snapshot-store wiring, identical
111
- * "distinct from canonical baseline" framing, and identical
112
- * swallow-and-warn resilience. The only divergence is the per-axis scoring
113
- * (`scoreFile`), the snapshot value extracted from a score (`captureScore`),
114
- * and the per-detect comparison that emits bullets (`detect`). Both detectors
115
- * now supply those deltas to this factory, mirroring the "shared walker +
116
- * per-axis delta" pattern already established by `walkComponentRegressions`.
117
- *
118
- * The factory owns:
119
- *
120
- * - `fs` / `cwd` / `files` resolution from `opts`.
121
- * - The `<cwd>/<baselineDir>/<baselineFilename>` baseline path and the
122
- * `createSnapshotStore` wiring (with caller-supplied `metadata`).
123
- * - The in-memory `baseline` cache and the `captureBaseline` /
124
- * `loadBaseline` lifecycle.
125
- *
126
- * The caller owns:
127
- *
128
- * - `scoreFile(relPath, ctx)` — returns the per-file score (any shape) or
129
- * `null` when the file can't be scored. `ctx` is whatever
130
- * `beforeScore()` returned for this pass (e.g. a coverage map), or
131
- * `undefined` when `beforeScore` is omitted.
132
- * - `captureScore(score)` — maps a `scoreFile` result to the value
133
- * persisted in the snapshot (e.g. extract `crap` from each method row).
134
- * - `detect({ baseline, scoreFile })` — async; returns the bullet list.
135
- * Receives the loaded baseline and a `scoreFile(relPath)` bound to this
136
- * pass's score context so the per-axis compare stays free of plumbing.
137
- * - `beforeScore()` (optional) — runs once per `captureBaseline` / `detect`
138
- * pass and returns the score context threaded into `scoreFile`.
139
- *
140
- * @param {{
141
- * cwd?: string,
142
- * files?: string[],
143
- * fs?: { readFileSync?: Function, writeFileSync?: Function, mkdirSync?: Function, existsSync?: Function },
144
- * baselineDir?: string,
145
- * baselineFilename: string,
146
- * metadata?: Record<string, unknown>,
147
- * beforeScore?: () => unknown,
148
- * scoreFile: (relPath: string, ctx: unknown) => unknown,
149
- * captureScore: (score: unknown) => unknown,
150
- * detect: (args: { baseline: Record<string, unknown>, scoreFile: (relPath: string) => unknown }) => Promise<string[]>,
151
- * }} opts
152
- */
153
- export function createDriftDetector(opts = {}) {
154
- const fs = opts.fs ?? nodeFs;
155
- const cwd = opts.cwd ?? process.cwd();
156
- const files = Array.isArray(opts.files) ? [...opts.files] : [];
157
- const baselineDir = opts.baselineDir ?? '.agents/state';
158
- const baselinePath = path.join(cwd, baselineDir, opts.baselineFilename);
159
- const beforeScore = opts.beforeScore ?? (() => undefined);
160
- const scoreFile = opts.scoreFile;
161
- const captureScore = opts.captureScore;
162
- const runDetect = opts.detect;
163
- const store = createSnapshotStore({
164
- fs,
165
- baselinePath,
166
- metadata: opts.metadata ?? {},
167
- });
168
-
169
- let baseline = null;
170
-
171
- return {
172
- get baselinePath() {
173
- return baselinePath;
174
- },
175
-
176
- captureBaseline() {
177
- const ctx = beforeScore();
178
- const snapshot = {};
179
- for (const f of files) {
180
- const score = scoreFile(f, ctx);
181
- if (score == null) continue;
182
- snapshot[f] = captureScore(score);
183
- }
184
- baseline = snapshot;
185
- store.persist(snapshot);
186
- return snapshot;
187
- },
188
-
189
- loadBaseline() {
190
- baseline = store.load();
191
- return baseline;
192
- },
193
-
194
- async detect() {
195
- if (!baseline) return [];
196
- const ctx = beforeScore();
197
- return runDetect({
198
- baseline,
199
- scoreFile: (relPath) => scoreFile(relPath, ctx),
200
- });
201
- },
202
- };
203
- }
@@ -1,227 +0,0 @@
1
- import nodeFs from 'node:fs';
2
- import path from 'node:path';
3
- import { loadCoverage as defaultLoadCoverage } from '../../../coverage-utils.js';
4
- import { calculateCrapForSource } from '../../../crap-engine.js';
5
- import { formatNumber } from './_bullet-format.js';
6
- import {
7
- createDriftDetector,
8
- walkComponentRegressions,
9
- } from './component-drift.js';
10
-
11
- const DEFAULT_THRESHOLD = 5.0;
12
- const DEFAULT_CEILING = 30;
13
- // Distinct from the canonical ratchet baseline at `baselines/crap.json`
14
- // (Epic #730 Story 5.5) — this is a per-wave drift SNAPSHOT, not the
15
- // committed score baseline. Filename intentionally differs so a repo-wide
16
- // grep for the canonical baseline no longer hits the snapshot.
17
- const BASELINE_FILENAME = 'wave-crap-snapshot.json';
18
-
19
- /**
20
- * Normalise a repo-relative path for coverage-key lookup. Pure; exported
21
- * for unit tests so the underlying branches don't have to be exercised
22
- * through the closure-bound `findCoverageEntry`.
23
- */
24
- export function normaliseCoveragePath(relPath) {
25
- return String(relPath)
26
- .replace(/\\/g, '/')
27
- .replace(/^\.\/+/, '');
28
- }
29
-
30
- /**
31
- * True when a coverage map key matches a (normalised) relative path,
32
- * either by full equality or by suffix-with-slash. Pure.
33
- */
34
- export function coverageKeyMatches(key, suffix) {
35
- const norm = String(key).replace(/\\/g, '/');
36
- return norm === suffix || norm.endsWith(`/${suffix}`);
37
- }
38
-
39
- /**
40
- * Detect per-component CRAP regressions from a baseline rollup against the
41
- * gate's configured floors. Pure — the caller loads the baseline via
42
- * `lib/baselines/reader.js#load('crap')` and passes the resulting
43
- * `{ rollup }` plus the gate config block.
44
- *
45
- * Bullet shape (Task #1919, Epic #1786):
46
- *
47
- * 🧨 crap: <component> <axis> <value> > floor <floor>
48
- *
49
- * CRAP is a "lower is better" gate — every axis breach reports when
50
- * `value > floor`. Component-scoped breaches do NOT trigger a `*` bullet
51
- * unless `*` itself breaches. The walk itself is the shared
52
- * `component-drift.js` helper (Story #3984).
53
- *
54
- * @param {{
55
- * rollup?: Record<string, Record<string, number>>,
56
- * gateConfig?: { floors?: Record<string, Record<string, number>> } & object,
57
- * }} params
58
- * @returns {string[]}
59
- */
60
- export function detectComponentRegressions(params = {}) {
61
- return walkComponentRegressions(params, {
62
- isBreach: (value, cap) => value > cap,
63
- formatBullet: (name, axis, value, cap) =>
64
- `🧨 crap: ${name} ${axis} ${formatNumber(value)} > floor ${formatNumber(cap)}`,
65
- });
66
- }
67
-
68
- /**
69
- * Detects per-method CRAP drift versus a wave-start baseline.
70
- *
71
- * Mirrors the `maintainability-drift.js` contract but at per-method
72
- * granularity: baseline snapshots are `{ "<file>::<method>@<startLine>": crap }`
73
- * rather than a single score per file. A method surfaces as a Notable bullet
74
- * when either condition holds:
75
- *
76
- * - **Crossed the ceiling** — current CRAP ≥ `ceiling` and baseline was
77
- * either absent or below `ceiling`. Captures newly introduced hotspots
78
- * and methods that tipped over the budget.
79
- * - **Rose by ≥ threshold** — baseline present and `current - baseline ≥
80
- * threshold`. Captures silent regressions that stay under the ceiling
81
- * but are trending badly.
82
- *
83
- * Bullet shape:
84
- *
85
- * 🧨 CRAP drift: <file>::<method> <score> (ceiling <N>)
86
- *
87
- * Persistence: baseline is written to `<cwd>/<baselineDir>/wave-crap-snapshot.json`
88
- * so a resumed epic run can reuse the wave-start anchor rather than lose it.
89
- *
90
- * Resilience: read / scoring errors for an individual file are swallowed (and
91
- * optionally logged) so a single unscorable file cannot take the progress
92
- * reporter down.
93
- *
94
- * @param {{
95
- * cwd?: string,
96
- * files?: string[],
97
- * fs?: { readFileSync: Function, writeFileSync?: Function, mkdirSync?: Function, existsSync?: Function },
98
- * calculate?: (source: string, coverageForFile: object|null) => Array<{ method: string, startLine: number, crap: number|null }>,
99
- * loadCoverage?: (coveragePath: string) => object|null,
100
- * coveragePath?: string | null,
101
- * threshold?: number,
102
- * ceiling?: number,
103
- * baselineDir?: string,
104
- * logger?: { warn?: Function },
105
- * }} [opts]
106
- */
107
- export function createCrapDriftDetector(opts = {}) {
108
- const fs = opts.fs ?? nodeFs;
109
- const cwd = opts.cwd ?? process.cwd();
110
- const files = Array.isArray(opts.files) ? [...opts.files] : [];
111
- const calculate = opts.calculate ?? calculateCrapForSource;
112
- const loadCoverage = opts.loadCoverage ?? defaultLoadCoverage;
113
- const coveragePath = opts.coveragePath ?? null;
114
- const threshold = Number.isFinite(opts.threshold)
115
- ? opts.threshold
116
- : DEFAULT_THRESHOLD;
117
- const ceiling = Number.isFinite(opts.ceiling)
118
- ? opts.ceiling
119
- : DEFAULT_CEILING;
120
- const logger = opts.logger ?? null;
121
-
122
- function methodKey(method, startLine) {
123
- return `${method}@${startLine}`;
124
- }
125
-
126
- // Hoisted helpers — exported via `_internal` for unit coverage on each
127
- // branch. Keeping them outside the closure keeps the orchestration
128
- // method's complexity low.
129
- // (see normaliseCoveragePath / coverageKeyMatches at module top.)
130
- function findCoverageEntry(coverageMap, relPath) {
131
- if (!coverageMap || typeof coverageMap !== 'object') return null;
132
- const suffix = normaliseCoveragePath(relPath);
133
- if (!suffix) return null;
134
- for (const key of Object.keys(coverageMap)) {
135
- if (coverageKeyMatches(key, suffix)) {
136
- return coverageMap[key] ?? null;
137
- }
138
- }
139
- return null;
140
- }
141
-
142
- function scoreFile(relPath, coverageMap) {
143
- try {
144
- const abs = path.join(cwd, relPath);
145
- const src = fs.readFileSync(abs, 'utf-8');
146
- const entry = findCoverageEntry(coverageMap, relPath);
147
- const rows = calculate(src, entry) ?? [];
148
- const methods = {};
149
- for (const row of rows) {
150
- if (!row || typeof row.method !== 'string') continue;
151
- if (typeof row.startLine !== 'number') continue;
152
- if (typeof row.crap !== 'number' || !Number.isFinite(row.crap))
153
- continue;
154
- methods[methodKey(row.method, row.startLine)] = {
155
- method: row.method,
156
- startLine: row.startLine,
157
- crap: row.crap,
158
- };
159
- }
160
- return methods;
161
- } catch (err) {
162
- logger?.warn?.(
163
- `[crap-drift] scoring failed for ${relPath}: ${err?.message ?? err}`,
164
- );
165
- return null;
166
- }
167
- }
168
-
169
- function readCoverageMap() {
170
- if (!coveragePath) return null;
171
- try {
172
- return loadCoverage(coveragePath);
173
- } catch (err) {
174
- logger?.warn?.(
175
- `[crap-drift] coverage load failed (${coveragePath}): ${err?.message ?? err}`,
176
- );
177
- return null;
178
- }
179
- }
180
-
181
- return createDriftDetector({
182
- fs,
183
- cwd,
184
- files: opts.files,
185
- baselineDir: opts.baselineDir,
186
- baselineFilename: BASELINE_FILENAME,
187
- metadata: { ceiling, threshold },
188
- beforeScore: readCoverageMap,
189
- scoreFile,
190
- captureScore: (methods) => {
191
- const persisted = {};
192
- for (const [key, row] of Object.entries(methods)) {
193
- persisted[key] = row.crap;
194
- }
195
- return persisted;
196
- },
197
- async detect({ baseline, scoreFile: score }) {
198
- const bullets = [];
199
- // Union of files watched and files present in baseline, so methods that
200
- // appeared since the snapshot (new files) are still checked, and methods
201
- // that disappeared from baseline-only files don't get us stuck iterating
202
- // anything spurious.
203
- const fileSet = new Set([...files, ...Object.keys(baseline)]);
204
- for (const relPath of fileSet) {
205
- const currentMethods = score(relPath);
206
- if (!currentMethods) continue;
207
- const fileBaseline = baseline[relPath] ?? {};
208
- for (const [key, row] of Object.entries(currentMethods)) {
209
- const prev = fileBaseline[key];
210
- const crossed =
211
- row.crap >= ceiling &&
212
- (prev === undefined || prev === null || prev < ceiling);
213
- const rose =
214
- typeof prev === 'number' &&
215
- Number.isFinite(prev) &&
216
- row.crap - prev >= threshold;
217
- if (crossed || rose) {
218
- bullets.push(
219
- `🧨 CRAP drift: ${relPath}::${row.method} ${row.crap.toFixed(2)} (ceiling ${ceiling})`,
220
- );
221
- }
222
- }
223
- }
224
- return bullets;
225
- },
226
- });
227
- }