mandrel 2.0.0 → 2.1.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 (311) hide show
  1. package/.agents/README.md +59 -28
  2. package/.agents/agents/acceptance-critic.md +9 -7
  3. package/.agents/agents/story-worker.md +41 -46
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +51 -44
  6. package/.agents/docs/agentrc-reference.json +8 -13
  7. package/.agents/docs/configuration.md +32 -56
  8. package/.agents/docs/execution-reference.md +39 -10
  9. package/.agents/docs/quality-gates.md +14 -16
  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/git-conventions-reference.md +42 -51
  14. package/.agents/schemas/agentrc.schema.json +34 -45
  15. package/.agents/schemas/audit-rules.json +59 -1
  16. package/.agents/schemas/audit-rules.schema.json +33 -1
  17. package/.agents/schemas/lifecycle/README.md +1 -2
  18. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  19. package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
  20. package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
  21. package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
  22. package/.agents/schemas/signal-event.schema.json +3 -3
  23. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  24. package/.agents/schemas/validation-evidence.schema.json +1 -1
  25. package/.agents/scripts/acceptance-eval.js +22 -66
  26. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  27. package/.agents/scripts/bootstrap.js +3 -3
  28. package/.agents/scripts/check-dead-exports.js +43 -104
  29. package/.agents/scripts/check-doc-links.js +2 -2
  30. package/.agents/scripts/check-lifecycle-lint.js +1 -1
  31. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  32. package/.agents/scripts/deliver-recover.js +122 -0
  33. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  34. package/.agents/scripts/evidence-gate.js +20 -50
  35. package/.agents/scripts/generate-skills-index.js +17 -1
  36. package/.agents/scripts/generate-workflows-doc.js +4 -4
  37. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  38. package/.agents/scripts/lib/audit-suite/selector.js +323 -23
  39. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
  40. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  41. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  42. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  43. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  44. package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
  45. package/.agents/scripts/lib/checks/index.js +1 -1
  46. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  47. package/.agents/scripts/lib/checks/state.js +17 -248
  48. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
  49. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  50. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  51. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  52. package/.agents/scripts/lib/cli-args.js +23 -2
  53. package/.agents/scripts/lib/close-validation/gates.js +13 -13
  54. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  55. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  56. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  57. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  58. package/.agents/scripts/lib/config/delivery-routing.js +7 -6
  59. package/.agents/scripts/lib/config/explain.js +10 -16
  60. package/.agents/scripts/lib/config/github.js +7 -5
  61. package/.agents/scripts/lib/config/limits.js +15 -25
  62. package/.agents/scripts/lib/config/quality.js +11 -14
  63. package/.agents/scripts/lib/config/runners.js +8 -21
  64. package/.agents/scripts/lib/config/temp-paths.js +18 -56
  65. package/.agents/scripts/lib/config-settings-schema-delivery.js +31 -13
  66. package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
  67. package/.agents/scripts/lib/config-settings-schema.js +48 -22
  68. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  69. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  70. package/.agents/scripts/lib/duplicate-search.js +38 -7
  71. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  72. package/.agents/scripts/lib/format-generated-json.js +97 -0
  73. package/.agents/scripts/lib/framework-version.js +19 -189
  74. package/.agents/scripts/lib/gh-exec.js +8 -0
  75. package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
  76. package/.agents/scripts/lib/git-utils.js +0 -14
  77. package/.agents/scripts/lib/json-utils.js +1 -2
  78. package/.agents/scripts/lib/label-constants.js +0 -15
  79. package/.agents/scripts/lib/label-taxonomy.js +1 -12
  80. package/.agents/scripts/lib/observability/active-story-env.js +42 -163
  81. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  82. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  83. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  84. package/.agents/scripts/lib/observability/source-classifier.js +2 -2
  85. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  86. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  87. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  88. package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
  89. package/.agents/scripts/lib/orchestration/code-review.js +58 -168
  90. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
  91. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  92. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  93. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  94. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  95. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  96. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
  97. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  98. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  99. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
  100. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  101. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  102. package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
  103. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  104. package/.agents/scripts/lib/orchestration/plan-context.js +114 -24
  105. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
  106. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +3 -7
  107. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  108. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
  109. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
  110. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  111. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -75
  112. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
  113. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
  114. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  115. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
  116. package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
  117. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  118. package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
  119. package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
  120. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  121. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  122. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  123. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  124. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  125. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  126. package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
  127. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
  128. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  129. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  130. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +3 -12
  131. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  132. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
  133. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  134. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  135. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
  136. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  137. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  138. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
  139. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  140. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  141. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -32
  142. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  143. package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
  144. package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
  145. package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
  146. package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
  147. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  148. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
  149. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
  150. package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
  151. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
  152. package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
  153. package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
  154. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  155. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  156. package/.agents/scripts/lib/planning-corpus.js +12 -286
  157. package/.agents/scripts/lib/preflight-runner.js +2 -2
  158. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  159. package/.agents/scripts/lib/signals/index.js +4 -17
  160. package/.agents/scripts/lib/signals/read.js +35 -35
  161. package/.agents/scripts/lib/signals/schema.js +8 -11
  162. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  163. package/.agents/scripts/lib/signals/write.js +0 -1
  164. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  165. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  166. package/.agents/scripts/lib/story-adjacency.js +8 -7
  167. package/.agents/scripts/lib/story-body/story-body.js +6 -5
  168. package/.agents/scripts/lib/templates/decomposer-prompts.js +7 -3
  169. package/.agents/scripts/lib/test-env.js +14 -1
  170. package/.agents/scripts/lib/test-tiers.js +0 -3
  171. package/.agents/scripts/lib/ticket-body-sections.js +0 -14
  172. package/.agents/scripts/lib/validation-evidence.js +31 -59
  173. package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
  174. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  175. package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
  176. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  177. package/.agents/scripts/plan-context.js +38 -6
  178. package/.agents/scripts/plan-persist.js +145 -35
  179. package/.agents/scripts/plan-run-epilogue.js +83 -38
  180. package/.agents/scripts/post-structured-comment.js +0 -38
  181. package/.agents/scripts/pr-watch-with-update.js +43 -22
  182. package/.agents/scripts/providers/github/compose.js +0 -1
  183. package/.agents/scripts/providers/github/errors.js +0 -19
  184. package/.agents/scripts/providers/github/issues.js +1 -11
  185. package/.agents/scripts/providers/github/mappers.js +5 -0
  186. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  187. package/.agents/scripts/providers/github/tickets.js +33 -153
  188. package/.agents/scripts/providers/github.js +17 -6
  189. package/.agents/scripts/resolve-stories.js +236 -0
  190. package/.agents/scripts/run-coverage.js +4 -1
  191. package/.agents/scripts/run-lint.js +2 -2
  192. package/.agents/scripts/run-verify.js +31 -2
  193. package/.agents/scripts/signals-view.js +9 -10
  194. package/.agents/scripts/single-story-close.js +173 -18
  195. package/.agents/scripts/single-story-confirm-merge.js +288 -15
  196. package/.agents/scripts/single-story-init.js +6 -10
  197. package/.agents/scripts/stories-wave-tick.js +79 -4
  198. package/.agents/scripts/story-plan.js +3 -3
  199. package/.agents/scripts/update-ticket-state.js +8 -50
  200. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  201. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  202. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  203. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  204. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  205. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  206. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  207. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  208. package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
  209. package/.agents/skills/core/scope-triage/SKILL.md +3 -0
  210. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  211. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  212. package/.agents/skills/skills.index.json +2 -12
  213. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  214. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  215. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  216. package/.agents/workflows/audit-architecture.md +3 -4
  217. package/.agents/workflows/audit-clean-code.md +4 -4
  218. package/.agents/workflows/audit-documentation.md +4 -5
  219. package/.agents/workflows/audit-lighthouse.md +8 -0
  220. package/.agents/workflows/audit-navigability.md +10 -0
  221. package/.agents/workflows/audit-performance.md +2 -3
  222. package/.agents/workflows/audit-quality.md +8 -9
  223. package/.agents/workflows/audit-security.md +1 -2
  224. package/.agents/workflows/audit-seo.md +10 -0
  225. package/.agents/workflows/audit-ux-ui.md +7 -0
  226. package/.agents/workflows/deliver.md +98 -45
  227. package/.agents/workflows/git-cleanup.md +2 -2
  228. package/.agents/workflows/git-deliver.md +1 -1
  229. package/.agents/workflows/helpers/acceptance-self-eval.md +21 -13
  230. package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
  231. package/.agents/workflows/helpers/code-review.md +12 -10
  232. package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
  233. package/.agents/workflows/helpers/deliver-story.md +193 -118
  234. package/.agents/workflows/helpers/parallel-tooling.md +2 -2
  235. package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
  236. package/.agents/workflows/plan.md +184 -19
  237. package/.agents/workflows/qa-assist.md +6 -6
  238. package/.agents/workflows/qa-explore.md +3 -3
  239. package/.agents/workflows/qa-run.md +1 -5
  240. package/bin/mandrel.js +12 -1
  241. package/docs/CHANGELOG.md +40 -0
  242. package/lib/cli/registry.js +262 -19
  243. package/lib/cli/sync-agents.js +157 -0
  244. package/lib/cli/sync-commands.js +115 -6
  245. package/lib/cli/sync.js +168 -6
  246. package/lib/cli/update.js +105 -8
  247. package/lib/cli/version-helpers.js +131 -0
  248. package/lib/migrations/README.md +7 -5
  249. package/lib/migrations/index.js +12 -9
  250. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  251. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  252. package/package.json +1 -1
  253. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  254. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  255. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  256. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  257. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  258. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  259. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  260. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  261. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  262. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  263. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  264. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  265. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  266. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  267. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  268. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  269. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  270. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  271. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  272. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  273. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  274. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  275. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  276. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  277. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  278. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  279. package/.agents/schemas/risk-verdict.schema.json +0 -53
  280. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  281. package/.agents/scripts/analyze-execution.js +0 -444
  282. package/.agents/scripts/check-prepush-recovery.js +0 -90
  283. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  284. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  285. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
  286. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  287. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  288. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  289. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
  290. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
  291. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  292. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  293. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  294. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  295. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  296. package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
  297. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  298. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  299. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  300. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
  301. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  302. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  303. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  304. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
  305. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  306. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
  307. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  308. package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
  309. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
  310. package/.agents/scripts/resolve-plan-run.js +0 -117
  311. package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
@@ -1,29 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://github.com/dsj1984/mandrel/blob/main/.agents/schemas/lifecycle/epic.watch.end.schema.json",
4
- "title": "epic.watch.end",
5
- "description": "Emitted by Watcher when required checks settle. checkOutcomes maps check-name → terminal state (success | failure | timed_out | skipped) or the slow-but-not-failed sentinel `still-running` (Story #4358: the poll cap fired with the check still pending after the resume budget was exhausted — distinct from a genuine `failure`). AutomergePredicate subscribes to this event (test-only Watcher path; the production Phase 8.5 boundary is epic.automerge.start).",
6
- "type": "object",
7
- "required": ["prUrl", "checkOutcomes"],
8
- "properties": {
9
- "prUrl": { "type": "string", "format": "uri", "minLength": 1 },
10
- "checkOutcomes": {
11
- "type": "object",
12
- "additionalProperties": {
13
- "type": "string",
14
- "enum": [
15
- "success",
16
- "failure",
17
- "neutral",
18
- "cancelled",
19
- "timed_out",
20
- "still-running",
21
- "action_required",
22
- "stale",
23
- "skipped"
24
- ]
25
- }
26
- }
27
- },
28
- "additionalProperties": false
29
- }
@@ -1,16 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://github.com/dsj1984/mandrel/blob/main/.agents/schemas/lifecycle/epic.watch.start.schema.json",
4
- "title": "epic.watch.start",
5
- "description": "Emitted by Watcher when required-check polling begins. Required-check names are resolved from GitHub at runtime via gh pr checks, not from .agentrc.json.",
6
- "type": "object",
7
- "required": ["prUrl", "requiredChecks"],
8
- "properties": {
9
- "prUrl": { "type": "string", "format": "uri", "minLength": 1 },
10
- "requiredChecks": {
11
- "type": "array",
12
- "items": { "type": "string", "minLength": 1 }
13
- }
14
- },
15
- "additionalProperties": false
16
- }
@@ -1,20 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://github.com/dsj1984/mandrel/blob/main/.agents/schemas/lifecycle/story.heartbeat.schema.json",
4
- "title": "story.heartbeat",
5
- "description": "Emitted by story-phase.js inside a Story's implementation loop. Surfaces the in-progress Story as an inspectable ledger event so /deliver's host-loop reconciler can confirm a long Story is still making forward progress between dispatch and merge — distinct from story.dispatch.start (one per Story per attempt) and story.merged (one per Story per close). The 2-tier hierarchy has no child Task tickets, so the heartbeat carries phase info only. The optional operator field (Story #3480) records the handle holding the assignee-as-lease claim on the Story so the ticket-lease primitive can decide claim liveness from the most-recent heartbeat for a given owner.",
6
- "type": "object",
7
- "required": ["event", "storyId", "epicId", "phase", "timestamp"],
8
- "properties": {
9
- "event": { "type": "string", "const": "story.heartbeat" },
10
- "storyId": { "type": "integer", "minimum": 1 },
11
- "epicId": { "type": "integer", "minimum": 1 },
12
- "phase": {
13
- "type": "string",
14
- "enum": ["init", "implementing", "closing", "blocked", "done"]
15
- },
16
- "timestamp": { "type": "string", "format": "date-time" },
17
- "operator": { "type": "string", "minLength": 1 }
18
- },
19
- "additionalProperties": false
20
- }
@@ -1,53 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "risk-verdict",
4
- "title": "Planning Risk Verdict",
5
- "description": "Planner-authored risk judgment for an Epic, produced by the epic-plan-spec-author Skill as the fourth planning artifact (risk-verdict.json) alongside the PRD, Tech Spec, and Acceptance Spec (Epic #3865). plan-persist.js validates the verdict against this schema, derives the deterministic planningRisk envelope via deriveRiskEnvelope (lib/orchestration/planning-risk.js), and records the verdict as a risk-verdict structured comment plus the epic-plan-state checkpoint riskVerdict field. The model supplies the judgment input (axes); the harness owns the gate decision. A malformed verdict fails closed to the validation-error path — it is never silently coerced.",
6
- "type": "object",
7
- "required": ["axes", "summary"],
8
- "properties": {
9
- "axes": {
10
- "type": "array",
11
- "description": "Risk axes the planner judges to genuinely apply to the Epic. An empty array asserts no recognized risk axis applies (derives an all-low envelope). Every entry must use the fixed axis vocabulary mirrored from REQUIRED_AXES / NOT_APPLICABLE_AXES in lib/orchestration/planning-risk.js.",
12
- "items": {
13
- "type": "object",
14
- "required": ["axis", "level", "rationale"],
15
- "properties": {
16
- "axis": {
17
- "type": "string",
18
- "enum": [
19
- "visible-behavior",
20
- "public-api",
21
- "security",
22
- "data-migration",
23
- "billing",
24
- "destructive-mutation",
25
- "critical-workflow",
26
- "docs-only",
27
- "test-harness",
28
- "internal-refactor"
29
- ],
30
- "description": "Risk axis identifier. The first seven force a required acceptance disposition when present (REQUIRED_AXES); the last three waive the acceptance spec when they are the only signals (NOT_APPLICABLE_AXES)."
31
- },
32
- "level": {
33
- "type": "string",
34
- "enum": ["low", "medium", "high"],
35
- "description": "Planner-judged severity of this axis for the Epic's actual change, not keyword presence — e.g. a PRD sentence saying 'out of scope: billing' is not a billing change."
36
- },
37
- "rationale": {
38
- "type": "string",
39
- "minLength": 1,
40
- "description": "Concrete, reviewer-readable justification grounded in the PRD / Tech Spec the planner just authored. Never an empty self-attestation."
41
- }
42
- },
43
- "additionalProperties": false
44
- }
45
- },
46
- "summary": {
47
- "type": "string",
48
- "minLength": 1,
49
- "description": "One-paragraph overall risk narrative for the audit trail (the rationale the retired regex classifier could never produce)."
50
- }
51
- },
52
- "additionalProperties": false
53
- }
@@ -1,73 +0,0 @@
1
- {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://github.com/dsj1984/mandrel/blob/main/.agents/schemas/story-perf-summary.schema.json",
4
- "title": "StoryPerfSummary",
5
- "description": "Payload of the <!-- structured:story-perf-summary --> comment posted once per Story at close (Epic #1030). Replaces the per-Task friction comment fanout and the standalone phase-timings comment.",
6
- "type": "object",
7
- "additionalProperties": false,
8
- "required": [
9
- "kind",
10
- "storyId",
11
- "epicId",
12
- "closedAt",
13
- "frictionByCategory",
14
- "phaseTimingsMs",
15
- "topSlowPhasesVsBaseline",
16
- "reworkScore",
17
- "retryDensity"
18
- ],
19
- "properties": {
20
- "kind": { "type": "string", "const": "story-perf-summary" },
21
- "storyId": { "type": "integer", "minimum": 1 },
22
- "epicId": { "type": "integer", "minimum": 1 },
23
- "closedAt": { "type": "string", "format": "date-time" },
24
- "frictionByCategory": {
25
- "type": "object",
26
- "description": "Counts of friction signals bucketed by category for this Story.",
27
- "additionalProperties": { "type": "integer", "minimum": 0 }
28
- },
29
- "phaseTimingsMs": {
30
- "type": "object",
31
- "description": "Elapsed ms per phase, sourced from phase-timer.js in-process record.",
32
- "additionalProperties": { "type": "integer", "minimum": 0 }
33
- },
34
- "topSlowPhasesVsBaseline": {
35
- "type": "array",
36
- "items": {
37
- "type": "object",
38
- "additionalProperties": false,
39
- "required": ["phase", "elapsedMs", "baselineP95Ms", "ratio"],
40
- "properties": {
41
- "phase": { "type": "string", "minLength": 1 },
42
- "elapsedMs": { "type": "integer", "minimum": 0 },
43
- "baselineP95Ms": { "type": "integer", "minimum": 0 },
44
- "ratio": { "type": "number", "minimum": 0 }
45
- }
46
- }
47
- },
48
- "reworkScore": {
49
- "type": "object",
50
- "additionalProperties": false,
51
- "required": ["filesEditedBeyondThreshold"],
52
- "properties": {
53
- "filesEditedBeyondThreshold": { "type": "integer", "minimum": 0 },
54
- "topPath": { "type": ["string", "null"], "minLength": 1 },
55
- "topPathEdits": { "type": ["integer", "null"], "minimum": 0 }
56
- }
57
- },
58
- "retryDensity": {
59
- "type": "object",
60
- "additionalProperties": false,
61
- "required": ["retries", "uniqueCommands"],
62
- "properties": {
63
- "retries": { "type": "integer", "minimum": 0 },
64
- "uniqueCommands": { "type": "integer", "minimum": 0 }
65
- }
66
- },
67
- "ghSpawnCount": {
68
- "type": "integer",
69
- "minimum": 0,
70
- "description": "Throw-away measurement field (Story #1795 / Epic #1788): the in-process `gh` spawn counter snapshot at end of close validation. Used to verify the ≥100 fewer-spawns acceptance criterion against the baseline recorded under `temp/epic-1788/`. Removed in a follow-up cleanup commit before the Story merges to the Epic branch."
71
- }
72
- }
73
- }
@@ -1,444 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * analyze-execution.js — single writer of the structured perf-summary
5
- * comments (Epic #1030 / Story #1123).
6
- *
7
- * Reads NDJSON from `temp/epic-<eid>/stories/story-<sid>/signals.ndjson` and
8
- * upserts one of two structured comments:
9
- *
10
- * - `--story <sid> --epic <eid>` (Story mode): posts
11
- * `<!-- structured:story-perf-summary -->` on the Story ticket. The
12
- * payload combines the Story's NDJSON signals with the timing
13
- * summary written by `post-merge-close.js` to
14
- * `temp/epic-<eid>/stories/story-<sid>/phase-timings.json` (path overridable
15
- * via `--phase-timings <path>`).
16
- *
17
- * - `--epic <eid>` (Epic mode): rolls up every Story under the Epic by
18
- * fetching each `story-perf-summary` structured comment from the
19
- * ticketing provider. Posts the
20
- * `<!-- structured:epic-perf-report -->` comment on the Epic ticket.
21
- * Run from the retro composer / `/deliver` Phase 6.0.
22
- *
23
- * Both modes are idempotent: `upsertStructuredComment` deletes the prior
24
- * marker before posting the new one.
25
- *
26
- * Exit code is `0` on success, `0` on tolerated soft failures (missing
27
- * NDJSON for a Story, no children for an Epic) so the close pipelines
28
- * never block on observability output. Hard failures (bad CLI args,
29
- * provider error, schema-violating payload) exit non-zero — the call
30
- * sites in `single-story-close.js` / `helpers/deliver-story.md` treat that
31
- * as a non-fatal warning.
32
- *
33
- * This file is a thin CLI: it wires the I/O readers from
34
- * `lib/observability/perf-report-readers.js` to the pure renderers in
35
- * `lib/observability/perf-report-render.js` inside `runStoryMode` /
36
- * `runEpicMode`, then parses argv and dispatches. The report-formatting
37
- * and signal-reading logic live in those two modules so report formatting
38
- * is unit-testable without fixtures (Story #3350).
39
- *
40
- * Usage:
41
- * node .agents/scripts/analyze-execution.js --story <sid> --epic <eid> \
42
- * [--phase-timings <path>]
43
- * node .agents/scripts/analyze-execution.js --epic <eid>
44
- * node .agents/scripts/analyze-execution.js --epic <eid> --plan-metrics-json
45
- *
46
- * `--plan-metrics-json` (Epic #4474 PR7 — the G2 extraction surface) is a
47
- * **local read-only** mode: it reads the per-Epic plan-metrics ledger
48
- * (`temp/epic-<eid>/plan-metrics.json`), rolls it up with
49
- * `summarizePlanMetrics`, and prints one compact JSON envelope
50
- * `{ epicId, planMetrics, summaryLine }` to stdout — no provider call, no
51
- * comment write, all logs on stderr — so a bench cohort can extract
52
- * turns-per-plan / per-mode invocation counts / critic skips without
53
- * scraping GitHub or the human log.
54
- *
55
- * @see docs/data-dictionary.md §StoryPerfSummary, §EpicPerfReport
56
- */
57
-
58
- import fs from 'node:fs/promises';
59
- import path from 'node:path';
60
- import { parseArgs } from 'node:util';
61
- import { runAsCli } from './lib/cli-utils.js';
62
- import { signalsFile } from './lib/config/temp-paths.js';
63
- import { PROJECT_ROOT, resolveConfig } from './lib/config-resolver.js';
64
- import { Logger, routeAllOutputToStderr } from './lib/Logger.js';
65
- import { computeBaselineRefreshRate } from './lib/observability/baseline-refresh-rate.js';
66
- import {
67
- computeEpicPerfReport,
68
- computeStoryPerfSummary,
69
- } from './lib/observability/perf-aggregator.js';
70
- import {
71
- aggregateBaselineFrictionFromSignals,
72
- collectStorySummaries,
73
- gatherEpicCommitsFromGit,
74
- readEpicLifecycleEvents,
75
- readGhSpawnCount,
76
- readPhaseTimings,
77
- readStorySignals,
78
- } from './lib/observability/perf-report-readers.js';
79
- import {
80
- EPIC_PERF_TYPE,
81
- renderEpicBody,
82
- renderStoryBody,
83
- STORY_PERF_TYPE,
84
- } from './lib/observability/perf-report-render.js';
85
- import {
86
- readPlanMetrics,
87
- renderPlanMetricsSummaryLine,
88
- summarizePlanMetrics,
89
- } from './lib/orchestration/plan-metrics.js';
90
- import { upsertStructuredComment } from './lib/orchestration/ticketing.js';
91
- import { createProvider } from './lib/provider-factory.js';
92
-
93
- // Re-exported so existing importers (tests, retro composer) keep a stable
94
- // entry point after the read/render split (Story #3350).
95
- export { extractStoryPerfSummaryFromComment } from './lib/observability/perf-report-render.js';
96
-
97
- /**
98
- * Resolve the wave-execution concurrency cap from the resolved config
99
- * (`delivery.deliverRunner.concurrencyCap`), falling back to the
100
- * dispatcher's safe default of 2. Story #3025 / Task #3030.
101
- */
102
- function resolveConcurrencyCap(config) {
103
- const cap = config?.delivery?.deliverRunner?.concurrencyCap;
104
- return Number.isInteger(cap) && cap >= 1 ? cap : 2;
105
- }
106
-
107
- /**
108
- * Resolve the verify-results concurrency cap from the resolved config
109
- * (`delivery.deliverRunner.verifyConcurrencyCap`), falling back to 4.
110
- * Story #3025 / Task #3030.
111
- */
112
- function resolveVerifyConcurrencyCap(config) {
113
- const cap = config?.delivery?.deliverRunner?.verifyConcurrencyCap;
114
- return Number.isInteger(cap) && cap >= 1 ? cap : 4;
115
- }
116
-
117
- /**
118
- * Story mode: read NDJSON + phase-timings, build the payload, upsert.
119
- *
120
- * @param {{
121
- * storyId: number,
122
- * epicId: number,
123
- * phaseTimingsPath?: string|null,
124
- * provider: object,
125
- * config: object,
126
- * logger?: object,
127
- * now?: () => Date,
128
- * }} ctx
129
- * @returns {Promise<{ commentId: number, payload: object }>}
130
- */
131
- export async function runStoryMode(ctx) {
132
- const { storyId, epicId, provider, config } = ctx;
133
- const logger = ctx.logger ?? Logger;
134
- const now = ctx.now ?? (() => new Date());
135
-
136
- logger.info?.(
137
- `[analyze-execution] story-mode story=#${storyId} epic=#${epicId}`,
138
- );
139
-
140
- const events = await readStorySignals(epicId, storyId, config);
141
- const phaseTiming = await readPhaseTimings(
142
- epicId,
143
- storyId,
144
- config,
145
- ctx.phaseTimingsPath ?? null,
146
- );
147
- const ghSpawnCount = await readGhSpawnCount(epicId, storyId, config);
148
-
149
- const payload = computeStoryPerfSummary(events, {
150
- storyId,
151
- epicId,
152
- closedAt: now().toISOString(),
153
- phaseTiming,
154
- });
155
- if (ghSpawnCount !== null) {
156
- // Throw-away measurement field (Story #1795). Surfaces the in-process
157
- // `gh-exec.getSpawnCount()` snapshot captured at the end of close
158
- // validation so the Story-close structured comment can be diffed
159
- // against the baseline recorded under `temp/epic-1788/` for the
160
- // "≥100 fewer spawns" acceptance criterion.
161
- payload.ghSpawnCount = ghSpawnCount;
162
- }
163
-
164
- const body = renderStoryBody(payload);
165
- const result = await upsertStructuredComment(
166
- provider,
167
- storyId,
168
- STORY_PERF_TYPE,
169
- body,
170
- );
171
- logger.info?.(
172
- `[analyze-execution] story-perf-summary upserted on Story #${storyId} (commentId=${result.commentId})`,
173
- );
174
- return { commentId: result.commentId, payload };
175
- }
176
-
177
- /**
178
- * Epic mode: collect every Story's perf summary, roll them up, upsert
179
- * the epic-perf-report comment.
180
- *
181
- * @param {{
182
- * epicId: number,
183
- * provider: object,
184
- * config?: object,
185
- * cwd?: string,
186
- * windowDays?: number,
187
- * logger?: object,
188
- * now?: () => Date,
189
- * collectSummariesFn?: typeof collectStorySummaries,
190
- * gatherEpicCommitsFn?: typeof gatherEpicCommitsFromGit,
191
- * aggregateFrictionFn?: typeof aggregateBaselineFrictionFromSignals,
192
- * }} ctx
193
- * @returns {Promise<{ commentId: number, payload: object, baselineRefreshRate: object | null, qualityGateFriction: object | null }>}
194
- */
195
- export async function runEpicMode(ctx) {
196
- const { epicId, provider } = ctx;
197
- const logger = ctx.logger ?? Logger;
198
- const now = ctx.now ?? (() => new Date());
199
- const windowDays =
200
- Number.isFinite(ctx.windowDays) && ctx.windowDays > 0
201
- ? Math.floor(ctx.windowDays)
202
- : 28;
203
- const collectFn = ctx.collectSummariesFn ?? collectStorySummaries;
204
- const gatherCommitsFn = ctx.gatherEpicCommitsFn ?? gatherEpicCommitsFromGit;
205
- const aggregateFrictionFn =
206
- ctx.aggregateFrictionFn ?? aggregateBaselineFrictionFromSignals;
207
-
208
- logger.info?.(`[analyze-execution] epic-mode epic=#${epicId}`);
209
-
210
- const summaries = await collectFn(provider, epicId, logger);
211
-
212
- // Story #3025 / Task #3030 — forward lifecycle events to the
213
- // aggregator so the report's `waveParallelism` array is populated
214
- // (the analyzer used to omit the field, leaving the report at `[]`).
215
- const readLifecycleEventsFn =
216
- ctx.readLifecycleEventsFn ?? readEpicLifecycleEvents;
217
- const lifecycleEvents = ctx.config
218
- ? await readLifecycleEventsFn(epicId, ctx.config, logger)
219
- : [];
220
- const concurrencyCap = resolveConcurrencyCap(ctx.config);
221
- const verifyConcurrencyCap = resolveVerifyConcurrencyCap(ctx.config);
222
-
223
- // Only forward `events` when we actually have something — passing an
224
- // empty array suppresses the friction-from-summaries fallback in
225
- // `computeEpicPerfReport.buildSignalCounts`, which would zero the
226
- // counts for callers that have summaries but no lifecycle stream
227
- // (e.g. legacy/test paths without a `config`).
228
- const reportOpts = {
229
- epicId,
230
- generatedAt: now().toISOString(),
231
- concurrencyCap,
232
- verifyConcurrencyCap,
233
- };
234
- if (lifecycleEvents.length > 0) {
235
- reportOpts.events = lifecycleEvents;
236
- }
237
-
238
- const payload = computeEpicPerfReport(summaries, reportOpts);
239
-
240
- // Story #1400 / Task #1427 — baseline-refresh-rate row. Pure reporter
241
- // operates on a fixture of git-log records gathered by the injected
242
- // `gatherEpicCommitsFn`. A spawn failure surfaces as `[]` and degrades
243
- // to the "no Story merges in window" line in the rendered body.
244
- let baselineRefreshRate = null;
245
- try {
246
- const commits = await gatherCommitsFn({
247
- epicId,
248
- windowDays,
249
- cwd: ctx.cwd ?? process.cwd(),
250
- logger,
251
- });
252
- baselineRefreshRate = computeBaselineRefreshRate(commits, {
253
- windowDays,
254
- now,
255
- });
256
- } catch (err) {
257
- logger.warn?.(
258
- `[analyze-execution] baseline-refresh-rate gather failed (non-fatal): ${
259
- err instanceof Error ? err.message : String(err)
260
- }`,
261
- );
262
- }
263
-
264
- // Story #1400 / Task #1429 — Quality gate friction block. Aggregates
265
- // `baseline-refresh-regression` records from the existing
266
- // signals.ndjson stream (no new file format).
267
- let qualityGateFriction = null;
268
- if (ctx.config) {
269
- try {
270
- const storyIds = (summaries ?? [])
271
- .map((s) => s?.storyId)
272
- .filter((n) => Number.isInteger(n) && n > 0);
273
- qualityGateFriction = await aggregateFrictionFn({
274
- epicId,
275
- storyIds,
276
- config: ctx.config,
277
- windowDays,
278
- now,
279
- });
280
- } catch (err) {
281
- logger.warn?.(
282
- `[analyze-execution] quality-gate-friction aggregate failed (non-fatal): ${
283
- err instanceof Error ? err.message : String(err)
284
- }`,
285
- );
286
- }
287
- }
288
-
289
- const body = renderEpicBody(payload, {
290
- baselineRefreshRate,
291
- qualityGateFriction,
292
- });
293
- const result = await upsertStructuredComment(
294
- provider,
295
- epicId,
296
- EPIC_PERF_TYPE,
297
- body,
298
- );
299
- logger.info?.(
300
- `[analyze-execution] epic-perf-report upserted on Epic #${epicId} (commentId=${result.commentId}, stories=${summaries.length})`,
301
- );
302
-
303
- // Plan-metrics roll-up (#4474 PR1) — surface the plan-CLI invocation
304
- // ledger (`temp/epic-<id>/plan-metrics.json`) alongside the perf report.
305
- // Additive and non-fatal: a missing or unreadable ledger yields `null`.
306
- let planMetrics = null;
307
- try {
308
- planMetrics = summarizePlanMetrics(
309
- await readPlanMetrics(epicId, ctx.config),
310
- );
311
- if (planMetrics) {
312
- logger.info?.(
313
- `[analyze-execution] ${renderPlanMetricsSummaryLine(planMetrics)}`,
314
- );
315
- }
316
- } catch (err) {
317
- logger.warn?.(
318
- `[analyze-execution] plan-metrics read failed (non-fatal): ${
319
- err instanceof Error ? err.message : String(err)
320
- }`,
321
- );
322
- }
323
-
324
- return {
325
- commentId: result.commentId,
326
- payload,
327
- baselineRefreshRate,
328
- qualityGateFriction,
329
- planMetrics,
330
- };
331
- }
332
-
333
- function parseCli(argv) {
334
- const { values } = parseArgs({
335
- args: argv,
336
- options: {
337
- story: { type: 'string' },
338
- epic: { type: 'string' },
339
- 'phase-timings': { type: 'string' },
340
- 'window-days': { type: 'string' },
341
- 'plan-metrics-json': { type: 'boolean', default: false },
342
- cwd: { type: 'string' },
343
- },
344
- strict: false,
345
- });
346
- const story = values.story != null ? Number.parseInt(values.story, 10) : null;
347
- const epic = values.epic != null ? Number.parseInt(values.epic, 10) : null;
348
- const windowDays =
349
- values['window-days'] != null
350
- ? Number.parseInt(values['window-days'], 10)
351
- : null;
352
- return {
353
- storyId: Number.isInteger(story) && story > 0 ? story : null,
354
- epicId: Number.isInteger(epic) && epic > 0 ? epic : null,
355
- phaseTimingsPath: values['phase-timings'] ?? null,
356
- windowDays:
357
- Number.isInteger(windowDays) && windowDays > 0 ? windowDays : null,
358
- planMetricsJson: values['plan-metrics-json'] === true,
359
- cwd: values.cwd ?? null,
360
- };
361
- }
362
-
363
- /**
364
- * `--plan-metrics-json` — local, read-only G2 extraction surface (Epic
365
- * #4474 PR7). Reads the per-Epic plan-metrics ledger and prints one
366
- * compact JSON envelope to stdout. No provider, no comment write; stdout
367
- * is reserved for the envelope (all logs on stderr). A missing ledger is
368
- * not an error — it yields `planMetrics: null` so a cohort script can
369
- * branch on the field instead of the exit code.
370
- *
371
- * @param {{ epicId: number, config: object }} input
372
- * @returns {Promise<{ epicId: number, planMetrics: object|null, summaryLine: string }>}
373
- */
374
- export async function runPlanMetricsJsonMode({ epicId, config }) {
375
- const planMetrics = summarizePlanMetrics(
376
- await readPlanMetrics(epicId, config),
377
- );
378
- return {
379
- epicId,
380
- planMetrics,
381
- summaryLine: renderPlanMetricsSummaryLine(planMetrics),
382
- };
383
- }
384
-
385
- async function main(argv = process.argv.slice(2)) {
386
- const args = parseCli(argv);
387
-
388
- if (!args.epicId) {
389
- throw new Error(
390
- 'Usage: analyze-execution.js --epic <eid> [--story <sid>] [--phase-timings <path>]',
391
- );
392
- }
393
-
394
- const cwd = path.resolve(args.cwd ?? PROJECT_ROOT);
395
- const config = resolveConfig({ cwd });
396
-
397
- if (args.planMetricsJson) {
398
- // Stdout is reserved for the compact envelope — flip every log sink to
399
- // stderr before any pipeline code runs (Story #2278 discipline).
400
- routeAllOutputToStderr();
401
- const result = await runPlanMetricsJsonMode({
402
- epicId: args.epicId,
403
- config,
404
- });
405
- process.stdout.write(`${JSON.stringify(result)}\n`);
406
- return;
407
- }
408
-
409
- const provider = createProvider(config);
410
-
411
- if (args.storyId) {
412
- // Existence guard: surface a clear log when the Story has no signals
413
- // yet, but still post the comment with empty arrays so the marker
414
- // exists for idempotence checks downstream.
415
- const sigPath = signalsFile(args.epicId, args.storyId, config);
416
- try {
417
- await fs.access(sigPath);
418
- } catch {
419
- Logger.info?.(
420
- `[analyze-execution] no signals.ndjson at ${sigPath} — posting empty story-perf-summary`,
421
- );
422
- }
423
- const result = await runStoryMode({
424
- storyId: args.storyId,
425
- epicId: args.epicId,
426
- phaseTimingsPath: args.phaseTimingsPath,
427
- provider,
428
- config,
429
- });
430
- process.stdout.write(`${JSON.stringify(result)}\n`);
431
- return;
432
- }
433
-
434
- const result = await runEpicMode({
435
- epicId: args.epicId,
436
- provider,
437
- config,
438
- cwd,
439
- windowDays: args.windowDays ?? undefined,
440
- });
441
- process.stdout.write(`${JSON.stringify(result)}\n`);
442
- }
443
-
444
- runAsCli(import.meta.url, main, { source: 'analyze-execution' });