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
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * epic-plan-healthcheck.js — Post-Plan Readiness Check
7
7
  *
8
- * Runs at the end of /epic-plan (Phase 10) to validate the backlog and
8
+ * Runs at the end of /plan (Phase 10) to validate the backlog and
9
9
  * optionally prime the execution environment before handing off to
10
10
  * /epic-deliver.
11
11
  *
@@ -27,7 +27,7 @@
27
27
  * node epic-plan-healthcheck.js --epic <EPIC_ID> \
28
28
  * [--paranoid] [--prime-install] [--dry-run]
29
29
  *
30
- * @see .agents/workflows/epic-plan.md Phase 10
30
+ * @see .agents/workflows/helpers/plan-epic.md Phase 10
31
31
  */
32
32
 
33
33
  import { spawnSync } from 'node:child_process';
@@ -131,7 +131,7 @@ function checkGitRemote(baseBranch, cwd) {
131
131
 
132
132
  /**
133
133
  * Detect whether a Story body carries an inline `## Acceptance` section with
134
- * at least one checklist item. Epic #3078 — under 3-tier hierarchy, Stories
134
+ * at least one checklist item. Epic #3078 — under 2-tier hierarchy, Stories
135
135
  * carry acceptance inline, so the hierarchy check uses this signal as the
136
136
  * mark of a complete, executable Story.
137
137
  *
@@ -152,7 +152,7 @@ function hasInlineAcceptance(body) {
152
152
  }
153
153
 
154
154
  /**
155
- * Validate Epic ticket hierarchy. 3-tier is the only supported hierarchy
155
+ * Validate Epic ticket hierarchy. 2-tier is the only supported hierarchy
156
156
  * after Task #3154 deleted `planning.hierarchy`: every Story must carry an
157
157
  * inline `## Acceptance` checklist; there is no Task layer to graph.
158
158
  *
@@ -181,13 +181,9 @@ async function checkTickets(provider, epicId) {
181
181
  return { ok: false, detail: `Epic #${epicId} has no child tickets.` };
182
182
  }
183
183
 
184
- const features = tickets.filter((t) =>
185
- t.labels.includes(TYPE_LABELS.FEATURE),
186
- );
187
184
  const stories = tickets.filter((t) => t.labels.includes(TYPE_LABELS.STORY));
188
185
 
189
186
  const errors = [];
190
- if (features.length === 0) errors.push('no type::feature tickets');
191
187
  if (stories.length === 0) errors.push('no type::story tickets');
192
188
 
193
189
  const missingAcceptance = stories.filter(
@@ -214,7 +210,7 @@ async function checkTickets(provider, epicId) {
214
210
 
215
211
  return {
216
212
  ok: true,
217
- detail: `${features.length} features, ${stories.length} stories (3-tier, inline acceptance) — hierarchy valid${advisory}.`,
213
+ detail: `${stories.length} stories (2-tier, inline acceptance) — hierarchy valid${advisory}.`,
218
214
  };
219
215
  }
220
216
 
@@ -313,7 +309,7 @@ export async function runPlanHealthcheck(opts = {}) {
313
309
  await timed('git-remote', async () => checkGitRemote(baseBranch, cwd)),
314
310
  );
315
311
 
316
- // Paranoid lane: ticket-hierarchy revalidation (3-tier only).
312
+ // Paranoid lane: ticket-hierarchy revalidation (2-tier only).
317
313
  if (paranoid) {
318
314
  const provider = opts.injectedProvider || createProvider(config);
319
315
  progress('CHECK', 'Validating ticket hierarchy...');
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * epic-plan-spec-validate.js — Phase 7.5 Tech Spec post-authoring gate CLI.
5
5
  *
6
- * `/epic-plan` Phase 7 authors the Tech Spec; Phase 8.3 (Holistic
6
+ * `/plan` Phase 7 authors the Tech Spec; Phase 8.3 (Holistic
7
7
  * Consolidation) reconciles the draft ticket array against the Tech Spec's
8
8
  * `## Delivery Slicing` section, which the decompose-author skill uses as the
9
9
  * capability-boundary anchor. When that section is absent the consolidation
@@ -195,11 +195,11 @@ export async function fetchGhState(provider, epicId) {
195
195
  }
196
196
 
197
197
  /**
198
- * Walk the spec depth-first and yield one `{ slug, entity, title,
199
- * parentSlug, dependsOn }` record per logical entity (epic → feature
200
- * story → task), mirroring the diff engine's `flattenSpec`. Local to the
201
- * CLI so the reseed pass (below) can map spec slugs onto live GH issues
202
- * without importing the diff engine's private walker. Pure.
198
+ * Walk the spec and yield one `{ slug, entity, title, parentSlug,
199
+ * dependsOn }` record per logical entity (epic → story), mirroring the
200
+ * diff engine's `flattenSpec`. Local to the CLI so the reseed pass
201
+ * (below) can map spec slugs onto live GH issues without importing the
202
+ * diff engine's private walker. Pure.
203
203
  *
204
204
  * @param {object} spec
205
205
  * @returns {Array<{slug: string, entity: string, title: string, parentSlug: string|null, dependsOn: string[]}>}
@@ -216,32 +216,14 @@ export function flattenSpecForReseed(spec) {
216
216
  dependsOn: [],
217
217
  });
218
218
  }
219
- for (const feature of spec.features ?? []) {
219
+ for (const story of spec.stories ?? []) {
220
220
  out.push({
221
- slug: feature.slug,
222
- entity: 'feature',
223
- title: String(feature.title ?? ''),
221
+ slug: story.slug,
222
+ entity: 'story',
223
+ title: String(story.title ?? ''),
224
224
  parentSlug: 'epic',
225
- dependsOn: [],
225
+ dependsOn: story.dependsOn ?? [],
226
226
  });
227
- for (const story of feature.stories ?? []) {
228
- out.push({
229
- slug: story.slug,
230
- entity: 'story',
231
- title: String(story.title ?? ''),
232
- parentSlug: feature.slug,
233
- dependsOn: story.dependsOn ?? [],
234
- });
235
- for (const task of story.tasks ?? []) {
236
- out.push({
237
- slug: task.slug,
238
- entity: 'task',
239
- title: String(task.title ?? ''),
240
- parentSlug: story.slug,
241
- dependsOn: [],
242
- });
243
- }
244
- }
245
227
  }
246
228
  return out;
247
229
  }
@@ -255,7 +237,7 @@ export function flattenSpecForReseed(spec) {
255
237
  * file is absent (a fresh checkout, a reaped temp dir, the exact
256
238
  * situation `--resume` exists to recover from), `loadState` returns an
257
239
  * empty mapping, the diff engine sees every spec slug as unmapped, and
258
- * `apply` recreates the entire Feature/Story tree on top of the existing
240
+ * `apply` recreates the entire Story set on top of the existing
259
241
  * one — duplicating every child. This is precisely the failure `--resume`
260
242
  * is meant to prevent.
261
243
  *
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * evidence-gate.js — evidence-aware wrapper around a single shell gate.
5
5
  *
6
- * Tech Spec #819 §"Evidence record (Story 7)" — `/epic-deliver` Phase 3
6
+ * Tech Spec #819 §"Evidence record (Story 7)" — `/deliver` Phase 3
7
7
  * (close-validation) runs `npm run lint` and `npm test` against the Epic
8
8
  * branch before opening the PR.
9
9
  * If the same gate has already passed against the current `git rev-parse
@@ -47,9 +47,9 @@
47
47
  import { spawnSync } from 'node:child_process';
48
48
  import { parseArgs } from 'node:util';
49
49
  import { runAsCli } from './lib/cli-utils.js';
50
- import { PROJECT_ROOT } from './lib/config-resolver.js';
51
50
  import { gitSpawn } from './lib/git-utils.js';
52
51
  import { Logger } from './lib/Logger.js';
52
+ import { PROJECT_ROOT } from './lib/project-root.js';
53
53
  import {
54
54
  hashCommandConfig,
55
55
  recordPass,
@@ -91,7 +91,7 @@ export function renderWorkflowsDoc(catalog) {
91
91
  'Every command file lives at `.agents/workflows/<name>.md` and is projected',
92
92
  'into a flat `.claude/commands/` tree by `npm run sync:commands` (the',
93
93
  'UserPromptSubmit hook keeps it current) so it shows up as a bare `/<name>`',
94
- 'slash command (e.g. `/epic-deliver`). The projection writes only',
94
+ 'slash command (e.g. `/deliver`). The projection writes only',
95
95
  '`.claude/commands/<name>.md` — there is no plugin manifest and no',
96
96
  'marketplace listing. The commands load in every Claude Code environment.',
97
97
  '',
@@ -40,9 +40,9 @@
40
40
 
41
41
  import { parseArgs } from 'node:util';
42
42
  import { runAsCli } from './lib/cli-utils.js';
43
- import { PROJECT_ROOT } from './lib/config-resolver.js';
44
43
  import { gitSpawn } from './lib/git-utils.js';
45
44
  import { Logger } from './lib/Logger.js';
45
+ import { PROJECT_ROOT } from './lib/project-root.js';
46
46
 
47
47
  function currentBranch(cwd) {
48
48
  const res = gitSpawn(cwd, 'rev-parse', '--abbrev-ref', 'HEAD');
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * .agents/scripts/hierarchy-gate.js — Hierarchy Completeness Gate
5
5
  *
6
- * Walks the Epic's full sub-issue graph (Features → Stories) and verifies
6
+ * Walks the Epic's full sub-issue graph (Stories) and verifies
7
7
  * every descendant is closed. Where the wave gate asks "did the sprint
8
8
  * complete what it committed to?" (manifest view), this gate asks "is
9
9
  * anything still open under this Epic?" (live GitHub graph view).
@@ -16,14 +16,13 @@
16
16
  * top-level Stories outside the Epic's sub-issue graph.
17
17
  *
18
18
  * Per ticket type the rule is:
19
- * - Features — must be closed.
20
19
  * - Stories — must be closed.
21
20
  * - Auxiliary (context::prd, context::tech-spec) — ignored.
22
21
  * These are closed by the operator after the Epic PR merges, so
23
22
  * requiring them closed here would block every Epic.
24
23
  *
25
- * **3-tier hierarchy (Epic #3078).** Mandrel ships only Epic / Feature /
26
- * Story tickets. `getSubTickets(<storyId>)` returns `[]`; the walk
24
+ * **2-tier hierarchy (Story #4041).** Mandrel ships only Epic / Story
25
+ * tickets. `getSubTickets(<storyId>)` returns `[]`; the walk
27
26
  * terminates at the Story. Acceptance criteria live inline on the
28
27
  * Story body.
29
28
  *
@@ -42,11 +41,17 @@ import { resolveConfig } from './lib/config-resolver.js';
42
41
  import { Logger } from './lib/Logger.js';
43
42
  import { CONTEXT_LABELS, TYPE_LABELS } from './lib/label-constants.js';
44
43
  import { createProvider } from './lib/provider-factory.js';
44
+ import { concurrentMap } from './lib/util/concurrent-map.js';
45
+
46
+ /**
47
+ * Bounded fan-out for per-level `getSubTickets` calls. Matches the
48
+ * wave-record-io.js precedent (Story #3024).
49
+ */
50
+ const SUB_TICKET_FETCH_CONCURRENCY = 4;
45
51
 
46
52
  function classify(ticket) {
47
53
  const labels = ticket.labels ?? [];
48
54
  if (labels.includes(TYPE_LABELS.STORY)) return 'story';
49
- if (labels.includes(TYPE_LABELS.FEATURE)) return 'feature';
50
55
  if (
51
56
  labels.includes(CONTEXT_LABELS.PRD) ||
52
57
  labels.includes(CONTEXT_LABELS.TECH_SPEC)
@@ -70,22 +75,35 @@ function ticketIsComplete(ticket) {
70
75
  */
71
76
  async function collectDescendants(provider, epicId) {
72
77
  const visited = new Set([epicId]);
73
- const queue = [epicId];
74
78
  const out = [];
75
- while (queue.length > 0) {
76
- const parentId = queue.shift();
77
- let children;
78
- try {
79
- children = await provider.getSubTickets(parentId);
80
- } catch (err) {
81
- throw new Error(`getSubTickets(#${parentId}) failed: ${err.message}`);
82
- }
83
- for (const child of children) {
84
- if (visited.has(child.id)) continue;
85
- visited.add(child.id);
86
- out.push(child);
87
- queue.push(child.id);
79
+ // Level-order BFS: each round fetches the whole frontier's children with a
80
+ // bounded-parallel map instead of one awaited round-trip per node. Stories
81
+ // are leaves (no sub-issues by contract), so they are never expanded
82
+ // — that skip alone removes the largest class of wasted GraphQL calls.
83
+ let frontier = [epicId];
84
+ while (frontier.length > 0) {
85
+ const levels = await concurrentMap(
86
+ frontier,
87
+ async (parentId) => {
88
+ try {
89
+ return await provider.getSubTickets(parentId);
90
+ } catch (err) {
91
+ throw new Error(`getSubTickets(#${parentId}) failed: ${err.message}`);
92
+ }
93
+ },
94
+ { concurrency: SUB_TICKET_FETCH_CONCURRENCY },
95
+ );
96
+ const next = [];
97
+ for (const children of levels) {
98
+ for (const child of children) {
99
+ if (visited.has(child.id)) continue;
100
+ visited.add(child.id);
101
+ out.push(child);
102
+ const labels = child.labels ?? [];
103
+ if (!labels.includes(TYPE_LABELS.STORY)) next.push(child.id);
104
+ }
88
105
  }
106
+ frontier = next;
89
107
  }
90
108
  return out;
91
109
  }
@@ -106,7 +124,7 @@ export async function runHierarchyGate({ epicId, injectedProvider } = {}) {
106
124
  process.exit(2);
107
125
  }
108
126
 
109
- const failures = { feature: [], story: [], other: [] };
127
+ const failures = { story: [], other: [] };
110
128
  let auxiliaryDeferred = 0;
111
129
  for (const ticket of descendants) {
112
130
  const kind = classify(ticket);
@@ -124,15 +142,13 @@ export async function runHierarchyGate({ epicId, injectedProvider } = {}) {
124
142
  }
125
143
  }
126
144
 
127
- const totalOpen =
128
- failures.feature.length + failures.story.length + failures.other.length;
145
+ const totalOpen = failures.story.length + failures.other.length;
129
146
 
130
147
  if (totalOpen > 0) {
131
148
  Logger.error(
132
149
  `[hierarchy-gate] ❌ Hierarchy-completeness gate FAILED for Epic #${epicId}: ${totalOpen} descendant(s) incomplete.`,
133
150
  );
134
151
  const sections = [
135
- ['feature', 'Features'],
136
152
  ['story', 'Stories'],
137
153
  ['other', 'Untyped descendants'],
138
154
  ];
@@ -143,7 +159,7 @@ export async function runHierarchyGate({ epicId, injectedProvider } = {}) {
143
159
  Logger.error(` - #${item.id} (${item.reason}) — ${item.title}`);
144
160
  }
145
161
  }
146
- Logger.error('\nClose the open descendants and re-run `/epic-deliver`.');
162
+ Logger.error('\nClose the open descendants and re-run `/deliver`.');
147
163
  process.exit(1);
148
164
  }
149
165
 
@@ -154,7 +154,7 @@ export class ITicketingProvider {
154
154
  /**
155
155
  * Create a child ticket within an Epic's structural hierarchy.
156
156
  *
157
- * @param {number} parentId - GitHub Issue number of the immediate structural parent (e.g. Epic, Feature, or Story).
157
+ * @param {number} parentId - GitHub Issue number of the immediate structural parent (e.g. Epic or Story).
158
158
  * @param {{
159
159
  * epicId: number,
160
160
  * title: string,
@@ -60,7 +60,7 @@ export function matchesAnyFilePattern(patterns, files) {
60
60
  * callers MUST pass the requested Epic's own branch ref (e.g.
61
61
  * `refs/heads/epic/<id>`) so the change set is pinned to that Epic's branch
62
62
  * rather than whatever HEAD the shared checkout happens to sit on. Under two
63
- * concurrent `/epic-deliver` runs sharing one checkout, diffing against
63
+ * concurrent `/deliver` runs sharing one checkout, diffing against
64
64
  * `HEAD` silently resolves the *other* Epic's change set (Story #3362). When
65
65
  * `headRef` cannot be resolved in the repo, the selector returns a
66
66
  * `degraded: true` envelope (or hard-fails in gate-mode) instead of diffing
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * lib/audit-to-stories/seed-epic-from-findings.js
3
3
  *
4
- * Build the `--idea`-shaped seed markdown that `/epic-plan` Phase 1
4
+ * Build the `--idea`-shaped seed markdown that `/plan` Phase 1
5
5
  * consumes when the operator picks the Single-Epic grouping mode.
6
6
  *
7
7
  * The seed renders the canonical one-pager sections so the idea-refinement
@@ -10,7 +10,7 @@
10
10
  * - Recommended Direction (rollup of recommendations by dimension)
11
11
  * - Key Assumptions (carries the source-report links forward)
12
12
  * - MVP Scope (the proposed Stories, one bullet per group)
13
- * - Key Files (explicit file paths so /epic-plan Phase 7 decompose
13
+ * - Key Files (explicit file paths so /plan Phase 7 decompose
14
14
  * has concrete anchors)
15
15
  * - Not Doing (out-of-scope items by convention)
16
16
  *
@@ -28,8 +28,8 @@ import { calculateAll, scanDirectory } from './maintainability-utils.js';
28
28
  * baseline-snapshot.js — per-Epic baseline lifecycle helpers.
29
29
  *
30
30
  * Story #1396 (Epic #1386). The Epic-snapshot scheme freezes the maintainability
31
- * and crap baselines at /epic-plan time and reconciles them back to `main`
32
- * at /epic-deliver time. Two helpers, both pure-ish (deterministic given the
31
+ * and crap baselines at /plan time and reconciles them back to `main`
32
+ * at /deliver time. Two helpers, both pure-ish (deterministic given the
33
33
  * working tree + injected I/O):
34
34
  *
35
35
  * - forkMainToEpic({ epicId, cwd }) — copies the tracked main baselines
@@ -37,20 +37,20 @@ import { calculateAll, scanDirectory } from './maintainability-utils.js';
37
37
  * source content produces the same destination bytes (no fs churn). When
38
38
  * the source baseline is missing, emits a warn through the injected
39
39
  * logger and returns `{ written: false, reason: 'source-missing' }` for
40
- * that file — callers (e.g. /epic-plan Phase 7) treat the absence as
40
+ * that file — callers (e.g. /plan Phase 7) treat the absence as
41
41
  * non-fatal and stay in `--full-scope` mode.
42
42
  *
43
43
  * - regenerateMainFromTree({ cwd }) — re-scores maintainability + crap
44
44
  * against the current working tree and writes the result to the tracked
45
45
  * main baseline paths. Returns `{ didChange, paths }` where `didChange`
46
46
  * is true iff any baseline file's content differs from what's already on
47
- * disk. Callers in /epic-deliver use `didChange === false` to skip the
47
+ * disk. Callers in /deliver use `didChange === false` to skip the
48
48
  * `baseline-refresh: epic-<id>` commit.
49
49
  *
50
50
  * Lifecycle note (Story #1467): per-epic ratchet snapshots are ephemeral
51
51
  * scratch state under the `temp/epic-<id>/baselines/` namespace, NOT committed
52
52
  * artifacts. They inherit the existing per-epic temp-tree cleanup contract —
53
- * `/epic-deliver` reaps the parent `temp/epic-<id>/` directory on merge, so
53
+ * `/deliver` reaps the parent `temp/epic-<id>/` directory on merge, so
54
54
  * no manual prune is required. Earlier versions of this module wrote under
55
55
  * `baselines/epic/<id>/`, which committed them to git and accumulated obsolete
56
56
  * snapshots forever.
@@ -103,7 +103,7 @@ export function epicSnapshotPathFor({ epicId, kind, cwd = process.cwd() }) {
103
103
  * - Source baseline missing → returned per-file `{ written: false,
104
104
  * reason: 'source-missing' }`. Logger warn fires once per missing file.
105
105
  * Caller stays in `--full-scope` mode.
106
- * - Source unreadable / not parseable → throws. Re-running /epic-plan
106
+ * - Source unreadable / not parseable → throws. Re-running /plan
107
107
  * with `--force` after fixing the source recovers.
108
108
  *
109
109
  * @param {{
@@ -428,7 +428,7 @@ export function commitSnapshotsToEpicBranch({
428
428
  * via `delivery.quality.gates.crap.coveragePath`. When coverage is missing and
429
429
  * `requireCoverage` is true, the crap regeneration is skipped (didChange stays
430
430
  * false for that file) and a warn is emitted — the operator is expected to run
431
- * `npm run test:coverage` before /epic-deliver if a refresh is anticipated.
431
+ * `npm run test:coverage` before /deliver if a refresh is anticipated.
432
432
  *
433
433
  * @param {{
434
434
  * cwd?: string,
@@ -7,19 +7,22 @@
7
7
  * `*` key and each component. Per-row denominators land later when the
8
8
  * components resolver and per-component weighting (Story #1902, #1919)
9
9
  * arrive; the rollup signature is stable so callers don't churn.
10
+ *
11
+ * Higher percentages are better. New paths land in the `additions` bucket
12
+ * (Story #2012 — partial coverage on a new file must never flip to a
13
+ * regression); removed paths inherit a perfect head row so any lower base
14
+ * registers as an improvement. Scaffold (sortRows / rollup / compare /
15
+ * applyEpsilon / mergeRows) is generated by `makeBaselineKind`
16
+ * (Story #3983).
10
17
  */
11
18
 
12
- import { componentMatches } from '../component-matcher.js';
13
19
  import { canonicalise } from '../path-canon.js';
14
- import { mergeRowsByScope } from '../scope.js';
20
+ import { makeBaselineKind } from './kind-factory.js';
15
21
 
16
22
  export const name = 'coverage';
17
23
  export const keyField = 'path';
18
- const KERNEL_VERSION = '1.0.0';
19
24
 
20
- export function kernelVersion() {
21
- return KERNEL_VERSION;
22
- }
25
+ const COV_AXES = ['lines', 'branches', 'functions'];
23
26
 
24
27
  export function projectRow(row) {
25
28
  return {
@@ -35,160 +38,41 @@ function roundPct(v) {
35
38
  return Number(v.toFixed(2));
36
39
  }
37
40
 
38
- export function sortRows(rows) {
39
- return [...rows].sort((a, b) => a.path.localeCompare(b.path));
41
+ function meanOf(rows, axis) {
42
+ let sum = 0;
43
+ for (const r of rows) sum += r[axis] ?? 0;
44
+ return Number((sum / rows.length).toFixed(2));
40
45
  }
41
46
 
42
47
  function aggregate(rows) {
43
48
  if (!rows || rows.length === 0) {
44
49
  return { lines: 0, branches: 0, functions: 0 };
45
50
  }
46
- let l = 0;
47
- let b = 0;
48
- let f = 0;
49
- for (const row of rows) {
50
- l += row.lines ?? 0;
51
- b += row.branches ?? 0;
52
- f += row.functions ?? 0;
53
- }
54
51
  return {
55
- lines: Number((l / rows.length).toFixed(2)),
56
- branches: Number((b / rows.length).toFixed(2)),
57
- functions: Number((f / rows.length).toFixed(2)),
52
+ lines: meanOf(rows, 'lines'),
53
+ branches: meanOf(rows, 'branches'),
54
+ functions: meanOf(rows, 'functions'),
58
55
  };
59
56
  }
60
57
 
61
- export function rollup(rows, components = []) {
62
- const out = { '*': aggregate(rows) };
63
- for (const c of components ?? []) {
64
- const matched = (rows ?? []).filter((r) => componentMatches(c, r.path));
65
- out[c.name] = aggregate(matched);
66
- }
67
- return out;
68
- }
69
-
70
- /**
71
- * Pure compare(head, base) for the coverage kind. Diffs rows by `path`.
72
- * Higher percentages are better — a row regresses if any axis (lines,
73
- * branches, functions) drops vs base; improves if any axis rises with no
74
- * axis dropping; unchanged otherwise. New paths (head has a row that
75
- * base lacks) land in the `additions` bucket; absolute-floor enforcement
76
- * is the unified `check-baselines` gate's job and runs independently.
77
- * Removed paths inherit a head of 100% so any lower base registers as
78
- * an improvement.
79
- *
80
- * Story #2012 — sibling fix to maintainability.compare. The prior
81
- * behaviour treated new paths as base=100% on every axis, so any partial
82
- * coverage on a new file flipped to a regression.
83
- *
84
- * No I/O. No process exit. No friction emission.
85
- */
86
- const COV_AXES = ['lines', 'branches', 'functions'];
87
-
88
- export function compare(head, base) {
89
- const headRows = Array.isArray(head?.rows) ? head.rows : [];
90
- const baseRows = Array.isArray(base?.rows) ? base.rows : [];
91
- const baseByKey = new Map();
92
- for (const r of baseRows) baseByKey.set(r.path, r);
93
- const seen = new Set();
94
- const regressions = [];
95
- const improvements = [];
96
- const unchanged = [];
97
- const additions = [];
98
- for (const h of headRows) {
99
- seen.add(h.path);
100
- const b = baseByKey.get(h.path);
101
- if (!b) {
102
- additions.push({ key: h.path, head: h, base: null });
103
- continue;
104
- }
105
- classifyCoverage(regressions, improvements, unchanged, h.path, h, b);
106
- }
107
- for (const b of baseRows) {
108
- if (seen.has(b.path)) continue;
109
- const h = perfectCoverageRow(b.path);
110
- classifyCoverage(regressions, improvements, unchanged, b.path, h, b);
111
- }
112
- return { regressions, improvements, unchanged, additions };
113
- }
114
-
115
58
  function perfectCoverageRow(path) {
116
59
  return { path, lines: 100, branches: 100, functions: 100 };
117
60
  }
118
61
 
119
- function classifyCoverage(
120
- regressions,
121
- improvements,
122
- unchanged,
123
- key,
124
- head,
125
- base,
126
- ) {
127
- let down = false;
128
- let up = false;
129
- for (const axis of COV_AXES) {
130
- const delta = (head[axis] ?? 0) - (base[axis] ?? 0);
131
- if (delta < 0) down = true;
132
- else if (delta > 0) up = true;
133
- }
134
- if (down) regressions.push({ key, head, base });
135
- else if (up) improvements.push({ key, head, base });
136
- else unchanged.push({ key, head, base });
137
- }
138
-
139
- /**
140
- * Pure stabilizer for s-stability-epsilon (Story #1964). Folds sub-epsilon
141
- * row deltas back to the prior bytes so env variance (e.g. ±0.05% jitter
142
- * on a coverage axis) does not rewrite the on-disk baseline.
143
- *
144
- * For coverage, the comparison metric is the maximum |delta| across the
145
- * three axes (lines, branches, functions). When the prior row exists and
146
- * every axis delta is within `epsilon`, the prior row is returned
147
- * verbatim; otherwise the regenerated row wins. Missing-prior rows always
148
- * fall through to the regenerated row.
149
- *
150
- * No I/O. No mutation of inputs.
151
- *
152
- * @param {Array<{path: string, lines: number, branches: number, functions: number}>} prior
153
- * @param {Array<{path: string, lines: number, branches: number, functions: number}>} regenerated
154
- * @param {number} epsilon non-negative absolute tolerance (percentage points)
155
- * @returns {Array<object>}
156
- */
157
- export function applyEpsilon(prior, regenerated, epsilon) {
158
- const priorRows = Array.isArray(prior) ? prior : [];
159
- const regenRows = Array.isArray(regenerated) ? regenerated : [];
160
- const eps = Number.isFinite(epsilon) && epsilon >= 0 ? epsilon : 0;
161
- const priorByKey = new Map();
162
- for (const r of priorRows) priorByKey.set(r.path, r);
163
- return regenRows.map((row) => {
164
- const p = priorByKey.get(row.path);
165
- if (!p) return row;
166
- let maxAxisDelta = 0;
167
- for (const axis of COV_AXES) {
168
- const d = Math.abs((row[axis] ?? 0) - (p[axis] ?? 0));
169
- if (d > maxAxisDelta) maxAxisDelta = d;
170
- }
171
- return maxAxisDelta <= eps ? p : row;
172
- });
173
- }
174
-
175
- /**
176
- * Pure scope-aware merge for s-diff-scoped-writes (Story #1974). Coverage
177
- * rows match by `path`. In diff mode, rows whose `path` is OUTSIDE
178
- * `scope.files` are preserved from `prior` verbatim; in-scope rows come
179
- * from `regenerated`. In full mode (or no scope), regenerated wins
180
- * everywhere. Pure; downstream `sortRows` re-sorts before write.
181
- *
182
- * @param {Array<{path: string, lines: number, branches: number, functions: number}>} prior
183
- * @param {Array<{path: string, lines: number, branches: number, functions: number}>} regenerated
184
- * @param {{mode: 'full'|'diff', files: Set<string>}|null|undefined} scope
185
- * @returns {Array<object>}
186
- */
187
- export function mergeRows(prior, regenerated, scope) {
188
- return mergeRowsByScope({
189
- prior,
190
- regenerated,
191
- scope,
192
- scopeKey: (row) => row.path,
193
- });
194
- }
62
+ export const {
63
+ kernelVersion,
64
+ sortRows,
65
+ rollup,
66
+ compare,
67
+ applyEpsilon,
68
+ mergeRows,
69
+ } = makeBaselineKind({
70
+ keyField,
71
+ kernelVersion: '1.0.0',
72
+ axes: COV_AXES,
73
+ betterWhen: 'higher',
74
+ aggregate,
75
+ missingBasePolicy: 'addition',
76
+ removedRowPolicy: { kind: 'perfect-head' },
77
+ perfectRow: perfectCoverageRow,
78
+ });