mandrel 2.0.0 → 2.2.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 (323) hide show
  1. package/.agents/README.md +59 -28
  2. package/.agents/agents/acceptance-critic.md +20 -9
  3. package/.agents/agents/story-worker.md +45 -48
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +60 -46
  6. package/.agents/docs/agentrc-reference.json +8 -13
  7. package/.agents/docs/configuration.md +33 -57
  8. package/.agents/docs/execution-reference.md +39 -10
  9. package/.agents/docs/quality-gates.md +17 -19
  10. package/.agents/docs/workflows.md +6 -6
  11. package/.agents/instructions.md +64 -79
  12. package/.agents/rules/ci-remediation.md +3 -3
  13. package/.agents/rules/gherkin-standards.md +10 -0
  14. package/.agents/rules/git-conventions-reference.md +42 -51
  15. package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
  16. package/.agents/schemas/agentrc.schema.json +35 -46
  17. package/.agents/schemas/audit-rules.json +59 -1
  18. package/.agents/schemas/audit-rules.schema.json +33 -1
  19. package/.agents/schemas/lifecycle/README.md +1 -2
  20. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  21. package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
  22. package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
  23. package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
  24. package/.agents/schemas/signal-event.schema.json +3 -3
  25. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  26. package/.agents/schemas/validation-evidence.schema.json +1 -1
  27. package/.agents/scripts/acceptance-eval.js +24 -68
  28. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  29. package/.agents/scripts/bootstrap.js +3 -3
  30. package/.agents/scripts/check-dead-exports.js +43 -104
  31. package/.agents/scripts/check-doc-links.js +2 -2
  32. package/.agents/scripts/check-lifecycle-lint.js +1 -1
  33. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  34. package/.agents/scripts/deliver-recover.js +122 -0
  35. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  36. package/.agents/scripts/evidence-gate.js +20 -50
  37. package/.agents/scripts/generate-skills-index.js +17 -1
  38. package/.agents/scripts/generate-workflows-doc.js +4 -4
  39. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  40. package/.agents/scripts/lib/audit-suite/selector.js +323 -23
  41. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
  42. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  43. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  44. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  45. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  46. package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
  47. package/.agents/scripts/lib/checks/index.js +1 -1
  48. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  49. package/.agents/scripts/lib/checks/state.js +17 -248
  50. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
  51. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  52. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  53. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  54. package/.agents/scripts/lib/cli-args.js +23 -2
  55. package/.agents/scripts/lib/close-validation/gates.js +13 -13
  56. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  57. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  58. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  59. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  60. package/.agents/scripts/lib/config/acceptance-eval.js +2 -2
  61. package/.agents/scripts/lib/config/delivery-routing.js +7 -6
  62. package/.agents/scripts/lib/config/explain.js +10 -16
  63. package/.agents/scripts/lib/config/github.js +7 -5
  64. package/.agents/scripts/lib/config/limits.js +15 -25
  65. package/.agents/scripts/lib/config/quality.js +11 -14
  66. package/.agents/scripts/lib/config/runners.js +8 -21
  67. package/.agents/scripts/lib/config/temp-paths.js +18 -56
  68. package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
  69. package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
  70. package/.agents/scripts/lib/config-settings-schema.js +48 -22
  71. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  72. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  73. package/.agents/scripts/lib/duplicate-search.js +38 -7
  74. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  75. package/.agents/scripts/lib/format-generated-json.js +97 -0
  76. package/.agents/scripts/lib/framework-version.js +19 -189
  77. package/.agents/scripts/lib/gh-exec.js +8 -0
  78. package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
  79. package/.agents/scripts/lib/git-utils.js +0 -14
  80. package/.agents/scripts/lib/json-utils.js +1 -2
  81. package/.agents/scripts/lib/label-constants.js +0 -15
  82. package/.agents/scripts/lib/label-taxonomy.js +1 -12
  83. package/.agents/scripts/lib/observability/active-story-env.js +42 -163
  84. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  85. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  86. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  87. package/.agents/scripts/lib/observability/source-classifier.js +2 -2
  88. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  89. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  90. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  91. package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
  92. package/.agents/scripts/lib/orchestration/change-set.js +103 -0
  93. package/.agents/scripts/lib/orchestration/code-review.js +70 -191
  94. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
  95. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  96. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  97. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  98. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  99. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  100. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
  101. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  102. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  103. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
  104. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  105. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  106. package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
  107. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  108. package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
  109. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
  110. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
  111. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  112. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
  113. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
  114. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  115. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
  116. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
  117. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
  118. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  119. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
  120. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
  121. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
  122. package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
  123. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  124. package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
  125. package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
  126. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  127. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  128. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  129. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  130. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  131. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  132. package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
  133. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
  134. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  135. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  136. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +4 -13
  137. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  138. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
  139. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  140. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  141. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
  142. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  143. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  144. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
  145. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  146. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  147. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
  148. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
  149. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
  150. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  151. package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
  152. package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
  153. package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
  154. package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
  155. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  156. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
  157. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
  158. package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
  159. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
  160. package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
  161. package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
  162. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  163. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  164. package/.agents/scripts/lib/planning-corpus.js +12 -286
  165. package/.agents/scripts/lib/preflight-runner.js +2 -2
  166. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  167. package/.agents/scripts/lib/signals/index.js +4 -17
  168. package/.agents/scripts/lib/signals/read.js +35 -35
  169. package/.agents/scripts/lib/signals/schema.js +8 -11
  170. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  171. package/.agents/scripts/lib/signals/write.js +0 -1
  172. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  173. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  174. package/.agents/scripts/lib/story-adjacency.js +8 -7
  175. package/.agents/scripts/lib/story-body/story-body.js +81 -13
  176. package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
  177. package/.agents/scripts/lib/test-env.js +14 -1
  178. package/.agents/scripts/lib/test-tiers.js +0 -3
  179. package/.agents/scripts/lib/ticket-body-sections.js +0 -14
  180. package/.agents/scripts/lib/validation-evidence.js +31 -59
  181. package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
  182. package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
  183. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  184. package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
  185. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  186. package/.agents/scripts/plan-context.js +38 -7
  187. package/.agents/scripts/plan-critics.js +203 -0
  188. package/.agents/scripts/plan-persist.js +145 -35
  189. package/.agents/scripts/plan-run-epilogue.js +83 -38
  190. package/.agents/scripts/post-structured-comment.js +0 -38
  191. package/.agents/scripts/pr-watch-with-update.js +43 -22
  192. package/.agents/scripts/providers/github/compose.js +0 -1
  193. package/.agents/scripts/providers/github/errors.js +0 -19
  194. package/.agents/scripts/providers/github/issues.js +1 -11
  195. package/.agents/scripts/providers/github/mappers.js +5 -0
  196. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  197. package/.agents/scripts/providers/github/tickets.js +33 -153
  198. package/.agents/scripts/providers/github.js +17 -6
  199. package/.agents/scripts/quality-preview.js +13 -6
  200. package/.agents/scripts/resolve-stories.js +236 -0
  201. package/.agents/scripts/run-coverage.js +4 -1
  202. package/.agents/scripts/run-lint.js +2 -2
  203. package/.agents/scripts/run-verify.js +31 -2
  204. package/.agents/scripts/signals-view.js +9 -10
  205. package/.agents/scripts/single-story-close.js +173 -18
  206. package/.agents/scripts/single-story-confirm-merge.js +288 -15
  207. package/.agents/scripts/single-story-init.js +6 -10
  208. package/.agents/scripts/stories-wave-tick.js +380 -53
  209. package/.agents/scripts/story-plan.js +3 -3
  210. package/.agents/scripts/update-ticket-state.js +8 -50
  211. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  212. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  213. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  214. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  215. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  216. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  217. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  218. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  219. package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
  220. package/.agents/skills/core/scope-triage/SKILL.md +3 -0
  221. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  222. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  223. package/.agents/skills/skills.index.json +2 -12
  224. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  225. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  226. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  227. package/.agents/workflows/audit-architecture.md +3 -4
  228. package/.agents/workflows/audit-clean-code.md +4 -4
  229. package/.agents/workflows/audit-documentation.md +4 -5
  230. package/.agents/workflows/audit-lighthouse.md +8 -0
  231. package/.agents/workflows/audit-navigability.md +10 -0
  232. package/.agents/workflows/audit-performance.md +2 -3
  233. package/.agents/workflows/audit-quality.md +8 -9
  234. package/.agents/workflows/audit-security.md +1 -2
  235. package/.agents/workflows/audit-seo.md +10 -0
  236. package/.agents/workflows/audit-ux-ui.md +7 -0
  237. package/.agents/workflows/deliver.md +133 -45
  238. package/.agents/workflows/git-cleanup.md +2 -2
  239. package/.agents/workflows/git-deliver.md +1 -1
  240. package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
  241. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
  242. package/.agents/workflows/helpers/code-review.md +14 -12
  243. package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
  244. package/.agents/workflows/helpers/deliver-story.md +209 -118
  245. package/.agents/workflows/helpers/parallel-tooling.md +2 -2
  246. package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
  247. package/.agents/workflows/plan.md +239 -19
  248. package/.agents/workflows/qa-assist.md +6 -6
  249. package/.agents/workflows/qa-explore.md +3 -3
  250. package/.agents/workflows/qa-run.md +1 -5
  251. package/bin/mandrel.js +12 -1
  252. package/docs/CHANGELOG.md +62 -0
  253. package/lib/cli/registry.js +262 -19
  254. package/lib/cli/sync-agents.js +157 -0
  255. package/lib/cli/sync-commands.js +115 -6
  256. package/lib/cli/sync.js +168 -6
  257. package/lib/cli/update.js +105 -8
  258. package/lib/cli/version-helpers.js +131 -0
  259. package/lib/migrations/README.md +7 -5
  260. package/lib/migrations/index.js +17 -9
  261. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  262. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  263. package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
  264. package/package.json +2 -2
  265. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  266. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  267. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  268. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  269. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  270. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  271. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  272. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  273. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  274. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  275. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  276. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  277. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  278. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  279. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  280. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  281. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  282. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  283. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  284. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  285. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  286. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  287. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  288. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  289. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  290. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  291. package/.agents/schemas/risk-verdict.schema.json +0 -53
  292. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  293. package/.agents/scripts/analyze-execution.js +0 -444
  294. package/.agents/scripts/check-prepush-recovery.js +0 -90
  295. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  296. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  297. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
  298. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  299. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  300. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  301. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
  302. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
  303. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  304. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  305. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  306. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  307. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  308. package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
  309. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  310. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  311. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  312. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
  313. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  314. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  315. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  316. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
  317. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  318. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
  319. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  320. package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
  321. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
  322. package/.agents/scripts/resolve-plan-run.js +0 -117
  323. package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
@@ -1,187 +0,0 @@
1
- /**
2
- * preflight-cache.js — Epic #3019 / Story #3027.
3
- *
4
- * Legacy cache adapter for the snapshot/DAG envelope the deleted
5
- * `epic-deliver-preflight.js` / `epic-deliver-prepare.js` pair produced
6
- * (pre-v2 Epic `/deliver`). Retained so fingerprint helpers and tests stay
7
- * stable; no live v2 entry seam reads or writes this cache.
8
- *
9
- * Contract:
10
- * - Cache file lives at `<cwd>/temp/epic-<epicId>/preflight-snapshot.json`.
11
- * - Envelope shape:
12
- * {
13
- * epicId: number,
14
- * baseSha: string, // deterministic fingerprint of the Epic
15
- * // snapshot returned by provider.getTicket
16
- * capturedAt: string, // ISO-8601 timestamp
17
- * epic: object, // the snapshot from runSnapshotPhase
18
- * stories: object[], // the open Story tickets from runBuildWaveDagPhase
19
- * waves: Array<Array<object>>, // wave DAG (ids + labels preserved)
20
- * }
21
- * - `computeBaseSha(epic)` derives the cache key from the same provider
22
- * call (`getTicket(epicId)`) used to walk the hierarchy. The hash
23
- * covers the Epic's id, body, labels (sorted), and (when present)
24
- * `updatedAt` — the same fields that drive Story discovery and the
25
- * acceptance start gate (the Epic body's ## Acceptance Table).
26
- * - Cache miss is signalled by `readCache` returning `null`. Callers
27
- * MUST fall back to a fresh snapshot/DAG pass.
28
- * - `writeCache` creates the parent directory recursively and writes
29
- * atomically (write to `<path>.tmp` then rename).
30
- *
31
- * The module is pure JS / node:fs — no provider dependency — so tests
32
- * can exercise it without spinning up a GitHub mock.
33
- */
34
-
35
- import { createHash } from 'node:crypto';
36
- import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
37
- import path from 'node:path';
38
-
39
- /**
40
- * Resolve the on-disk path for an Epic's preflight cache.
41
- *
42
- * @param {{ epicId: number, cwd?: string }} args
43
- * @returns {string}
44
- */
45
- export function preflightCachePath({ epicId, cwd }) {
46
- if (!Number.isInteger(epicId) || epicId <= 0) {
47
- throw new TypeError(
48
- 'preflightCachePath: epicId must be a positive integer',
49
- );
50
- }
51
- const root = cwd ?? process.cwd();
52
- return path.join(root, 'temp', `epic-${epicId}`, 'preflight-snapshot.json');
53
- }
54
-
55
- /**
56
- * Per-ticket fingerprint fields shared by the Epic and each Story in the
57
- * cache key: id, body, sorted labels, and updatedAt. Story bodies carry
58
- * the dependency edges, so hashing them means a Story-dependency edit
59
- * forces a cache miss (Story #4019 — the previous Epic-only key let
60
- * dependency edits slip through unnoticed).
61
- *
62
- * @param {{ id?: number|string, number?: number|string, body?: string, labels?: string[], updatedAt?: string }} ticket
63
- * @returns {{ id: number|string|null, body: string, labels: string[], updatedAt: string|null }}
64
- */
65
- function ticketFingerprint(ticket) {
66
- const t = ticket && typeof ticket === 'object' ? ticket : {};
67
- return {
68
- id: t.id ?? t.number ?? null,
69
- body: typeof t.body === 'string' ? t.body : '',
70
- labels: Array.isArray(t.labels) ? [...t.labels].map(String).sort() : [],
71
- updatedAt: typeof t.updatedAt === 'string' ? t.updatedAt : null,
72
- };
73
- }
74
-
75
- /**
76
- * Stable string fingerprint of an Epic snapshot **plus its Story
77
- * dependency state**. The hash is keyed on the exact fields
78
- * `runSnapshotPhase` reads (`getTicket(epicId)` return value) and on each
79
- * Story's id/body/labels/updatedAt, so that any drift the snapshot or
80
- * wave-DAG phases would observe — including a Story-dependency edit —
81
- * forces a cache miss (Story #4019).
82
- *
83
- * Labels are sorted to absorb GitHub's non-deterministic label order
84
- * across responses; stories are sorted by id so enumeration order never
85
- * perturbs the key. The hash is sha256; we return the full hex digest so
86
- * the cache key is collision-resistant for the lifetime of a delivery.
87
- *
88
- * @param {{ id?: number|string, number?: number|string, body?: string, labels?: string[], updatedAt?: string }} epic
89
- * @param {Array<{ id?: number|string, number?: number|string, body?: string, labels?: string[], updatedAt?: string }>} [stories]
90
- * @returns {string}
91
- */
92
- export function computeBaseSha(epic, stories = []) {
93
- if (!epic || typeof epic !== 'object') {
94
- throw new TypeError('computeBaseSha: epic snapshot must be an object');
95
- }
96
- const epicPrint = ticketFingerprint(epic);
97
- const storyPrints = (Array.isArray(stories) ? stories : [])
98
- .map(ticketFingerprint)
99
- .sort((a, b) => Number(a.id ?? 0) - Number(b.id ?? 0));
100
- const payload = JSON.stringify({ ...epicPrint, stories: storyPrints });
101
- return createHash('sha256').update(payload).digest('hex');
102
- }
103
-
104
- /**
105
- * Read the cached preflight envelope from disk. Returns `null` when the
106
- * file is missing or unreadable — callers MUST treat that as a cache
107
- * miss and fall back to a fresh snapshot/DAG pass.
108
- *
109
- * The function intentionally swallows `ENOENT` and JSON-parse errors so
110
- * a stale or partially-written file does not poison legacy Epic prepare callers.
111
- *
112
- * @param {{ epicId: number, cwd?: string }} args
113
- * @returns {Promise<object | null>}
114
- */
115
- export async function readPreflightCache({ epicId, cwd }) {
116
- const cachePath = preflightCachePath({ epicId, cwd });
117
- let raw;
118
- try {
119
- raw = await readFile(cachePath, 'utf8');
120
- } catch (err) {
121
- if (err && err.code === 'ENOENT') return null;
122
- throw err;
123
- }
124
- try {
125
- const parsed = JSON.parse(raw);
126
- if (!parsed || typeof parsed !== 'object') return null;
127
- if (parsed.epicId !== epicId) return null;
128
- if (typeof parsed.baseSha !== 'string' || !parsed.baseSha) return null;
129
- if (!parsed.epic || typeof parsed.epic !== 'object') return null;
130
- if (!Array.isArray(parsed.stories)) return null;
131
- if (!Array.isArray(parsed.waves)) return null;
132
- return parsed;
133
- } catch {
134
- return null;
135
- }
136
- }
137
-
138
- /**
139
- * Persist the preflight envelope to disk. Writes atomically via tmp +
140
- * rename so concurrent readers never observe a partial file.
141
- *
142
- * @param {{ epicId: number, baseSha: string, epic: object, stories: object[], waves: Array<Array<object>>, cwd?: string, capturedAt?: string }} args
143
- * @returns {Promise<{ cachePath: string, baseSha: string }>}
144
- */
145
- export async function writePreflightCache({
146
- epicId,
147
- baseSha,
148
- epic,
149
- stories,
150
- waves,
151
- cwd,
152
- capturedAt,
153
- }) {
154
- if (!Number.isInteger(epicId) || epicId <= 0) {
155
- throw new TypeError(
156
- 'writePreflightCache: epicId must be a positive integer',
157
- );
158
- }
159
- if (typeof baseSha !== 'string' || !baseSha) {
160
- throw new TypeError(
161
- 'writePreflightCache: baseSha must be a non-empty string',
162
- );
163
- }
164
- if (!epic || typeof epic !== 'object') {
165
- throw new TypeError('writePreflightCache: epic must be an object');
166
- }
167
- if (!Array.isArray(stories)) {
168
- throw new TypeError('writePreflightCache: stories must be an array');
169
- }
170
- if (!Array.isArray(waves)) {
171
- throw new TypeError('writePreflightCache: waves must be an array');
172
- }
173
- const cachePath = preflightCachePath({ epicId, cwd });
174
- await mkdir(path.dirname(cachePath), { recursive: true });
175
- const envelope = {
176
- epicId,
177
- baseSha,
178
- capturedAt: capturedAt ?? new Date().toISOString(),
179
- epic,
180
- stories,
181
- waves,
182
- };
183
- const tmp = `${cachePath}.tmp`;
184
- await writeFile(tmp, `${JSON.stringify(envelope, null, 2)}\n`, 'utf8');
185
- await rename(tmp, cachePath);
186
- return { cachePath, baseSha };
187
- }
@@ -1,155 +0,0 @@
1
- /**
2
- * Pure helpers for resolve-plan-run.js — keep the CLI entry thin so new-file
3
- * CRAP stays under the quality-preview ceiling.
4
- *
5
- * @module lib/orchestration/resolve-plan-run
6
- */
7
-
8
- import { TYPE_LABELS } from '../label-constants.js';
9
- import { buildStoryAdjacency } from '../story-adjacency.js';
10
- import {
11
- normalizePlanRunId,
12
- PLAN_RUN_LABEL_PREFIX,
13
- } from './plan-persist/story-ops.js';
14
-
15
- /**
16
- * Normalize a plan-run id into the canonical label `plan-run::<token>`.
17
- *
18
- * @param {string} raw
19
- * @returns {string}
20
- */
21
- export function normalizePlanRunLabel(raw) {
22
- const token = normalizePlanRunId(raw);
23
- return `${PLAN_RUN_LABEL_PREFIX}${token}`;
24
- }
25
-
26
- /**
27
- * Extract a bare plan-run id from a label or raw token.
28
- *
29
- * @param {string} label
30
- * @returns {string}
31
- */
32
- export function planRunIdFromLabel(label) {
33
- return String(label ?? '')
34
- .trim()
35
- .replace(new RegExp(`^${PLAN_RUN_LABEL_PREFIX}`), '');
36
- }
37
-
38
- /**
39
- * @param {object} issue
40
- * @returns {string[]}
41
- */
42
- export function normalizeIssueLabels(issue) {
43
- const raw = issue?.labels;
44
- if (!Array.isArray(raw)) return [];
45
- return raw
46
- .map((l) => (typeof l === 'string' ? l : l?.name))
47
- .filter((n) => typeof n === 'string' && n.length > 0);
48
- }
49
-
50
- /**
51
- * @param {object} issue
52
- * @returns {{ id: number, title: string, body: string, url: string|null, labels: string[], state: string }|null}
53
- */
54
- export function toStoryRecord(issue) {
55
- const id = Number(issue?.number ?? issue?.id);
56
- if (!Number.isInteger(id) || id <= 0) return null;
57
- const labels = normalizeIssueLabels(issue);
58
- if (!labels.includes(TYPE_LABELS.STORY)) return null;
59
- return {
60
- id,
61
- title: String(issue?.title ?? ''),
62
- body: String(issue?.body ?? ''),
63
- url: issue?.html_url ?? issue?.url ?? null,
64
- labels,
65
- state: String(issue?.state ?? 'open').toLowerCase(),
66
- };
67
- }
68
-
69
- /**
70
- * @param {object[]} stories
71
- * @returns {{ id: number, dependsOn: number[] }[]}
72
- */
73
- export function storiesToDag(stories) {
74
- const adjacency = buildStoryAdjacency(stories, { dropForeign: false });
75
- return stories.map((s) => ({
76
- id: s.id,
77
- dependsOn: adjacency.get(s.id) ?? [],
78
- }));
79
- }
80
-
81
- /**
82
- * Map provider issues into the deliver-router story list + DAG nodes.
83
- *
84
- * @param {object[]} issues
85
- * @param {{ planRunId: string, planRunLabel: string }} meta
86
- * @returns {{ kind: string, planRunId: string, planRunLabel: string, stories: object[], dag: object[] }}
87
- */
88
- export function buildPlanRunEnvelope(issues, { planRunId, planRunLabel }) {
89
- const stories = (Array.isArray(issues) ? issues : [])
90
- .map(toStoryRecord)
91
- .filter(Boolean)
92
- .sort((a, b) => a.id - b.id);
93
-
94
- return {
95
- kind: 'plan-run',
96
- planRunId,
97
- planRunLabel,
98
- stories: stories.map(({ id, title, url, labels, state }) => ({
99
- id,
100
- title,
101
- url,
102
- labels,
103
- state,
104
- })),
105
- dag: storiesToDag(stories),
106
- done: stories
107
- .filter(
108
- (story) =>
109
- story.state === 'closed' || story.labels.includes('agent::done'),
110
- )
111
- .map((story) => story.id),
112
- };
113
- }
114
-
115
- /**
116
- * Pure core used by tests: given issues + meta, build the envelope.
117
- *
118
- * @param {{ run: string, issues: object[] }} args
119
- * @returns {ReturnType<typeof buildPlanRunEnvelope>}
120
- */
121
- export function resolvePlanRunFromIssues({ run, issues }) {
122
- const planRunLabel = normalizePlanRunLabel(run);
123
- const planRunId = planRunIdFromLabel(planRunLabel);
124
- return buildPlanRunEnvelope(issues, { planRunId, planRunLabel });
125
- }
126
-
127
- /**
128
- * Fetch issues for a plan-run label via the ticketing provider.
129
- *
130
- * @param {object} provider
131
- * @param {{ planRunLabel: string, state: string }} args
132
- * @returns {Promise<object[]>}
133
- */
134
- export async function fetchPlanRunIssues(provider, { planRunLabel, state }) {
135
- if (typeof provider?.listIssuesByLabel !== 'function') {
136
- throw new Error('resolve-plan-run: provider.listIssuesByLabel is required');
137
- }
138
- const labels = `${planRunLabel},${TYPE_LABELS.STORY}`;
139
- const issues = await provider.listIssuesByLabel({ state, labels });
140
- return Array.isArray(issues) ? issues : [];
141
- }
142
-
143
- /**
144
- * @param {string|undefined} state
145
- * @returns {string}
146
- */
147
- export function assertIssueState(state) {
148
- const value = state ?? 'open';
149
- if (!['open', 'closed', 'all'].includes(value)) {
150
- throw new Error(
151
- `resolve-plan-run: --state must be open|closed|all (got ${value})`,
152
- );
153
- }
154
- return value;
155
- }
@@ -1,275 +0,0 @@
1
- /**
2
- * lib/orchestration/retro-perf-heuristics.js — classify perf signals.
3
- *
4
- * Story #3042 / Task #3045 (Epic #3019). Pure functions that turn the
5
- * `epic-perf-report.waveParallelism` row shape (Story #3025) into a flat
6
- * list of perf signals. The retro renderer (`./retro/phases/compose-body.js`)
7
- * consumes the list to surface a `## Performance Signals` section and a
8
- * `## Recommended Follow-Ons` paste-ready stanza per signal.
9
- *
10
- * Signals (kinds):
11
- *
12
- * - `low-utilisation` — any wave whose `utilisation < thresholds.utilisation`.
13
- * One signal per offending wave. The signal carries the wave index and
14
- * the observed utilisation so the renderer can name the offender.
15
- *
16
- * - `high-bootstrap-share` — emitted when the summed `story-init` /
17
- * bootstrap phase time across all waves exceeds
18
- * `thresholds.bootstrapShare` of the cumulative `summedStoryMs`. This is
19
- * a single signal across the whole report (not one per wave) because the
20
- * framework remediation is an Epic-wide bootstrap-cost story, not a
21
- * per-wave tweak. The signal payload carries the observed share.
22
- *
23
- * - `cap-binding-run` — emitted when `>= thresholds.capBindingRunLength`
24
- * consecutive waves report `capBinding: true`. One signal per maximal
25
- * run, carrying the run's first/last wave index and the run length so
26
- * the renderer can attribute the run-length back to the deliver-runner
27
- * concurrency cap.
28
- *
29
- * Threshold contract (code constants only — not operator-tunable via
30
- * `.agentrc.json`; the former `delivery.retro.perfThresholds` key was
31
- * retired):
32
- *
33
- * - `utilisation`: number in [0, 1]. Default 0.6.
34
- * - `bootstrapShare`: number in [0, 1]. Default 0.4.
35
- * - `capBindingRunLength`: positive integer. Default 2.
36
- *
37
- * The function is total: it never throws on malformed input. A missing or
38
- * malformed `report` returns `[]`. A missing `thresholds` falls back to
39
- * `DEFAULT_RETRO_PERF_THRESHOLDS` below.
40
- */
41
-
42
- /**
43
- * Framework-fixed defaults for `classifyPerfSignals`. Callers (and tests)
44
- * can omit `thresholds` and get these values. Not sourced from agentrc —
45
- * edit this constant (and keep tests in lockstep) to change the gates.
46
- */
47
- export const DEFAULT_RETRO_PERF_THRESHOLDS = Object.freeze({
48
- utilisation: 0.6,
49
- bootstrapShare: 0.4,
50
- capBindingRunLength: 2,
51
- });
52
-
53
- function isObject(v) {
54
- return v != null && typeof v === 'object' && !Array.isArray(v);
55
- }
56
-
57
- function clampUnit(n, fallback) {
58
- if (typeof n !== 'number' || !Number.isFinite(n)) return fallback;
59
- // Out-of-range values fall back to the documented default rather than
60
- // being clamped — operators wiring nonsensical thresholds should see the
61
- // safe default applied, not a silent 0/1 substitute that subtly changes
62
- // the heuristic.
63
- if (n < 0 || n > 1) return fallback;
64
- return n;
65
- }
66
-
67
- function positiveInt(n, fallback) {
68
- if (typeof n !== 'number' || !Number.isFinite(n)) return fallback;
69
- if (!Number.isInteger(n) || n < 1) return fallback;
70
- return n;
71
- }
72
-
73
- /**
74
- * Resolve the threshold trio against the documented defaults. Exported for
75
- * the test surface so callers can verify the fallback semantics in
76
- * isolation.
77
- *
78
- * @param {object|null|undefined} thresholds
79
- * @returns {{ utilisation: number, bootstrapShare: number, capBindingRunLength: number }}
80
- */
81
- export function resolvePerfThresholds(thresholds) {
82
- const src = isObject(thresholds) ? thresholds : {};
83
- return {
84
- utilisation: clampUnit(
85
- src.utilisation,
86
- DEFAULT_RETRO_PERF_THRESHOLDS.utilisation,
87
- ),
88
- bootstrapShare: clampUnit(
89
- src.bootstrapShare,
90
- DEFAULT_RETRO_PERF_THRESHOLDS.bootstrapShare,
91
- ),
92
- capBindingRunLength: positiveInt(
93
- src.capBindingRunLength,
94
- DEFAULT_RETRO_PERF_THRESHOLDS.capBindingRunLength,
95
- ),
96
- };
97
- }
98
-
99
- /**
100
- * Extract the validated `waveParallelism` rows from the report. Rows whose
101
- * `waveIndex` is not a non-negative integer are dropped (the schema requires
102
- * one, but the heuristics module must tolerate malformed historical reports).
103
- *
104
- * @param {object|null|undefined} report
105
- * @returns {Array<{ waveIndex: number, wallClockMs: number, summedStoryMs: number, utilisation: number, capBinding: boolean }>}
106
- */
107
- function extractRows(report) {
108
- if (!isObject(report)) return [];
109
- const arr = Array.isArray(report.waveParallelism)
110
- ? report.waveParallelism
111
- : [];
112
- return arr
113
- .filter(
114
- (r) => isObject(r) && Number.isInteger(r.waveIndex) && r.waveIndex >= 0,
115
- )
116
- .map((r) => ({
117
- waveIndex: r.waveIndex,
118
- wallClockMs:
119
- typeof r.wallClockMs === 'number' && r.wallClockMs > 0
120
- ? r.wallClockMs
121
- : 0,
122
- summedStoryMs:
123
- typeof r.summedStoryMs === 'number' && r.summedStoryMs > 0
124
- ? r.summedStoryMs
125
- : 0,
126
- utilisation:
127
- typeof r.utilisation === 'number' && Number.isFinite(r.utilisation)
128
- ? r.utilisation
129
- : 0,
130
- capBinding: Boolean(r.capBinding),
131
- }));
132
- }
133
-
134
- /**
135
- * Build the `low-utilisation` signals — one per wave whose `utilisation` is
136
- * strictly below the threshold. Returns `[]` when no row crosses the gate.
137
- *
138
- * Waves with `summedStoryMs <= 0` are skipped unconditionally: when no
139
- * per-Story timing windows are present the computed utilisation is 0.0%
140
- * because the numerator is 0, not because the wave was genuinely idle.
141
- * Scoring unknown timing as low would flood the retro with non-actionable
142
- * follow-ons (Story #3850).
143
- */
144
- function detectLowUtilisation(rows, threshold) {
145
- const out = [];
146
- for (const row of rows) {
147
- // Skip waves with no timing data — utilisation is unknown, not low.
148
- if (row.summedStoryMs <= 0) continue;
149
- if (row.utilisation < threshold) {
150
- out.push({
151
- kind: 'low-utilisation',
152
- waveIndex: row.waveIndex,
153
- utilisation: row.utilisation,
154
- threshold,
155
- });
156
- }
157
- }
158
- return out;
159
- }
160
-
161
- /**
162
- * Sum the bootstrap (story-init) phase time across per-Story summaries on the
163
- * report. Returns the absolute milliseconds. Falls back to 0 when no Story
164
- * summary in the report carries a `phaseTimingsMs['story-init']` entry — in
165
- * that case the share check necessarily returns no signal.
166
- */
167
- function sumBootstrapMs(report) {
168
- if (!isObject(report)) return 0;
169
- const stories = Array.isArray(report.storyPerfSummaries)
170
- ? report.storyPerfSummaries
171
- : [];
172
- let total = 0;
173
- for (const s of stories) {
174
- if (!isObject(s)) continue;
175
- const timings = isObject(s.phaseTimingsMs) ? s.phaseTimingsMs : null;
176
- if (!timings) continue;
177
- const v = timings['story-init'];
178
- if (typeof v === 'number' && Number.isFinite(v) && v > 0) {
179
- total += v;
180
- }
181
- }
182
- return total;
183
- }
184
-
185
- /**
186
- * Build the (at-most-one) `high-bootstrap-share` signal. Returns `[]` when
187
- * either the cumulative `summedStoryMs` is zero (no wave timing) or the
188
- * observed bootstrap share is at-or-below the threshold.
189
- */
190
- function detectHighBootstrapShare(rows, report, threshold) {
191
- const totalStoryMs = rows.reduce((acc, r) => acc + r.summedStoryMs, 0);
192
- if (totalStoryMs <= 0) return [];
193
- const bootstrapMs = sumBootstrapMs(report);
194
- if (bootstrapMs <= 0) return [];
195
- const share = bootstrapMs / totalStoryMs;
196
- if (share <= threshold) return [];
197
- return [
198
- {
199
- kind: 'high-bootstrap-share',
200
- share,
201
- bootstrapMs,
202
- summedStoryMs: totalStoryMs,
203
- threshold,
204
- },
205
- ];
206
- }
207
-
208
- /**
209
- * Walk the rows in `waveIndex` order and emit one `cap-binding-run` signal
210
- * per maximal run of consecutive `capBinding: true` waves whose length is at
211
- * least `threshold`.
212
- */
213
- function detectCapBindingRuns(rows, threshold) {
214
- const ordered = [...rows].sort((a, b) => a.waveIndex - b.waveIndex);
215
- const out = [];
216
- let runStart = null;
217
- let runLen = 0;
218
- for (let i = 0; i < ordered.length; i += 1) {
219
- const row = ordered[i];
220
- if (row.capBinding) {
221
- if (runStart === null) runStart = row.waveIndex;
222
- runLen += 1;
223
- // End of array? Flush the run.
224
- if (i === ordered.length - 1 && runLen >= threshold) {
225
- out.push({
226
- kind: 'cap-binding-run',
227
- fromWaveIndex: runStart,
228
- toWaveIndex: row.waveIndex,
229
- runLength: runLen,
230
- threshold,
231
- });
232
- }
233
- } else {
234
- if (runLen >= threshold && runStart !== null) {
235
- out.push({
236
- kind: 'cap-binding-run',
237
- fromWaveIndex: runStart,
238
- toWaveIndex: ordered[i - 1].waveIndex,
239
- runLength: runLen,
240
- threshold,
241
- });
242
- }
243
- runStart = null;
244
- runLen = 0;
245
- }
246
- }
247
- return out;
248
- }
249
-
250
- /**
251
- * Classify perf signals from an `epic-perf-report` payload.
252
- *
253
- * Always returns an array of signal objects. The array is empty when no
254
- * signal trips. The caller (retro renderer) is responsible for suppressing
255
- * the entire `## Performance Signals` and `## Recommended Follow-Ons`
256
- * sections when this returns `[]`.
257
- *
258
- * @param {object|null|undefined} report `epic-perf-report` payload.
259
- * @param {object|null|undefined} thresholds Optional override of
260
- * `{ utilisation, bootstrapShare, capBindingRunLength }`. Defaults
261
- * resolve via `DEFAULT_RETRO_PERF_THRESHOLDS`.
262
- * @returns {Array<object>} Signal list.
263
- */
264
- export function classifyPerfSignals(report, thresholds) {
265
- const resolved = resolvePerfThresholds(thresholds);
266
- const rows = extractRows(report);
267
- if (rows.length === 0) return [];
268
- const signals = [];
269
- signals.push(...detectLowUtilisation(rows, resolved.utilisation));
270
- signals.push(
271
- ...detectHighBootstrapShare(rows, report, resolved.bootstrapShare),
272
- );
273
- signals.push(...detectCapBindingRuns(rows, resolved.capBindingRunLength));
274
- return signals;
275
- }