@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
@@ -1,26 +1,26 @@
1
1
  /**
2
- * engine/lifecycle.js — Post-completion hooks, PR sync, agent history/metrics, plan chaining.
2
+ * engine/orchestration/lifecycle.js — Post-completion hooks, PR sync, agent history/metrics, plan chaining.
3
3
  * Extracted from engine.js.
4
4
  */
5
5
 
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
  const os = require('os');
9
- const shared = require('./shared');
9
+ const shared = require('../core/shared');
10
10
  const { safeRead, safeJson, safeJsonArr, safeJsonNoRestore, safeWrite, mutateJsonFileLocked, mutateWorkItems, execAsync, getPrLinks,
11
11
  log, ts, dateStamp, WI_STATUS, DONE_STATUSES, PLAN_TERMINAL_STATUSES, WORK_TYPE, PLAN_STATUS, PRD_ITEM_STATUS, PRD_MATERIALIZABLE, PR_STATUS, REVIEW_STATUS, RETRY_DELAY_MS, DISPATCH_RESULT,
12
12
  ENGINE_DEFAULTS, DEFAULT_AGENT_METRICS, FAILURE_CLASS } = shared;
13
- const { resolveRuntime, hasUnfinishedToolCall } = require('./runtimes');
14
- const adoGitAuth = require('./ado-git-auth');
15
- const queries = require('./queries');
16
- const harness = require('./harness');
17
- const reviewLearning = require('./review-learning');
18
- const smallStateStore = require('./small-state-store');
19
- const prdStore = require('./prd-store');
20
- const memoryStore = require('./memory-store');
21
- const promotion = require('./promotion');
13
+ const { resolveRuntime, hasUnfinishedToolCall } = require('../runtimes');
14
+ const adoGitAuth = require('../ado/git-auth');
15
+ const queries = require('../core/queries');
16
+ const harness = require('../agents/harness');
17
+ const reviewLearning = require('../memory/review-learning');
18
+ const smallStateStore = require('../persistence/small-state-store');
19
+ const prdStore = require('../planning/prd-store');
20
+ const memoryStore = require('../memory/memory-store');
21
+ const promotion = require('../memory/promotion');
22
22
  const { isBranchActive } = require('./cooldown');
23
- const { resolveExecutionModel } = require('./execution-model');
23
+ const { resolveExecutionModel } = require('../core/execution-model');
24
24
  const { worktreeMatchesBranch, getWorktreeBranch, cleanupMergedPrLocalBranch } = require('./cleanup');
25
25
  const { getConfig, getInboxFiles, getNotes, getPrs, getDispatch,
26
26
  MINIONS_DIR, ENGINE_DIR, PLANS_DIR, INBOX_DIR, AGENTS_DIR } = queries;
@@ -356,7 +356,7 @@ function checkPlanCompletion(meta, config) {
356
356
  // a crafted `plan.feature_branch` (e.g. `main; rm -rf ~`) or PR
357
357
  // `branch` field reaches the Bash tool via template literal
358
358
  // interpolation. github.js validates pr.branch on API persistence
359
- // (engine/github.js:612-622), but feature_branch has no upstream
359
+ // (engine/providers/github.js:612-622), but feature_branch has no upstream
360
360
  // validator and mainBranch can fall through unvalidated from project
361
361
  // config when `git rev-parse` can't verify it (shared.js:1328). Defense
362
362
  // in depth lives here, at the actual interpolation site.
@@ -728,6 +728,8 @@ function updateWorkItemStatus(meta, status, reason) {
728
728
  delete target.failReason;
729
729
  delete target.failedAt;
730
730
  delete target._retryCount;
731
+ delete target._infrastructureRetryCount;
732
+ delete target._lastInfrastructureFailureClass;
731
733
  delete target._retriesByAgent;
732
734
  target.completedAgents = Object.entries(target.agentResults)
733
735
  .filter(([, r]) => r.status === WI_STATUS.DONE)
@@ -745,6 +747,8 @@ function updateWorkItemStatus(meta, status, reason) {
745
747
  delete target.failReason;
746
748
  delete target.failedAt;
747
749
  delete target._retryCount;
750
+ delete target._infrastructureRetryCount;
751
+ delete target._lastInfrastructureFailureClass;
748
752
  delete target._retriesByAgent;
749
753
  // P-e0b4f7a5 — successful completion (including a phantom-retry
750
754
  // succeeding) clears the phantom markers so cleanup can reap the
@@ -1195,12 +1199,12 @@ function syncPrsFromOutput(output, agentId, meta, config, opts = {}) {
1195
1199
  log('warn', `Duplicate PR detected: ${fullId} on branch ${entry.branch || entryBranch} — already tracked as ${duplicateOnBranch.id}. Skipping.`);
1196
1200
  // Best-effort close the duplicate on GitHub (non-blocking, fire-and-forget).
1197
1201
  // The closing comment is wrapped with the minions marker so the PR-comment
1198
- // classifier (engine/github.js _isMinionsAuthoredComment, sub-task -b)
1202
+ // classifier (engine/providers/github.js _isMinionsAuthoredComment, sub-task -b)
1199
1203
  // recognizes it as engine-authored and never queues a fix-dispatch on it.
1200
1204
  try {
1201
1205
  const ghSlug = outputText.match(/github\.com\/([^/]+\/[^/]+)/)?.[1];
1202
1206
  if (ghSlug) {
1203
- const { buildMinionsCommentBody } = require('./gh-comment');
1207
+ const { buildMinionsCommentBody } = require('../providers/gh-comment');
1204
1208
  const dupComment = buildMinionsCommentBody({
1205
1209
  agentId: 'engine',
1206
1210
  kind: 'positive-signal',
@@ -1261,7 +1265,7 @@ async function _fetchGitHubPrStateForEnrollment(slug, prNumber) {
1261
1265
  }
1262
1266
  let token = null;
1263
1267
  try {
1264
- const ghToken = require('./gh-token');
1268
+ const ghToken = require('../providers/gh-token');
1265
1269
  if (typeof ghToken.resolveTokenForSlug === 'function') {
1266
1270
  token = ghToken.resolveTokenForSlug(slug);
1267
1271
  }
@@ -1291,7 +1295,7 @@ function _liveGitHubStateToPrStatus(liveState) {
1291
1295
 
1292
1296
  function _existingPrRecordForCanonicalId(canonicalPrId) {
1293
1297
  try {
1294
- const store = require('./pull-requests-store');
1298
+ const store = require('../persistence/pull-requests-store');
1295
1299
  if (typeof store.readAllPullRequests !== 'function') return null;
1296
1300
  const all = store.readAllPullRequests() || [];
1297
1301
  return all.find(pr => pr && pr.id === canonicalPrId) || null;
@@ -1398,7 +1402,7 @@ async function enrollPrFromCanonicalId(canonicalPrId, project, opts = {}) {
1398
1402
  try {
1399
1403
  const adoParts = scope.slice('ado:'.length).split('/');
1400
1404
  if (adoParts.length === 3) {
1401
- const ado = require('./ado');
1405
+ const ado = require('../ado');
1402
1406
  if (typeof ado.fetchAdoPrMetadata === 'function') {
1403
1407
  const meta = await ado.fetchAdoPrMetadata(prNumber, adoParts[0], adoParts[1], adoParts[2], project);
1404
1408
  if (meta) {
@@ -1501,7 +1505,7 @@ function stampWiPrRef(meta, config) {
1501
1505
  const projects = (config && Array.isArray(config.projects))
1502
1506
  ? config.projects : shared.getProjects(config || {});
1503
1507
 
1504
- const prStore = require('./pull-requests-store');
1508
+ const prStore = require('../persistence/pull-requests-store');
1505
1509
  const allPrs = prStore.readAllPullRequests();
1506
1510
  let found = allPrs.find(r => Array.isArray(r.prdItems) && r.prdItems.includes(itemId)) || null;
1507
1511
 
@@ -1646,10 +1650,12 @@ function runFileCapture(file, args, opts = {}) {
1646
1650
  const timer = timeout ? setTimeout(() => {
1647
1651
  timedOut = true;
1648
1652
  if (!child) return;
1649
- shared.killGracefully(child, 1000);
1653
+ shared.terminateProcess('engine.lifecycle.hook-timeout-graceful', child, {
1654
+ graceMs: 1000,
1655
+ });
1650
1656
  // Hard-kill fallback if the child ignores SIGTERM. Cleared on close.
1651
1657
  hardKillTimer = setTimeout(() => {
1652
- try { shared.killImmediate(child); } catch {}
1658
+ try { shared.terminateProcess('engine.lifecycle.hook-timeout-force', child); } catch {}
1653
1659
  }, 2500);
1654
1660
  }, timeout) : null;
1655
1661
  try {
@@ -1662,14 +1668,20 @@ function runFileCapture(file, args, opts = {}) {
1662
1668
  child.stderr?.setEncoding('utf8');
1663
1669
  child.stdout?.on('data', chunk => {
1664
1670
  if (stdout.length + chunk.length > MAX_BUFFER) {
1665
- if (!killedForBuffer) { killedForBuffer = true; try { shared.killImmediate(child); } catch {} }
1671
+ if (!killedForBuffer) {
1672
+ killedForBuffer = true;
1673
+ try { shared.terminateProcess('engine.lifecycle.hook-buffer-limit', child); } catch {}
1674
+ }
1666
1675
  return;
1667
1676
  }
1668
1677
  stdout += chunk;
1669
1678
  });
1670
1679
  child.stderr?.on('data', chunk => {
1671
1680
  if (stderr.length + chunk.length > MAX_BUFFER) {
1672
- if (!killedForBuffer) { killedForBuffer = true; try { shared.killImmediate(child); } catch {} }
1681
+ if (!killedForBuffer) {
1682
+ killedForBuffer = true;
1683
+ try { shared.terminateProcess('engine.lifecycle.hook-buffer-limit', child); } catch {}
1684
+ }
1673
1685
  return;
1674
1686
  }
1675
1687
  stderr += chunk;
@@ -1747,7 +1759,7 @@ async function findOpenPrForBranch(meta, config) {
1747
1759
  return null;
1748
1760
  }
1749
1761
  if (host === 'ado') {
1750
- const found = await require('./ado').findOpenPrOnBranch(projectObj, meta.branch);
1762
+ const found = await require('../ado').findOpenPrOnBranch(projectObj, meta.branch);
1751
1763
  return found ? { project: projectObj, prNumber: found.prNumber, url: found.url } : null;
1752
1764
  }
1753
1765
  log('debug', `Skipping branch PR lookup for unsupported repo host "${host}" on ${projectObj.name}`);
@@ -2368,8 +2380,8 @@ async function updatePrAfterReview(agentId, pr, project, config, resultSummary,
2368
2380
  : null;
2369
2381
  const hostForChecks = hostFromCanonical || projectObjForChecks?.repoHost || null;
2370
2382
  let checkFn = null;
2371
- if (hostForChecks === 'github') checkFn = require('./github').checkLiveReviewStatus;
2372
- else if (hostForChecks === 'ado') checkFn = require('./ado').checkLiveReviewStatus;
2383
+ if (hostForChecks === 'github') checkFn = require('../providers/github').checkLiveReviewStatus;
2384
+ else if (hostForChecks === 'ado') checkFn = require('../ado').checkLiveReviewStatus;
2373
2385
  else log('warn', `Cannot resolve repoHost for review of ${reviewPr.id} (unparseable canonical id and no project config); skipping live review check.`);
2374
2386
  try {
2375
2387
  if (projectObjForChecks && checkFn) {
@@ -2410,9 +2422,9 @@ async function updatePrAfterReview(agentId, pr, project, config, resultSummary,
2410
2422
  if (canDismissOwnPriorNegative) {
2411
2423
  try {
2412
2424
  const reconcileFn = hostForChecks === 'github'
2413
- ? require('./github').dismissPriorViewerChangesRequestedReviews
2425
+ ? require('../providers/github').dismissPriorViewerChangesRequestedReviews
2414
2426
  : hostForChecks === 'ado'
2415
- ? require('./ado').resetReviewerNegativeVote
2427
+ ? require('../ado').resetReviewerNegativeVote
2416
2428
  : null;
2417
2429
  if (typeof reconcileFn === 'function') {
2418
2430
  const result = await reconcileFn(reviewPr, projectObjForChecks);
@@ -2584,15 +2596,19 @@ async function updatePrAfterReview(agentId, pr, project, config, resultSummary,
2584
2596
 
2585
2597
  log('info', `Updated ${reviewPr.id} → minions review: ${postReviewStatus || REVIEW_STATUS.WAITING} by ${reviewerName}`);
2586
2598
  if (updatedTarget) {
2587
- createReviewFeedbackForAuthor(agentId, updatedTarget, config, {
2588
- reviewContent: reviewNote,
2589
- project: reviewProject,
2590
- dispatchId: dispatchItem?.id || structuredCompletion?.dispatchId || null,
2591
- sourceItem: dispatchItem?.meta?.item?.id || null,
2592
- postReviewStatus,
2593
- reviewVerdict: verdictRaw,
2594
- findingIdentity: shared.prReviewFindingIdentity(updatedTarget),
2595
- });
2599
+ try {
2600
+ createReviewFeedbackForAuthor(agentId, updatedTarget, config, {
2601
+ reviewContent: reviewNote,
2602
+ project: reviewProject,
2603
+ dispatchId: dispatchItem?.id || structuredCompletion?.dispatchId || null,
2604
+ sourceItem: dispatchItem?.meta?.item?.id || null,
2605
+ postReviewStatus,
2606
+ reviewVerdict: verdictRaw,
2607
+ findingIdentity: shared.prReviewFindingIdentity(updatedTarget),
2608
+ });
2609
+ } catch (err) {
2610
+ log('warn', `Review-learning candidate capture failed for ${reviewPr.id}: ${err.message}`);
2611
+ }
2596
2612
  }
2597
2613
  }
2598
2614
 
@@ -2715,13 +2731,13 @@ async function detectPrFixBranchChange(meta, config) {
2715
2731
  }
2716
2732
 
2717
2733
  // #639 — lazy requires for the host-specific live build/conflict checkers.
2718
- // engine/github.js and engine/ado.js both lazy-require lifecycle.js (see
2734
+ // engine/providers/github.js and engine/ado/index.js both lazy-require lifecycle.js (see
2719
2735
  // their `lifecycleModule()` helpers), so a top-level require here would form
2720
2736
  // an import cycle. Deferring to first call avoids that.
2721
2737
  let _githubPrModule = null;
2722
- function githubPrModule() { if (!_githubPrModule) _githubPrModule = require('./github'); return _githubPrModule; }
2738
+ function githubPrModule() { if (!_githubPrModule) _githubPrModule = require('../providers/github'); return _githubPrModule; }
2723
2739
  let _adoPrModule = null;
2724
- function adoPrModule() { if (!_adoPrModule) _adoPrModule = require('./ado'); return _adoPrModule; }
2740
+ function adoPrModule() { if (!_adoPrModule) _adoPrModule = require('../ado'); return _adoPrModule; }
2725
2741
 
2726
2742
  // #639 — No-op fix detection previously only proved whether the agent pushed
2727
2743
  // a NEW commit; it never checked whether the build the fix was dispatched
@@ -3113,7 +3129,7 @@ function clearPrNoOpFixAttempt(target, cause) {
3113
3129
  }
3114
3130
 
3115
3131
  // W-mpx44p05000ze8d8 — Proactive GC for stale `_noOpFixes` records. Called
3116
- // from `engine/ado.js` + `engine/github.js` `pollPrStatus` after the live PR
3132
+ // from `engine/ado/index.js` + `engine/providers/github.js` `pollPrStatus` after the live PR
3117
3133
  // fields (headSha, buildStatus, humanFeedback, mergeStatus, reviewStatus,
3118
3134
  // lastPushedAt, etc.) have been refreshed for the current tick. Any
3119
3135
  // `_noOpFixes[cause]` whose stored `evidenceFingerprint` no longer matches
@@ -3604,7 +3620,7 @@ async function handlePostMerge(pr, project, config, newStatus) {
3604
3620
 
3605
3621
  // `project.localPath` guard: central/observed PRs (e.g. ADO PRs tracked in
3606
3622
  // central PRs with no configured project are polled via a
3607
- // *synthesized* project that has no localPath (engine/ado.js + engine/github.js
3623
+ // *synthesized* project that has no localPath (engine/ado/index.js + engine/providers/github.js
3608
3624
  // central-poll loops). There is no checkout to clean for those, and an
3609
3625
  // unguarded path.resolve(undefined) here threw "paths[0] argument must be of
3610
3626
  // type string" — caught upstream and logged as "failed to poll central PR".
@@ -3876,7 +3892,7 @@ function extractSkillsFromOutput(output, agentId, dispatchItem, config, runtimeN
3876
3892
  const ccContent = `---\nname: ${name}\ndescription: ${description}\n---\n\n${marker}\n\n${body.trim()}\n`;
3877
3893
  if (!fs.existsSync(skillDir)) fs.mkdirSync(skillDir, { recursive: true });
3878
3894
  shared.safeWrite(skillPath, ccContent);
3879
- try { require('./queries').invalidateSkillsCache(); } catch {}
3895
+ try { require('../core/queries').invalidateSkillsCache(); } catch {}
3880
3896
  log('info', `Extracted skill "${name}" from ${agentName} → ${skillPath}`);
3881
3897
 
3882
3898
  }
@@ -5194,7 +5210,7 @@ function detectNonTerminalResultSummary(_resultSummary, structuredCompletion, co
5194
5210
  // the PR-attachment contract hard-fails them with no retry.
5195
5211
  //
5196
5212
  // Only enabled at call sites where there is no other success signal (e.g.
5197
- // engine/timeout.js completeFromOutput). runPostCompletionHooks has richer
5213
+ // engine/orchestration/timeout.js completeFromOutput). runPostCompletionHooks has richer
5198
5214
  // signals (PRs created, plan-to-prd PRD file existence, autoRecover) and
5199
5215
  // must not be demoted by this heuristic.
5200
5216
  if (opts && opts.detectPhantom) {
@@ -5950,7 +5966,7 @@ function dispatchReReviewForFix(fixDispatchItem, meta, config, opts = {}) {
5950
5966
  }
5951
5967
 
5952
5968
  const routing = require('./routing');
5953
- const playbook = require('./playbook');
5969
+ const playbook = require('../agents/playbook');
5954
5970
  const dispatchModule = require('./dispatch');
5955
5971
 
5956
5972
  const reviewAuthor = livePr.agent || pr.agent || null;
@@ -6153,7 +6169,7 @@ function captureTaskMemoryEpisode(dispatchItem, agentId, outcome, structuredComp
6153
6169
  ].join('\n');
6154
6170
 
6155
6171
  try {
6156
- return require('./memory-store').upsertMemoryRecord({
6172
+ return require('../memory/memory-store').upsertMemoryRecord({
6157
6173
  memoryType: 'episodic',
6158
6174
  scopeType: project ? 'project' : (agentId ? 'agent' : 'global'),
6159
6175
  scopeKey: project || agentId || '',
@@ -6520,24 +6536,28 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
6520
6536
  let prdFound = false;
6521
6537
  let resolvedPrdFilename = null;
6522
6538
  const expectedFile = meta.item._prdFilename;
6523
- const prdStore = require('./prd-store');
6539
+ const prdStore = require('../planning/prd-store');
6540
+ const prdResultSidecar = require('../planning/prd-result-sidecar');
6524
6541
  const agentId = meta._agentId || meta.item.dispatched_to;
6525
- const resultPath = agentId ? path.join(AGENTS_DIR, agentId, 'prd-result.json') : null;
6526
- if (expectedFile && resultPath && fs.existsSync(resultPath)) {
6527
- try {
6528
- const envelope = shared.safeJsonNoRestore(resultPath);
6529
- const filename = envelope?.filename || expectedFile;
6530
- const prd = envelope?.prd || envelope;
6531
- if (filename !== expectedFile) {
6532
- throw new Error(`sidecar filename "${filename}" does not match expected "${expectedFile}"`);
6533
- }
6534
- if (!prd || !shared.prdMatchesSourcePlan(prd.source_plan, meta.item.planFile)) {
6535
- throw new Error(`sidecar source_plan "${prd?.source_plan ?? '(unknown)'}" does not match "${meta.item.planFile}"`);
6536
- }
6537
- prdStore.writePrd(expectedFile, prd);
6538
- fs.unlinkSync(resultPath);
6539
- } catch (e) {
6540
- log('warn', `plan-to-prd ${meta.item.id}: could not import result sidecar: ${e.message}`);
6542
+ if (expectedFile && agentId) {
6543
+ // W-msa0mrus00mh466f — a revision import must land the PRD back in a
6544
+ // coherent state in the SAME write that imports it: status returns to
6545
+ // awaiting-approval, staleness clears, the source-plan sync fields are
6546
+ // re-baselined and the revision pointers are dropped. The relation is
6547
+ // handed to the sidecar module (which owns the single writePrd) rather
6548
+ // than re-written here, so no reader can observe a completed revision
6549
+ // still paired with the stale recovery controls.
6550
+ const outcome = prdResultSidecar.consumePrdResultSidecar({
6551
+ agentsDir: AGENTS_DIR,
6552
+ agentId,
6553
+ expectedFilename: expectedFile,
6554
+ planFile: meta.item.planFile,
6555
+ workItemId: meta.item.id,
6556
+ revisionForPrd: meta.item._revisionForPrd,
6557
+ plansDir: PLANS_DIR,
6558
+ });
6559
+ if (!outcome.imported && outcome.code !== prdResultSidecar.PRD_RESULT_OUTCOME.ABSENT) {
6560
+ log('warn', `plan-to-prd ${meta.item.id}: could not import result sidecar: ${outcome.reason}`);
6541
6561
  }
6542
6562
  }
6543
6563
  for (const { filename, archived, plan } of prdStore.listPrdRows()) {
@@ -6549,8 +6569,27 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
6549
6569
  }
6550
6570
  }
6551
6571
  if (!prdFound && meta.item.planFile) {
6572
+ // Fallback scan. The canonical hit is a live PRD that declares this plan
6573
+ // markdown as its `source_plan`.
6574
+ //
6575
+ // The second clause is a legacy carve-out: a work item whose `planFile`
6576
+ // names a PRD file rather than the source plan markdown (the shape
6577
+ // /api/plans/revise produced before W-msa1y79a01271b91) can NEVER match by
6578
+ // source_plan — prdMatchesSourcePlan compares basenames and a `.json` key
6579
+ // never equals a `.md` one — so the gate would fail every attempt until
6580
+ // maxRetries. Treating a planFile that directly names a live PRD as a hit
6581
+ // lets such an item converge onto the PRD it actually pointed at, and the
6582
+ // migration block below repoints `_prdFilename` at it.
6583
+ //
6584
+ // This deliberately does NOT relax the sidecar-import `source_plan` guard
6585
+ // above: that guard is what stopped a mis-addressed sidecar from being
6586
+ // written under a freshly-minted filename and duplicating the PRD.
6587
+ const planFileKey = shared.sourcePlanKey(meta.item.planFile);
6588
+ const planFileNamesPrd = planFileKey.toLowerCase().endsWith('.json');
6552
6589
  for (const { filename, archived, plan } of prdStore.listPrdRows()) {
6553
- if (!archived && shared.prdMatchesSourcePlan(plan.source_plan, meta.item.planFile)) {
6590
+ if (archived) continue;
6591
+ if (shared.prdMatchesSourcePlan(plan.source_plan, meta.item.planFile)
6592
+ || (planFileNamesPrd && filename === planFileKey)) {
6554
6593
  prdFound = true;
6555
6594
  resolvedPrdFilename = filename;
6556
6595
  break;
@@ -6594,7 +6633,12 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
6594
6633
  // buildWorkItemDispatchVars()'s retry_context var (W-mrbq53ra000lf2e5) so the
6595
6634
  // retried agent knows the PRD file was never found instead of silently
6596
6635
  // repeating the same mistake.
6597
- const gateReason = `Validation gate (#893): no PRD result sidecar was imported for planFile "${meta.item.planFile || w.planFile || '(unknown)'}" (expected filename "${expectedFile || '(unset)'}") after the previous attempt completed. Write agents/<agent-id>/prd-result.json exactly as specified in the playbook before finishing.`;
6636
+ // W-ms9tcry701xz0389 — the reason deliberately carries NO filename.
6637
+ // It is replayed verbatim into the retry prompt, and a filename
6638
+ // captured here is a snapshot of the FAILED attempt; quoting it can
6639
+ // only compete with the authoritative `{{prd_filename}}` the next
6640
+ // dispatch renders. Name the plan and the contract, not the value.
6641
+ const gateReason = `Validation gate (#893): no PRD result sidecar was imported for planFile "${meta.item.planFile || w.planFile || '(unknown)'}" after the previous attempt completed. Write agents/<agent-id>/prd-result.json exactly as the playbook's Output section specifies — including the "filename" the engine pins for this dispatch — before finishing.`;
6598
6642
  if (retries < ENGINE_DEFAULTS.maxRetries) {
6599
6643
  w.status = WI_STATUS.PENDING;
6600
6644
  w._retryCount = retries + 1;
@@ -6846,7 +6890,7 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
6846
6890
  // id lives at `dispatchItem.meta.item.meta.qaRunId` in production, NOT
6847
6891
  // at `dispatchItem.meta.qaRunId`. Accept both locations to mirror the
6848
6892
  // keep_processes / managed_spawn skip-worktree-removal pattern below
6849
- // (engine/lifecycle.js, "_wiMetaForSkip" block) — that way fast-path
6893
+ // (engine/orchestration/lifecycle.js, "_wiMetaForSkip" block) — that way fast-path
6850
6894
  // dispatchers that synthesize meta.qaRunId at the top level keep
6851
6895
  // working too. The agent writes agents/<id>/qa-run-result.json before
6852
6896
  // exit. qa-runs consumes the file exactly once and verifies its runId
@@ -6858,12 +6902,12 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
6858
6902
  && completionContractFailure?.nonTerminal !== true
6859
6903
  && Number(meta?.item?._retryCount || 0) < ENGINE_DEFAULTS.maxRetries;
6860
6904
  if (qaRunId && lifecycleGateCanRetry) {
6861
- try { require('./qa-runs').prepareResultSidecar(agentId || '_unknown', qaRunId); }
6905
+ try { require('../qa/runs').prepareResultSidecar(agentId || '_unknown', qaRunId); }
6862
6906
  catch (e) { log('warn', `qa-validate deferred sidecar cleanup for ${qaRunId}: ${e.message}`); }
6863
6907
  log('warn', `qa-validate run ${qaRunId} remains pending while completion validation retries`);
6864
6908
  } else if (qaRunId) {
6865
6909
  try {
6866
- const qaRuns = require('./qa-runs');
6910
+ const qaRuns = require('../qa/runs');
6867
6911
  const consumed = qaRuns.consumeResultSidecar(agentId || '_unknown', qaRunId);
6868
6912
  if (consumed.cleanupError) {
6869
6913
  log('warn', `qa-validate sidecar cleanup for ${qaRunId}: ${consumed.cleanupError}`);
@@ -6879,6 +6923,10 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
6879
6923
  status: parsed.status,
6880
6924
  summary: typeof parsed.summary === 'string' ? parsed.summary : '',
6881
6925
  artifacts: Array.isArray(parsed.artifacts) ? parsed.artifacts : [],
6926
+ // W-msb9kgs402813a97-c — per-journey outcomes drive the EXECUTE
6927
+ // evidence gate. Forwarded only when the agent declared them, so
6928
+ // legacy qa-validate sidecars keep their historical record shape.
6929
+ journeyCoverage: parsed.journeyCoverage,
6882
6930
  });
6883
6931
  log('info', `qa-validate run ${qaRunId} → ${parsed.status} (${(parsed.artifacts || []).length} artifacts)`);
6884
6932
  } else {
@@ -6910,7 +6958,7 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
6910
6958
 
6911
6959
  // P-a3f7c1b2 — QA Session lifecycle hook. The SETUP / DRAFT / EXECUTE work
6912
6960
  // items carry `meta.sessionId` and `meta.sessionPhase` (set by the
6913
- // qa-sessions chain helpers — see engine/qa-sessions.js). On each agent
6961
+ // qa-sessions chain helpers — see engine/qa/sessions.js). On each agent
6914
6962
  // exit we delegate to the matching handler, which validates the state
6915
6963
  // transition and queues the next phase WI when applicable. The qa-validate
6916
6964
  // qaRunId hook above (line 4340) already handles the qa-runs sidecar for
@@ -6925,7 +6973,7 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
6925
6973
  const qaSessionPhase = meta?.sessionPhase || meta?.item?.meta?.sessionPhase;
6926
6974
  if (qaSessionId && qaSessionPhase && !lifecycleGateCanRetry) {
6927
6975
  try {
6928
- const qaSessions = require('./qa-sessions');
6976
+ const qaSessions = require('../qa/sessions');
6929
6977
  const scope = resolveWorkItemScope(meta);
6930
6978
  const project = meta?.project?.name || meta?.item?.project || null;
6931
6979
  const failureReason = typeof resultSummary === 'string' && resultSummary
@@ -6944,14 +6992,31 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
6944
6992
  }
6945
6993
  } else if (qaSessionPhase === 'draft') {
6946
6994
  // The DRAFT agent reports the relative test file path in its
6947
- // structured completion as `testFile` (or the playbook can write a
6948
- // qa-session-draft-result.json sidecar in a future iteration). For
6949
- // now read it from structuredCompletion when present; the session
6950
- // record still works without it (EXECUTE prompt falls back to the
6951
- // generic test.<ext> hint).
6995
+ // structured completion as `testFile`; the session record still works
6996
+ // without it (EXECUTE prompt falls back to the generic test.<ext>
6997
+ // hint).
6952
6998
  const testFile = structuredCompletion && typeof structuredCompletion.testFile === 'string'
6953
6999
  ? structuredCompletion.testFile
6954
7000
  : null;
7001
+ // W-msb9kgs402813a97-b — the DRAFT visual-journey manifest rides its
7002
+ // own single-use sidecar (agents/<id>/qa-session-draft-result.json),
7003
+ // consumed here exactly once. Consumed on EVERY draft completion, not
7004
+ // just successful ones, so a failed attempt cannot leave a manifest
7005
+ // behind for the next dispatch of the same session to inherit.
7006
+ const visualJourney = require('../qa/visual-journey');
7007
+ let draftManifest = null;
7008
+ try {
7009
+ const consumed = visualJourney.consumeDraftResultSidecar(agentId || '_unknown', qaSessionId);
7010
+ if (consumed.cleanupError) {
7011
+ log('warn', `qa-session draft sidecar cleanup for ${qaSessionId}: ${consumed.cleanupError}`);
7012
+ }
7013
+ if (consumed.ok) draftManifest = consumed.manifest;
7014
+ else if (consumed.reason !== 'missing') {
7015
+ log('warn', `qa-session ${qaSessionId} draft sidecar ${consumed.reason} at ${consumed.filePath}`);
7016
+ }
7017
+ } catch (sidecarErr) {
7018
+ log('warn', `qa-session draft sidecar read for ${qaSessionId}: ${sidecarErr.message}`);
7019
+ }
6955
7020
  const session = qaSessions.getSession(qaSessionId);
6956
7021
  if (session && session.spec && session.spec.mode === 'auto' && effectiveSuccess) {
6957
7022
  // Auto-mode chains EXECUTE; we need a qa-runs record up-front. The
@@ -6960,9 +7025,9 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
6960
7025
  // the qa-runs record is created here on the auto path.
6961
7026
  let qaRunId = null;
6962
7027
  try {
6963
- const qaRuns = require('./qa-runs');
7028
+ const qaRuns = require('../qa/runs');
6964
7029
  const run = qaRuns.createRun({
6965
- runbookId: 'qa-session-' + qaSessionId,
7030
+ runbookId: qaSessions.sessionOwnedRunbookId(qaSessionId),
6966
7031
  targetName: session.managedSpawnName,
6967
7032
  project: project || session.spec.project || null,
6968
7033
  });
@@ -6970,31 +7035,55 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
6970
7035
  } catch (createErr) {
6971
7036
  log('warn', `qa-session auto-mode createRun failed for ${qaSessionId}: ${createErr.message}`);
6972
7037
  }
6973
- qaSessions.handleDraftComplete(qaSessionId, {
7038
+ const draftOutcome = qaSessions.handleDraftComplete(qaSessionId, {
6974
7039
  success: true,
6975
7040
  testFile,
7041
+ manifest: draftManifest,
6976
7042
  scope,
6977
7043
  project,
6978
7044
  qaRunId,
6979
7045
  reason: failureReason,
6980
7046
  });
7047
+ // The visual-journey gate can refuse a draft AFTER the qa-run above
7048
+ // was created. EXECUTE never runs, so terminalize the record here
7049
+ // instead of leaving it pending forever.
7050
+ if (qaRunId && draftOutcome && draftOutcome.nextState === qaSessions.QA_SESSION_STATE.FAILED) {
7051
+ try {
7052
+ require('../qa/runs').completeRun(qaRunId, {
7053
+ status: 'errored',
7054
+ summary: (qaSessions.getSession(qaSessionId) || {}).error
7055
+ || 'QA Session DRAFT rejected before EXECUTE',
7056
+ artifacts: [],
7057
+ });
7058
+ } catch (runErr) {
7059
+ log('warn', `qa-session ${qaSessionId} could not terminalize orphaned run ${qaRunId}: ${runErr.message}`);
7060
+ }
7061
+ }
6981
7062
  } else {
6982
7063
  qaSessions.handleDraftComplete(qaSessionId, {
6983
7064
  success: !!effectiveSuccess,
6984
7065
  testFile,
7066
+ manifest: draftManifest,
6985
7067
  reason: failureReason,
6986
7068
  });
6987
7069
  }
6988
7070
  } else if (qaSessionPhase === 'execute') {
6989
7071
  // The qaRunId block above (line 4340) already wrote the terminal
6990
- // qa-runs record. Re-read it to drive the session state.
7072
+ // qa-runs record. Re-read it to drive the session state — and hand
7073
+ // the record itself to the session layer so the W-msb9kgs402813a97-c
7074
+ // evidence gate reads the same artifacts/journeyCoverage rather than
7075
+ // re-querying.
6991
7076
  let qaRunStatus = null;
7077
+ let qaRunRecord = null;
6992
7078
  const linkedQaRunId = meta?.qaRunId || meta?.item?.meta?.qaRunId;
6993
7079
  if (linkedQaRunId) {
6994
7080
  try {
6995
- const qaRuns = require('./qa-runs');
7081
+ const qaRuns = require('../qa/runs');
6996
7082
  const run = qaRuns.getRun(linkedQaRunId);
6997
- if (run && run.status) qaRunStatus = run.status;
7083
+ if (run) {
7084
+ qaRunRecord = run;
7085
+ if (run.status) qaRunStatus = run.status;
7086
+ }
6998
7087
  } catch (readErr) {
6999
7088
  log('warn', `qa-session execute qa-run lookup failed for ${qaSessionId}: ${readErr.message}`);
7000
7089
  }
@@ -7002,6 +7091,7 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
7002
7091
  qaSessions.handleExecuteComplete(qaSessionId, {
7003
7092
  success: !!effectiveSuccess,
7004
7093
  qaRunStatus,
7094
+ qaRun: qaRunRecord,
7005
7095
  summary: resultSummary || null,
7006
7096
  reason: failureReason,
7007
7097
  });
@@ -7014,6 +7104,24 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
7014
7104
  // Plan chaining removed — user must explicitly execute plan-to-prd after reviewing the plan
7015
7105
  if (effectiveSuccess && meta?.item?.sourcePlan) checkPlanCompletion(meta, config);
7016
7106
 
7107
+ // W-msal3ser01q4ccb3 — auto-QA for successfully verified PRDs (opt-in via
7108
+ // engine.autoQaCompletedPrds, default off). Deliberately hooked on a
7109
+ // SUCCESSFUL verify work item rather than on the PRD's `status: completed`
7110
+ // flip: checkPlanCompletion above sets that status BEFORE verification runs,
7111
+ // so triggering there would race the verify agent and miss the canonical
7112
+ // prd/guides/verify-<plan>.md the QA spec builder prefers. `skipDoneStatus`
7113
+ // is honoured because the verify-missing-PR guard flips the WI to `failed`
7114
+ // without clearing effectiveSuccess. Everything else — guide presence,
7115
+ // cross-repo fan-in, durable dedupe — is re-derived from persisted state
7116
+ // inside the hook so a restart or duplicate callback cannot double-launch.
7117
+ if (effectiveSuccess && !skipDoneStatus && type === WORK_TYPE.VERIFY && meta?.item?.sourcePlan) {
7118
+ try {
7119
+ require('../qa/auto-prd-qa').maybeAutoQaAfterVerify(meta.item, config);
7120
+ } catch (err) {
7121
+ log('warn', `auto-qa after verify: ${err.message}`);
7122
+ }
7123
+ }
7124
+
7017
7125
  // Archive is manual — user archives plans from the dashboard when ready
7018
7126
 
7019
7127
  // Scheduled task back-reference: update SQL state and write a linked inbox note.
@@ -7023,7 +7131,7 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
7023
7131
  const itemId = meta.item.id;
7024
7132
  shared.mutateScheduleRuns((runs) => {
7025
7133
  // P-c549d07e — preserve existing fields (esp. recentWorkItemIds ring
7026
- // populated at dispatch time by engine/scheduler.writeScheduleRunEntry).
7134
+ // populated at dispatch time by engine/orchestration/scheduler.writeScheduleRunEntry).
7027
7135
  // The literal-object replacement that lived here previously silently
7028
7136
  // clobbered the ring on every successful completion.
7029
7137
  const existing = typeof runs[scheduleId] === 'object' && runs[scheduleId] ? runs[scheduleId] : {};
@@ -7065,7 +7173,7 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
7065
7173
  // returns, and their workdir validator checks `<cwd>` on disk. If the
7066
7174
  // worktree is deleted here first, every sidecar pointing at the worktree
7067
7175
  // fails with a bogus `invalid-workdir: directory does not exist`
7068
- // rejection. The cleanup sweep (engine/cleanup.js) reaps the worktree
7176
+ // rejection. The cleanup sweep (engine/orchestration/cleanup.js) reaps the worktree
7069
7177
  // afterwards: when acceptance fails, the >2h age sweep picks it up; when
7070
7178
  // acceptance succeeds, the managed-spawn cwd anchor in cleanup.js keeps
7071
7179
  // the worktree alive while the engine-owned services run inside it.
@@ -7152,7 +7260,7 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
7152
7260
  // reviewer slot can't sit at -5 indefinitely. Idempotent via the PR-target
7153
7261
  // dedup in addToDispatch; agent assignment is a soft preference (review-
7154
7262
  // skilled agents first, then any idle agent) — NEVER block on a specific
7155
- // reviewer. See engine/ado.js:resetReviewerNegativeVote for the verdict-
7263
+ // reviewer. See engine/ado/index.js:resetReviewerNegativeVote for the verdict-
7156
7264
  // flip reset that fires on the resulting APPROVE.
7157
7265
  try {
7158
7266
  dispatchReReviewForFix(dispatchItem, meta, config, { agentId, resultSummary });
@@ -7236,7 +7344,7 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
7236
7344
  function handleQaSessionSetupCompletion(meta, opts = {}) {
7237
7345
  const qaSessionId = meta?.sessionId || meta?.item?.meta?.sessionId;
7238
7346
  if (!qaSessionId) return null;
7239
- const qaSessions = require('./qa-sessions');
7347
+ const qaSessions = require('../qa/sessions');
7240
7348
  const scope = opts.scope || resolveWorkItemScope(meta);
7241
7349
  const project = opts.project !== undefined
7242
7350
  ? opts.project
@@ -7259,7 +7367,7 @@ function handleQaSessionSetupCompletion(meta, opts = {}) {
7259
7367
  // (e.g., manually raised PRs, cross-plan PRs, or PRs created while engine was paused).
7260
7368
  function syncPrdFromPrs(config) {
7261
7369
  try {
7262
- const { reconcileItemsWithPrs } = require('../engine');
7370
+ const { reconcileItemsWithPrs } = require('../../engine');
7263
7371
  config = config || queries.getConfig();
7264
7372
  const allProjects = shared.getProjects(config);
7265
7373
 
@@ -7325,7 +7433,7 @@ function syncPrdFromPrs(config) {
7325
7433
  // Paused, rejected, awaiting-approval, completed, and archived PRDs are
7326
7434
  // skipped via _PRD_FROZEN_STATUSES. The locked write is idempotent.
7327
7435
  function advancePrdStatusOnAllItemsDone(config) {
7328
- const prdFiles = require('./prd-store').listPrdRows().filter(row => !row.archived).map(row => row.filename);
7436
+ const prdFiles = require('../planning/prd-store').listPrdRows().filter(row => !row.archived).map(row => row.filename);
7329
7437
  if (!prdFiles.length) return;
7330
7438
 
7331
7439
  const allWorkItems = queries.getWorkItems(config);
@@ -7393,7 +7501,7 @@ function advancePrdStatusOnAllItemsDone(config) {
7393
7501
  //
7394
7502
  // This runs per-tick (right after syncPrdFromPrs). It reads the NORMALIZED
7395
7503
  // shared PR schema, so a single sweep covers both GitHub and ADO
7396
- // pollers at once (engine/github.js + engine/ado.js are intentional mirrors
7504
+ // pollers at once (engine/providers/github.js + engine/ado/index.js are intentional mirrors
7397
7505
  // writing the same schema) — the durable single-source approach the task
7398
7506
  // allows in lieu of hooking each poller's merged transition separately. Because
7399
7507
  // it re-stamps every tick, the persisted copy is KEPT CURRENT through the
@@ -7673,7 +7781,7 @@ function pruneScopeMismatchDuplicatePrs(config) {
7673
7781
  }
7674
7782
 
7675
7783
  // Index all PRs by repository-scoped identity across all projects.
7676
- const store = require('./pull-requests-store');
7784
+ const store = require('../persistence/pull-requests-store');
7677
7785
  const allPrs = store.readAllPullRequests() || [];
7678
7786
  const byId = new Map();
7679
7787
  for (const pr of allPrs) {
@@ -8097,7 +8205,7 @@ module.exports = {
8097
8205
  // exercise both paths directly.
8098
8206
  recordWorktreeHeldPause,
8099
8207
  clearWorktreeHeldPause,
8100
- // W-mpx44p05000ze8d8 — exported so engine/ado.js + engine/github.js can
8208
+ // W-mpx44p05000ze8d8 — exported so engine/ado/index.js + engine/providers/github.js can
8101
8209
  // drop stale `_noOpFixes` records during their PR status poll, and so
8102
8210
  // unit tests can exercise the sweep helper directly.
8103
8211
  sweepStalePrNoOpFixes,