mandrel 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/.agents/README.md +59 -28
  2. package/.agents/agents/acceptance-critic.md +20 -9
  3. package/.agents/agents/story-worker.md +45 -48
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +60 -46
  6. package/.agents/docs/agentrc-reference.json +8 -13
  7. package/.agents/docs/configuration.md +33 -57
  8. package/.agents/docs/execution-reference.md +39 -10
  9. package/.agents/docs/quality-gates.md +17 -19
  10. package/.agents/docs/workflows.md +6 -6
  11. package/.agents/instructions.md +64 -79
  12. package/.agents/rules/ci-remediation.md +3 -3
  13. package/.agents/rules/gherkin-standards.md +10 -0
  14. package/.agents/rules/git-conventions-reference.md +42 -51
  15. package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
  16. package/.agents/schemas/agentrc.schema.json +35 -46
  17. package/.agents/schemas/audit-rules.json +59 -1
  18. package/.agents/schemas/audit-rules.schema.json +33 -1
  19. package/.agents/schemas/lifecycle/README.md +1 -2
  20. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  21. package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
  22. package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
  23. package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
  24. package/.agents/schemas/signal-event.schema.json +3 -3
  25. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  26. package/.agents/schemas/validation-evidence.schema.json +1 -1
  27. package/.agents/scripts/acceptance-eval.js +24 -68
  28. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  29. package/.agents/scripts/bootstrap.js +3 -3
  30. package/.agents/scripts/check-dead-exports.js +43 -104
  31. package/.agents/scripts/check-doc-links.js +2 -2
  32. package/.agents/scripts/check-lifecycle-lint.js +1 -1
  33. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  34. package/.agents/scripts/deliver-recover.js +122 -0
  35. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  36. package/.agents/scripts/evidence-gate.js +20 -50
  37. package/.agents/scripts/generate-skills-index.js +17 -1
  38. package/.agents/scripts/generate-workflows-doc.js +4 -4
  39. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  40. package/.agents/scripts/lib/audit-suite/selector.js +323 -23
  41. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
  42. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  43. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  44. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  45. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  46. package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
  47. package/.agents/scripts/lib/checks/index.js +1 -1
  48. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  49. package/.agents/scripts/lib/checks/state.js +17 -248
  50. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
  51. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  52. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  53. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  54. package/.agents/scripts/lib/cli-args.js +23 -2
  55. package/.agents/scripts/lib/close-validation/gates.js +13 -13
  56. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  57. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  58. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  59. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  60. package/.agents/scripts/lib/config/acceptance-eval.js +2 -2
  61. package/.agents/scripts/lib/config/delivery-routing.js +7 -6
  62. package/.agents/scripts/lib/config/explain.js +10 -16
  63. package/.agents/scripts/lib/config/github.js +7 -5
  64. package/.agents/scripts/lib/config/limits.js +15 -25
  65. package/.agents/scripts/lib/config/quality.js +11 -14
  66. package/.agents/scripts/lib/config/runners.js +8 -21
  67. package/.agents/scripts/lib/config/temp-paths.js +18 -56
  68. package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
  69. package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
  70. package/.agents/scripts/lib/config-settings-schema.js +48 -22
  71. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  72. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  73. package/.agents/scripts/lib/duplicate-search.js +38 -7
  74. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  75. package/.agents/scripts/lib/format-generated-json.js +97 -0
  76. package/.agents/scripts/lib/framework-version.js +19 -189
  77. package/.agents/scripts/lib/gh-exec.js +8 -0
  78. package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
  79. package/.agents/scripts/lib/git-utils.js +0 -14
  80. package/.agents/scripts/lib/json-utils.js +1 -2
  81. package/.agents/scripts/lib/label-constants.js +0 -15
  82. package/.agents/scripts/lib/label-taxonomy.js +1 -12
  83. package/.agents/scripts/lib/observability/active-story-env.js +42 -163
  84. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  85. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  86. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  87. package/.agents/scripts/lib/observability/source-classifier.js +2 -2
  88. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  89. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  90. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  91. package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
  92. package/.agents/scripts/lib/orchestration/change-set.js +103 -0
  93. package/.agents/scripts/lib/orchestration/code-review.js +70 -191
  94. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
  95. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  96. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  97. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  98. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  99. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  100. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
  101. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  102. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  103. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
  104. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  105. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  106. package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
  107. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  108. package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
  109. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
  110. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
  111. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  112. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
  113. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
  114. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  115. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
  116. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
  117. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
  118. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  119. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
  120. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
  121. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
  122. package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
  123. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  124. package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
  125. package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
  126. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  127. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  128. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  129. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  130. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  131. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  132. package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
  133. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
  134. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  135. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  136. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +4 -13
  137. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  138. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
  139. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  140. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  141. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
  142. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  143. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  144. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
  145. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  146. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  147. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
  148. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
  149. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
  150. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  151. package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
  152. package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
  153. package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
  154. package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
  155. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  156. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
  157. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
  158. package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
  159. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
  160. package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
  161. package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
  162. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  163. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  164. package/.agents/scripts/lib/planning-corpus.js +12 -286
  165. package/.agents/scripts/lib/preflight-runner.js +2 -2
  166. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  167. package/.agents/scripts/lib/signals/index.js +4 -17
  168. package/.agents/scripts/lib/signals/read.js +35 -35
  169. package/.agents/scripts/lib/signals/schema.js +8 -11
  170. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  171. package/.agents/scripts/lib/signals/write.js +0 -1
  172. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  173. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  174. package/.agents/scripts/lib/story-adjacency.js +8 -7
  175. package/.agents/scripts/lib/story-body/story-body.js +81 -13
  176. package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
  177. package/.agents/scripts/lib/test-env.js +14 -1
  178. package/.agents/scripts/lib/test-tiers.js +0 -3
  179. package/.agents/scripts/lib/ticket-body-sections.js +0 -14
  180. package/.agents/scripts/lib/validation-evidence.js +31 -59
  181. package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
  182. package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
  183. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  184. package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
  185. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  186. package/.agents/scripts/plan-context.js +38 -7
  187. package/.agents/scripts/plan-critics.js +203 -0
  188. package/.agents/scripts/plan-persist.js +145 -35
  189. package/.agents/scripts/plan-run-epilogue.js +83 -38
  190. package/.agents/scripts/post-structured-comment.js +0 -38
  191. package/.agents/scripts/pr-watch-with-update.js +43 -22
  192. package/.agents/scripts/providers/github/compose.js +0 -1
  193. package/.agents/scripts/providers/github/errors.js +0 -19
  194. package/.agents/scripts/providers/github/issues.js +1 -11
  195. package/.agents/scripts/providers/github/mappers.js +5 -0
  196. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  197. package/.agents/scripts/providers/github/tickets.js +33 -153
  198. package/.agents/scripts/providers/github.js +17 -6
  199. package/.agents/scripts/quality-preview.js +13 -6
  200. package/.agents/scripts/resolve-stories.js +236 -0
  201. package/.agents/scripts/run-coverage.js +4 -1
  202. package/.agents/scripts/run-lint.js +2 -2
  203. package/.agents/scripts/run-verify.js +31 -2
  204. package/.agents/scripts/signals-view.js +9 -10
  205. package/.agents/scripts/single-story-close.js +173 -18
  206. package/.agents/scripts/single-story-confirm-merge.js +288 -15
  207. package/.agents/scripts/single-story-init.js +6 -10
  208. package/.agents/scripts/stories-wave-tick.js +380 -53
  209. package/.agents/scripts/story-plan.js +3 -3
  210. package/.agents/scripts/update-ticket-state.js +8 -50
  211. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  212. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  213. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  214. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  215. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  216. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  217. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  218. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  219. package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
  220. package/.agents/skills/core/scope-triage/SKILL.md +3 -0
  221. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  222. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  223. package/.agents/skills/skills.index.json +2 -12
  224. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  225. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  226. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  227. package/.agents/workflows/audit-architecture.md +3 -4
  228. package/.agents/workflows/audit-clean-code.md +4 -4
  229. package/.agents/workflows/audit-documentation.md +4 -5
  230. package/.agents/workflows/audit-lighthouse.md +8 -0
  231. package/.agents/workflows/audit-navigability.md +10 -0
  232. package/.agents/workflows/audit-performance.md +2 -3
  233. package/.agents/workflows/audit-quality.md +8 -9
  234. package/.agents/workflows/audit-security.md +1 -2
  235. package/.agents/workflows/audit-seo.md +10 -0
  236. package/.agents/workflows/audit-ux-ui.md +7 -0
  237. package/.agents/workflows/deliver.md +133 -45
  238. package/.agents/workflows/git-cleanup.md +2 -2
  239. package/.agents/workflows/git-deliver.md +1 -1
  240. package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
  241. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
  242. package/.agents/workflows/helpers/code-review.md +14 -12
  243. package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
  244. package/.agents/workflows/helpers/deliver-story.md +209 -118
  245. package/.agents/workflows/helpers/parallel-tooling.md +2 -2
  246. package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
  247. package/.agents/workflows/plan.md +239 -19
  248. package/.agents/workflows/qa-assist.md +6 -6
  249. package/.agents/workflows/qa-explore.md +3 -3
  250. package/.agents/workflows/qa-run.md +1 -5
  251. package/bin/mandrel.js +12 -1
  252. package/docs/CHANGELOG.md +62 -0
  253. package/lib/cli/registry.js +262 -19
  254. package/lib/cli/sync-agents.js +157 -0
  255. package/lib/cli/sync-commands.js +115 -6
  256. package/lib/cli/sync.js +168 -6
  257. package/lib/cli/update.js +105 -8
  258. package/lib/cli/version-helpers.js +131 -0
  259. package/lib/migrations/README.md +7 -5
  260. package/lib/migrations/index.js +17 -9
  261. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  262. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  263. package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
  264. package/package.json +2 -2
  265. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  266. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  267. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  268. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  269. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  270. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  271. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  272. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  273. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  274. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  275. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  276. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  277. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  278. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  279. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  280. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  281. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  282. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  283. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  284. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  285. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  286. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  287. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  288. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  289. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  290. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  291. package/.agents/schemas/risk-verdict.schema.json +0 -53
  292. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  293. package/.agents/scripts/analyze-execution.js +0 -444
  294. package/.agents/scripts/check-prepush-recovery.js +0 -90
  295. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  296. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  297. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
  298. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  299. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  300. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  301. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
  302. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
  303. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  304. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  305. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  306. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  307. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  308. package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
  309. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  310. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  311. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  312. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
  313. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  314. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  315. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  316. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
  317. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  318. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
  319. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  320. package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
  321. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
  322. package/.agents/scripts/resolve-plan-run.js +0 -117
  323. package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
@@ -12,6 +12,7 @@ import path from 'node:path';
12
12
  import { parseSprintArgs } from '../../../cli-args.js';
13
13
  import { getDeliveryRouting } from '../../../config/delivery-routing.js';
14
14
  import { PROJECT_ROOT } from '../../../project-root.js';
15
+ import { isOperatorMergeReason } from './auto-merge.js';
15
16
 
16
17
  /**
17
18
  * Resolve a flag value from an explicit override, a parsed CLI arg, or a
@@ -28,34 +29,66 @@ function resolveFlag(paramValue, parsedValue, defaultValue) {
28
29
  }
29
30
 
30
31
  /**
31
- * Resolve the `waitForMerge` option:
32
- * 1. `--no-wait-merge` / injected opt-out always wins (stop at
33
- * `agent::closing`).
34
- * 2. Explicit `--wait-merge` / injected true forces land-in-close.
35
- * 3. Otherwise default from `delivery.routing.closeAndLand` (true) so
36
- * attended and headless delivers share the close-and-land happy path.
32
+ * Resolve whether close lands the PR in-process (`waitForMerge`).
37
33
  *
38
- * @param {{ waitForMergeParam, noWaitForMergeParam, parsed }} raw
39
- * @returns {boolean}
34
+ * Called by `runSingleStoryClose` **after** config resolution and the
35
+ * auto-merge phase, because two of its four inputs are not knowable at
36
+ * parse time: the resolved config (whose `cwd` this parse produces) and the
37
+ * actual arm outcome. Resolving it here — once, where every input exists —
38
+ * is what makes `delivery.routing.closeAndLand` governable at all.
39
+ *
40
+ * Precedence, highest first:
41
+ * 1. `--no-wait-merge` / injected opt-out — always wins.
42
+ * 2. **Operator owns the merge** (`--no-auto-merge`, or
43
+ * `delivery.ci.autoMerge: "strict"`): the PR was deliberately left
44
+ * un-armed, so there is nothing for close to land. Resting at
45
+ * `agent::closing` for the human IS the documented contract for both
46
+ * surfaces; waiting would only burn the poll budget and then block a
47
+ * perfectly healthy Story. An explicit `--wait-merge` cannot override
48
+ * this — you cannot land-in-one-close a PR you refused to arm — so the
49
+ * caller is told rather than silently ignored.
50
+ * 3. Explicit `--wait-merge` / injected boolean.
51
+ * 4. `delivery.routing.closeAndLand` (framework default `true`).
52
+ *
53
+ * @param {{
54
+ * waitForMergeExplicit?: boolean,
55
+ * noWaitForMerge?: boolean,
56
+ * config?: object|null,
57
+ * autoMergeReason?: string|null,
58
+ * }} args
59
+ * @returns {{ waitForMerge: boolean, reason: 'opt-out-flag'|'operator-merge'|'explicit-flag'|'config-close-and-land' }}
40
60
  */
41
- function resolveWaitForMerge({
42
- waitForMergeParam,
43
- noWaitForMergeParam,
44
- parsed,
45
- }) {
46
- if (resolveFlag(noWaitForMergeParam, parsed.noWaitForMerge, false)) {
47
- return false;
61
+ export function resolveWaitForMerge({
62
+ waitForMergeExplicit,
63
+ noWaitForMerge = false,
64
+ config = null,
65
+ autoMergeReason = null,
66
+ } = {}) {
67
+ if (noWaitForMerge) {
68
+ return { waitForMerge: false, reason: 'opt-out-flag' };
69
+ }
70
+ if (isOperatorMergeReason(autoMergeReason)) {
71
+ return { waitForMerge: false, reason: 'operator-merge' };
48
72
  }
49
- const explicit = waitForMergeParam ?? parsed.waitForMerge;
50
- if (typeof explicit === 'boolean') return explicit;
51
- return getDeliveryRouting().closeAndLand;
73
+ if (typeof waitForMergeExplicit === 'boolean') {
74
+ return { waitForMerge: waitForMergeExplicit, reason: 'explicit-flag' };
75
+ }
76
+ return {
77
+ waitForMerge: getDeliveryRouting(config).closeAndLand,
78
+ reason: 'config-close-and-land',
79
+ };
52
80
  }
53
81
 
54
82
  /**
55
83
  * Parse and resolve all CLI / injection options for `runSingleStoryClose`.
56
84
  *
57
- * @param {{ storyIdParam, cwdParam, skipValidationParam, skipSyncParam, noAutoMergeParam, noFullScopeCrapParam, waitForMergeParam, noWaitForMergeParam }} raw
58
- * @returns {{ storyId, cwd, skipValidation, skipSync, noAutoMerge, noFullScopeCrap, waitForMerge }}
85
+ * `waitForMerge` is deliberately **not** resolved here see
86
+ * {@link resolveWaitForMerge}. This returns the raw operator intent
87
+ * (`waitForMergeExplicit` / `noWaitForMerge`) for the runner to resolve once
88
+ * the config and the arm outcome exist.
89
+ *
90
+ * @param {{ storyIdParam, cwdParam, skipValidationParam, skipSyncParam, noAutoMergeParam, waitForMergeParam, noWaitForMergeParam, maxWaitSecondsParam }} raw
91
+ * @returns {{ storyId, cwd, skipValidation, skipSync, noAutoMerge, waitForMergeExplicit, noWaitForMerge, maxWaitSeconds }}
59
92
  */
60
93
  export function parseCloseOptions({
61
94
  storyIdParam,
@@ -63,9 +96,9 @@ export function parseCloseOptions({
63
96
  skipValidationParam,
64
97
  skipSyncParam,
65
98
  noAutoMergeParam,
66
- noFullScopeCrapParam,
67
99
  waitForMergeParam,
68
100
  noWaitForMergeParam,
101
+ maxWaitSecondsParam,
69
102
  }) {
70
103
  const parsed =
71
104
  storyIdParam !== undefined
@@ -75,16 +108,26 @@ export function parseCloseOptions({
75
108
  skipValidation: !!skipValidationParam,
76
109
  skipSync: !!skipSyncParam,
77
110
  noAutoMerge: !!noAutoMergeParam,
78
- noFullScopeCrap: !!noFullScopeCrapParam,
79
111
  // Preserve undefined so resolveWaitForMerge can apply the
80
112
  // closeAndLand config default when neither flag was injected.
81
113
  waitForMerge: waitForMergeParam,
82
114
  noWaitForMerge: !!noWaitForMergeParam,
115
+ maxWaitSeconds: maxWaitSecondsParam,
83
116
  }
84
117
  : parseSprintArgs();
118
+ const waitForMergeExplicit = waitForMergeParam ?? parsed.waitForMerge;
119
+ const maxWaitSeconds = maxWaitSecondsParam ?? parsed.maxWaitSeconds;
85
120
  return {
86
121
  storyId: parsed.storyId,
87
122
  cwd: path.resolve(cwdParam ?? parsed.cwd ?? PROJECT_ROOT),
123
+ // `undefined` when unsupplied — the merge wait then reads
124
+ // `delivery.mergeWatch.maxWaitSeconds`. A per-run override exists so a
125
+ // headless caller with no host tool-invocation ceiling can keep
126
+ // single-block semantics without editing the consumer's config.
127
+ maxWaitSeconds:
128
+ Number.isInteger(maxWaitSeconds) && maxWaitSeconds > 0
129
+ ? maxWaitSeconds
130
+ : undefined,
88
131
  skipValidation: resolveFlag(
89
132
  skipValidationParam,
90
133
  parsed.skipValidation,
@@ -92,15 +135,14 @@ export function parseCloseOptions({
92
135
  ),
93
136
  skipSync: resolveFlag(skipSyncParam, parsed.skipSync, false),
94
137
  noAutoMerge: resolveFlag(noAutoMergeParam, parsed.noAutoMerge, false),
95
- noFullScopeCrap: resolveFlag(
96
- noFullScopeCrapParam,
97
- parsed.noFullScopeCrap,
138
+ waitForMergeExplicit:
139
+ typeof waitForMergeExplicit === 'boolean'
140
+ ? waitForMergeExplicit
141
+ : undefined,
142
+ noWaitForMerge: resolveFlag(
143
+ noWaitForMergeParam,
144
+ parsed.noWaitForMerge,
98
145
  false,
99
146
  ),
100
- waitForMerge: resolveWaitForMerge({
101
- waitForMergeParam,
102
- noWaitForMergeParam,
103
- parsed,
104
- }),
105
147
  };
106
148
  }
@@ -0,0 +1,305 @@
1
+ /**
2
+ * phases/post-land.js — the script-owned land tail (Story #4543).
3
+ *
4
+ * Everything after "the PR merged and the Story is `agent::done`" used to be
5
+ * prose in `helpers/deliver-story.md`: Step 5.5 resync, Step 6 cleanup, and
6
+ * follow-up capture, each a separate CLI an agent may or may not have run.
7
+ * Follow-up capture was the sharpest edge — it lived only on the standalone
8
+ * `single-story-confirm-merge.js` path, which close-and-land (the DEFAULT)
9
+ * is explicitly told to skip, so per-Story follow-ups were captured *never*
10
+ * on the default path, and a belated manual confirm could not backfill (the
11
+ * Story is already `agent::done`, so confirm short-circuits `noop` and the
12
+ * capture's `action === 'done'` gate never opens).
13
+ *
14
+ * This module folds all four steps into one phase both landing surfaces
15
+ * reach — the in-close wait (`phases/confirm-merge.js`) and the standalone
16
+ * `single-story-confirm-merge.js` CLI — so the two paths cannot diverge and
17
+ * "landed" means the whole tail ran.
18
+ *
19
+ * **Per-step booleans, not an aggregate.** {@link runPostLandTail} reports
20
+ * each step's outcome individually. That is not bookkeeping fastidiousness:
21
+ * the worktree-reap defect this repo fixed existed because a phase reported
22
+ * an outcome it never checked, and a single `tailOk: true` bit invites that
23
+ * class of bug straight back. A degraded step is visible in the terminal
24
+ * envelope without failing an otherwise-healthy land.
25
+ *
26
+ * **Never throws.** The merge already landed — the code is on the base
27
+ * branch. Failing the land because a Projects v2 mutation flaked would
28
+ * report a false negative about work that is demonstrably done. Every step
29
+ * is best-effort and records its own reason.
30
+ */
31
+
32
+ import { gitSpawn as defaultGitSpawn } from '../../../git-utils.js';
33
+ import { Logger } from '../../../Logger.js';
34
+ import {
35
+ executeFastForward as defaultExecuteFastForward,
36
+ planFastForward as defaultPlanFastForward,
37
+ } from '../../git-cleanup/phases/fast-forward.js';
38
+ import { reassertStatusColumn as defaultReassertStatusColumn } from '../../reassert-status-column.js';
39
+ import { captureStoryFollowUps as defaultCaptureStoryFollowUps } from '../../story-follow-ups.js';
40
+
41
+ /**
42
+ * Run one tail step, converting any throw into a `false` + reason. Keeps
43
+ * each step's own body free of defensive boilerplate while guaranteeing the
44
+ * module-level never-throws contract.
45
+ *
46
+ * @template T
47
+ * @param {() => Promise<{ ok: boolean, detail?: string|null }>} run
48
+ * @param {{ name: string, progress?: Function }} ctx
49
+ * @returns {Promise<{ ok: boolean, detail: string|null }>}
50
+ */
51
+ async function step(run, { name, progress }) {
52
+ try {
53
+ const outcome = await run();
54
+ if (!outcome.ok) {
55
+ progress?.(
56
+ 'POST-LAND',
57
+ `⚠️ ${name} degraded (land stands): ${outcome.detail ?? 'no detail'}`,
58
+ );
59
+ }
60
+ return { ok: Boolean(outcome.ok), detail: outcome.detail ?? null };
61
+ } catch (err) {
62
+ const detail = String(err?.message ?? err);
63
+ Logger.warn(`[post-land] ${name} threw (land stands): ${detail}`);
64
+ progress?.('POST-LAND', `⚠️ ${name} threw (land stands): ${detail}`);
65
+ return { ok: false, detail };
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Capture the Story's follow-ups from its friction signal stream.
71
+ *
72
+ * Calls `captureStoryFollowUps` **directly** rather than through the
73
+ * `captureFollowUpsAfterConfirm` action-gate wrapper: by the time the tail
74
+ * runs, the merge is already confirmed, so re-deriving that fact from a
75
+ * confirmation envelope's `action` field is the exact coupling that made
76
+ * the default path skip capture entirely.
77
+ */
78
+ async function stepFollowUps({
79
+ storyId,
80
+ provider,
81
+ config,
82
+ cwd,
83
+ progress,
84
+ captureStoryFollowUpsFn,
85
+ }) {
86
+ const result = await captureStoryFollowUpsFn({
87
+ storyId,
88
+ provider,
89
+ config,
90
+ cwd,
91
+ progress,
92
+ });
93
+ return {
94
+ ok: result?.ok === true,
95
+ detail: result?.ok === true ? null : (result?.reason ?? 'capture-failed'),
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Re-assert the Projects v2 Status column against the bot's late write.
101
+ *
102
+ * A `skipped` envelope (`no-project`, `not-on-project`, `no-meta`) is a
103
+ * **success**: the board the helper would defend does not exist, so there
104
+ * is nothing to get wrong. Only a genuine `drifted` outcome — the helper
105
+ * fired, polled, and still lost — degrades the step.
106
+ */
107
+ async function stepStatusResync({
108
+ storyId,
109
+ provider,
110
+ config,
111
+ progress,
112
+ reassertStatusColumnFn,
113
+ }) {
114
+ const outcome = await reassertStatusColumnFn({
115
+ provider,
116
+ ticketId: storyId,
117
+ config,
118
+ logger: {
119
+ info: (m) => progress?.('POST-LAND', m),
120
+ warn: (m) => progress?.('POST-LAND', `⚠️ ${m}`),
121
+ },
122
+ });
123
+ if (outcome?.status === 'synced' || outcome?.status === 'skipped') {
124
+ return { ok: true, detail: null };
125
+ }
126
+ return {
127
+ ok: false,
128
+ detail: `status column ${outcome?.status ?? 'unknown'} (target=${outcome?.column ?? 'n/a'}, attempts=${outcome?.attempts ?? 0})`,
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Reap the local `story-<id>` ref. GitHub deletes the *remote* branch on
134
+ * squash-merge (`--delete-branch`), but the local ref lingers in the main
135
+ * checkout until something prunes it.
136
+ *
137
+ * An absent ref is a success, not a failure — the sweep is idempotent and
138
+ * a previous run (or the init-time merged-sweep) may have already reaped it.
139
+ */
140
+ async function stepRefCleanup({ cwd, storyBranch, progress, gitSpawnFn }) {
141
+ const exists = gitSpawnFn(
142
+ cwd,
143
+ 'show-ref',
144
+ '--verify',
145
+ '--quiet',
146
+ `refs/heads/${storyBranch}`,
147
+ );
148
+ if (exists.status !== 0) {
149
+ progress?.('POST-LAND', `⏭ local ${storyBranch} already absent.`);
150
+ return { ok: true, detail: null };
151
+ }
152
+ const del = gitSpawnFn(cwd, 'branch', '-D', storyBranch);
153
+ if (del.status !== 0) {
154
+ return { ok: false, detail: `git branch -D failed: ${del.stderr ?? ''}` };
155
+ }
156
+ progress?.('POST-LAND', `🧹 reaped local ${storyBranch}.`);
157
+ return { ok: true, detail: null };
158
+ }
159
+
160
+ /**
161
+ * Fast-forward local `baseBranch` so the next Story seeds from the tip that
162
+ * now contains this merge. Reuses the same planner/executor pair
163
+ * `single-story-init.js` imports (rather than shelling out to
164
+ * `/git-cleanup`), which is the established in-process composition pattern.
165
+ *
166
+ * `already-up-to-date` is a success. A dirty tree is a legitimate, expected
167
+ * skip on a shared checkout — another worker is mid-flight — so it degrades
168
+ * the step's report without pretending the base moved.
169
+ */
170
+ async function stepBaseFastForward({
171
+ cwd,
172
+ baseBranch,
173
+ progress,
174
+ planFastForwardFn,
175
+ executeFastForwardFn,
176
+ }) {
177
+ const plan = planFastForwardFn({ cwd, baseBranch });
178
+ if (!plan.runnable && plan.reason === 'already-up-to-date') {
179
+ progress?.('POST-LAND', `⏭ local ${baseBranch} already up to date.`);
180
+ return { ok: true, detail: null };
181
+ }
182
+ const ff = executeFastForwardFn({
183
+ cwd,
184
+ baseBranch,
185
+ plan,
186
+ logger: {
187
+ info: (m) => progress?.('POST-LAND', m),
188
+ warn: (m) => progress?.('POST-LAND', `⚠️ ${m}`),
189
+ },
190
+ });
191
+ if (ff.applied) {
192
+ progress?.(
193
+ 'POST-LAND',
194
+ `⏩ fast-forwarded ${baseBranch} by ${ff.behind} commit(s).`,
195
+ );
196
+ return { ok: true, detail: null };
197
+ }
198
+ return {
199
+ ok: false,
200
+ detail: `fast-forward skipped: ${ff.reason ?? plan.reason ?? 'unknown'}`,
201
+ };
202
+ }
203
+
204
+ /**
205
+ * Run the whole post-land tail. Never throws.
206
+ *
207
+ * Steps run **sequentially** and in this order deliberately: follow-up
208
+ * capture and the status resync touch GitHub, while the ref reap and the
209
+ * fast-forward mutate the local checkout — and the fast-forward must run
210
+ * after the ref reap so `git branch -D` is not fighting a checkout that just
211
+ * moved HEAD.
212
+ *
213
+ * @param {object} args
214
+ * @param {number} args.storyId
215
+ * @param {string} args.storyBranch
216
+ * @param {string} args.baseBranch
217
+ * @param {string} args.cwd The MAIN checkout (never the worktree).
218
+ * @param {object} args.provider
219
+ * @param {object} [args.config]
220
+ * @param {(tag: string, msg: string) => void} [args.progress]
221
+ * @param {Function} [args.captureStoryFollowUpsFn] Test seam.
222
+ * @param {Function} [args.reassertStatusColumnFn] Test seam.
223
+ * @param {Function} [args.gitSpawnFn] Test seam.
224
+ * @param {Function} [args.planFastForwardFn] Test seam.
225
+ * @param {Function} [args.executeFastForwardFn] Test seam.
226
+ * @returns {Promise<{ followUps: boolean, statusResync: boolean, refCleanup: boolean, baseFastForward: boolean, details: Record<string, string|null> }>}
227
+ */
228
+ export async function runPostLandTail({
229
+ storyId,
230
+ storyBranch,
231
+ baseBranch,
232
+ cwd,
233
+ provider,
234
+ config,
235
+ progress,
236
+ captureStoryFollowUpsFn = defaultCaptureStoryFollowUps,
237
+ reassertStatusColumnFn = defaultReassertStatusColumn,
238
+ gitSpawnFn = defaultGitSpawn,
239
+ planFastForwardFn = defaultPlanFastForward,
240
+ executeFastForwardFn = defaultExecuteFastForward,
241
+ }) {
242
+ progress?.('POST-LAND', `🧾 Running land tail for Story #${storyId}...`);
243
+
244
+ const followUps = await step(
245
+ () =>
246
+ stepFollowUps({
247
+ storyId,
248
+ provider,
249
+ config,
250
+ cwd,
251
+ progress,
252
+ captureStoryFollowUpsFn,
253
+ }),
254
+ { name: 'follow-up capture', progress },
255
+ );
256
+ const statusResync = await step(
257
+ () =>
258
+ stepStatusResync({
259
+ storyId,
260
+ provider,
261
+ config,
262
+ progress,
263
+ reassertStatusColumnFn,
264
+ }),
265
+ { name: 'status-column resync', progress },
266
+ );
267
+ const refCleanup = await step(
268
+ () => stepRefCleanup({ cwd, storyBranch, progress, gitSpawnFn }),
269
+ { name: 'local ref cleanup', progress },
270
+ );
271
+ const baseFastForward = await step(
272
+ () =>
273
+ stepBaseFastForward({
274
+ cwd,
275
+ baseBranch,
276
+ progress,
277
+ planFastForwardFn,
278
+ executeFastForwardFn,
279
+ }),
280
+ { name: 'base fast-forward', progress },
281
+ );
282
+
283
+ const tail = {
284
+ followUps: followUps.ok,
285
+ statusResync: statusResync.ok,
286
+ refCleanup: refCleanup.ok,
287
+ baseFastForward: baseFastForward.ok,
288
+ details: {
289
+ followUps: followUps.detail,
290
+ statusResync: statusResync.detail,
291
+ refCleanup: refCleanup.detail,
292
+ baseFastForward: baseFastForward.detail,
293
+ },
294
+ };
295
+ const degraded = Object.entries(tail)
296
+ .filter(([k, v]) => k !== 'details' && v === false)
297
+ .map(([k]) => k);
298
+ progress?.(
299
+ 'POST-LAND',
300
+ degraded.length === 0
301
+ ? `✅ Land tail complete for Story #${storyId} (all steps ok).`
302
+ : `✅ Land tail complete for Story #${storyId} — degraded: ${degraded.join(', ')} (the merge stands).`,
303
+ );
304
+ return tail;
305
+ }
@@ -95,7 +95,7 @@ export async function ensurePullRequestWith({
95
95
  const body = [
96
96
  `Closes #${storyId}`,
97
97
  '',
98
- `_Auto-opened by \`/single-story-deliver\`._`,
98
+ `_Auto-opened by \`/deliver\`._`,
99
99
  ].join('\n');
100
100
  try {
101
101
  const createResult = await gh.pr.create([
@@ -1,6 +1,9 @@
1
1
  import { Logger } from '../../../Logger.js';
2
- import { AGENT_LABELS } from '../../../label-constants.js';
3
- import { upsertStructuredComment } from '../../ticketing.js';
2
+ import {
3
+ STATE_LABELS,
4
+ transitionTicketState,
5
+ upsertStructuredComment,
6
+ } from '../../ticketing.js';
4
7
 
5
8
  /**
6
9
  * Record a critical code-review halt as authoritative blocked state before
@@ -25,13 +28,14 @@ export async function handleCriticalReviewBlock({
25
28
  `[single-story-close] failed to post review-block friction: ${err?.message ?? err}`,
26
29
  );
27
30
  }
31
+ // Story #4539 — route through the canonical mutator rather than writing
32
+ // labels directly. A bare `provider.updateTicket` skips the Projects v2
33
+ // column sync (Story #2548), leaving the board on the Story's prior
34
+ // status. That is benign today only because agent::blocked and
35
+ // agent::executing happen to map to the same column — a coincidence, not
36
+ // a design, and exactly how the next drift gets in.
28
37
  try {
29
- await provider.updateTicket(storyId, {
30
- labels: {
31
- add: [AGENT_LABELS.BLOCKED],
32
- remove: [AGENT_LABELS.EXECUTING, AGENT_LABELS.READY],
33
- },
34
- });
38
+ await transitionTicketState(provider, storyId, STATE_LABELS.BLOCKED, {});
35
39
  } catch (err) {
36
40
  Logger.warn(
37
41
  `[single-story-close] failed to block Story after critical review: ${err?.message ?? err}`,
@@ -7,6 +7,16 @@
7
7
  * reports success because the operator can clean stale worktrees out of
8
8
  * band.
9
9
  *
10
+ * **Report what happened, not what was attempted** (Story #4539). This
11
+ * phase used to set `worktreeReaped = true` and log "🧹 Reaped worktree"
12
+ * on any non-throwing call — without reading the returned envelope. Since
13
+ * `reap` signals refusal by *returning* `{ removed: false, reason }`
14
+ * rather than throwing, and a v2-era precondition refused every
15
+ * `story-<id>` worktree outright, the result was that no close ever
16
+ * actually reaped and every close said it did. Best-effort means the close
17
+ * still succeeds on refusal; it does not mean the close may claim an
18
+ * outcome it never checked.
19
+ *
10
20
  * Also clears the trace-hook env vars so subsequent tooling falls back to
11
21
  * the no-op branch instead of pointing at a (now-reaped) worktree.
12
22
  */
@@ -26,7 +36,8 @@ import { WorktreeManager as DefaultWorktreeManager } from '../../../worktree-man
26
36
  * wtIsolation: object|undefined,
27
37
  * progress: (tag: string, msg: string) => void,
28
38
  * }} args
29
- * @returns {Promise<boolean>} true when reap completed
39
+ * @returns {Promise<boolean>} `true` only when the worktree was actually
40
+ * removed — never merely because the call did not throw.
30
41
  */
31
42
  export async function reapWorktreePhase({
32
43
  cwd,
@@ -49,9 +60,31 @@ export async function reapWorktreePhase({
49
60
  error: (m) => Logger.error(`[single-story-close] ${m}`),
50
61
  },
51
62
  });
52
- await wm.reap(storyId);
53
- worktreeReaped = true;
54
- progress('WORKTREE', `🧹 Reaped worktree for story-${storyId}.`);
63
+ // Deliberately NO base ref. This phase runs BEFORE the merge (see
64
+ // `../runner.js` — reap precedes the confirm phase), so "is this work
65
+ // integrated into the base?" is the wrong question: the answer is
66
+ // always no, and supplying a ref would activate `isSafeToRemove`'s
67
+ // merge-reachability gate and refuse every reap with
68
+ // `unmerged-commits` — trading one always-refuse precondition for
69
+ // another.
70
+ //
71
+ // What actually makes the reap safe here is that close has already
72
+ // pushed `story-<id>` to origin and opened the PR, so the work is
73
+ // durable off-machine; and `isSafeToRemove` still refuses a dirty
74
+ // tree (`uncommitted-changes`), which is the check that protects
75
+ // unsaved work. Refusal is signalled by the returned envelope rather
76
+ // than by throwing — so read it.
77
+ const result = await wm.reap(storyId);
78
+ worktreeReaped = result?.removed === true;
79
+ if (worktreeReaped) {
80
+ progress('WORKTREE', `🧹 Reaped worktree for story-${storyId}.`);
81
+ } else {
82
+ progress(
83
+ 'WORKTREE',
84
+ `⚠️ Worktree for story-${storyId} not reaped (${result?.reason ?? 'unknown reason'}) — ` +
85
+ `${worktreePath} left in place for the next sweep.`,
86
+ );
87
+ }
55
88
  } catch (err) {
56
89
  Logger.error(
57
90
  `[single-story-close] ⚠️ Failed to reap worktree: ${err?.message ?? err}`,
@@ -232,7 +232,7 @@ export function formatWrongTreeFinding({ storyId, strayFiles, worktreePath }) {
232
232
  `**Recovery:** relocate these edits into the worktree (re-apply them under ` +
233
233
  `\`${worktreePath}\`), restore the main checkout ` +
234
234
  `(\`git -C <main-repo> checkout -- <files>\`), then re-run ` +
235
- `\`/single-story-deliver\`.`
235
+ `\`/deliver ${storyId}\`.`
236
236
  );
237
237
  }
238
238
 
@@ -307,7 +307,7 @@ async function abortWrongTree({
307
307
  `diff-path set while the worktree (${worktreePath}) is the active work ` +
308
308
  `tree. Close aborted to avoid an empty-diff PR. Stray files: ` +
309
309
  `${strayFiles.join(', ')}. Relocate the edits into the worktree, ` +
310
- `restore the main checkout, then re-run /single-story-deliver.`,
310
+ `restore the main checkout, then re-run /deliver ${storyId}.`,
311
311
  );
312
312
  }
313
313