@yemi33/minions 0.1.2448 → 0.1.2449

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 (315) hide show
  1. package/bin/cli-api-client.js +1 -1
  2. package/bin/install-internal-minions.js +1382 -44
  3. package/bin/install-layout.js +150 -0
  4. package/bin/minions.js +460 -167
  5. package/dashboard/docs/typography.md +65 -12
  6. package/dashboard/js/command-center.js +58 -4
  7. package/dashboard/js/detail-panel.js +36 -0
  8. package/dashboard/js/memory-panel.js +59 -12
  9. package/dashboard/js/qa.js +179 -20
  10. package/dashboard/js/refresh.js +148 -12
  11. package/dashboard/js/render-dispatch.js +3 -4
  12. package/dashboard/js/render-inbox.js +2 -2
  13. package/dashboard/js/render-other.js +3 -3
  14. package/dashboard/js/render-pipelines.js +14 -0
  15. package/dashboard/js/render-plans.js +57 -9
  16. package/dashboard/js/render-prd.js +132 -23
  17. package/dashboard/js/render-prs.js +195 -166
  18. package/dashboard/js/render-schedules.js +63 -3
  19. package/dashboard/js/render-utils.js +3 -3
  20. package/dashboard/js/render-watches.js +19 -3
  21. package/dashboard/js/render-work-items.js +238 -30
  22. package/dashboard/js/settings.js +205 -54
  23. package/dashboard/js/utils.js +51 -1
  24. package/dashboard/pages/home.html +1 -1
  25. package/dashboard/pages/qa.html +1 -16
  26. package/dashboard/pages/work.html +40 -0
  27. package/dashboard/shared/cc-limits.js +79 -0
  28. package/dashboard/shared/pr-filters.js +21 -38
  29. package/dashboard/shared/project-git-summary.js +1 -1
  30. package/dashboard/shared/record-filters.js +169 -0
  31. package/dashboard/shared/watches-source.js +1 -1
  32. package/dashboard/shared/welcome-popup.js +1 -1
  33. package/dashboard/shared/wi-filters.js +302 -0
  34. package/dashboard/slim/body.html +1 -0
  35. package/dashboard/slim/js/command-send.js +26 -0
  36. package/dashboard/slim/js/modals-tiles.js +380 -39
  37. package/dashboard/slim/js/status.js +13 -21
  38. package/dashboard/slim/layout.html +1 -0
  39. package/dashboard/slim/panel-bootstrap.js +6 -2
  40. package/dashboard/slim/styles.css +38 -0
  41. package/dashboard/styles.css +159 -55
  42. package/dashboard-build.js +13 -2
  43. package/dashboard.js +956 -423
  44. package/docs/README.md +11 -6
  45. package/docs/api-errors.md +2 -2
  46. package/docs/architecture-review-2026-07-09.md +1 -1
  47. package/docs/architecture.excalidraw +2 -2
  48. package/docs/auto-discovery.md +18 -9
  49. package/docs/branch-derivation.md +4 -4
  50. package/docs/capture-demos.js +39 -2
  51. package/docs/ci-runner-canary.md +123 -0
  52. package/docs/claude-md-propagation.md +2 -2
  53. package/docs/cloud-agent-dispatch.md +204 -0
  54. package/docs/command-center.md +7 -7
  55. package/docs/completion-reports.md +43 -20
  56. package/docs/constants.md +10 -3
  57. package/docs/constellation-bridge.md +134 -6
  58. package/docs/constellation-style-telemetry.md +4 -4
  59. package/docs/contracts/capability-protocol.v1.json +165 -0
  60. package/docs/cooldown-merge-semantics.md +12 -12
  61. package/docs/copilot-cli-schema.md +7 -7
  62. package/docs/cross-repo-plans.md +10 -10
  63. package/docs/dead-code-audit-retractions.md +5 -5
  64. package/docs/default-branch-ci.md +173 -0
  65. package/docs/deprecated.json +31 -31
  66. package/docs/design-inbox-entries-schema.md +3 -3
  67. package/docs/design-language.md +1051 -0
  68. package/docs/design-state-storage.md +11 -11
  69. package/docs/diagnostics-crash-reports.md +9 -9
  70. package/docs/diagnostics-memory.md +5 -5
  71. package/docs/documentation-audit-2026-07-09.md +7 -7
  72. package/docs/engine-restart.md +90 -5
  73. package/docs/harness-mode.md +1 -1
  74. package/docs/internal-install.md +338 -39
  75. package/docs/kb-dedup-duplicate-pair-investigation.md +5 -5
  76. package/docs/kb-pr3223-cascade-archiving.md +1 -1
  77. package/docs/kb-pr696-merge-conflict-docs.md +6 -6
  78. package/docs/kb-sweep.md +35 -35
  79. package/docs/keep-processes.md +1 -1
  80. package/docs/live-checkout-mode.md +30 -30
  81. package/docs/managed-spawn.md +18 -14
  82. package/docs/named-agents.md +7 -7
  83. package/docs/plan-lifecycle.md +69 -2
  84. package/docs/pr-author-identity.md +114 -0
  85. package/docs/pr-auto-fix-dispatch.md +19 -4
  86. package/docs/pr-comment-followup.md +6 -6
  87. package/docs/pr-review-fix-loop.md +59 -10
  88. package/docs/process-termination.md +40 -0
  89. package/docs/proposals/repo-pool-for-live-checkout.md +13 -13
  90. package/docs/qa-runbook-lifecycle.md +367 -17
  91. package/docs/qa-runbooks.md +3 -3
  92. package/docs/rfc-completion-json.md +18 -18
  93. package/docs/runtime-adapters.md +26 -21
  94. package/docs/security.md +6 -6
  95. package/docs/self-improvement.md +4 -4
  96. package/docs/shared-lifecycle-module-map.md +473 -472
  97. package/docs/skills.md +52 -3
  98. package/docs/slim-ux/concepts.md +121 -116
  99. package/docs/specs/agent-configurability.md +18 -18
  100. package/docs/specs/agent-rename.md +18 -18
  101. package/docs/team-memory.md +38 -21
  102. package/docs/timeouts-and-liveness.md +118 -10
  103. package/docs/tutorials/01-install-and-connect.md +1 -1
  104. package/docs/watches.md +40 -39
  105. package/docs/workspace-manifests.md +4 -4
  106. package/docs/worktree-lifecycle.md +293 -14
  107. package/engine/README.md +46 -0
  108. package/engine/{ado-comment.js → ado/comment.js} +8 -8
  109. package/engine/{ado-git-auth.js → ado/git-auth.js} +4 -4
  110. package/engine/{ado.js → ado/index.js} +417 -63
  111. package/engine/{ado-status.js → ado/status.js} +6 -8
  112. package/engine/{ado-token.js → ado/token.js} +1 -1
  113. package/engine/{acp-transport.js → agents/acp-transport.js} +62 -22
  114. package/engine/{agent-worker-pool.js → agents/agent-worker-pool.js} +17 -8
  115. package/engine/{cc-worker-pool.js → agents/cc-worker-pool.js} +16 -6
  116. package/engine/{claude-md-context.js → agents/claude-md-context.js} +5 -5
  117. package/engine/{harness-context.js → agents/harness-context.js} +5 -5
  118. package/engine/{harness.js → agents/harness.js} +3 -3
  119. package/engine/{llm.js → agents/llm.js} +18 -14
  120. package/engine/{model-discovery.js → agents/model-discovery.js} +2 -2
  121. package/engine/{playbook.js → agents/playbook.js} +155 -22
  122. package/engine/{pooled-agent-process.js → agents/pooled-agent-process.js} +14 -12
  123. package/engine/{preflight.js → agents/preflight.js} +29 -10
  124. package/engine/{spawn-agent.js → agents/spawn-agent.js} +25 -14
  125. package/engine/{spawn-phase-watchdog.js → agents/spawn-phase-watchdog.js} +16 -7
  126. package/engine/{steering.js → agents/steering.js} +5 -5
  127. package/engine/{tools-inventory.js → agents/tools-inventory.js} +2 -2
  128. package/engine/{agent-api-validation.js → api/agent-api-validation.js} +2 -2
  129. package/engine/{api-validation.js → api/api-validation.js} +1 -1
  130. package/engine/api/bridge.js +787 -0
  131. package/engine/{cc-api-validation.js → api/cc-api-validation.js} +1 -1
  132. package/engine/api/companion.js +560 -0
  133. package/engine/{content-api-validation.js → api/content-api-validation.js} +2 -2
  134. package/engine/{pr-issue-validation.js → api/pr-issue-validation.js} +33 -6
  135. package/engine/{settings-validation.js → api/settings-validation.js} +32 -4
  136. package/engine/api-contracts/agent-content.js +4 -4
  137. package/engine/api-contracts/capability-manifest.js +236 -0
  138. package/engine/api-contracts/capability-protocol.js +333 -0
  139. package/engine/api-contracts/cc-ops.js +1 -1
  140. package/engine/api-contracts/config-runtime.js +5 -0
  141. package/engine/api-contracts/core.js +28 -1
  142. package/engine/api-contracts/index.js +100 -0
  143. package/engine/api-contracts/orchestration.js +18 -5
  144. package/engine/api-contracts/pull-requests.js +37 -6
  145. package/engine/api-contracts/qa-process.js +29 -6
  146. package/engine/api-contracts/work-plan-prd.js +21 -1
  147. package/engine/cloud/contract.js +212 -0
  148. package/engine/cloud/index.js +159 -0
  149. package/engine/{execution-model.js → core/execution-model.js} +1 -1
  150. package/engine/{features.js → core/features.js} +4 -4
  151. package/engine/{operator-identity.js → core/operator-identity.js} +1 -1
  152. package/engine/{queries.js → core/queries.js} +201 -36
  153. package/engine/{safe-expr.js → core/safe-expr.js} +1 -1
  154. package/engine/{shared.js → core/shared.js} +1637 -175
  155. package/engine/{stdio-timestamps.js → core/stdio-timestamps.js} +1 -1
  156. package/engine/{untrusted-fence.js → core/untrusted-fence.js} +3 -3
  157. package/engine/db/index.js +11 -2
  158. package/engine/db/migrations/002-dispatches.js +3 -3
  159. package/engine/db/migrations/003-work-items.js +1 -1
  160. package/engine/db/migrations/004-pull-requests.js +1 -1
  161. package/engine/db/migrations/006-metrics.js +1 -1
  162. package/engine/db/migrations/007-watches.js +2 -2
  163. package/engine/db/migrations/008-small-state.js +1 -1
  164. package/engine/db/migrations/009-qa.js +1 -1
  165. package/engine/db/migrations/010-pr-links.js +1 -1
  166. package/engine/db/migrations/011-remaining-state.js +1 -1
  167. package/engine/db/migrations/012-steering-deliveries.js +2 -2
  168. package/engine/db/migrations/013-backfill-broken-note-links.js +1 -1
  169. package/engine/db/migrations/014-pr-fix-target-prefs.js +2 -2
  170. package/engine/db/migrations/015-plans-prds.js +0 -0
  171. package/engine/db/migrations/018-sql-only-cutover.js +2 -2
  172. package/engine/db/migrations/021-archived-work-items.js +1 -1
  173. package/engine/db/migrations/022-global-cc-session.js +1 -1
  174. package/engine/db/migrations/023-engine-state.js +1 -1
  175. package/engine/db/migrations/025-malformed-work-item-phantoms.js +1 -1
  176. package/engine/db/migrations/027-review-learning-lifecycle.js +1 -1
  177. package/engine/db/migrations/029-repair-reused-versions.js +20 -0
  178. package/engine/db/migrations/031-pr-author-identity.js +137 -0
  179. package/engine/{consolidation.js → memory/consolidation.js} +6 -6
  180. package/engine/{kb-sweep-runner.js → memory/kb-sweep-runner.js} +2 -2
  181. package/engine/{kb-sweep.js → memory/kb-sweep.js} +9 -7
  182. package/engine/{memory-retrieval.js → memory/memory-retrieval.js} +46 -4
  183. package/engine/{memory-store.js → memory/memory-store.js} +3 -3
  184. package/engine/{promotion.js → memory/promotion.js} +3 -3
  185. package/engine/{review-learning-backfill.js → memory/review-learning-backfill.js} +6 -6
  186. package/engine/{review-learning.js → memory/review-learning.js} +10 -5
  187. package/engine/{diagnostics-memory.js → observability/diagnostics-memory.js} +1 -1
  188. package/engine/{logs-store.js → observability/logs-store.js} +5 -5
  189. package/engine/{metrics-store.js → observability/metrics-store.js} +4 -4
  190. package/engine/{check-status.js → operations/check-status.js} +3 -3
  191. package/engine/{cli.js → operations/cli.js} +271 -113
  192. package/engine/{distribution.js → operations/distribution.js} +5 -6
  193. package/engine/{cleanup.js → orchestration/cleanup.js} +72 -45
  194. package/engine/{cooldown.js → orchestration/cooldown.js} +5 -5
  195. package/engine/{dispatch-events.js → orchestration/dispatch-events.js} +2 -2
  196. package/engine/{dispatch.js → orchestration/dispatch.js} +129 -36
  197. package/engine/orchestration/failed-scheduled-cleanup.js +274 -0
  198. package/engine/{lifecycle.js → orchestration/lifecycle.js} +198 -90
  199. package/engine/{meeting.js → orchestration/meeting.js} +6 -16
  200. package/engine/{pipeline.js → orchestration/pipeline.js} +12 -12
  201. package/engine/{pre-dispatch-eval.js → orchestration/pre-dispatch-eval.js} +10 -9
  202. package/engine/{routing.js → orchestration/routing.js} +3 -3
  203. package/engine/{schedule-bootstrap.js → orchestration/schedule-bootstrap.js} +4 -4
  204. package/engine/{scheduler.js → orchestration/scheduler.js} +38 -8
  205. package/engine/{timeout.js → orchestration/timeout.js} +158 -109
  206. package/engine/{db-events.js → persistence/db-events.js} +2 -2
  207. package/engine/{dispatch-store.js → persistence/dispatch-store.js} +7 -7
  208. package/engine/{inbox-store.js → persistence/inbox-store.js} +2 -2
  209. package/engine/{note-link-backfill.js → persistence/note-link-backfill.js} +4 -4
  210. package/engine/{pr-fix-target-store.js → persistence/pr-fix-target-store.js} +8 -8
  211. package/engine/{pull-requests-store.js → persistence/pull-requests-store.js} +21 -7
  212. package/engine/{small-state-store.js → persistence/small-state-store.js} +31 -31
  213. package/engine/persistence/state-operations.js +350 -0
  214. package/engine/{steering-store.js → persistence/steering-store.js} +6 -6
  215. package/engine/{issues.js → planning/issues.js} +2 -2
  216. package/engine/{plan-prd-validation.js → planning/plan-prd-validation.js} +8 -2
  217. package/engine/planning/prd-result-sidecar.js +190 -0
  218. package/engine/{prd-store.js → planning/prd-store.js} +17 -17
  219. package/engine/{project-discovery.js → planning/project-discovery.js} +5 -5
  220. package/engine/{projects.js → planning/projects.js} +10 -10
  221. package/engine/{resolve-area.js → planning/resolve-area.js} +1 -1
  222. package/engine/{work-item-validation.js → planning/work-item-validation.js} +39 -3
  223. package/engine/{work-items-store.js → planning/work-items-store.js} +29 -21
  224. package/engine/{keep-process-sweep.js → processes/keep-process-sweep.js} +57 -17
  225. package/engine/{managed-spawn-launcher.js → processes/managed-spawn-launcher.js} +3 -3
  226. package/engine/{managed-spawn.js → processes/managed-spawn.js} +97 -46
  227. package/engine/{process-utils.js → processes/process-utils.js} +599 -55
  228. package/engine/{abandoned-pr-reconciliation.js → providers/abandoned-pr-reconciliation.js} +17 -7
  229. package/engine/{comment-classifier.js → providers/comment-classifier.js} +85 -17
  230. package/engine/{comment-format.js → providers/comment-format.js} +5 -5
  231. package/engine/{gh-comment.js → providers/gh-comment.js} +15 -15
  232. package/engine/{gh-token.js → providers/gh-token.js} +4 -4
  233. package/engine/{github.js → providers/github.js} +131 -54
  234. package/engine/{pr-action.js → providers/pr-action.js} +13 -12
  235. package/engine/{pr-clone-keep.js → providers/pr-clone-keep.js} +7 -7
  236. package/engine/{pr-devbox.js → providers/pr-devbox.js} +6 -6
  237. package/engine/{pr-fix-target.js → providers/pr-fix-target.js} +13 -13
  238. package/engine/{pr-remote-patch.js → providers/pr-remote-patch.js} +4 -4
  239. package/engine/{pr-resolve.js → providers/pr-resolve.js} +7 -7
  240. package/engine/{pr-temp-clone.js → providers/pr-temp-clone.js} +5 -5
  241. package/engine/{pr-track.js → providers/pr-track.js} +11 -13
  242. package/engine/{shared-branch-pr-reconcile.js → providers/shared-branch-pr-reconcile.js} +4 -4
  243. package/engine/qa/auto-prd-qa.js +313 -0
  244. package/engine/{qa-from-prd.js → qa/from-prd.js} +42 -12
  245. package/engine/qa/prd-session.js +240 -0
  246. package/engine/{qa-process-validation.js → qa/process-validation.js} +14 -9
  247. package/engine/{qa-runbooks.js → qa/runbooks.js} +1 -1
  248. package/engine/{qa-runs.js → qa/runs.js} +286 -15
  249. package/engine/{qa-sessions.js → qa/sessions.js} +595 -49
  250. package/engine/qa/visual-journey.js +654 -0
  251. package/engine/{qa-runners.js → qa-runners/index.js} +7 -7
  252. package/engine/qa-runners/maestro.js +3 -3
  253. package/engine/qa-runners/playwright.js +2 -2
  254. package/engine/{restart-health.js → recovery/restart-health.js} +48 -4
  255. package/engine/recovery/stop-stack.js +607 -0
  256. package/engine/{supervisor.js → recovery/supervisor.js} +105 -175
  257. package/engine/{watchdog.js → recovery/watchdog.js} +136 -13
  258. package/engine/runtimes/claude.js +14 -12
  259. package/engine/runtimes/codex.js +8 -6
  260. package/engine/runtimes/copilot.js +17 -16
  261. package/engine/{watch-actions.js → watches/actions.js} +13 -13
  262. package/engine/{watches.js → watches/index.js} +43 -32
  263. package/engine/{watches-store.js → watches/store.js} +4 -4
  264. package/engine/{create-pr-worktree.js → worktrees/create-pr.js} +1 -1
  265. package/engine/{worktree-gc.js → worktrees/gc.js} +70 -22
  266. package/engine/worktrees/inventory.js +671 -0
  267. package/engine/{live-checkout.js → worktrees/live-checkout.js} +4 -4
  268. package/engine/{worktree-pool.js → worktrees/pool.js} +2 -2
  269. package/engine/{worktree-preflight.js → worktrees/preflight.js} +1 -0
  270. package/engine/worktrees/quarantine-refs.js +173 -0
  271. package/engine.js +1137 -208
  272. package/minions.js +147 -77
  273. package/package.json +10 -6
  274. package/playbooks/_pr-description-audit.md +110 -78
  275. package/playbooks/build-fix-complex.md +2 -0
  276. package/playbooks/fix.md +16 -12
  277. package/playbooks/implement-shared.md +2 -0
  278. package/playbooks/implement.md +19 -20
  279. package/playbooks/plan-to-prd.md +18 -3
  280. package/playbooks/qa-session-draft.md +136 -1
  281. package/playbooks/qa-session-execute.md +80 -2
  282. package/playbooks/qa-session-setup.md +17 -1
  283. package/playbooks/qa-validate.md +1 -1
  284. package/playbooks/setup.md +2 -0
  285. package/playbooks/shared-rules.md +25 -32
  286. package/playbooks/templates/followup-dispatch.md +4 -3
  287. package/playbooks/verify.md +1 -1
  288. package/prompts/cc-system.md +19 -27
  289. package/watch-plugins/README.md +92 -0
  290. package/watch-plugins/ado-author-prs.js +336 -0
  291. package/watch-plugins/gh-author-prs.js +375 -0
  292. package/watch-plugins/http.js +474 -0
  293. package/watch-plugins/teams-channel.js +869 -0
  294. package/docs/dev-composite-workflow.md +0 -101
  295. package/docs/pr-screenshots/pr-886/after-single-header.png +0 -0
  296. package/docs/pr-screenshots/pr-886/before-duplicate-header.png +0 -0
  297. package/docs/pr-screenshots/pr-895/01-cancellation-reason-detail.png +0 -0
  298. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
  299. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
  300. package/docs/pr-screenshots/pr-901/projects-tab-default.png +0 -0
  301. package/docs/pr-screenshots/pr-901/projects-tab-fmf-selected.png +0 -0
  302. package/docs/pr-screenshots/pr-916/model-picker-AFTER-crop.png +0 -0
  303. package/docs/pr-screenshots/pr-916/model-picker-AFTER.png +0 -0
  304. package/docs/pr-screenshots/pr-916/model-picker-BEFORE-crop.png +0 -0
  305. package/docs/pr-screenshots/pr-916/model-picker-BEFORE.png +0 -0
  306. package/docs/pr-screenshots/pr-916/model-picker-dropdown-AFTER.png +0 -0
  307. package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
  308. package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
  309. package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
  310. package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
  311. package/docs/visual-evidence-ci.md +0 -103
  312. package/engine/bridge.js +0 -379
  313. package/engine/quarantine-refs.js +0 -103
  314. package/engine/state-operations.js +0 -178
  315. /package/engine/{steering-constraints.js → agents/steering-constraints.js} +0 -0
@@ -18,12 +18,53 @@ How plans go from idea to verified, running code.
18
18
  → human archives the PRD/plan from the dashboard when ready
19
19
  ```
20
20
 
21
+ ## Plan-to-PRD result sidecar
22
+
23
+ The `plan-to-prd` agent does not write PRD state directly. It writes one
24
+ short-lived envelope to `agents/<agent-id>/prd-result.json`:
25
+
26
+ ```json
27
+ { "filename": "<engine-pinned>.json", "work_item": "W-…", "prd": { … } }
28
+ ```
29
+
30
+ The whole contract is owned by `engine/planning/prd-result-sidecar.js`, shared by
31
+ both import sites so their validation can never drift.
32
+
33
+ **Result identity is retry-stable.** The expected filename is generated once and
34
+ pinned on the work item as `_prdFilename` (with `_planKey`); every later dispatch
35
+ of the same work item for the same plan reuses it. It is deliberately *not*
36
+ re-derived per dispatch — for a cross-repo plan the slug is `cross-<uid>-<date>`,
37
+ so re-deriving it on retry silently orphaned the previous attempt's valid result
38
+ and forced a full regeneration (W-ms9tcry701xz0389).
39
+
40
+ **Import is fail-closed.** A sidecar is imported only when it *declares* the
41
+ expected filename (there is no permissive "assume it's ours" fallback), its
42
+ `prd.source_plan` matches the work item's plan, and — when present — its
43
+ `work_item` matches the dispatched work item. A rejected sidecar is left on disk
44
+ untouched, because it may belong to a sibling work item that has not retried yet.
45
+
46
+ **Consume is atomic and exactly-once.** The sidecar is claimed with an
47
+ intra-directory rename before the PRD is written and removed after a successful
48
+ import, so one sidecar can never materialize two PRDs and a consumed result can
49
+ never be inherited by a later unrelated dispatch from the same agent.
50
+
51
+ **Recovery beats re-running.** Because the identity is stable, a valid sidecar
52
+ from an interrupted attempt still matches. `engine.js#discoverCentralWorkItems`
53
+ imports it *before* resolving an agent and completes the work item, instead of
54
+ spending another full conversion run recreating identical content. When no result
55
+ can be imported, the `#893` gate bounces the item back to `pending` with a reason
56
+ that names the plan but never the failed attempt's filename — the reason is
57
+ replayed verbatim into the retry prompt, where only `{{prd_filename}}` is
58
+ authoritative.
59
+
21
60
  ## Dependency Management
22
61
 
23
62
  ### Dispatch Gating
24
63
 
25
64
  PRD items can declare `depends_on: ["P001", "P003"]`. The engine won't dispatch an item until all its dependencies have status `done`. This prevents agents from starting work before prerequisite code exists.
26
65
 
66
+ A dependency that ends `failed` does **not** cascade. The dependent stays `pending` with `_pendingReason: 'dependency_failed'` and is skipped for that tick — it is never marked `failed` and no retry is charged (W-mpbqhstz001lf518, closes #2627). Cascading used to burn the dependent's retry budget every time the dependency was auto-retried, and left the dependent stuck at `failed` even after the dependency eventually succeeded. Because the dependent stays pending, it dispatches on its own once the dependency reaches `done`.
67
+
27
68
  ### Branch Merging at Spawn
28
69
 
29
70
  When an agent is spawned for a work item with dependencies, the engine merges same-project dependency PR branches into the worktree **before the agent starts**. Cross-repo dependencies are exposed as advisory refs instead of being merged. This lets later items inspect prerequisite code that has not reached the target repo's main branch.
@@ -130,12 +171,37 @@ Manual archive behavior:
130
171
 
131
172
  Use unarchive only when the archived PRD/plan should reappear in active views. Reopening completed work should happen before archive, or after unarchive, so the materializer can see the active SQL PRD.
132
173
 
174
+ ## Revision vs Staleness (W-msa0mrus00mh466f)
175
+
176
+ Two different things can invalidate a PRD, and they must never offer competing recovery paths at the same time:
177
+
178
+ | Signal | Meaning | Recovery |
179
+ |--------|---------|----------|
180
+ | `planStale: true` | The source markdown changed under an existing PRD (engine sweep, content-hash gated) | Generic stale banner → `Regenerate PRD` / `Resume as-is` |
181
+ | `status: "revision-requested"` | A human asked for a revision; a `plan-to-prd` agent is rewriting this PRD | `Revision in progress` banner → no regenerate/resume |
182
+
183
+ `POST /api/plans/revise` persists `revisionWorkItemId` on the PRD and stamps the reverse `_revisionForPrd` link on the work item. That two-way relation — never title text — is the single correlation. `shared.findPrdRevisionWorkItem()` resolves it (pointer first, reverse stamp as fallback) and `shared.derivePrdRevisionState()` reduces it to one of:
184
+
185
+ - `in-progress` — the revision work item is `pending` / `dispatched` / `queued` / `paused`. The dashboard suppresses `Regenerate PRD`, `Resume as-is`, and the group Pause/Approve/Archive actions so nothing can race the agent. A second `POST /api/plans/revise` is rejected `409 { code: 'revision-in-progress', workItemId }` and enqueues nothing.
186
+ - `failed` — the work item is terminal (`failed`/`cancelled`), finished `done` without updating the PRD, or is missing entirely. The dashboard renders an explicit `Revision failed` banner with `Retry revision` (requeues the same work item via `POST /api/work-items/retry`). Staleness is never silently cleared, and a fresh revise request is accepted so the PRD is not stranded in `revision-requested`.
187
+ - `none` — no revision operation applies; the ordinary stale banner and its actions behave exactly as before.
188
+
189
+ On a successful revision import, `engine/planning/prd-result-sidecar.js#consumePrdResultSidecar` normalizes the agent's PRD through `shared.applyPrdRevisionCompletion()` and persists it with a **single** `prdStore.writePrd()`: status returns to `awaiting-approval`, `planStale` and the pre-revision approval fields are cleared, `_sourcePlanContentHash` / `sourcePlanModifiedAt` / `lastSyncedFromPlan` are re-baselined against the live markdown, and the revision pointers are dropped. Readers therefore never observe a completed revision paired with stale recovery controls. The rule lives beside the only write — the module that owns the sidecar import — so both callers apply it identically: the post-completion import in `engine/orchestration/lifecycle.js` and the pre-spawn sidecar recovery in `engine.js#discoverCentralWorkItems` each pass the work item's `_revisionForPrd` stamp, and a rescued revision therefore lands in exactly the same state as a completed one.
190
+
191
+ Derivation happens once, server-side: `engine/core/queries.js#getPrdInfo` publishes it as `progress.planRevisions[<prdFile>]` and `dashboard/js/render-prd.js` only paints the result.
192
+
193
+ ### The revision feedback reaches the agent explicitly
194
+
195
+ A revision run is an ordinary `plan-to-prd` dispatch whose only distinguishing input is the operator's feedback, so that text needs a real channel. It used to arrive by accident: the work item's `planFile` named the PRD `.json`, the engine's `PLANS_DIR/<planFile>` read failed, and the description (which embeds the feedback) leaked in through the `{{plan_content}}` fallback. Pointing `planFile` at the true source markdown closed that accident, and `playbooks/plan-to-prd.md` has no `{{task_description}}` token — so without a dedicated channel a revision silently degrades into a no-op regeneration that still reports success.
196
+
197
+ `engine.js` therefore resolves `shared.resolvePrdRevisionFeedback(item, resolvedPrd, prdFilename)` into `vars.revision_feedback`, and `plan-to-prd.md` renders it inside a `{{#revision_feedback}}…{{/revision_feedback}}` "Revision Requested" block. The feedback is read from the PRD (the single source of truth — `handlePlansRevise` writes it, `applyPrdRevisionCompletion` clears it), so a retry that reuses the same work item can never render superseded text. It is gated on the same structural `_revisionForPrd` ⇄ resolved-PRD-filename relation, so a plain regeneration cannot pick up feedback left behind by an earlier failed revision.
198
+
133
199
  ## Human Feedback on PRs
134
200
 
135
201
  After PRs are created, any new actionable human comment can trigger a fix task;
136
202
  an `@minions` mention is optional and is stripped before the feedback reaches
137
203
  the agent. Pollers filter bot, CI, preview, and Minions-authored comments. See
138
- `pollPrHumanComments()` in `engine/ado.js` and `engine/github.js`.
204
+ `pollPrHumanComments()` in `engine/ado/index.js` and `engine/providers/github.js`.
139
205
 
140
206
  ## Routing
141
207
 
@@ -161,5 +227,6 @@ the agent. Pollers filter bot, CI, preview, and Minions-authored comments. See
161
227
  | `playbooks/verify.md` | Verification task playbook |
162
228
  | `playbooks/implement.md` | Implementation playbook |
163
229
  | `playbooks/plan-to-prd.md` | Plan → PRD conversion playbook |
164
- | `engine/lifecycle.js` | `checkPlanCompletion`, completion hooks, PRD sync |
230
+ | `engine/planning/prd-result-sidecar.js` | One-shot PRD result envelope: validation + atomic exactly-once consume |
231
+ | `engine/orchestration/lifecycle.js` | `checkPlanCompletion`, completion hooks, PRD sync |
165
232
  | `engine.js` | `spawnAgent` (dependency merging), `resolveDependencyBranches` |
@@ -0,0 +1,114 @@
1
+ # PR author identity vs. Minions agent attribution
2
+
3
+ W-mscibegy005e8b74. A tracked pull request now carries **two distinct
4
+ identities** that used to be conflated in the single overloaded `pr.agent`
5
+ field:
6
+
7
+ | Field | Meaning | Used for |
8
+ |-------------|---------|----------|
9
+ | `pr.agent` | The **Minions agent** that created/owns the automated work. | Routing, self-review prevention, fix ownership, metrics, diagnostics. |
10
+ | `pr.author` | The **real individual** on whose behalf the PR exists, linked to an authoritative provider identity. | The Pull Requests table's Author column, author filter/search, and the PR detail panel. |
11
+
12
+ ## The bug this replaced
13
+
14
+ GitHub and ADO provider sync used to overwrite `pr.agent` with the platform
15
+ author login/display name whenever `agent === 'human'`
16
+ (`engine/providers/github.js`, `engine/ado/index.js`, and the dashboard link
17
+ enrichment). Meanwhile orchestration, routing, and metrics read `pr.agent` as
18
+ the **Minions** author-agent. A human-authored PR therefore encoded a human
19
+ login in a field that self-review prevention and routing treat as a Minions
20
+ agent id. `pr.agent` is now **never** a human login.
21
+
22
+ ## Identity conventions
23
+
24
+ 1. **`pr.agent`** stays the Minions agent id/name. The sentinel
25
+ `shared.PR_AGENT_HUMAN` (`'human'`) means "no Minions author-agent" (a human
26
+ or externally created PR). We keep the `'human'` sentinel rather than `null`
27
+ because `agent === 'human'` checks are embedded across routing and
28
+ lifecycle. Providers/dashboard never write a login here again.
29
+ 2. **`pr.author`** is a structured, provider-linked shape:
30
+
31
+ ```js
32
+ { provider, id, nodeId?, descriptor?, login?, displayName?, url? }
33
+ ```
34
+
35
+ - GitHub: built from the PR `user` object — `id`, `node_id`, `login`,
36
+ `name` (→ `displayName`), `html_url` (→ `url`).
37
+ - ADO: built from `createdBy` — `id` (GUID), `descriptor`, `uniqueName`
38
+ (→ `login`), `displayName`. ADO's `createdBy.url` is an API endpoint, not a
39
+ browsable profile, so no `url` is surfaced unless a real `http(s)` link is
40
+ present under `_links.web.href`.
41
+ 3. **Shared automation credential.** When the platform credential is a shared
42
+ bot but Minions has explicit structured provenance for the real human, both
43
+ the platform creator and the effective author are preserved rather than
44
+ claiming one is the other. Identity is **never** inferred from title /
45
+ description / free text — only from structured provider metadata or an
46
+ existing configured/operator identity seam.
47
+ 4. **Unknown / legacy records** render as **Unknown**, never silently borrowing
48
+ the agent name. Provider reconciliation enriches old rows naturally as they
49
+ are re-polled; migration 031 backfills what it safely can (below).
50
+ 5. **Human-created PRs** no longer encode their login in `agent`; `agent` is the
51
+ `'human'` sentinel and `author` carries the human identity.
52
+
53
+ ## Canonical helpers (`engine/core/shared.js`)
54
+
55
+ - `buildGithubAuthorIdentity(user)` / `buildAdoAuthorIdentity(createdBy)` —
56
+ build the structured shape from a provider response.
57
+ - `normalizePrAuthorIdentity(raw)` — coerce an arbitrary value (structured
58
+ object or bare login string) into the canonical shape or `null`. Every field
59
+ is length-capped; `url` accepts only `^https?://` values (dropped otherwise so
60
+ the dashboard never links an unvalidated href).
61
+ - `mergePrAuthorIdentity(existing, incoming)` — field-by-field merge; a
62
+ non-empty incoming value wins, otherwise the existing value is preserved, so a
63
+ **partial provider refresh never erases a richer author already stored**. A
64
+ provider change trusts the newer identity wholesale.
65
+ - `prAuthorLabel(author)` — human-readable label (displayName → login → id) for
66
+ the typed SQL column and filter/search text.
67
+
68
+ `upsertPullRequestRecord` normalizes `author` on the create path and merges it
69
+ via `mergePrAuthorIdentity` on the update path (and in `_mergeDuplicatePrInto`),
70
+ so all ingestion/reconciliation paths — GitHub, ADO, output-based discovery,
71
+ manual link, dashboard enrich, dedup — converge on one merge behavior.
72
+
73
+ ## Persistence — migration 031
74
+
75
+ `engine/db/migrations/031-pr-author-identity.js` adds a typed `author`
76
+ projection column (plus `idx_pr_author`). The canonical structured identity
77
+ always lives in the row's `data` JSON; the column is a label projection for
78
+ filters/sort. `engine/persistence/pull-requests-store.js` writes it via a local
79
+ `_authorLabel(pr.author)` (displayName → login → id).
80
+
81
+ Backfill is **fail-closed**. A legacy row whose `agent` is a human login (i.e.
82
+ not `'human'`, not a configured Minions agent id from `config.json`, not
83
+ `temp-*`, not `unknown`/`unassigned`) has that login moved into
84
+ `author = { login }` and `agent` reset to `'human'`. **If the configured
85
+ agent-id set cannot be resolved, the destructive agent→author move is skipped
86
+ entirely** so a real agent row (e.g. `dallas`) is never mistaken for a human
87
+ login. Rows that already carry a structured `data.author` only get the label
88
+ projected into the new column. Rows whose identity cannot be established are
89
+ left untouched (`author` NULL → renders Unknown).
90
+
91
+ ## Snapshot / bridge projection
92
+
93
+ `engine/api/bridge.js#_projectPullRequest` projects an allowlisted, length-capped
94
+ `author` object (`provider`, `id`, `descriptor`, `login`, `displayName`, `url`)
95
+ alongside the existing `agent` field.
96
+
97
+ ## Dashboard
98
+
99
+ `dashboard/js/render-prs.js` replaces the Pull Requests table's **Agent** column
100
+ with **Author** (display name + `@login`, safely escaped, linked to the provider
101
+ profile only when a validated `http(s)` URL is present). The Minions agent stays
102
+ visible in the PR detail panel under **Minions agent**, next to a new **Author**
103
+ line, so operators can still answer both "who is this for?" and "which agent
104
+ handled it?". The shared author filter (`dashboard/shared/pr-filters.js`) now
105
+ reads structured `pr.author` only and no longer falls back to `pr.agent`, so the
106
+ filter and the displayed column stay aligned (legacy rows group as Unknown).
107
+
108
+ ## Backward-compatible reads
109
+
110
+ `normalizePrAuthorIdentity` still accepts a bare author string and coerces it to
111
+ `{ login }`, so any legacy record or external caller that supplied a plain
112
+ author string keeps rendering. This is a tolerant read, not a retained
113
+ compatibility shim — the overloaded `agent = login` write behavior was removed
114
+ outright (no `docs/deprecated.json` entry required).
@@ -17,6 +17,15 @@ All dispatch decisions happen inside `engine.js#discoverFromPrs` once per discov
17
17
 
18
18
  \* `autoFixPaused` is the auto-**fix** kill-switch. It forces sites 2, 4, 5, and 6 off, but is **not** wired to suppress review dispatch (sites 1 and 3) — review verdicts stay fresh during a fix-storm incident. To halt review dispatch, clear `autoReviewPrs` / `autoReReviewPrs` directly, or set `pollingPaused: true` (which forces every per-PR auto-dispatch gate inert by zeroing `pollEnabled`).
19
19
 
20
+ ### Per-PR row controls
21
+
22
+ The Pull Requests table exposes adjacent **Auto Fix** and **Auto Review**
23
+ switches for each tracked PR. Auto Fix gates sites 2, 4, 5, and 6 in the table
24
+ above; Auto Review gates sites 1 and 3. Existing records inherit both switch
25
+ values from `contextOnly` until either switch is changed. The dashboard persists
26
+ the independent booleans through `POST /api/pull-requests/automation`; when both
27
+ are off, it also sets `contextOnly:true`.
28
+
20
29
  ### Provider polling gate
21
30
 
22
31
  Each PR carries a `repoHost` (`github` / `ado`). Before evaluating any of the six sites, `discoverFromPrs` resolves a `pollEnabled` boolean from that provider's poll gate:
@@ -41,7 +50,13 @@ There are two independent operator-facing master flags:
41
50
 
42
51
  These compose: setting `pollingPaused: true` plus `autoFixPaused: true` halts *all* automated PR work (review + fix + polling). Setting only `autoFixPaused: true` halts only auto-fix dispatch. Setting only `pollingPaused: true` halts polling *and reconciliation* (all provider network traffic) and effectively halts dispatch (because the per-PR gates rely on cached poll state), while still letting manual `/api/pull-requests/observe` or operator-driven dispatch proceed.
43
52
 
44
- Flip via Dashboard → Settings → Polling (`pollingPaused`) / Auto-fix & Review Loop (`autoFixPaused`), or set `engine.<flag>: true` in `config.json`.
53
+ Flip via Dashboard → Settings → Polling (`pollingPaused`) / Plan & PR Workflow → Monitor (`autoFixPaused`), or set `engine.<flag>: true` in `config.json`.
54
+
55
+ ### What `autoFixPaused` deliberately does NOT cover
56
+
57
+ `autoFixPaused` gates exactly the four auto-**fix dispatch** decisions above. It is not a general "stop writing to the platform" switch, because the table row's promise — operators keep seeing fresh verdicts *and build status* during a fix-storm — depends on polling-side work continuing.
58
+
59
+ The clearest example is `adoQueueDraftPrBuilds` (ADO draft-PR branch-policy build queueing, W-ms7kklp8002zad24). It runs inside `pollPrStatus`, POSTs a build queue request, and dispatches **no agent**, so it is gated by `pollingPaused` / `adoPollEnabled` / `adoPrStatusPollEnabled` and not by `autoFixPaused`. Its Settings control therefore lives in the **Polling** pane under "Poll-side actions", *not* in the Monitor per-cause tile grid whose copy promises agent dispatch and Pause-ALL inertness. Any future poll-side action that writes to a provider without dispatching an agent belongs in the same place — putting it in the auto-fix grid advertises a kill-switch that will not fire.
45
60
 
46
61
  ## Per-PR per-cause pause (different mechanism)
47
62
 
@@ -58,9 +73,9 @@ This per-PR per-cause pause is unrelated to `autoFixPaused`. Cause values are va
58
73
  | File | Role |
59
74
  |---------------------------|---------------------------------------------------------------------------------------------------|
60
75
  | `engine.js#discoverFromPrs` | Single dispatch site walker — evaluates the six causes per tick in fixed order |
61
- | `engine/shared.js` (`ENGINE_DEFAULTS`) | Default values for every per-cause gate + both kill-switches |
62
- | `engine/lifecycle.js#runPostCompletionHooks` | Wires `autoReReviewPrs` into the closure-loop re-review path (P-e8b1c4d2) |
63
- | `dashboard/js/settings.js` | Auto-fix & Review Loop pane (gates + `autoFixPaused`); Polling pane (`pollingPaused` + granular polls) |
76
+ | `engine/core/shared.js` (`ENGINE_DEFAULTS`) | Default values for every per-cause gate + both kill-switches |
77
+ | `engine/orchestration/lifecycle.js#runPostCompletionHooks` | Wires `autoReReviewPrs` into the closure-loop re-review path (P-e8b1c4d2) |
78
+ | `dashboard/js/settings.js` | Plan & PR Workflow → Monitor subsection (gates + `autoFixPaused`); Polling pane (`pollingPaused` + granular polls + poll-side actions like `adoQueueDraftPrBuilds`) |
64
79
  | `dashboard/js/render-dispatch.js` | `renderPausedBanner(engine)` — sticky cross-page banner showing both pause states |
65
80
  | `POST /api/pull-requests/clear-paused-cause` | Clears a per-PR per-cause pause record |
66
81
  | `POST /api/engine/polling/{pause,resume}`, `POST /api/engine/auto-fix/{pause,resume}` | Convenience endpoints for the two master flags (P-f3c9d0e7) |
@@ -5,7 +5,7 @@ proposal investigated in CC turn `cct-mpeira1y0001ddff`).
5
5
 
6
6
  ## Why
7
7
 
8
- `engine/{github,ado}.js` `pollPrHumanComments` routes every actionable human PR
8
+ `engine/providers/github.js` and `engine/ado/index.js` `pollPrHumanComments` route every actionable human PR
9
9
  comment into `pr.humanFeedback.pendingFix:true`, which `discover-from-prs`
10
10
  turns into a `fix` dispatch on the **same** branch. The `fix.md` playbook then
11
11
  forbids broadening the PR or creating a replacement branch. The net result was
@@ -28,7 +28,7 @@ playbook contract. The poller is unchanged; the carve-out is purely additive.
28
28
  1. **Human leaves a comment on PR #2400** asking for a related-but-distinct
29
29
  refactor: *"Can you also extract the markdown sanitizer into a shared module?
30
30
  Probably worth a separate PR though."*
31
- 2. **Engine pollers** (`engine/github.js` `pollPrHumanComments`) flag the
31
+ 2. **Engine pollers** (`engine/providers/github.js` `pollPrHumanComments`) flag the
32
32
  comment, set `pr.humanFeedback.pendingFix:true`, and `discover-from-prs`
33
33
  queues a `fix` dispatch against PR #2400's branch.
34
34
  3. **Fix agent (e.g. lambert)** starts working. After fetching the diff and
@@ -102,7 +102,7 @@ playbook contract. The poller is unchanged; the carve-out is purely additive.
102
102
  ]
103
103
  }
104
104
  ```
105
- `engine/lifecycle.js processCompletionFollowups` logs each entry at `info`
105
+ `engine/orchestration/lifecycle.js processCompletionFollowups` logs each entry at `info`
106
106
  and warns if the claimed `wi_id` is missing from any project's
107
107
  SQL work-item store (catches reverted or non-landing dispatches).
108
108
 
@@ -141,7 +141,7 @@ exists; you still reply on the comment thread with the returned id.
141
141
 
142
142
  ## What does NOT change
143
143
 
144
- - The pollers (`engine/github.js`, `engine/ado.js`) still set
144
+ - The pollers (`engine/providers/github.js`, `engine/ado/index.js`) still set
145
145
  `pr.humanFeedback.pendingFix:true` for every actionable human comment.
146
146
  - `discover-from-prs` still queues `fix` dispatches against the original
147
147
  branch; nothing prevents the in-place fix from proceeding.
@@ -179,7 +179,7 @@ exists; you still reply on the comment thread with the returned id.
179
179
  | `dashboard.js` (`validatePrFollowupShape`, `findExistingFollowupForComment`, `extractMinionsAgentHeader`, `extractMinionsOriginWiHeader`, `handleWorkItemsCreate`) | Shape validation, parent_comment_id dedup, header persistence, 200/400/409 responses. |
180
180
  | `dashboard/js/render-work-items.js` (`prFollowup` block in `wiRow`) | `↪ from PR #N` chip on follow-up WIs. |
181
181
  | `dashboard/js/render-prs.js` (`_countPrFollowups`) | `+N follow-ups` chip on PR rows. |
182
- | `engine/lifecycle.js` (`processCompletionFollowups`) | Parses the optional `followups` array on completion reports; warns on mismatch. |
182
+ | `engine/orchestration/lifecycle.js` (`processCompletionFollowups`) | Parses the optional `followups` array on completion reports; warns on mismatch. |
183
183
  | `docs/completion-reports.md` (`PR-comment follow-ups`) | Schema for the optional `followups` array. |
184
184
 
185
185
  ## Migration / rollout
@@ -200,7 +200,7 @@ To adopt:
200
200
 
201
201
  ## Related
202
202
 
203
- - `engine/github.js pollPrHumanComments` (human-comment routing — unchanged)
203
+ - `engine/providers/github.js pollPrHumanComments` (human-comment routing — unchanged)
204
204
  - `engine.js HUMAN_FEEDBACK fix dispatch` (in-place fix flow — unchanged)
205
205
  - `playbooks/templates/followup-dispatch.md` (the contract agents read)
206
206
  - `docs/completion-reports.md` `PR-comment follow-ups` section
@@ -37,12 +37,12 @@ A successful review dispatch that posts no `VERDICT:` comment and is not a recog
37
37
 
38
38
  When multiple problems coexist, earlier triggers get the first chance to enqueue work. The local `fixDispatched` flag is declared before the initial review trigger and set after first-review, human-feedback, review-feedback, and build-failure dispatches. Each later trigger checks `!fixDispatched`, so those sites suppress all subsequent review/fix work for that PR in the same discovery pass. Re-review is the exception: it checks `!fixDispatched` before enqueueing but does not set the flag afterward. Consequently, a re-review may enqueue alongside a later build-failure fix or, when no build fix is queued, a merge-conflict fix in the same pass.
39
39
 
40
- The engine does not cap review→fix cycles or build-fix attempts. Each trigger evaluates its own gates on every discovery pass; loops stop only when the underlying condition clears (reviewer approves, build passes, conflict resolves, human feedback handled). Operators who need to halt automation on a runaway PR have three escalating tools: (1) clear the relevant per-cause flag (`evalLoop`, `autoFixBuilds`, `autoFixConflicts`, `autoFixHumanComments`, `autoFixReviewFeedback`, `autoReviewPrs`, `autoReReviewPrs`); (2) flip the `autoFixPaused` master kill-switch to halt every auto-**fix** dispatch (sites B, C, D, and human-comment fix) at once while leaving review verdicts and polling fresh — Dashboard → Settings → Auto-fix & Review Loop or `POST /api/engine/auto-fix/pause`; (3) flip `pollingPaused` to halt PR polling entirely, which also forces every per-PR auto-dispatch gate inert. Per-PR per-cause noop pauses (`_noOpFixes[cause].paused`) are an orthogonal recovery surface — see §4E and `POST /api/pull-requests/clear-paused-cause`. See [docs/pr-auto-fix-dispatch.md](pr-auto-fix-dispatch.md) for the full site × gate × kill-switch reference table.
40
+ The engine does not cap review→fix cycles or build-fix attempts. Each trigger evaluates its own gates on every discovery pass; loops stop only when the underlying condition clears (reviewer approves, build passes, conflict resolves, human feedback handled). Operators who need to halt automation on a runaway PR have three escalating tools: (1) clear the relevant per-cause flag (`evalLoop`, `autoFixBuilds`, `autoFixConflicts`, `autoFixHumanComments`, `autoFixReviewFeedback`, `autoReviewPrs`, `autoReReviewPrs`); (2) flip the `autoFixPaused` master kill-switch to halt every auto-**fix** dispatch (sites B, C, D, and human-comment fix) at once while leaving review verdicts and polling fresh — Dashboard → Settings → Plan & PR Workflow → Monitor or `POST /api/engine/auto-fix/pause`; (3) flip `pollingPaused` to halt PR polling entirely, which also forces every per-PR auto-dispatch gate inert. Per-PR per-cause noop pauses (`_noOpFixes[cause].paused`) are an orthogonal recovery surface — see §4E and `POST /api/pull-requests/clear-paused-cause`. See [docs/pr-auto-fix-dispatch.md](pr-auto-fix-dispatch.md) for the full site × gate × kill-switch reference table.
41
41
 
42
42
  ### A. Human comments (`humanFeedback.pendingFix`)
43
43
 
44
44
  - Gate: `pendingFix || hasCoalescedFeedback` + not already dispatched/on cooldown
45
- - Agent comments filtered out via `_isNonActionableComment()` (`engine/github.js`, `engine/ado.js`) — composes preview/bot body checks and the structural Minions-author check from `engine/comment-classifier.js`. The structural check matches the `<!-- minions:agent=<id> kind=<kind> -->` HTML marker on the body's first line (`hasMinionsMarker`); on GitHub it must combine with `viewerDidAuthor === true` (anti-spoof), on ADO with a `config.engine.minionsAdoIdentities` author match. Marker presence alone never classifies a comment as agent-authored on either platform.
45
+ - Agent comments filtered out via `_isNonActionableComment()` (`engine/providers/github.js`, `engine/ado/index.js`) — composes preview/bot body checks and the structural Minions-author check from `engine/providers/comment-classifier.js`. Both pollers resolve marker authorship through the shared `classifyMarkerAuthorship()` seam, which combines the `<!-- minions:agent=<id> kind=<kind> -->` HTML marker on the body's first line (`hasMinionsMarker`) with a host identity signal: `viewerDidAuthor` on GitHub, a `config.engine.minionsAdoIdentities` author match on ADO. A marker plus an identity **mismatch** is never agent-authored (spoof guard). A marker with **no usable identity signal** — an unconfigured `minionsAdoIdentities`, the default — fails *safe* and is treated as agent-authored, and ADO logs a one-time warning that the spoof guard is inactive (W-msbf1kf6033f46cb). The previous fail-open reading gave the marker zero filtering power on ADO, so every agent fix-summary re-triggered a fix dispatch on the same PR.
46
46
  - Coalesces multiple comments arriving during cooldown into single fix
47
47
  - Routes to author
48
48
  - Fix agents must treat human comments as claims to verify, not commands. They inspect or reproduce each claimed issue, make the smallest correct fix only when the claim is valid, and otherwise reply with evidence-backed rationale.
@@ -82,11 +82,11 @@ A paused cause **suppresses further auto-dispatch for that cause** until cleared
82
82
  2. **Click the red chip on the dashboard** — confirms, then `POST /api/pull-requests/clear-paused-cause` (`dashboard.js`, issue #2969) validates the cause against `shared.PR_FIX_CAUSE`, locates the PR via `mutatePullRequests` across project/central files, and calls `clearPrNoOpFixAttempt` to wipe the cause record.
83
83
  3. **Direct API call** — `POST /api/pull-requests/clear-paused-cause` with `{ prId, cause }`.
84
84
 
85
- The exported `recordPrNoOpFixAttempt` / `clearPrNoOpFixAttempt` / `sweepStalePrNoOpFixes` helpers in `engine/lifecycle.js` are the only sanctioned entry points. The dispatch evaluator gates re-dispatch on `shared.isPrNoOpFixCausePaused(pr, cause)` (which re-validates the fingerprint per call), the dashboard chip surface uses `shared.getPrPausedCauses(pr)` (same gate), and the `pollPrStatus` callback in `engine/ado.js` + `engine/github.js` calls `sweepStalePrNoOpFixes(pr)` each tick to GC records whose fingerprint no longer matches the live PR.
85
+ The exported `recordPrNoOpFixAttempt` / `clearPrNoOpFixAttempt` / `sweepStalePrNoOpFixes` helpers in `engine/orchestration/lifecycle.js` are the only sanctioned entry points. The dispatch evaluator gates re-dispatch on `shared.isPrNoOpFixCausePaused(pr, cause)` (which re-validates the fingerprint per call), the dashboard chip surface uses `shared.getPrPausedCauses(pr)` (same gate), and the `pollPrStatus` callback in `engine/ado/index.js` + `engine/providers/github.js` calls `sweepStalePrNoOpFixes(pr)` each tick to GC records whose fingerprint no longer matches the live PR.
86
86
 
87
87
  #### E2. Build-fix-ineffective tracker (`pr._buildFixIneffective`, issue #639)
88
88
 
89
- A "branch unchanged" no-op report for a `BUILD_FAILURE`-cause fix used to be conflated with the generic `_noOpFixes[BUILD_FAILURE]` counter even when the build was still genuinely red on the unchanged head — silently masking a stuck failure as "handled". `checkBuildStillFailingLive()` re-polls host-specific live CI (`checkLiveBuildAndConflict` in `engine/github.js` / `engine/ado.js`) right after `detectPrFixBranchChange()` reports no branch change for a build-failure fix. If the build is still failing, `recordBuildFixIneffective()` tracks a **separate**, `headSha`-keyed counter (`pr._buildFixIneffective`) instead of bumping `_noOpFixes[BUILD_FAILURE]`, pausing after `prNoOpFixPauseAttempts` on the same head and writing a distinct `build-fix-ineffective-<pr>` inbox alert; `shared.isBuildFixIneffectivePaused()` re-validates the stored `headSha` against the live PR before honoring the pause (same fingerprint-revalidation pattern as `isPrNoOpFixCausePaused`), and `engine.js` wires `isBuildFixIneffectiveSuppressed()` into the `BUILD_FAILURE` dispatch gate alongside the existing no-op check. A real branch-changing fix clears the stale record. The dashboard PR list (`dashboard/js/render-prs.js`) renders a separate, non-interactive "infra issue suspected" chip for a paused `_buildFixIneffective` record (tooltip shows `reason`/`count`/`headSha`/`liveBuildStatus`) alongside the existing `_pausedCauses` chips.
89
+ A "branch unchanged" no-op report for a `BUILD_FAILURE`-cause fix used to be conflated with the generic `_noOpFixes[BUILD_FAILURE]` counter even when the build was still genuinely red on the unchanged head — silently masking a stuck failure as "handled". `checkBuildStillFailingLive()` re-polls host-specific live CI (`checkLiveBuildAndConflict` in `engine/providers/github.js` / `engine/ado/index.js`) right after `detectPrFixBranchChange()` reports no branch change for a build-failure fix. If the build is still failing, `recordBuildFixIneffective()` tracks a **separate**, `headSha`-keyed counter (`pr._buildFixIneffective`) instead of bumping `_noOpFixes[BUILD_FAILURE]`, pausing after `prNoOpFixPauseAttempts` on the same head and writing a distinct `build-fix-ineffective-<pr>` inbox alert; `shared.isBuildFixIneffectivePaused()` re-validates the stored `headSha` against the live PR before honoring the pause (same fingerprint-revalidation pattern as `isPrNoOpFixCausePaused`), and `engine.js` wires `isBuildFixIneffectiveSuppressed()` into the `BUILD_FAILURE` dispatch gate alongside the existing no-op check. A real branch-changing fix clears the stale record. The dashboard PR list (`dashboard/js/render-prs.js`) renders a separate, non-interactive "infra issue suspected" chip for a paused `_buildFixIneffective` record (tooltip shows `reason`/`count`/`headSha`/`liveBuildStatus`) alongside the existing `_pausedCauses` chips.
90
90
 
91
91
  ## 5. Fix completes
92
92
 
@@ -128,12 +128,12 @@ A "branch unchanged" no-op report for a `BUILD_FAILURE`-cause fix used to be con
128
128
  | File | Functions |
129
129
  |---|---|
130
130
  | `engine.js` | `discoverFromPrs()` — discovery + dispatch logic |
131
- | `engine/lifecycle.js` | `syncPrsFromOutput()`, `updatePrAfterReview()`, `updatePrAfterFix()` |
132
- | `engine/github.js` | `pollPrStatus()`, `pollPrHumanComments()`, `fetchGhBuildErrorLog()` |
133
- | `engine/ado.js` | `pollPrStatus()`, `pollPrHumanComments()`, `fetchAdoBuildErrorLog()` |
134
- | `engine/comment-classifier.js` | Host-agnostic body-only predicates (`isPreviewStatusBody`, `hasMinionsMarker`, `hasVerdictPrefix`) shared by GH + ADO pollers (extracted in F2+F3+F4) |
135
- | `engine/dispatch.js` | `addToDispatch()` — dedup by work item ID and dispatchKey |
136
- | `engine/cooldown.js` | `isBranchActive()`, cooldown management |
131
+ | `engine/orchestration/lifecycle.js` | `syncPrsFromOutput()`, `updatePrAfterReview()`, `updatePrAfterFix()` |
132
+ | `engine/providers/github.js` | `pollPrStatus()`, `pollPrHumanComments()`, `fetchGhBuildErrorLog()` |
133
+ | `engine/ado/index.js` | `pollPrStatus()`, `pollPrHumanComments()`, `fetchAdoBuildErrorLog()` |
134
+ | `engine/providers/comment-classifier.js` | Host-agnostic body-only predicates (`isPreviewStatusBody`, `hasMinionsMarker`, `hasVerdictPrefix`) plus the shared marker+identity seam (`classifyMarkerAuthorship`, `isMarkerAuthored`, `MARKER_AUTHORSHIP`) used by GH + ADO pollers (extracted in F2+F3+F4; seam added in W-msbf1kf6033f46cb) |
135
+ | `engine/orchestration/dispatch.js` | `addToDispatch()` — dedup by work item ID and dispatchKey |
136
+ | `engine/orchestration/cooldown.js` | `isBranchActive()`, cooldown management |
137
137
  | `playbooks/review.md` | Reviewer playbook |
138
138
  | `playbooks/fix.md` | Fix agent playbook |
139
139
 
@@ -154,6 +154,54 @@ A "branch unchanged" no-op report for a `BUILD_FAILURE`-cause fix used to be con
154
154
  | `humanFeedback` | `pollPrHumanComments()` | `{ pendingFix, feedbackContent, lastProcessedCommentDate }` |
155
155
  | `_noOpFixes[cause]` | `recordPrNoOpFixAttempt()` | Per-cause record `{ count, paused, fingerprint, beforeHead, afterHead }` driving the issue #2969 pause loop |
156
156
  | `_pausedCauses` | `queries.getPullRequests()` enrichment | Read-only list of currently-paused cause keys for UI rendering (computed from `_noOpFixes`) |
157
+ | `_abandonedAt` | Poller (on every entry into `abandoned`) | Anchor for the bounded abandoned re-probe window (below). Cleared on reopen |
158
+ | `_reconciliation404Confirmed` | Poller + startup reconciliation | Host confirmed the PR is genuinely deleted — closes the re-probe window early |
159
+
160
+ ## Abandoned is not a one-way door (W-msav5edk00g81710)
161
+
162
+ `abandoned` is the only terminal PR status a record can legitimately **leave**:
163
+ GitHub reopens a closed PR and ADO reactivates an abandoned one. Both pollers
164
+ already implement the reopen transition (`oldStatus === abandoned && newStatus
165
+ === active` → `P-f5reopenreset` clears the stale review session), but before this
166
+ WI that code was unreachable: `forEachActiveGhPr` / `forEachActivePr` filtered on
167
+ `PR_POLLABLE_STATUSES = {active, linked}`, so an abandoned record left the poll
168
+ loop forever and the only un-flip path was the **boot-only, version-gated**
169
+ `reconcileAbandonedPrs`. Incident: `github:opg-microsoft/minions#1154` was closed
170
+ at 16:07:07Z, reopened at 16:10:07Z, and sat `abandoned` for hours while OPEN and
171
+ approved — invisible to `discoverFromPrs`, misread by `syncPrdFromPrs`, and one
172
+ "remove merged + abandoned PRs" sweep away from a permanent `userDeleted`
173
+ tombstone.
174
+
175
+ **The contract now:**
176
+
177
+ - `shared.isPrPollable(pr, { windowMs, now })` is the single pollability
178
+ predicate. Both providers filter through it — never re-check
179
+ `PR_POLLABLE_STATUSES` directly, or the two pollers drift again.
180
+ - An `abandoned` record stays pollable for `engine.abandonedPrRepollWindowMs`
181
+ (`ENGINE_DEFAULTS`, default **7 days**; Settings → *Abandoned PR Re-probe
182
+ Window*; `0` disables) measured from `_abandonedAt`, falling back to `closedAt`
183
+ for records stamped before this WI.
184
+ - **Opt-in per call site.** Only `pollPrStatus` passes the window. The comment
185
+ poller never widens — the reopen signal lives on the status endpoint, so
186
+ widening it would burn quota for no signal.
187
+ - **Cost controls preserved.** The window is aged (not "re-probe every terminal
188
+ record forever"); a record with no anchor fails **CLOSED** and is never
189
+ re-probed; and a host-confirmed deletion sets `_reconciliation404Confirmed`,
190
+ which drops the record out of the window immediately. On GitHub that marker is
191
+ now reachable from the normal poll too, after the same
192
+ `prAbandonConfirmCount` consecutive 404s that the abandon trapdoor requires.
193
+ - `merged` remains genuinely irreversible and is never re-probed.
194
+ - Records older than the window are healed only by the version-gated startup pass
195
+ (`ENGINE_DEFAULTS.abandonedReconciliationVersion`), which is why this WI bumped
196
+ it — that boot pass is the one-off repair for pre-existing mis-stamps.
197
+
198
+ **Implication for the merged/abandoned tracker sweep:** a `status: abandoned`
199
+ stamp is a *sampled observation*, not proof of terminality, so verify at the host
200
+ before tombstoning. `POST /api/pull-requests/delete` sets a permanent
201
+ `userDeleted` marker that pollers must never clear, so a mis-stamp pruned by the
202
+ sweep is unrecoverable. The bounded window shrinks that hazard structurally: a
203
+ mis-stamp now self-heals within one poll cycle instead of persisting until the
204
+ next engine boot.
157
205
 
158
206
  ## Platform differences
159
207
 
@@ -171,4 +219,5 @@ A "branch unchanged" no-op report for a `BUILD_FAILURE`-cause fix used to be con
171
219
  - Builds use the merge commit hash as `sourceVersion`, not the source branch commit — compare against `lastMergeCommit.commitId`
172
220
  - `partiallySucceeded` counts as passing (warnings, not failures)
173
221
  - A stale but passing build is still valid — don't re-trigger builds that already passed
222
+ - `refs/pull/<n>/merge` accumulates builds for EVERY push to the PR (the poller reads `$top=25`), so when no build targets the current merge commit the stale set spans the PR's whole history. `classifyStaleBuilds()` therefore groups by `sourceVersion` via `selectNewestMergeCommitBuilds()` and classifies only the **most recently built merge commit** — OR-ing failures across the full history pinned a PR to `failing` forever once any build had ever failed, burning a build-fix dispatch per poll on an already-green branch (W-msaux21y00g79c48, ADO PR 5531150). When that newest generation is green and the cached status is `failing`, the cached failure is provably older (the current merge commit has no build at all), so it is dropped to `none` — `supersededCachedFailure: true`, which `checkLiveBuildAndConflict()` also surfaces so the pre-dispatch gate skips the fix. Never `passing`: the current merge commit is still unbuilt. Draft PRs hit this path most, since ADO doesn't auto-run branch policies on drafts
174
223
  - ADO never auto-clears a reviewer's numeric vote when new commits land, so a hard-reject `-10` vote stays live forever unless the reviewer explicitly re-votes. Both `pollPrStatus()` and the pre-dispatch `checkLiveReviewStatus()` treat a `-10` as **stale/superseded** (resolving to `waiting` instead of `changes-requested`) via `isStaleAdoRejectVote()` once a fix has completed after the vote's last review (`fixedAt > lastReviewedAt`) with no push since (`lastPushedAt <= fixedAt`) — otherwise the auto re-review-after-fix flow can never fire for that PR (issue #633).
@@ -0,0 +1,40 @@
1
+ # Process termination
2
+
3
+ All Minions-initiated process termination goes through
4
+ `engine/processes/process-utils.js#terminateProcess`. The exported
5
+ `PROCESS_TERMINATION_SITES` object is the authoritative inventory: every entry
6
+ declares its termination mode, ownership proof, and purpose. Unknown sites fail
7
+ closed.
8
+
9
+ Print the current inventory:
10
+
11
+ ```powershell
12
+ node -e "console.table(Object.entries(require('./engine/processes/process-utils').PROCESS_TERMINATION_SITES).map(([site, policy]) => ({ site, ...policy })))"
13
+ ```
14
+
15
+ `test/unit/process-termination-gateway.test.js` enforces that:
16
+
17
+ - every production and test-harness `terminateProcess()` call uses a registered site;
18
+ - every non-compatibility inventory entry has a caller;
19
+ - raw process signals, child/facade `.kill()`, and platform kill commands occur
20
+ only inside `engine/processes/process-utils.js`;
21
+ - production code cannot use the legacy compatibility wrappers or assert
22
+ `ownershipVerified`; the gateway re-checks script identity, PID birth time,
23
+ declaration time, tracked-agent command line, or a live child handle itself.
24
+
25
+ Signal-0 liveness probes (`process.kill(pid, 0)`) do not terminate a process and
26
+ are explicitly exempt. The PowerShell/bash snippets emitted in the managed-spawn
27
+ smoke-test instructions clean up a process launched by that same interactive
28
+ snippet; they are guidance text, not engine-executed termination.
29
+
30
+ The supervisor's dashboard replacement site requires consecutive ticks where
31
+ neither the port probe nor `/api/health` proves responsiveness. A single loaded-
32
+ host probe miss cannot reach `engine.supervisor.daemon-replace`.
33
+
34
+ Multi-file unit, integration, and perf runners also fail before spawning workers
35
+ when Windows PID 4 is already above the system-handle safety limit, and stop
36
+ scheduling after an OS resource-exhaustion result. This prevents a damaged host
37
+ from turning one infrastructure failure into process churn and daemon restarts.
38
+ The engine also stamps conservative test concurrency into every cold, pooled,
39
+ and steering-resumed agent environment, so stale PR branches are bounded before
40
+ their repository-local runner code loads.
@@ -50,13 +50,13 @@ additive.
50
50
  (source: `config.json` → projects[] `src`).
51
51
  - **Mode resolution.** `shared.resolveCheckoutMode(project, workItemType)`
52
52
  returns `'live'|'worktree'`, honoring the legacy `worktreeMode` field and the
53
- `liveValidation` hybrid routing (source: `engine/shared.js:2911`).
53
+ `liveValidation` hybrid routing (source: `engine/core/shared.js:2911`).
54
54
  `shared.isLiveCheckoutProject(project)` is the convenience predicate
55
- (source: `engine/shared.js:2943`).
55
+ (source: `engine/core/shared.js:2943`).
56
56
  - **Path resolution.** `shared.resolveSpawnPaths(project, type, minionsDir, opts)`
57
57
  short-circuits for live mode and returns
58
58
  `{ cwd: path.resolve(project.localPath) (+workdir), worktreeRootDir:null, liveMode:true }`
59
- (source: `engine/shared.js:6355-6382`). It throws `LIVE_CHECKOUT_NO_LOCALPATH`
59
+ (source: `engine/core/shared.js:6355-6382`). It throws `LIVE_CHECKOUT_NO_LOCALPATH`
60
60
  if `localPath` is falsy. **This is the single point where the enlistment path
61
61
  is chosen** — it hard-reads `project.localPath`.
62
62
  - **Per-project mutating-concurrency cap of 1.** The allocation loop in the
@@ -69,25 +69,25 @@ additive.
69
69
  it is derived from `dispatch.active`, which is SQL-backed** (see persistence
70
70
  below). The pool needs this to become a **per-path** claim, not per-project.
71
71
  - **In-place checkout + recovery policies.** `spawnAgent` (engine.js) calls
72
- `engine/live-checkout.js#prepareLiveCheckout({ localPath: cwd, branchName, … })`
72
+ `engine/worktrees/live-checkout.js#prepareLiveCheckout({ localPath: cwd, branchName, … })`
73
73
  which: runs `git status --porcelain` (dirty gate), mid-operation / detached-HEAD
74
74
  preflight, stale-base guard, and branch checkout/creation — all against the one
75
- `cwd` (source: `engine.js:2453-2497`, `engine/live-checkout.js:374`). The dirty
75
+ `cwd` (source: `engine.js:2453-2497`, `engine/worktrees/live-checkout.js:374`). The dirty
76
76
  recovery policies resolve per-project-then-fleet-wide:
77
- - `liveCheckoutAutoStash` — `resolveLiveCheckoutAutoStash` (source: `engine/live-checkout.js:1248`), applied via `applyLiveCheckoutAutoStash` (source: `engine.js:2509`).
78
- - `liveCheckoutAutoReset` — `shared.resolveLiveCheckoutAutoReset` (self-resolves by matching project on `localPath`, source: `docs/live-checkout-mode.md` §2b, `engine/live-checkout.js:332`).
79
- - `liveCheckoutAutoBaseRepair` — `_defaultResolveAutoBaseRepair` (source: `engine/live-checkout.js:352`).
77
+ - `liveCheckoutAutoStash` — `resolveLiveCheckoutAutoStash` (source: `engine/worktrees/live-checkout.js:1248`), applied via `applyLiveCheckoutAutoStash` (source: `engine.js:2509`).
78
+ - `liveCheckoutAutoReset` — `shared.resolveLiveCheckoutAutoReset` (self-resolves by matching project on `localPath`, source: `docs/live-checkout-mode.md` §2b, `engine/worktrees/live-checkout.js:332`).
79
+ - `liveCheckoutAutoBaseRepair` — `_defaultResolveAutoBaseRepair` (source: `engine/worktrees/live-checkout.js:352`).
80
80
  **All three resolve/match by `localPath`**, so they need to key off the
81
81
  selected enlistment path, not the project's canonical `localPath`.
82
82
  - **Dispatch-end restore.** `restoreLiveCheckoutAtDispatchEnd` /
83
83
  `maybeRestoreLiveCheckoutFromRecord` put the tree back on `originalRef`
84
- (source: `engine/live-checkout.js:1009, 1423`). These operate on a recorded
84
+ (source: `engine/worktrees/live-checkout.js:1009, 1423`). These operate on a recorded
85
85
  `localPath` and must record/restore the **selected** enlistment.
86
86
 
87
87
  ### Persistence (survives restart)
88
88
 
89
89
  - Dispatch state lives in **SQLite** `engine/state.db` `dispatches` table via
90
- `engine/dispatch-store.js` (`readDispatchSectioned` / `applyDispatchMutation`).
90
+ `engine/persistence/dispatch-store.js` (`readDispatchSectioned` / `applyDispatchMutation`).
91
91
  Each active dispatch is one JSON record. **Because `liveProjectsInUse` is
92
92
  rebuilt from `dispatch.active` every tick, recording the claimed enlistment
93
93
  path on the active dispatch record makes the per-path claim automatically
@@ -99,7 +99,7 @@ additive.
99
99
 
100
100
  - `meta.workdir` is already a validated relative-POSIX subpath under the spawn
101
101
  base; when set, the engine lands the agent at `<base>/<workdir>` and clips
102
- harness discovery to that subtree (source: `engine/shared.js:6404-6416`,
102
+ harness discovery to that subtree (source: `engine/core/shared.js:6404-6416`,
103
103
  `validateWorkItemWorkdir`). This is the natural signal for "which subpath does
104
104
  this WI need" — subproject validation can reuse/extend it. There is also
105
105
  `detectDominantSubproject({ projectPath, changedPaths })` used post-completion
@@ -118,7 +118,7 @@ additive.
118
118
  - **Project cards.** `dashboard/js/render-other.js` renders per-project git
119
119
  branch/dirty/state pills from `p.gitBranch/p.gitDirty/p.gitState`
120
120
  (source: `dashboard/js/render-other.js:103-172`), fed by the per-`localPath`
121
- git-status probe cache in `engine/queries.js:2512-2607` (15s TTL, keyed by
121
+ git-status probe cache in `engine/core/queries.js:2512-2607` (15s TTL, keyed by
122
122
  normalized `localPath`). Per-enlistment status reuses this probe per path.
123
123
  - **Status payload.** `dashboard.js:2768-2785` emits `checkoutMode`,
124
124
  `liveValidationType`, etc. per project — the place to add pool-member status.
@@ -172,7 +172,7 @@ Key design decisions and rationale:
172
172
  dispatch record before the agent spawns, closing the double-claim window.
173
173
 
174
174
  4. **Clean-tree + subpath validation at selection are cheap.** Clean-tree reuses
175
- the existing 15s-TTL per-path git-status probe cache (`engine/queries.js`), so
175
+ the existing 15s-TTL per-path git-status probe cache (`engine/core/queries.js`), so
176
176
  selection does not add a fresh `git status` per tick per path. Subpath
177
177
  validation is a pure `fs.existsSync(<path>/<subpath>)` (or a `sparseScope`
178
178
  prefix match when declared) — no build, no git. Rationale: selection runs every