@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,27 +1,27 @@
1
1
  /**
2
- * engine/playbook.js — Playbook rendering, system prompt building, agent context,
2
+ * engine/agents/playbook.js — Playbook rendering, system prompt building, agent context,
3
3
  * task context resolution, and repo-host helpers.
4
4
  * Extracted from engine.js.
5
5
  */
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
- const shared = require('./shared');
10
- const queries = require('./queries');
11
- const { wrapUntrusted, buildSource } = require('./untrusted-fence');
12
- const { MINIONS_BRAND_URL } = require('./comment-format');
13
- const features = require('./features');
14
- const memoryRetrieval = require('./memory-retrieval');
15
- const memoryStore = require('./memory-store');
9
+ const shared = require('../core/shared');
10
+ const queries = require('../core/queries');
11
+ const { wrapUntrusted, buildSource } = require('../core/untrusted-fence');
12
+ const { MINIONS_BRAND_URL } = require('../providers/comment-format');
13
+ const features = require('../core/features');
14
+ const memoryRetrieval = require('../memory/memory-retrieval');
15
+ const memoryStore = require('../memory/memory-store');
16
16
 
17
17
  const { safeJsonArr, safeRead, getProjects, log, ts, dateStamp, truncateTextBytes, ENGINE_DEFAULTS, WI_STATUS, WORK_TYPE, PR_STATUS, DISPATCH_RESULT, getProjectOrg } = shared;
18
18
  const { getConfig, getDispatch, getNotes, getPrs, getKnowledgeBaseIndex, AGENTS_DIR } = queries;
19
19
 
20
20
  const MINIONS_DIR = shared.MINIONS_DIR;
21
- const PLAYBOOKS_DIR = path.join(MINIONS_DIR, 'playbooks');
21
+ const PLAYBOOKS_DIR = shared.resolveDefinitionDir('playbooks');
22
22
 
23
23
  // Import tempAgents from routing module
24
- const { tempAgents } = require('./routing');
24
+ const { tempAgents } = require('../orchestration/routing');
25
25
 
26
26
  // ─── Repo Host Helpers ──────────────────────────────────────────────────────
27
27
 
@@ -70,7 +70,7 @@ function getPrCreateInstructions(project, config) {
70
70
  `- Use --head to specify your feature branch name\n` +
71
71
  (createPrsAsDraft ? `- Open the PR as a DRAFT: include the \`--draft\` flag exactly as shown above (the operator has "Create PRs as draft" enabled)\n` : '') +
72
72
  `- Include a meaningful --title and body file describing the changes\n` +
73
- `- If this PR contains a meaningful VISUAL/UI change (dashboard pages, slim-ux, Settings, work-item/PR/plan views, badges, layout/CSS), proactively capture + EMBED screenshots in the body — BEFORE/AFTER for fixes, AFTER-only for new UI. Publish PNGs as assets on a per-PR prerelease (\`gh release create pr-<num>-visual … --prerelease\`, or \`gh release upload … --clobber\` on re-dispatch) with a per-command \`GH_TOKEN\` for the ${org} account (NEVER \`gh auth switch\`), resolve each \`browser_download_url\`, and reference it as \`![view (after)](url)\`. Never \`git add\` a PNG; skip + record on failure. Full recipe: the "Screenshot handling" section of the PR description audit.\n` +
73
+ `- If this PR contains a meaningful VISUAL/UI change (dashboard pages, slim-ux, Settings, work-item/PR/plan views, badges, layout/CSS), proactively capture + EMBED screenshots in the body — BEFORE/AFTER for fixes, AFTER-only for new UI. Publish PNGs as assets on a per-PR prerelease (\`gh release create pr-<num>-visual … --prerelease\`, or \`gh release upload … --clobber\` on re-dispatch) with a per-command \`GH_TOKEN\` for the ${org} account (NEVER \`gh auth switch\`), resolve each \`browser_download_url\`, and reference it as \`![view (after)](url)\`. Never \`git add\` a PNG; skip + record on failure. Full recipe: the "Visual evidence handling" section of the PR description audit.\n` +
74
74
  PR_ATTRIBUTION_INSTRUCTION;
75
75
  }
76
76
  // Default: Azure DevOps — prefer `az` CLI first, ADO REST API as fallback
@@ -85,7 +85,7 @@ function getPrCreateInstructions(project, config) {
85
85
  `- Use \`@<file>\` syntax for \`--description\` so Markdown, quotes, and newlines pass safely\n` +
86
86
  `- Always set --target-branch to \`${mainBranch}\` (the main branch)\n` +
87
87
  (createPrsAsDraft ? `- Open the PR as a DRAFT: include \`--draft true\` exactly as shown above (the operator has "Create PRs as draft" enabled). For the REST API fallback, set \`"isDraft": true\` in the request body\n` : '') +
88
- `- If this PR contains a meaningful VISUAL/UI change (dashboard pages, slim-ux, Settings, work-item/PR/plan views, badges, layout/CSS), proactively capture + EMBED screenshots in the description — BEFORE/AFTER for fixes, AFTER-only for new UI. Upload via \`PUT .../pullRequests/<id>/attachments/<filename>\` with the PNG bytes and reference the returned URL. Save PNGs under \`agents/<id>/screenshots/\` only; never \`git add\` a PNG; skip + record on failure. Full recipe: the "Screenshot handling" section of the PR description audit\n` +
88
+ `- If this PR contains a meaningful VISUAL/UI change (dashboard pages, slim-ux, Settings, work-item/PR/plan views, badges, layout/CSS), proactively capture + EMBED screenshots in the description — BEFORE/AFTER for fixes, AFTER-only for new UI. Upload via \`PUT .../pullRequests/<id>/attachments/<filename>\` with the PNG bytes and reference the returned URL. Save PNGs under \`agents/<id>/screenshots/\` only; never \`git add\` a PNG; skip + record on failure. Full recipe: the "Visual evidence handling" section of the PR description audit\n` +
89
89
  PR_ATTRIBUTION_INSTRUCTION + `\n` +
90
90
  `\n` +
91
91
  `If \`az\` is unavailable, fall back to the ADO REST API: get a token with \`az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv\` and \`POST .../_apis/git/repositories/${repoId}/pullrequests?api-version=7.1\` (Bearer auth). Do not use \`gh\` for Azure DevOps repositories.`;
@@ -165,7 +165,7 @@ function getPrVoteInstructions(project, config) {
165
165
  "<your review body here>\n" +
166
166
  "```\n" +
167
167
  `Then run: \`gh pr review <number> --comment --body-file <verdict.md> --repo ${org}/${repo}\`. Do NOT use \`--approve\` or \`--request-changes\` flags — they will fail.\n\n` +
168
- `**Brand link:** whenever the body names "Minions" (e.g. the \`Reviewed by Minions (…)\` sign-off), render it as \`[Minions](${MINIONS_BRAND_URL})\` per the shared rules. On the GitHub path the engine also auto-links the first bare \`… by Minions\` trailer as a backstop (\`engine/comment-format.js#linkifyBrandTrailer\`), but write the link yourself so the first occurrence is correct.`;
168
+ `**Brand link:** whenever the body names "Minions" (e.g. the \`Reviewed by Minions (…)\` sign-off), render it as \`[Minions](${MINIONS_BRAND_URL})\` per the shared rules. On the GitHub path the engine also auto-links the first bare \`… by Minions\` trailer as a backstop (\`engine/providers/comment-format.js#linkifyBrandTrailer\`), but write the link yourself so the first occurrence is correct.`;
169
169
  }
170
170
  // Azure DevOps — prefer `az` CLI first, ADO REST API as fallback.
171
171
  // The platform reviewer vote is a POSITIVE mutation gated by autoApplyReviewVote
@@ -342,6 +342,7 @@ const PLAYBOOK_OPTIONAL_VARS = new Set([
342
342
  'acceptance_criteria', // only set when item.acceptanceCriteria has entries
343
343
  'checkpoint_context', // only set when resuming from a prior timeout
344
344
  'retry_context', // only set when retrying after a validation-gate failure (#893)
345
+ 'revision_feedback', // only set when a plan-to-prd run is an operator revision (W-msa0mrus00mh466f)
345
346
  // Host-specific identifiers — always one of {ado_*, github_*} is empty
346
347
  // (project is either ADO or GitHub, never both). Keeping them as warns
347
348
  // produces ~96 noise lines per day on a single-host project.
@@ -417,6 +418,24 @@ const PLAYBOOK_OPTIONAL_VARS = new Set([
417
418
  'role', // 'primary' | 'co-service'
418
419
  'primary_project', // canonical primary project name; empty on single-project sessions
419
420
  'co_services_json', // JSON-encoded co-service project names (e.g. ["api","worker"]); empty when none
421
+ // W-msb9kgs402813a97-a — every managed-spawn the session owns, as
422
+ // [{name, project, primary, health, baseUrl, ports}]. DRAFT/EXECUTE address
423
+ // co-service origins through this; `managed_spawn_name` stays the primary.
424
+ // Empty string for non-QA-session dispatches.
425
+ 'session_services_json',
426
+ // W-msb9kgs402813a97-b — absolute path of the DRAFT visual-journey manifest
427
+ // sidecar (agents/<id>/qa-session-draft-result.json). Populated only for the
428
+ // DRAFT phase; SETUP/EXECUTE and non-QA dispatches render ''.
429
+ 'qa_draft_result_sidecar',
430
+ // W-msb9kgs402813a97-c — the DRAFT-accepted visual-journey manifest projected
431
+ // into the qa-run-result coverage-entry shape
432
+ // ([{journeyId, name, kind, projects, services}]). EXECUTE's evidence gate
433
+ // grades journeyCoverage against these ids verbatim, so the EXECUTE prompt
434
+ // has to carry them — the DRAFT sidecar that delivered them is consumed and
435
+ // unlinked, and the accepted manifest lives only on the session record.
436
+ // Populated ONLY for the EXECUTE phase of a session that persisted a
437
+ // manifest; SETUP/DRAFT, logs-only, legacy, and non-QA dispatches render ''.
438
+ 'visual_journey_manifest_json',
420
439
  // M006 — typed handoff envelope fields. Set on a follow-up dispatch meta
421
440
  // when lifecycle.js queues the item after a completing agent so the
422
441
  // receiving agent has explicit context about who handed off and why.
@@ -430,6 +449,12 @@ const PLAYBOOK_OPTIONAL_VARS = new Set([
430
449
  // when no prior explore WI exists; the {{#prior_explore_context}} conditional
431
450
  // in fix.md renders the section only when non-empty.
432
451
  'prior_explore_context',
452
+ // W-ms5tb6ha016fda10 — declared execution surface for this dispatch.
453
+ // 'control-plane' when the work item validly opted out of a repository
454
+ // checkout; empty/'repo' otherwise. Playbooks don't reference it directly —
455
+ // renderPlaybook uses it to append the control-plane hint and to relax the
456
+ // project_path requirement.
457
+ 'execution_surface',
433
458
  ]);
434
459
 
435
460
 
@@ -444,7 +469,11 @@ const PLAYBOOK_REQUIRED_VARS = {
444
469
  'explore': ['task_description'],
445
470
  'ask': ['question'],
446
471
  'plan': ['task_description', 'project_path'],
447
- 'plan-to-prd': ['plan_content', 'plan_file', 'prd_filename', 'project_path'],
472
+ // W-ms9tcry701xz0389 `item_id` is required because the sidecar envelope
473
+ // binds itself to the work item via {{item_id}}. An unrendered token there
474
+ // would produce a permanently unimportable result, so fail the render loudly
475
+ // instead of shipping a poisoned contract to the agent.
476
+ 'plan-to-prd': ['plan_content', 'plan_file', 'prd_filename', 'project_path', 'item_id'],
448
477
  'decompose': ['item_id', 'item_description', 'project_path'],
449
478
  'verify': ['task_description'],
450
479
  'test': ['item_name'],
@@ -514,7 +543,16 @@ function validatePlaybookVars(playbookName, vars) {
514
543
  const required = PLAYBOOK_REQUIRED_VARS[playbookName];
515
544
  if (!required) return { valid: true, missing: [] };
516
545
 
517
- const missing = required.filter(key => {
546
+ // W-ms5tb6ha016fda10 a control-plane dispatch has no repository checkout,
547
+ // so `project_path` is legitimately empty. Requiring it would reject exactly
548
+ // the maintenance jobs the surface exists to make cheap. Every other
549
+ // required var still applies.
550
+ const controlPlane = vars?.execution_surface === 'control-plane';
551
+ const effective = controlPlane
552
+ ? required.filter(key => key !== 'project_path' && key !== 'worktree_path')
553
+ : required;
554
+
555
+ const missing = effective.filter(key => {
518
556
  const val = vars[key];
519
557
  return val === undefined || val === null || String(val).trim() === '';
520
558
  });
@@ -643,7 +681,7 @@ function buildQaValidateContextBlock({ runId, runbook, target, artifactsDir, res
643
681
  lines.push('');
644
682
  }
645
683
 
646
- lines.push(`Use this context to execute the runbook against the live target. Write the result sidecar to \`${resultSidecar || 'the injected qa_result_sidecar path'}\` before exit — the engine consumes it in \`engine/lifecycle.js\` and calls \`qaRuns.completeRun(runId, ...)\`.`);
684
+ lines.push(`Use this context to execute the runbook against the live target. Write the result sidecar to \`${resultSidecar || 'the injected qa_result_sidecar path'}\` before exit — the engine consumes it in \`engine/orchestration/lifecycle.js\` and calls \`qaRuns.completeRun(runId, ...)\`.`);
647
685
 
648
686
  return lines.join('\n');
649
687
  }
@@ -872,7 +910,29 @@ function renderPlaybook(type, vars) {
872
910
  applicable.text,
873
911
  buildSource('review-learning', { item: vars.item_id || vars.task_id || 'unknown' })
874
912
  );
875
- inertAppendices.push('\n\n---\n\n## Applicable Prior Review Lessons (task-selected; verify against live code)\n\n' + (fenced || applicable.text));
913
+ // #3 pre-PR checklist: for dispatches that open or update a PR (every
914
+ // non read-only work type), frame the recalled lessons as an explicit
915
+ // gate the agent must clear before finalizing the PR, and tie each one
916
+ // back to the existing reviewLearningApplications completion contract so
917
+ // the confirmation is auditable. Read-only dispatches (ask/explore/plan/
918
+ // meeting) keep the informational framing — they open no PR to gate.
919
+ // The directive is trusted Minions instruction and stays OUTSIDE the
920
+ // untrusted fence; only the lesson bodies are fenced.
921
+ const recallType = String(vars.work_type || type || '').toLowerCase();
922
+ const prAuthoringDispatch = !shared.READ_ONLY_ROOT_TASK_TYPES.has(recallType);
923
+ const checklistDirective = prAuthoringDispatch
924
+ ? 'Treat the lessons below as a **pre-PR checklist**: each is a review finding raised on a '
925
+ + 'prior pull request for related code. Before you open or update this PR, confirm every one is '
926
+ + 'addressed in your current changes (or does not apply), verifying each against the live code — '
927
+ + 'do not assume a lesson still holds. In your completion report, record one '
928
+ + '`reviewLearningApplications` entry per lesson with outcome `applied`, '
929
+ + '`considered-not-applicable`, or `contradicted` and brief evidence.\n\n'
930
+ : '';
931
+ inertAppendices.push(
932
+ '\n\n---\n\n## Applicable Prior Review Lessons (task-selected; verify against live code)\n\n'
933
+ + checklistDirective
934
+ + (fenced || applicable.text)
935
+ );
876
936
  }
877
937
  } catch (e) {
878
938
  log('warn', `Review-learning recall failed for ${vars.item_id || vars.task_id || type}: ${e.message}`);
@@ -925,7 +985,7 @@ function renderPlaybook(type, vars) {
925
985
  // capability flag to avoid double-injection. Orthogonal to each runtime's
926
986
  // native AGENTS.md discovery (CLAUDE.md is never read by Copilot/Codex, so
927
987
  // there is nothing to conflict with). Bounded discovery (nearest-applicable
928
- // walk-up, byte-capped) lives in engine/claude-md-context.js. Degrades
988
+ // walk-up, byte-capped) lives in engine/agents/claude-md-context.js. Degrades
929
989
  // silently on any error.
930
990
  //
931
991
  // These resolved facts (runtime, bare mode, propagation flag, and the ACTUAL
@@ -1029,13 +1089,28 @@ function renderPlaybook(type, vars) {
1029
1089
  } catch (e) { log('warn', `harness PR-section inject failed: ${e.message}`); }
1030
1090
  }
1031
1091
 
1092
+ // W-ms5tb6ha016fda10 — control-plane execution surface. Injected only when
1093
+ // the dispatcher set vars.execution_surface = 'control-plane' from the work
1094
+ // item's validated `meta.executionSurface`. The agent has NO repository
1095
+ // checkout, so it must be told that explicitly — otherwise a maintenance job
1096
+ // wastes turns hunting for a worktree that was deliberately never allocated.
1097
+ if (vars.execution_surface === 'control-plane') {
1098
+ inertAppendices.push(buildControlPlaneHint({
1099
+ projectName: vars.project_name,
1100
+ repoName: vars.repo_name,
1101
+ repoHost: vars.repo_host,
1102
+ mainBranch: vars.main_branch,
1103
+ cwd: MINIONS_DIR,
1104
+ }));
1105
+ }
1106
+
1032
1107
  // W-mp68q6ke0010de68 — opt-in keep_processes hint. Injected only when the
1033
1108
  // dispatcher set vars.keep_processes (truthy) from the work item's
1034
1109
  // `meta.keep_processes`. Built via the keep-process-sweep module so the
1035
1110
  // copy stays in sync with the engine-side caps it actually enforces.
1036
1111
  if (vars.keep_processes) {
1037
1112
  try {
1038
- const keepProcessSweep = require('./keep-process-sweep');
1113
+ const keepProcessSweep = require('../processes/keep-process-sweep');
1039
1114
  const hint = keepProcessSweep.buildKeepProcessesHint({
1040
1115
  agentId: vars.agent_id,
1041
1116
  workItemId: vars.item_id || vars.task_id,
@@ -1052,7 +1127,7 @@ function renderPlaybook(type, vars) {
1052
1127
  // managed-spawn sidecar so the engine takes over spawn + healthcheck.
1053
1128
  if (vars.managed_spawn) {
1054
1129
  try {
1055
- const managedSpawn = require('./managed-spawn');
1130
+ const managedSpawn = require('../processes/managed-spawn');
1056
1131
  const hint = managedSpawn.buildManagedSpawnHint({
1057
1132
  agentId: vars.agent_id,
1058
1133
  workItemId: vars.item_id || vars.task_id,
@@ -1072,7 +1147,7 @@ function renderPlaybook(type, vars) {
1072
1147
  // string is returned when nothing matches, so we short-circuit on falsy.
1073
1148
  if (vars.project_name) {
1074
1149
  try {
1075
- const managedSpawn = require('./managed-spawn');
1150
+ const managedSpawn = require('../processes/managed-spawn');
1076
1151
  const liveBlock = managedSpawn.buildLiveManagedProcessesBlock({
1077
1152
  project: vars.project_name,
1078
1153
  });
@@ -1164,6 +1239,11 @@ function renderPlaybook(type, vars) {
1164
1239
  repoHost: vars.repo_host || vars.repoHost || dispatchProject.repoHost,
1165
1240
  };
1166
1241
  const repoHost = getRepoHost(renderProject);
1242
+ const liveDashboard = shared.readDashboardPortFile(MINIONS_DIR);
1243
+ const dashboardPort = liveDashboard?.port || shared.resolveDashboardPort({
1244
+ env: process.env.MINIONS_DASHBOARD_PORT,
1245
+ config: config.engine?.dashboardPort,
1246
+ }).port;
1167
1247
  const projectVars = {
1168
1248
  project_name: renderProject.name || 'Unknown Project',
1169
1249
  ado_org: repoHost === 'ado' ? (renderProject.adoOrg || 'Unknown') : '',
@@ -1179,6 +1259,7 @@ function renderPlaybook(type, vars) {
1179
1259
  pr_fetch_instructions: getPrFetchInstructions(renderProject),
1180
1260
  pr_vote_instructions: getPrVoteInstructions(renderProject, config),
1181
1261
  repo_host_label: getRepoHostLabel(renderProject),
1262
+ dashboard_port: dashboardPort,
1182
1263
  };
1183
1264
  const allVars = { ...projectVars, ...vars };
1184
1265
 
@@ -1385,7 +1466,7 @@ function buildAgentContext(agentId, config, project) {
1385
1466
  }
1386
1467
  // Also check the central SQL pull-request scope.
1387
1468
  try {
1388
- const centralPrs = require('./pull-requests-store').readPullRequestsForScope('central');
1469
+ const centralPrs = require('../persistence/pull-requests-store').readPullRequestsForScope('central');
1389
1470
  for (const pr of centralPrs.filter(pr => pr.status === PR_STATUS.ACTIVE)) {
1390
1471
  if (!allPrs.some(p => p.id === pr.id)) allPrs.push({ ...pr, _project: 'central' });
1391
1472
  }
@@ -1452,6 +1533,57 @@ function buildBaseVars(agentId, config, project) {
1452
1533
  };
1453
1534
  }
1454
1535
 
1536
+ /**
1537
+ * W-ms5tb6ha016fda10 — control-plane dispatch hint.
1538
+ *
1539
+ * Appended to the rendered prompt when the work item validly declared
1540
+ * `meta.executionSurface: 'control-plane'`. The agent runs at the Minions root
1541
+ * with NO repository checkout, so the contract has to be stated explicitly:
1542
+ * project identity is available as context, the repo is not on disk, and
1543
+ * anything that needs repository files belongs on a normal `repo` dispatch.
1544
+ *
1545
+ * Pure + platform-agnostic (no shell snippets, no hardcoded commands) so it
1546
+ * behaves identically on Windows, Linux, and macOS.
1547
+ */
1548
+ function buildControlPlaneHint({ projectName, repoName, repoHost, mainBranch, cwd } = {}) {
1549
+ const lines = [
1550
+ '## Execution surface: control-plane (no repository checkout)',
1551
+ '',
1552
+ 'This work item declared `meta.executionSurface: "control-plane"`, so the engine '
1553
+ + 'deliberately did **not** allocate a git worktree for it. Your working directory is the '
1554
+ + 'Minions root (`' + (cwd || '') + '`), not a checkout of the target repository.',
1555
+ '',
1556
+ '**What that means**',
1557
+ '',
1558
+ '- The target repository is **not** on disk for this dispatch. Do not look for a worktree, '
1559
+ + 'do not `git clone`, and do not run git commands against the operator\'s checkout.',
1560
+ '- Do your work through the Minions dashboard API and the platform CLIs you already have '
1561
+ + '(`gh` for GitHub, `az` for Azure DevOps). Those reach the remote directly and need no '
1562
+ + 'local checkout.',
1563
+ '- Do not create a branch, commit, or open a PR from this dispatch — there is nothing to '
1564
+ + 'commit from.',
1565
+ ];
1566
+ const context = [];
1567
+ if (projectName && projectName !== 'Unknown Project') context.push(`project \`${projectName}\``);
1568
+ if (repoName && repoName !== 'Unknown') context.push(`repo \`${repoName}\``);
1569
+ if (repoHost) context.push(`host \`${repoHost}\``);
1570
+ if (mainBranch) context.push(`main branch \`${mainBranch}\``);
1571
+ if (context.length) {
1572
+ lines.push(
1573
+ '',
1574
+ '**Project context** (carried without a checkout): ' + context.join(', ') + '.',
1575
+ );
1576
+ }
1577
+ lines.push(
1578
+ '',
1579
+ 'If you discover the task genuinely requires reading or changing repository files, **stop** '
1580
+ + 'and report that in your completion summary instead of improvising a checkout. The work '
1581
+ + 'item should be re-declared as `executionSurface: "repo"` so the engine allocates an '
1582
+ + 'isolated worktree for it.',
1583
+ );
1584
+ return lines.join('\n');
1585
+ }
1586
+
1455
1587
  function selectPlaybook(workType, item) {
1456
1588
  // W-mpeiwz6k0005bf34-c — explicit playbook override via item.meta.playbook.
1457
1589
  // Used by /api/qa/runbooks/run to route a `test`-type work item to the
@@ -1523,6 +1655,7 @@ module.exports = {
1523
1655
  resolvePlaybookPath,
1524
1656
  renderPlaybook,
1525
1657
  validatePlaybookVars,
1658
+ buildControlPlaneHint, // W-ms5tb6ha016fda10 — exported for testing
1526
1659
  PLAYBOOK_REQUIRED_VARS,
1527
1660
  PLAYBOOK_OPTIONAL_VARS,
1528
1661
  _parsePlaybookFrontmatter,
@@ -1,6 +1,6 @@
1
1
  /**
2
- * engine/pooled-agent-process.js — child_process-shaped facade over a leased
3
- * engine/agent-worker-pool.js worker (P-1d8f0b93).
2
+ * engine/agents/pooled-agent-process.js — child_process-shaped facade over a leased
3
+ * engine/agents/agent-worker-pool.js worker (P-1d8f0b93).
4
4
  *
5
5
  * engine.js#spawnAgent's downstream code (stdout/stderr `.on('data', ...)`
6
6
  * handlers, the `onAgentClose` completion listener, PID-file/timeout
@@ -30,7 +30,7 @@ const { PassThrough } = require('stream');
30
30
  const {
31
31
  resolveRuntimeByCapability,
32
32
  encodePooledOutput,
33
- } = require('./runtimes');
33
+ } = require('../runtimes');
34
34
 
35
35
  const CANCEL_GRACE_MS = 1500;
36
36
  const DESCENDANT_INITIAL_SNAPSHOT_MS = 3000;
@@ -41,19 +41,21 @@ const _internals = {
41
41
  cancelGraceMs: CANCEL_GRACE_MS,
42
42
  killVerifyDelayMs: 50,
43
43
  listAllProcesses() {
44
- return require('./shared').listAllProcessesAsync();
44
+ return require('../core/shared').listAllProcessesAsync();
45
45
  },
46
46
  listProcessDescendants(pid, processes) {
47
- return require('./shared').listProcessDescendants(pid, processes);
47
+ return require('../core/shared').listProcessDescendants(pid, processes);
48
48
  },
49
49
  findProcessesWithCwdInside(cwd) {
50
- return require('./shared').findProcessesWithCwdInsideAsync(cwd);
50
+ return require('../core/shared').findProcessesWithCwdInsideAsync(cwd);
51
51
  },
52
- killByPidsImmediate(pids) {
53
- return require('./shared').killByPidsImmediate(pids);
52
+ killByPidsImmediate(pids, expectedIdentities) {
53
+ return require('../core/shared').terminateProcess('engine.pooled-agent.descendants', pids, {
54
+ expectedIdentities,
55
+ });
54
56
  },
55
57
  computeReapPlan(pids, agentId, opts) {
56
- return require('./keep-process-sweep').computeReapPlan(pids, agentId, opts);
58
+ return require('../processes/keep-process-sweep').computeReapPlan(pids, agentId, opts);
57
59
  },
58
60
  };
59
61
 
@@ -83,7 +85,7 @@ function dispatchOwnedDescendants(rootPid, baselinePids, processes) {
83
85
  class PooledAgentProcess extends EventEmitter {
84
86
  /**
85
87
  * @param {object} opts
86
- * @param {object} opts.pool - engine/agent-worker-pool.js module (or a test double)
88
+ * @param {object} opts.pool - engine/agents/agent-worker-pool.js module (or a test double)
87
89
  * @param {object} opts.runtime - resolved runtime adapter
88
90
  * @param {string} opts.dispatchId
89
91
  * @param {string} opts.cwd
@@ -472,7 +474,7 @@ class PooledAgentProcess extends EventEmitter {
472
474
  if (currentStartedAt === startedAt) pids.push(pid);
473
475
  }
474
476
  if (pids.length === 0) return true;
475
- _internals.killByPidsImmediate(pids);
477
+ _internals.killByPidsImmediate(pids, identities);
476
478
  const delayMs = Math.max(0, Number(_internals.killVerifyDelayMs) || 0);
477
479
  if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
478
480
  const processes = await _internals.listAllProcesses();
@@ -624,7 +626,7 @@ class PooledAgentProcess extends EventEmitter {
624
626
  // child_process.kill()-shaped primitive. Cancels the in-flight ACP turn and
625
627
  // releases its lease; the pool evicts the worker if that turn is not settled,
626
628
  // preventing a cancelled response from leaking into the next dispatch.
627
- // Full timeout/steering-kill cancel-vs-kill wiring in engine/timeout.js is
629
+ // Full timeout/steering-kill cancel-vs-kill wiring in engine/orchestration/timeout.js is
628
630
  // P-6e2a4c15 — this method is the primitive that work item calls into.
629
631
  kill() {
630
632
  if (this.killed) return true;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * engine/preflight.js — Prerequisite and health checks for Minions.
2
+ * engine/agents/preflight.js — Prerequisite and health checks for Minions.
3
3
  * Used by `minions init`, `minions start`, and `minions doctor`.
4
4
  *
5
5
  * Per-runtime binary + model-discovery checks (P-9e8a3f1d) — for every distinct
@@ -12,7 +12,7 @@ const fs = require('fs');
12
12
  const os = require('os');
13
13
  const path = require('path');
14
14
  const { execSync, execFileSync } = require('child_process');
15
- const shared = require('./shared');
15
+ const shared = require('../core/shared');
16
16
 
17
17
  /**
18
18
  * Resolve the Claude Code CLI binary path. Legacy helper preserved for back-
@@ -120,7 +120,7 @@ function _distinctRuntimes(config) {
120
120
  function _checkRuntimeBinary(runtimeName, { optional = false } = {}) {
121
121
  let adapter;
122
122
  try {
123
- adapter = require('./runtimes').resolveRuntime(runtimeName);
123
+ adapter = require('../runtimes').resolveRuntime(runtimeName);
124
124
  } catch (e) {
125
125
  return {
126
126
  name: `Runtime: ${runtimeName}`,
@@ -226,7 +226,7 @@ function runPreflight(opts = {}) {
226
226
  let runtimes = configuredRuntimes;
227
227
  if (opts.includeAllRegistered) {
228
228
  let registered = [];
229
- try { registered = require('./runtimes').listRuntimes(); }
229
+ try { registered = require('../runtimes').listRuntimes(); }
230
230
  catch { /* registry may be missing during partial installs */ }
231
231
  runtimes = Array.from(new Set([...configuredRuntimes, ...registered])).sort();
232
232
  }
@@ -252,7 +252,7 @@ function runPreflight(opts = {}) {
252
252
  // worktreeRoot check.
253
253
  try {
254
254
  let runtimeNames = [];
255
- try { runtimeNames = require('./runtimes').listRuntimes(); }
255
+ try { runtimeNames = require('../runtimes').listRuntimes(); }
256
256
  catch { /* registry may be missing during partial installs */ }
257
257
  const warns = shared.runtimeConfigWarnings(opts.config, runtimeNames);
258
258
  for (const w of warns) {
@@ -358,7 +358,7 @@ function _fleetSummaryResults(config, { dispatch } = {}) {
358
358
  if (!config || typeof config !== 'object') return results;
359
359
  const engine = config.engine || {};
360
360
  const defaultCli = engine.defaultCli ? String(engine.defaultCli) : 'copilot';
361
- const display = require('./queries').resolveRuntimeModelDisplay({
361
+ const display = require('../core/queries').resolveRuntimeModelDisplay({
362
362
  runtime: defaultCli,
363
363
  configuredModel: engine.defaultModel,
364
364
  dispatch,
@@ -398,7 +398,7 @@ async function _modelDiscoveryResults(config) {
398
398
  let md;
399
399
  try { md = require('./model-discovery'); } catch { return results; }
400
400
  let registry;
401
- try { registry = require('./runtimes'); } catch { return results; }
401
+ try { registry = require('../runtimes'); } catch { return results; }
402
402
  const fleetDisabled = config.engine && config.engine.disableModelDiscovery === true;
403
403
  const runtimes = _distinctRuntimes(config);
404
404
  for (const runtimeName of runtimes) {
@@ -521,7 +521,7 @@ function _bypassFlagResults(config) {
521
521
  const results = [];
522
522
  if (!config || typeof config !== 'object') return results;
523
523
  let registry;
524
- try { registry = require('./runtimes'); } catch { return results; }
524
+ try { registry = require('../runtimes'); } catch { return results; }
525
525
  const runtimes = _distinctRuntimes(config);
526
526
  for (const runtimeName of runtimes) {
527
527
  let adapter;
@@ -665,7 +665,7 @@ function doctor(minionsHome) {
665
665
  // for hours. Only flagged when installed AND broken; not-installed is a
666
666
  // neutral note since the watchdog is opt-in.
667
667
  try {
668
- const watchdog = require(path.join(minionsHome, 'engine', 'watchdog'));
668
+ const watchdog = require(path.join(minionsHome, 'engine', 'recovery', 'watchdog'));
669
669
  const wh = watchdog.health({ minionsHome });
670
670
  if (!wh.installed) {
671
671
  runtimeResults.push({ name: 'Watchdog', ok: 'warn', message: wh.message });
@@ -698,6 +698,25 @@ function doctor(minionsHome) {
698
698
  const bypassResults = _bypassFlagResults(preflightConfig);
699
699
  runtimeResults.push(...bypassResults);
700
700
 
701
+ // Constellation companion presence (P-e93c85a1). Read-only and bounded:
702
+ // a marker read plus, at most, one loopback health request with an explicit
703
+ // timeout. Reporting only — Minions never downloads, executes, or installs
704
+ // Constellation, and an absent/incompatible companion is always a `warn`
705
+ // so `minions doctor` keeps its exit code on hosts without Constellation.
706
+ // Suppressed entirely (no row at all) by the documented opt-out.
707
+ try {
708
+ const companion = require('../api/companion');
709
+ const companionResult = await companion.probeCompanion({ config: preflightConfig });
710
+ const companionRow = companion.companionDoctorResult(companionResult);
711
+ if (companionRow) runtimeResults.push(companionRow);
712
+ } catch (err) {
713
+ runtimeResults.push({
714
+ name: 'Constellation companion',
715
+ ok: 'warn',
716
+ message: `companion-unknown — probe unavailable: ${(err && err.message) || err} (see docs/constellation-bridge.md)`,
717
+ });
718
+ }
719
+
701
720
  // Print all
702
721
  const allResults = [...results, ...runtimeResults];
703
722
  const ok = printPreflight(allResults, { label: 'Minions Doctor' });
@@ -861,7 +880,7 @@ function runHarnessDoctor(minionsHome, opts = {}) {
861
880
  const out = opts.out || (line => console.log(line));
862
881
 
863
882
  let registry;
864
- try { registry = require('./runtimes'); }
883
+ try { registry = require('../runtimes'); }
865
884
  catch (e) {
866
885
  out(`Minions Runtime Harness Inventory`);
867
886
  out(` Could not load runtime registry: ${e.message}`);
@@ -33,16 +33,16 @@
33
33
 
34
34
  const fs = require('fs');
35
35
  const path = require('path');
36
- const { runFile, cleanChildEnv, killImmediate, killByPidsImmediate, listProcessDescendants, ts, readFileNoFollow } = require('./shared');
36
+ const { runFile, cleanChildEnv, terminateProcess, listAllProcesses, listProcessDescendants, ts, readFileNoFollow } = require('../core/shared');
37
37
  const {
38
38
  resolveRuntime,
39
39
  RUNTIME_OPTIONS_FLAG,
40
40
  RUNTIME_IMAGES_FILE_OPTION,
41
41
  decodeRuntimeOptions,
42
42
  prepareWorkspace: prepareRuntimeWorkspace,
43
- } = require('./runtimes');
44
- const { acquireAdoTokenSync, isLikelyAdoToken } = require('./ado-token');
45
- const keepProcessSweep = require('./keep-process-sweep');
43
+ } = require('../runtimes');
44
+ const { acquireAdoTokenSync, isLikelyAdoToken } = require('../ado/token');
45
+ const keepProcessSweep = require('../processes/keep-process-sweep');
46
46
  const MAX_RUNTIME_IMAGES_FILE_BYTES = 32 * 1024 * 1024;
47
47
 
48
48
  // ─── Pure helpers (exported for tests) ──────────────────────────────────────
@@ -246,7 +246,7 @@ async function _worktreeDirtyFiles(execFn, opts) {
246
246
  const raw = typeof out === 'string'
247
247
  ? out
248
248
  : (out?.stdout?.toString?.() ?? String(out ?? ''));
249
- const shared = require('./shared');
249
+ const shared = require('../core/shared');
250
250
  return String(raw)
251
251
  .split(/\r?\n/)
252
252
  .filter((line) => line.trim().length > 0)
@@ -329,7 +329,7 @@ async function assertStaleHeadOk({
329
329
  if (!cwd) throw new Error('assertStaleHeadOk: cwd is required');
330
330
  const execFn = typeof exec === 'function'
331
331
  ? exec
332
- : require('./shared').execAsync;
332
+ : require('../core/shared').execAsync;
333
333
  const opts = { ...(gitOpts || {}), cwd };
334
334
 
335
335
  // Proof of engine ownership. Absence = cannot prove current-dispatch-safe,
@@ -339,7 +339,7 @@ async function assertStaleHeadOk({
339
339
  if (typeof isEngineOwned === 'function') {
340
340
  try { return !!isEngineOwned(cwd); } catch { return false; }
341
341
  }
342
- try { return require('./shared').hasWorktreeOwnerMarker(cwd); } catch { return false; }
342
+ try { return require('../core/shared').hasWorktreeOwnerMarker(cwd); } catch { return false; }
343
343
  };
344
344
 
345
345
  const attempts = Math.max(1, Math.trunc(Number(maxRefreshAttempts) || 0));
@@ -632,7 +632,7 @@ function main() {
632
632
  deliverPromptToStdin(proc.stdin, invocation.finalPrompt, (err) => {
633
633
  console.error(`FATAL: child startup stdin delivery failed (${err.code || 'error'}): ${err.message}`);
634
634
  fs.appendFileSync(debugPath, `STDIN ERROR (${err.code || 'error'}): ${err.message}\n`);
635
- killImmediate(proc);
635
+ terminateProcess('engine.spawn-agent.stdin-failure', proc);
636
636
  process.exit(1);
637
637
  });
638
638
  } else {
@@ -649,7 +649,7 @@ function main() {
649
649
  // `--add-dir` traversal, Windows process startup) the read can land well
650
650
  // after any short timer fires, leaving Claude to bail with
651
651
  // "System prompt file not found". The exit/SIGTERM handler below — plus the
652
- // matching unlinks in engine/dispatch.js and engine/meeting.js on dispatch
652
+ // matching unlinks in engine/orchestration/dispatch.js and engine/orchestration/meeting.js on dispatch
653
653
  // completion — are sufficient to keep tmp files from leaking.
654
654
  function _cleanupSpawnTempFiles() {
655
655
  if (wantsSystemPromptFile) {
@@ -678,7 +678,7 @@ function main() {
678
678
  // that are actively working (incident: ralph-fix-mrz8ka5g0015124a pushed merge
679
679
  // commit 8b0e6ff1 and wrote a nonce-valid success report, yet the 180s timer's
680
680
  // killGracefully forced result:error). Startup-stall detection is owned by the
681
- // engine-side spawn-phase watchdog (engine/spawn-phase-watchdog.js), which
681
+ // engine-side spawn-phase watchdog (engine/agents/spawn-phase-watchdog.js), which
682
682
  // distinguishes a genuinely wedged pre-task process (idle, no live-output task
683
683
  // activity, CPU below threshold past the grace window) from a productive-but-
684
684
  // silent one (burning CPU) — preserving the invariant that a live tracked agent
@@ -698,13 +698,18 @@ function main() {
698
698
  // taskkill /T against the dead parent PID no longer finds them. We snapshot
699
699
  // the descendant tree periodically while the runtime is alive so the close
700
700
  // handler can reap any survivors by PID after the parent has gone.
701
- const trackedDescendants = new Set();
701
+ const trackedDescendants = new Map();
702
702
  let snapshotInFlight = false;
703
703
  function snapshotDescendants() {
704
704
  if (!proc.pid || snapshotInFlight) return;
705
705
  snapshotInFlight = true;
706
706
  try {
707
- for (const pid of listProcessDescendants(proc.pid)) trackedDescendants.add(pid);
707
+ const processes = listAllProcesses();
708
+ const byPid = new Map(processes.map(entry => [Number(entry.pid), entry]));
709
+ for (const pid of listProcessDescendants(proc.pid, processes)) {
710
+ const startedAt = Number(byPid.get(pid)?.startedAt);
711
+ if (startedAt > 0) trackedDescendants.set(pid, startedAt);
712
+ }
708
713
  } catch { /* best-effort */ }
709
714
  finally { snapshotInFlight = false; }
710
715
  }
@@ -782,7 +787,7 @@ function main() {
782
787
  // (engine.js:1451 sets it to agents/<agentId>/live-output.log) and
783
788
  // subtract validated, alive PIDs from the kill set. Missing or
784
789
  // invalid file → fall through to today's behavior (reap everything).
785
- let toKillPids = [...trackedDescendants];
790
+ let toKillPids = [...trackedDescendants.keys()];
786
791
  let kept = [];
787
792
  let keepRecord = null;
788
793
  let keepReason = null;
@@ -819,7 +824,13 @@ function main() {
819
824
  } else if (keepReason) {
820
825
  try { fs.appendFileSync(debugPath, `KEEP-PIDS skipped: ${keepReason}\n`); } catch {}
821
826
  }
822
- const reaped = toKillPids.length ? killByPidsImmediate(toKillPids) : 0;
827
+ const reaped = toKillPids.length
828
+ ? terminateProcess('engine.spawn-agent.descendants', toKillPids, {
829
+ expectedIdentities: new Map(
830
+ toKillPids.map(pid => [pid, trackedDescendants.get(pid)]),
831
+ ),
832
+ })
833
+ : 0;
823
834
  try { fs.appendFileSync(debugPath, `DESCENDANTS reaped=${reaped}/${toKillPids.length} kept=${kept.length}\n`); } catch {}
824
835
  }
825
836
  }