@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
@@ -6,11 +6,11 @@
6
6
  * orchestration (SETUP → DRAFT → EXECUTE) can be re-used verbatim.
7
7
  *
8
8
  * This module is deliberately PURE in the same sense validateSpec is pure:
9
- * the only I/O it performs is reading the PRD JSON (under prd/ with an
10
- * archive fallback) and, optionally, a guide markdown file under
11
- * prd/guides/. No git, no network, no dispatch — every live target/worktree
12
- * decision is deferred to the SETUP phase. That keeps it trivially
13
- * unit-testable against fixture PRDs in an isolated MINIONS_TEST_DIR.
9
+ * the only I/O it performs is reading the PRD (from the SQL PRD store, with a
10
+ * legacy prd/ + prd/archive/ filesystem fallback) and, optionally, a guide
11
+ * markdown file under prd/guides/. No git, no network, no dispatch — every
12
+ * live target/worktree decision is deferred to the SETUP phase. That keeps it
13
+ * trivially unit-testable against fixture PRDs in an isolated MINIONS_TEST_DIR.
14
14
  *
15
15
  * Public API:
16
16
  * buildQaSessionSpecFromPrd(prdInput, opts) -> { spec, warnings, provenance }
@@ -27,8 +27,8 @@
27
27
 
28
28
  const fs = require('fs');
29
29
  const path = require('path');
30
- const shared = require('./shared');
31
- const qaSessions = require('./qa-sessions');
30
+ const shared = require('../core/shared');
31
+ const qaSessions = require('./sessions');
32
32
 
33
33
  const { LIMITS, VALID_MODES, validateSpec } = qaSessions;
34
34
 
@@ -68,6 +68,19 @@ class QaFromPrdError extends Error {
68
68
  }
69
69
  }
70
70
 
71
+ /**
72
+ * Classify a caught error as a builder rejection WITHOUT `instanceof`.
73
+ *
74
+ * This module is deliberately re-required per isolated MINIONS_TEST_DIR (see
75
+ * test/_helpers.js ISOLATED_MODULES) because it captures `shared`, so two live
76
+ * copies of the class can exist at once. An `instanceof` check across that
77
+ * boundary silently downgrades a typed 400 into a 500, which is exactly the
78
+ * bug this predicate exists to prevent. Callers in other modules MUST use it.
79
+ */
80
+ function isQaFromPrdError(err) {
81
+ return !!err && err.name === 'QaFromPrdError' && typeof err.reason === 'string';
82
+ }
83
+
71
84
  function _isNonEmptyString(v) {
72
85
  return typeof v === 'string' && v.trim().length > 0;
73
86
  }
@@ -95,9 +108,17 @@ function _guidesDir() {
95
108
  }
96
109
 
97
110
  /**
98
- * Resolve prdInput to { prd, prdFile }. When given a string, read the JSON
99
- * from prd/<file> (then prd/archive/<file>). When given an object, use it
100
- * verbatim (prdFile stays null → no guide lookup is possible).
111
+ * Resolve prdInput to { prd, prdFile }. When given a string, read the PRD from
112
+ * the authoritative SQL store (live bucket, then the archive bucket), falling
113
+ * back to legacy on-disk prd/<file> / prd/archive/<file> JSON. When given an
114
+ * object, use it verbatim (prdFile stays null → no guide lookup is possible).
115
+ *
116
+ * The SQL read is FIRST because migration 018 made `engine/state.db` the sole
117
+ * PRD authority — `prd/*.json` files no longer exist on a migrated install, so
118
+ * a filesystem-only lookup answered `prd-not-found` for every real PRD and made
119
+ * the guide-preferring flow priority (which needs the PRD *filename*)
120
+ * unreachable. The filesystem read is kept as a fallback for legacy installs
121
+ * and fixture-seeded test dirs.
101
122
  *
102
123
  * @throws {QaFromPrdError} prd-invalid-input / prd-not-found / prd-invalid-json
103
124
  */
@@ -112,16 +133,24 @@ function _resolvePrd(prdInput) {
112
133
  if (!_isSafePrdFileName(file)) {
113
134
  throw new QaFromPrdError(`invalid PRD filename: ${file}`);
114
135
  }
136
+
137
+ for (const archived of [false, true]) {
138
+ let stored = null;
139
+ try { stored = require('../planning/prd-store').readPrd(file, { archived }); }
140
+ catch { stored = null; /* no DB / unreadable row → fall through to disk */ }
141
+ if (stored && typeof stored === 'object' && !Array.isArray(stored)) {
142
+ return { prd: stored, prdFile: file };
143
+ }
144
+ }
145
+
115
146
  const candidates = [
116
147
  path.join(_prdDir(), file),
117
148
  path.join(_prdDir(), 'archive', file),
118
149
  ];
119
150
  let raw = null;
120
- let found = null;
121
151
  for (const p of candidates) {
122
152
  try {
123
153
  raw = fs.readFileSync(p, 'utf8');
124
- found = p;
125
154
  break;
126
155
  } catch { /* try next */ }
127
156
  }
@@ -421,6 +450,7 @@ function buildQaSessionSpecFromPrd(prdInput, opts = {}) {
421
450
  module.exports = {
422
451
  buildQaSessionSpecFromPrd,
423
452
  QaFromPrdError,
453
+ isQaFromPrdError,
424
454
  // Constants + internals exposed for tests.
425
455
  QA_READY_STATUSES,
426
456
  QA_PRD_FILENAME_MAX,
@@ -0,0 +1,240 @@
1
+ /**
2
+ * engine/qa/prd-session.js — the ONE PRD → QA-Session orchestration path.
3
+ *
4
+ * `POST /api/qa/from-prd` (dashboard.js#handleQaFromPrdCreate) and the
5
+ * post-verify auto-QA hook (engine/qa/auto-prd-qa.js) must create *identical*
6
+ * sessions: same spec builder, same project canonicalization, same
7
+ * createSession + queueSetup ordering, same provenance shape. Before this
8
+ * module the orchestration lived inline in the HTTP handler, so the only way
9
+ * for the engine to launch a PRD-driven session was to fork it — and a fork
10
+ * drifts. Everything that is genuinely HTTP-specific (body validation, header
11
+ * identity, status-code mapping, the non-blocking runner hint) stays in the
12
+ * handler; everything that defines *what a PRD-driven QA session is* lives
13
+ * here.
14
+ *
15
+ * The only injected seam is `resolveTargets`. Project resolution differs by
16
+ * caller on purpose: the dashboard runs each name through its HTTP-input
17
+ * validator so a bad project is a 400 with an `ApiInputError` path, while the
18
+ * engine resolves names that came out of the PRD itself against the live
19
+ * config. Injecting it keeps those two contracts intact without duplicating
20
+ * the orchestration around them.
21
+ */
22
+
23
+ const shared = require('../core/shared');
24
+ const qaSessions = require('./sessions');
25
+ const qaFromPrd = require('./from-prd');
26
+
27
+ /**
28
+ * Thrown when the resolver rejects a project. Carries `.reason` so callers can
29
+ * surface the same 400 body the inline implementation produced.
30
+ */
31
+ class QaPrdSessionError extends Error {
32
+ constructor(reason, extra = {}) {
33
+ super(reason);
34
+ this.name = 'QaPrdSessionError';
35
+ this.reason = reason;
36
+ if (extra && typeof extra === 'object') {
37
+ for (const [k, v] of Object.entries(extra)) {
38
+ if (k !== 'reason' && k !== 'name') this[k] = v;
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Name-based classifier, for the same reason `from-prd.js#isQaFromPrdError`
46
+ * exists: this module is re-required per isolated test root, so `instanceof`
47
+ * across a module boundary is not a reliable way to recognise the error.
48
+ */
49
+ function isQaPrdSessionError(err) {
50
+ return !!err && err.name === 'QaPrdSessionError' && typeof err.reason === 'string';
51
+ }
52
+
53
+ /**
54
+ * Rewrite the spec's project field(s) to the CANONICAL names the resolver
55
+ * produced, preserving the single-string vs array shape validateSpec expects.
56
+ * Extracted verbatim from dashboard.js#_qaCanonicalizeResolvedProjects.
57
+ *
58
+ * W-msb9kgs402813a97-a — `primaryProject` and the `projectTargets` keys name
59
+ * projects too, so they are canonicalized through the SAME resolver output.
60
+ * Without this, a spec that named `"Web"` would end up with
61
+ * `projects: ["web"]` but `primaryProject: "Web"`, and validateSpec would
62
+ * reject a spec the caller wrote correctly.
63
+ */
64
+ function canonicalizeResolvedProjects(spec, resolution) {
65
+ const normalized = { ...spec };
66
+ const targets = Array.isArray(resolution && resolution.targets) ? resolution.targets : [];
67
+ // Both resolvers stamp `requested` (the name the caller wrote) next to the
68
+ // canonical `project`, so the mapping survives de-duplication and the
69
+ // primary-first reordering below. The case-insensitive fallback covers
70
+ // callers that hand back bare `{ project, scope }` targets.
71
+ const byRequested = new Map();
72
+ const byLowerCanonical = new Map();
73
+ for (const target of targets) {
74
+ if (!target || !target.project) continue;
75
+ if (typeof target.requested === 'string' && target.requested) {
76
+ byRequested.set(target.requested, target.project);
77
+ }
78
+ byLowerCanonical.set(String(target.project).toLowerCase(), target.project);
79
+ }
80
+ const canonical = (name) => byRequested.get(name)
81
+ || byLowerCanonical.get(String(name).trim().toLowerCase())
82
+ || name;
83
+
84
+ if (Array.isArray(normalized.projects) && normalized.projects.length > 0) {
85
+ normalized.projects = targets.map(target => target.project);
86
+ } else if (targets.length === 1 && targets[0].project) {
87
+ normalized.project = targets[0].project;
88
+ }
89
+ if (typeof normalized.primaryProject === 'string' && normalized.primaryProject) {
90
+ normalized.primaryProject = canonical(normalized.primaryProject);
91
+ }
92
+ if (normalized.projectTargets && typeof normalized.projectTargets === 'object'
93
+ && !Array.isArray(normalized.projectTargets)) {
94
+ const rekeyed = {};
95
+ for (const [name, target] of Object.entries(normalized.projectTargets)) {
96
+ rekeyed[canonical(name)] = target;
97
+ }
98
+ normalized.projectTargets = rekeyed;
99
+ }
100
+ return normalized;
101
+ }
102
+
103
+ /**
104
+ * Move the target that hosts `spec.primaryProject` to the front and stamp a
105
+ * `primary` boolean on every entry. Shared by both resolvers so the HTTP and
106
+ * engine paths hand `queueSetup` an identically-ordered list.
107
+ *
108
+ * A `primaryProject` naming a project that was not resolved is left alone —
109
+ * `qaSessions.validateSpec` owns that rejection and produces the actionable
110
+ * error; silently reordering around an unknown name would hide it.
111
+ */
112
+ function orderTargetsByPrimary(spec, targets) {
113
+ const requestedPrimary = spec && typeof spec.primaryProject === 'string' ? spec.primaryProject : '';
114
+ const ordered = targets.slice();
115
+ if (requestedPrimary) {
116
+ const lower = requestedPrimary.trim().toLowerCase();
117
+ const index = ordered.findIndex(t => t
118
+ && (t.requested === requestedPrimary || String(t.project || '').toLowerCase() === lower));
119
+ if (index > 0) ordered.unshift(ordered.splice(index, 1)[0]);
120
+ }
121
+ return ordered.map((target, index) => ({ ...target, primary: index === 0 }));
122
+ }
123
+
124
+ /**
125
+ * Engine-side project resolver — the non-HTTP counterpart to the dashboard's
126
+ * `_qaSessionsResolveTargets`. Walks the same `shared.resolveProjectSource`
127
+ * seam `resolveWorkItemsCreateTarget` uses (including `defaultWhenSingle`, so a
128
+ * single-project install still binds a project when the PRD names none) and
129
+ * returns `{ targets }` or `{ error }`.
130
+ *
131
+ * Duplicates are DROPPED rather than rejected: unlike an operator-authored HTTP
132
+ * body, a PRD's project list is derived from its items, so a repeat is expected
133
+ * shape rather than a caller bug.
134
+ */
135
+ function resolveTargetsFromConfig(spec, config) {
136
+ const projects = shared.getProjects(config);
137
+ const list = Array.isArray(spec && spec.projects) && spec.projects.length > 0
138
+ ? spec.projects.slice()
139
+ : [spec && spec.project ? spec.project : ''];
140
+ const targets = [];
141
+ const seen = new Set();
142
+ for (const name of list) {
143
+ const resolved = shared.resolveProjectSource(name, projects, {
144
+ defaultWhenSingle: true,
145
+ minionsDir: shared.MINIONS_DIR,
146
+ });
147
+ if (resolved.error) return { error: `project "${name || '(central)'}": ${resolved.error}` };
148
+ const projectName = resolved.project ? resolved.project.name : null;
149
+ const key = projectName || '__central__';
150
+ if (seen.has(key)) continue;
151
+ seen.add(key);
152
+ targets.push({ scope: resolved.scope, project: projectName, requested: name });
153
+ }
154
+ return { targets: orderTargetsByPrimary(spec, targets) };
155
+ }
156
+
157
+ /**
158
+ * Build a QA Session from a PRD and queue its SETUP phase.
159
+ *
160
+ * @param {string|object} prdInput PRD filename (under prd/) or parsed PRD object.
161
+ * @param {object} opts
162
+ * @param {string} [opts.mode] 'confirm' (default) | 'auto'
163
+ * @param {string} [opts.runner] explicit kebab-case runner
164
+ * @param {string} [opts.createdBy] audit identity stamped on the session
165
+ * @param {object} [opts.provenanceExtra] merged into the builder's provenance
166
+ * @param {Function} opts.resolveTargets (spec) => { targets } | { error }
167
+ * @returns {{ session, sessionId, setupWorkItemId, resolution, spec, warnings, provenance, flowSource }}
168
+ * @throws {qaFromPrd.QaFromPrdError} unreadable/malformed/non-QA-ready PRD, bad runner
169
+ * @throws {QaPrdSessionError} project resolution rejected the spec
170
+ * @throws {Error} createSession/queueSetup rejection (`.sessionId`
171
+ * is stamped when the session already exists)
172
+ */
173
+ function createQaSessionFromPrd(prdInput, opts = {}) {
174
+ const options = opts && typeof opts === 'object' ? opts : {};
175
+ if (typeof options.resolveTargets !== 'function') {
176
+ throw new TypeError('qa/prd-session: createQaSessionFromPrd requires opts.resolveTargets');
177
+ }
178
+
179
+ const built = qaFromPrd.buildQaSessionSpecFromPrd(prdInput, {
180
+ mode: options.mode,
181
+ runner: options.runner,
182
+ });
183
+ const { spec, warnings, provenance } = built;
184
+
185
+ let createSpec = { ...spec };
186
+ if (typeof options.createdBy === 'string' && options.createdBy) {
187
+ createSpec.createdBy = options.createdBy;
188
+ }
189
+ // Provenance rides alongside the spec → persisted on the session record by
190
+ // createSession (P-c5a2f8b7). Not part of the validateSpec contract.
191
+ createSpec.provenance = options.provenanceExtra && typeof options.provenanceExtra === 'object'
192
+ ? { ...provenance, ...options.provenanceExtra }
193
+ : provenance;
194
+
195
+ // Resolve EACH project BEFORE persisting — one bad project rejects the whole
196
+ // submit rather than stranding a half-created session.
197
+ const resolution = options.resolveTargets(createSpec);
198
+ if (resolution && resolution.error) {
199
+ throw new QaPrdSessionError(resolution.error, { kind: 'project' });
200
+ }
201
+ if (!resolution || !Array.isArray(resolution.targets) || resolution.targets.length === 0) {
202
+ throw new QaPrdSessionError('QA project resolution produced no targets', { kind: 'project' });
203
+ }
204
+ createSpec = canonicalizeResolvedProjects(createSpec, resolution);
205
+
206
+ const session = qaSessions.createSession(createSpec);
207
+
208
+ let setupWorkItemId = null;
209
+ try {
210
+ setupWorkItemId = qaSessions.queueSetup(session.id, {
211
+ resolvedTargets: resolution.targets.map(t => ({ project: t.project, scope: t.scope })),
212
+ });
213
+ } catch (e) {
214
+ // The session exists but has no SETUP WI. Surface the id so the caller can
215
+ // report/inspect it instead of leaking an orphan.
216
+ e.sessionId = session.id;
217
+ e.kind = 'queue';
218
+ throw e;
219
+ }
220
+
221
+ return {
222
+ session,
223
+ sessionId: session.id,
224
+ setupWorkItemId,
225
+ resolution,
226
+ spec: createSpec,
227
+ warnings: Array.isArray(warnings) ? warnings : [],
228
+ provenance: createSpec.provenance,
229
+ flowSource: (createSpec.provenance && createSpec.provenance.flowSource) || null,
230
+ };
231
+ }
232
+
233
+ module.exports = {
234
+ createQaSessionFromPrd,
235
+ canonicalizeResolvedProjects,
236
+ orderTargetsByPrimary,
237
+ resolveTargetsFromConfig,
238
+ QaPrdSessionError,
239
+ isQaPrdSessionError,
240
+ };
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
- const apiValidation = require('./api-validation');
4
- const keepProcesses = require('./keep-process-sweep');
5
- const qaFromPrd = require('./qa-from-prd');
6
- const qaRunbooks = require('./qa-runbooks');
7
- const qaRuns = require('./qa-runs');
8
- const qaSessions = require('./qa-sessions');
9
- const shared = require('./shared');
3
+ const apiValidation = require('../api/api-validation');
4
+ const keepProcesses = require('../processes/keep-process-sweep');
5
+ const qaFromPrd = require('./from-prd');
6
+ const qaRunbooks = require('./runbooks');
7
+ const qaRuns = require('./runs');
8
+ const qaSessions = require('./sessions');
9
+ const shared = require('../core/shared');
10
10
 
11
11
  const {
12
12
  ApiInputError,
@@ -112,7 +112,7 @@ function validateObjectFields(value, allowedFields, pathName = 'body') {
112
112
 
113
113
  function validateKnownRunner(runner, pathName) {
114
114
  if (runner === undefined || runner === null || runner === '') return runner;
115
- const qaRunners = require('./qa-runners');
115
+ const qaRunners = require('../qa-runners');
116
116
  if (!qaRunners.getRunner(runner)) {
117
117
  throw new ApiInputError(`Unknown QA runner: ${runner}`, {
118
118
  code: 'unknown-qa-runner',
@@ -163,6 +163,11 @@ function validateQaSessionRequest(body, options = {}) {
163
163
  'runner',
164
164
  'project',
165
165
  'projects',
166
+ // W-msb9kgs402813a97-a — explicit multi-project contract. The allowlist
167
+ // rejects unknown body fields, so omitting these would 400 a spec that
168
+ // qaSessions.validateSpec accepts.
169
+ 'primaryProject',
170
+ 'projectTargets',
166
171
  'createdBy',
167
172
  ]);
168
173
  const result = qaSessions.validateSpec(body);
@@ -414,7 +419,7 @@ function validateRunbookId(value, pathName = 'path.id') {
414
419
  }
415
420
 
416
421
  function validateManagedSpecName(value, pathName) {
417
- const result = require('./managed-spawn').validateManagedSpecName(value);
422
+ const result = require('../processes/managed-spawn').validateManagedSpecName(value);
418
423
  if (!result.ok) {
419
424
  throw new ApiInputError('Invalid managed process name', {
420
425
  code: 'invalid-managed-process-name',
@@ -17,7 +17,7 @@
17
17
 
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
- const shared = require('./shared');
20
+ const shared = require('../core/shared');
21
21
 
22
22
  const RUNBOOKS_DIR = 'runbooks';
23
23