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
@@ -6,17 +6,24 @@ import { getCiDelivery } from '../../config/ci.js';
6
6
  import { resolveConfig } from '../../config-resolver.js';
7
7
  import { getStoryBranch, gitSync } from '../../git-utils.js';
8
8
  import { Logger } from '../../Logger.js';
9
+ import { emitTerminalFriction } from '../../observability/runtime-friction.js';
9
10
  import { createProvider } from '../../provider-factory.js';
10
11
  import { flipLabelAndNotify } from '../../single-story/story-merged-notify.js';
11
12
  import { WorktreeManager } from '../../worktree-manager.js';
12
13
  import { runCodeReview as runCodeReviewDefault } from '../code-review.js';
13
14
  import { releaseStoryLease } from '../single-story-lease-guard.js';
15
+ import {
16
+ buildTerminalEnvelope,
17
+ emitTerminalEnvelope,
18
+ NEXT_COMMANDS,
19
+ terminalFromWaitOutcome,
20
+ } from '../story-deliver-terminal.js';
14
21
  import { runAutoMergePhase } from './phases/auto-merge.js';
15
22
  import { runBaseSyncPhase } from './phases/base-sync.js';
16
23
  import { runCloseValidationPhase } from './phases/close-validation.js';
17
24
  import { parsePrNumber, runStoryScopeReview } from './phases/code-review.js';
18
25
  import { runConfirmMergePhase } from './phases/confirm-merge.js';
19
- import { parseCloseOptions } from './phases/options.js';
26
+ import { parseCloseOptions, resolveWaitForMerge } from './phases/options.js';
20
27
  import { ensurePullRequestWith } from './phases/pull-request.js';
21
28
  import { pushStoryBranch } from './phases/push.js';
22
29
  import { handleCriticalReviewBlock } from './phases/review-block.js';
@@ -25,16 +32,94 @@ import { runWrongTreeGuardPhase } from './phases/wrong-tree-guard.js';
25
32
 
26
33
  const progress = Logger.createProgress('single-story-close', { stderr: true });
27
34
 
28
- function alreadyClosedResult(storyId) {
29
- return {
30
- success: true,
31
- result: {
35
+ /**
36
+ * Emit the terminal envelope on stdout alongside the legacy close result.
37
+ *
38
+ * Both are printed: the envelope is the contract callers parse (Story
39
+ * #4543), while `STORY CLOSE RESULT` stays byte-compatible for the existing
40
+ * surfaces that grep it. One writer, one place — so the envelope can never
41
+ * be emitted from a path that forgot it.
42
+ *
43
+ * Story #4578 — the same one-place property is why the runtime-derived
44
+ * friction emit hangs here: a close that ends `failed`, or parks `pending`
45
+ * on an exhausted merge-wait budget, is friction the retro must see, and
46
+ * routing it through the single emitter means no terminal path can forget
47
+ * it. `emitTerminalFriction` decides what (if anything) is worth a record —
48
+ * see `frictionForTerminal` for why `blocked` and a `--no-wait-merge`
49
+ * `pending` are deliberately NOT flagged here.
50
+ *
51
+ * Async, and awaited by every caller: the CLI exits via `process.exit` the
52
+ * moment `main` resolves, so a fire-and-forget append would be discarded.
53
+ * The emit is best-effort internally and cannot throw.
54
+ */
55
+ async function emitTerminal({ terminal, result, config }) {
56
+ // The human-facing result dump stays level-gated; the terminal envelope is
57
+ // the machine contract and must survive AGENT_LOG_LEVEL=silent.
58
+ if (result) {
59
+ Logger.info(
60
+ `\n--- STORY CLOSE RESULT ---\n${JSON.stringify(result, null, 2)}\n--- END RESULT ---\n`,
61
+ );
62
+ }
63
+ emitTerminalEnvelope(terminal);
64
+ await emitTerminalFriction({ envelope: terminal, config });
65
+ }
66
+
67
+ /**
68
+ * Terminal for a Story that was already closed before this run started.
69
+ *
70
+ * `state: 'closed'` alone does NOT mean the work landed. GitHub closes an
71
+ * issue as `completed` (the `Closes #<id>` footer firing on merge — the work
72
+ * IS on the base branch) or as `not_planned` (superseded by a re-plan,
73
+ * abandoned — nothing ever merged). Reporting the second as `landed` told
74
+ * `/deliver` that work had reached `main` when no PR ever merged, which would
75
+ * also satisfy any dependent Story waiting on it. Fail loudly instead: being
76
+ * handed an abandoned Story is an input error only the operator can resolve.
77
+ *
78
+ * A null `stateReason` keeps the `completed` reading — GitHub defaults to it,
79
+ * and issues closed before the field existed carry null.
80
+ */
81
+ async function alreadyClosedResult(storyId, stateReason = null, config) {
82
+ if (stateReason === 'not_planned') {
83
+ const result = {
32
84
  storyId,
33
85
  standalone: true,
34
86
  action: 'noop',
35
- reason: 'already-closed',
36
- },
87
+ reason: 'closed-not-planned',
88
+ };
89
+ const terminal = buildTerminalEnvelope({
90
+ storyId,
91
+ status: 'failed',
92
+ phase: 'init',
93
+ failure: {
94
+ reason:
95
+ `Story #${storyId} is closed as not planned (superseded or abandoned) — ` +
96
+ 'there is nothing to land. Re-plan it as a new Story, or pass a Story that is still open.',
97
+ },
98
+ nextCommand: null,
99
+ elapsedSeconds: 0,
100
+ });
101
+ await emitTerminal({ terminal, result, config });
102
+ return { success: false, result, terminal };
103
+ }
104
+
105
+ const result = {
106
+ storyId,
107
+ standalone: true,
108
+ action: 'noop',
109
+ reason: 'already-closed',
37
110
  };
111
+ // Idempotent re-run against a Story closed as completed. `landed` is the
112
+ // honest status — the issue closed on merge, so the work is on the base
113
+ // branch — and there is nothing left to command.
114
+ const terminal = buildTerminalEnvelope({
115
+ storyId,
116
+ status: 'landed',
117
+ phase: 'done',
118
+ nextCommand: null,
119
+ elapsedSeconds: 0,
120
+ });
121
+ await emitTerminal({ terminal, result, config });
122
+ return { success: true, result, terminal };
38
123
  }
39
124
 
40
125
  function resolveWorktreePath({ cwd, config, storyId }) {
@@ -55,7 +140,9 @@ async function runPrePushPhases({
55
140
  skipSync,
56
141
  injectedSync,
57
142
  injectedGitSpawn,
143
+ setPhase = () => {},
58
144
  }) {
145
+ setPhase('wrong-tree-guard');
59
146
  await runWrongTreeGuardPhase({
60
147
  cwd,
61
148
  worktreePath,
@@ -66,6 +153,7 @@ async function runPrePushPhases({
66
153
  gitSpawn: injectedGitSpawn,
67
154
  });
68
155
  if (!skipValidation) {
156
+ setPhase('close-validation');
69
157
  await runCloseValidationPhase({
70
158
  cwd,
71
159
  worktreePath,
@@ -81,6 +169,7 @@ async function runPrePushPhases({
81
169
  progress('VALIDATE', '⏭ Skipped (--skip-validation).');
82
170
  }
83
171
  if (!skipSync) {
172
+ setPhase('base-sync');
84
173
  await runBaseSyncPhase({
85
174
  cwd,
86
175
  worktreePath,
@@ -105,8 +194,11 @@ async function openAndReviewPr({
105
194
  provider,
106
195
  injectedGh,
107
196
  injectedRunCodeReview,
197
+ setPhase = () => {},
108
198
  }) {
199
+ setPhase('push');
109
200
  pushStoryBranch({ cwd, storyBranch, gitSync, progress });
201
+ setPhase('pull-request');
110
202
  const prUrl = await ensurePullRequestWith({
111
203
  cwd,
112
204
  storyId,
@@ -117,6 +209,7 @@ async function openAndReviewPr({
117
209
  progress,
118
210
  });
119
211
  const prNumber = parsePrNumber(prUrl);
212
+ setPhase('code-review');
120
213
  const reviewOutcome = await runStoryScopeReview({
121
214
  cwd,
122
215
  storyId,
@@ -176,8 +269,12 @@ async function releaseLease({
176
269
  * The two recoverable-blocked close exits (base-sync conflict in
177
270
  * `runBaseSyncPhase`, and a critical-blocker review halt in
178
271
  * `openAndReviewPr`) throw before the clean-close lease release at the
179
- * tail of `runSingleStoryClose`, stranding the operator's lease until its
180
- * TTL expires. That fail-closed-refuses a different operator who picks up
272
+ * tail of `runSingleStoryClose`, stranding the operator's lease
273
+ * indefinitely. The standalone lease does **not** expire by TTL: it is
274
+ * fail-closed by design (`lease-guard-shared.js` anchors `heartbeatAt` to
275
+ * now, so `isClaimLive` is true for any foreign assignee regardless of the
276
+ * configured TTL), so a stranded claim is cleared only by `--steal` or
277
+ * de-assignment. That fail-closed-refuses a different operator who picks up
181
278
  * the blocked Story — exactly the hand-off case. Releasing here closes
182
279
  * that gap.
183
280
  *
@@ -232,7 +329,7 @@ function closeResult({
232
329
  waitedForMerge,
233
330
  merged,
234
331
  note: waitedForMerge
235
- ? 'Headless must-land: PR merge confirmed. Story flipped agent::closing → agent::done and the issue closed (confirmStoryMerged).'
332
+ ? 'Close-and-land: PR merge confirmed. Story flipped agent::closing → agent::done, the issue closed (confirmStoryMerged), and the post-land tail ran.'
236
333
  : autoMergeEnabled
237
334
  ? 'PR open against baseBranch with auto-merge enabled. Story rests at agent::closing (issue stays OPEN). GitHub will squash-merge when required checks pass; run single-story-confirm-merge.js after the merge confirms to flip agent::done and close the issue (the Closes #<id> footer also auto-closes it).'
238
335
  : 'PR open against baseBranch. Story rests at agent::closing (issue stays OPEN). Operator merges via GitHub UI; run single-story-confirm-merge.js after the merge confirms to flip agent::done (the Closes #<id> footer also auto-closes the issue).',
@@ -245,9 +342,9 @@ export async function runSingleStoryClose({
245
342
  skipValidation: skipValidationParam,
246
343
  skipSync: skipSyncParam,
247
344
  noAutoMerge: noAutoMergeParam,
248
- noFullScopeCrap: noFullScopeCrapParam,
249
345
  waitForMerge: waitForMergeParam,
250
346
  noWaitForMerge: noWaitForMergeParam,
347
+ maxWaitSeconds: maxWaitSecondsParam,
251
348
  injectedProvider,
252
349
  injectedConfig,
253
350
  injectedNotify,
@@ -263,16 +360,65 @@ export async function runSingleStoryClose({
263
360
  skipValidationParam,
264
361
  skipSyncParam,
265
362
  noAutoMergeParam,
266
- noFullScopeCrapParam,
267
363
  waitForMergeParam,
268
364
  noWaitForMergeParam,
365
+ maxWaitSecondsParam,
269
366
  });
270
367
  if (!options.storyId) {
271
368
  throw new Error(
272
- 'Usage: node single-story-close.js --story <STORY_ID> [--cwd <main-repo>] [--skip-validation] [--skip-sync] [--no-auto-merge] [--no-full-scope-crap] [--wait-merge|--no-wait-merge]',
369
+ 'Usage: node single-story-close.js --story <STORY_ID> [--cwd <main-repo>] [--skip-validation] [--skip-sync] [--no-auto-merge] [--wait-merge|--no-wait-merge] [--max-wait-seconds <n>]',
273
370
  );
274
371
  }
275
372
 
373
+ // Story #4543 — track the phase so a crash is attributable. The runner
374
+ // deliberately keeps THROWING (the library contract every caller and test
375
+ // relies on: a failed gate must not look like a return value); it only tags
376
+ // the error with the phase it died in, and the CLI boundary turns that into
377
+ // the `failed` terminal envelope. Tagging rather than swallowing is what
378
+ // lets `failed` name its phase without inventing a second success path.
379
+ let phase = 'init';
380
+ const setPhase = (next) => {
381
+ phase = next;
382
+ };
383
+ try {
384
+ return await runClosePipeline({
385
+ options,
386
+ setPhase,
387
+ injectedProvider,
388
+ injectedConfig,
389
+ injectedNotify,
390
+ injectedSync,
391
+ injectedRunCodeReview,
392
+ injectedGh,
393
+ injectedGitSpawn,
394
+ injectedReleaseLease,
395
+ });
396
+ } catch (err) {
397
+ if (err && typeof err === 'object' && !err.closePhase) {
398
+ err.closePhase = phase;
399
+ }
400
+ throw err;
401
+ }
402
+ }
403
+
404
+ /**
405
+ * The close pipeline proper. Split out of `runSingleStoryClose` so the
406
+ * phase-tagging wrapper above stays a thin, obviously-correct boundary rather
407
+ * than a try block wrapped around a hundred lines of pipeline.
408
+ */
409
+ async function runClosePipeline({
410
+ options,
411
+ setPhase,
412
+ injectedProvider,
413
+ injectedConfig,
414
+ injectedNotify,
415
+ injectedSync,
416
+ injectedRunCodeReview,
417
+ injectedGh,
418
+ injectedGitSpawn,
419
+ injectedReleaseLease,
420
+ }) {
421
+ const startedAtMs = Date.now();
276
422
  const config = injectedConfig || resolveConfig({ cwd: options.cwd });
277
423
  const provider = injectedProvider || createProvider(config);
278
424
  const baseBranch = config.project?.baseBranch ?? 'main';
@@ -283,9 +429,15 @@ export async function runSingleStoryClose({
283
429
  if (story.state === 'closed') {
284
430
  progress(
285
431
  'NOOP',
286
- `Story #${options.storyId} is already closed. Nothing to do.`,
432
+ story.stateReason === 'not_planned'
433
+ ? `Story #${options.storyId} is closed as not planned — nothing to land.`
434
+ : `Story #${options.storyId} is already closed. Nothing to do.`,
435
+ );
436
+ return await alreadyClosedResult(
437
+ options.storyId,
438
+ story.stateReason,
439
+ config,
287
440
  );
288
- return alreadyClosedResult(options.storyId);
289
441
  }
290
442
 
291
443
  const worktreePath = resolveWorktreePath({
@@ -314,6 +466,7 @@ export async function runSingleStoryClose({
314
466
  worktreePath,
315
467
  injectedSync,
316
468
  injectedGitSpawn,
469
+ setPhase,
317
470
  }),
318
471
  leaseArgs,
319
472
  );
@@ -329,9 +482,11 @@ export async function runSingleStoryClose({
329
482
  provider,
330
483
  injectedGh,
331
484
  injectedRunCodeReview,
485
+ setPhase,
332
486
  }),
333
487
  leaseArgs,
334
488
  );
489
+ setPhase('auto-merge');
335
490
  const { autoMergeEnabled, autoMergeReason } = await runAutoMergePhase({
336
491
  cwd: options.cwd,
337
492
  prNumber,
@@ -362,32 +517,68 @@ export async function runSingleStoryClose({
362
517
  });
363
518
  const leaseReleased = await releaseLease(leaseArgs);
364
519
 
365
- // Story #4428 headless must-land: `--wait-merge` polls the just-armed
366
- // PR to merge confirmation (or an explicit `agent::blocked` +
367
- // `merge.unlanded`) instead of resting at `agent::closing`. Attended
368
- // (non-headless) runs never set this flag, so the exit shape below is
369
- // unreachable and the pre-existing early-return is byte-identical.
370
- if (options.waitForMerge) {
520
+ // Close-and-land (Story #4428; default since `delivery.routing.closeAndLand`
521
+ // — Story #4539): poll the just-armed PR to merge confirmation, or block
522
+ // explicitly with `merge.unlanded`, instead of resting at `agent::closing`.
523
+ // This is the DEFAULT path for attended and headless runs alike.
524
+ //
525
+ // Resolved here rather than at parse time because two inputs do not exist
526
+ // until now: the resolved config (whose cwd the parse produces) and the
527
+ // actual arm outcome. A PR the operator deliberately left un-armed
528
+ // (`--no-auto-merge` / `autoMerge: "strict"`) has nothing to land, so it
529
+ // rests at `agent::closing` for the human instead of burning the poll
530
+ // budget and then blocking a healthy Story.
531
+ const { waitForMerge, reason: waitForMergeReason } = resolveWaitForMerge({
532
+ waitForMergeExplicit: options.waitForMergeExplicit,
533
+ noWaitForMerge: options.noWaitForMerge,
534
+ config,
535
+ autoMergeReason,
536
+ });
537
+ if (waitForMergeReason === 'operator-merge') {
538
+ progress(
539
+ 'MERGE',
540
+ `⏭ Not waiting for merge (${autoMergeReason}) — the operator owns this merge; ` +
541
+ `Story #${options.storyId} rests at agent::closing.` +
542
+ (options.waitForMergeExplicit === true
543
+ ? ' --wait-merge cannot land a PR that was deliberately left un-armed.'
544
+ : ''),
545
+ );
546
+ }
547
+ const gates = {
548
+ validation: options.skipValidation ? 'skipped' : 'passed',
549
+ baseSync: options.skipSync ? 'skipped' : 'passed',
550
+ codeReview: 'passed',
551
+ };
552
+
553
+ if (waitForMerge) {
554
+ setPhase('confirm-merge');
371
555
  const waitOutcome = await runConfirmMergePhase({
372
556
  cwd: options.cwd,
373
557
  storyId: options.storyId,
558
+ storyBranch,
559
+ baseBranch,
374
560
  prNumber,
375
561
  prUrl,
376
562
  autoMergeEnabled,
377
563
  autoMergeReason,
378
564
  provider,
379
565
  config,
566
+ maxWaitSeconds: options.maxWaitSeconds,
380
567
  progress,
381
568
  injectedGh,
382
569
  injectedNotify,
383
570
  });
384
- if (!waitOutcome.confirmed) {
385
- throw new Error(
386
- `[single-story-close] Headless must-land: PR ${prUrl} did not reach a confirmed merge ` +
387
- `(blockClass=${waitOutcome.blockClass}). Story #${options.storyId} was transitioned to ` +
388
- `agent::blocked with a merge.unlanded lifecycle event. Reason: ${waitOutcome.reason}`,
389
- );
390
- }
571
+ const terminal = terminalFromWaitOutcome({
572
+ waitOutcome,
573
+ storyId: options.storyId,
574
+ storyBranch,
575
+ baseBranch,
576
+ prNumber,
577
+ prUrl,
578
+ autoMergeEnabled,
579
+ gates,
580
+ elapsedSeconds: Math.round((Date.now() - startedAtMs) / 1000),
581
+ });
391
582
  const result = closeResult({
392
583
  storyId: options.storyId,
393
584
  storyBranch,
@@ -399,16 +590,29 @@ export async function runSingleStoryClose({
399
590
  worktreeReaped,
400
591
  leaseReleased,
401
592
  waitedForMerge: true,
402
- merged: true,
593
+ merged: waitOutcome.confirmed === true,
403
594
  });
404
- Logger.info(
405
- `\n--- STORY CLOSE RESULT ---\n${JSON.stringify(result, null, 2)}\n--- END RESULT ---\n`,
406
- );
407
- progress(
408
- 'DONE',
409
- `✅ Standalone Story #${options.storyId}: PR merged ${prUrl}`,
410
- );
411
- return { success: true, result };
595
+ await emitTerminal({ terminal, result, config });
596
+
597
+ if (terminal.status === 'landed') {
598
+ progress('DONE', `✅ Story #${options.storyId}: PR merged → ${prUrl}`);
599
+ } else if (terminal.status === 'pending') {
600
+ // NOT a failure and NOT a block — the wait reached the edge of its
601
+ // host slot with the PR healthy and in flight. The CLI maps this to
602
+ // its own exit code so a caller can resume without classifying.
603
+ progress(
604
+ 'PENDING',
605
+ `⏸ Story #${options.storyId}: PR ${prUrl} still in flight — resume with: ${terminal.nextCommand}`,
606
+ );
607
+ } else {
608
+ progress(
609
+ 'BLOCKED',
610
+ `🛑 Story #${options.storyId}: PR ${prUrl} did not land ` +
611
+ `(blockClass=${terminal.blocked?.blockClass}). Story is at agent::blocked. ` +
612
+ `Next: ${terminal.nextCommand}`,
613
+ );
614
+ }
615
+ return { success: terminal.status === 'landed', result, terminal };
412
616
  }
413
617
 
414
618
  const result = closeResult({
@@ -422,13 +626,30 @@ export async function runSingleStoryClose({
422
626
  worktreeReaped,
423
627
  leaseReleased,
424
628
  });
425
-
426
- Logger.info(
427
- `\n--- STORY CLOSE RESULT ---\n${JSON.stringify(result, null, 2)}\n--- END RESULT ---\n`,
428
- );
629
+ // `--no-wait-merge` / operator-merge: the PR is open and the human owns
630
+ // the land. That is a `pending` terminal by definition — the work is not
631
+ // done, nothing is broken, and one named command finishes it — rather
632
+ // than a fourth status invented for this one case.
633
+ const terminal = buildTerminalEnvelope({
634
+ storyId: options.storyId,
635
+ status: 'pending',
636
+ phase: 'auto-merge',
637
+ storyBranch,
638
+ baseBranch,
639
+ pr: {
640
+ number: prNumber,
641
+ url: prUrl ?? null,
642
+ state: 'OPEN',
643
+ autoMergeEnabled: Boolean(autoMergeEnabled),
644
+ },
645
+ gates,
646
+ nextCommand: NEXT_COMMANDS.confirmMerge(options.storyId),
647
+ elapsedSeconds: Math.round((Date.now() - startedAtMs) / 1000),
648
+ });
649
+ await emitTerminal({ terminal, result, config });
429
650
  progress(
430
651
  'DONE',
431
- `✅ Standalone Story #${options.storyId}: PR ready → ${prUrl}`,
652
+ `✅ Story #${options.storyId}: PR ready → ${prUrl} (${waitForMergeReason})`,
432
653
  );
433
- return { success: true, result };
654
+ return { success: true, result, terminal };
434
655
  }
@@ -116,7 +116,7 @@ export async function acquireStoryLease({
116
116
  anchorHeartbeatToNow: true,
117
117
  renderRefusal: (result) =>
118
118
  `single-story lease: Story #${storyId} is currently held by @${result.owner}. ` +
119
- 'Another /single-story-deliver run owns this Story. Coordinate with that ' +
119
+ 'Another /deliver run owns this Story. Coordinate with that ' +
120
120
  'operator, or re-run with --steal to forcibly transfer the claim once you ' +
121
121
  'have confirmed the other run is dead. (The standalone path has no Epic ' +
122
122
  'heartbeat ledger, so a foreign assignee always blocks unless stolen.)',
@@ -328,7 +328,7 @@ export function runFormatAutofix({
328
328
  }
329
329
 
330
330
  /**
331
- * List the files changed between `epicBranch` and `storyBranch` using the
331
+ * List the files changed between `baseBranch` and `storyBranch` using the
332
332
  * three-dot merge-base diff. Delegates parsing to `diffNameOnly` from
333
333
  * `changed-files.js` so the stdout → path-list conversion lives in one place.
334
334
  *
@@ -336,10 +336,10 @@ export function runFormatAutofix({
336
336
  * `(args: string[], opts: object) => string`. A bridge adapter wraps it into
337
337
  * the `gitSpawn(cwd, ...args)` shape that `diffNameOnly` expects.
338
338
  *
339
- * @param {{ cwd: string, epicBranch: string, storyBranch: string, git: Function }} opts
339
+ * @param {{ cwd: string, baseBranch: string, storyBranch: string, git: Function }} opts
340
340
  * @returns {string[]}
341
341
  */
342
- function listChangedFiles({ cwd, epicBranch, storyBranch, git }) {
342
+ function listChangedFiles({ cwd, baseBranch, storyBranch, git }) {
343
343
  // Bridge the (args, opts) → string interface into gitSpawn(cwd, ...args).
344
344
  const gitSpawn = (_cwd, ...args) => {
345
345
  try {
@@ -358,7 +358,7 @@ function listChangedFiles({ cwd, epicBranch, storyBranch, git }) {
358
358
  }
359
359
  };
360
360
  return diffNameOnly({
361
- range: `${epicBranch}...${storyBranch}`,
361
+ range: `${baseBranch}...${storyBranch}`,
362
362
  cwd,
363
363
  gitSpawn,
364
364
  });
@@ -401,7 +401,7 @@ function listChangedFiles({ cwd, epicBranch, storyBranch, git }) {
401
401
  * cwd: string,
402
402
  * worktreePath?: string,
403
403
  * storyId: number|string,
404
- * epicBranch: string,
404
+ * baseBranch: string,
405
405
  * storyBranch: string,
406
406
  * config?: object,
407
407
  * logger?: object,
@@ -420,7 +420,7 @@ export function runScopedFormatAutofix({
420
420
  cwd,
421
421
  worktreePath,
422
422
  storyId,
423
- epicBranch,
423
+ baseBranch,
424
424
  storyBranch,
425
425
  config,
426
426
  logger = DefaultLogger,
@@ -428,8 +428,8 @@ export function runScopedFormatAutofix({
428
428
  gitSync,
429
429
  } = {}) {
430
430
  if (!cwd) throw new Error('runScopedFormatAutofix: cwd is required');
431
- if (!epicBranch)
432
- throw new Error('runScopedFormatAutofix: epicBranch is required');
431
+ if (!baseBranch)
432
+ throw new Error('runScopedFormatAutofix: baseBranch is required');
433
433
  if (!storyBranch)
434
434
  throw new Error('runScopedFormatAutofix: storyBranch is required');
435
435
 
@@ -449,13 +449,13 @@ export function runScopedFormatAutofix({
449
449
 
450
450
  const changed = listChangedFiles({
451
451
  cwd: workTree,
452
- epicBranch,
452
+ baseBranch,
453
453
  storyBranch,
454
454
  git,
455
455
  });
456
456
  if (changed.length === 0) {
457
457
  logger.info?.(
458
- `${SCOPED_TAG} skipped — no changed files between ${epicBranch} and ${storyBranch}.`,
458
+ `${SCOPED_TAG} skipped — no changed files between ${baseBranch} and ${storyBranch}.`,
459
459
  );
460
460
  return { ran: false, committed: false, reason: 'no-changed-files' };
461
461
  }
@@ -205,15 +205,12 @@ function buildCodeReviewBlockedExtra({ storyId, reviewResult }) {
205
205
  * `refresh.js`).
206
206
  * - Standalone close: propagates throws (a review failure stops the close).
207
207
  *
208
- * The optional `planningRisk` envelope (Story #3940) is forwarded verbatim to
209
- * `runCodeReview`, which folds its `overallLevel` together with the
210
- * `baseRef...headRef` changed-file count (which `runCodeReview` enumerates
211
- * itself, scoped to the diff under review) into the review depth via the
212
- * shared {@link resolveDepth} resolver. It is an **input-only** signal: it
213
- * tells the provider how thorough to be and never alters the review's output
214
- * envelope or the posted structured-comment body. Absent (`null` / `undefined`,
215
- * the standalone close path) → `runCodeReview` resolves the neutral depth from
216
- * diff width alone, byte-identical to the pre-#3940 behaviour.
208
+ * Review depth is not passed in: `runCodeReview` derives it entirely from the
209
+ * `baseRef...headRef` diff it enumerates itself the changed files' sensitive-
210
+ * path intersection plus their count (Story #4542, which retired the
211
+ * planner-authored risk envelope this spine used to forward). Depth remains an
212
+ * **input-only** signal: it tells the provider how thorough to be and never
213
+ * alters the review's output envelope or the posted structured-comment body.
217
214
  *
218
215
  * @param {{
219
216
  * storyId: number|string,
@@ -223,7 +220,6 @@ function buildCodeReviewBlockedExtra({ storyId, reviewResult }) {
223
220
  * provider: object,
224
221
  * progress: (tag: string, msg: string) => void,
225
222
  * progressTag?: string,
226
- * planningRisk?: { overallLevel?: ('low'|'medium'|'high'), axes?: Array<{ axis?: string, level?: string }> }|null,
227
223
  * runCodeReviewFn?: typeof runCodeReview,
228
224
  * runLocalLensReviewFn?: typeof runLocalLensReview,
229
225
  * }} args
@@ -241,7 +237,6 @@ export async function runStoryReviewCore({
241
237
  provider,
242
238
  progress,
243
239
  progressTag = 'CODE-REVIEW',
244
- planningRisk = null,
245
240
  runCodeReviewFn = runCodeReview,
246
241
  runLocalLensReviewFn = runLocalLensReview,
247
242
  }) {
@@ -260,12 +255,6 @@ export async function runStoryReviewCore({
260
255
  if (commentTargetId != null) {
261
256
  opts.commentTargetId = commentTargetId;
262
257
  }
263
- // Forward the parent Epic's judged risk only on the Epic-attached path; the
264
- // standalone caller leaves this null so `runCodeReview` resolves depth from
265
- // diff width alone (no plan checkpoint exists for a standalone Story).
266
- if (planningRisk != null) {
267
- opts.planningRisk = planningRisk;
268
- }
269
258
 
270
259
  // Shift-left local-lens pass (Epic #4405). Runs matched local lenses at
271
260
  // `light` depth against the actual Story diff, inside this close-subprocess
@@ -291,23 +280,19 @@ export async function runStoryReviewCore({
291
280
  * `blocked` is either `null` (caller proceeds to open/merge the PR) or the
292
281
  * blocked-envelope (caller returns it verbatim and the CLI exits 1).
293
282
  *
294
- * The optional `planningRisk` envelope (Story #3940) is the parent Epic's
295
- * judged risk, read best-effort off the `epic-plan-state` checkpoint by the
296
- * locked pipeline. It is forwarded into `runCodeReview` so the review depth is
297
- * resolved from BOTH the parent Epic's judged risk (pre-v2) and the Story's own
298
- * base…head changed-file count a small Story under a
299
- * high-risk Epic still earns `deep`, a small Story under a low-risk Epic gets
300
- * `light`, and an absent envelope resolves `standard` (today's behaviour).
301
- * Depth is input-only: it never changes `{ blocked }` or the posted comment.
283
+ * Review depth is derived inside `runCodeReview` from this Story's own base…head
284
+ * diff a narrow change touching a registered sensitive path earns `deep`, a
285
+ * small change touching none gets `light`, a wide diff earns `deep` on size, and
286
+ * an unenumerable diff resolves `standard` (Story #4542). Depth is input-only:
287
+ * it never changes `{ blocked }` or the posted comment.
302
288
  *
303
289
  * @param {{
304
290
  * storyId: number|string,
305
- * epicBranch: string,
291
+ * baseBranch: string,
306
292
  * storyBranch: string,
307
293
  * provider: object,
308
294
  * bus: { emit: Function }|null,
309
295
  * progress: (tag: string, msg: string) => void,
310
- * planningRisk?: { overallLevel?: ('low'|'medium'|'high'), axes?: Array<{ axis?: string, level?: string }> }|null,
311
296
  * runCodeReviewFn?: typeof runCodeReview,
312
297
  * runLocalLensReviewFn?: typeof runLocalLensReview,
313
298
  * }} args
@@ -319,12 +304,11 @@ export async function runStoryReviewCore({
319
304
  export async function runStoryCodeReview(args) {
320
305
  const {
321
306
  storyId,
322
- epicBranch,
307
+ baseBranch,
323
308
  storyBranch,
324
309
  provider,
325
310
  bus,
326
311
  progress,
327
- planningRisk = null,
328
312
  runCodeReviewFn = runCodeReview,
329
313
  runLocalLensReviewFn = runLocalLensReview,
330
314
  } = args;
@@ -332,18 +316,17 @@ export async function runStoryCodeReview(args) {
332
316
  const storyIdNum = Number(storyId);
333
317
  progress(
334
318
  'CODE-REVIEW',
335
- `Running Story-scope review (${epicBranch}…${storyBranch})...`,
319
+ `Running Story-scope review (${baseBranch}…${storyBranch})...`,
336
320
  );
337
321
 
338
322
  let reviewResult;
339
323
  try {
340
324
  reviewResult = await runStoryReviewCore({
341
325
  storyId: storyIdNum,
342
- baseRef: epicBranch,
326
+ baseRef: baseBranch,
343
327
  headRef: storyBranch,
344
328
  provider,
345
329
  progress,
346
- planningRisk,
347
330
  runCodeReviewFn,
348
331
  runLocalLensReviewFn,
349
332
  });