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
@@ -82,27 +82,13 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
82
82
  'epic-run-progress',
83
83
  'epic-plan-state',
84
84
  'parked-follow-ons',
85
- // Story #566 — per-phase wall-clock summary posted by single-story-close.js
86
- // and consumed by analyze-execution / perf tooling to surface median /
87
- // p95 phase timings across completed stories.
85
+ // Story #566 — per-phase wall-clock summary posted by single-story-close.js.
88
86
  'phase-timings',
89
87
  // Story #831 — story-init upserts a `story-init` comment that
90
88
  // surfaces `dependenciesInstalled` (and the underlying installStatus) so
91
89
  // downstream workflow steps don't have to infer install state from
92
90
  // node_modules presence.
93
91
  'story-init',
94
- // Story #908 — /deliver upserts a `story-run-progress` snapshot
95
- // on each Story per Task transition. The /deliver aggregator and
96
- // `analyze-execution.js` both read this comment to derive
97
- // Story-level state without re-fetching ticket labels.
98
- 'story-run-progress',
99
- // Story #1123 — analyze-execution.js upserts perf summaries at close
100
- // time. Story-mode posts `story-perf-summary` on each Story; Epic-mode
101
- // posts `epic-perf-report` on the Epic. Both replace the legacy
102
- // per-Task `friction` fan-out and the standalone `phase-timings`
103
- // surface (Epic #1030).
104
- 'story-perf-summary',
105
- 'epic-perf-report',
106
92
  // Story #2128 — Phase 6 Epic Clarity Gate (CLI retired). Historical
107
93
  // `clarity-gate-update` comments may still exist on older tickets.
108
94
  'clarity-gate-update',
@@ -151,19 +137,13 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
151
137
  // the documented remediation actually executable
152
138
  // (assertValidStructuredCommentType would otherwise throw).
153
139
  'wave-stall',
154
- // Story #3873 (Epic #3865) — `plan-persist.js` upserts a `risk-verdict`
155
- // comment on the Epic at persist time, recording the planner-authored,
156
- // schema-validated risk verdict and the planningRisk envelope derived
157
- // from it (`deriveRiskEnvelope`). One entry per Epic; re-plans upsert in
158
- // place. Schema: `.agents/schemas/risk-verdict.schema.json`.
159
- 'risk-verdict',
160
- // Story #4019 `epic-plan-lease-guard.js` upserts a `plan-lease`
161
- // comment on the Epic at lease-acquire time, recording the claiming
162
- // operator and the claim timestamp. `/plan` emits no
163
- // `story.heartbeat`, so this claim-time is the liveness signal that
164
- // makes the documented `--steal` contract decidable: a foreign claim
165
- // older than the lease TTL is reclaimed automatically; a fresh one
166
- // refuses with the claim age. One entry per Epic; re-acquires upsert
140
+ // Story #4019 the plan-tier lease guard upserts a `plan-lease`
141
+ // comment at lease-acquire time, recording the claiming operator and the
142
+ // claim timestamp. Nothing emits a per-run liveness beat, so this
143
+ // claim-time is the only age signal a reader has when reasoning about the
144
+ // documented `--steal` contract. The guard itself fails closed regardless
145
+ // (it anchors liveness to `now`), so a stranded claim is cleared with
146
+ // `--steal`, not by TTL expiry. One entry per ticket; re-acquires upsert
167
147
  // in place.
168
148
  'plan-lease',
169
149
  // Story #4415 (Epic #4406) — the feedback-loop graduators
@@ -183,6 +163,13 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
183
163
  // v2 Stage 3 — flat Story persist checkpoint on every created Story
184
164
  // (replaces epic-plan-state for new plans). plan-summary stays primary-only.
185
165
  'story-plan-state',
166
+ // Story #4535 — `plan-persist.js` upserts a `superseded-by` comment on
167
+ // each `/plan --tickets` source issue at persist time, naming the single
168
+ // Story that claims it (plus any per-supersede note the plan authored),
169
+ // immediately before closing the issue as `not_planned`. Keying off this
170
+ // marker rather than a bare `postComment` is what makes a re-run
171
+ // non-double-commenting. One entry per source issue.
172
+ 'superseded-by',
186
173
  ]);
187
174
 
188
175
  export const WAVE_TYPE_PATTERN = WAVE_MARKER_RE;
@@ -268,8 +255,8 @@ export function structuredCommentMarker(type, attrs = null) {
268
255
  * recent `findStructuredComment` / `upsertStructuredComment` call.
269
256
  * Story #1795 — every Epic run owns its process for the duration of
270
257
  * the run, so a single seed-then-reuse window saves one
271
- * `getTicketComments` per repeat upsert on the hot path (wave-level
272
- * `story-run-progress`, `wave-N-end`, etc).
258
+ * `getTicketComments` per repeat upsert on the hot path (`story-init`,
259
+ * `verification-results`, etc).
273
260
  *
274
261
  * Lifecycle:
275
262
  * - First call to `findStructuredComment(provider, t, type, attrs)`
@@ -355,8 +342,8 @@ export function structuredCommentCacheKey(ticketId, type, attrs) {
355
342
  * array returned by the most recent `provider.getTicketComments(ticketId)`
356
343
  * call. Story #2465 — `findStructuredComment` is invoked back-to-back for
357
344
  * different `type` discriminators against the same ticket (e.g.
358
- * `story-run-progress` + `epic-run-progress` + a `friction` probe during
359
- * an Epic-close wave). Without this cache each lookup pays a full
345
+ * `story-init` + `verification-results` + a `friction` probe during a
346
+ * single close). Without this cache each lookup pays a full
360
347
  * pagination round-trip even when the prior call already fetched the
361
348
  * same comments. The structured-comment-id cache short-circuits *repeat*
362
349
  * lookups for the same `(type, attrs)` tuple but does not help across
@@ -37,6 +37,10 @@ import {
37
37
  eventSeverity,
38
38
  renderTransitionMessage,
39
39
  } from '../../notifications/notifier.js';
40
+ import {
41
+ emitRuntimeFriction,
42
+ RUNTIME_FRICTION_CATEGORIES,
43
+ } from '../../observability/runtime-friction.js';
40
44
  import { ColumnSync } from '../column-sync.js';
41
45
  import {
42
46
  ALL_STATES,
@@ -279,6 +283,46 @@ function dispatchTransitionNotification(args) {
279
283
  });
280
284
  }
281
285
 
286
+ /**
287
+ * Emit a runtime-derived `friction` signal when a ticket parks at
288
+ * `agent::blocked` (Story #4578). No-op for every other target state.
289
+ *
290
+ * Why here: `agent::blocked` is the single runtime HITL pause point
291
+ * (`.agents/instructions.md` § 1.J), and this is its canonical mutator — so
292
+ * one hook catches every block regardless of which path drove it (the
293
+ * `merge.unlanded` and `merge.flip-failed` paths in
294
+ * `single-story-close/phases/confirm-merge.js`, the review-block path, an
295
+ * operator's `update-ticket-state.js`, a Story worker giving up). Before
296
+ * this, a block was only ever a *label* — it left no trace in the friction
297
+ * stream the retro reads, so a run could park a worker and still produce a
298
+ * zero-signal roll-up.
299
+ *
300
+ * This is also why the terminal-envelope hook deliberately skips `blocked`
301
+ * (see `frictionForTerminal`): the two would otherwise count one incident
302
+ * twice.
303
+ *
304
+ * Best-effort and awaited: `emitRuntimeFriction` swallows its own failures
305
+ * and resolves `false`, so this can neither throw nor block the transition.
306
+ * It is awaited rather than fire-and-forget because CLI entry points exit
307
+ * via `process.exit` as soon as `main` resolves (`cli-utils.runAsCli` with
308
+ * `propagateExitCode`), which would discard a still-pending append.
309
+ *
310
+ * @param {number} ticketId
311
+ * @param {string} newState
312
+ * @param {{ config?: object }} opts
313
+ * @returns {Promise<void>}
314
+ */
315
+ async function emitBlockedFriction(ticketId, newState, opts) {
316
+ if (newState !== STATE_LABELS.BLOCKED) return;
317
+ await emitRuntimeFriction({
318
+ storyId: ticketId,
319
+ category: RUNTIME_FRICTION_CATEGORIES.STORY_BLOCKED,
320
+ tool: 'transitionTicketState',
321
+ details: { toState: newState },
322
+ config: opts?.config,
323
+ });
324
+ }
325
+
282
326
  /**
283
327
  * Transitions a ticket's label to the new state.
284
328
  * Removes other agent:: state labels.
@@ -361,6 +405,10 @@ export async function transitionTicketState(
361
405
  _ticketSnapshot: ticketSnapshot,
362
406
  });
363
407
 
408
+ // Story #4578 — derive a friction signal from the block, at the point the
409
+ // runtime already knows. Best-effort; never blocks the transition.
410
+ await emitBlockedFriction(ticketId, newState, opts);
411
+
364
412
  // Story #2548 — mirror the new state onto the Projects v2 Status
365
413
  // column. Best-effort; never blocks the transition.
366
414
  // Story #3645 — thread the DIP seam so callers can inject a stub.
@@ -460,14 +508,25 @@ export async function toggleTasklistCheckbox(
460
508
  /**
461
509
  * Post a structured comment to a ticket.
462
510
  *
511
+ * Returns whatever the provider's `postComment` resolved to (Story #4543).
512
+ * Previously the result was swallowed, which made it impossible for a caller
513
+ * to reference the comment it had just written — the terminal envelope's
514
+ * `blocked.frictionCommentId` pointer needs exactly that, so an operator can
515
+ * be sent straight to the remediation instead of told to go find it. Callers
516
+ * that don't need the id simply ignore the return, so this is additive.
517
+ *
518
+ * The shape is provider-defined and may be `undefined` for providers that do
519
+ * not surface one; callers must treat the id as best-effort.
520
+ *
463
521
  * @param {import('../../ITicketingProvider.js').ITicketingProvider} provider
464
522
  * @param {number} ticketId
465
523
  * @param {'progress'|'friction'|'notification'} type
466
524
  * @param {string} payload
525
+ * @returns {Promise<unknown>} The provider's `postComment` result.
467
526
  */
468
527
  export async function postStructuredComment(provider, ticketId, type, payload) {
469
528
  assertValidStructuredCommentType(type);
470
- await provider.postComment(ticketId, {
529
+ const posted = await provider.postComment(ticketId, {
471
530
  type,
472
531
  body: payload,
473
532
  });
@@ -475,4 +534,5 @@ export async function postStructuredComment(provider, ticketId, type, payload) {
475
534
  // `findStructuredComment` against this ticket re-fetches and sees the
476
535
  // freshly-posted comment.
477
536
  invalidateRawCommentsCache(provider, ticketId);
537
+ return posted;
478
538
  }
@@ -26,7 +26,6 @@
26
26
  export {
27
27
  __resetParentCascadeLocks,
28
28
  __setCascadeRetryDelays,
29
- cascadeCompletion,
30
29
  logCascadePartialFailures,
31
30
  } from './ticketing/bulk.js';
32
31
  // Re-export the read surface.
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * plan-phase-cleanup.js — Post-phase temp-file cleanup for `/plan`.
3
3
  *
4
- * The spec and decompose phases write several Epic-scoped temp files under
5
- * the per-Epic tree (`temp/epic-<id>/planner-context.json`,
6
- * `temp/epic-<id>/techspec.md`, etc. — see `lib/config/temp-paths.js`). The
4
+ * The spec and decompose phases write several run-scoped temp files under
5
+ * the per-run tree (`temp/run-<id>/planner-context.json`,
6
+ * `temp/run-<id>/techspec.md`, etc. — see `lib/config/temp-paths.js`). The
7
7
  * workflow .md previously told the operator to `Remove-Item` those files by
8
8
  * name at the end of each phase, which rots: adding a new temp file in the
9
9
  * script required a synchronized markdown edit, and missed edits left
@@ -19,20 +19,20 @@
19
19
  * never sinks a successful phase.
20
20
  *
21
21
  * Migration note (Epic #1030 Story #1040): the legacy flat layout
22
- * (`temp/planner-context-epic-<id>.json` etc.) has been retired. The
23
- * resolver now delegates to `epicArtifactPath` from
24
- * `lib/config/temp-paths.js`, which yields `temp/epic-<id>/<basename>`
22
+ * (`temp/planner-context-for-<id>.json` etc.) has been retired. The
23
+ * resolver now delegates to `runArtifactPath` from
24
+ * `lib/config/temp-paths.js`, which yields `temp/run-<id>/<basename>`
25
25
  * under the configured `tempRoot`.
26
26
  */
27
27
 
28
28
  import fs from 'node:fs/promises';
29
29
  import path from 'node:path';
30
- import { epicArtifactPath } from './config/temp-paths.js';
30
+ import { runArtifactPath } from './config/temp-paths.js';
31
31
  import { PROJECT_ROOT, resolveConfig } from './config-resolver.js';
32
32
 
33
33
  /**
34
- * Map of phase → artifact basenames (no path components). The per-Epic
35
- * directory prefix is supplied by `epicArtifactPath` at resolution time.
34
+ * Map of phase → artifact basenames (no path components). The per-run
35
+ * directory prefix is supplied by `runArtifactPath` at resolution time.
36
36
  */
37
37
  export const PHASE_TEMP_BASENAMES = Object.freeze({
38
38
  spec: Object.freeze([
@@ -45,15 +45,13 @@ export const PHASE_TEMP_BASENAMES = Object.freeze({
45
45
  // plan-phase artifact and deletes them ONLY at terminal success (after
46
46
  // the `agent::ready` flip), fixing the mid-pipeline deletion defect where
47
47
  // per-phase cleanup removed artifacts a `--force`/`--resume` re-persist
48
- // was still entitled to reuse. `risk-verdict.json` joins the set here:
49
- // the split-phase cleanup never owned it, which orphaned it in temp/.
48
+ // was still entitled to reuse.
50
49
  // `plan-metrics.json` stays deliberately excluded (PR1) — the ledger
51
50
  // must survive cleanup so the whole plan run is visible in one stream.
52
51
  persist: Object.freeze([
53
52
  'planner-context.json',
54
53
  'techspec.md',
55
54
  'acceptance-spec.md',
56
- 'risk-verdict.json',
57
55
  'decomposer-context.json',
58
56
  'tickets.json',
59
57
  ]),
@@ -84,8 +82,8 @@ export function resolvePhaseTempPaths(phase, epicId, repoRoot = PROJECT_ROOT) {
84
82
  config = undefined;
85
83
  }
86
84
  return basenames.map((basename) => {
87
- const rel = epicArtifactPath(epicId, basename, config);
88
- // `epicArtifactPath` yields a path under `tempRoot` (relative when
85
+ const rel = runArtifactPath(epicId, basename, config);
86
+ // `runArtifactPath` yields a path under `tempRoot` (relative when
89
87
  // tempRoot is itself relative — the framework default). Rebase
90
88
  // relative paths against `repoRoot` so callers always receive an
91
89
  // absolute path — matches the pre-migration contract that
@@ -2,310 +2,36 @@
2
2
  * planning-corpus.js — corpus-aware context for the standalone-Story
3
3
  * planning path (Story #4432).
4
4
  *
5
- * `/plan --seed` previously drafted a standalone Story from a blank
6
- * slate: the seed, the body template, and a title-only duplicate scan.
7
- * For a change request that is really a small delta against an
8
- * already-delivered surface, that blank slate throws away context the
9
- * project already has — the docs digest and the relevant Tech Spec
10
- * sections of existing Epics that cover the touched area.
11
- *
12
- * This module assembles that inherited context (`corpusContext`) for
13
- * `story-plan.js`'s `--emit-context` envelope:
5
+ * `/plan --seed` drafts a standalone Story from the seed, the body
6
+ * template, and a title-only duplicate scan. This module assembles the
7
+ * inherited context (`corpusContext`) for `story-plan.js`'s
8
+ * `--emit-context` envelope:
14
9
  *
15
10
  * 1. `docsDigest` — the same per-project docs digest
16
11
  * `orchestration/docs-digest.js` builds for `/deliver` Story
17
12
  * children, reused here so the standalone path gets the same
18
13
  * compact outline instead of re-reading the whole docs set.
19
14
  * `null` when `project.docsContextFiles` is not configured.
20
- * 2. `relevantSections` — a ranked list of existing Epic Tech Spec
21
- * (or lede, when no Tech Spec region exists) excerpts that overlap
22
- * with the seed, so the draft can build on prior art instead of
23
- * re-deriving it.
24
- *
25
- * The historical Epic list surface (`provider.getEpics`) returns a
26
- * list-scale payload with no `body`. Body content therefore requires an
27
- * **explicit**, bounded per-candidate fetch via `provider.getEpic(id)` —
28
- * never a silent assumption that the list response carries prose to score
29
- * against.
30
- *
31
- * Relevance scoring reuses the same `tokenize` / `overlapScore` Jaccard
32
- * primitives `duplicate-search.js` exports for Epic-dedupe and
33
- * `story-plan.js` reuses for Story-dedupe — one matcher, three
34
- * consumers, no forked scoring logic.
15
+ * 2. `relevantSections` — always `[]`. This field previously carried
16
+ * ranked Tech Spec excerpts mined from open Epics. v2.0.0 removed
17
+ * the Epic tier, and the provider's Epic-list surface was reduced
18
+ * to a `return []` stub, which made the entire ranking pipeline a
19
+ * permanent no-op. The pipeline has been removed; the field is
20
+ * retained so the envelope shape stays stable for its consumers.
35
21
  */
36
22
 
37
- import { overlapScore, tokenize } from './duplicate-search.js';
38
- import { Logger } from './Logger.js';
39
23
  import { buildDocsDigest } from './orchestration/docs-digest.js';
40
- import {
41
- extractTicketSection,
42
- hasTicketSection,
43
- } from './ticket-body-sections.js';
44
-
45
- /** Top-K Epics kept after the cheap title-only ranking pass. */
46
- const DEFAULT_CORPUS_MAX_CANDIDATES = 5;
47
-
48
- /**
49
- * Bound on the explicit per-candidate body fetch. Keeps corpus-context
50
- * assembly at a fixed, small number of GitHub reads regardless of how
51
- * many open Epics the repo carries.
52
- */
53
- export const DEFAULT_CORPUS_BODY_FETCH_TOP_K = 3;
54
-
55
- /** Minimum Jaccard overlap for a section excerpt to be worth surfacing. */
56
- const DEFAULT_CORPUS_MIN_SCORE = 0.1;
57
-
58
- /** Max relevant-section excerpts returned in the envelope. */
59
- const DEFAULT_CORPUS_MAX_SECTIONS = 5;
60
-
61
- /** Excerpt length cap (chars) so one oversized Tech Spec doesn't blow the envelope. */
62
- const EXCERPT_MAX_CHARS = 600;
63
-
64
- /**
65
- * Page-scan cap passed to `provider.getEpics({ state: 'open', pageCap })`.
66
- * The corpus lookup only ever ranks the list down to a top-5 shortlist
67
- * (`DEFAULT_CORPUS_MAX_CANDIDATES`), so there is no need to inherit
68
- * `paginateRest`'s full default ceiling (50 pages / 5000 items) to build
69
- * it — a bounded scan keeps this call a fixed, small number of GitHub
70
- * reads regardless of how many open Epics the repo carries.
71
- */
72
- const CORPUS_EPICS_PAGE_CAP = 5;
73
-
74
- /**
75
- * Rank open Epics by title-overlap with the seed. This is the cheap
76
- * first pass over the list surface (title only — no `body`), used solely
77
- * to pick the bounded top-K candidates
78
- * worth an explicit body fetch. It is not the final relevance signal;
79
- * `extractRelevantSections` re-scores against actual section content.
80
- *
81
- * @param {{ seed: string, epics: Array<{ id:number, title:string }>, maxResults?: number }} opts
82
- * @returns {Array<{ id:number, title:string, score:number }>}
83
- */
84
- export function rankCandidateEpics({
85
- seed,
86
- epics,
87
- maxResults = DEFAULT_CORPUS_MAX_CANDIDATES,
88
- }) {
89
- if (!seed || typeof seed !== 'string') {
90
- throw new Error('rankCandidateEpics: seed must be a non-empty string');
91
- }
92
- if (!Array.isArray(epics)) {
93
- throw new Error('rankCandidateEpics: epics must be an array');
94
- }
95
- const seedTokens = tokenize(seed);
96
- if (seedTokens.size === 0) return [];
97
-
98
- const ranked = [];
99
- for (const epic of epics) {
100
- if (!epic || typeof epic.title !== 'string') continue;
101
- const score = overlapScore(seedTokens, tokenize(epic.title));
102
- ranked.push({
103
- id: epic.id,
104
- title: epic.title,
105
- score: Number(score.toFixed(4)),
106
- });
107
- }
108
- ranked.sort((a, b) => b.score - a.score);
109
- return ranked.slice(0, maxResults);
110
- }
111
-
112
- /**
113
- * Fetch full bodies for the top-K ranked candidates via the single-issue
114
- * read (`provider.getEpic`), which — unlike the `getEpics` list mapper —
115
- * does carry `body`. This is the explicit, bounded fetch the corpus
116
- * lookup performs instead of assuming the list surface already has
117
- * prose to score: a candidate never contributes a relevant section
118
- * without this round-trip resolving its body.
119
- *
120
- * A single candidate's fetch failing (deleted issue, transient error) is
121
- * non-fatal — it is dropped from the result rather than aborting corpus
122
- * assembly for every other candidate. Failures are logged via
123
- * `Logger.debug` (stderr) so they are visible under
124
- * `AGENT_LOG_LEVEL=verbose` triage without violating the friction-
125
- * telemetry posture in `.agents/instructions.md` §1.H of never silently
126
- * swallowing an error.
127
- *
128
- * The bounded candidate slice is fetched concurrently
129
- * (`Promise.allSettled`) rather than sequentially — `topK` is a fixed
130
- * small ceiling (default 3), so this is a bounded fan-out, not an
131
- * unbounded one, and it removes the serial network-latency stacking a
132
- * plain `for`-await loop would otherwise incur.
133
- *
134
- * @param {{ provider: object, candidates: Array<{ id:number, title:string }>, topK?: number }} opts
135
- * @returns {Promise<Array<{ id:number, title:string, body:string }>>}
136
- */
137
- export async function fetchCandidateBodies({
138
- provider,
139
- candidates,
140
- topK = DEFAULT_CORPUS_BODY_FETCH_TOP_K,
141
- }) {
142
- if (!provider || typeof provider.getEpic !== 'function') return [];
143
- if (!Array.isArray(candidates) || candidates.length === 0) return [];
144
-
145
- const bounded = candidates.slice(0, topK);
146
- const settled = await Promise.allSettled(
147
- bounded.map(async (candidate) => {
148
- const epic = await provider.getEpic(candidate.id);
149
- return {
150
- id: candidate.id,
151
- title: candidate.title ?? epic?.title ?? '',
152
- body: epic?.body ?? '',
153
- };
154
- }),
155
- );
156
-
157
- const results = [];
158
- for (let i = 0; i < settled.length; i += 1) {
159
- const outcome = settled[i];
160
- if (outcome.status === 'fulfilled') {
161
- results.push(outcome.value);
162
- continue;
163
- }
164
- // Best-effort: one candidate failing to resolve must not abort
165
- // corpus-context assembly for the rest — but the failure is still
166
- // surfaced for triage rather than silently swallowed.
167
- Logger.debug(
168
- `[planning-corpus] fetchCandidateBodies: candidate #${bounded[i].id} failed to resolve: ${outcome.reason?.message ?? outcome.reason}`,
169
- );
170
- }
171
- return results;
172
- }
173
-
174
- /**
175
- * Extract a scoreable excerpt from an Epic body: the managed Tech Spec
176
- * region when present (the folded `## Delivery Slicing` section, #4324),
177
- * otherwise the ideation lede — the prose before the first `##` heading
178
- * — so a plain-body Epic still contributes something to score.
179
- *
180
- * @param {string} body
181
- * @returns {{ kind:'techSpec'|'lede', content:string }}
182
- */
183
- function extractScoreableExcerpt(body) {
184
- if (hasTicketSection(body, 'techSpec')) {
185
- return {
186
- kind: 'techSpec',
187
- content: extractTicketSection(body, 'techSpec'),
188
- };
189
- }
190
- const lede = (body ?? '').split(/^##\s+/m)[0].trim();
191
- return { kind: 'lede', content: lede };
192
- }
193
-
194
- /**
195
- * Rank existing-Epic body excerpts (Tech Spec section, or lede) by
196
- * overlap with the seed. Reuses the same `tokenize` / `overlapScore`
197
- * primitives as the title-ranking pass above and as
198
- * `duplicate-search.js` — one matcher shared across every corpus /
199
- * dedupe surface.
200
- *
201
- * @param {{ seed:string, epicBodies: Array<{ id:number, title:string, body:string }>, maxResults?: number, minScore?: number }} opts
202
- * @returns {Array<{ epicId:number, epicTitle:string, section:'techSpec'|'lede', score:number, excerpt:string }>}
203
- */
204
- export function extractRelevantSections({
205
- seed,
206
- epicBodies,
207
- maxResults = DEFAULT_CORPUS_MAX_SECTIONS,
208
- minScore = DEFAULT_CORPUS_MIN_SCORE,
209
- }) {
210
- if (!seed || typeof seed !== 'string') {
211
- throw new Error('extractRelevantSections: seed must be a non-empty string');
212
- }
213
- if (!Array.isArray(epicBodies)) {
214
- throw new Error('extractRelevantSections: epicBodies must be an array');
215
- }
216
- const seedTokens = tokenize(seed);
217
- if (seedTokens.size === 0) return [];
218
-
219
- const ranked = [];
220
- for (const epic of epicBodies) {
221
- if (!epic) continue;
222
- const { kind, content } = extractScoreableExcerpt(epic.body ?? '');
223
- if (!content) continue;
224
- const score = overlapScore(seedTokens, tokenize(content));
225
- if (score < minScore) continue;
226
- ranked.push({
227
- epicId: epic.id,
228
- epicTitle: epic.title ?? null,
229
- section: kind,
230
- score: Number(score.toFixed(4)),
231
- excerpt: content.slice(0, EXCERPT_MAX_CHARS),
232
- });
233
- }
234
- ranked.sort((a, b) => b.score - a.score);
235
- return ranked.slice(0, maxResults);
236
- }
237
24
 
238
25
  /**
239
26
  * Assemble the `corpusContext` field of the story-plan context envelope.
240
- * Pure orchestration over the three helpers above plus `buildDocsDigest`
241
- * — no I/O beyond what `provider` and the docs-digest reader perform.
242
- *
243
- * `relevantSections` is `[]` (not an error) when the provider has no
244
- * `getEpics` surface, the seed tokenizes to nothing, or no candidate
245
- * clears `minScore` — the standalone-Story draft path degrades to
246
- * exactly today's blank-slate behavior in that case.
247
27
  *
248
28
  * @param {{
249
- * seed: string,
250
- * provider?: { getEpics?: Function, getEpic?: Function },
251
29
  * docsContextFiles?: string[],
252
30
  * docsRoot?: string,
253
- * maxCandidates?: number,
254
- * bodyFetchTopK?: number,
255
- * maxSections?: number,
256
- * minScore?: number,
257
31
  * }} opts
258
32
  * @returns {Promise<{ docsDigest: string|null, relevantSections: Array<object> }>}
259
33
  */
260
- export async function buildCorpusContext({
261
- seed,
262
- provider,
263
- docsContextFiles,
264
- docsRoot,
265
- maxCandidates = DEFAULT_CORPUS_MAX_CANDIDATES,
266
- bodyFetchTopK = DEFAULT_CORPUS_BODY_FETCH_TOP_K,
267
- maxSections = DEFAULT_CORPUS_MAX_SECTIONS,
268
- minScore = DEFAULT_CORPUS_MIN_SCORE,
269
- }) {
34
+ export async function buildCorpusContext({ docsContextFiles, docsRoot }) {
270
35
  const docsDigest = await buildDocsDigest({ docsContextFiles, docsRoot });
271
-
272
- let relevantSections = [];
273
- if (provider && typeof provider.getEpics === 'function') {
274
- // A single candidate-listing call failing (rate limit, transient
275
- // network error, provider outage) must not abort the whole
276
- // `--emit-context` envelope build — degrade to an empty candidate
277
- // list instead of letting the rejection propagate out of the
278
- // caller's `Promise.all` and take down the rest of the envelope
279
- // (body template, tech-stack summary, docs digest) with it.
280
- let epics = [];
281
- try {
282
- epics = await provider.getEpics({
283
- state: 'open',
284
- pageCap: CORPUS_EPICS_PAGE_CAP,
285
- });
286
- } catch (err) {
287
- Logger.debug(
288
- `[planning-corpus] buildCorpusContext: provider.getEpics failed, degrading to an empty candidate list: ${err?.message ?? err}`,
289
- );
290
- epics = [];
291
- }
292
- const ranked = rankCandidateEpics({
293
- seed,
294
- epics: Array.isArray(epics) ? epics : [],
295
- maxResults: maxCandidates,
296
- });
297
- const bodies = await fetchCandidateBodies({
298
- provider,
299
- candidates: ranked,
300
- topK: bodyFetchTopK,
301
- });
302
- relevantSections = extractRelevantSections({
303
- seed,
304
- epicBodies: bodies,
305
- maxResults: maxSections,
306
- minScore,
307
- });
308
- }
309
-
310
- return { docsDigest, relevantSections };
36
+ return { docsDigest, relevantSections: [] };
311
37
  }
@@ -52,8 +52,8 @@ const DEFAULT_LOGGER = {
52
52
  * flag, finding/fix routing, and pretty-print of the blocker table.
53
53
  *
54
54
  * @param {object} opts
55
- * @param {string} opts.scope Consumer surface — `'epic-close'`,
56
- * `'story-close'`, `'npm-test'`, etc.
55
+ * @param {string} opts.scope Consumer surface — `'story-close'`,
56
+ * `'npm-test'`, `'diagnose'`, etc.
57
57
  * @param {boolean} [opts.autoFix=true] Forwarded to `runChecks`. Defaults
58
58
  * to `true` because every wiring call site at this Story's level wants
59
59
  * auto-correction (the `retro` consumer that needs `autoFix:false` does
@@ -142,7 +142,7 @@ export async function verifySurfaceMap(surfaceMap, gitPort, baseRef) {
142
142
  * with no network when the ports are fakes.
143
143
  *
144
144
  * @param {{
145
- * epicNumber: number,
145
+ * ticketNumber: number,
146
146
  * githubPort: GithubPort,
147
147
  * gitPort: GitPort,
148
148
  * surfaceMap?: SurfaceMapEntry[],
@@ -162,7 +162,7 @@ export async function verifySurfaceMap(surfaceMap, gitPort, baseRef) {
162
162
  */
163
163
  export async function hydrateQaContext(opts) {
164
164
  const {
165
- epicNumber,
165
+ ticketNumber,
166
166
  githubPort,
167
167
  gitPort,
168
168
  surfaceMap = [],
@@ -172,9 +172,9 @@ export async function hydrateQaContext(opts) {
172
172
  fsImpl,
173
173
  } = opts ?? {};
174
174
 
175
- if (!Number.isInteger(epicNumber)) {
175
+ if (!Number.isInteger(ticketNumber)) {
176
176
  throw new Error(
177
- 'hydrateQaContext: `epicNumber` is required and must be an integer',
177
+ 'hydrateQaContext: `ticketNumber` is required and must be an integer',
178
178
  );
179
179
  }
180
180
  if (!githubPort || typeof githubPort.fetchIssue !== 'function') {
@@ -192,7 +192,7 @@ export async function hydrateQaContext(opts) {
192
192
  );
193
193
  }
194
194
 
195
- const epicIssue = await githubPort.fetchIssue(epicNumber);
195
+ const epicIssue = await githubPort.fetchIssue(ticketNumber);
196
196
  const epic = {
197
197
  number: epicIssue.number,
198
198
  body: epicIssue.body ?? '',