mandrel 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/.agents/README.md +59 -28
  2. package/.agents/agents/acceptance-critic.md +20 -9
  3. package/.agents/agents/story-worker.md +45 -48
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +60 -46
  6. package/.agents/docs/agentrc-reference.json +8 -13
  7. package/.agents/docs/configuration.md +33 -57
  8. package/.agents/docs/execution-reference.md +39 -10
  9. package/.agents/docs/quality-gates.md +17 -19
  10. package/.agents/docs/workflows.md +6 -6
  11. package/.agents/instructions.md +64 -79
  12. package/.agents/rules/ci-remediation.md +3 -3
  13. package/.agents/rules/gherkin-standards.md +10 -0
  14. package/.agents/rules/git-conventions-reference.md +42 -51
  15. package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
  16. package/.agents/schemas/agentrc.schema.json +35 -46
  17. package/.agents/schemas/audit-rules.json +59 -1
  18. package/.agents/schemas/audit-rules.schema.json +33 -1
  19. package/.agents/schemas/lifecycle/README.md +1 -2
  20. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  21. package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
  22. package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
  23. package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
  24. package/.agents/schemas/signal-event.schema.json +3 -3
  25. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  26. package/.agents/schemas/validation-evidence.schema.json +1 -1
  27. package/.agents/scripts/acceptance-eval.js +24 -68
  28. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  29. package/.agents/scripts/bootstrap.js +3 -3
  30. package/.agents/scripts/check-dead-exports.js +43 -104
  31. package/.agents/scripts/check-doc-links.js +2 -2
  32. package/.agents/scripts/check-lifecycle-lint.js +1 -1
  33. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  34. package/.agents/scripts/deliver-recover.js +122 -0
  35. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  36. package/.agents/scripts/evidence-gate.js +20 -50
  37. package/.agents/scripts/generate-skills-index.js +17 -1
  38. package/.agents/scripts/generate-workflows-doc.js +4 -4
  39. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  40. package/.agents/scripts/lib/audit-suite/selector.js +323 -23
  41. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
  42. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  43. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  44. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  45. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  46. package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
  47. package/.agents/scripts/lib/checks/index.js +1 -1
  48. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  49. package/.agents/scripts/lib/checks/state.js +17 -248
  50. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
  51. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  52. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  53. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  54. package/.agents/scripts/lib/cli-args.js +23 -2
  55. package/.agents/scripts/lib/close-validation/gates.js +13 -13
  56. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  57. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  58. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  59. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  60. package/.agents/scripts/lib/config/acceptance-eval.js +2 -2
  61. package/.agents/scripts/lib/config/delivery-routing.js +7 -6
  62. package/.agents/scripts/lib/config/explain.js +10 -16
  63. package/.agents/scripts/lib/config/github.js +7 -5
  64. package/.agents/scripts/lib/config/limits.js +15 -25
  65. package/.agents/scripts/lib/config/quality.js +11 -14
  66. package/.agents/scripts/lib/config/runners.js +8 -21
  67. package/.agents/scripts/lib/config/temp-paths.js +18 -56
  68. package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
  69. package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
  70. package/.agents/scripts/lib/config-settings-schema.js +48 -22
  71. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  72. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  73. package/.agents/scripts/lib/duplicate-search.js +38 -7
  74. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  75. package/.agents/scripts/lib/format-generated-json.js +97 -0
  76. package/.agents/scripts/lib/framework-version.js +19 -189
  77. package/.agents/scripts/lib/gh-exec.js +8 -0
  78. package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
  79. package/.agents/scripts/lib/git-utils.js +0 -14
  80. package/.agents/scripts/lib/json-utils.js +1 -2
  81. package/.agents/scripts/lib/label-constants.js +0 -15
  82. package/.agents/scripts/lib/label-taxonomy.js +1 -12
  83. package/.agents/scripts/lib/observability/active-story-env.js +42 -163
  84. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  85. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  86. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  87. package/.agents/scripts/lib/observability/source-classifier.js +2 -2
  88. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  89. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  90. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  91. package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
  92. package/.agents/scripts/lib/orchestration/change-set.js +103 -0
  93. package/.agents/scripts/lib/orchestration/code-review.js +70 -191
  94. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
  95. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  96. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  97. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  98. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  99. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  100. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
  101. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  102. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  103. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
  104. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  105. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  106. package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
  107. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  108. package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
  109. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
  110. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
  111. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  112. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
  113. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
  114. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  115. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
  116. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
  117. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
  118. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  119. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
  120. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
  121. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
  122. package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
  123. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  124. package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
  125. package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
  126. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  127. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  128. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  129. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  130. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  131. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  132. package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
  133. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
  134. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  135. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  136. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +4 -13
  137. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  138. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
  139. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  140. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  141. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
  142. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  143. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  144. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
  145. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  146. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  147. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
  148. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
  149. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
  150. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  151. package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
  152. package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
  153. package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
  154. package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
  155. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  156. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
  157. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
  158. package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
  159. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
  160. package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
  161. package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
  162. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  163. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  164. package/.agents/scripts/lib/planning-corpus.js +12 -286
  165. package/.agents/scripts/lib/preflight-runner.js +2 -2
  166. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  167. package/.agents/scripts/lib/signals/index.js +4 -17
  168. package/.agents/scripts/lib/signals/read.js +35 -35
  169. package/.agents/scripts/lib/signals/schema.js +8 -11
  170. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  171. package/.agents/scripts/lib/signals/write.js +0 -1
  172. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  173. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  174. package/.agents/scripts/lib/story-adjacency.js +8 -7
  175. package/.agents/scripts/lib/story-body/story-body.js +81 -13
  176. package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
  177. package/.agents/scripts/lib/test-env.js +14 -1
  178. package/.agents/scripts/lib/test-tiers.js +0 -3
  179. package/.agents/scripts/lib/ticket-body-sections.js +0 -14
  180. package/.agents/scripts/lib/validation-evidence.js +31 -59
  181. package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
  182. package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
  183. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  184. package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
  185. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  186. package/.agents/scripts/plan-context.js +38 -7
  187. package/.agents/scripts/plan-critics.js +203 -0
  188. package/.agents/scripts/plan-persist.js +145 -35
  189. package/.agents/scripts/plan-run-epilogue.js +83 -38
  190. package/.agents/scripts/post-structured-comment.js +0 -38
  191. package/.agents/scripts/pr-watch-with-update.js +43 -22
  192. package/.agents/scripts/providers/github/compose.js +0 -1
  193. package/.agents/scripts/providers/github/errors.js +0 -19
  194. package/.agents/scripts/providers/github/issues.js +1 -11
  195. package/.agents/scripts/providers/github/mappers.js +5 -0
  196. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  197. package/.agents/scripts/providers/github/tickets.js +33 -153
  198. package/.agents/scripts/providers/github.js +17 -6
  199. package/.agents/scripts/quality-preview.js +13 -6
  200. package/.agents/scripts/resolve-stories.js +236 -0
  201. package/.agents/scripts/run-coverage.js +4 -1
  202. package/.agents/scripts/run-lint.js +2 -2
  203. package/.agents/scripts/run-verify.js +31 -2
  204. package/.agents/scripts/signals-view.js +9 -10
  205. package/.agents/scripts/single-story-close.js +173 -18
  206. package/.agents/scripts/single-story-confirm-merge.js +288 -15
  207. package/.agents/scripts/single-story-init.js +6 -10
  208. package/.agents/scripts/stories-wave-tick.js +380 -53
  209. package/.agents/scripts/story-plan.js +3 -3
  210. package/.agents/scripts/update-ticket-state.js +8 -50
  211. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  212. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  213. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  214. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  215. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  216. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  217. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  218. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  219. package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
  220. package/.agents/skills/core/scope-triage/SKILL.md +3 -0
  221. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  222. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  223. package/.agents/skills/skills.index.json +2 -12
  224. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  225. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  226. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  227. package/.agents/workflows/audit-architecture.md +3 -4
  228. package/.agents/workflows/audit-clean-code.md +4 -4
  229. package/.agents/workflows/audit-documentation.md +4 -5
  230. package/.agents/workflows/audit-lighthouse.md +8 -0
  231. package/.agents/workflows/audit-navigability.md +10 -0
  232. package/.agents/workflows/audit-performance.md +2 -3
  233. package/.agents/workflows/audit-quality.md +8 -9
  234. package/.agents/workflows/audit-security.md +1 -2
  235. package/.agents/workflows/audit-seo.md +10 -0
  236. package/.agents/workflows/audit-ux-ui.md +7 -0
  237. package/.agents/workflows/deliver.md +133 -45
  238. package/.agents/workflows/git-cleanup.md +2 -2
  239. package/.agents/workflows/git-deliver.md +1 -1
  240. package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
  241. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
  242. package/.agents/workflows/helpers/code-review.md +14 -12
  243. package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
  244. package/.agents/workflows/helpers/deliver-story.md +209 -118
  245. package/.agents/workflows/helpers/parallel-tooling.md +2 -2
  246. package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
  247. package/.agents/workflows/plan.md +239 -19
  248. package/.agents/workflows/qa-assist.md +6 -6
  249. package/.agents/workflows/qa-explore.md +3 -3
  250. package/.agents/workflows/qa-run.md +1 -5
  251. package/bin/mandrel.js +12 -1
  252. package/docs/CHANGELOG.md +62 -0
  253. package/lib/cli/registry.js +262 -19
  254. package/lib/cli/sync-agents.js +157 -0
  255. package/lib/cli/sync-commands.js +115 -6
  256. package/lib/cli/sync.js +168 -6
  257. package/lib/cli/update.js +105 -8
  258. package/lib/cli/version-helpers.js +131 -0
  259. package/lib/migrations/README.md +7 -5
  260. package/lib/migrations/index.js +17 -9
  261. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  262. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  263. package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
  264. package/package.json +2 -2
  265. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  266. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  267. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  268. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  269. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  270. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  271. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  272. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  273. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  274. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  275. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  276. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  277. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  278. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  279. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  280. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  281. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  282. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  283. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  284. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  285. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  286. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  287. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  288. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  289. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  290. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  291. package/.agents/schemas/risk-verdict.schema.json +0 -53
  292. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  293. package/.agents/scripts/analyze-execution.js +0 -444
  294. package/.agents/scripts/check-prepush-recovery.js +0 -90
  295. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  296. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  297. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
  298. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  299. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  300. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  301. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
  302. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
  303. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  304. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  305. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  306. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  307. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  308. package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
  309. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  310. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  311. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  312. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
  313. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  314. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  315. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  316. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
  317. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  318. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
  319. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  320. package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
  321. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
  322. package/.agents/scripts/resolve-plan-run.js +0 -117
  323. package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
@@ -1,78 +0,0 @@
1
- /**
2
- * phases/notification.js — post-merge story-complete notification phase.
3
- *
4
- * Fires a single consolidated story-merged webhook, then a rolled-up
5
- * `epic-progress` webhook (comment-suppressed) so operators see the
6
- * Epic's stories-done count tick up at each story-close without
7
- * subscribing to the per-story `story-merged` channel. The rolled-up
8
- * dispatch is best-effort — a flaky webhook MUST NOT block story-close.
9
- */
10
-
11
- import { notify } from '../../../../notify.js';
12
- import { Logger } from '../../../Logger.js';
13
-
14
- function reapPhaseLogger(progress) {
15
- return progress ?? (() => {});
16
- }
17
-
18
- export async function notificationPhase(ctx, state) {
19
- const {
20
- epicId,
21
- storyId,
22
- story,
23
- epicBranch,
24
- config,
25
- progress,
26
- provider,
27
- notifyFn = notify,
28
- logger = Logger,
29
- } = ctx;
30
- const closedTickets = state.ticketClosure?.closedTickets ?? [];
31
- const log = reapPhaseLogger(progress);
32
- log('NOTIFY', `Sending story-complete notification for Story #${storyId}...`);
33
- await notifyFn(
34
- epicId,
35
- {
36
- severity: 'medium',
37
- message: `✅ Story #${storyId} — *${story.title}* — has been completed and merged into \`${epicBranch}\`. ${closedTickets.length} ticket(s) closed.`,
38
- event: 'story-merged',
39
- level: 'story',
40
- epicId,
41
- },
42
- { config },
43
- );
44
- // Fire a rolled-up `epic-progress` webhook so operators see the Epic's
45
- // overall stories-done count tick up at each story-close, without
46
- // subscribing to the per-story `story-merged` channel. Comment is
47
- // suppressed (skipComment: true) — the operator-facing GitHub
48
- // comment is owned by the wave-record path; this fire is webhook-only.
49
- // Failures are swallowed by design (warn-then-continue) so a flaky
50
- // webhook never blocks story-close.
51
- if (provider && epicId) {
52
- try {
53
- const subs = (await provider.getSubTickets?.(epicId)) ?? [];
54
- const stories = subs.filter(
55
- (t) => Array.isArray(t.labels) && t.labels.includes('type::story'),
56
- );
57
- const total = stories.length;
58
- const done = stories.filter((s) => s.state === 'closed').length;
59
- const pct = total === 0 ? 0 : Math.round((done / total) * 100);
60
- await notifyFn(
61
- epicId,
62
- {
63
- severity: 'medium',
64
- message: `Epic #${epicId} progress · ${done}/${total} stories done (${pct}%) · Story #${storyId} merged`,
65
- event: 'epic-progress',
66
- level: 'epic',
67
- epicId,
68
- },
69
- { config, skipComment: true },
70
- );
71
- } catch (err) {
72
- logger?.warn?.(
73
- `[notificationPhase] rolled-up epic-progress dispatch failed (swallowed): ${err?.message ?? err}`,
74
- );
75
- }
76
- }
77
- log('NOTIFY', '✅ Notification sent');
78
- }
@@ -1,68 +0,0 @@
1
- /**
2
- * phases/temp-cleanup.js — remove the per-Story manifest pair under
3
- * `temp/epic-<eid>/stories/story-<sid>/manifest.{md,json}` (Epic #1030
4
- * Story #1040). Falls back to the legacy flat
5
- * `temp/story-manifest-<id>.{md,json}` layout when `epicId` is unknown
6
- * — both paths are tried so partial migrations don't leak files in
7
- * either layout. All deletes are idempotent; ENOENT is silently
8
- * absorbed.
9
- */
10
-
11
- import path from 'node:path';
12
- import { storyArtifactPath } from '../../../config/temp-paths.js';
13
-
14
- function reapPhaseLogger(progress) {
15
- return progress ?? (() => {});
16
- }
17
-
18
- export async function tempCleanupPhase(ctx) {
19
- const { storyId, epicId, projectRoot, progress, unlinkFn, config } = ctx;
20
- const log = reapPhaseLogger(progress);
21
- const unlink = unlinkFn ?? (await import('node:fs/promises')).unlink;
22
-
23
- // Per-Epic layout (Epic #1030 Story #1040): `temp/epic-<eid>/stories/story-<sid>/manifest.{md,json}`.
24
- // Legacy flat layout: `temp/story-manifest-<sid>.{md,json}`. The migration
25
- // tolerates both — try the per-Epic path first when `epicId` is known,
26
- // and always sweep the legacy path so a half-migrated cohort doesn't
27
- // leave residue.
28
- const targets = [];
29
- if (epicId) {
30
- const eid = Number(epicId);
31
- const sid = Number(storyId);
32
- targets.push(
33
- {
34
- path: storyArtifactPath(eid, sid, 'manifest.md', config),
35
- label: `temp/epic-${epicId}/stories/story-${storyId}/manifest.md`,
36
- },
37
- {
38
- path: storyArtifactPath(eid, sid, 'manifest.json', config),
39
- label: `temp/epic-${epicId}/stories/story-${storyId}/manifest.json`,
40
- },
41
- );
42
- }
43
- // Legacy flat layout is rooted at the framework's projectRoot — this is
44
- // a half-migrated-cohort sweep, not a configured-tempRoot target. Once
45
- // the legacy paths can no longer exist on any live install, this block
46
- // can be deleted entirely.
47
- const legacyBase = path.join(
48
- projectRoot,
49
- 'temp',
50
- `story-manifest-${storyId}`,
51
- );
52
- targets.push(
53
- { path: `${legacyBase}.md`, label: `temp/story-manifest-${storyId}.md` },
54
- {
55
- path: `${legacyBase}.json`,
56
- label: `temp/story-manifest-${storyId}.json`,
57
- },
58
- );
59
-
60
- for (const target of targets) {
61
- try {
62
- await unlink(target.path);
63
- log('CLEANUP', `🗑️ Deleted ${target.label}`);
64
- } catch {
65
- // File may not exist — deletion is idempotent.
66
- }
67
- }
68
- }
@@ -1,118 +0,0 @@
1
- /**
2
- * phases/ticket-closure.js — post-merge ticket transition + cascade phase.
3
- *
4
- * Transitions the Story to `agent::done`, then runs cascade completion
5
- * so any parent Epic-side rollup that is now fully resolved closes too.
6
- *
7
- * **2-tier closure (Story #3127).** Under the 2-tier hierarchy a Story
8
- * is the leaf unit of execution and has no child tickets — `tasks`
9
- * arrives as an empty array. `batchTransitionTickets` handles the empty
10
- * input cleanly (the loop trivially completes), the Story is
11
- * transitioned alone, and cascade completion walks upward to
12
- * parent. No branch on hierarchy mode is required here.
13
- *
14
- * Notifications are intentionally
15
- * NOT routed through the per-ticket transitions here — `notificationPhase`
16
- * fires a single rolled-up story-complete message immediately after this
17
- * phase, so threading `notify` through would double-emit events on every
18
- * close (Story #2534 / Task #2539).
19
- */
20
-
21
- import { batchTransitionTickets } from '../../../story-lifecycle.js';
22
- import { toDone } from '../../label-transitions.js';
23
- import { cascadeCompletion, STATE_LABELS } from '../../ticketing.js';
24
-
25
- function reapPhaseLogger(progress) {
26
- return progress ?? (() => {});
27
- }
28
-
29
- export async function ticketClosurePhase(ctx) {
30
- const { provider, tasks, storyId, progress, logger } = ctx;
31
- const log = reapPhaseLogger(progress);
32
-
33
- // The `notify` function is intentionally NOT forwarded to per-ticket
34
- // transitions here. `notificationPhase` fires a single consolidated
35
- // story-complete message immediately after this phase; passing notify
36
- // through would emit redundant state-transition events (one from the
37
- // cascade-up triggered by any child ticket, one from the explicit
38
- // Story toDone below) that show up as duplicate Slack/webhook lines per
39
- // story close.
40
- log(
41
- 'TICKETS',
42
- `Transitioning ${tasks.length} child ticket(s) to agent::done...`,
43
- );
44
- const batch = await batchTransitionTickets(
45
- provider,
46
- tasks,
47
- STATE_LABELS.DONE,
48
- { progress: log },
49
- );
50
- const closedTickets = [...batch.transitioned, ...batch.skipped];
51
-
52
- // Story #2534 / Task #2539 — auto-transition `agent::closing →
53
- // agent::done` deterministically and idempotently on every successful
54
- // merge. The pre-Story #2534 path silently swallowed transport errors
55
- // here, which made an already-merged-but-not-labelled Story look closed
56
- // in the close-result envelope. We now:
57
- // - read the Story snapshot first to detect an "already done" state
58
- // (label `agent::done` AND issue state `closed`) and treat re-runs
59
- // as a no-op record, satisfying the idempotency contract;
60
- // - otherwise call `toDone` unconditionally — there is no other
61
- // conditional skip in any normal exit path;
62
- // - rethrow transport errors so the surrounding
63
- // `runPhase('ticket-closure', ...)` logs the failure loudly via
64
- // `[phase=ticket-closure] <message>` instead of letting the close
65
- // report success on a half-failed transition.
66
- log('TICKETS', `Transitioning Story #${storyId} to agent::done...`);
67
- let storySnapshot = null;
68
- try {
69
- storySnapshot = await provider.getTicket(storyId);
70
- } catch (err) {
71
- logger?.warn?.(
72
- `[phase=tickets] Story #${storyId} snapshot read failed (continuing with transition): ${err?.message ?? err}`,
73
- );
74
- }
75
- const alreadyDone =
76
- storySnapshot &&
77
- Array.isArray(storySnapshot.labels) &&
78
- storySnapshot.labels.includes(STATE_LABELS.DONE) &&
79
- storySnapshot.state === 'closed';
80
- if (alreadyDone) {
81
- log(
82
- 'TICKETS',
83
- ` #${storyId} already agent::done — no-op (idempotent re-run)`,
84
- );
85
- closedTickets.push(storyId);
86
- } else {
87
- await toDone(provider, [storyId]);
88
- closedTickets.push(storyId);
89
- log('TICKETS', ` #${storyId} → agent::done ✅`);
90
- }
91
-
92
- log('TICKETS', 'Running cascade completion...');
93
- let cascadedTo = [];
94
- let cascadeFailed = [];
95
- try {
96
- const cascade = (await cascadeCompletion(provider, storyId)) ?? {
97
- cascadedTo: [],
98
- failed: [],
99
- };
100
- cascadedTo = cascade.cascadedTo ?? [];
101
- cascadeFailed = cascade.failed ?? [];
102
- if (cascadedTo.length > 0) {
103
- log(
104
- 'TICKETS',
105
- ` Cascaded to: ${cascadedTo.map((id) => `#${id}`).join(', ')}`,
106
- );
107
- }
108
- for (const { parentId, error } of cascadeFailed) {
109
- logger.error(
110
- ` Cascade partial-failure on parent #${parentId}: ${error}`,
111
- );
112
- }
113
- } catch (err) {
114
- logger.error(` Cascade fully failed (non-fatal): ${err.message}`);
115
- }
116
-
117
- return { closedTickets, cascadedTo, cascadeFailed };
118
- }
@@ -1,397 +0,0 @@
1
- /**
2
- * phases/worktree-reap.js — post-merge worktree reap phase.
3
- *
4
- * After a Story branch is merged (pre-v2: into the Epic branch; v2:
5
- * squash-merged to `main` via `single-story-close.js`), this phase removes
6
- * the per-Story worktree at `.worktrees/story-<id>/`. The reap is split
7
- * into three concerns:
8
- *
9
- * 1. `worktreeReapPhase` — the phase entry point. Honors the
10
- * `worktreeIsolation.{enabled,reapOnSuccess}` config, delegates the
11
- * actual remove to `WorktreeManager.reap`, then runs a re-prune
12
- * retry loop to clear stale `.git/worktrees/<name>/` registry
13
- * entries that Windows file-locks occasionally leave behind.
14
- * 2. `applyStillRegisteredState` — classifies a still-registered entry
15
- * after retries as either `stale-registry-entry` (operationally
16
- * complete; schedule pending-cleanup drain) or `still-registered`
17
- * (genuine failure; OPERATOR ACTION REQUIRED).
18
- * 3. Friction-signal emission via `signals-writer` so retrospectives
19
- * surface lock failures and stale-registry escalations.
20
- */
21
-
22
- import fs from 'node:fs';
23
- import path from 'node:path';
24
- import { setTimeout as sleepPromise } from 'node:timers/promises';
25
- import { getStoryBranch } from '../../../git-utils.js';
26
- import { appendSignal } from '../../../observability/signals-writer.js';
27
- import { recordPendingCleanup } from '../../../worktree/lifecycle/pending-cleanup.js';
28
- import { WorktreeManager } from '../../../worktree-manager.js';
29
-
30
- const WINDOWS_LOCK_RE =
31
- /(permission denied|access is denied|directory not empty|resource busy|device or resource busy|sharing violation|EACCES|EBUSY|ENOTEMPTY)/i;
32
-
33
- /**
34
- * Backoff delays (ms) between `git worktree prune` + re-list passes when a
35
- * worktree entry is still registered after a successful reap. On Windows the
36
- * registry-cleanup half of `git worktree remove` often loses a race with a
37
- * background file handle (Defender / Search indexer / the editor) — the
38
- * directory and branch are gone, but `.git/worktrees/<name>/` is locked
39
- * for a beat. Three retries (250ms, 1s, 4s) cover the common case without
40
- * adding noticeable latency to clean POSIX runs.
41
- */
42
- const STALE_REGISTRY_REPRUNE_DELAYS_MS = [250, 1000, 4000];
43
-
44
- function isWindowsReapLockFailure(reason) {
45
- return typeof reason === 'string' && WINDOWS_LOCK_RE.test(reason);
46
- }
47
-
48
- function findStillRegisteredEntry(entries, storyId) {
49
- if (!Array.isArray(entries)) return undefined;
50
- const want = Number(storyId);
51
- return entries.find((r) => {
52
- if (!r || typeof r.path !== 'string') return false;
53
- const match = r.path.match(/[/\\]story-(\d+)$/);
54
- return match ? Number(match[1]) === want : false;
55
- });
56
- }
57
-
58
- function resolveWorktreeRoot(repoRoot, delivery) {
59
- if (!repoRoot) return null;
60
- const configuredRoot = delivery?.worktreeIsolation?.root ?? '.worktrees';
61
- return path.join(repoRoot, configuredRoot);
62
- }
63
-
64
- async function retryPruneUntilCleared(
65
- wm,
66
- storyId,
67
- { sleep, delays = STALE_REGISTRY_REPRUNE_DELAYS_MS } = {},
68
- ) {
69
- const sleepFn = typeof sleep === 'function' ? sleep : sleepPromise;
70
- let attempts = 0;
71
- let lastEntry;
72
- for (const delay of delays) {
73
- await sleepFn(delay);
74
- attempts += 1;
75
- if (typeof wm.prune === 'function') {
76
- try {
77
- await wm.prune();
78
- } catch {
79
- // best-effort — prune failure is non-fatal; fall through to list check
80
- }
81
- }
82
- const refreshed = (await wm.list()) ?? [];
83
- lastEntry = findStillRegisteredEntry(refreshed, storyId);
84
- if (!lastEntry) return { cleared: true, attempts };
85
- }
86
- return { cleared: false, attempts, stillRegistered: lastEntry };
87
- }
88
-
89
- function reapPhaseLogger(progress) {
90
- return progress ?? (() => {});
91
- }
92
-
93
- export function createWorktreeReapState(overrides = {}) {
94
- return {
95
- status: 'not-run',
96
- path: null,
97
- reason: null,
98
- method: null,
99
- pendingCleanup: null,
100
- branchDeleted: null,
101
- remoteBranchDeleted: null,
102
- ...overrides,
103
- };
104
- }
105
-
106
- async function emitReapFailureFriction({
107
- storyId,
108
- epicId,
109
- reapResult,
110
- epicBranch,
111
- logger,
112
- config,
113
- }) {
114
- if (!epicId || !storyId) return;
115
- const reason = String(reapResult?.reason ?? 'unknown');
116
- const wtPath = reapResult?.path ?? '(unknown path)';
117
- try {
118
- await appendSignal({
119
- epicId: Number(epicId),
120
- storyId: Number(storyId),
121
- signal: {
122
- kind: 'friction',
123
- ts: new Date().toISOString(),
124
- epicId: Number(epicId),
125
- storyId: Number(storyId),
126
- category: 'reap-failure',
127
- emitter: { tool: 'story-close.js' },
128
- details: { message: `Worktree reap failed: ${reason}` },
129
- epicBranch,
130
- worktreePath: wtPath,
131
- reason,
132
- },
133
- config,
134
- });
135
- } catch (err) {
136
- logger?.warn?.(
137
- `[post-merge-pipeline] friction signal append failed: ${err?.message ?? err}`,
138
- );
139
- }
140
- }
141
-
142
- function resolveSkipState(wtConfig, log) {
143
- if (!wtConfig?.enabled) {
144
- log('WORKTREE', '⏭️ Skipping worktree reap (worktree isolation disabled)');
145
- return createWorktreeReapState({ status: 'skipped-disabled' });
146
- }
147
- if (!(wtConfig.reapOnSuccess ?? true)) {
148
- log('WORKTREE', '⏭️ Skipping worktree reap (reapOnSuccess=false)');
149
- return createWorktreeReapState({ status: 'skipped-config' });
150
- }
151
- return null;
152
- }
153
-
154
- function resolveInitialReapStatus(reapResult) {
155
- if (reapResult.removed) return 'removed';
156
- if (reapResult.method === 'deferred-to-sweep') return 'deferred-to-sweep';
157
- return 'failed';
158
- }
159
-
160
- function initialReapState(reapResult) {
161
- return createWorktreeReapState({
162
- status: resolveInitialReapStatus(reapResult),
163
- path: reapResult.path ?? null,
164
- reason: reapResult.reason ?? null,
165
- method: reapResult.method ?? null,
166
- pendingCleanup: reapResult.pendingCleanup ?? null,
167
- branchDeleted:
168
- reapResult.branchDeleted !== undefined ? reapResult.branchDeleted : null,
169
- remoteBranchDeleted:
170
- reapResult.remoteBranchDeleted !== undefined
171
- ? reapResult.remoteBranchDeleted
172
- : null,
173
- });
174
- }
175
-
176
- async function logReapOutcome({
177
- reapResult,
178
- log,
179
- logger,
180
- storyId,
181
- epicId,
182
- epicBranch,
183
- config,
184
- }) {
185
- if (reapResult.removed) {
186
- log('WORKTREE', `🗑️ Reaped worktree: ${reapResult.path}`);
187
- return;
188
- }
189
- if (!reapResult.reason) return;
190
- await emitReapFailureFriction({
191
- storyId,
192
- epicId,
193
- reapResult,
194
- epicBranch,
195
- logger,
196
- config,
197
- });
198
- log(
199
- 'WORKTREE',
200
- `⚠️ Worktree not reaped (${reapResult.reason}): ${reapResult.path}`,
201
- );
202
- if (isWindowsReapLockFailure(reapResult.reason)) {
203
- logger.error(
204
- `[story-close] OPERATOR ACTION REQUIRED: Worktree at ${reapResult.path} ` +
205
- `could not be removed (Windows lock/permission error: ${reapResult.reason}). ` +
206
- 'Close any editor/terminal holding the path, then run ' +
207
- '`git worktree remove <path> --force && git worktree prune` to clean up.',
208
- );
209
- }
210
- }
211
-
212
- async function detectStillRegistered({ wm, storyId, log, sleep }) {
213
- const leftover = (await wm.list()) ?? [];
214
- const initial = findStillRegisteredEntry(leftover, storyId);
215
- if (!initial) return null;
216
- const retry = await retryPruneUntilCleared(wm, storyId, { sleep });
217
- if (retry.cleared) {
218
- log(
219
- 'WORKTREE',
220
- `🧹 Stale worktree registry entry cleared after ${retry.attempts} re-prune attempt(s)`,
221
- );
222
- return null;
223
- }
224
- return retry.stillRegistered ?? initial;
225
- }
226
-
227
- async function escalateStillRegistered({
228
- state,
229
- stillRegistered,
230
- storyId,
231
- epicId,
232
- epicBranch,
233
- logger,
234
- config,
235
- }) {
236
- logger.error(
237
- `[story-close] OPERATOR ACTION REQUIRED: Worktree still registered after reap: ` +
238
- `${stillRegistered.path}. Run ` +
239
- '`git worktree remove <path> --force && git worktree prune` to clean up.',
240
- );
241
- await emitReapFailureFriction({
242
- storyId,
243
- epicId,
244
- reapResult: {
245
- path: stillRegistered.path,
246
- reason: 'still-registered-after-reap',
247
- },
248
- epicBranch,
249
- logger,
250
- config,
251
- });
252
- return state;
253
- }
254
-
255
- function logStaleRegistryEntry({ state, stillRegistered, logger }) {
256
- logger.warn(
257
- `[story-close] Worktree directory removed and branch deleted, but ` +
258
- `\`git worktree list\` still shows ${stillRegistered.path}. ` +
259
- 'Scheduled for background prune via pending-cleanup; ' +
260
- `branchDeleted=${state.branchDeleted}.`,
261
- );
262
- return state;
263
- }
264
-
265
- export async function worktreeReapPhase(ctx) {
266
- const {
267
- delivery,
268
- storyId,
269
- epicId,
270
- epicBranch,
271
- repoRoot,
272
- progress,
273
- logger,
274
- worktreeManagerFactory,
275
- config,
276
- sleep,
277
- recordPendingCleanupFn = recordPendingCleanup,
278
- pathExistsFn = fs.existsSync,
279
- } = ctx;
280
- const wtConfig = delivery?.worktreeIsolation;
281
- const log = reapPhaseLogger(progress);
282
- const skipState = resolveSkipState(wtConfig, log);
283
- if (skipState) return skipState;
284
-
285
- const wm = worktreeManagerFactory
286
- ? worktreeManagerFactory({ repoRoot, config: wtConfig })
287
- : new WorktreeManager({ repoRoot, config: wtConfig });
288
- const reapResult = await wm.reap(storyId, { epicBranch });
289
- let state = initialReapState(reapResult);
290
- await logReapOutcome({
291
- reapResult,
292
- log,
293
- logger,
294
- storyId,
295
- epicId,
296
- epicBranch,
297
- config,
298
- });
299
-
300
- const stillRegistered = await detectStillRegistered({
301
- wm,
302
- storyId,
303
- log,
304
- sleep,
305
- });
306
- if (!stillRegistered) return state;
307
-
308
- state = applyStillRegisteredState({
309
- state,
310
- stillRegistered,
311
- reapResult,
312
- storyId,
313
- delivery,
314
- repoRoot,
315
- logger,
316
- recordPendingCleanupFn,
317
- pathExistsFn,
318
- });
319
- if (state.status === 'still-registered') {
320
- return escalateStillRegistered({
321
- state,
322
- stillRegistered,
323
- storyId,
324
- epicId,
325
- epicBranch,
326
- logger,
327
- config,
328
- });
329
- }
330
- if (state.status === 'stale-registry-entry') {
331
- return logStaleRegistryEntry({ state, stillRegistered, logger });
332
- }
333
- return state;
334
- }
335
-
336
- /**
337
- * Decide how to treat a worktree entry that is still registered after reap +
338
- * re-prune retries. Two outcomes:
339
- *
340
- * - `stale-registry-entry` (operationally complete): the reap succeeded,
341
- * the worktree directory is gone, and the local branch was deleted by
342
- * reap. The only artifact left is the `.git/worktrees/<name>/` registry
343
- * entry — a Windows file-lock artifact, not a genuine cleanup failure.
344
- * Record a pending-cleanup entry so the post-close drain (or the next
345
- * plan-time sweep) re-runs `git worktree prune`, and let the close
346
- * pipeline report `branchDeleted: true` honestly.
347
- * - `still-registered` (genuine failure): the directory is still on disk
348
- * OR the branch was not deleted. The pre-existing OPERATOR ACTION
349
- * escalation still fires.
350
- */
351
- function applyStillRegisteredState({
352
- state,
353
- stillRegistered,
354
- reapResult,
355
- storyId,
356
- delivery,
357
- repoRoot,
358
- logger,
359
- recordPendingCleanupFn,
360
- pathExistsFn,
361
- }) {
362
- const pathGone = !pathExistsFn(stillRegistered.path);
363
- const branchDeleted = reapResult.branchDeleted === true;
364
- const operationallyComplete =
365
- reapResult.removed === true && pathGone && branchDeleted;
366
- if (!operationallyComplete) {
367
- return {
368
- ...state,
369
- status: 'still-registered',
370
- path: stillRegistered.path,
371
- reason: 'still-registered-after-reap',
372
- };
373
- }
374
- const worktreeRoot = resolveWorktreeRoot(repoRoot, delivery);
375
- let manifestEntry = null;
376
- if (worktreeRoot) {
377
- try {
378
- manifestEntry = recordPendingCleanupFn(worktreeRoot, {
379
- storyId: Number(storyId),
380
- branch: getStoryBranch(storyId),
381
- path: stillRegistered.path,
382
- push: false,
383
- });
384
- } catch (err) {
385
- logger?.warn?.(
386
- `[post-merge-pipeline] pending-cleanup record failed (continuing): ${err?.message ?? err}`,
387
- );
388
- }
389
- }
390
- return {
391
- ...state,
392
- status: 'stale-registry-entry',
393
- path: stillRegistered.path,
394
- reason: 'stale-registry-entry',
395
- pendingCleanup: manifestEntry ?? state.pendingCleanup,
396
- };
397
- }