mandrel 1.57.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 (843) hide show
  1. package/.agents/README.md +954 -0
  2. package/.agents/docs/SDLC.md +1420 -0
  3. package/.agents/docs/agentrc-reference.json +278 -0
  4. package/.agents/docs/configuration.md +1040 -0
  5. package/.agents/docs/workflows.md +59 -0
  6. package/.agents/instructions.md +384 -0
  7. package/.agents/personas/architect.md +107 -0
  8. package/.agents/personas/devops-engineer.md +36 -0
  9. package/.agents/personas/engineer-mobile.md +119 -0
  10. package/.agents/personas/engineer-web.md +110 -0
  11. package/.agents/personas/engineer.md +90 -0
  12. package/.agents/personas/product.md +88 -0
  13. package/.agents/personas/project-manager.md +110 -0
  14. package/.agents/personas/qa-engineer.md +91 -0
  15. package/.agents/personas/refactorer.md +110 -0
  16. package/.agents/personas/security-engineer.md +112 -0
  17. package/.agents/personas/sre.md +86 -0
  18. package/.agents/personas/technical-writer.md +100 -0
  19. package/.agents/personas/ux-designer.md +95 -0
  20. package/.agents/rules/api-conventions.md +75 -0
  21. package/.agents/rules/changelog-style.md +238 -0
  22. package/.agents/rules/gherkin-standards.md +146 -0
  23. package/.agents/rules/git-conventions.md +146 -0
  24. package/.agents/rules/orchestration-error-handling.md +35 -0
  25. package/.agents/rules/security-baseline.md +92 -0
  26. package/.agents/rules/shell-conventions.md +70 -0
  27. package/.agents/rules/test-seams.md +59 -0
  28. package/.agents/rules/testing-standards.md +177 -0
  29. package/.agents/runtime-deps.json +18 -0
  30. package/.agents/schemas/acceptance-eval-verdict.schema.json +93 -0
  31. package/.agents/schemas/agentrc.schema.json +1583 -0
  32. package/.agents/schemas/audit-results.schema.json +69 -0
  33. package/.agents/schemas/audit-rules.json +134 -0
  34. package/.agents/schemas/audit-rules.schema.json +69 -0
  35. package/.agents/schemas/baselines/baseline-envelope.schema.json +44 -0
  36. package/.agents/schemas/baselines/bundle-size.schema.json +47 -0
  37. package/.agents/schemas/baselines/coverage.schema.json +50 -0
  38. package/.agents/schemas/baselines/crap.schema.json +52 -0
  39. package/.agents/schemas/baselines/duplication.schema.json +62 -0
  40. package/.agents/schemas/baselines/lighthouse.schema.json +59 -0
  41. package/.agents/schemas/baselines/lint.schema.json +47 -0
  42. package/.agents/schemas/baselines/maintainability.schema.json +71 -0
  43. package/.agents/schemas/baselines/mutation.schema.json +52 -0
  44. package/.agents/schemas/crap-baseline.schema.json +57 -0
  45. package/.agents/schemas/crap-report.schema.json +102 -0
  46. package/.agents/schemas/dispatch-manifest.json +232 -0
  47. package/.agents/schemas/epic-perf-report.schema.json +89 -0
  48. package/.agents/schemas/epic-spec.schema.json +183 -0
  49. package/.agents/schemas/friction-event.schema.json +56 -0
  50. package/.agents/schemas/lifecycle/README.md +18 -0
  51. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +13 -0
  52. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +13 -0
  53. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +13 -0
  54. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +12 -0
  55. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +13 -0
  56. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +13 -0
  57. package/.agents/schemas/lifecycle/close-validate.end.schema.json +18 -0
  58. package/.agents/schemas/lifecycle/close-validate.start.schema.json +13 -0
  59. package/.agents/schemas/lifecycle/code-review.end.schema.json +30 -0
  60. package/.agents/schemas/lifecycle/code-review.start.schema.json +12 -0
  61. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +14 -0
  62. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +13 -0
  63. package/.agents/schemas/lifecycle/epic.blocked.schema.json +13 -0
  64. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +12 -0
  65. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +12 -0
  66. package/.agents/schemas/lifecycle/epic.close.end.schema.json +12 -0
  67. package/.agents/schemas/lifecycle/epic.complete.schema.json +13 -0
  68. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +13 -0
  69. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +12 -0
  70. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +13 -0
  71. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +14 -0
  72. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +17 -0
  73. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +15 -0
  74. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +18 -0
  75. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +12 -0
  76. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +16 -0
  77. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +12 -0
  78. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +28 -0
  79. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +16 -0
  80. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +15 -0
  81. package/.agents/schemas/lifecycle/ledger-record.schema.json +59 -0
  82. package/.agents/schemas/lifecycle/notification.emitted.schema.json +18 -0
  83. package/.agents/schemas/lifecycle/pr.created.schema.json +14 -0
  84. package/.agents/schemas/lifecycle/retro.end.schema.json +16 -0
  85. package/.agents/schemas/lifecycle/retro.start.schema.json +12 -0
  86. package/.agents/schemas/lifecycle/story.blocked.schema.json +13 -0
  87. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +17 -0
  88. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +15 -0
  89. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +20 -0
  90. package/.agents/schemas/lifecycle/story.merged.schema.json +13 -0
  91. package/.agents/schemas/mi-report.schema.json +58 -0
  92. package/.agents/schemas/model-attribution.schema.json +49 -0
  93. package/.agents/schemas/qa-finding.schema.json +133 -0
  94. package/.agents/schemas/qa-ledger.schema.json +89 -0
  95. package/.agents/schemas/risk-verdict.schema.json +53 -0
  96. package/.agents/schemas/signal-event.schema.json +58 -0
  97. package/.agents/schemas/skill.schema.json +31 -0
  98. package/.agents/schemas/skills-index.schema.json +81 -0
  99. package/.agents/schemas/story-perf-summary.schema.json +73 -0
  100. package/.agents/schemas/validation-evidence.schema.json +78 -0
  101. package/.agents/scripts/README.md +93 -0
  102. package/.agents/scripts/acceptance-eval.js +284 -0
  103. package/.agents/scripts/acceptance-spec-reconciler.js +556 -0
  104. package/.agents/scripts/agents-bootstrap-github.js +634 -0
  105. package/.agents/scripts/analyze-execution.js +369 -0
  106. package/.agents/scripts/assert-branch.js +83 -0
  107. package/.agents/scripts/audit-labels-bootstrap.js +253 -0
  108. package/.agents/scripts/audit-to-stories.js +257 -0
  109. package/.agents/scripts/bootstrap.js +1378 -0
  110. package/.agents/scripts/check-baselines.js +81 -0
  111. package/.agents/scripts/check-dead-exports.js +311 -0
  112. package/.agents/scripts/check-doc-links.js +401 -0
  113. package/.agents/scripts/check-gherkin-placeholders.js +663 -0
  114. package/.agents/scripts/check-lifecycle-doc-drift.js +402 -0
  115. package/.agents/scripts/check-lifecycle-lint.js +379 -0
  116. package/.agents/scripts/check-prepush-recovery.js +90 -0
  117. package/.agents/scripts/check-windows-git-perf.js +138 -0
  118. package/.agents/scripts/cleanup-repo-test-temp.js +67 -0
  119. package/.agents/scripts/coverage-capture.js +112 -0
  120. package/.agents/scripts/detect-merges.js +111 -0
  121. package/.agents/scripts/diagnose-friction.js +257 -0
  122. package/.agents/scripts/diagnose.js +240 -0
  123. package/.agents/scripts/dispatcher.js +295 -0
  124. package/.agents/scripts/drain-pending-cleanup.js +147 -0
  125. package/.agents/scripts/epic-audit-prepare.js +419 -0
  126. package/.agents/scripts/epic-audit-recheck.js +241 -0
  127. package/.agents/scripts/epic-deliver-note-intervention.js +192 -0
  128. package/.agents/scripts/epic-deliver-preflight.js +407 -0
  129. package/.agents/scripts/epic-deliver-prepare.js +383 -0
  130. package/.agents/scripts/epic-execute-record-wave.js +463 -0
  131. package/.agents/scripts/epic-plan-clarity.js +201 -0
  132. package/.agents/scripts/epic-plan-decompose.js +79 -0
  133. package/.agents/scripts/epic-plan-healthcheck.js +363 -0
  134. package/.agents/scripts/epic-plan-spec-validate.js +111 -0
  135. package/.agents/scripts/epic-plan-spec.js +198 -0
  136. package/.agents/scripts/epic-reconcile.js +637 -0
  137. package/.agents/scripts/evidence-gate.js +235 -0
  138. package/.agents/scripts/generate-config-docs.js +516 -0
  139. package/.agents/scripts/generate-lifecycle-docs.js +224 -0
  140. package/.agents/scripts/generate-skills-index.js +252 -0
  141. package/.agents/scripts/generate-workflows-doc.js +168 -0
  142. package/.agents/scripts/git-cleanup.js +124 -0
  143. package/.agents/scripts/git-pr-quality-gate.js +203 -0
  144. package/.agents/scripts/git-rebase-and-resolve.js +234 -0
  145. package/.agents/scripts/hierarchy-gate.js +176 -0
  146. package/.agents/scripts/hydrate-context.js +179 -0
  147. package/.agents/scripts/install-matrix-assert.js +282 -0
  148. package/.agents/scripts/lib/Graph.js +326 -0
  149. package/.agents/scripts/lib/ITicketingProvider.js +349 -0
  150. package/.agents/scripts/lib/Logger.js +194 -0
  151. package/.agents/scripts/lib/audit-suite/cli.js +64 -0
  152. package/.agents/scripts/lib/audit-suite/findings.js +164 -0
  153. package/.agents/scripts/lib/audit-suite/frontmatter-lint.js +32 -0
  154. package/.agents/scripts/lib/audit-suite/frontmatter.js +110 -0
  155. package/.agents/scripts/lib/audit-suite/index.js +22 -0
  156. package/.agents/scripts/lib/audit-suite/runner.js +233 -0
  157. package/.agents/scripts/lib/audit-suite/selector.js +235 -0
  158. package/.agents/scripts/lib/audit-suite/substitutions.js +124 -0
  159. package/.agents/scripts/lib/audit-suite/workflow-loader.js +49 -0
  160. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +130 -0
  161. package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +114 -0
  162. package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +93 -0
  163. package/.agents/scripts/lib/audit-to-stories/group-findings.js +265 -0
  164. package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +246 -0
  165. package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +160 -0
  166. package/.agents/scripts/lib/auto-refresh-baselines.js +308 -0
  167. package/.agents/scripts/lib/baseline-loader.js +0 -0
  168. package/.agents/scripts/lib/baseline-schema-registry.js +69 -0
  169. package/.agents/scripts/lib/baseline-snapshot.js +716 -0
  170. package/.agents/scripts/lib/baselines/component-matcher.js +21 -0
  171. package/.agents/scripts/lib/baselines/components.js +126 -0
  172. package/.agents/scripts/lib/baselines/diff-scope-cli.js +203 -0
  173. package/.agents/scripts/lib/baselines/duplication-scanner.js +220 -0
  174. package/.agents/scripts/lib/baselines/env-overrides.js +129 -0
  175. package/.agents/scripts/lib/baselines/envelope.js +368 -0
  176. package/.agents/scripts/lib/baselines/exit-codes.js +89 -0
  177. package/.agents/scripts/lib/baselines/git-base.js +0 -0
  178. package/.agents/scripts/lib/baselines/kernel.js +111 -0
  179. package/.agents/scripts/lib/baselines/kinds/_shared-metric.js +220 -0
  180. package/.agents/scripts/lib/baselines/kinds/bundle-size.js +157 -0
  181. package/.agents/scripts/lib/baselines/kinds/coverage.js +194 -0
  182. package/.agents/scripts/lib/baselines/kinds/crap.js +555 -0
  183. package/.agents/scripts/lib/baselines/kinds/duplication.js +197 -0
  184. package/.agents/scripts/lib/baselines/kinds/lighthouse.js +185 -0
  185. package/.agents/scripts/lib/baselines/kinds/lint.js +172 -0
  186. package/.agents/scripts/lib/baselines/kinds/maintainability.js +340 -0
  187. package/.agents/scripts/lib/baselines/kinds/mutation.js +153 -0
  188. package/.agents/scripts/lib/baselines/path-canon.js +279 -0
  189. package/.agents/scripts/lib/baselines/preview-gates.js +298 -0
  190. package/.agents/scripts/lib/baselines/reader.js +321 -0
  191. package/.agents/scripts/lib/baselines/refresh-service.js +733 -0
  192. package/.agents/scripts/lib/baselines/scope.js +291 -0
  193. package/.agents/scripts/lib/baselines/writer.js +312 -0
  194. package/.agents/scripts/lib/bdd-runner-detect.js +417 -0
  195. package/.agents/scripts/lib/bdd-scenario-scanner.js +310 -0
  196. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +202 -0
  197. package/.agents/scripts/lib/bootstrap/branch-protection.js +222 -0
  198. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +171 -0
  199. package/.agents/scripts/lib/bootstrap/commit-push.js +146 -0
  200. package/.agents/scripts/lib/bootstrap/gh-list.js +153 -0
  201. package/.agents/scripts/lib/bootstrap/gh-preflight.js +306 -0
  202. package/.agents/scripts/lib/bootstrap/hitl-confirm.js +89 -0
  203. package/.agents/scripts/lib/bootstrap/install-ledger.js +174 -0
  204. package/.agents/scripts/lib/bootstrap/manifest.js +272 -0
  205. package/.agents/scripts/lib/bootstrap/merge-methods.js +108 -0
  206. package/.agents/scripts/lib/bootstrap/preflight.js +195 -0
  207. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +801 -0
  208. package/.agents/scripts/lib/bootstrap/prompt.js +480 -0
  209. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +370 -0
  210. package/.agents/scripts/lib/bootstrap/summary.js +75 -0
  211. package/.agents/scripts/lib/bootstrap/workflow-audit.js +256 -0
  212. package/.agents/scripts/lib/branch-name-guard.js +98 -0
  213. package/.agents/scripts/lib/c8-cli-path.js +21 -0
  214. package/.agents/scripts/lib/changed-files.js +184 -0
  215. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +104 -0
  216. package/.agents/scripts/lib/checks/core-bare-clean.js +48 -0
  217. package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +54 -0
  218. package/.agents/scripts/lib/checks/index.js +288 -0
  219. package/.agents/scripts/lib/checks/push-hook-parity.js +106 -0
  220. package/.agents/scripts/lib/checks/stale-origin-epic.js +49 -0
  221. package/.agents/scripts/lib/checks/state.js +558 -0
  222. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +186 -0
  223. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +182 -0
  224. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +92 -0
  225. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +81 -0
  226. package/.agents/scripts/lib/checks/worktree-residue-biome.js +55 -0
  227. package/.agents/scripts/lib/cli/parse-numeric.js +60 -0
  228. package/.agents/scripts/lib/cli/standard-args.js +351 -0
  229. package/.agents/scripts/lib/cli-args.js +286 -0
  230. package/.agents/scripts/lib/cli-utils.js +69 -0
  231. package/.agents/scripts/lib/close-validation/projections/head-sha.js +44 -0
  232. package/.agents/scripts/lib/close-validation/projections/inputs.js +86 -0
  233. package/.agents/scripts/lib/close-validation/projections/maintainability.js +286 -0
  234. package/.agents/scripts/lib/close-validation.js +897 -0
  235. package/.agents/scripts/lib/codebase-snapshot.js +513 -0
  236. package/.agents/scripts/lib/command-header.js +33 -0
  237. package/.agents/scripts/lib/config/acceptance-eval.js +95 -0
  238. package/.agents/scripts/lib/config/baselines.js +60 -0
  239. package/.agents/scripts/lib/config/ci.js +30 -0
  240. package/.agents/scripts/lib/config/commands.js +36 -0
  241. package/.agents/scripts/lib/config/defaults.js +119 -0
  242. package/.agents/scripts/lib/config/explain.js +348 -0
  243. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +23 -0
  244. package/.agents/scripts/lib/config/gates/coverage.schema.js +18 -0
  245. package/.agents/scripts/lib/config/gates/crap.schema.js +33 -0
  246. package/.agents/scripts/lib/config/gates/duplication.schema.js +26 -0
  247. package/.agents/scripts/lib/config/gates/index.js +36 -0
  248. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +23 -0
  249. package/.agents/scripts/lib/config/gates/lint.schema.js +9 -0
  250. package/.agents/scripts/lib/config/gates/maintainability.schema.js +20 -0
  251. package/.agents/scripts/lib/config/gates/mutation.schema.js +12 -0
  252. package/.agents/scripts/lib/config/gates/shared.js +117 -0
  253. package/.agents/scripts/lib/config/github.js +122 -0
  254. package/.agents/scripts/lib/config/lifecycle.js +40 -0
  255. package/.agents/scripts/lib/config/limits.js +211 -0
  256. package/.agents/scripts/lib/config/paths.js +73 -0
  257. package/.agents/scripts/lib/config/preflight.js +58 -0
  258. package/.agents/scripts/lib/config/quality.js +665 -0
  259. package/.agents/scripts/lib/config/retro.js +77 -0
  260. package/.agents/scripts/lib/config/runners.js +105 -0
  261. package/.agents/scripts/lib/config/runtime.js +167 -0
  262. package/.agents/scripts/lib/config/shared.js +46 -0
  263. package/.agents/scripts/lib/config/sync-agentrc.js +243 -0
  264. package/.agents/scripts/lib/config/temp-paths.js +373 -0
  265. package/.agents/scripts/lib/config/validate-orchestration.js +81 -0
  266. package/.agents/scripts/lib/config/worktree-isolation.js +80 -0
  267. package/.agents/scripts/lib/config-resolver.js +298 -0
  268. package/.agents/scripts/lib/config-schema-shared.js +32 -0
  269. package/.agents/scripts/lib/config-schema.js +20 -0
  270. package/.agents/scripts/lib/config-settings-schema-delivery.js +332 -0
  271. package/.agents/scripts/lib/config-settings-schema-quality.js +165 -0
  272. package/.agents/scripts/lib/config-settings-schema.js +420 -0
  273. package/.agents/scripts/lib/coverage-baseline.js +352 -0
  274. package/.agents/scripts/lib/coverage-capture.js +195 -0
  275. package/.agents/scripts/lib/coverage-utils.js +239 -0
  276. package/.agents/scripts/lib/cpu-pool.js +223 -0
  277. package/.agents/scripts/lib/crap-engine.js +119 -0
  278. package/.agents/scripts/lib/crap-utils.js +479 -0
  279. package/.agents/scripts/lib/degraded-mode.js +69 -0
  280. package/.agents/scripts/lib/dependency-parser.js +129 -0
  281. package/.agents/scripts/lib/duplicate-search.js +189 -0
  282. package/.agents/scripts/lib/dynamic-workflow/architecture-report-contract.js +70 -0
  283. package/.agents/scripts/lib/dynamic-workflow/audit-orchestrator.js +197 -0
  284. package/.agents/scripts/lib/dynamic-workflow/capability.js +396 -0
  285. package/.agents/scripts/lib/dynamic-workflow/clean-code-report-contract.js +80 -0
  286. package/.agents/scripts/lib/dynamic-workflow/performance-report-contract.js +72 -0
  287. package/.agents/scripts/lib/dynamic-workflow/quality-report-contract.js +90 -0
  288. package/.agents/scripts/lib/dynamic-workflow/report-contract-core.js +43 -0
  289. package/.agents/scripts/lib/dynamic-workflow/security-report-contract.js +83 -0
  290. package/.agents/scripts/lib/env-loader.js +52 -0
  291. package/.agents/scripts/lib/epic-merge-lock.js +239 -0
  292. package/.agents/scripts/lib/epic-plan-clarity.js +142 -0
  293. package/.agents/scripts/lib/epic-plan-ideation.js +228 -0
  294. package/.agents/scripts/lib/error-redactor.js +125 -0
  295. package/.agents/scripts/lib/errors/index.js +67 -0
  296. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +230 -0
  297. package/.agents/scripts/lib/feedback-loop/code-review-graduator.js +207 -0
  298. package/.agents/scripts/lib/feedback-loop/graduator-core.js +421 -0
  299. package/.agents/scripts/lib/feedback-loop/memory-freshness.js +480 -0
  300. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +229 -0
  301. package/.agents/scripts/lib/findings/classify-finding.js +195 -0
  302. package/.agents/scripts/lib/findings/promote-finding.js +353 -0
  303. package/.agents/scripts/lib/findings/route-finding.js +283 -0
  304. package/.agents/scripts/lib/findings/semantic-issue-search.js +179 -0
  305. package/.agents/scripts/lib/findings/severity.js +102 -0
  306. package/.agents/scripts/lib/gates/baseline-store.js +106 -0
  307. package/.agents/scripts/lib/gates/friction.js +43 -0
  308. package/.agents/scripts/lib/gh-exec.js +553 -0
  309. package/.agents/scripts/lib/git/cached-fetch.js +0 -0
  310. package/.agents/scripts/lib/git/sync-from-base.js +162 -0
  311. package/.agents/scripts/lib/git-branch-cleanup.js +213 -0
  312. package/.agents/scripts/lib/git-branch-lifecycle.js +353 -0
  313. package/.agents/scripts/lib/git-merge-orchestrator.js +261 -0
  314. package/.agents/scripts/lib/git-utils.js +363 -0
  315. package/.agents/scripts/lib/github-url.js +29 -0
  316. package/.agents/scripts/lib/install-cmd-parser.js +51 -0
  317. package/.agents/scripts/lib/issue-link-parser.js +74 -0
  318. package/.agents/scripts/lib/json-utils.js +60 -0
  319. package/.agents/scripts/lib/label-constants.js +169 -0
  320. package/.agents/scripts/lib/label-taxonomy.js +200 -0
  321. package/.agents/scripts/lib/maintainability-engine.js +164 -0
  322. package/.agents/scripts/lib/maintainability-utils.js +343 -0
  323. package/.agents/scripts/lib/mandrel-catalog.js +170 -0
  324. package/.agents/scripts/lib/mutation/baseline-snapshot.js +238 -0
  325. package/.agents/scripts/lib/mutation/config-detector.js +119 -0
  326. package/.agents/scripts/lib/mutation/stryker-runner.js +306 -0
  327. package/.agents/scripts/lib/mutation/survivor-report.js +160 -0
  328. package/.agents/scripts/lib/notifications/notifier.js +75 -0
  329. package/.agents/scripts/lib/observability/active-story-env.js +182 -0
  330. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +221 -0
  331. package/.agents/scripts/lib/observability/perf-aggregator.js +887 -0
  332. package/.agents/scripts/lib/observability/perf-report-readers.js +319 -0
  333. package/.agents/scripts/lib/observability/perf-report-render.js +182 -0
  334. package/.agents/scripts/lib/observability/signals-writer.js +296 -0
  335. package/.agents/scripts/lib/observability/source-classifier.js +103 -0
  336. package/.agents/scripts/lib/observability/tool-trace-hook.js +417 -0
  337. package/.agents/scripts/lib/onboard/detect-stack.js +300 -0
  338. package/.agents/scripts/lib/onboard/scaffold-docs.js +128 -0
  339. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +173 -0
  340. package/.agents/scripts/lib/orchestration/cascade-grouping.js +275 -0
  341. package/.agents/scripts/lib/orchestration/check-baselines/phases/compare.js +131 -0
  342. package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +80 -0
  343. package/.agents/scripts/lib/orchestration/check-baselines/phases/floors.js +132 -0
  344. package/.agents/scripts/lib/orchestration/check-baselines/phases/friction.js +142 -0
  345. package/.agents/scripts/lib/orchestration/check-baselines/phases/parse-args.js +149 -0
  346. package/.agents/scripts/lib/orchestration/check-baselines/phases/pipeline.js +158 -0
  347. package/.agents/scripts/lib/orchestration/check-baselines/phases/report.js +56 -0
  348. package/.agents/scripts/lib/orchestration/code-review.js +652 -0
  349. package/.agents/scripts/lib/orchestration/column-sync.js +286 -0
  350. package/.agents/scripts/lib/orchestration/context-envelope.js +280 -0
  351. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +581 -0
  352. package/.agents/scripts/lib/orchestration/dependency-analyzer.js +88 -0
  353. package/.agents/scripts/lib/orchestration/detectors-phase.js +188 -0
  354. package/.agents/scripts/lib/orchestration/dispatch-engine.js +144 -0
  355. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +206 -0
  356. package/.agents/scripts/lib/orchestration/doc-reader.js +94 -0
  357. package/.agents/scripts/lib/orchestration/epic-cleanup.js +473 -0
  358. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +310 -0
  359. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/cli.js +167 -0
  360. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +151 -0
  361. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +74 -0
  362. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +78 -0
  363. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +72 -0
  364. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +155 -0
  365. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +321 -0
  366. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +75 -0
  367. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +86 -0
  368. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +235 -0
  369. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +197 -0
  370. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/cli-args.js +48 -0
  371. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +94 -0
  372. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +414 -0
  373. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +55 -0
  374. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/risk-verdict.js +105 -0
  375. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +235 -0
  376. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +120 -0
  377. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +118 -0
  378. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +295 -0
  379. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +186 -0
  380. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +50 -0
  381. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +146 -0
  382. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +110 -0
  383. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +392 -0
  384. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +217 -0
  385. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +235 -0
  386. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter.js +69 -0
  387. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +32 -0
  388. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +291 -0
  389. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +175 -0
  390. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +37 -0
  391. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +127 -0
  392. package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +400 -0
  393. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +285 -0
  394. package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +66 -0
  395. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +797 -0
  396. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +619 -0
  397. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +335 -0
  398. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +230 -0
  399. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +363 -0
  400. package/.agents/scripts/lib/orchestration/error-journal.js +139 -0
  401. package/.agents/scripts/lib/orchestration/file-assumption-enum.js +31 -0
  402. package/.agents/scripts/lib/orchestration/file-assumptions.js +506 -0
  403. package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +116 -0
  404. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +241 -0
  405. package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +489 -0
  406. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +88 -0
  407. package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches-reap.js +219 -0
  408. package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches.js +309 -0
  409. package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +99 -0
  410. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +123 -0
  411. package/.agents/scripts/lib/orchestration/git-cleanup/phases/filters.js +57 -0
  412. package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes-ff.js +114 -0
  413. package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes.js +426 -0
  414. package/.agents/scripts/lib/orchestration/git-cleanup/phases/parse-args.js +84 -0
  415. package/.agents/scripts/lib/orchestration/git-cleanup/phases/phase-drivers.js +365 -0
  416. package/.agents/scripts/lib/orchestration/git-cleanup/phases/prompts.js +72 -0
  417. package/.agents/scripts/lib/orchestration/git-cleanup/phases/prune.js +69 -0
  418. package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +214 -0
  419. package/.agents/scripts/lib/orchestration/git-cleanup/phases/stashes.js +137 -0
  420. package/.agents/scripts/lib/orchestration/label-transitions.js +43 -0
  421. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +309 -0
  422. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +147 -0
  423. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +155 -0
  424. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +226 -0
  425. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +69 -0
  426. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +378 -0
  427. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +248 -0
  428. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +527 -0
  429. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +259 -0
  430. package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +278 -0
  431. package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +355 -0
  432. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +647 -0
  433. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +331 -0
  434. package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +140 -0
  435. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +421 -0
  436. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +168 -0
  437. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +668 -0
  438. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +322 -0
  439. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +114 -0
  440. package/.agents/scripts/lib/orchestration/manifest-builder.js +216 -0
  441. package/.agents/scripts/lib/orchestration/model-attribution.js +390 -0
  442. package/.agents/scripts/lib/orchestration/parked-follow-ons.js +147 -0
  443. package/.agents/scripts/lib/orchestration/phase-runner.js +87 -0
  444. package/.agents/scripts/lib/orchestration/plan-review-routing.js +63 -0
  445. package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +86 -0
  446. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +212 -0
  447. package/.agents/scripts/lib/orchestration/planning-context-budget.js +213 -0
  448. package/.agents/scripts/lib/orchestration/planning-risk.js +155 -0
  449. package/.agents/scripts/lib/orchestration/planning-state-manager.js +318 -0
  450. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +56 -0
  451. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +33 -0
  452. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +78 -0
  453. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +68 -0
  454. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +118 -0
  455. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +396 -0
  456. package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +205 -0
  457. package/.agents/scripts/lib/orchestration/pr-base-guard.js +47 -0
  458. package/.agents/scripts/lib/orchestration/preflight-cache.js +164 -0
  459. package/.agents/scripts/lib/orchestration/reassert-status-column.js +202 -0
  460. package/.agents/scripts/lib/orchestration/reconciler.js +137 -0
  461. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +152 -0
  462. package/.agents/scripts/lib/orchestration/recut.js +56 -0
  463. package/.agents/scripts/lib/orchestration/resolves-token.js +127 -0
  464. package/.agents/scripts/lib/orchestration/retro/phases/checks.js +94 -0
  465. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +448 -0
  466. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +335 -0
  467. package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +133 -0
  468. package/.agents/scripts/lib/orchestration/retro-heuristics.js +57 -0
  469. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +275 -0
  470. package/.agents/scripts/lib/orchestration/retro-proposals.js +395 -0
  471. package/.agents/scripts/lib/orchestration/retro-runner.js +171 -0
  472. package/.agents/scripts/lib/orchestration/review-depth.js +93 -0
  473. package/.agents/scripts/lib/orchestration/review-providers/codex.js +363 -0
  474. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +205 -0
  475. package/.agents/scripts/lib/orchestration/review-providers/native.js +805 -0
  476. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +73 -0
  477. package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +396 -0
  478. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +373 -0
  479. package/.agents/scripts/lib/orchestration/review-providers/types.js +89 -0
  480. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +107 -0
  481. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +159 -0
  482. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +194 -0
  483. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +81 -0
  484. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +190 -0
  485. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +70 -0
  486. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +106 -0
  487. package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +42 -0
  488. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +73 -0
  489. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +225 -0
  490. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +315 -0
  491. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +149 -0
  492. package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +110 -0
  493. package/.agents/scripts/lib/orchestration/spec-freshness.js +320 -0
  494. package/.agents/scripts/lib/orchestration/spec-renderer.js +456 -0
  495. package/.agents/scripts/lib/orchestration/spec-section-validator.js +80 -0
  496. package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +797 -0
  497. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +163 -0
  498. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +152 -0
  499. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +387 -0
  500. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +266 -0
  501. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +48 -0
  502. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +67 -0
  503. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +161 -0
  504. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +117 -0
  505. package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +86 -0
  506. package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +147 -0
  507. package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +142 -0
  508. package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +62 -0
  509. package/.agents/scripts/lib/orchestration/story-close/format-autofix-scoped.js +221 -0
  510. package/.agents/scripts/lib/orchestration/story-close/format-autofix-shared.js +123 -0
  511. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +216 -0
  512. package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +636 -0
  513. package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +198 -0
  514. package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +105 -0
  515. package/.agents/scripts/lib/orchestration/story-close/phases/close.js +222 -0
  516. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +220 -0
  517. package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +291 -0
  518. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +234 -0
  519. package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +110 -0
  520. package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +86 -0
  521. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +112 -0
  522. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +157 -0
  523. package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +434 -0
  524. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +290 -0
  525. package/.agents/scripts/lib/orchestration/story-close-recovery.js +643 -0
  526. package/.agents/scripts/lib/orchestration/structured-comment-parser.js +67 -0
  527. package/.agents/scripts/lib/orchestration/task-body-validator.js +391 -0
  528. package/.agents/scripts/lib/orchestration/ticket-lease.js +358 -0
  529. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +783 -0
  530. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +367 -0
  531. package/.agents/scripts/lib/orchestration/ticket-validator.js +691 -0
  532. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +723 -0
  533. package/.agents/scripts/lib/orchestration/ticketing/reads.js +474 -0
  534. package/.agents/scripts/lib/orchestration/ticketing/state.js +559 -0
  535. package/.agents/scripts/lib/orchestration/ticketing.js +55 -0
  536. package/.agents/scripts/lib/orchestration/wave-marker.js +28 -0
  537. package/.agents/scripts/lib/orchestration/wave-record-io.js +277 -0
  538. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +189 -0
  539. package/.agents/scripts/lib/orchestration/wave-record-projection.js +423 -0
  540. package/.agents/scripts/lib/path-security.js +25 -0
  541. package/.agents/scripts/lib/plan-phase-cleanup.js +125 -0
  542. package/.agents/scripts/lib/preflight-runner.js +196 -0
  543. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +95 -0
  544. package/.agents/scripts/lib/presentation/manifest-builder.js +245 -0
  545. package/.agents/scripts/lib/presentation/manifest-formatter.js +243 -0
  546. package/.agents/scripts/lib/presentation/manifest-helpers.js +213 -0
  547. package/.agents/scripts/lib/presentation/manifest-persistence.js +262 -0
  548. package/.agents/scripts/lib/presentation/manifest-procedures.js +55 -0
  549. package/.agents/scripts/lib/presentation/manifest-render-waves.js +252 -0
  550. package/.agents/scripts/lib/presentation/manifest-renderer.js +188 -0
  551. package/.agents/scripts/lib/presentation/manifest-story-views.js +119 -0
  552. package/.agents/scripts/lib/provider-factory.js +80 -0
  553. package/.agents/scripts/lib/push-epic-retry.js +209 -0
  554. package/.agents/scripts/lib/qa/console-allowlist.js +151 -0
  555. package/.agents/scripts/lib/qa/coverage-report.js +181 -0
  556. package/.agents/scripts/lib/qa/coverage-verdict.js +296 -0
  557. package/.agents/scripts/lib/qa/propose-missing-test.js +95 -0
  558. package/.agents/scripts/lib/qa/qa-context-hydrator.js +296 -0
  559. package/.agents/scripts/lib/qa/qa-session.js +197 -0
  560. package/.agents/scripts/lib/qa/redact-evidence.js +245 -0
  561. package/.agents/scripts/lib/qa/resolve-qa-contract.js +190 -0
  562. package/.agents/scripts/lib/qa/resolve-selection.js +373 -0
  563. package/.agents/scripts/lib/runtime-deps/ensure-installed.js +100 -0
  564. package/.agents/scripts/lib/runtime-deps/manifest.js +96 -0
  565. package/.agents/scripts/lib/runtime-deps/preflight.js +78 -0
  566. package/.agents/scripts/lib/runtime-deps/scan-imports.js +202 -0
  567. package/.agents/scripts/lib/signals/detectors/common.js +36 -0
  568. package/.agents/scripts/lib/signals/detectors/hotspot.js +298 -0
  569. package/.agents/scripts/lib/signals/detectors/index.js +14 -0
  570. package/.agents/scripts/lib/signals/detectors/retry.js +289 -0
  571. package/.agents/scripts/lib/signals/detectors/rework.js +204 -0
  572. package/.agents/scripts/lib/signals/index.js +39 -0
  573. package/.agents/scripts/lib/signals/read.js +268 -0
  574. package/.agents/scripts/lib/signals/schema.js +225 -0
  575. package/.agents/scripts/lib/signals/span-tree.js +290 -0
  576. package/.agents/scripts/lib/signals/write.js +19 -0
  577. package/.agents/scripts/lib/single-story/confirm-merge.js +201 -0
  578. package/.agents/scripts/lib/single-story/story-merged-notify.js +126 -0
  579. package/.agents/scripts/lib/single-story-sweep/protection.js +274 -0
  580. package/.agents/scripts/lib/single-story-sweep/sweep-lock.js +169 -0
  581. package/.agents/scripts/lib/single-story-sweep.js +329 -0
  582. package/.agents/scripts/lib/skills/parse-skill.js +202 -0
  583. package/.agents/scripts/lib/skills/walk-skill-files.js +56 -0
  584. package/.agents/scripts/lib/spec/index.js +36 -0
  585. package/.agents/scripts/lib/spec/loader.js +425 -0
  586. package/.agents/scripts/lib/spec/state.js +217 -0
  587. package/.agents/scripts/lib/story-body/story-body.js +743 -0
  588. package/.agents/scripts/lib/story-init/blocker-validator.js +68 -0
  589. package/.agents/scripts/lib/story-init/branch-initializer.js +422 -0
  590. package/.agents/scripts/lib/story-init/context-resolver.js +92 -0
  591. package/.agents/scripts/lib/story-init/donor-precheck.js +207 -0
  592. package/.agents/scripts/lib/story-init/hierarchy-tracer.js +36 -0
  593. package/.agents/scripts/lib/story-init/state-transitioner.js +80 -0
  594. package/.agents/scripts/lib/story-init/task-graph-builder.js +114 -0
  595. package/.agents/scripts/lib/story-init/transition-summary.js +34 -0
  596. package/.agents/scripts/lib/story-lifecycle.js +186 -0
  597. package/.agents/scripts/lib/story-plan.js +246 -0
  598. package/.agents/scripts/lib/task-utils.js +26 -0
  599. package/.agents/scripts/lib/templates/decomposer-prompts.js +168 -0
  600. package/.agents/scripts/lib/test-env.js +30 -0
  601. package/.agents/scripts/lib/test-isolate/env-snapshot-loader.js +52 -0
  602. package/.agents/scripts/lib/test-isolate/list-files.js +90 -0
  603. package/.agents/scripts/lib/test-isolate/parse-tap.js +75 -0
  604. package/.agents/scripts/lib/test-isolate/runner.js +483 -0
  605. package/.agents/scripts/lib/test-profile/parse-tap.js +136 -0
  606. package/.agents/scripts/lib/test-profile/render-report.js +45 -0
  607. package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +35 -0
  608. package/.agents/scripts/lib/test-tiers.js +94 -0
  609. package/.agents/scripts/lib/util/concurrent-map.js +59 -0
  610. package/.agents/scripts/lib/util/phase-timer-state.js +72 -0
  611. package/.agents/scripts/lib/util/phase-timer.js +163 -0
  612. package/.agents/scripts/lib/util/poll-loop.js +86 -0
  613. package/.agents/scripts/lib/util/with-timeout.js +32 -0
  614. package/.agents/scripts/lib/validation-evidence.js +323 -0
  615. package/.agents/scripts/lib/wave-runner/tick.js +665 -0
  616. package/.agents/scripts/lib/wave-runner/wave-checkpoint.js +91 -0
  617. package/.agents/scripts/lib/wave-runner/wave-runner-error.js +19 -0
  618. package/.agents/scripts/lib/workers/crap-worker.js +197 -0
  619. package/.agents/scripts/lib/workers/maintainability-report-worker.js +137 -0
  620. package/.agents/scripts/lib/workers/maintainability-worker.js +79 -0
  621. package/.agents/scripts/lib/workspace-provisioner.js +189 -0
  622. package/.agents/scripts/lib/worktree/bootstrapper.js +48 -0
  623. package/.agents/scripts/lib/worktree/inspector.js +140 -0
  624. package/.agents/scripts/lib/worktree/lifecycle/creation.js +118 -0
  625. package/.agents/scripts/lib/worktree/lifecycle/drift-detection.js +62 -0
  626. package/.agents/scripts/lib/worktree/lifecycle/force-drain.js +276 -0
  627. package/.agents/scripts/lib/worktree/lifecycle/gc.js +49 -0
  628. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +178 -0
  629. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +264 -0
  630. package/.agents/scripts/lib/worktree/lifecycle/precheck.js +100 -0
  631. package/.agents/scripts/lib/worktree/lifecycle/reap.js +588 -0
  632. package/.agents/scripts/lib/worktree/lifecycle/registry-sync.js +124 -0
  633. package/.agents/scripts/lib/worktree/lifecycle/shared.js +26 -0
  634. package/.agents/scripts/lib/worktree/lifecycle-manager.js +40 -0
  635. package/.agents/scripts/lib/worktree/node-modules-strategy.js +349 -0
  636. package/.agents/scripts/lib/worktree-manager.js +243 -0
  637. package/.agents/scripts/lifecycle-diff.js +206 -0
  638. package/.agents/scripts/lifecycle-emit-story-dispatch.js +194 -0
  639. package/.agents/scripts/lifecycle-emit.js +479 -0
  640. package/.agents/scripts/lint-baseline.js +507 -0
  641. package/.agents/scripts/lint-label-vocabulary.js +237 -0
  642. package/.agents/scripts/loc-delta.js +205 -0
  643. package/.agents/scripts/notify.js +307 -0
  644. package/.agents/scripts/package.json +3 -0
  645. package/.agents/scripts/post-structured-comment.js +127 -0
  646. package/.agents/scripts/pr-watch-with-update.js +152 -0
  647. package/.agents/scripts/providers/github/auth.js +65 -0
  648. package/.agents/scripts/providers/github/board-add.js +63 -0
  649. package/.agents/scripts/providers/github/branch-protection.js +186 -0
  650. package/.agents/scripts/providers/github/cache.js +72 -0
  651. package/.agents/scripts/providers/github/comments.js +131 -0
  652. package/.agents/scripts/providers/github/compose.js +111 -0
  653. package/.agents/scripts/providers/github/errors.js +242 -0
  654. package/.agents/scripts/providers/github/issues.js +242 -0
  655. package/.agents/scripts/providers/github/labels.js +179 -0
  656. package/.agents/scripts/providers/github/mappers.js +126 -0
  657. package/.agents/scripts/providers/github/merge-methods.js +82 -0
  658. package/.agents/scripts/providers/github/project-board.js +47 -0
  659. package/.agents/scripts/providers/github/projects-v2-graphql.js +472 -0
  660. package/.agents/scripts/providers/github/prs.js +103 -0
  661. package/.agents/scripts/providers/github/request-helpers.js +110 -0
  662. package/.agents/scripts/providers/github/sub-issues.js +369 -0
  663. package/.agents/scripts/providers/github/tickets.js +381 -0
  664. package/.agents/scripts/providers/github/transient-retry.js +62 -0
  665. package/.agents/scripts/providers/github.js +157 -0
  666. package/.agents/scripts/quality-preview.js +327 -0
  667. package/.agents/scripts/quality-watch.js +223 -0
  668. package/.agents/scripts/render-manifest.js +143 -0
  669. package/.agents/scripts/resync-status-column.js +176 -0
  670. package/.agents/scripts/retro-run.js +167 -0
  671. package/.agents/scripts/run-audit-suite.js +97 -0
  672. package/.agents/scripts/run-coverage.js +103 -0
  673. package/.agents/scripts/run-lint.js +94 -0
  674. package/.agents/scripts/run-test-profile.js +126 -0
  675. package/.agents/scripts/run-tests.js +185 -0
  676. package/.agents/scripts/run-verify.js +56 -0
  677. package/.agents/scripts/select-audits.js +155 -0
  678. package/.agents/scripts/signals-view.js +294 -0
  679. package/.agents/scripts/single-story-close.js +83 -0
  680. package/.agents/scripts/single-story-confirm-merge.js +183 -0
  681. package/.agents/scripts/single-story-init.js +692 -0
  682. package/.agents/scripts/stories-wave-tick.js +415 -0
  683. package/.agents/scripts/story-close.js +246 -0
  684. package/.agents/scripts/story-deliver-prepare.js +267 -0
  685. package/.agents/scripts/story-init.js +516 -0
  686. package/.agents/scripts/story-phase.js +327 -0
  687. package/.agents/scripts/story-plan.js +284 -0
  688. package/.agents/scripts/sync-agentrc.js +71 -0
  689. package/.agents/scripts/sync-branch-from-base.js +138 -0
  690. package/.agents/scripts/sync-claude-commands.js +151 -0
  691. package/.agents/scripts/test-isolate.js +222 -0
  692. package/.agents/scripts/test-wrapper.js +108 -0
  693. package/.agents/scripts/update-coverage-baseline.js +129 -0
  694. package/.agents/scripts/update-crap-baseline.js +177 -0
  695. package/.agents/scripts/update-duplication-baseline.js +134 -0
  696. package/.agents/scripts/update-maintainability-baseline.js +183 -0
  697. package/.agents/scripts/update-mutation-baseline.js +189 -0
  698. package/.agents/scripts/update-ticket-state.js +107 -0
  699. package/.agents/scripts/validate-docs-freshness.js +259 -0
  700. package/.agents/scripts/validate-skills.js +278 -0
  701. package/.agents/scripts/wave-tick.js +335 -0
  702. package/.agents/skills/core/analyze-execution/SKILL.md +98 -0
  703. package/.agents/skills/core/api-and-interface-design/SKILL.md +327 -0
  704. package/.agents/skills/core/baseline-refresh/SKILL.md +181 -0
  705. package/.agents/skills/core/browser-testing-with-devtools/SKILL.md +352 -0
  706. package/.agents/skills/core/ci-cd-and-automation/SKILL.md +274 -0
  707. package/.agents/skills/core/ci-cd-and-automation/examples.md +211 -0
  708. package/.agents/skills/core/code-review-and-quality/SKILL.md +421 -0
  709. package/.agents/skills/core/code-simplification/SKILL.md +389 -0
  710. package/.agents/skills/core/context-engineering/SKILL.md +309 -0
  711. package/.agents/skills/core/context-engineering/examples.md +58 -0
  712. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +338 -0
  713. package/.agents/skills/core/deprecation-and-migration/SKILL.md +250 -0
  714. package/.agents/skills/core/diagnose-friction/SKILL.md +79 -0
  715. package/.agents/skills/core/documentation-and-adrs/SKILL.md +323 -0
  716. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +145 -0
  717. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +425 -0
  718. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +393 -0
  719. package/.agents/skills/core/frontend-ui-engineering/SKILL.md +357 -0
  720. package/.agents/skills/core/git-workflow-and-versioning/SKILL.md +352 -0
  721. package/.agents/skills/core/hydrate-context/SKILL.md +118 -0
  722. package/.agents/skills/core/idea-refinement/SKILL.md +317 -0
  723. package/.agents/skills/core/idea-refinement/examples.md +437 -0
  724. package/.agents/skills/core/idea-refinement/frameworks.md +135 -0
  725. package/.agents/skills/core/idea-refinement/refinement-criteria.md +155 -0
  726. package/.agents/skills/core/idea-refinement/scripts/idea-refine.sh +15 -0
  727. package/.agents/skills/core/incremental-implementation/SKILL.md +271 -0
  728. package/.agents/skills/core/introducing-a-baseline-gate/SKILL.md +213 -0
  729. package/.agents/skills/core/knowledge-transfer/SKILL.md +175 -0
  730. package/.agents/skills/core/mutation-survivor-remediation/SKILL.md +117 -0
  731. package/.agents/skills/core/performance-optimization/SKILL.md +314 -0
  732. package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +277 -0
  733. package/.agents/skills/core/property-based-testing/SKILL.md +148 -0
  734. package/.agents/skills/core/qa-coverage-mapping/SKILL.md +105 -0
  735. package/.agents/skills/core/refactoring-discipline/SKILL.md +111 -0
  736. package/.agents/skills/core/scope-triage/SKILL.md +127 -0
  737. package/.agents/skills/core/security-and-hardening/SKILL.md +400 -0
  738. package/.agents/skills/core/shipping-and-launch/SKILL.md +328 -0
  739. package/.agents/skills/core/spec-driven-development/SKILL.md +252 -0
  740. package/.agents/skills/core/test-driven-development/SKILL.md +475 -0
  741. package/.agents/skills/core/using-agent-skills/SKILL.md +232 -0
  742. package/.agents/skills/skills.index.json +596 -0
  743. package/.agents/skills/stack/architecture/monorepo-path-strategist/SKILL.md +31 -0
  744. package/.agents/skills/stack/architecture/structured-output-zod/SKILL.md +51 -0
  745. package/.agents/skills/stack/architecture/subagent-orchestration/SKILL.md +48 -0
  746. package/.agents/skills/stack/backend/cloudflare-hono-architect/SKILL.md +31 -0
  747. package/.agents/skills/stack/backend/cloudflare-hono-architect/examples/route-template.ts +33 -0
  748. package/.agents/skills/stack/backend/cloudflare-queue-manager/SKILL.md +31 -0
  749. package/.agents/skills/stack/backend/cloudflare-workers/SKILL.md +51 -0
  750. package/.agents/skills/stack/backend/highlevel-crm/SKILL.md +54 -0
  751. package/.agents/skills/stack/backend/sqlite-drizzle-expert/SKILL.md +29 -0
  752. package/.agents/skills/stack/backend/sqlite-drizzle-expert/examples/schema-template.ts +30 -0
  753. package/.agents/skills/stack/backend/stripe-integration/SKILL.md +57 -0
  754. package/.agents/skills/stack/backend/stripe-integration/scripts/listen-stripe.sh +9 -0
  755. package/.agents/skills/stack/backend/turso-sqlite/SKILL.md +48 -0
  756. package/.agents/skills/stack/frontend/astro/SKILL.md +62 -0
  757. package/.agents/skills/stack/frontend/astro-react-island-strategist/SKILL.md +30 -0
  758. package/.agents/skills/stack/frontend/expo-react-native-developer/SKILL.md +29 -0
  759. package/.agents/skills/stack/frontend/google-analytics-v4/SKILL.md +50 -0
  760. package/.agents/skills/stack/frontend/tailwind-v4/SKILL.md +58 -0
  761. package/.agents/skills/stack/frontend/ui-accessibility-engineer/SKILL.md +34 -0
  762. package/.agents/skills/stack/qa/audit-accessibility/SKILL.md +51 -0
  763. package/.agents/skills/stack/qa/gherkin-authoring/SKILL.md +257 -0
  764. package/.agents/skills/stack/qa/gherkin-authoring/examples/invoice-issue.feature +41 -0
  765. package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +199 -0
  766. package/.agents/skills/stack/qa/playwright/SKILL.md +50 -0
  767. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +188 -0
  768. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +142 -0
  769. package/.agents/skills/stack/qa/qa-harness/SKILL.md +220 -0
  770. package/.agents/skills/stack/qa/vitest/SKILL.md +51 -0
  771. package/.agents/skills/stack/security/backend-security-patterns/SKILL.md +68 -0
  772. package/.agents/starter-agentrc.json +22 -0
  773. package/.agents/templates/agent-protocol.md +72 -0
  774. package/.agents/templates/docs/architecture.md +30 -0
  775. package/.agents/templates/docs/decisions.md +24 -0
  776. package/.agents/templates/epic-from-idea.md +21 -0
  777. package/.agents/templates/single-story-body.md +17 -0
  778. package/.agents/workflows/agents-update.md +415 -0
  779. package/.agents/workflows/audit-architecture.md +312 -0
  780. package/.agents/workflows/audit-clean-code.md +179 -0
  781. package/.agents/workflows/audit-dependencies.md +91 -0
  782. package/.agents/workflows/audit-devops.md +110 -0
  783. package/.agents/workflows/audit-lighthouse.md +260 -0
  784. package/.agents/workflows/audit-performance.md +161 -0
  785. package/.agents/workflows/audit-privacy.md +104 -0
  786. package/.agents/workflows/audit-quality.md +191 -0
  787. package/.agents/workflows/audit-security.md +156 -0
  788. package/.agents/workflows/audit-seo.md +118 -0
  789. package/.agents/workflows/audit-sre.md +139 -0
  790. package/.agents/workflows/audit-to-stories.md +257 -0
  791. package/.agents/workflows/audit-ux-ui.md +102 -0
  792. package/.agents/workflows/epic-deliver.md +864 -0
  793. package/.agents/workflows/epic-plan.md +998 -0
  794. package/.agents/workflows/explain.md +118 -0
  795. package/.agents/workflows/git-cleanup.md +250 -0
  796. package/.agents/workflows/git-commit-all.md +15 -0
  797. package/.agents/workflows/git-merge-pr.md +377 -0
  798. package/.agents/workflows/git-pr-all.md +278 -0
  799. package/.agents/workflows/git-push.md +60 -0
  800. package/.agents/workflows/helpers/_merge-conflict-template.md +54 -0
  801. package/.agents/workflows/helpers/acceptance-self-eval.md +74 -0
  802. package/.agents/workflows/helpers/agents-sync-config.md +129 -0
  803. package/.agents/workflows/helpers/code-quality-guardrails.md +101 -0
  804. package/.agents/workflows/helpers/code-review.md +370 -0
  805. package/.agents/workflows/helpers/diagnose.md +117 -0
  806. package/.agents/workflows/helpers/epic-audit.md +295 -0
  807. package/.agents/workflows/helpers/epic-deliver-story.md +370 -0
  808. package/.agents/workflows/helpers/epic-plan-decompose.md +199 -0
  809. package/.agents/workflows/helpers/epic-plan-spec.md +184 -0
  810. package/.agents/workflows/helpers/epic-testing.md +125 -0
  811. package/.agents/workflows/helpers/parallel-tooling.md +88 -0
  812. package/.agents/workflows/helpers/signals.md +112 -0
  813. package/.agents/workflows/helpers/single-story-deliver.md +636 -0
  814. package/.agents/workflows/helpers/worktree-lifecycle.md +317 -0
  815. package/.agents/workflows/onboard.md +207 -0
  816. package/.agents/workflows/qa-assist.md +293 -0
  817. package/.agents/workflows/qa-explore.md +350 -0
  818. package/.agents/workflows/qa-run-harness.md +288 -0
  819. package/.agents/workflows/story-deliver.md +327 -0
  820. package/.agents/workflows/story-plan.md +233 -0
  821. package/LICENSE +21 -0
  822. package/README.md +193 -0
  823. package/bin/mandrel.js +56 -0
  824. package/bin/postinstall.js +195 -0
  825. package/lib/cli/__tests__/migrate.test.js +268 -0
  826. package/lib/cli/__tests__/sync-local-zone.test.js +247 -0
  827. package/lib/cli/__tests__/sync.test.js +372 -0
  828. package/lib/cli/__tests__/update-major.test.js +217 -0
  829. package/lib/cli/__tests__/update.test.js +696 -0
  830. package/lib/cli/__tests__/version-check.test.js +398 -0
  831. package/lib/cli/doctor.js +124 -0
  832. package/lib/cli/explain.js +107 -0
  833. package/lib/cli/migrate.js +260 -0
  834. package/lib/cli/registry.js +830 -0
  835. package/lib/cli/sync-commands.js +50 -0
  836. package/lib/cli/sync.js +200 -0
  837. package/lib/cli/uninstall.js +795 -0
  838. package/lib/cli/update.js +854 -0
  839. package/lib/cli/version-check.js +206 -0
  840. package/lib/migrations/README.md +69 -0
  841. package/lib/migrations/__tests__/index.test.js +216 -0
  842. package/lib/migrations/index.js +164 -0
  843. package/package.json +105 -0
@@ -0,0 +1,887 @@
1
+ /**
2
+ * Performance signal aggregator (Epic #1030 / Story #1123; rewrite under
3
+ * Epic #1181 / Story #1438 / Task #1460).
4
+ *
5
+ * Pure functions that turn the per-Story `signals.ndjson` stream into the
6
+ * structured payloads posted by `analyze-execution.js`:
7
+ *
8
+ * - `computeStoryPerfSummary(events, opts)` → `<!-- structured:story-perf-summary -->`
9
+ * - `computeEpicPerfReport(perStorySummaries, opts)` → `<!-- structured:epic-perf-report -->`
10
+ *
11
+ * Plus streaming counterparts that consume the canonical
12
+ * `lib/signals/read` iterator directly, so the aggregator owns its own
13
+ * NDJSON ingestion through the shared reader (Task #1460 AC):
14
+ *
15
+ * - `computeStoryPerfSummaryFromStore({ storyId, epicId, config? })`
16
+ * - `computeEpicPerfReportFromStore({ epicId, perStorySummaries, config? })`
17
+ *
18
+ * Schemas:
19
+ * - `.agents/schemas/story-perf-summary.schema.json`
20
+ * - `.agents/schemas/epic-perf-report.schema.json`
21
+ *
22
+ * Robustness contract:
23
+ * - Both helpers tolerate empty / partial input. Empty streams produce a
24
+ * well-formed payload with zeroed counters and empty arrays so the
25
+ * analyzer can still upsert a comment without throwing.
26
+ * - Malformed events (missing `kind`, non-object payload) are silently
27
+ * skipped; the caller is responsible for reading them off the wire and
28
+ * deciding whether to log. The aggregator never throws on bad data.
29
+ * - Numeric fields are floored to non-negative integers so the schemas
30
+ * (`integer`, `minimum: 0`) hold by construction.
31
+ *
32
+ * NDJSON ingestion discipline (Epic #1181):
33
+ * - Field-name literals for event `kind` come from
34
+ * `lib/signals/schema.js` so writer ↔ reader names stay in lockstep.
35
+ * - All file I/O for `signals.ndjson` goes through `lib/signals/read.js`
36
+ * (no direct `readFileSync` / `createReadStream` on signals.ndjson in
37
+ * this module). A grep gate in `tests/lib/checks/` enforces this on
38
+ * CI.
39
+ */
40
+
41
+ import { isObject } from '../json-utils.js';
42
+ import { read as readSignals } from '../signals/read.js';
43
+ import { EVENT_KINDS } from '../signals/schema.js';
44
+
45
+ const FRICTION_KIND = EVENT_KINDS.FRICTION;
46
+ const HOTSPOT_KIND = EVENT_KINDS.HOTSPOT;
47
+ const REWORK_KIND = EVENT_KINDS.REWORK;
48
+ const RETRY_KIND = EVENT_KINDS.RETRY;
49
+ const SIGNAL_COUNT_KINDS = Object.freeze([
50
+ EVENT_KINDS.FRICTION,
51
+ EVENT_KINDS.HOTSPOT,
52
+ EVENT_KINDS.REWORK,
53
+ EVENT_KINDS.CHURN,
54
+ EVENT_KINDS.IDLE,
55
+ EVENT_KINDS.RETRY,
56
+ ]);
57
+
58
+ function nonNegativeInt(v) {
59
+ const n = Number(v);
60
+ if (!Number.isFinite(n) || n < 0) return 0;
61
+ return Math.floor(n);
62
+ }
63
+
64
+ function nonNegativeNumber(v) {
65
+ const n = Number(v);
66
+ if (!Number.isFinite(n) || n < 0) return 0;
67
+ return n;
68
+ }
69
+
70
+ /**
71
+ * Pull friction-by-category counts off a list of NDJSON events. Keys are
72
+ * the `details.category` strings; values ≥ 0 integers.
73
+ *
74
+ * @param {Iterable<object>} events
75
+ * @returns {Object<string, number>}
76
+ */
77
+ function frictionByCategory(events) {
78
+ const out = {};
79
+ for (const evt of events) {
80
+ if (!isObject(evt) || evt.kind !== FRICTION_KIND) continue;
81
+ const category =
82
+ isObject(evt.details) && typeof evt.details.category === 'string'
83
+ ? evt.details.category
84
+ : 'Unknown';
85
+ out[category] = (out[category] ?? 0) + 1;
86
+ }
87
+ return out;
88
+ }
89
+
90
+ /**
91
+ * Build the `topSlowPhasesVsBaseline` array. We accept hotspot signals
92
+ * carrying `{ phase, elapsedMs, baselineP95Ms, ratio }` in `details` and
93
+ * surface them sorted by ratio descending. The hotspot detector is a
94
+ * future Epic-#1030 Story; until it lands the input list is empty and
95
+ * this returns `[]`.
96
+ *
97
+ * @param {Iterable<object>} events
98
+ * @param {{ limit?: number }} [opts]
99
+ * @returns {Array<{phase: string, elapsedMs: number, baselineP95Ms: number, ratio: number}>}
100
+ */
101
+ function topSlowPhasesVsBaseline(events, opts = {}) {
102
+ const limit = Number.isInteger(opts.limit) && opts.limit > 0 ? opts.limit : 5;
103
+ const rows = [];
104
+ for (const evt of events) {
105
+ if (!isObject(evt) || evt.kind !== HOTSPOT_KIND) continue;
106
+ const d = isObject(evt.details) ? evt.details : {};
107
+ const phase =
108
+ typeof evt.phase === 'string' && evt.phase.length > 0
109
+ ? evt.phase
110
+ : typeof d.phase === 'string' && d.phase.length > 0
111
+ ? d.phase
112
+ : null;
113
+ if (!phase) continue;
114
+ rows.push({
115
+ phase,
116
+ elapsedMs: nonNegativeInt(d.elapsedMs),
117
+ baselineP95Ms: nonNegativeInt(d.baselineP95Ms),
118
+ ratio: nonNegativeNumber(d.ratio),
119
+ });
120
+ }
121
+ rows.sort((a, b) => b.ratio - a.ratio);
122
+ return rows.slice(0, limit);
123
+ }
124
+
125
+ /**
126
+ * Build the `reworkScore` object: `{ filesEditedBeyondThreshold, topPath?,
127
+ * topPathEdits? }`. We aggregate `kind: 'rework'` signals whose details
128
+ * carry a `path` and an `edits` count. When the input has no rework
129
+ * signals we return the zero-shape: `{ filesEditedBeyondThreshold: 0 }`.
130
+ *
131
+ * @param {Iterable<object>} events
132
+ * @returns {{ filesEditedBeyondThreshold: number, topPath?: string|null, topPathEdits?: number|null }}
133
+ */
134
+ function reworkScore(events) {
135
+ const editsByPath = new Map();
136
+ for (const evt of events) {
137
+ if (!isObject(evt) || evt.kind !== REWORK_KIND) continue;
138
+ const d = isObject(evt.details) ? evt.details : {};
139
+ const p = typeof d.path === 'string' && d.path.length > 0 ? d.path : null;
140
+ if (!p) continue;
141
+ const edits = nonNegativeInt(d.edits);
142
+ editsByPath.set(p, Math.max(editsByPath.get(p) ?? 0, edits));
143
+ }
144
+ if (editsByPath.size === 0) {
145
+ return { filesEditedBeyondThreshold: 0 };
146
+ }
147
+ let topPath = null;
148
+ let topPathEdits = 0;
149
+ for (const [p, n] of editsByPath) {
150
+ if (n > topPathEdits) {
151
+ topPath = p;
152
+ topPathEdits = n;
153
+ }
154
+ }
155
+ return {
156
+ filesEditedBeyondThreshold: editsByPath.size,
157
+ topPath,
158
+ topPathEdits,
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Build the `retryDensity` object: `{ retries, uniqueCommands }`. Sums
164
+ * `kind: 'retry'` signals; `uniqueCommands` is the number of distinct
165
+ * `details.command` strings observed. Zero-shape on empty input.
166
+ *
167
+ * @param {Iterable<object>} events
168
+ * @returns {{ retries: number, uniqueCommands: number }}
169
+ */
170
+ function retryDensity(events) {
171
+ let retries = 0;
172
+ const commands = new Set();
173
+ for (const evt of events) {
174
+ if (!isObject(evt) || evt.kind !== RETRY_KIND) continue;
175
+ const d = isObject(evt.details) ? evt.details : {};
176
+ retries += 1;
177
+ if (typeof d.command === 'string' && d.command.length > 0) {
178
+ commands.add(d.command);
179
+ }
180
+ }
181
+ return { retries, uniqueCommands: commands.size };
182
+ }
183
+
184
+ /**
185
+ * Convert a phase-timer summary `{ phases: [{ name, elapsedMs }, ...] }`
186
+ * into the flat `{ <name>: <ms> }` map the schema wants. Last entry wins
187
+ * if a phase appears twice (mark/finish boundaries).
188
+ *
189
+ * @param {{ phases?: Array<{ name: string, elapsedMs: number }> } | null | undefined} timing
190
+ * @returns {Object<string, number>}
191
+ */
192
+ function phaseTimingsMs(timing) {
193
+ if (!isObject(timing) || !Array.isArray(timing.phases)) return {};
194
+ const out = {};
195
+ for (const p of timing.phases) {
196
+ if (!isObject(p)) continue;
197
+ if (typeof p.name !== 'string' || p.name.length === 0) continue;
198
+ out[p.name] = nonNegativeInt(p.elapsedMs);
199
+ }
200
+ return out;
201
+ }
202
+
203
+ /**
204
+ * Compute the StoryPerfSummary payload from a list of NDJSON events
205
+ * sampled out of `temp/epic-<eid>/stories/story-<sid>/signals.ndjson` plus an
206
+ * optional phase-timer summary.
207
+ *
208
+ * @param {Iterable<object>} events
209
+ * @param {{ storyId: number, epicId: number, closedAt?: string, phaseTiming?: object|null }} opts
210
+ * @returns {object} StoryPerfSummary payload (schema: story-perf-summary)
211
+ */
212
+ export function computeStoryPerfSummary(events, opts) {
213
+ if (!isObject(opts)) {
214
+ throw new TypeError('computeStoryPerfSummary: opts is required');
215
+ }
216
+ const storyId = Number(opts.storyId);
217
+ const epicId = Number(opts.epicId);
218
+ if (!Number.isInteger(storyId) || storyId < 1) {
219
+ throw new RangeError(
220
+ `computeStoryPerfSummary: storyId must be a positive integer (got ${opts.storyId})`,
221
+ );
222
+ }
223
+ if (!Number.isInteger(epicId) || epicId < 1) {
224
+ throw new RangeError(
225
+ `computeStoryPerfSummary: epicId must be a positive integer (got ${opts.epicId})`,
226
+ );
227
+ }
228
+ const closedAt =
229
+ typeof opts.closedAt === 'string' && opts.closedAt.length > 0
230
+ ? opts.closedAt
231
+ : new Date().toISOString();
232
+
233
+ // Materialise the iterable so each helper can scan independently.
234
+ const evtArr = [];
235
+ for (const e of events ?? []) {
236
+ if (isObject(e) && typeof e.kind === 'string') evtArr.push(e);
237
+ }
238
+
239
+ return {
240
+ kind: 'story-perf-summary',
241
+ storyId,
242
+ epicId,
243
+ closedAt,
244
+ frictionByCategory: frictionByCategory(evtArr),
245
+ phaseTimingsMs: phaseTimingsMs(opts.phaseTiming),
246
+ topSlowPhasesVsBaseline: topSlowPhasesVsBaseline(evtArr),
247
+ reworkScore: reworkScore(evtArr),
248
+ retryDensity: retryDensity(evtArr),
249
+ };
250
+ }
251
+
252
+ /**
253
+ * Compute the EpicPerfReport payload from a list of per-Story summaries
254
+ * (each shaped like `computeStoryPerfSummary`'s return value) plus an
255
+ * optional list of raw events for signal-count rollup.
256
+ *
257
+ * `signalCounts` rolls up across **events**, not summaries — a Story's
258
+ * `frictionByCategory` only carries friction (the named slice the schema
259
+ * surfaces), but the Epic-level rollup wants every kind. When `opts.events`
260
+ * is absent we fall back to summing each summary's friction count and
261
+ * leave the other kinds at 0.
262
+ *
263
+ * @param {Iterable<object>} perStorySummaries
264
+ * @param {{ epicId: number, generatedAt?: string, events?: Iterable<object>, waveParallelism?: Array<object>, topHotspots?: Array<object> }} opts
265
+ * @returns {object} EpicPerfReport payload (schema: epic-perf-report)
266
+ */
267
+ /**
268
+ * Predicate / collector: walk a `perStorySummaries` iterable and emit
269
+ * only the entries whose `kind === 'story-perf-summary'`. Extracted from
270
+ * `computeEpicPerfReport` so the input-validation cascade is independently
271
+ * testable and the parent stays straight-line. Returns `[]` for nullish
272
+ * inputs, which matches the parent's prior behaviour.
273
+ *
274
+ * @param {Iterable<object>|null|undefined} perStorySummaries
275
+ * @returns {object[]}
276
+ */
277
+ export function collectValidStorySamples(perStorySummaries) {
278
+ const out = [];
279
+ if (!perStorySummaries) return out;
280
+ for (const s of perStorySummaries) {
281
+ if (isObject(s) && s.kind === 'story-perf-summary') out.push(s);
282
+ }
283
+ return out;
284
+ }
285
+
286
+ /**
287
+ * Build the `signalCounts` block. When `events` is supplied we roll up
288
+ * across every kind in `SIGNAL_COUNT_KINDS`; otherwise we sum the
289
+ * per-Story friction counts so the legacy summary-only path keeps the
290
+ * same friction total.
291
+ *
292
+ * @param {Iterable<object>|null|undefined} events
293
+ * @param {object[]} summaries
294
+ * @returns {{friction: number, hotspot: number, rework: number, churn: number, idle: number, retry: number}}
295
+ */
296
+ function buildSignalCounts(events, summaries) {
297
+ const counts = {
298
+ friction: 0,
299
+ hotspot: 0,
300
+ rework: 0,
301
+ churn: 0,
302
+ idle: 0,
303
+ retry: 0,
304
+ };
305
+ if (events) {
306
+ for (const evt of events) {
307
+ if (!isObject(evt) || typeof evt.kind !== 'string') continue;
308
+ if (SIGNAL_COUNT_KINDS.includes(evt.kind)) {
309
+ counts[evt.kind] += 1;
310
+ }
311
+ }
312
+ return counts;
313
+ }
314
+ for (const s of summaries) {
315
+ if (!isObject(s.frictionByCategory)) continue;
316
+ for (const v of Object.values(s.frictionByCategory)) {
317
+ counts.friction += nonNegativeInt(v);
318
+ }
319
+ }
320
+ return counts;
321
+ }
322
+
323
+ /**
324
+ * Aggregate the `topHotspots` block from per-Story samples: group each
325
+ * story's `topSlowPhasesVsBaseline` rows by phase, count occurrences,
326
+ * average the ratio, then sort by `occurrences desc, avgRatio desc` and
327
+ * cap at 5. Extracted from `computeEpicPerfReport` so the parent stays
328
+ * straight-line; callers that pass `opts.topHotspots` skip this helper
329
+ * entirely.
330
+ *
331
+ * @param {object[]} summaries
332
+ * @returns {Array<{phase: string, occurrences: number, avgRatio: number}>}
333
+ */
334
+ function aggregateTopHotspots(summaries) {
335
+ const acc = new Map();
336
+ for (const s of summaries) {
337
+ const arr = Array.isArray(s.topSlowPhasesVsBaseline)
338
+ ? s.topSlowPhasesVsBaseline
339
+ : [];
340
+ for (const row of arr) {
341
+ if (!isObject(row) || typeof row.phase !== 'string') continue;
342
+ const rec = acc.get(row.phase) ?? {
343
+ phase: row.phase,
344
+ occurrences: 0,
345
+ ratioSum: 0,
346
+ };
347
+ rec.occurrences += 1;
348
+ rec.ratioSum += nonNegativeNumber(row.ratio);
349
+ acc.set(row.phase, rec);
350
+ }
351
+ }
352
+ return [...acc.values()]
353
+ .map((r) => ({
354
+ phase: r.phase,
355
+ occurrences: r.occurrences,
356
+ avgRatio: r.occurrences > 0 ? r.ratioSum / r.occurrences : 0,
357
+ }))
358
+ .sort((a, b) => b.occurrences - a.occurrences || b.avgRatio - a.avgRatio)
359
+ .slice(0, 5);
360
+ }
361
+
362
+ /**
363
+ * Default verify-concurrency cap when the caller does not override it.
364
+ * Mirrors the default for `delivery.deliverRunner.verifyConcurrencyCap`
365
+ * in `.agentrc.json` (Epic #3019 Tech Spec §1.4).
366
+ */
367
+ const DEFAULT_VERIFY_CONCURRENCY_CAP = 4;
368
+
369
+ /**
370
+ * Default wave-execution concurrency cap used when the caller does not
371
+ * supply `concurrencyCap` to {@link computeWaveParallelismRows}. The
372
+ * project default (`delivery.deliverRunner.concurrencyCap`) is 2 today;
373
+ * the value here is the safe fallback for offline / test contexts.
374
+ */
375
+ const DEFAULT_WAVE_CONCURRENCY_CAP = 2;
376
+
377
+ function tsOf(evt) {
378
+ return evt?.ts ?? evt?.timestamp ?? null;
379
+ }
380
+
381
+ function tsToMs(ts) {
382
+ if (typeof ts !== 'string') return null;
383
+ const n = Date.parse(ts);
384
+ return Number.isFinite(n) ? n : null;
385
+ }
386
+
387
+ function storyIdOf(evt) {
388
+ const raw = evt?.story ?? evt?.storyId;
389
+ const n = Number(raw);
390
+ return Number.isInteger(n) && n > 0 ? n : null;
391
+ }
392
+
393
+ /**
394
+ * Materialise an event iterable into an array of `{ evt, ms }` records,
395
+ * parsing each event's timestamp **exactly once** (Story #3343). Events
396
+ * with a non-string `kind` are dropped (matching the prior inline guard);
397
+ * the `ms` field is `null` when the timestamp is missing or unparseable so
398
+ * downstream passes can skip it without re-parsing.
399
+ *
400
+ * @param {Iterable<object>} events
401
+ * @returns {Array<{ evt: object, ms: number|null }>}
402
+ */
403
+ function materialiseTimedEvents(events) {
404
+ const out = [];
405
+ for (const evt of events ?? []) {
406
+ if (isObject(evt) && typeof evt.kind === 'string') {
407
+ out.push({ evt, ms: tsToMs(tsOf(evt)) });
408
+ }
409
+ }
410
+ return out;
411
+ }
412
+
413
+ /**
414
+ * Index Story state-transition windows from pre-timed events: first
415
+ * `agent::executing` → last terminal (`agent::done` | `agent::blocked` |
416
+ * `agent::failed`). Reuses the per-event `ms` parsed by
417
+ * {@link materialiseTimedEvents}. Extracted from
418
+ * {@link computeWaveParallelismRows} (Story #3343).
419
+ *
420
+ * @param {Array<{ evt: object, ms: number|null }>} timedEvents
421
+ * @returns {Map<number, { startMs: number|null, endMs: number|null }>}
422
+ */
423
+ function indexStoryWindows(timedEvents) {
424
+ const storyWindows = new Map();
425
+ for (const { evt, ms } of timedEvents) {
426
+ if (evt.kind !== 'state-transition') continue;
427
+ const sid = storyIdOf(evt);
428
+ if (sid == null) continue;
429
+ if (ms == null) continue;
430
+ const to =
431
+ (isObject(evt.details) && evt.details.to) ?? evt.to ?? evt.toState;
432
+ const rec = storyWindows.get(sid) ?? { startMs: null, endMs: null };
433
+ if (to === 'agent::executing') {
434
+ if (rec.startMs == null || ms < rec.startMs) rec.startMs = ms;
435
+ } else if (
436
+ to === 'agent::done' ||
437
+ to === 'agent::blocked' ||
438
+ to === 'agent::failed'
439
+ ) {
440
+ if (rec.endMs == null || ms > rec.endMs) rec.endMs = ms;
441
+ }
442
+ storyWindows.set(sid, rec);
443
+ }
444
+ return storyWindows;
445
+ }
446
+
447
+ /**
448
+ * Bucket `wave-start` / `wave-complete` events by index from pre-timed
449
+ * events. Reuses the per-event `ms` parsed by
450
+ * {@link materialiseTimedEvents}. Extracted from
451
+ * {@link computeWaveParallelismRows} (Story #3343).
452
+ *
453
+ * @param {Array<{ evt: object, ms: number|null }>} timedEvents
454
+ * @returns {Map<number, { startMs: number|null, endMs: number|null, stories: number[] }>}
455
+ */
456
+ function bucketWaves(timedEvents) {
457
+ const waves = new Map();
458
+ for (const { evt, ms } of timedEvents) {
459
+ if (ms == null) continue;
460
+ if (evt.kind === 'wave-start') {
461
+ const idx = Number(evt.index);
462
+ if (!Number.isInteger(idx) || idx < 0) continue;
463
+ const storiesField = Array.isArray(evt.stories) ? evt.stories : [];
464
+ const storyIds = storiesField
465
+ .map((s) => {
466
+ const n = Number(isObject(s) ? (s.id ?? s.storyId) : s);
467
+ return Number.isInteger(n) && n > 0 ? n : null;
468
+ })
469
+ .filter((n) => n != null);
470
+ const rec = waves.get(idx) ?? {
471
+ startMs: null,
472
+ endMs: null,
473
+ stories: [],
474
+ };
475
+ if (rec.startMs == null || ms < rec.startMs) rec.startMs = ms;
476
+ rec.stories = storyIds;
477
+ waves.set(idx, rec);
478
+ } else if (evt.kind === 'wave-complete') {
479
+ const idx = Number(evt.index);
480
+ if (!Number.isInteger(idx) || idx < 0) continue;
481
+ const rec = waves.get(idx) ?? {
482
+ startMs: null,
483
+ endMs: null,
484
+ stories: [],
485
+ };
486
+ if (rec.endMs == null || ms > rec.endMs) rec.endMs = ms;
487
+ waves.set(idx, rec);
488
+ }
489
+ }
490
+ return waves;
491
+ }
492
+
493
+ /**
494
+ * Largest value `< hi` that is `>= lo` in a sorted ascending array, or
495
+ * `null` when the half-open window `[lo, hi)` contains no element. Pure
496
+ * binary search — used by {@link fillMissingWaveEnds} to find a wave's
497
+ * fallback terminator without re-scanning the whole event array per wave.
498
+ *
499
+ * @param {number[]} sortedMs ascending
500
+ * @param {number} lo inclusive lower bound
501
+ * @param {number} hi exclusive upper bound (may be Infinity)
502
+ * @returns {number|null}
503
+ */
504
+ function maxInWindow(sortedMs, lo, hi) {
505
+ // Find the first index with value >= hi (upper bound), then step back to
506
+ // the last element strictly below hi.
507
+ let left = 0;
508
+ let right = sortedMs.length;
509
+ while (left < right) {
510
+ const mid = (left + right) >> 1;
511
+ if (sortedMs[mid] < hi) left = mid + 1;
512
+ else right = mid;
513
+ }
514
+ const candidate = left - 1; // last index with value < hi
515
+ if (candidate < 0) return null;
516
+ const val = sortedMs[candidate];
517
+ return val >= lo ? val : null;
518
+ }
519
+
520
+ /**
521
+ * Fill the `endMs` of any wave that never observed a `wave-complete`:
522
+ * each such wave's terminator is the max event timestamp in the half-open
523
+ * window `[startMs, nextStartMs)`, where `nextStartMs` is the start of the
524
+ * next wave by ascending index (or `Infinity` for the last wave).
525
+ *
526
+ * Replaces the prior O(waves × events) nested scan with a single sort of
527
+ * the already-parsed timestamps plus one binary search per gap-wave
528
+ * (Story #3343). Output is byte-identical to the prior implementation.
529
+ *
530
+ * @param {Array<[number, { startMs: number|null, endMs: number|null, stories: number[] }]>} orderedWaves sorted by index asc
531
+ * @param {Array<{ evt: object, ms: number|null }>} timedEvents
532
+ * @returns {void} mutates the wave records in `orderedWaves` in place
533
+ */
534
+ function fillMissingWaveEnds(orderedWaves, timedEvents) {
535
+ const needsFill = orderedWaves.some(
536
+ ([, rec]) => rec.endMs == null && rec.startMs != null,
537
+ );
538
+ if (!needsFill) return;
539
+ const sortedMs = [];
540
+ for (const { ms } of timedEvents) {
541
+ if (ms != null) sortedMs.push(ms);
542
+ }
543
+ sortedMs.sort((a, b) => a - b);
544
+ for (let i = 0; i < orderedWaves.length; i += 1) {
545
+ const [, rec] = orderedWaves[i];
546
+ if (rec.endMs != null) continue;
547
+ const startMs = rec.startMs;
548
+ if (startMs == null) continue;
549
+ const nextStartMs =
550
+ i + 1 < orderedWaves.length ? orderedWaves[i + 1][1].startMs : Infinity;
551
+ const maxMs = maxInWindow(sortedMs, startMs, nextStartMs);
552
+ rec.endMs = maxMs == null ? startMs : Math.max(startMs, maxMs);
553
+ }
554
+ }
555
+
556
+ /**
557
+ * Compute per-wave parallelism rows from a chronological iterable of
558
+ * lifecycle events (Task #3028, Epic #3019 / Story #3025).
559
+ *
560
+ * Wave windows are bracketed by `wave-start` (carrying `index` +
561
+ * `stories[]`) and the matching `wave-complete` (same `index`). When no
562
+ * `wave-complete` is observed for a wave, the wave's wallClockMs falls
563
+ * back to the timestamp of the last in-wave event observed (so partial
564
+ * runs still emit a row). When `wave-start` is missing entirely we emit
565
+ * no row for that wave.
566
+ *
567
+ * Per-Story durations within a wave come from `state-transition` events
568
+ * (`agent::executing` → `agent::done`) for the Story IDs the
569
+ * `wave-start` payload enumerated. We bracket the **first**
570
+ * `executing` transition and the **last** terminal transition (`done`,
571
+ * `blocked`, or `failed`) per Story; if a Story is missing one boundary
572
+ * its contribution to `summedStoryMs` is 0.
573
+ *
574
+ * Field contract (per the extended `epic-perf-report.schema.json`):
575
+ * - `waveIndex`: integer ≥ 0, from `wave-start.index`
576
+ * - `storyCount`: integer ≥ 0, number of Stories in the wave (from
577
+ * `wave-start.stories`). Added under Story #3850.
578
+ * - `wallClockMs`: integer ≥ 0, `(waveEnd - waveStart)` in ms
579
+ * - `summedStoryMs`: integer ≥ 0, Σ per-Story `(end - start)`
580
+ * - `utilisation`: `summedStoryMs / (wallClockMs * effectiveCap)`,
581
+ * where `effectiveCap = min(storyCount, concurrencyCap)`, clamped
582
+ * to `[0, 1]`. Zero when `wallClockMs === 0` or effectiveCap is 0.
583
+ * Using the effective (not raw) cap means a fully-busy 1-Story wave
584
+ * scores 1.0 rather than `1/cap`, eliminating false-positive
585
+ * `low-utilisation` signals on serialized/narrow waves (Story #3850).
586
+ * - `capBinding`: true when `summedStoryMs / wallClockMs >=
587
+ * concurrencyCap`, false otherwise (and false when wallClockMs
588
+ * is 0). Still uses the raw cap so the signal fires when the
589
+ * configured parallelism ceiling is actually saturated.
590
+ * - `verifyConcurrencyCap`: forwarded from `opts.verifyConcurrencyCap`
591
+ * (or the project default 4) so the post-merge close comment can
592
+ * attribute saturation back to the cap value in force at the time.
593
+ *
594
+ * @param {Iterable<object>} events
595
+ * @param {{
596
+ * concurrencyCap?: number,
597
+ * verifyConcurrencyCap?: number,
598
+ * }} [opts]
599
+ * @returns {Array<{
600
+ * waveIndex: number,
601
+ * storyCount: number,
602
+ * wallClockMs: number,
603
+ * summedStoryMs: number,
604
+ * utilisation: number,
605
+ * capBinding: boolean,
606
+ * verifyConcurrencyCap: number,
607
+ * }>}
608
+ */
609
+ export function computeWaveParallelismRows(events, opts = {}) {
610
+ const concurrencyCap =
611
+ Number.isInteger(opts.concurrencyCap) && opts.concurrencyCap >= 1
612
+ ? opts.concurrencyCap
613
+ : DEFAULT_WAVE_CONCURRENCY_CAP;
614
+ const verifyConcurrencyCap =
615
+ Number.isInteger(opts.verifyConcurrencyCap) &&
616
+ opts.verifyConcurrencyCap >= 1
617
+ ? opts.verifyConcurrencyCap
618
+ : DEFAULT_VERIFY_CONCURRENCY_CAP;
619
+
620
+ // Materialise the iterable once, parsing each event's timestamp a
621
+ // single time so every downstream pass reuses the same `ms` value
622
+ // (Story #3343). Events are typically a few thousand per Epic at most.
623
+ const timedEvents = materialiseTimedEvents(events);
624
+
625
+ // Index Story state-transition windows: first `agent::executing` →
626
+ // last terminal (`agent::done` | `agent::blocked` | `agent::failed`).
627
+ const storyWindows = indexStoryWindows(timedEvents);
628
+
629
+ // Bucket wave-start / wave-complete events by index, then fill any wave
630
+ // that never saw `wave-complete` via a single sorted-timestamp sweep
631
+ // (replacing the prior O(waves × events) nested scan).
632
+ const waves = bucketWaves(timedEvents);
633
+ const orderedWaves = [...waves.entries()].sort((a, b) => a[0] - b[0]);
634
+ fillMissingWaveEnds(orderedWaves, timedEvents);
635
+
636
+ // Build rows.
637
+ const rows = [];
638
+ for (const [idx, rec] of orderedWaves) {
639
+ if (rec.startMs == null) continue;
640
+ const wallClockMs = Math.max(
641
+ 0,
642
+ Math.floor((rec.endMs ?? rec.startMs) - rec.startMs),
643
+ );
644
+ const storyCount = rec.stories.length;
645
+ let summedStoryMs = 0;
646
+ for (const sid of rec.stories) {
647
+ const w = storyWindows.get(sid);
648
+ if (!w || w.startMs == null || w.endMs == null) continue;
649
+ const dur = w.endMs - w.startMs;
650
+ if (Number.isFinite(dur) && dur > 0) summedStoryMs += Math.floor(dur);
651
+ }
652
+ // Use the effective cap — min(storyCount, concurrencyCap) — as the
653
+ // utilisation denominator so a fully-busy 1-Story wave scores 1.0
654
+ // instead of 1/cap. This eliminates false-positive `low-utilisation`
655
+ // signals on serialized / narrow-wave Epics (Story #3850).
656
+ // capBinding still uses the raw concurrencyCap because it signals that
657
+ // the configured parallelism ceiling is genuinely saturated.
658
+ const effectiveCap = Math.min(storyCount, concurrencyCap);
659
+ let utilisation = 0;
660
+ let capBinding = false;
661
+ if (wallClockMs > 0 && effectiveCap > 0) {
662
+ utilisation = clamp(summedStoryMs / (wallClockMs * effectiveCap), 0, 1);
663
+ }
664
+ if (wallClockMs > 0 && concurrencyCap > 0) {
665
+ capBinding = summedStoryMs / wallClockMs >= concurrencyCap;
666
+ }
667
+ rows.push({
668
+ waveIndex: idx,
669
+ storyCount,
670
+ wallClockMs,
671
+ summedStoryMs,
672
+ utilisation,
673
+ capBinding,
674
+ verifyConcurrencyCap,
675
+ });
676
+ }
677
+
678
+ return rows;
679
+ }
680
+
681
+ /**
682
+ * Clamp `n` to the inclusive range [lo, hi]. Returns `lo` for NaN /
683
+ * non-finite inputs so the coercer stays well-behaved on garbage.
684
+ *
685
+ * @param {number} n
686
+ * @param {number} lo
687
+ * @param {number} hi
688
+ * @returns {number}
689
+ */
690
+ function clamp(n, lo, hi) {
691
+ if (!Number.isFinite(n)) return lo;
692
+ if (n < lo) return lo;
693
+ if (n > hi) return hi;
694
+ return n;
695
+ }
696
+
697
+ /**
698
+ * Coerce a single waveParallelism input row into the schema-canonical
699
+ * shape `{ waveIndex, storyCount, wallClockMs, summedStoryMs, utilisation,
700
+ * capBinding, verifyConcurrencyCap }` (Story #3025; storyCount added
701
+ * Story #3850).
702
+ *
703
+ * - Numeric fields are floored to non-negative integers (or clamped
704
+ * numbers for utilisation) so the JSON-schema `integer` / `minimum: 0`
705
+ * constraints hold by construction.
706
+ * - `utilisation` is clamped to `[0, 1]` per the Tech Spec
707
+ * contract (§1.1).
708
+ * - `capBinding` is coerced to boolean.
709
+ * - `verifyConcurrencyCap` falls back to the project default (4) when the
710
+ * caller omits it or supplies a non-positive integer; the schema
711
+ * requires `minimum: 1` so 0 is not a valid carrier value.
712
+ * - `storyCount` falls back to 0 when absent (older payloads predating
713
+ * Story #3850 do not carry the field).
714
+ *
715
+ * Exported for unit-testing the coercer in isolation.
716
+ *
717
+ * @param {object | null | undefined} row
718
+ * @returns {{ waveIndex: number, storyCount: number, wallClockMs: number, summedStoryMs: number, utilisation: number, capBinding: boolean, verifyConcurrencyCap: number }}
719
+ */
720
+ export function coerceWaveParallelismRow(row) {
721
+ const src = isObject(row) ? row : {};
722
+ const cap = Number(src.verifyConcurrencyCap);
723
+ const verifyConcurrencyCap =
724
+ Number.isInteger(cap) && cap >= 1 ? cap : DEFAULT_VERIFY_CONCURRENCY_CAP;
725
+ return {
726
+ waveIndex: nonNegativeInt(src.waveIndex),
727
+ storyCount: nonNegativeInt(src.storyCount),
728
+ wallClockMs: nonNegativeInt(src.wallClockMs),
729
+ summedStoryMs: nonNegativeInt(src.summedStoryMs),
730
+ utilisation: clamp(nonNegativeNumber(src.utilisation), 0, 1),
731
+ capBinding: Boolean(src.capBinding),
732
+ verifyConcurrencyCap,
733
+ };
734
+ }
735
+
736
+ export function computeEpicPerfReport(perStorySummaries, opts) {
737
+ if (!isObject(opts)) {
738
+ throw new TypeError('computeEpicPerfReport: opts is required');
739
+ }
740
+ const epicId = Number(opts.epicId);
741
+ if (!Number.isInteger(epicId) || epicId < 1) {
742
+ throw new RangeError(
743
+ `computeEpicPerfReport: epicId must be a positive integer (got ${opts.epicId})`,
744
+ );
745
+ }
746
+ const generatedAt =
747
+ typeof opts.generatedAt === 'string' && opts.generatedAt.length > 0
748
+ ? opts.generatedAt
749
+ : new Date().toISOString();
750
+
751
+ const summaries = collectValidStorySamples(perStorySummaries);
752
+
753
+ // signalCounts: prefer the raw-event roll-up; fall back to friction-only
754
+ // when the caller did not pass events.
755
+ const signalCounts = buildSignalCounts(opts.events, summaries);
756
+
757
+ const topHotspots = Array.isArray(opts.topHotspots)
758
+ ? opts.topHotspots
759
+ : aggregateTopHotspots(summaries);
760
+
761
+ // mostFrictionStories: per-Story friction count, sorted desc, capped.
762
+ const mostFrictionStories = summaries
763
+ .map((s) => {
764
+ const counts = isObject(s.frictionByCategory)
765
+ ? Object.values(s.frictionByCategory).reduce(
766
+ (acc, v) => acc + nonNegativeInt(v),
767
+ 0,
768
+ )
769
+ : 0;
770
+ return {
771
+ storyId: nonNegativeInt(s.storyId),
772
+ frictionCount: counts,
773
+ };
774
+ })
775
+ .filter((row) => row.storyId > 0)
776
+ .sort((a, b) => b.frictionCount - a.frictionCount)
777
+ .slice(0, 5);
778
+
779
+ let waveParallelism;
780
+ if (Array.isArray(opts.waveParallelism)) {
781
+ waveParallelism = opts.waveParallelism.map((row) =>
782
+ coerceWaveParallelismRow(row),
783
+ );
784
+ } else if (opts.events) {
785
+ // Derive rows from the raw lifecycle event stream when the caller
786
+ // hands us the events but no pre-computed array (Story #3025 /
787
+ // Task #3028).
788
+ waveParallelism = computeWaveParallelismRows(opts.events, {
789
+ concurrencyCap: opts.concurrencyCap,
790
+ verifyConcurrencyCap: opts.verifyConcurrencyCap,
791
+ });
792
+ } else {
793
+ waveParallelism = [];
794
+ }
795
+
796
+ return {
797
+ kind: 'epic-perf-report',
798
+ epicId,
799
+ generatedAt,
800
+ signalCounts,
801
+ waveParallelism,
802
+ topHotspots,
803
+ mostFrictionStories,
804
+ };
805
+ }
806
+
807
+ // ---------------------------------------------------------------------------
808
+ // Streaming entry-points (Epic #1181 / Story #1438 / Task #1460)
809
+ // ---------------------------------------------------------------------------
810
+
811
+ /**
812
+ * Streaming variant of `computeStoryPerfSummary` that ingests events
813
+ * directly from `lib/signals/read.js` rather than expecting the caller
814
+ * to materialise the iterable upstream. The aggregation logic is
815
+ * identical — we collect the events through the shared reader and then
816
+ * delegate to `computeStoryPerfSummary`.
817
+ *
818
+ * Use this when the caller is the analyzer and already has the
819
+ * `{ epicId, storyId, config }` triple; use the pure
820
+ * `computeStoryPerfSummary(events, opts)` when the caller already
821
+ * holds an in-memory event array (tests, mock injections).
822
+ *
823
+ * @param {{
824
+ * storyId: number,
825
+ * epicId: number,
826
+ * closedAt?: string,
827
+ * phaseTiming?: object|null,
828
+ * config?: object,
829
+ * }} opts
830
+ * @returns {Promise<object>} StoryPerfSummary payload.
831
+ */
832
+ export async function computeStoryPerfSummaryFromStore(opts) {
833
+ if (!opts || typeof opts !== 'object') {
834
+ throw new TypeError('computeStoryPerfSummaryFromStore: opts is required');
835
+ }
836
+ const { storyId, epicId, config } = opts;
837
+ const events = [];
838
+ for await (const evt of readSignals({
839
+ epic: Number(epicId),
840
+ story: Number(storyId),
841
+ config,
842
+ })) {
843
+ events.push(evt);
844
+ }
845
+ return computeStoryPerfSummary(events, {
846
+ storyId,
847
+ epicId,
848
+ closedAt: opts.closedAt,
849
+ phaseTiming: opts.phaseTiming,
850
+ });
851
+ }
852
+
853
+ /**
854
+ * Streaming variant of `computeEpicPerfReport` that ingests the
855
+ * raw-event roll-up directly from `lib/signals/read.js` (across every
856
+ * Story under the Epic). Per-Story summaries are still passed in by
857
+ * the caller — those are the canonical per-Story payloads upserted
858
+ * onto each Story ticket and not derivable from the raw stream alone
859
+ * (they fold in phase-timer data).
860
+ *
861
+ * @param {{
862
+ * epicId: number,
863
+ * perStorySummaries?: Iterable<object>,
864
+ * generatedAt?: string,
865
+ * waveParallelism?: Array<object>,
866
+ * topHotspots?: Array<object>,
867
+ * config?: object,
868
+ * }} opts
869
+ * @returns {Promise<object>} EpicPerfReport payload.
870
+ */
871
+ export async function computeEpicPerfReportFromStore(opts) {
872
+ if (!opts || typeof opts !== 'object') {
873
+ throw new TypeError('computeEpicPerfReportFromStore: opts is required');
874
+ }
875
+ const { epicId, perStorySummaries, config } = opts;
876
+ const events = [];
877
+ for await (const evt of readSignals({ epic: Number(epicId), config })) {
878
+ events.push(evt);
879
+ }
880
+ return computeEpicPerfReport(perStorySummaries ?? [], {
881
+ epicId,
882
+ generatedAt: opts.generatedAt,
883
+ events,
884
+ waveParallelism: opts.waveParallelism,
885
+ topHotspots: opts.topHotspots,
886
+ });
887
+ }