@yemi33/minions 0.1.2447 → 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 +66 -7
  7. package/dashboard/js/detail-panel.js +36 -0
  8. package/dashboard/js/memory-panel.js +59 -12
  9. package/dashboard/js/qa.js +186 -45
  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 +52 -6
  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,5 +1,5 @@
1
1
  /**
2
- * engine/pr-fix-target.js — Execution-target choice surface for projectless PR
2
+ * engine/providers/pr-fix-target.js — Execution-target choice surface for projectless PR
3
3
  * fixes ("never clone silently").
4
4
  *
5
5
  * Plan: projectless-pr-actions, Phase 2, item P-ppa10005. This module is the
@@ -7,13 +7,13 @@
7
7
  * unconfigured repo until the user explicitly picks an execution target. It does
8
8
  * NOT execute anything — the executors land in:
9
9
  * - temp-clone → P-ppa10006 (ephemeral clone, fix, push, discard) [DEFAULT]
10
- * — IMPLEMENTED in engine/pr-temp-clone.js
10
+ * — IMPLEMENTED in engine/providers/pr-temp-clone.js
11
11
  * - clone-keep → P-ppa10007 (auto-clone + persistent project registration)
12
- * — IMPLEMENTED in engine/pr-clone-keep.js
12
+ * — IMPLEMENTED in engine/providers/pr-clone-keep.js
13
13
  * - remote-patch → P-ppa10008 (GitHub Contents API, trivial single-file, NO test run)
14
- * — IMPLEMENTED in engine/pr-remote-patch.js
14
+ * — IMPLEMENTED in engine/providers/pr-remote-patch.js
15
15
  * - devbox → P-ppa10011 (ephemeral clone+fix+push ON A DEVBOX, zero local
16
- * footprint) — IMPLEMENTED in engine/pr-devbox.js. Never default,
16
+ * footprint) — IMPLEMENTED in engine/providers/pr-devbox.js. Never default,
17
17
  * never auto-selected; explicit consent only.
18
18
  *
19
19
  * Load-bearing invariant (read before extending): "never clone silently". A fix
@@ -34,10 +34,10 @@
34
34
  * P-ppa10006/7/8 executors land).
35
35
  */
36
36
 
37
- const shared = require('./shared');
37
+ const shared = require('../core/shared');
38
38
  const prResolve = require('./pr-resolve');
39
39
  const { PrActionError } = require('./pr-action');
40
- const prFixTargetStore = require('./pr-fix-target-store');
40
+ const prFixTargetStore = require('../persistence/pr-fix-target-store');
41
41
 
42
42
  // ── Execution targets ─────────────────────────────────────────────────────────
43
43
 
@@ -126,9 +126,9 @@ function _routedRecord(plan, target, extra = {}) {
126
126
  module: desc.executorModule || null,
127
127
  validates: desc.validates,
128
128
  trivialOnly: desc.trivialOnly,
129
- // temp-clone's executor landed in P-ppa10006 (engine/pr-temp-clone.js),
130
- // clone-keep's in P-ppa10007 (engine/pr-clone-keep.js), and remote-patch's
131
- // in P-ppa10008 (engine/pr-remote-patch.js). `implemented` reflects whether
129
+ // temp-clone's executor landed in P-ppa10006 (engine/providers/pr-temp-clone.js),
130
+ // clone-keep's in P-ppa10007 (engine/providers/pr-clone-keep.js), and remote-patch's
131
+ // in P-ppa10008 (engine/providers/pr-remote-patch.js). `implemented` reflects whether
132
132
  // a real executor backs the choice.
133
133
  implemented: !!desc.implemented,
134
134
  },
@@ -181,7 +181,7 @@ function findConfiguredProjectForRef(ref, config) {
181
181
  //
182
182
  // P-ppa10009: optionally remember the user's execution-target pick keyed by repo
183
183
  // scope so a repeat fix on the same repo doesn't re-prompt. Persisted SQL-first
184
- // via engine/pr-fix-target-store.js. Absence of a remembered choice still
184
+ // via engine/persistence/pr-fix-target-store.js. Absence of a remembered choice still
185
185
  // prompts — the safe "never clone silently" default is preserved.
186
186
 
187
187
  /** The remembered execution-target id for a ref's repo scope, or null. */
@@ -338,8 +338,8 @@ function planPrFix(input = {}, opts = {}) {
338
338
  *
339
339
  * IMPORTANT: this still does NOT clone or spawn — selection only routes. The
340
340
  * returned `routed` record carries `executor.implemented` (true once a real
341
- * executor backs the choice: temp-clone → engine/pr-temp-clone.js; clone-keep →
342
- * engine/pr-clone-keep.js; remote-patch → engine/pr-remote-patch.js). The caller is
341
+ * executor backs the choice: temp-clone → engine/providers/pr-temp-clone.js; clone-keep →
342
+ * engine/providers/pr-clone-keep.js; remote-patch → engine/providers/pr-remote-patch.js). The caller is
343
343
  * responsible for invoking the executor; this surface never clones, keeping
344
344
  * "never clone silently" absolute.
345
345
  *
@@ -1,9 +1,9 @@
1
1
  /**
2
- * engine/pr-remote-patch.js — Remote patch fast path (the "no validation"
2
+ * engine/providers/pr-remote-patch.js — Remote patch fast path (the "no validation"
3
3
  * execution target for a projectless one-off PR fix).
4
4
  *
5
5
  * Plan: projectless-pr-actions, Phase 2, item P-ppa10008. The execution-target
6
- * choice surface (engine/pr-fix-target.js) routes `remote-patch` here once the
6
+ * choice surface (engine/providers/pr-fix-target.js) routes `remote-patch` here once the
7
7
  * user has EXPLICITLY picked it (the "never clone silently" invariant lives
8
8
  * upstream — by the time we run, the user has consented to a no-clone push).
9
9
  *
@@ -38,7 +38,7 @@
38
38
  * repo; the defaults are real (shellSafeGh + fetchPrBranches).
39
39
  */
40
40
 
41
- const shared = require('./shared');
41
+ const shared = require('../core/shared');
42
42
  const prResolve = require('./pr-resolve');
43
43
  const ghToken = require('./gh-token');
44
44
  const { PrActionError } = require('./pr-action');
@@ -136,7 +136,7 @@ function _normalizePatch(patch) {
136
136
  * branch, and normalize/validate the single-file patch. NOTHING is mutated here.
137
137
  *
138
138
  * `input` — `{ url }`, a normalized ref, or a routed plan from
139
- * engine/pr-fix-target.js#resolvePrFixTarget.
139
+ * engine/providers/pr-fix-target.js#resolvePrFixTarget.
140
140
  * `opts.patch` — REQUIRED single-file patch (see `_normalizePatch`).
141
141
  * `opts.fetchPrBranches` — override the branch fetch (test seam).
142
142
  * `opts.prUrl` — original user URL (preferred for surfacing).
@@ -1,5 +1,5 @@
1
1
  /**
2
- * engine/pr-resolve.js — Projectless PR reference resolver + diff/metadata fetcher.
2
+ * engine/providers/pr-resolve.js — Projectless PR reference resolver + diff/metadata fetcher.
3
3
  *
4
4
  * Plan: projectless-pr-actions, item P-ppa10001. Foundation for all projectless
5
5
  * PR actions (review / summarize / comment / triage with no configured project
@@ -14,20 +14,20 @@
14
14
  * for that PR WITHOUT a configured project or clone, via `gh` (GitHub) and the
15
15
  * ADO REST API (`_apis/git/...`).
16
16
  *
17
- * Token routing is per-slug through engine/gh-token.js#resolveTokenForSlug; for an
17
+ * Token routing is per-slug through engine/providers/gh-token.js#resolveTokenForSlug; for an
18
18
  * unknown GitHub slug (no mapping, no fleet default) it falls back across the
19
19
  * authed accounts configured in `engine.ghAccounts` rather than relying on the
20
20
  * active `gh auth` profile. It NEVER runs `gh auth switch`. ADO tokens come from
21
- * engine/ado-token.js (`az account get-access-token` / azureauth).
21
+ * engine/ado/token.js (`az account get-access-token` / azureauth).
22
22
  *
23
23
  * All shell-outs use argv form (shell:false) and slug/number are validated before
24
24
  * interpolation, so poisoned PR refs cannot inject shell metacharacters.
25
25
  */
26
26
 
27
27
  const path = require('path');
28
- const shared = require('./shared');
28
+ const shared = require('../core/shared');
29
29
  const ghToken = require('./gh-token');
30
- const adoToken = require('./ado-token');
30
+ const adoToken = require('../ado/token');
31
31
 
32
32
  const { safeJson, MINIONS_DIR, log } = shared;
33
33
 
@@ -174,7 +174,7 @@ function normalizePrRef(input) {
174
174
  * Shared by the 6 PR-action executor modules (pr-clone-keep, pr-devbox,
175
175
  * pr-remote-patch, pr-temp-clone, pr-track, pr-fix-target), which previously
176
176
  * each defined a byte-identical copy of this function. `PrActionError` must be
177
- * passed in by the caller — pr-resolve.js does not import engine/pr-action.js
177
+ * passed in by the caller — pr-resolve.js does not import engine/providers/pr-action.js
178
178
  * (that would introduce a new cross-module dependency in that direction).
179
179
  */
180
180
  function resolvePrActionRef(input, PrActionError) {
@@ -412,7 +412,7 @@ function _stripRefsHeads(ref) {
412
412
 
413
413
  /**
414
414
  * Fetch the source/target branch names + a clone URL for a PR with NO configured
415
- * project and NO local clone. The temp-clone fix executor (engine/pr-temp-clone.js)
415
+ * project and NO local clone. The temp-clone fix executor (engine/providers/pr-temp-clone.js)
416
416
  * needs the PR's HEAD branch to clone + push the fix back to, plus the clone URL
417
417
  * (fork-aware on GitHub). Token routing mirrors `fetchPrPayload` (per-slug GH
418
418
  * tokens / ADO bearer); shell-outs are argv-form and validated.
@@ -1,10 +1,10 @@
1
1
  /**
2
- * engine/pr-temp-clone.js — Temp-clone fix executor (the DEFAULT execution target
2
+ * engine/providers/pr-temp-clone.js — Temp-clone fix executor (the DEFAULT execution target
3
3
  * for a projectless one-off PR fix).
4
4
  *
5
5
  * Plan: projectless-pr-actions, Phase 2, item P-ppa10006. This is the first
6
6
  * executor in the chain that actually MUTATES code. The execution-target choice
7
- * surface (engine/pr-fix-target.js) routes `temp-clone` here once the user has
7
+ * surface (engine/providers/pr-fix-target.js) routes `temp-clone` here once the user has
8
8
  * explicitly picked it (the "never clone silently" invariant lives upstream — by
9
9
  * the time we run, the user has chosen to clone).
10
10
  *
@@ -40,7 +40,7 @@
40
40
  const fs = require('fs');
41
41
  const path = require('path');
42
42
 
43
- const shared = require('./shared');
43
+ const shared = require('../core/shared');
44
44
  const prResolve = require('./pr-resolve');
45
45
  const ghToken = require('./gh-token');
46
46
  const { PrActionError } = require('./pr-action');
@@ -78,7 +78,7 @@ function _tmpRoot(opts = {}) {
78
78
  * and compute the ephemeral clone dir. NOTHING is created or cloned here.
79
79
  *
80
80
  * `input` — `{ url }`, a normalized ref, or a routed plan from
81
- * engine/pr-fix-target.js#resolvePrFixTarget.
81
+ * engine/providers/pr-fix-target.js#resolvePrFixTarget.
82
82
  * `opts.fetchPrBranches` — override the branch fetch (test seam).
83
83
  * `opts.prUrl` — original user URL (preferred for surfacing).
84
84
  * `opts.tmpRoot` — override the ephemeral-clone root.
@@ -129,7 +129,7 @@ async function _resolveToken(plan, opts = {}) {
129
129
  return resolve(plan.sourceRepoSlug || plan.ref.slug) || null;
130
130
  }
131
131
  if (plan.ref.host === 'ado') {
132
- const acquire = opts._acquireAdoToken || require('./ado-token').acquireAdoToken;
132
+ const acquire = opts._acquireAdoToken || require('../ado/token').acquireAdoToken;
133
133
  const acquired = await acquire();
134
134
  return (acquired && acquired.token) || null;
135
135
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * engine/pr-track.js — Promote a one-off PR to TRACKED auto-fix (enrollment
2
+ * engine/providers/pr-track.js — Promote a one-off PR to TRACKED auto-fix (enrollment
3
3
  * wiring).
4
4
  *
5
5
  * Plan: projectless-pr-actions, Phase 3, item P-ppa10010. The "Track for auto-fix"
@@ -8,14 +8,13 @@
8
8
  * pollers → PR discovery → review / fix / re-review → auto-merge.
9
9
  *
10
10
  * There is NO new fix engine here — enrollment is "mostly wiring":
11
- * 1. The canonical auto-managed signal is a SINGLE field: `contextOnly:false`
12
- * (`shared.isAutoManagedPrRecord` — engine/shared.js). Enrolling a PR means
13
- * ensuring a tracked record exists with `contextOnly:false` so the existing
14
- * PR-discovery auto-managed gate flips on.
11
+ * 1. Enrolling with `contextOnly:false` enables both per-PR automation
12
+ * controls through the manual-link path, so the existing discovery gate
13
+ * admits review and fix dispatches.
15
14
  * 2. If the PR's repo is NOT a configured project, ongoing auto-fix needs a
16
15
  * PERSISTENT checkout, so promotion surfaces the SAME execution-target
17
- * choice as a one-off fix (engine/pr-fix-target.js, P-ppa10005) but with
18
- * "Clone & keep" (engine/pr-clone-keep.js, P-ppa10007 — the persistent
16
+ * choice as a one-off fix (engine/providers/pr-fix-target.js, P-ppa10005) but with
17
+ * "Clone & keep" (engine/providers/pr-clone-keep.js, P-ppa10007 — the persistent
19
18
  * project executor) as the implied/default selection. NOTHING is cloned
20
19
  * until the user picks.
21
20
  * 3. Once the repo is project-ready (already configured, or after Clone & keep
@@ -43,7 +42,7 @@ const prFixTarget = require('./pr-fix-target');
43
42
  const { PrActionError } = require('./pr-action');
44
43
 
45
44
  // For ongoing auto-fix the checkout must persist across ticks, so "Clone & keep"
46
- // (engine/pr-clone-keep.js, P-ppa10007) is the implied selection when promoting a
45
+ // (engine/providers/pr-clone-keep.js, P-ppa10007) is the implied selection when promoting a
47
46
  // PR whose repo is not yet a configured project. temp-clone discards its clone
48
47
  // after one push, so it can never back ongoing tracking.
49
48
  const TRACK_RECOMMENDED_TARGET = 'clone-keep';
@@ -92,7 +91,7 @@ function buildPrTrackTargetPrompt(plan) {
92
91
  * point — it does NOT write anything.
93
92
  *
94
93
  * Delegates configured-project detection + the choice surface to
95
- * engine/pr-fix-target.js (so the "never clone silently" invariant + the
94
+ * engine/providers/pr-fix-target.js (so the "never clone silently" invariant + the
96
95
  * configured-vs-unconfigured logic live in exactly one place), then re-flavors
97
96
  * the unconfigured branch for ongoing tracking (Clone & keep recommended).
98
97
  *
@@ -151,10 +150,9 @@ function planPrTrack(input = {}, opts = {}) {
151
150
  }
152
151
 
153
152
  /**
154
- * Build the canonical enrollment signal for a PR (or plan/ref). Enrollment is a
155
- * single field: `contextOnly:false` — that is the ONE thing
156
- * `shared.isAutoManagedPrRecord` reads to decide a PR is auto-managed. The caller
157
- * feeds `{ url, contextOnly }` to the existing manual-link path
153
+ * Build the canonical enrollment signal for a PR (or plan/ref). The caller
154
+ * feeds `{ url, contextOnly:false }` to the existing manual-link path, which
155
+ * enables Auto Fix and Auto Review together
158
156
  * (dashboard.js#linkPullRequestForTracking), which creates the record if missing
159
157
  * and promotes an existing context-only record (upsert refuses to demote).
160
158
  *
@@ -23,10 +23,10 @@
23
23
 
24
24
  const fs = require('fs');
25
25
  const path = require('path');
26
- const shared = require('./shared');
27
- const queries = require('./queries');
26
+ const shared = require('../core/shared');
27
+ const queries = require('../core/queries');
28
28
  const github = require('./github');
29
- const ado = require('./ado');
29
+ const ado = require('../ado');
30
30
 
31
31
  const { log } = shared;
32
32
  const PRD_DIR = queries.PRD_DIR;
@@ -143,7 +143,7 @@ async function _reconcilePrdForProject(plan, file, project, planIds) {
143
143
  * the sweep.
144
144
  */
145
145
  async function reconcileSharedBranchPrs(config) {
146
- const planFiles = require('./prd-store').listPrdRows().filter(row => !row.archived);
146
+ const planFiles = require('../planning/prd-store').listPrdRows().filter(row => !row.archived);
147
147
 
148
148
  for (const { filename: file, plan } of planFiles) {
149
149
  try {
@@ -0,0 +1,313 @@
1
+ /**
2
+ * engine/qa/auto-prd-qa.js — automatic QA for successfully verified PRDs.
3
+ *
4
+ * W-msal3ser01q4ccb3. Opt-in via `engine.autoQaCompletedPrds` (default false).
5
+ * When ON, a PRD whose verification actually SUCCEEDED gets a PRD-driven QA
6
+ * Session automatically, through the exact same orchestration the manual
7
+ * `POST /api/qa/from-prd` bridge uses (engine/qa/prd-session.js).
8
+ *
9
+ * ── Why the trigger is verify-completion, not `status: completed` ───────────
10
+ * `checkPlanCompletion` flips the PRD to `completed` and only THEN creates the
11
+ * verify work item(s). Triggering on the status flip would therefore fire
12
+ * before verification has run and before the verify agent has written the
13
+ * canonical `prd/guides/verify-<plan>.md` — the guide the spec builder prefers
14
+ * as its flow source. So the trigger is: a verify work item completed, its
15
+ * guide exists on disk, and EVERY verify work item belonging to that plan is
16
+ * `done`.
17
+ *
18
+ * ── Idempotency ────────────────────────────────────────────────────────────
19
+ * The dedupe key is a "verify generation": a hash of the plan filename plus
20
+ * every verify WI's `id@completedAt`. It is durable (stamped on the PRD row in
21
+ * SQL via prd-store) so it survives ticks, engine restarts, lifecycle retries,
22
+ * and duplicate completion callbacks — an in-memory flag would not. A reopened
23
+ * PRD re-verified later produces DIFFERENT `completedAt` values, hence a new
24
+ * generation and a legitimately new QA session; the same generation always
25
+ * collapses to exactly one.
26
+ *
27
+ * The generation is CLAIMED inside the prd-store mutation before any session is
28
+ * created, and the claim is never released — a failed launch records its error
29
+ * and stops, so a broken QA path cannot become an unbounded auto-retry loop.
30
+ * PRD completion state is never touched: QA failure stays visible in QA state
31
+ * only.
32
+ */
33
+
34
+ const fs = require('fs');
35
+ const path = require('path');
36
+ const crypto = require('crypto');
37
+
38
+ const shared = require('../core/shared');
39
+ const queries = require('../core/queries');
40
+ const prdStore = require('../planning/prd-store');
41
+ const qaFromPrd = require('./from-prd');
42
+ const qaPrdSession = require('./prd-session');
43
+
44
+ const { log, ts, WORK_TYPE, DONE_STATUSES } = shared;
45
+
46
+ // Bounded per-PRD launch history. Keeps the dedupe durable across the handful
47
+ // of generations a re-opened PRD can accumulate without growing the PRD row.
48
+ const AUTO_QA_LAUNCH_HISTORY = 5;
49
+
50
+ // Reasons returned when no session is launched. Stable strings so tests and
51
+ // callers can assert on them instead of on log text.
52
+ const SKIP_REASON = Object.freeze({
53
+ DISABLED: 'disabled',
54
+ NOT_VERIFY: 'not-verify-work-item',
55
+ NO_SOURCE_PLAN: 'no-source-plan',
56
+ UNSAFE_PLAN_FILE: 'unsafe-plan-filename',
57
+ PRD_NOT_FOUND: 'prd-not-found',
58
+ VERIFY_GUIDE_MISSING: 'verify-guide-missing',
59
+ NO_VERIFY_ITEMS: 'no-verify-work-items',
60
+ VERIFY_INCOMPLETE: 'verify-incomplete',
61
+ ALREADY_LAUNCHED: 'already-launched',
62
+ PRD_NOT_QA_READY: 'prd-not-qa-ready',
63
+ });
64
+
65
+ function _isNonEmptyString(v) {
66
+ return typeof v === 'string' && v.trim().length > 0;
67
+ }
68
+
69
+ function _planSlug(planFile) {
70
+ return String(planFile).replace(/\.json$/i, '');
71
+ }
72
+
73
+ function verifyGuidePath(planFile) {
74
+ return path.join(shared.MINIONS_DIR, 'prd', 'guides', `verify-${_planSlug(planFile)}.md`);
75
+ }
76
+
77
+ /**
78
+ * Has the verify agent persisted the canonical guide for this plan? Presence,
79
+ * not content, is the gate — the spec builder does its own emptiness check and
80
+ * falls back through manual-qa-<plan>.md → acceptance criteria, preserving the
81
+ * documented flow priority.
82
+ */
83
+ function hasVerifyGuide(planFile) {
84
+ try { return fs.statSync(verifyGuidePath(planFile)).isFile(); }
85
+ catch { return false; }
86
+ }
87
+
88
+ /**
89
+ * Every verify work item that belongs to `planFile`, across all project scopes
90
+ * and central. Mirrors the lookup in lifecycle.js#checkPlanCompletion.
91
+ */
92
+ function collectVerifyItems(planFile, config) {
93
+ const all = queries.getWorkItems(config);
94
+ if (!Array.isArray(all)) return [];
95
+ return all.filter(w => w
96
+ && w.sourcePlan === planFile
97
+ && (w.itemType === WORK_TYPE.VERIFY || w.type === WORK_TYPE.VERIFY));
98
+ }
99
+
100
+ /**
101
+ * Cross-repo fan-in gate. A plan fans verification out to one verify WI per
102
+ * touched project; QA covers the plan as a whole, so it must wait for ALL of
103
+ * them and launch ONCE — not once per verify completion.
104
+ *
105
+ * Anything that is not a DONE status blocks: a pending/dispatched sibling means
106
+ * the generation is still forming, and a failed/cancelled sibling means the
107
+ * verification did not succeed, so no QA runs at all.
108
+ */
109
+ function verifyItemsAllSucceeded(items) {
110
+ if (!Array.isArray(items) || items.length === 0) return false;
111
+ return items.every(w => w && DONE_STATUSES.has(w.status));
112
+ }
113
+
114
+ /**
115
+ * Stable identity for one completion generation of a plan's verification.
116
+ * `completedAt` is what makes a re-verified PRD a new generation:
117
+ * shared.reopenWorkItem deletes it, and the next successful completion stamps a
118
+ * fresh timestamp.
119
+ */
120
+ function computeVerifyGeneration(planFile, items) {
121
+ const parts = items
122
+ .map(w => `${w.id || ''}@${w.completedAt || ''}`)
123
+ .sort();
124
+ return crypto.createHash('sha256')
125
+ .update(`${planFile}\n${parts.join('\n')}`)
126
+ .digest('hex')
127
+ .slice(0, 16);
128
+ }
129
+
130
+ function _readLaunches(prd) {
131
+ const autoQa = prd && prd._autoQa && typeof prd._autoQa === 'object' && !Array.isArray(prd._autoQa)
132
+ ? prd._autoQa
133
+ : null;
134
+ return autoQa && Array.isArray(autoQa.launches) ? autoQa.launches : [];
135
+ }
136
+
137
+ /**
138
+ * Compare-and-set the generation claim under the prd-store transaction. Returns
139
+ * true only for the caller that actually inserted it, so concurrent verify
140
+ * completions (cross-repo fan-in landing in the same tick) and duplicate
141
+ * completion callbacks collapse to a single launch.
142
+ */
143
+ function claimGeneration(planFile, generation) {
144
+ let claimed = false;
145
+ prdStore.mutatePrd(planFile, (data) => {
146
+ const launches = _readLaunches(data);
147
+ if (launches.some(l => l && l.generation === generation)) return data;
148
+ const next = launches.concat([{
149
+ generation,
150
+ status: 'claimed',
151
+ sessionId: null,
152
+ setupWorkItemId: null,
153
+ claimedAt: ts(),
154
+ }]);
155
+ data._autoQa = { launches: next.slice(-AUTO_QA_LAUNCH_HISTORY) };
156
+ claimed = true;
157
+ return data;
158
+ });
159
+ return claimed;
160
+ }
161
+
162
+ function recordLaunchOutcome(planFile, generation, patch) {
163
+ try {
164
+ prdStore.mutatePrd(planFile, (data) => {
165
+ const launches = _readLaunches(data);
166
+ const next = launches.map(l => (l && l.generation === generation ? { ...l, ...patch } : l));
167
+ data._autoQa = { launches: next.slice(-AUTO_QA_LAUNCH_HISTORY) };
168
+ return data;
169
+ });
170
+ } catch (e) {
171
+ log('warn', `auto-qa: recording launch outcome for ${planFile}/${generation} failed: ${e.message}`);
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Post-verify hook. Called from lifecycle.js#runPostCompletionHooks after a
177
+ * work item completes successfully.
178
+ *
179
+ * Never throws: every failure degrades to `{ launched: false }` plus a warn, so
180
+ * an auto-QA problem can never regress the verify work item or the PRD.
181
+ *
182
+ * @param {object} item the completed work item (meta.item)
183
+ * @param {object} config live engine config
184
+ * @returns {{ launched: boolean, reason?: string, generation?: string, sessionId?: string,
185
+ * setupWorkItemId?: string|null, error?: string }}
186
+ */
187
+ function maybeAutoQaAfterVerify(item, config) {
188
+ if (!shared.resolveAutoQaCompletedPrds(config && config.engine)) {
189
+ return { launched: false, reason: SKIP_REASON.DISABLED };
190
+ }
191
+ if (!item || (item.itemType !== WORK_TYPE.VERIFY && item.type !== WORK_TYPE.VERIFY)) {
192
+ return { launched: false, reason: SKIP_REASON.NOT_VERIFY };
193
+ }
194
+ const planFile = _isNonEmptyString(item.sourcePlan) ? item.sourcePlan.trim() : null;
195
+ if (!planFile) return { launched: false, reason: SKIP_REASON.NO_SOURCE_PLAN };
196
+ // The plan filename reaches path.join for the guide lookup and prd-store for
197
+ // the claim; reuse the builder's traversal guard rather than trusting it.
198
+ if (!qaFromPrd._isSafePrdFileName(planFile)) {
199
+ return { launched: false, reason: SKIP_REASON.UNSAFE_PLAN_FILE };
200
+ }
201
+
202
+ let prd = null;
203
+ try { prd = prdStore.readPrd(planFile); }
204
+ catch (e) {
205
+ log('warn', `auto-qa: reading PRD ${planFile} failed: ${e.message}`);
206
+ return { launched: false, reason: SKIP_REASON.PRD_NOT_FOUND };
207
+ }
208
+ if (!prd || typeof prd !== 'object') {
209
+ return { launched: false, reason: SKIP_REASON.PRD_NOT_FOUND };
210
+ }
211
+
212
+ // Gate on the canonical verify guide, not on the PRD status flip: the guide
213
+ // is the artifact that proves verification actually ran to completion.
214
+ if (!hasVerifyGuide(planFile)) {
215
+ return { launched: false, reason: SKIP_REASON.VERIFY_GUIDE_MISSING };
216
+ }
217
+
218
+ let verifyItems;
219
+ try { verifyItems = collectVerifyItems(planFile, config); }
220
+ catch (e) {
221
+ log('warn', `auto-qa: collecting verify work items for ${planFile} failed: ${e.message}`);
222
+ return { launched: false, reason: SKIP_REASON.NO_VERIFY_ITEMS };
223
+ }
224
+ if (verifyItems.length === 0) {
225
+ return { launched: false, reason: SKIP_REASON.NO_VERIFY_ITEMS };
226
+ }
227
+ if (!verifyItemsAllSucceeded(verifyItems)) {
228
+ return { launched: false, reason: SKIP_REASON.VERIFY_INCOMPLETE };
229
+ }
230
+
231
+ const generation = computeVerifyGeneration(planFile, verifyItems);
232
+
233
+ // Cheap pre-check outside the claim so the steady-state re-scan does no write.
234
+ if (_readLaunches(prd).some(l => l && l.generation === generation)) {
235
+ return { launched: false, reason: SKIP_REASON.ALREADY_LAUNCHED, generation };
236
+ }
237
+
238
+ let claimed = false;
239
+ try { claimed = claimGeneration(planFile, generation); }
240
+ catch (e) {
241
+ log('warn', `auto-qa: claiming generation ${generation} for ${planFile} failed: ${e.message}`);
242
+ return { launched: false, reason: SKIP_REASON.ALREADY_LAUNCHED, generation, error: e.message };
243
+ }
244
+ if (!claimed) {
245
+ return { launched: false, reason: SKIP_REASON.ALREADY_LAUNCHED, generation };
246
+ }
247
+
248
+ try {
249
+ const created = qaPrdSession.createQaSessionFromPrd(planFile, {
250
+ // `auto` runs SETUP → DRAFT → EXECUTE without waiting for approval; an
251
+ // automatic session has no operator standing by to approve the draft.
252
+ mode: 'auto',
253
+ createdBy: 'engine:auto-qa-verified-prd',
254
+ provenanceExtra: {
255
+ autoTrigger: {
256
+ reason: 'verified-prd',
257
+ generation,
258
+ verifyWorkItemIds: verifyItems.map(w => w.id).filter(Boolean).sort(),
259
+ triggeredBy: item.id || null,
260
+ at: ts(),
261
+ },
262
+ },
263
+ resolveTargets: (spec) => qaPrdSession.resolveTargetsFromConfig(spec, config),
264
+ });
265
+ recordLaunchOutcome(planFile, generation, {
266
+ status: 'launched',
267
+ sessionId: created.sessionId,
268
+ setupWorkItemId: created.setupWorkItemId || null,
269
+ launchedAt: ts(),
270
+ });
271
+ log('info', `auto-qa: launched QA session ${created.sessionId} for verified PRD ${planFile} `
272
+ + `(generation ${generation}, verify WIs: ${verifyItems.map(w => w.id).join(', ')})`);
273
+ return {
274
+ launched: true,
275
+ generation,
276
+ sessionId: created.sessionId,
277
+ setupWorkItemId: created.setupWorkItemId || null,
278
+ };
279
+ } catch (e) {
280
+ // The claim is intentionally NOT released: recording the error and stopping
281
+ // is what keeps a broken QA path from re-firing every tick. QA failure is
282
+ // visible in QA/PRD auto-qa state and never regresses PRD completion.
283
+ const reason = e && e.reason ? e.reason : e.message;
284
+ const isNotReady = qaFromPrd.isQaFromPrdError(e) && /not QA-ready/i.test(String(reason));
285
+ recordLaunchOutcome(planFile, generation, {
286
+ status: 'error',
287
+ error: String(reason).slice(0, 500),
288
+ sessionId: (e && e.sessionId) || null,
289
+ erroredAt: ts(),
290
+ });
291
+ log('warn', `auto-qa: QA session for verified PRD ${planFile} not created: ${reason}`);
292
+ return {
293
+ launched: false,
294
+ reason: isNotReady ? SKIP_REASON.PRD_NOT_QA_READY : 'launch-failed',
295
+ generation,
296
+ error: String(reason),
297
+ };
298
+ }
299
+ }
300
+
301
+ module.exports = {
302
+ maybeAutoQaAfterVerify,
303
+ // Exported for tests / callers that need the individual gates.
304
+ verifyGuidePath,
305
+ hasVerifyGuide,
306
+ collectVerifyItems,
307
+ verifyItemsAllSucceeded,
308
+ computeVerifyGeneration,
309
+ claimGeneration,
310
+ recordLaunchOutcome,
311
+ SKIP_REASON,
312
+ AUTO_QA_LAUNCH_HISTORY,
313
+ };