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
@@ -5,12 +5,10 @@
5
5
  * `/deliver` story-list path.
6
6
  *
7
7
  * Thin **adapter** over the path-agnostic ready-set scheduling core
8
- * (`lib/wave-runner/ready-set.js#selectReadySet`). It consumes an
9
- * operator-supplied dependency DAG of standalone Story IDs plus the live
10
- * progress of the run (which Stories are done, how many are in flight) and
11
- * emits the set of Stories safe to dispatch **on this beat** — a Story
12
- * becomes dispatchable the instant its own dependencies are done, under the
13
- * same global concurrency cap and the same file-overlap co-dispatch guard
8
+ * (`lib/wave-runner/ready-set.js#selectReadySet`). It emits the set of
9
+ * Stories safe to dispatch **on this beat** a Story becomes dispatchable
10
+ * the instant its own dependencies are done, under the same global
11
+ * concurrency cap and the same file-overlap co-dispatch guard
14
12
  * `lib/wave-runner/ready-set.js` applies everywhere. There is no wave barrier: this no longer batches
15
13
  * Stories into fully-draining waves; it selects continuously.
16
14
  *
@@ -18,9 +16,27 @@
18
16
  * group N+1 opens, via `Graph.js#assignLayers`) is gone. The scheduling
19
17
  * kernel — adjacency derivation, the done-predicate classifier, the
20
18
  * eligibility rule, and the overlap guard — lives once in `selectReadySet`;
21
- * this file only parses input, resolves the cap, and renders the envelope.
19
+ * this file only gathers input, resolves the cap, and renders the envelope.
20
+ *
21
+ * **Two modes, one kernel.**
22
+ *
23
+ * - **Probe mode** (`--stories <csv> --probe-live [--dispatched <csv>]`) is
24
+ * the canonical `/deliver` beat: the graph, the done set, and the in-flight
25
+ * count are resolved from **live state** via `lib/wave-runner/live-probe.js`.
26
+ * The caller supplies ids, so there is no accounting to hand-maintain
27
+ * across beats — the seed-the-first-beat's-`--done` footgun the workflow
28
+ * used to warn about is structurally impossible rather than merely
29
+ * documented. `--dispatched` is the one fact live state cannot yet report
30
+ * ("I spawned this id; its label has not appeared"); it is additive and
31
+ * live-state-filtered, never authoritative (Story #4601).
32
+ * - **Flag mode** (`--dag`/`--dag-file` + `--done`/`--in-flight`) keeps the
33
+ * caller-supplied contract byte-compatible for tests and hand-driven
34
+ * runs. The two are mutually exclusive: honouring a supplied `--done`
35
+ * under `--probe-live` would silently reintroduce exactly the
36
+ * hand-maintained state probe mode retires.
22
37
  *
23
38
  * Usage:
39
+ * node .agents/scripts/stories-wave-tick.js --stories 101,102 --probe-live
24
40
  * node .agents/scripts/stories-wave-tick.js --dag '<json>'
25
41
  * node .agents/scripts/stories-wave-tick.js --dag-file <path>
26
42
  * node .agents/scripts/stories-wave-tick.js --dag '<json>' --concurrency 5
@@ -37,14 +53,23 @@
37
53
  * totalStories: number,
38
54
  * concurrencyCap: number,
39
55
  * inFlight: number,
40
- * cycleError: string | null
56
+ * cycleError: string | null,
57
+ * wedged: { reason, stories: [{ id, unmetBlockers }] } | null
41
58
  * }
42
59
  *
43
- * The standalone loop calls this once per beat: after each Story closes it
44
- * re-runs with the closed Story added to `--done` and the live in-flight
45
- * count in `--in-flight`, dispatching the returned `ready` set (already
46
- * capped at `concurrencyCap inFlight` by the core). The run is complete
47
- * when every Story is in `--done` and `ready` is empty.
60
+ * Probe mode adds fields the caller can no longer compute for itself:
61
+ * `done: number[]` (the resolved done set, in-set satisfied foreign
62
+ * blockers), `epilogueDue: boolean` (true exactly when every listed Story
63
+ * is done the run-end signal for `plan-run-epilogue.js`), and `blocked:
64
+ * number[]` + `blockedReason: string|null` (Story #4601 the `agent::blocked`
65
+ * HITL pause, which ends the loop rather than being polled).
66
+ *
67
+ * The standalone loop calls this once per beat and dispatches the returned
68
+ * `ready` set (already capped at `concurrencyCap − inFlight` by the core).
69
+ * Under `--probe-live` each beat re-reads reality, so the run is complete
70
+ * when `epilogueDue` is true; under flag mode the caller re-supplies `--done`
71
+ * and `--in-flight` itself, and the run is complete when every Story is in
72
+ * `--done` and `ready` is empty.
48
73
  *
49
74
  * The per-beat concurrency cap is resolved from the same config seam
50
75
  * `/deliver` uses — `resolveConfig` + `getRunners` reading
@@ -54,7 +79,17 @@
54
79
  * deterministic config source (`delivery.deliverRunner.concurrencyCap`) and
55
80
  * one scheduling kernel with every `/deliver` multi-Story invocation.
56
81
  *
57
- * On cycle detection, exits with code 2 and sets cycleError in the envelope.
82
+ * Exit codes: 0 ok · 1 input error · 2 dependency cycle (`cycleError`) ·
83
+ * 3 wedged (`wedged`) — ready is empty, nothing is in flight, and undone
84
+ * Stories are waiting on blockers that are not done · 4 blocked (`blocked`) —
85
+ * a Story carries `agent::blocked`. A cycle is a self-referential DAG the
86
+ * operator must fix; a wedge is a well-formed DAG whose gates cannot be
87
+ * satisfied from the supplied `--done` set (usually a blocker outside the
88
+ * delivered set that has not landed); a block is the protocol's HITL pause,
89
+ * where a human owes a decision no beat can supply. All three are distinct
90
+ * from the ordinary `ready: []` that means "waiting on in-flight work" — and
91
+ * that distinction is the point: each of them previously presented AS that
92
+ * ordinary empty set, so the loop polled a state that could never improve.
58
93
  */
59
94
 
60
95
  import { readFileSync } from 'node:fs';
@@ -65,16 +100,48 @@ import { getRunners, resolveConfig } from './lib/config-resolver.js';
65
100
  import { detectCycle } from './lib/Graph.js';
66
101
  import { Logger } from './lib/Logger.js';
67
102
  import { AGENT_LABELS } from './lib/label-constants.js';
103
+ import { parseIds } from './lib/orchestration/resolve-stories.js';
68
104
  import { buildStoryAdjacency } from './lib/story-adjacency.js';
105
+ import {
106
+ createProbeContext,
107
+ probeLiveState,
108
+ validateProbeFlags,
109
+ } from './lib/wave-runner/live-probe.js';
69
110
  import { selectReadySet } from './lib/wave-runner/ready-set.js';
70
111
 
71
- const HELP = `Usage: node .agents/scripts/stories-wave-tick.js --dag '<json>' | --dag-file <path> [--concurrency <n>] [--done <csv>] [--in-flight <n>]
112
+ /**
113
+ * Exit code for a wedged run — deliberately distinct from the cycle exit (2)
114
+ * so a caller can tell "your DAG is self-referential" from "your DAG is fine
115
+ * but its gates can never be satisfied from this `--done` set".
116
+ */
117
+ export const WEDGED_EXIT_CODE = 3;
72
118
 
73
- Continuous ready-set planner for standalone Story delivery. Consumes a
74
- dependency graph of Story IDs plus the live run progress and emits the set
75
- of Stories safe to dispatch on this beat a Story is dispatchable the
76
- instant its own dependencies are done plus the resolved per-beat
77
- concurrency cap and the same file-overlap guard as selectReadySet.
119
+ /**
120
+ * Exit code for a run holding an `agent::blocked` Story distinct from the
121
+ * cycle (2) and wedge (3) exits because the remediation is categorically
122
+ * different: a cycle is a malformed DAG and a wedge is an unlanded blocker,
123
+ * whereas this is the protocol's one runtime HITL pause. A human must decide
124
+ * something before any beat can help. Probe-mode only: flag-mode nodes carry
125
+ * no labels, so nothing there can classify blocked.
126
+ */
127
+ export const BLOCKED_EXIT_CODE = 4;
128
+
129
+ const HELP = `Usage:
130
+ node .agents/scripts/stories-wave-tick.js --stories <csv> --probe-live [--dispatched <csv>] [--concurrency <n>]
131
+ node .agents/scripts/stories-wave-tick.js --dag '<json>' | --dag-file <path> [--concurrency <n>] [--done <csv>] [--in-flight <n>]
132
+
133
+ Continuous ready-set planner for standalone Story delivery. Emits the set of
134
+ Stories safe to dispatch on this beat — a Story is dispatchable the instant
135
+ its own dependencies are done — plus the resolved per-beat concurrency cap
136
+ and the same file-overlap guard as selectReadySet.
137
+
138
+ Two modes:
139
+ --probe-live Resolve the graph and derive done / in-flight from LIVE state
140
+ (the canonical /deliver beat). Nothing is hand-maintained
141
+ across beats. Mutually exclusive with --dag/--dag-file/--done/
142
+ --in-flight. Adds "done" and "epilogueDue" to the envelope.
143
+ --dag Legacy flag mode: the caller supplies the graph and the run
144
+ progress. Kept for tests and hand-driven runs.
78
145
 
79
146
  Input DAG format (JSON array):
80
147
  [{ "id": 101, "dependsOn": [] }, { "id": 102, "dependsOn": [101] }]
@@ -84,6 +151,19 @@ Each entry must include:
84
151
  dependsOn - Array of Story IDs that must complete before this Story runs
85
152
 
86
153
  Options:
154
+ --stories <csv> Story ids to deliver (probe mode). The graph, the done
155
+ set, and the in-flight count are resolved from live
156
+ state — no --done / --in-flight bookkeeping.
157
+ --probe-live Enable probe mode. Requires --stories.
158
+ --dispatched <csv> Probe mode only. Ids you have SPAWNED this run. Unioned
159
+ into the live-derived in-flight set, then filtered by
160
+ live state, so it closes the init window: a Story reads
161
+ agent::ready for the 3-6 minutes single-story-init.js
162
+ takes to flip agent::executing, and without this it is
163
+ dispatched a second time onto the same branch. Append
164
+ every id you dispatch and never remove one — a stale id
165
+ that has since gone done is dropped automatically, so
166
+ over-supplying is free and forgetting is the only error.
87
167
  --concurrency <n> Override the per-beat concurrency cap for this run only.
88
168
  Must be a positive integer. When omitted, the cap is
89
169
  resolved from delivery.deliverRunner.concurrencyCap in
@@ -102,15 +182,47 @@ Output envelope:
102
182
  "totalStories": 2,
103
183
  "concurrencyCap": 3,
104
184
  "inFlight": 0,
105
- "cycleError": null
185
+ "cycleError": null,
186
+ "wedged": null
106
187
  }
107
188
 
108
189
  Exit codes:
109
190
  0 - Success, ready set emitted
110
191
  1 - Invalid input (missing/malformed DAG, invalid --concurrency/--in-flight/--done)
111
192
  2 - Cycle detected in dependency graph
193
+ 3 - Wedged: ready is empty, nothing is in flight, and undone Stories are
194
+ waiting on blockers that are not done. Distinct from an ordinary empty
195
+ ready set (which means "waiting on in-flight work") and from a cycle.
196
+ 4 - Blocked: a Story carries agent::blocked (probe mode only). The HITL
197
+ pause — no beat can clear it. STOP the loop; do not poll.
112
198
  `;
113
199
 
200
+ /**
201
+ * Build the exit-1 input-error result. Shared by both modes so a malformed
202
+ * `--concurrency` reports identically whether it arrived alongside `--dag` or
203
+ * `--probe-live`.
204
+ *
205
+ * @param {string} message
206
+ * @param {number|null} [concurrencyCap]
207
+ * @param {number} [inFlightValue]
208
+ * @returns {{ envelope: object, exitCode: 1 }}
209
+ */
210
+ function inputErrorResult(message, concurrencyCap = null, inFlightValue = 0) {
211
+ return {
212
+ envelope: {
213
+ kind: 'stories-ready-set',
214
+ ready: [],
215
+ totalStories: 0,
216
+ concurrencyCap,
217
+ inFlight: inFlightValue,
218
+ cycleError: null,
219
+ wedged: null,
220
+ inputError: message,
221
+ },
222
+ exitCode: 1,
223
+ };
224
+ }
225
+
114
226
  /**
115
227
  * Parse and validate the raw DAG input array.
116
228
  *
@@ -182,15 +294,16 @@ export function parseDag(raw) {
182
294
  }
183
295
 
184
296
  /**
185
- * Parse a comma-separated `--done` list of Story IDs into a deduped set of
186
- * positive integers. Empty / absent input yields an empty set. Rejects any
187
- * token that is not a positive integer so a typo never silently drops a
188
- * dependency gate.
297
+ * Parse a comma-separated list of Story IDs into a deduped set of positive
298
+ * integers. Empty / absent input yields an empty set. Rejects any token that
299
+ * is not a positive integer so a typo never silently drops a dependency gate
300
+ * (`--done`) or a held dispatch slot (`--dispatched`).
189
301
  *
190
302
  * @param {string|undefined} raw
303
+ * @param {string} flag Flag name, for the error message.
191
304
  * @returns {{ ids: Set<number>|null, error: string|null }}
192
305
  */
193
- export function parseDoneIds(raw) {
306
+ export function parseIdCsv(raw, flag) {
194
307
  if (raw == null || raw === '') {
195
308
  return { ids: new Set(), error: null };
196
309
  }
@@ -202,7 +315,7 @@ export function parseDoneIds(raw) {
202
315
  if (!Number.isInteger(num) || num <= 0) {
203
316
  return {
204
317
  ids: null,
205
- error: `--done must be a comma-separated list of positive integers, got "${trimmed}"`,
318
+ error: `${flag} must be a comma-separated list of positive integers, got "${trimmed}"`,
206
319
  };
207
320
  }
208
321
  ids.add(num);
@@ -210,6 +323,16 @@ export function parseDoneIds(raw) {
210
323
  return { ids, error: null };
211
324
  }
212
325
 
326
+ /**
327
+ * Parse the `--done` CSV of already-completed Story IDs (flag mode).
328
+ *
329
+ * @param {string|undefined} raw
330
+ * @returns {{ ids: Set<number>|null, error: string|null }}
331
+ */
332
+ export function parseDoneIds(raw) {
333
+ return parseIdCsv(raw, '--done');
334
+ }
335
+
213
336
  /**
214
337
  * Parse the raw `--in-flight` value into a non-negative integer. Absent
215
338
  * input defaults to 0. Rejects negatives and non-integers.
@@ -322,6 +445,7 @@ export function buildReadySetEnvelope(
322
445
  concurrencyCap,
323
446
  inFlight,
324
447
  cycleError: null,
448
+ wedged: null,
325
449
  };
326
450
 
327
451
  if (totalStories === 0) {
@@ -352,7 +476,12 @@ export function buildReadySetEnvelope(
352
476
  const rec = {
353
477
  id: node.id,
354
478
  dependsOn: node.dependsOn,
355
- labels: doneIds.has(node.id) ? [AGENT_LABELS.DONE] : [],
479
+ // A node's own live labels (probe mode) are preserved so the core's
480
+ // classifier withholds an in-flight `agent::executing` / `agent::closing`
481
+ // Story rather than re-dispatching it onto a second branch. Flag-mode
482
+ // nodes carry none — `parseDag` accepts no labels — so this is inert
483
+ // there and the legacy contract is unchanged.
484
+ labels: doneIds.has(node.id) ? [AGENT_LABELS.DONE] : (node.labels ?? []),
356
485
  };
357
486
  if (node.files !== undefined) rec.files = node.files;
358
487
  return rec;
@@ -365,7 +494,62 @@ export function buildReadySetEnvelope(
365
494
  globalCap: concurrencyCap,
366
495
  }).map((rec) => rec.id);
367
496
 
368
- return { envelope: { ...base, ready }, exitCode: 0 };
497
+ // Wedge detection (Story #4540). `ready: []` is normal while work is in
498
+ // flight — the loop is simply waiting. But ready-empty AND nothing in
499
+ // flight AND undone Stories remaining means no beat can ever make
500
+ // progress: the run is stuck, and the previous behaviour was to return
501
+ // exit 0 with an empty ready set forever, indistinguishable from
502
+ // "waiting". Name the stuck ids and their unmet blockers.
503
+ //
504
+ // Distinct from `cycleError`/exit 2: a cycle is a self-referential DAG,
505
+ // whereas this is a DAG whose gates are real but unsatisfiable from the
506
+ // supplied `done` set (typically a blocker outside the delivered set that
507
+ // has not landed).
508
+ const wedge = detectWedge({ nodes, doneIds, ready, inFlight });
509
+ if (wedge) {
510
+ return {
511
+ envelope: { ...base, ready, wedged: wedge },
512
+ exitCode: WEDGED_EXIT_CODE,
513
+ };
514
+ }
515
+
516
+ return { envelope: { ...base, ready, wedged: null }, exitCode: 0 };
517
+ }
518
+
519
+ /**
520
+ * Identify a run that cannot progress: nothing dispatchable, nothing in
521
+ * flight, work remaining.
522
+ *
523
+ * @param {{ nodes: object[], doneIds: Set<number>, ready: number[], inFlight: number }} args
524
+ * @returns {{ reason: string, stories: Array<{ id: number, unmetBlockers: number[] }> }|null}
525
+ */
526
+ export function detectWedge({ nodes, doneIds, ready, inFlight }) {
527
+ if (ready.length > 0 || inFlight > 0) return null;
528
+ const undone = nodes.filter((n) => !doneIds.has(n.id));
529
+ if (undone.length === 0) return null;
530
+
531
+ const stories = undone
532
+ .map((n) => ({
533
+ id: n.id,
534
+ unmetBlockers: (n.dependsOn ?? []).filter((dep) => !doneIds.has(dep)),
535
+ }))
536
+ .filter((s) => s.unmetBlockers.length > 0);
537
+
538
+ // Undone work with no unmet blockers would have been dispatched; if that
539
+ // is the whole set, the cap or in-flight accounting explains the empty
540
+ // ready set rather than a wedge.
541
+ if (stories.length === 0) return null;
542
+
543
+ const detail = stories
544
+ .map((s) => `#${s.id} ← ${s.unmetBlockers.map((d) => `#${d}`).join(', ')}`)
545
+ .join('; ');
546
+ return {
547
+ reason:
548
+ `No Story can be dispatched: nothing is in flight and ${stories.length} ` +
549
+ `Story(ies) are waiting on blockers that are not done — ${detail}. ` +
550
+ `A blocker outside the delivered set must land first, or be included in --ids.`,
551
+ stories,
552
+ };
369
553
  }
370
554
 
371
555
  /**
@@ -397,36 +581,23 @@ export function runStoriesWaveTick({
397
581
  cwd,
398
582
  config,
399
583
  } = {}) {
400
- const inputError = (message, concurrencyCap = null, inFlightValue = 0) => ({
401
- envelope: {
402
- kind: 'stories-ready-set',
403
- ready: [],
404
- totalStories: 0,
405
- concurrencyCap,
406
- inFlight: inFlightValue,
407
- cycleError: null,
408
- inputError: message,
409
- },
410
- exitCode: 1,
411
- });
412
-
413
584
  // Validate the --concurrency override before resolving config so an invalid
414
585
  // value fails fast with exit code 1 regardless of DAG validity.
415
586
  const { value: override, error: concurrencyError } =
416
587
  parseConcurrencyOverride(concurrency);
417
588
  if (concurrencyError) {
418
- return inputError(concurrencyError);
589
+ return inputErrorResult(concurrencyError);
419
590
  }
420
591
 
421
592
  const { value: inFlightValue, error: inFlightError } =
422
593
  parseInFlight(inFlight);
423
594
  if (inFlightError) {
424
- return inputError(inFlightError);
595
+ return inputErrorResult(inFlightError);
425
596
  }
426
597
 
427
598
  const { ids: doneIds, error: doneError } = parseDoneIds(done);
428
599
  if (doneError) {
429
- return inputError(doneError, null, inFlightValue);
600
+ return inputErrorResult(doneError, null, inFlightValue);
430
601
  }
431
602
 
432
603
  const concurrencyCap = resolveConcurrencyCap({ cwd, config, override });
@@ -437,7 +608,7 @@ export function runStoriesWaveTick({
437
608
  try {
438
609
  rawJson = readFileSync(dagFile, 'utf8');
439
610
  } catch (err) {
440
- return inputError(
611
+ return inputErrorResult(
441
612
  `Could not read DAG file "${dagFile}": ${err.message}`,
442
613
  concurrencyCap,
443
614
  inFlightValue,
@@ -446,7 +617,7 @@ export function runStoriesWaveTick({
446
617
  } else if (dagJson) {
447
618
  rawJson = dagJson;
448
619
  } else {
449
- return inputError(
620
+ return inputErrorResult(
450
621
  'Either --dag <json> or --dag-file <path> is required',
451
622
  concurrencyCap,
452
623
  inFlightValue,
@@ -457,7 +628,7 @@ export function runStoriesWaveTick({
457
628
  try {
458
629
  parsed = JSON.parse(rawJson);
459
630
  } catch (err) {
460
- return inputError(
631
+ return inputErrorResult(
461
632
  `Invalid JSON: ${err.message}`,
462
633
  concurrencyCap,
463
634
  inFlightValue,
@@ -466,7 +637,7 @@ export function runStoriesWaveTick({
466
637
 
467
638
  const { nodes, error: parseError } = parseDag(parsed);
468
639
  if (parseError) {
469
- return inputError(parseError, concurrencyCap, inFlightValue);
640
+ return inputErrorResult(parseError, concurrencyCap, inFlightValue);
470
641
  }
471
642
 
472
643
  return buildReadySetEnvelope(nodes, {
@@ -476,12 +647,144 @@ export function runStoriesWaveTick({
476
647
  });
477
648
  }
478
649
 
650
+ /**
651
+ * Probe mode: resolve the graph and the run's progress from **live state**,
652
+ * then run the same scheduling kernel the flag mode does.
653
+ *
654
+ * This is the flag-free beat. The caller supplies only the Story ids it was
655
+ * asked to deliver; `done` and `inFlight` are probed rather than transcribed,
656
+ * which is what makes the `/deliver` loop's old seed-the-first-beat footgun
657
+ * structurally impossible instead of merely documented.
658
+ *
659
+ * The envelope is the flag mode's, plus three probe-only fields the caller can
660
+ * no longer compute for itself:
661
+ * - `done` — the resolved done set (in-set ∪ satisfied foreign blockers).
662
+ * - `epilogueDue` — true exactly when every listed Story is done, which is
663
+ * the run-end signal for `plan-run-epilogue.js`.
664
+ * - `blocked` — ids carrying `agent::blocked` (Story #4601). Non-empty means
665
+ * the loop must END, not poll: see `BLOCKED_EXIT_CODE`.
666
+ *
667
+ * @param {object} args
668
+ * @param {string} args.stories Raw `--stories` CSV of Story ids.
669
+ * @param {string|number} [args.concurrency] Raw `--concurrency` override.
670
+ * @param {string} [args.dispatched] Raw `--dispatched` CSV of ids the host
671
+ * has spawned but may not yet have observed labelled.
672
+ * @param {string} [args.cwd] Repo root for config resolution.
673
+ * @param {object} [args.config] Pre-resolved config (test injection).
674
+ * @param {Function} [args.probe] Probe seam (test injection).
675
+ * @param {Function} [args.context] Provider-context seam (test injection).
676
+ * @returns {Promise<{ envelope: object, exitCode: number }>}
677
+ */
678
+ export async function runProbedStoriesWaveTick({
679
+ stories,
680
+ concurrency,
681
+ dispatched,
682
+ cwd,
683
+ config,
684
+ probe = probeLiveState,
685
+ context = createProbeContext,
686
+ } = {}) {
687
+ const { value: override, error: concurrencyError } =
688
+ parseConcurrencyOverride(concurrency);
689
+ if (concurrencyError) {
690
+ return inputErrorResult(concurrencyError);
691
+ }
692
+
693
+ let ids;
694
+ try {
695
+ ids = parseIds(stories);
696
+ } catch (err) {
697
+ return inputErrorResult(err.message);
698
+ }
699
+
700
+ const { ids: dispatchedIds, error: dispatchedError } = parseIdCsv(
701
+ dispatched,
702
+ '--dispatched',
703
+ );
704
+ if (dispatchedError) {
705
+ return inputErrorResult(dispatchedError);
706
+ }
707
+
708
+ const concurrencyCap = resolveConcurrencyCap({ cwd, config, override });
709
+
710
+ let probed;
711
+ try {
712
+ const { provider, owner, repo } = context();
713
+ probed = await probe({
714
+ ids,
715
+ provider,
716
+ owner,
717
+ repo,
718
+ dispatched: [...dispatchedIds],
719
+ warn: (m) => Logger.warn(m),
720
+ });
721
+ } catch (err) {
722
+ // A failed probe must never degrade into "nothing is ready" — that is
723
+ // indistinguishable from a healthy waiting beat and would silently stall
724
+ // the run. Fail loud with the input-error contract instead.
725
+ return inputErrorResult(
726
+ `Could not probe live state: ${err?.message ?? err}`,
727
+ concurrencyCap,
728
+ );
729
+ }
730
+
731
+ const { nodes, doneIds, inFlight, blockedIds = [] } = probed;
732
+ const { envelope, exitCode } = buildReadySetEnvelope(nodes, {
733
+ concurrencyCap,
734
+ doneIds,
735
+ inFlight,
736
+ });
737
+
738
+ const done = [...doneIds].sort((a, b) => a - b);
739
+ const epilogueDue =
740
+ nodes.length > 0 && nodes.every((node) => doneIds.has(node.id));
741
+ return {
742
+ envelope: {
743
+ ...envelope,
744
+ done,
745
+ epilogueDue,
746
+ blocked: blockedIds,
747
+ blockedReason: blockedReasonFor(blockedIds),
748
+ },
749
+ // A blocked Story outranks the scheduler's own verdict — including a
750
+ // wedge, whose named blockers are moot while a human owes a decision.
751
+ // A cycle (2) does not yield: a self-referential DAG is a planning error
752
+ // that must be fixed before any of this run's state means anything.
753
+ exitCode:
754
+ blockedIds.length > 0 && !envelope.cycleError
755
+ ? BLOCKED_EXIT_CODE
756
+ : exitCode,
757
+ };
758
+ }
759
+
760
+ /**
761
+ * Render the operator-facing reason for a blocked run, or `null` when nothing
762
+ * is blocked.
763
+ *
764
+ * @param {number[]} blockedIds
765
+ * @returns {string|null}
766
+ */
767
+ function blockedReasonFor(blockedIds) {
768
+ if (blockedIds.length === 0) return null;
769
+ const list = blockedIds.map((id) => `#${id}`).join(', ');
770
+ return (
771
+ `${blockedIds.length} Story(ies) carry agent::blocked — ${list}. ` +
772
+ `agent::blocked is the protocol's HITL pause: no beat can clear it and ` +
773
+ `the loop must stop rather than poll. Read each Story's friction comment ` +
774
+ `(gh issue view <id> --comments), resolve the blocker, then flip it back ` +
775
+ `with: node .agents/scripts/update-ticket-state.js --ticket <id> --state agent::ready`
776
+ );
777
+ }
778
+
479
779
  async function main(argv) {
480
780
  const { values } = parseArgs({
481
781
  args: argv,
482
782
  options: {
483
783
  dag: { type: 'string' },
484
784
  'dag-file': { type: 'string' },
785
+ stories: { type: 'string' },
786
+ 'probe-live': { type: 'boolean' },
787
+ dispatched: { type: 'string' },
485
788
  concurrency: { type: 'string' },
486
789
  done: { type: 'string' },
487
790
  'in-flight': { type: 'string' },
@@ -496,19 +799,43 @@ async function main(argv) {
496
799
  return;
497
800
  }
498
801
 
499
- const { envelope, exitCode } = runStoriesWaveTick({
500
- dagJson: values.dag,
802
+ const flagError = validateProbeFlags({
803
+ probeLive: values['probe-live'],
804
+ stories: values.stories,
805
+ dag: values.dag,
501
806
  dagFile: values['dag-file'],
502
- concurrency: values.concurrency,
503
807
  done: values.done,
504
808
  inFlight: values['in-flight'],
809
+ dispatched: values.dispatched,
505
810
  });
506
811
 
812
+ const { envelope, exitCode } = flagError
813
+ ? inputErrorResult(flagError)
814
+ : values['probe-live']
815
+ ? await runProbedStoriesWaveTick({
816
+ stories: values.stories,
817
+ concurrency: values.concurrency,
818
+ dispatched: values.dispatched,
819
+ })
820
+ : runStoriesWaveTick({
821
+ dagJson: values.dag,
822
+ dagFile: values['dag-file'],
823
+ concurrency: values.concurrency,
824
+ done: values.done,
825
+ inFlight: values['in-flight'],
826
+ });
827
+
507
828
  process.stdout.write(`${JSON.stringify(envelope, null, 2)}\n`);
508
829
 
509
830
  if (exitCode !== 0) {
510
831
  Logger.error(
511
- `stories-wave-tick: ${envelope.inputError ?? envelope.cycleError ?? 'error'}`,
832
+ `stories-wave-tick: ${
833
+ envelope.inputError ??
834
+ envelope.cycleError ??
835
+ envelope.blockedReason ??
836
+ envelope.wedged?.reason ??
837
+ 'error'
838
+ }`,
512
839
  );
513
840
  process.exitCode = exitCode;
514
841
  }
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * Operator planning:
14
14
  * node .agents/scripts/plan-context.js --seed "…" | --seed-file <path> | --tickets <ids>
15
- * node .agents/scripts/plan-persist.js --stories … --risk-verdict …
15
+ * node .agents/scripts/plan-persist.js --stories …
16
16
  */
17
17
 
18
18
  import { readFile } from 'node:fs/promises';
@@ -146,7 +146,7 @@ async function runEmitContext({
146
146
  // corpus digest reads the project's actual docs directory regardless
147
147
  // of the directory this CLI happens to be invoked from — matching the
148
148
  // sibling resolution pattern in
149
- // epic-plan-spec/phases/authoring-context.js.
149
+ // planning/authoring-context.js.
150
150
  const docsRoot = path.resolve(
151
151
  PROJECT_ROOT,
152
152
  config?.project?.paths?.docsRoot ?? 'docs',
@@ -157,7 +157,7 @@ async function runEmitContext({
157
157
  loadBodyTemplate(projectRoot),
158
158
  fetchOpenStories(provider),
159
159
  readTechStackSummary(projectRoot),
160
- buildCorpusContext({ seed, provider, docsContextFiles, docsRoot }),
160
+ buildCorpusContext({ docsContextFiles, docsRoot }),
161
161
  ]);
162
162
 
163
163
  const duplicateCandidates = rankDuplicateCandidates({