@yemi33/minions 0.1.2448 → 0.1.2449

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/bin/cli-api-client.js +1 -1
  2. package/bin/install-internal-minions.js +1382 -44
  3. package/bin/install-layout.js +150 -0
  4. package/bin/minions.js +460 -167
  5. package/dashboard/docs/typography.md +65 -12
  6. package/dashboard/js/command-center.js +58 -4
  7. package/dashboard/js/detail-panel.js +36 -0
  8. package/dashboard/js/memory-panel.js +59 -12
  9. package/dashboard/js/qa.js +179 -20
  10. package/dashboard/js/refresh.js +148 -12
  11. package/dashboard/js/render-dispatch.js +3 -4
  12. package/dashboard/js/render-inbox.js +2 -2
  13. package/dashboard/js/render-other.js +3 -3
  14. package/dashboard/js/render-pipelines.js +14 -0
  15. package/dashboard/js/render-plans.js +57 -9
  16. package/dashboard/js/render-prd.js +132 -23
  17. package/dashboard/js/render-prs.js +195 -166
  18. package/dashboard/js/render-schedules.js +63 -3
  19. package/dashboard/js/render-utils.js +3 -3
  20. package/dashboard/js/render-watches.js +19 -3
  21. package/dashboard/js/render-work-items.js +238 -30
  22. package/dashboard/js/settings.js +205 -54
  23. package/dashboard/js/utils.js +51 -1
  24. package/dashboard/pages/home.html +1 -1
  25. package/dashboard/pages/qa.html +1 -16
  26. package/dashboard/pages/work.html +40 -0
  27. package/dashboard/shared/cc-limits.js +79 -0
  28. package/dashboard/shared/pr-filters.js +21 -38
  29. package/dashboard/shared/project-git-summary.js +1 -1
  30. package/dashboard/shared/record-filters.js +169 -0
  31. package/dashboard/shared/watches-source.js +1 -1
  32. package/dashboard/shared/welcome-popup.js +1 -1
  33. package/dashboard/shared/wi-filters.js +302 -0
  34. package/dashboard/slim/body.html +1 -0
  35. package/dashboard/slim/js/command-send.js +26 -0
  36. package/dashboard/slim/js/modals-tiles.js +380 -39
  37. package/dashboard/slim/js/status.js +13 -21
  38. package/dashboard/slim/layout.html +1 -0
  39. package/dashboard/slim/panel-bootstrap.js +6 -2
  40. package/dashboard/slim/styles.css +38 -0
  41. package/dashboard/styles.css +159 -55
  42. package/dashboard-build.js +13 -2
  43. package/dashboard.js +956 -423
  44. package/docs/README.md +11 -6
  45. package/docs/api-errors.md +2 -2
  46. package/docs/architecture-review-2026-07-09.md +1 -1
  47. package/docs/architecture.excalidraw +2 -2
  48. package/docs/auto-discovery.md +18 -9
  49. package/docs/branch-derivation.md +4 -4
  50. package/docs/capture-demos.js +39 -2
  51. package/docs/ci-runner-canary.md +123 -0
  52. package/docs/claude-md-propagation.md +2 -2
  53. package/docs/cloud-agent-dispatch.md +204 -0
  54. package/docs/command-center.md +7 -7
  55. package/docs/completion-reports.md +43 -20
  56. package/docs/constants.md +10 -3
  57. package/docs/constellation-bridge.md +134 -6
  58. package/docs/constellation-style-telemetry.md +4 -4
  59. package/docs/contracts/capability-protocol.v1.json +165 -0
  60. package/docs/cooldown-merge-semantics.md +12 -12
  61. package/docs/copilot-cli-schema.md +7 -7
  62. package/docs/cross-repo-plans.md +10 -10
  63. package/docs/dead-code-audit-retractions.md +5 -5
  64. package/docs/default-branch-ci.md +173 -0
  65. package/docs/deprecated.json +31 -31
  66. package/docs/design-inbox-entries-schema.md +3 -3
  67. package/docs/design-language.md +1051 -0
  68. package/docs/design-state-storage.md +11 -11
  69. package/docs/diagnostics-crash-reports.md +9 -9
  70. package/docs/diagnostics-memory.md +5 -5
  71. package/docs/documentation-audit-2026-07-09.md +7 -7
  72. package/docs/engine-restart.md +90 -5
  73. package/docs/harness-mode.md +1 -1
  74. package/docs/internal-install.md +338 -39
  75. package/docs/kb-dedup-duplicate-pair-investigation.md +5 -5
  76. package/docs/kb-pr3223-cascade-archiving.md +1 -1
  77. package/docs/kb-pr696-merge-conflict-docs.md +6 -6
  78. package/docs/kb-sweep.md +35 -35
  79. package/docs/keep-processes.md +1 -1
  80. package/docs/live-checkout-mode.md +30 -30
  81. package/docs/managed-spawn.md +18 -14
  82. package/docs/named-agents.md +7 -7
  83. package/docs/plan-lifecycle.md +69 -2
  84. package/docs/pr-author-identity.md +114 -0
  85. package/docs/pr-auto-fix-dispatch.md +19 -4
  86. package/docs/pr-comment-followup.md +6 -6
  87. package/docs/pr-review-fix-loop.md +59 -10
  88. package/docs/process-termination.md +40 -0
  89. package/docs/proposals/repo-pool-for-live-checkout.md +13 -13
  90. package/docs/qa-runbook-lifecycle.md +367 -17
  91. package/docs/qa-runbooks.md +3 -3
  92. package/docs/rfc-completion-json.md +18 -18
  93. package/docs/runtime-adapters.md +26 -21
  94. package/docs/security.md +6 -6
  95. package/docs/self-improvement.md +4 -4
  96. package/docs/shared-lifecycle-module-map.md +473 -472
  97. package/docs/skills.md +52 -3
  98. package/docs/slim-ux/concepts.md +121 -116
  99. package/docs/specs/agent-configurability.md +18 -18
  100. package/docs/specs/agent-rename.md +18 -18
  101. package/docs/team-memory.md +38 -21
  102. package/docs/timeouts-and-liveness.md +118 -10
  103. package/docs/tutorials/01-install-and-connect.md +1 -1
  104. package/docs/watches.md +40 -39
  105. package/docs/workspace-manifests.md +4 -4
  106. package/docs/worktree-lifecycle.md +293 -14
  107. package/engine/README.md +46 -0
  108. package/engine/{ado-comment.js → ado/comment.js} +8 -8
  109. package/engine/{ado-git-auth.js → ado/git-auth.js} +4 -4
  110. package/engine/{ado.js → ado/index.js} +417 -63
  111. package/engine/{ado-status.js → ado/status.js} +6 -8
  112. package/engine/{ado-token.js → ado/token.js} +1 -1
  113. package/engine/{acp-transport.js → agents/acp-transport.js} +62 -22
  114. package/engine/{agent-worker-pool.js → agents/agent-worker-pool.js} +17 -8
  115. package/engine/{cc-worker-pool.js → agents/cc-worker-pool.js} +16 -6
  116. package/engine/{claude-md-context.js → agents/claude-md-context.js} +5 -5
  117. package/engine/{harness-context.js → agents/harness-context.js} +5 -5
  118. package/engine/{harness.js → agents/harness.js} +3 -3
  119. package/engine/{llm.js → agents/llm.js} +18 -14
  120. package/engine/{model-discovery.js → agents/model-discovery.js} +2 -2
  121. package/engine/{playbook.js → agents/playbook.js} +155 -22
  122. package/engine/{pooled-agent-process.js → agents/pooled-agent-process.js} +14 -12
  123. package/engine/{preflight.js → agents/preflight.js} +29 -10
  124. package/engine/{spawn-agent.js → agents/spawn-agent.js} +25 -14
  125. package/engine/{spawn-phase-watchdog.js → agents/spawn-phase-watchdog.js} +16 -7
  126. package/engine/{steering.js → agents/steering.js} +5 -5
  127. package/engine/{tools-inventory.js → agents/tools-inventory.js} +2 -2
  128. package/engine/{agent-api-validation.js → api/agent-api-validation.js} +2 -2
  129. package/engine/{api-validation.js → api/api-validation.js} +1 -1
  130. package/engine/api/bridge.js +787 -0
  131. package/engine/{cc-api-validation.js → api/cc-api-validation.js} +1 -1
  132. package/engine/api/companion.js +560 -0
  133. package/engine/{content-api-validation.js → api/content-api-validation.js} +2 -2
  134. package/engine/{pr-issue-validation.js → api/pr-issue-validation.js} +33 -6
  135. package/engine/{settings-validation.js → api/settings-validation.js} +32 -4
  136. package/engine/api-contracts/agent-content.js +4 -4
  137. package/engine/api-contracts/capability-manifest.js +236 -0
  138. package/engine/api-contracts/capability-protocol.js +333 -0
  139. package/engine/api-contracts/cc-ops.js +1 -1
  140. package/engine/api-contracts/config-runtime.js +5 -0
  141. package/engine/api-contracts/core.js +28 -1
  142. package/engine/api-contracts/index.js +100 -0
  143. package/engine/api-contracts/orchestration.js +18 -5
  144. package/engine/api-contracts/pull-requests.js +37 -6
  145. package/engine/api-contracts/qa-process.js +29 -6
  146. package/engine/api-contracts/work-plan-prd.js +21 -1
  147. package/engine/cloud/contract.js +212 -0
  148. package/engine/cloud/index.js +159 -0
  149. package/engine/{execution-model.js → core/execution-model.js} +1 -1
  150. package/engine/{features.js → core/features.js} +4 -4
  151. package/engine/{operator-identity.js → core/operator-identity.js} +1 -1
  152. package/engine/{queries.js → core/queries.js} +201 -36
  153. package/engine/{safe-expr.js → core/safe-expr.js} +1 -1
  154. package/engine/{shared.js → core/shared.js} +1637 -175
  155. package/engine/{stdio-timestamps.js → core/stdio-timestamps.js} +1 -1
  156. package/engine/{untrusted-fence.js → core/untrusted-fence.js} +3 -3
  157. package/engine/db/index.js +11 -2
  158. package/engine/db/migrations/002-dispatches.js +3 -3
  159. package/engine/db/migrations/003-work-items.js +1 -1
  160. package/engine/db/migrations/004-pull-requests.js +1 -1
  161. package/engine/db/migrations/006-metrics.js +1 -1
  162. package/engine/db/migrations/007-watches.js +2 -2
  163. package/engine/db/migrations/008-small-state.js +1 -1
  164. package/engine/db/migrations/009-qa.js +1 -1
  165. package/engine/db/migrations/010-pr-links.js +1 -1
  166. package/engine/db/migrations/011-remaining-state.js +1 -1
  167. package/engine/db/migrations/012-steering-deliveries.js +2 -2
  168. package/engine/db/migrations/013-backfill-broken-note-links.js +1 -1
  169. package/engine/db/migrations/014-pr-fix-target-prefs.js +2 -2
  170. package/engine/db/migrations/015-plans-prds.js +0 -0
  171. package/engine/db/migrations/018-sql-only-cutover.js +2 -2
  172. package/engine/db/migrations/021-archived-work-items.js +1 -1
  173. package/engine/db/migrations/022-global-cc-session.js +1 -1
  174. package/engine/db/migrations/023-engine-state.js +1 -1
  175. package/engine/db/migrations/025-malformed-work-item-phantoms.js +1 -1
  176. package/engine/db/migrations/027-review-learning-lifecycle.js +1 -1
  177. package/engine/db/migrations/029-repair-reused-versions.js +20 -0
  178. package/engine/db/migrations/031-pr-author-identity.js +137 -0
  179. package/engine/{consolidation.js → memory/consolidation.js} +6 -6
  180. package/engine/{kb-sweep-runner.js → memory/kb-sweep-runner.js} +2 -2
  181. package/engine/{kb-sweep.js → memory/kb-sweep.js} +9 -7
  182. package/engine/{memory-retrieval.js → memory/memory-retrieval.js} +46 -4
  183. package/engine/{memory-store.js → memory/memory-store.js} +3 -3
  184. package/engine/{promotion.js → memory/promotion.js} +3 -3
  185. package/engine/{review-learning-backfill.js → memory/review-learning-backfill.js} +6 -6
  186. package/engine/{review-learning.js → memory/review-learning.js} +10 -5
  187. package/engine/{diagnostics-memory.js → observability/diagnostics-memory.js} +1 -1
  188. package/engine/{logs-store.js → observability/logs-store.js} +5 -5
  189. package/engine/{metrics-store.js → observability/metrics-store.js} +4 -4
  190. package/engine/{check-status.js → operations/check-status.js} +3 -3
  191. package/engine/{cli.js → operations/cli.js} +271 -113
  192. package/engine/{distribution.js → operations/distribution.js} +5 -6
  193. package/engine/{cleanup.js → orchestration/cleanup.js} +72 -45
  194. package/engine/{cooldown.js → orchestration/cooldown.js} +5 -5
  195. package/engine/{dispatch-events.js → orchestration/dispatch-events.js} +2 -2
  196. package/engine/{dispatch.js → orchestration/dispatch.js} +129 -36
  197. package/engine/orchestration/failed-scheduled-cleanup.js +274 -0
  198. package/engine/{lifecycle.js → orchestration/lifecycle.js} +198 -90
  199. package/engine/{meeting.js → orchestration/meeting.js} +6 -16
  200. package/engine/{pipeline.js → orchestration/pipeline.js} +12 -12
  201. package/engine/{pre-dispatch-eval.js → orchestration/pre-dispatch-eval.js} +10 -9
  202. package/engine/{routing.js → orchestration/routing.js} +3 -3
  203. package/engine/{schedule-bootstrap.js → orchestration/schedule-bootstrap.js} +4 -4
  204. package/engine/{scheduler.js → orchestration/scheduler.js} +38 -8
  205. package/engine/{timeout.js → orchestration/timeout.js} +158 -109
  206. package/engine/{db-events.js → persistence/db-events.js} +2 -2
  207. package/engine/{dispatch-store.js → persistence/dispatch-store.js} +7 -7
  208. package/engine/{inbox-store.js → persistence/inbox-store.js} +2 -2
  209. package/engine/{note-link-backfill.js → persistence/note-link-backfill.js} +4 -4
  210. package/engine/{pr-fix-target-store.js → persistence/pr-fix-target-store.js} +8 -8
  211. package/engine/{pull-requests-store.js → persistence/pull-requests-store.js} +21 -7
  212. package/engine/{small-state-store.js → persistence/small-state-store.js} +31 -31
  213. package/engine/persistence/state-operations.js +350 -0
  214. package/engine/{steering-store.js → persistence/steering-store.js} +6 -6
  215. package/engine/{issues.js → planning/issues.js} +2 -2
  216. package/engine/{plan-prd-validation.js → planning/plan-prd-validation.js} +8 -2
  217. package/engine/planning/prd-result-sidecar.js +190 -0
  218. package/engine/{prd-store.js → planning/prd-store.js} +17 -17
  219. package/engine/{project-discovery.js → planning/project-discovery.js} +5 -5
  220. package/engine/{projects.js → planning/projects.js} +10 -10
  221. package/engine/{resolve-area.js → planning/resolve-area.js} +1 -1
  222. package/engine/{work-item-validation.js → planning/work-item-validation.js} +39 -3
  223. package/engine/{work-items-store.js → planning/work-items-store.js} +29 -21
  224. package/engine/{keep-process-sweep.js → processes/keep-process-sweep.js} +57 -17
  225. package/engine/{managed-spawn-launcher.js → processes/managed-spawn-launcher.js} +3 -3
  226. package/engine/{managed-spawn.js → processes/managed-spawn.js} +97 -46
  227. package/engine/{process-utils.js → processes/process-utils.js} +599 -55
  228. package/engine/{abandoned-pr-reconciliation.js → providers/abandoned-pr-reconciliation.js} +17 -7
  229. package/engine/{comment-classifier.js → providers/comment-classifier.js} +85 -17
  230. package/engine/{comment-format.js → providers/comment-format.js} +5 -5
  231. package/engine/{gh-comment.js → providers/gh-comment.js} +15 -15
  232. package/engine/{gh-token.js → providers/gh-token.js} +4 -4
  233. package/engine/{github.js → providers/github.js} +131 -54
  234. package/engine/{pr-action.js → providers/pr-action.js} +13 -12
  235. package/engine/{pr-clone-keep.js → providers/pr-clone-keep.js} +7 -7
  236. package/engine/{pr-devbox.js → providers/pr-devbox.js} +6 -6
  237. package/engine/{pr-fix-target.js → providers/pr-fix-target.js} +13 -13
  238. package/engine/{pr-remote-patch.js → providers/pr-remote-patch.js} +4 -4
  239. package/engine/{pr-resolve.js → providers/pr-resolve.js} +7 -7
  240. package/engine/{pr-temp-clone.js → providers/pr-temp-clone.js} +5 -5
  241. package/engine/{pr-track.js → providers/pr-track.js} +11 -13
  242. package/engine/{shared-branch-pr-reconcile.js → providers/shared-branch-pr-reconcile.js} +4 -4
  243. package/engine/qa/auto-prd-qa.js +313 -0
  244. package/engine/{qa-from-prd.js → qa/from-prd.js} +42 -12
  245. package/engine/qa/prd-session.js +240 -0
  246. package/engine/{qa-process-validation.js → qa/process-validation.js} +14 -9
  247. package/engine/{qa-runbooks.js → qa/runbooks.js} +1 -1
  248. package/engine/{qa-runs.js → qa/runs.js} +286 -15
  249. package/engine/{qa-sessions.js → qa/sessions.js} +595 -49
  250. package/engine/qa/visual-journey.js +654 -0
  251. package/engine/{qa-runners.js → qa-runners/index.js} +7 -7
  252. package/engine/qa-runners/maestro.js +3 -3
  253. package/engine/qa-runners/playwright.js +2 -2
  254. package/engine/{restart-health.js → recovery/restart-health.js} +48 -4
  255. package/engine/recovery/stop-stack.js +607 -0
  256. package/engine/{supervisor.js → recovery/supervisor.js} +105 -175
  257. package/engine/{watchdog.js → recovery/watchdog.js} +136 -13
  258. package/engine/runtimes/claude.js +14 -12
  259. package/engine/runtimes/codex.js +8 -6
  260. package/engine/runtimes/copilot.js +17 -16
  261. package/engine/{watch-actions.js → watches/actions.js} +13 -13
  262. package/engine/{watches.js → watches/index.js} +43 -32
  263. package/engine/{watches-store.js → watches/store.js} +4 -4
  264. package/engine/{create-pr-worktree.js → worktrees/create-pr.js} +1 -1
  265. package/engine/{worktree-gc.js → worktrees/gc.js} +70 -22
  266. package/engine/worktrees/inventory.js +671 -0
  267. package/engine/{live-checkout.js → worktrees/live-checkout.js} +4 -4
  268. package/engine/{worktree-pool.js → worktrees/pool.js} +2 -2
  269. package/engine/{worktree-preflight.js → worktrees/preflight.js} +1 -0
  270. package/engine/worktrees/quarantine-refs.js +173 -0
  271. package/engine.js +1137 -208
  272. package/minions.js +147 -77
  273. package/package.json +10 -6
  274. package/playbooks/_pr-description-audit.md +110 -78
  275. package/playbooks/build-fix-complex.md +2 -0
  276. package/playbooks/fix.md +16 -12
  277. package/playbooks/implement-shared.md +2 -0
  278. package/playbooks/implement.md +19 -20
  279. package/playbooks/plan-to-prd.md +18 -3
  280. package/playbooks/qa-session-draft.md +136 -1
  281. package/playbooks/qa-session-execute.md +80 -2
  282. package/playbooks/qa-session-setup.md +17 -1
  283. package/playbooks/qa-validate.md +1 -1
  284. package/playbooks/setup.md +2 -0
  285. package/playbooks/shared-rules.md +25 -32
  286. package/playbooks/templates/followup-dispatch.md +4 -3
  287. package/playbooks/verify.md +1 -1
  288. package/prompts/cc-system.md +19 -27
  289. package/watch-plugins/README.md +92 -0
  290. package/watch-plugins/ado-author-prs.js +336 -0
  291. package/watch-plugins/gh-author-prs.js +375 -0
  292. package/watch-plugins/http.js +474 -0
  293. package/watch-plugins/teams-channel.js +869 -0
  294. package/docs/dev-composite-workflow.md +0 -101
  295. package/docs/pr-screenshots/pr-886/after-single-header.png +0 -0
  296. package/docs/pr-screenshots/pr-886/before-duplicate-header.png +0 -0
  297. package/docs/pr-screenshots/pr-895/01-cancellation-reason-detail.png +0 -0
  298. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
  299. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
  300. package/docs/pr-screenshots/pr-901/projects-tab-default.png +0 -0
  301. package/docs/pr-screenshots/pr-901/projects-tab-fmf-selected.png +0 -0
  302. package/docs/pr-screenshots/pr-916/model-picker-AFTER-crop.png +0 -0
  303. package/docs/pr-screenshots/pr-916/model-picker-AFTER.png +0 -0
  304. package/docs/pr-screenshots/pr-916/model-picker-BEFORE-crop.png +0 -0
  305. package/docs/pr-screenshots/pr-916/model-picker-BEFORE.png +0 -0
  306. package/docs/pr-screenshots/pr-916/model-picker-dropdown-AFTER.png +0 -0
  307. package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
  308. package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
  309. package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
  310. package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
  311. package/docs/visual-evidence-ci.md +0 -103
  312. package/engine/bridge.js +0 -379
  313. package/engine/quarantine-refs.js +0 -103
  314. package/engine/state-operations.js +0 -178
  315. /package/engine/{steering-constraints.js → agents/steering-constraints.js} +0 -0
@@ -1,27 +1,27 @@
1
1
  /**
2
- * engine/cli.js — CLI command handlers for Minions engine.
2
+ * engine/operations/cli.js — CLI command handlers for Minions engine.
3
3
  * Extracted from engine.js to reduce monolith size.
4
4
  */
5
5
 
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
- const shared = require('./shared');
8
+ const shared = require('../core/shared');
9
9
  const { safeRead, safeJson, safeJsonArr, safeWrite, readWorkItems, readPullRequests, mutateControl, mutateWorkItems, ts, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS, REVIEW_STATUS, DISPATCH_RESULT, FAILURE_CLASS } = shared;
10
- const queries = require('./queries');
11
- const agentWorkerPool = require('./agent-worker-pool');
12
- const { resolveCommentExecutionModel } = require('./execution-model');
10
+ const queries = require('../core/queries');
11
+ const agentWorkerPool = require('../agents/agent-worker-pool');
12
+ const { resolveCommentExecutionModel } = require('../core/execution-model');
13
13
  const { getConfig, getControl, getDispatch, getAgentStatus,
14
14
  MINIONS_DIR, ENGINE_DIR, AGENTS_DIR, PLANS_DIR, PRD_DIR, CONTROL_PATH } = queries;
15
15
 
16
16
  // Lazy require — only for engine-specific functions (log, ts, tick, addToDispatch, etc.)
17
17
  let _engine = null;
18
18
  function engine() {
19
- if (!_engine) _engine = require('../engine');
19
+ if (!_engine) _engine = require('../../engine');
20
20
  return _engine;
21
21
  }
22
22
 
23
23
  let _dispatchModule = null;
24
- function dispatchModule() { if (!_dispatchModule) _dispatchModule = require('./dispatch'); return _dispatchModule; }
24
+ function dispatchModule() { if (!_dispatchModule) _dispatchModule = require('../orchestration/dispatch'); return _dispatchModule; }
25
25
 
26
26
  function spawnPendingDispatch(e, item, config) {
27
27
  return e._withSpawnAttemptLease(
@@ -81,13 +81,54 @@ function readDispatchPid(itemId) {
81
81
  catch { return null; }
82
82
  }
83
83
 
84
- function isPidAlive(pid) {
85
- try {
86
- process.kill(shared.validatePid(pid), 0);
87
- return true;
88
- } catch {
89
- return false;
84
+ // W-ms6dmv99005o26f4 — routed through the tri-state probe in engine/process-utils.js
85
+ // so an EPERM (process EXISTS, signal refused) is no longer read as death. A false
86
+ // here zeroes `agentPid` at reattach, which is the `hadPid && !agentPid` condition
87
+ // that marks a dispatch confirmed-dead and exempts it from the orphan grace window.
88
+ function isPidAlive(pid, opts = {}) {
89
+ let value;
90
+ try { value = shared.validatePid(pid); }
91
+ catch { return false; }
92
+ return shared.inspectPidLiveness(value, opts).alive;
93
+ }
94
+
95
+ // W-ms6dmv99005o26f4 — restart reattach evidence, exported for testing.
96
+ //
97
+ // Decides whether a surviving OS PID may be reattached to a dispatch, and — just
98
+ // as importantly — whether a refusal is backed by evidence. `conclusive: false`
99
+ // means "we could not observe this process", which is NOT the same as "this
100
+ // process is gone" and must never be escalated to a confirmed-dead marking.
101
+ function evaluateReattachEvidence({ pid, runtimePidObservedAt, snapshot, probes = {} } = {}) {
102
+ const inspectIdentity = probes.inspectProcessIdentity || shared.inspectProcessIdentity;
103
+ const inspectCommandLine = probes.inspectProcessCommandLineForAgent || shared.inspectProcessCommandLineForAgent;
104
+ const observedAt = Number(runtimePidObservedAt);
105
+
106
+ // Recycled-PID guard: a process that started AFTER we recorded the launch cannot
107
+ // be our agent. Only meaningful when a launch timestamp was persisted.
108
+ if (observedAt > 0) {
109
+ const identity = inspectIdentity(pid, { processes: snapshot });
110
+ if (!identity.conclusive) {
111
+ return { reattach: false, conclusive: false, reason: `PID ${pid} identity evidence unavailable (${identity.reason})` };
112
+ }
113
+ if (!shared.isProcessIdentityCompatible(identity.identity, observedAt, 2000)) {
114
+ return { reattach: false, conclusive: true, reason: `PID ${pid} identity does not match the recorded launch` };
115
+ }
116
+ }
117
+
118
+ const cmdline = inspectCommandLine(pid, { processes: snapshot });
119
+ if (!cmdline.conclusive) {
120
+ return { reattach: false, conclusive: false, reason: `PID ${pid} command-line evidence unavailable (${cmdline.reason})` };
121
+ }
122
+ if (!cmdline.match) {
123
+ return { reattach: false, conclusive: true, reason: `PID ${pid} command line does not match an agent runtime` };
90
124
  }
125
+ return { reattach: true, conclusive: true, reason: 'verified' };
126
+ }
127
+
128
+ // W-ms6dmv99005o26f4 — the single decision that turns a restart-time observation
129
+ // into an instant orphan. Exported for testing.
130
+ function shouldMarkConfirmedDeadAtRestart({ hadPid, agentPid, inconclusive } = {}) {
131
+ return !!hadPid && !agentPid && !inconclusive;
91
132
  }
92
133
 
93
134
  function summarizeActiveDispatchPids(activeItems = []) {
@@ -356,7 +397,7 @@ function _parseRuntimeFlags(args) {
356
397
  function _modelLooksIncompatible(runtime, model) {
357
398
  if (!model) return false;
358
399
  let adapter;
359
- try { adapter = require('./runtimes').resolveRuntime(runtime); }
400
+ try { adapter = require('../runtimes').resolveRuntime(runtime); }
360
401
  catch { return false; } // unknown runtime → no opinion
361
402
  if (typeof adapter.modelLooksFamiliar !== 'function') return false; // adapter doesn't claim a model namespace → no opinion
362
403
  return !adapter.modelLooksFamiliar(model);
@@ -381,7 +422,7 @@ function _applyRuntimeFlags({ cli, model, modelExplicit }) {
381
422
  // Validate the runtime name BEFORE touching disk so typos fail loudly with
382
423
  // a list of registered runtimes — same UX shape as resolveRuntime() throws.
383
424
  let registered = [];
384
- try { registered = require('./runtimes').listRuntimes(); }
425
+ try { registered = require('../runtimes').listRuntimes(); }
385
426
  catch { /* registry missing during partial install — skip validation */ }
386
427
  if (cli !== undefined && registered.length > 0 && !registered.includes(cli)) {
387
428
  const err = new Error(`Unknown CLI runtime "${cli}". Registered runtimes: ${registered.join(', ')}`);
@@ -449,7 +490,7 @@ const commands = {
449
490
 
450
491
  // Run preflight checks (warn but don't block — engine may still be useful)
451
492
  try {
452
- const { runPreflight, printPreflight } = require('./preflight');
493
+ const { runPreflight, printPreflight } = require('../agents/preflight');
453
494
  // Pass config so runtime-fleet warnings (P-3b8e5f1d) can fire pre-start.
454
495
  // getConfig() is cheap (cached); failure here is non-fatal — preflight
455
496
  // simply skips the runtime-config warnings when config is missing.
@@ -497,9 +538,9 @@ const commands = {
497
538
  // Bust require cache so npm updates are detected after minions restart
498
539
  let codeVersion = null;
499
540
  try {
500
- const pkgPath = require.resolve('../package.json');
541
+ const pkgPath = require.resolve('../../package.json');
501
542
  delete require.cache[pkgPath];
502
- codeVersion = require('../package.json').version;
543
+ codeVersion = require('../../package.json').version;
503
544
  } catch {}
504
545
  if (!codeVersion) {
505
546
  try {
@@ -509,7 +550,7 @@ const commands = {
509
550
  } catch {}
510
551
  }
511
552
  let codeCommit = null;
512
- try { codeCommit = require('child_process').execSync('git rev-parse --short HEAD', { cwd: path.resolve(__dirname, '..'), encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] }).trim(); } catch {}
553
+ try { codeCommit = require('child_process').execSync('git rev-parse --short HEAD', { cwd: path.resolve(__dirname, '..', '..'), encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] }).trim(); } catch {}
513
554
  const controlOwner = createControlOwner();
514
555
  mutateControl(() => ({
515
556
  state: 'running',
@@ -556,7 +597,7 @@ const commands = {
556
597
  // through mutateWorkItems so the repair is transactional — a no-op
557
598
  // once healed, so it's safe to run on every boot.
558
599
  try {
559
- const res = require('./note-link-backfill').runNoteLinkBackfill({ config });
600
+ const res = require('../persistence/note-link-backfill').runNoteLinkBackfill({ config });
560
601
  if (res.fixedLinks > 0) {
561
602
  e.log('info', `Repointed ${res.fixedLinks} broken note link(s) across ${res.fixedItems} work item(s)`);
562
603
  console.log(` Repaired ${res.fixedLinks} broken note link(s) in ${res.fixedItems} work item(s).`);
@@ -565,7 +606,7 @@ const commands = {
565
606
 
566
607
  // Backfill work_items.prd_item_id for rows created before FK stamping.
567
608
  try {
568
- const fk = require('./prd-store').backfillWorkItemPrdItemIds();
609
+ const fk = require('../planning/prd-store').backfillWorkItemPrdItemIds();
569
610
  const filled = (fk.byWorkItemId || 0) + (fk.bySourcePlan || 0);
570
611
  if (fk.ok && filled > 0) {
571
612
  e.log('info', `Backfilled prd_item_id on ${filled} work item(s) (${fk.byWorkItemId} by workItemId, ${fk.bySourcePlan} by sourcePlan)`);
@@ -596,7 +637,7 @@ const commands = {
596
637
  } catch (err) { e.log('warn', `workSources auto-heal failed: ${err.message}`); }
597
638
  const interval = config.engine?.tickInterval || shared.ENGINE_DEFAULTS.tickInterval;
598
639
 
599
- const { getProjects } = require('./shared');
640
+ const { getProjects } = require('../core/shared');
600
641
  const projects = getProjects(config);
601
642
  if (projects.length === 0) {
602
643
  console.log(' \x1b[33mNo projects configured.\x1b[0m Link one with: minions add <path-to-repo>');
@@ -624,6 +665,15 @@ const commands = {
624
665
  const dispatch = getDispatch();
625
666
  const activeOnStart = (dispatch.active || []);
626
667
  if (activeOnStart.length > 0) {
668
+ let processSnapshot = shared.captureProcessSnapshot();
669
+ if (!processSnapshot.ok) {
670
+ // W-ms6dmv99005o26f4 — a failed enumeration used to be swallowed into an
671
+ // empty array that then "proved" every active dispatch was gone. Log it
672
+ // loudly: the reattach verdicts below now degrade to inconclusive, which
673
+ // keeps the fleet on the normal bounded orphan grace window instead of
674
+ // reaping it on the next tick.
675
+ e.log('warn', `Restart: process enumeration unavailable (${processSnapshot.reason}) — reattach evidence will be re-probed per PID`);
676
+ }
627
677
  let reattached = 0;
628
678
  for (const item of activeOnStart) {
629
679
  const agentId = item.agent;
@@ -631,10 +681,10 @@ const commands = {
631
681
  // P-3f5b7d26 — a pooled dispatch (engine.js#spawnAgent, item.pooled
632
682
  // persisted at dispatch-time) can NEVER be reattached, regardless of
633
683
  // what the PID file says. The ACP worker child
634
- // (engine/acp-transport.js#spawnAcp) is not spawned with
684
+ // (engine/agents/acp-transport.js#spawnAcp) is not spawned with
635
685
  // `detached: true` like the cold-spawn path, and even on the rare
636
686
  // chance its OS process survived the restart, the freshly-started
637
- // engine's engine/agent-worker-pool.js always cold-starts with an
687
+ // engine's engine/agents/agent-worker-pool.js always cold-starts with an
638
688
  // empty worker set — there is no in-process handle whose stdio could
639
689
  // be reconnected to that PID. Treating a live-but-orphaned worker PID
640
690
  // as "still running" here would silently hang the dispatch forever
@@ -688,17 +738,50 @@ const commands = {
688
738
  }
689
739
  }
690
740
 
741
+ const runtimePidObservedAt = Number(item.runtimePidObservedAt);
691
742
  if (agentPid && agentPid > 0 && !isPidAlive(agentPid)) agentPid = null;
743
+ // W-ms6dmv99005o26f4 — `inconclusiveEvidence` records that we refused the
744
+ // reattach because we could not OBSERVE the process, not because we saw it
745
+ // gone. Refusing is still correct (never reattach to an unverified PID),
746
+ // but the confirmed-dead marking below must not follow from it.
747
+ let inconclusiveEvidence = false;
748
+ if (agentPid && agentPid > 0) {
749
+ const verdict = evaluateReattachEvidence({
750
+ pid: agentPid,
751
+ runtimePidObservedAt,
752
+ snapshot: processSnapshot,
753
+ });
754
+ if (!verdict.reattach) {
755
+ e.log('warn', `Reattach: refusing ${item.id} — ${verdict.reason}`);
756
+ if (!verdict.conclusive) inconclusiveEvidence = true;
757
+ agentPid = null;
758
+ }
759
+ }
692
760
  if (agentPid && !shared.isDispatchExecutionPathSafe(item, config)) {
693
761
  e.log('warn', `Reattach: refusing ${item.id} — persisted execution path is not safe under current checkout config`);
694
- try { shared.killImmediate({ pid: agentPid }); }
762
+ try {
763
+ shared.terminateProcess('engine.cli.unsafe-reattach', { pid: agentPid }, {
764
+ recordedAtMs: runtimePidObservedAt,
765
+ processes: processSnapshot,
766
+ skewMs: 2000,
767
+ });
768
+ }
695
769
  catch (err) { e.log('warn', `Reattach: failed to terminate unsafe dispatch ${item.id}: ${err.message}`); }
696
770
  agentPid = null;
697
771
  }
698
772
 
699
- // PID was found but confirmed dead — exempt from restart grace period (#869)
700
- if (hadPid && !agentPid) {
773
+ // PID was found but confirmed dead — exempt from restart grace period (#869).
774
+ // W-ms6dmv99005o26f4 the exemption sets `confirmedDeadAtRestart` in
775
+ // engine/timeout.js, which bypasses BOTH minAliveBeforeOrphanMs and the
776
+ // engine-restart grace window, so the dispatch is orphaned on the very next
777
+ // tick after only seconds of silence. That is the correct handling for a PID
778
+ // we OBSERVED to be dead, and catastrophic for one we merely failed to
779
+ // observe — hence the conclusive-evidence gate.
780
+ if (shouldMarkConfirmedDeadAtRestart({ hadPid, agentPid, inconclusive: inconclusiveEvidence })) {
701
781
  e.engineRestartGraceExempt.add(item.id);
782
+ e.log('info', `Restart: ${agentId} (${item.id}) runtime PID confirmed dead or mismatched — marking for infrastructure recovery`);
783
+ } else if (hadPid && !agentPid) {
784
+ e.log('warn', `Restart: ${agentId} (${item.id}) liveness evidence was unavailable — NOT marking confirmed dead; the standard orphan grace window applies`);
702
785
  }
703
786
 
704
787
  if (agentPid) {
@@ -723,7 +806,15 @@ const commands = {
723
806
  }
724
807
  } catch {}
725
808
  const runtimeName = item.runtimeName || item.runtime || item.meta?.runtimeName || item.meta?.runtime || null;
726
- e.activeProcesses.set(item.id, { proc: { pid: agentPid > 0 ? agentPid : null }, agentId, startedAt: item.created_at, reattached: true, sessionId, runtimeName });
809
+ e.activeProcesses.set(item.id, {
810
+ proc: { pid: agentPid > 0 ? agentPid : null },
811
+ agentId,
812
+ startedAt: item.created_at,
813
+ reattached: true,
814
+ sessionId,
815
+ runtimeName,
816
+ runtimePidObservedAt: item.runtimePidObservedAt,
817
+ });
727
818
  // Sync work item status to dispatched — atomic write to avoid lifecycle lazy init issues
728
819
  if (item.meta?.item?.id && item.meta?.project?.localPath) {
729
820
  try {
@@ -761,8 +852,8 @@ const commands = {
761
852
  // check if the agent actually completed by scanning its output file.
762
853
  // If it did, run the post-completion hooks now so work items get updated.
763
854
  (function detectOrphanCompletions() {
764
- const shared = require('./shared');
765
- const lifecycle = require('./lifecycle');
855
+ const shared = require('../core/shared');
856
+ const lifecycle = require('../orchestration/lifecycle');
766
857
  let recovered = 0;
767
858
 
768
859
  for (const item of activeOnStart) {
@@ -859,7 +950,7 @@ const commands = {
859
950
  // originalRef fields.
860
951
  if (shared.isLiveCheckoutDispatchRecord(item, config)) {
861
952
  try {
862
- require('./live-checkout').maybeRestoreLiveCheckoutFromRecord({
953
+ require('../worktrees/live-checkout').maybeRestoreLiveCheckoutFromRecord({
863
954
  item,
864
955
  config,
865
956
  isTerminalFailure: !isSuccess,
@@ -887,7 +978,7 @@ const commands = {
887
978
 
888
979
  // Recovery sweep
889
980
  (function recoverBrokenState() {
890
- const shared = require('./shared');
981
+ const shared = require('../core/shared');
891
982
  const projects = shared.getProjects(config);
892
983
  let fixes = 0;
893
984
 
@@ -927,7 +1018,7 @@ const commands = {
927
1018
  // operates on `active`/`linked`, so there is no per-record race.
928
1019
  (function startupReconcileAbandonedPrs() {
929
1020
  try {
930
- const reconciler = require('./abandoned-pr-reconciliation');
1021
+ const reconciler = require('../providers/abandoned-pr-reconciliation');
931
1022
  Promise.resolve(reconciler.runStartupReconciliation(config))
932
1023
  .then(result => {
933
1024
  if (!result.skipped && result.totals.flipped > 0) {
@@ -948,7 +1039,7 @@ const commands = {
948
1039
  // dead). Run once at boot before the first tick. Idempotent.
949
1040
  (function startupReconcileKeepProcesses() {
950
1041
  try {
951
- const { sweepKeepProcesses } = require('./keep-process-sweep');
1042
+ const { sweepKeepProcesses } = require('../processes/keep-process-sweep');
952
1043
  const stats = sweepKeepProcesses();
953
1044
  if (stats.scanned > 0 && (stats.expiredFiles || stats.deadFiles)) {
954
1045
  console.log(` Keep-processes boot reconcile: ${stats.expiredFiles} expired, ${stats.deadFiles} dead, ${stats.killedPids} killed`);
@@ -966,8 +1057,8 @@ const commands = {
966
1057
  // fire-and-forget — the first tick is allowed to start regardless.
967
1058
  (function startupReconcileManagedSpawn() {
968
1059
  try {
969
- const managedSpawn = require('./managed-spawn');
970
- const limits = (require('./shared').ENGINE_DEFAULTS.managedSpawn || {});
1060
+ const managedSpawn = require('../processes/managed-spawn');
1061
+ const limits = (require('../core/shared').ENGINE_DEFAULTS.managedSpawn || {});
971
1062
  if (limits.enabled === false) return;
972
1063
  const maxMs = Math.max(1, Number(limits.bootReconcileMaxMs) || 2000);
973
1064
  let settled = false;
@@ -1004,11 +1095,11 @@ const commands = {
1004
1095
  // boot prune just keeps re-attach sane.
1005
1096
  (function startupReconcileWorktreePool() {
1006
1097
  try {
1007
- const worktreePool = require('./worktree-pool');
1008
- const dispatchSnap = require('./queries').getDispatch();
1098
+ const worktreePool = require('../worktrees/pool');
1099
+ const dispatchSnap = require('../core/queries').getDispatch();
1009
1100
  const activeIds = new Set((dispatchSnap.active || []).map(d => d.id));
1010
- const cfg = require('./queries').getConfig();
1011
- const idleTtlMs = cfg?.engine?.worktreePoolIdleTtlMs ?? require('./shared').ENGINE_DEFAULTS.worktreePoolIdleTtlMs;
1101
+ const cfg = require('../core/queries').getConfig();
1102
+ const idleTtlMs = cfg?.engine?.worktreePoolIdleTtlMs ?? require('../core/shared').ENGINE_DEFAULTS.worktreePoolIdleTtlMs;
1012
1103
  const result = worktreePool.pruneStale({ activeDispatchIds: activeIds, idleTtlMs });
1013
1104
  if (result.evicted > 0) {
1014
1105
  console.log(` Worktree-pool boot reconcile: evicted ${result.evicted} stale entr${result.evicted === 1 ? 'y' : 'ies'}`);
@@ -1029,10 +1120,10 @@ const commands = {
1029
1120
  // about to be pruned but still appeared as a pool member).
1030
1121
  (function startupReconcileOrphanWorktrees() {
1031
1122
  try {
1032
- const sharedMod = require('./shared');
1033
- const worktreeGc = require('./worktree-gc');
1034
- const dispatchSnap = require('./queries').getDispatch();
1035
- const cfg = require('./queries').getConfig();
1123
+ const sharedMod = require('../core/shared');
1124
+ const worktreeGc = require('../worktrees/gc');
1125
+ const dispatchSnap = require('../core/queries').getDispatch();
1126
+ const cfg = require('../core/queries').getConfig();
1036
1127
  const projects = sharedMod.getProjects(cfg);
1037
1128
  if (projects.length === 0) return;
1038
1129
  const worktreeProjects = projects.filter((project) => {
@@ -1092,7 +1183,7 @@ const commands = {
1092
1183
  // /api/knowledge/sweep/status until someone POSTs a new sweep. Idempotent.
1093
1184
  (function startupReconcileKbSweep() {
1094
1185
  try {
1095
- const { reconcileSweepStateOnBoot } = require('./kb-sweep');
1186
+ const { reconcileSweepStateOnBoot } = require('../memory/kb-sweep');
1096
1187
  const stats = reconcileSweepStateOnBoot();
1097
1188
  if (stats.released > 0) {
1098
1189
  console.log(` KB-sweep boot reconcile: released stale ${stats.prevStatus} record (pid=${stats.prevPid}, reason=${stats.reason})`);
@@ -1109,7 +1200,7 @@ const commands = {
1109
1200
  // shares the same promise.
1110
1201
  (function warmKnowledgeBaseCache() {
1111
1202
  try {
1112
- const queries = require('./queries');
1203
+ const queries = require('../core/queries');
1113
1204
  Promise.resolve(queries.getKnowledgeBaseEntries())
1114
1205
  .catch(err => e.log('warn', `KB cache warm failed: ${err && err.message}`));
1115
1206
  } catch (err) {
@@ -1140,7 +1231,7 @@ const commands = {
1140
1231
 
1141
1232
  // Fast poll: check steering every 1s (lightweight — just fs.stat per agent)
1142
1233
  // and wakeup signals every 1s (control.json read)
1143
- const { checkSteering } = require('./timeout');
1234
+ const { checkSteering } = require('../orchestration/timeout');
1144
1235
  const fastPollTimer = setInterval(() => {
1145
1236
  try { checkSteering(); } catch {}
1146
1237
  const ctrl = getControl();
@@ -1188,7 +1279,7 @@ const commands = {
1188
1279
  // a stderr line if a flush fails so a refactor mistake (typo, missing
1189
1280
  // export) doesn't go silent.
1190
1281
  function drainBuffers() {
1191
- try { require('./llm').flushMetricsBuffer(); }
1282
+ try { require('../agents/llm').flushMetricsBuffer(); }
1192
1283
  catch (err) { console.error(`[shutdown] flushMetricsBuffer failed: ${err.message}`); }
1193
1284
  try { shared.flushLogs(); }
1194
1285
  catch (err) { console.error(`[shutdown] flushLogs failed: ${err.message}`); }
@@ -1262,7 +1353,7 @@ const commands = {
1262
1353
  const procInfo = e.activeProcesses.get(id);
1263
1354
  if (!procInfo?._pooled) continue;
1264
1355
  try {
1265
- procInfo.proc.kill();
1356
+ shared.terminateProcess('engine.cli.shutdown-pooled', procInfo.proc);
1266
1357
  if (typeof procInfo.proc.waitForClose === 'function') {
1267
1358
  closing.push(procInfo.proc.waitForClose());
1268
1359
  }
@@ -1282,7 +1373,12 @@ const commands = {
1282
1373
  DISPATCH_RESULT.ERROR,
1283
1374
  'Pooled dispatch cancelled after graceful shutdown timeout',
1284
1375
  '',
1285
- { failureClass: FAILURE_CLASS.TIMEOUT, retryable: true },
1376
+ {
1377
+ failureClass: FAILURE_CLASS.ENGINE_RESTART,
1378
+ retryable: true,
1379
+ retryBudget: 'infrastructure',
1380
+ countAgentRetryFailure: false,
1381
+ },
1286
1382
  );
1287
1383
  }
1288
1384
  finishShutdown(`Shutdown timeout (${timeout / 1000}s) — cancelled ${ids.length} pooled agent(s) for retry.`, 1);
@@ -1343,7 +1439,7 @@ const commands = {
1343
1439
  // Write stop-intent BEFORE killing so the external supervisor (engine/
1344
1440
  // supervisor.js) sees the flag on its next tick and stands down instead
1345
1441
  // of respawning. Cleared by `minions start`/`restart` AND by every
1346
- // successful engine boot (engine/cli.js#start) — so a direct
1442
+ // successful engine boot (engine/operations/cli.js#start) — so a direct
1347
1443
  // `node engine.js start` after this stop still clears the flag.
1348
1444
  if (!shared.writeStopIntent('engine.js stop')) {
1349
1445
  e.log('warn', 'Failed to write stop-intent');
@@ -1393,7 +1489,7 @@ const commands = {
1393
1489
  const dispatch = getDispatch();
1394
1490
  const agents = config.agents || {};
1395
1491
 
1396
- const { getProjects } = require('./shared');
1492
+ const { getProjects } = require('../core/shared');
1397
1493
  const projects = getProjects(config);
1398
1494
 
1399
1495
  // Version info
@@ -1646,7 +1742,7 @@ const commands = {
1646
1742
  }
1647
1743
 
1648
1744
  const config = getConfig();
1649
- const { getProjects, resolveProjectSource, uid } = require('./shared');
1745
+ const { getProjects, resolveProjectSource, uid } = require('../core/shared');
1650
1746
  const projects = getProjects(config);
1651
1747
  const target = opts.project ? resolveProjectSource(opts.project, projects, { allowCentral: false }) : null;
1652
1748
  if (target?.error) {
@@ -1670,7 +1766,7 @@ const commands = {
1670
1766
  }
1671
1767
  requestedId = opts.id.trim();
1672
1768
  const liveItems = readWorkItems(scope);
1673
- const archivedItems = require('./work-items-store').readArchivedWorkItemsForScope(scope);
1769
+ const archivedItems = require('../planning/work-items-store').readArchivedWorkItemsForScope(scope);
1674
1770
  if (liveItems.some(w => w?.id === requestedId) || archivedItems.some(w => w?.id === requestedId)) {
1675
1771
  console.log(`Error: work item id "${requestedId}" already exists in scope "${scope}" or its archive`);
1676
1772
  process.exit(1);
@@ -1737,7 +1833,7 @@ const commands = {
1737
1833
  }
1738
1834
 
1739
1835
  const config = getConfig();
1740
- const { getProjects, resolveProjectSource } = require('./shared');
1836
+ const { getProjects, resolveProjectSource } = require('../core/shared');
1741
1837
  const projects = getProjects(config);
1742
1838
  const target = projectName ? resolveProjectSource(projectName, projects, { allowCentral: false }) : null;
1743
1839
  if (target?.error) {
@@ -1833,7 +1929,7 @@ const commands = {
1833
1929
  sources() {
1834
1930
  const e = engine();
1835
1931
  const config = getConfig();
1836
- const shared = require('./shared');
1932
+ const shared = require('../core/shared');
1837
1933
  const projects = shared.getProjects(config);
1838
1934
 
1839
1935
  console.log('\n=== Work Sources ===\n');
@@ -1897,12 +1993,13 @@ const commands = {
1897
1993
  const e = engine();
1898
1994
  console.log('\n=== Kill All Active Work ===\n');
1899
1995
  const config = getConfig();
1900
- const shared = require('./shared');
1996
+ const shared = require('../core/shared');
1901
1997
 
1902
1998
  // Kill processes via PID files (expensive — outside dispatch lock).
1903
1999
  // PID files live in engine/tmp/ — both legacy flat layout
1904
2000
  // (`pid-<id>.pid`) and per-dispatch dirs (`dispatch-<id>-XXX/pid-<id>.pid`)
1905
2001
  // post-P-f6-tmp-toctou. shared.forEachPidFile visits both.
2002
+ const processSnapshot = shared.listAllProcesses();
1906
2003
  shared.forEachPidFile((pidPath, fileName, layout) => {
1907
2004
  const raw = safeRead(pidPath).trim();
1908
2005
  // Guard against falsy/zero/NaN PIDs. Empty pid files would resolve to
@@ -1910,14 +2007,25 @@ const commands = {
1910
2007
  // calling process group — which would kill the engine itself.
1911
2008
  let pidNum = NaN;
1912
2009
  try { pidNum = shared.validatePid(raw); } catch { /* invalid — skip */ }
1913
- if (pidNum > 0) {
2010
+ let pidFileMtimeMs = 0;
2011
+ try { pidFileMtimeMs = fs.statSync(pidPath).mtimeMs; } catch {}
2012
+ const terminated = pidNum > 0 && shared.terminateProcess('engine.cli.kill-command', {
2013
+ pid: pidNum,
2014
+ }, {
2015
+ recordedAtMs: pidFileMtimeMs,
2016
+ processes: processSnapshot,
2017
+ skewMs: 2000,
2018
+ });
2019
+ if (terminated) {
1914
2020
  // killImmediate force-kills the whole process tree (bare process.kill
1915
2021
  // defaults to a non-recursing SIGTERM that is Windows-broken and
1916
2022
  // leaks child processes — CLAUDE.md Footgun #4).
1917
- try { shared.killImmediate({ pid: pidNum }); console.log(`Killed process ${pidNum} (${fileName})`); }
2023
+ try {
2024
+ console.log(`Killed process ${pidNum} (${fileName})`);
2025
+ }
1918
2026
  catch { console.log(`Process ${pidNum} already dead`); }
1919
2027
  } else {
1920
- console.log(`Skipping ${fileName}: invalid or empty PID`);
2028
+ console.log(`Skipping ${fileName}: invalid, stale, or non-agent PID`);
1921
2029
  }
1922
2030
  if (layout === 'dispatch-dir') {
1923
2031
  try { shared.removeDispatchTmpDir(path.dirname(pidPath)); } catch { /* may not exist */ }
@@ -1977,7 +2085,7 @@ const commands = {
1977
2085
 
1978
2086
  'mcp-sync'() {
1979
2087
  // P-90a37c52 — repurposed as a harness diagnostic printer. Same source
1980
- // as `minions doctor --harness` (engine/preflight.js → runHarnessDoctor)
2088
+ // as `minions doctor --harness` (engine/agents/preflight.js → runHarnessDoctor)
1981
2089
  // but stays exit 0 regardless of the diagnostic's hardError signal, so
1982
2090
  // callers that scripted around the legacy `mcp-sync` (which was a pure
1983
2091
  // print + exit 0) don't have their automation flipped. For an exit
@@ -1986,12 +2094,12 @@ const commands = {
1986
2094
  // Stays a no-op: no writes to ~/.claude.json, no state mutation. The
1987
2095
  // legacy "Sync MCP servers from ~/.claude.json — no sync needed."
1988
2096
  // message is replaced by the structured per-runtime propagation survey.
1989
- const { runHarnessDoctor } = require('./preflight');
2097
+ const { runHarnessDoctor } = require('../agents/preflight');
1990
2098
  runHarnessDoctor(MINIONS_DIR);
1991
2099
  },
1992
2100
 
1993
2101
  doctor(...doctorArgs) {
1994
- const { doctor, runHarnessDoctor } = require('./preflight');
2102
+ const { doctor, runHarnessDoctor } = require('../agents/preflight');
1995
2103
  if (doctorArgs.includes('--harness')) {
1996
2104
  // Read-only runtime harness inventory. See
1997
2105
  // docs/harness-propagation.md and the parallel branch in bin/minions.js.
@@ -2012,7 +2120,7 @@ const commands = {
2012
2120
  if (!cliName || cliName.startsWith('--')) {
2013
2121
  console.error('Usage: minions config set-cli <runtime> [--model <model>]');
2014
2122
  let registered = [];
2015
- try { registered = require('./runtimes').listRuntimes(); } catch {}
2123
+ try { registered = require('../runtimes').listRuntimes(); } catch {}
2016
2124
  if (registered.length > 0) console.error(`Registered runtimes: ${registered.join(', ')}`);
2017
2125
  process.exit(2);
2018
2126
  }
@@ -2074,7 +2182,7 @@ const commands = {
2074
2182
  console.log('');
2075
2183
  },
2076
2184
 
2077
- // `minions pr <subcommand> ...` — wraps engine/gh-comment.js so playbook-
2185
+ // `minions pr <subcommand> ...` — wraps engine/providers/gh-comment.js so playbook-
2078
2186
  // driven shells have a single command that automatically prepends the hidden
2079
2187
  // minions marker (so engine classifiers can identify agent-authored PR
2080
2188
  // comments by structure, not body shape).
@@ -2178,7 +2286,7 @@ const commands = {
2178
2286
  process.exit(2);
2179
2287
  }
2180
2288
  const orgBase = flags['org-base'] || `https://dev.azure.com/${adoOrg}`;
2181
- const adoComment = require('./ado-comment');
2289
+ const adoComment = require('../ado/comment');
2182
2290
  adoComment.postAdoPrComment({
2183
2291
  orgBase, project, repositoryId, prNumber, body, agentId, kind, workItemId,
2184
2292
  model: executionModel.model,
@@ -2195,7 +2303,7 @@ const commands = {
2195
2303
  }
2196
2304
 
2197
2305
  // ── GitHub: <repo> <prNumber> ──
2198
- const ghComment = require('./gh-comment');
2306
+ const ghComment = require('../providers/gh-comment');
2199
2307
  // GitHub conversation (issue) comments have no "resolved" concept — only
2200
2308
  // inline review threads can be resolved, and that needs a GraphQL
2201
2309
  // resolveReviewThread round-trip the agent runs itself (see shared-rules.md
@@ -2237,18 +2345,19 @@ const commands = {
2237
2345
  // Subcommands:
2238
2346
  // status Print enabled flag + last-seen Constellation agent timestamp.
2239
2347
  // health Probe the versioned local snapshot endpoint and print the same
2240
- // metadata summary the Constellation bridge consumes.
2348
+ // metadata summary the Constellation bridge consumes, preceded by
2349
+ // the Constellation companion presence/compatibility report.
2241
2350
  // enable Atomically set engine.constellationBridge.enabled = true.
2242
2351
  // disable Atomically set engine.constellationBridge.enabled = false.
2243
2352
  bridge(subcmd, ...rest) {
2244
- const bridge = require('./bridge');
2353
+ const bridge = require('../api/bridge');
2245
2354
  const BRIDGE_USAGE = 'Usage: minions bridge <status|health|enable|disable>';
2246
2355
 
2247
2356
  if (!subcmd || subcmd === 'help' || subcmd === '--help' || subcmd === '-h') {
2248
2357
  console.log(BRIDGE_USAGE);
2249
2358
  console.log('');
2250
2359
  console.log(' status Show enabled flag + last-seen Constellation agent timestamp');
2251
- console.log(' health Probe the local versioned Constellation snapshot');
2360
+ console.log(' health Probe the local versioned Constellation snapshot + report companion presence');
2252
2361
  console.log(' enable Set engine.constellationBridge.enabled = true');
2253
2362
  console.log(' disable Set engine.constellationBridge.enabled = false');
2254
2363
  return;
@@ -2289,49 +2398,30 @@ const commands = {
2289
2398
  }
2290
2399
 
2291
2400
  if (subcmd === 'health') {
2292
- const http = require('http');
2293
- const req = http.get(
2294
- { hostname: '127.0.0.1', port: 7331, path: '/api/constellation/v1/snapshot', timeout: 5000 },
2295
- (res) => {
2296
- let body = '';
2297
- res.setEncoding('utf8');
2298
- res.on('data', (chunk) => { body += chunk; });
2299
- res.on('end', () => {
2300
- if (res.statusCode !== 200) {
2301
- console.error(`error: dashboard returned HTTP ${res.statusCode}`);
2302
- process.exit(1);
2303
- }
2304
- let parsed;
2305
- try { parsed = JSON.parse(body); }
2306
- catch (e) {
2307
- console.error(`error: dashboard response was not JSON: ${e.message}`);
2308
- process.exit(1);
2309
- }
2310
- const projection = bridge.projectSnapshotHealthForBridge(parsed);
2311
- if (!projection) {
2312
- console.error('error: dashboard returned an incompatible Constellation snapshot');
2313
- process.exit(1);
2314
- }
2315
- console.log('bridge: dashboard reachable on http://127.0.0.1:7331');
2316
- console.log(' snapshot (same metadata the Constellation bridge mirrors):');
2317
- for (const [k, v] of Object.entries(projection)) {
2318
- console.log(` ${k}: ${v === null ? '(unknown)' : v}`);
2319
- }
2320
- });
2321
- }
2322
- );
2323
- req.on('timeout', () => {
2324
- req.destroy(new Error('connect timeout'));
2325
- });
2326
- req.on('error', (err) => {
2327
- if (err.code === 'ECONNREFUSED') {
2328
- console.error('error: dashboard not running on :7331 — start it with `minions dash`');
2329
- } else {
2330
- console.error(`error: ${err.message}`);
2331
- }
2332
- process.exit(1);
2333
- });
2334
- return;
2401
+ // Companion first: it is the one part of `bridge health` that still says
2402
+ // something useful when the dashboard is down. Bounded by construction
2403
+ // (marker read + optional loopback probe with an explicit timeout) and
2404
+ // never fatal — an absent or incompatible companion does not change the
2405
+ // exit code, which stays a pure dashboard-reachability signal.
2406
+ const companion = require('../api/companion');
2407
+ let companionConfig = null;
2408
+ try { companionConfig = getConfig(); } catch { /* config problems are the dashboard probe's business */ }
2409
+ return Promise.resolve()
2410
+ .then(() => companion.probeCompanion({
2411
+ config: companionConfig,
2412
+ markerPath: bridge.CONSTELLATION_BRIDGE_MARKER_PATH,
2413
+ }))
2414
+ .catch(() => null)
2415
+ .then((companionResult) => {
2416
+ for (const line of companion.formatCompanionLines(companionResult)) console.log(line);
2417
+ _bridgeDashboardHealthProbe();
2418
+ })
2419
+ .catch((err) => {
2420
+ // Preserves the pre-existing contract: any failure reaching the top of
2421
+ // `bridge health` is an error + exit 1, never a silent rejection.
2422
+ console.error(`error: ${(err && err.message) || err}`);
2423
+ process.exit(1);
2424
+ });
2335
2425
  }
2336
2426
 
2337
2427
  console.error(`Unknown bridge subcommand: ${subcmd}`);
@@ -2340,6 +2430,72 @@ const commands = {
2340
2430
  }
2341
2431
  };
2342
2432
 
2433
+ /**
2434
+ * Probe the versioned Constellation snapshot endpoint and print its metadata
2435
+ * summary. Extracted from `bridge health` so the companion report can be
2436
+ * printed first without nesting the dashboard probe inside a promise body.
2437
+ * Unchanged behavior: exit 1 on any dashboard/protocol failure.
2438
+ */
2439
+ function _bridgeDashboardHealthProbe() {
2440
+ const bridge = require('../api/bridge');
2441
+ const http = require('http');
2442
+ const req = http.get(
2443
+ { hostname: '127.0.0.1', port: 7331, path: '/api/constellation/v1/snapshot', timeout: 5000 },
2444
+ (res) => {
2445
+ let body = '';
2446
+ res.setEncoding('utf8');
2447
+ res.on('data', (chunk) => { body += chunk; });
2448
+ res.on('end', () => {
2449
+ // The snapshot endpoint is gated on engine.constellationBridge.enabled,
2450
+ // so a 404 carrying the `bridge-disabled` code is the operator's own
2451
+ // flag, not a broken dashboard. Name the flag and the fix instead of
2452
+ // surfacing a bare status code. Any other non-200 keeps the generic
2453
+ // transport-level message.
2454
+ if (res.statusCode === 404) {
2455
+ let gate;
2456
+ try { gate = JSON.parse(body); } catch { gate = null; }
2457
+ if (gate && gate.code === 'bridge-disabled') {
2458
+ console.error('error: Constellation bridge is disabled — enable it with `minions bridge enable`');
2459
+ console.error(' config: engine.constellationBridge.enabled = false');
2460
+ process.exit(1);
2461
+ }
2462
+ }
2463
+ if (res.statusCode !== 200) {
2464
+ console.error(`error: dashboard returned HTTP ${res.statusCode}`);
2465
+ process.exit(1);
2466
+ }
2467
+ let parsed;
2468
+ try { parsed = JSON.parse(body); }
2469
+ catch (e) {
2470
+ console.error(`error: dashboard response was not JSON: ${e.message}`);
2471
+ process.exit(1);
2472
+ }
2473
+ const projection = bridge.projectSnapshotHealthForBridge(parsed);
2474
+ if (!projection) {
2475
+ console.error('error: dashboard returned an incompatible Constellation snapshot');
2476
+ process.exit(1);
2477
+ }
2478
+ console.log('bridge: dashboard reachable on http://127.0.0.1:7331');
2479
+ console.log(' snapshot (same metadata the Constellation bridge mirrors):');
2480
+ for (const [k, v] of Object.entries(projection)) {
2481
+ console.log(` ${k}: ${v === null ? '(unknown)' : v}`);
2482
+ }
2483
+ });
2484
+ }
2485
+ );
2486
+ req.on('timeout', () => {
2487
+ req.destroy(new Error('connect timeout'));
2488
+ });
2489
+ req.on('error', (err) => {
2490
+ if (err.code === 'ECONNREFUSED') {
2491
+ console.error('error: dashboard not running on :7331 — start it with `minions dash`');
2492
+ } else {
2493
+ console.error(`error: ${err.message}`);
2494
+ }
2495
+ process.exit(1);
2496
+ });
2497
+ }
2498
+
2343
2499
  module.exports = {
2344
2500
  handleCommand,
2345
2501
  // exported for downstream indexing (dashboard CC preamble)
@@ -2357,6 +2513,8 @@ module.exports = {
2357
2513
  _summarizeActiveDispatchPids: summarizeActiveDispatchPids,
2358
2514
  _dispatchSafeId: dispatchSafeId,
2359
2515
  _readDispatchPid: readDispatchPid,
2516
+ _evaluateReattachEvidence: evaluateReattachEvidence,
2517
+ _shouldMarkConfirmedDeadAtRestart: shouldMarkConfirmedDeadAtRestart,
2360
2518
  _normalizeSessionBranch: normalizeSessionBranch,
2361
2519
  _dispatchSessionBranch: dispatchSessionBranch,
2362
2520
  _resolveCommentExecutionModel: resolveCommentExecutionModel, // exported for testing