@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,45 +1,46 @@
1
1
  /**
2
- * engine/github.js — GitHub integration for Minions engine.
2
+ * engine/providers/github.js — GitHub integration for Minions engine.
3
3
  * Parallel to ado.js: PR status polling, comment polling, reconciliation for GitHub-hosted projects.
4
4
  * Uses `gh` CLI for all GitHub API calls.
5
5
  */
6
6
 
7
- const shared = require('./shared');
8
- const { exec, execAsync, getProjects, readWorkItems, readPullRequests, safeJson, safeJsonArr, safeWrite, mutateMetrics, mutatePullRequests, MINIONS_DIR, getPrLinks, backfillPrPrdItems, log, ts, dateStamp, PR_STATUS, PR_POLLABLE_STATUSES, BUILD_STATUS, REVIEW_STATUS, FETCH_TIMEOUT_MS, ENGINE_DEFAULTS, createThrottleTracker, getProjectOrg } = shared;
9
- const { getPrs } = require('./queries');
7
+ const shared = require('../core/shared');
8
+ const { exec, execAsync, getProjects, readWorkItems, readPullRequests, safeJson, safeJsonArr, safeWrite, mutateMetrics, mutatePullRequests, MINIONS_DIR, getPrLinks, backfillPrPrdItems, log, ts, dateStamp, PR_STATUS, BUILD_STATUS, REVIEW_STATUS, FETCH_TIMEOUT_MS, ENGINE_DEFAULTS, createThrottleTracker, getProjectOrg } = shared;
9
+ const { getPrs } = require('../core/queries');
10
10
  const {
11
11
  isPreviewStatusBody,
12
- hasMinionsMarker,
13
12
  hasVerdictPrefix,
13
+ classifyMarkerAuthorship,
14
+ isMarkerAuthored,
14
15
  classifyNonActionableAutomationComment,
15
16
  } = require('./comment-classifier');
16
- const { wrapUntrusted, buildSource } = require('./untrusted-fence');
17
- const reviewLearning = require('./review-learning');
17
+ const { wrapUntrusted, buildSource } = require('../core/untrusted-fence');
18
+ const reviewLearning = require('../memory/review-learning');
18
19
  const ghToken = require('./gh-token');
19
20
  const path = require('path');
20
21
 
21
22
  // Lazy require to avoid circular dependency — only needed for engine().handlePostMerge
22
23
  let _engine = null;
23
24
  function engine() {
24
- if (!_engine) _engine = require('../engine');
25
+ if (!_engine) _engine = require('../../engine');
25
26
  return _engine;
26
27
  }
27
28
 
28
29
  // Lazy require for dispatch module (avoids circular dependency via engine)
29
30
  let _dispatch = null;
30
- function dispatchModule() { if (!_dispatch) _dispatch = require('./dispatch'); return _dispatch; }
31
+ function dispatchModule() { if (!_dispatch) _dispatch = require('../orchestration/dispatch'); return _dispatch; }
31
32
 
32
33
  // Lazy require for lifecycle module — only needed for the stale-noopfix sweep
33
34
  // in pollPrStatus (W-mpx44p05000ze8d8). lifecycle.js lazy-requires github.js
34
35
  // back, so this MUST stay lazy to avoid a circular import at module-load time.
35
36
  let _lifecycle = null;
36
- function lifecycleModule() { if (!_lifecycle) _lifecycle = require('./lifecycle'); return _lifecycle; }
37
+ function lifecycleModule() { if (!_lifecycle) _lifecycle = require('../orchestration/lifecycle'); return _lifecycle; }
37
38
 
38
39
  // W-mp5trwh60008386d: test seam so unit tests can mock `gh api` shell-outs
39
40
  // without spawning the real CLI. Production code goes through the real
40
41
  // `execAsync` from shared; tests call `_setExecAsyncForTest(fn)` to swap in
41
42
  // a stub. Pass `null` to restore. Mirrors the `_setAdoTokenForTest` pattern
42
- // in engine/ado.js.
43
+ // in engine/ado/index.js.
43
44
  let _execAsyncOverride = null;
44
45
  function _runExec(cmd, opts) {
45
46
  return (_execAsyncOverride || execAsync)(cmd, opts);
@@ -142,11 +143,11 @@ function getConfiguredGitHubAuthorLogins(config = {}) {
142
143
  }
143
144
 
144
145
  // P-f23classifier (F4): delegates to the host-agnostic VERDICT prefix check
145
- // in engine/comment-classifier.js. The literal regex used to live here;
146
+ // in engine/providers/comment-classifier.js. The literal regex used to live here;
146
147
  // extraction allowed F4 (Wave 3) to tighten the prefix in a single place.
147
148
  // The tightened form requires `VERDICT: APPROVE|REQUEST_CHANGES` at line
148
149
  // start (under /m), optionally wrapped in `**`, `__`, or `#+ ` markers —
149
- // see engine/comment-classifier.js for the full contract.
150
+ // see engine/providers/comment-classifier.js for the full contract.
150
151
  function _hasMinionsReviewVerdict(body) {
151
152
  return hasVerdictPrefix(body);
152
153
  }
@@ -169,7 +170,7 @@ function _isGitHubBotComment(c) {
169
170
 
170
171
  // P-f23classifier (F2): retains the GH-side bot-author gate (ADO has no
171
172
  // `user.type === 'bot'` equivalent) but delegates the body check to the
172
- // host-agnostic isPreviewStatusBody helper in engine/comment-classifier.js.
173
+ // host-agnostic isPreviewStatusBody helper in engine/providers/comment-classifier.js.
173
174
  // The body matcher covers Coverage/Build/Test/Deploy/Pipeline report
174
175
  // headings + Firebase App Distribution + Appetize + Deploy-ready phrases +
175
176
  // shields.io badges (same set as before the extraction).
@@ -213,11 +214,11 @@ function _entryCommentKey(entry) {
213
214
  // Replaces the body-shape classifier chain (PR #2431's _isAgentSelfReviewDeclinedComment,
214
215
  // PR #2442's _isAgentPositiveSignalComment + _hasAgentPositiveSignalBody) with a single
215
216
  // structural check: a Minions-authored comment carries `MINIONS_COMMENT_MARKER` (defined
216
- // in engine/gh-comment.js) on its own first line AND was authored by the viewer (the
217
+ // in engine/providers/gh-comment.js) on its own first line AND was authored by the viewer (the
217
218
  // shared PAT identity used by all minions agents).
218
219
  //
219
- // Both gates are required:
220
- // - `viewerDidAuthor: true` prevents humans from spoofing the marker by quoting it.
220
+ // Both gates are required whenever the viewer identity is KNOWN:
221
+ // - `viewerDidAuthor: false` prevents humans from spoofing the marker by quoting it.
221
222
  // - `MINIONS_COMMENT_MARKER_RE` enforces "own first line" — quoted markers in the
222
223
  // middle of a comment, or markers with trailing content on the same line, do NOT
223
224
  // match. This is the structural complement to the viewer check.
@@ -233,12 +234,31 @@ function _entryCommentKey(entry) {
233
234
  // Sub-item -c migrates existing engine call sites + playbook examples.
234
235
  //
235
236
  // P-f23classifier (F3): the body-only "structural marker on own first line"
236
- // check is now hasMinionsMarker in engine/comment-classifier.js. GH retains
237
- // the viewerDidAuthor gate as the spoof guard (ADO uses a uniqueName cross-
238
- // check against config.engine.minionsAdoIdentities instead — see ado.js).
237
+ // check is now hasMinionsMarker in engine/providers/comment-classifier.js.
238
+ //
239
+ // W-msbf1kf6033f46cb — marker authorship resolves through the shared
240
+ // `classifyMarkerAuthorship` seam that engine/ado/index.js also uses, so the
241
+ // two pollers cannot drift (mirror-ado-github-poll-fix convention). Only the
242
+ // identity SOURCE differs: GH derives it from `viewerDidAuthor` (resolved at
243
+ // runtime), ADO from the `config.engine.minionsAdoIdentities` allowlist.
244
+ //
245
+ // `viewerDidAuthor` is a tri-state here: `true`/`false` mean the viewer login
246
+ // was resolved and the comment either is or is not ours, while `null`/absent
247
+ // means the signal is unavailable (`_backfillViewerDidAuthor` no-ops without a
248
+ // viewer login or an author login). The spoof guard is unchanged for the
249
+ // `false` case — a real human who pasted our marker stays actionable. Only the
250
+ // unavailable case now fails safe instead of open, matching ADO. In production
251
+ // GH never reaches that case: `pollPrHumanComments` already skips the whole PR
252
+ // when `_resolveViewerLogin` returns null (OQ-Y), so this is defense in depth
253
+ // against that upstream guard being removed.
239
254
  function _isMinionsAuthoredComment(c) {
240
- if (!c || c.viewerDidAuthor !== true) return false;
241
- return hasMinionsMarker(c.body || '');
255
+ if (!c) return false;
256
+ const identityAvailable = c.viewerDidAuthor === true || c.viewerDidAuthor === false;
257
+ return isMarkerAuthored(classifyMarkerAuthorship({
258
+ body: c.body || '',
259
+ identityAvailable,
260
+ identityMatches: c.viewerDidAuthor === true,
261
+ }));
242
262
  }
243
263
 
244
264
  // ─── Viewer Login Resolution ────────────────────────────────────────────────
@@ -251,7 +271,7 @@ function _isMinionsAuthoredComment(c) {
251
271
  // the viewer login resolved via `gh api user`.
252
272
  //
253
273
  // W-mp76pw7a001da7c1 — The viewer login is no longer process-wide because
254
- // `ghApi` now resolves a per-slug token via `engine/gh-token.js`. Different
274
+ // `ghApi` now resolves a per-slug token via `engine/providers/gh-token.js`. Different
255
275
  // accounts return different logins from `gh api user`, so the cache is keyed
256
276
  // by the resolved account name (or the sentinel `__ambient__` when no
257
277
  // account mapping applies and the call falls back to the ambient gh
@@ -291,7 +311,7 @@ async function _resolveViewerLogin(slug) {
291
311
  // data is still worth tracking so future audits can prioritize a deeper fix
292
312
  // (e.g. a credentialed re-probe of the gh CLI). Global counter under
293
313
  // `_engine.viewerLoginResolutionFailures`; surfaced through the existing
294
- // engine metrics aggregation (`getMetrics()` in `engine/queries.js`).
314
+ // engine metrics aggregation (`getMetrics()` in `engine/core/queries.js`).
295
315
  function _incrementViewerLoginResolutionFailures() {
296
316
  try {
297
317
  mutateMetrics((metrics) => {
@@ -530,10 +550,18 @@ async function fetchGhBuildErrorLog(slug, failedRuns) {
530
550
 
531
551
  // ─── Shared PR Polling Loop ─────────────────────────────────────────────────
532
552
 
533
- async function forEachActiveGhPr(config, callback) {
553
+ async function forEachActiveGhPr(config, callback, options = {}) {
534
554
  const projects = getProjects(config).filter(isGitHub);
535
555
  let totalUpdated = 0;
536
556
 
557
+ // W-msav5edk00g81710 — bounded re-probe of recently-abandoned records. Opt-in
558
+ // per call site (pollPrStatus only): the reopen signal lives on the status
559
+ // endpoint, so widening the comment poller would burn quota for no signal.
560
+ const pollOpts = {
561
+ windowMs: Number(options.abandonedRepollWindowMs) || 0,
562
+ now: Date.now(),
563
+ };
564
+
537
565
  for (const project of projects) {
538
566
  // W-mp625n27000m6e78 — Honor per-project workSources.pullRequests.enabled.
539
567
  // Default-ON: only skip when explicitly disabled (=== false). Missing/undefined
@@ -545,7 +573,7 @@ async function forEachActiveGhPr(config, callback) {
545
573
  if (src && src.enabled === false) continue;
546
574
 
547
575
  const prs = getPrs(project);
548
- const activePrs = prs.filter(pr => PR_POLLABLE_STATUSES.has(pr.status)
576
+ const activePrs = prs.filter(pr => shared.isPrPollable(pr, pollOpts)
549
577
  && shared.isPrCompatibleWithProject(project, pr, pr.url || ''));
550
578
  if (activePrs.length === 0) continue;
551
579
 
@@ -640,7 +668,7 @@ async function forEachActiveGhPr(config, callback) {
640
668
  for (const slug of getProjectRepoSlugs(project)) configuredGhProjectsBySlug.set(slug.toLowerCase(), project);
641
669
  }
642
670
  const activeCentral = centralPrs.filter(pr => {
643
- if (!PR_POLLABLE_STATUSES.has(pr.status)) return false;
671
+ if (!shared.isPrPollable(pr, pollOpts)) return false;
644
672
  if (!pr.url) return false;
645
673
  // Symmetric fix for #328: only defer to the project loop when the PR is
646
674
  // ACTUALLY present in that project's per-project file. If it's absent there,
@@ -696,7 +724,7 @@ async function forEachActiveGhPr(config, callback) {
696
724
  if (updated) {
697
725
  // Also update title/author/branch if still placeholder
698
726
  const currentTitle = pr.title || '';
699
- if (shared.isPlaceholderPrTitle(currentTitle) || pr.agent === 'human' || pr.description === undefined) {
727
+ if (shared.isPlaceholderPrTitle(currentTitle) || !pr.author || pr.description === undefined) {
700
728
  const prData = await ghApi(`/pulls/${prNum}`, slug);
701
729
  if (prData) {
702
730
  const latestTitle = pr.title || '';
@@ -704,7 +732,10 @@ async function forEachActiveGhPr(config, callback) {
704
732
  pr.title = (prData.title || latestTitle).slice(0, 120);
705
733
  }
706
734
  if (pr.description === undefined) pr.description = (prData.body || '').slice(0, 500);
707
- if (pr.agent === 'human' && prData.user?.login) pr.agent = prData.user.login;
735
+ // Populate the real author identity from PR metadata. Never overwrite
736
+ // pr.agent (the Minions author-agent) with a human login.
737
+ const ghAuthor = shared.buildGithubAuthorIdentity(prData.user);
738
+ if (ghAuthor) pr.author = shared.mergePrAuthorIdentity(pr.author, ghAuthor);
708
739
  if (!pr.branch && prData.head?.ref) {
709
740
  // P-a7c4d2e8 (F3): validate API-derived branch ref before
710
741
  // persistence. Defensive degrade — log and skip on invalid ref
@@ -757,7 +788,7 @@ async function forEachActiveGhPr(config, callback) {
757
788
  }
758
789
 
759
790
  /**
760
- * Issue #633 (mirror of engine/ado.js isStaleAdoRejectVote) — GitHub reviews
791
+ * Issue #633 (mirror of engine/ado/index.js isStaleAdoRejectVote) — GitHub reviews
761
792
  * generally get dismissed/replaced when a reviewer re-reviews, but a stale
762
793
  * CHANGES_REQUESTED review from a login that never re-reviews after a fix can
763
794
  * still pin `reviewStatus` at 'changes-requested' forever (the reviewer's
@@ -796,19 +827,36 @@ async function pollPrStatus(config) {
796
827
  if (next >= confirmCount) {
797
828
  if (pr.status !== PR_STATUS.ABANDONED) {
798
829
  pr.status = PR_STATUS.ABANDONED;
830
+ // W-msav5edk00g81710: anchor the bounded re-probe window. This path
831
+ // never sets `closedAt` (there is no platform record to read it from),
832
+ // so without the stamp a 404-abandoned record would have no age at all
833
+ // and `isPrPollable` would fail closed on it forever.
834
+ shared.stampPrAbandonedAt(pr, ts());
799
835
  delete pr._consecutive404s;
800
836
  log('info', `PR ${pr.id} returned 404 ${next} consecutive polls — marking abandoned`);
801
837
  return true;
802
838
  }
803
- // Already abandoned; clear the counter if it lingered.
839
+ // Already abandoned and still 404ing under a healthy base-repo probe —
840
+ // that is exactly the evidence reconcileAbandonedPrs uses to confirm a
841
+ // genuine deletion (W-msav5edk00g81710). Record it so the PR drops out
842
+ // of the bounded re-probe window immediately instead of sawtoothing the
843
+ // counter for the window's full duration.
844
+ let changed = false;
804
845
  if (pr._consecutive404s) {
805
846
  delete pr._consecutive404s;
806
- return true;
847
+ changed = true;
848
+ }
849
+ if (!pr._reconciliation404Confirmed) {
850
+ pr._reconciliation404Confirmed = true;
851
+ log('info', `PR ${pr.id} confirmed deleted after ${next} consecutive 404s — closing the abandoned re-probe window`);
852
+ changed = true;
807
853
  }
808
- return false;
854
+ return changed;
809
855
  }
810
856
  pr._consecutive404s = next;
811
- log('warn', `PR ${pr.id} returned 404 (${next}/${confirmCount}) — deferring abandonment until threshold`);
857
+ log('warn', pr.status === PR_STATUS.ABANDONED
858
+ ? `PR ${pr.id} returned 404 (${next}/${confirmCount}) — confirming deletion before closing the abandoned re-probe window`
859
+ : `PR ${pr.id} returned 404 (${next}/${confirmCount}) — deferring abandonment until threshold`);
812
860
  return true; // counter increment is a state change worth persisting
813
861
  }
814
862
 
@@ -855,9 +903,17 @@ async function pollPrStatus(config) {
855
903
  pr.description = (prData.body || '').slice(0, 500);
856
904
  updated = true;
857
905
  }
858
- if (pr.agent === 'human' && prData.user?.login) {
859
- pr.agent = prData.user.login;
860
- updated = true;
906
+ // Populate the real author identity from PR metadata. Never overwrite
907
+ // pr.agent (the Minions author-agent) with a human login.
908
+ {
909
+ const ghAuthor = shared.buildGithubAuthorIdentity(prData.user);
910
+ if (ghAuthor) {
911
+ const mergedAuthor = shared.mergePrAuthorIdentity(pr.author, ghAuthor);
912
+ if (JSON.stringify(mergedAuthor) !== JSON.stringify(pr.author || null)) {
913
+ pr.author = mergedAuthor;
914
+ updated = true;
915
+ }
916
+ }
861
917
  }
862
918
 
863
919
  // Map GitHub PR state to minions status
@@ -904,7 +960,7 @@ async function pollPrStatus(config) {
904
960
  }
905
961
 
906
962
  // P-w1a3f9b2 — Phase 1.1: plumb mergeable / isDraft / mergeStateStatus /
907
- // headRefOid onto the PR object so watches captureState (engine/watches.js)
963
+ // headRefOid onto the PR object so watches captureState (engine/watches/index.js)
908
964
  // and future predicates (Phase 2.1: head-commit-change, mergeable-flipped,
909
965
  // ready-for-merge, draft-flipped) can read them. We mirror GitHub's
910
966
  // tri-state contract for `mergeable` (true|false|null — see
@@ -949,9 +1005,9 @@ async function pollPrStatus(config) {
949
1005
  // needing a separate counter. When skipped (flag off, PR closed,
950
1006
  // missing refs, cross-repo fork, or compare-API error), behindBy is
951
1007
  // explicitly set to null so the `behind-master` predicate
952
- // (engine/watches.js BEHIND_MASTER) treats it as "no signal" and does
1008
+ // (engine/watches/index.js BEHIND_MASTER) treats it as "no signal" and does
953
1009
  // not over-fire on legacy / stale state. ADO PRs are handled in
954
- // engine/ado.js with the same null contract.
1010
+ // engine/ado/index.js with the same null contract.
955
1011
  const includeBehindBy = config.engine?.watchesIncludeBehindBy === true;
956
1012
  let newBehindBy = null;
957
1013
  if (includeBehindBy && prData.state === 'open' && prData.base?.ref && prData.head?.sha) {
@@ -987,6 +1043,12 @@ async function pollPrStatus(config) {
987
1043
  if ((newStatus === PR_STATUS.ABANDONED || newStatus === PR_STATUS.CLOSED) && !pr.closedAt) {
988
1044
  pr.closedAt = prData.closed_at || ts();
989
1045
  }
1046
+ // W-msav5edk00g81710: anchor the bounded re-probe window on every entry
1047
+ // into `abandoned`, including a re-close after a reopen (the window must
1048
+ // restart, not inherit the first closure's age).
1049
+ if (newStatus === PR_STATUS.ABANDONED) {
1050
+ shared.stampPrAbandonedAt(pr, prData.closed_at || pr.closedAt || ts());
1051
+ }
990
1052
 
991
1053
  if (newStatus === PR_STATUS.MERGED || newStatus === PR_STATUS.ABANDONED) {
992
1054
  // Resolve stale 'waiting' review status — won't be polled again after this
@@ -1022,7 +1084,7 @@ async function pollPrStatus(config) {
1022
1084
  // references it. Invariant #14 ('approved is permanent terminal') is
1023
1085
  // preserved: we keep reviewStatus when it was 'approved' rather than
1024
1086
  // carving a wasReopened exception into the 8+ guard sites in
1025
- // engine/lifecycle.js, engine.js, engine/github.js, and engine/ado.js.
1087
+ // engine/orchestration/lifecycle.js, engine.js, engine/providers/github.js, and engine/ado/index.js.
1026
1088
  // Re-review safety on new commits is handled by the pre-existing
1027
1089
  // pr.lastPushedAt > pr.lastReviewedAt path (engine.js alreadyReviewed
1028
1090
  // gate) plus the changes-requested override in lifecycle.js.
@@ -1035,6 +1097,11 @@ async function pollPrStatus(config) {
1035
1097
  // F6 not yet shipped (field absent).
1036
1098
  delete pr.humanFeedback.editsSeen;
1037
1099
  pr.fixDispatched = false;
1100
+ // W-msav5edk00g81710: the record is no longer abandoned, so the
1101
+ // re-probe anchor (and the stale close timestamp it falls back to)
1102
+ // must not survive — a later re-close re-stamps a fresh window.
1103
+ shared.clearPrAbandonedAt(pr);
1104
+ delete pr.closedAt;
1038
1105
  if (pr.reviewStatus !== REVIEW_STATUS.APPROVED) pr.reviewStatus = REVIEW_STATUS.PENDING;
1039
1106
  log('info', `PR ${pr.id} reopened — reset transient state (reviewStatus=${pr.reviewStatus})`);
1040
1107
  }
@@ -1215,6 +1282,11 @@ async function pollPrStatus(config) {
1215
1282
  }
1216
1283
 
1217
1284
  return updated;
1285
+ }, {
1286
+ // W-msav5edk00g81710 — only the STATUS poll re-probes recently-abandoned
1287
+ // records; that is where the host reports the reopen. `0` (operator opt-out)
1288
+ // restores the strict PR_POLLABLE_STATUSES filter.
1289
+ abandonedRepollWindowMs: shared.resolveAbandonedPrRepollWindowMs(config),
1218
1290
  });
1219
1291
 
1220
1292
  if (totalUpdated > 0) {
@@ -1228,13 +1300,13 @@ async function pollPrHumanComments(config) {
1228
1300
  // Load dispatch state once per poll cycle (cached for ~2s by queries.getDispatch).
1229
1301
  // (Previously used by `_isAgentSelfReviewDeclinedComment` to verify dispatch ids;
1230
1302
  // kept loaded in case downstream phases need dispatch context — cheap on cache hit.)
1231
- const queries = require('./queries');
1303
+ const queries = require('../core/queries');
1232
1304
  const dispatch = (() => {
1233
1305
  try { return queries.getDispatch(); }
1234
1306
  catch { return { pending: [], active: [], completed: [] }; }
1235
1307
  })();
1236
1308
  // Viewer login is resolved per-PR (cheap on cache hit) because each slug
1237
- // can route to a different gh account via `engine/gh-token.js`. The cache
1309
+ // can route to a different gh account via `engine/providers/gh-token.js`. The cache
1238
1310
  // is keyed by the resolved account name, so multiple PRs sharing one
1239
1311
  // account pay only the first shell-out.
1240
1312
  const totalUpdated = await forEachActiveGhPr(config, async (project, pr, prNum, slug) => {
@@ -1463,7 +1535,7 @@ async function pollPrHumanComments(config) {
1463
1535
  // `${threadId}:${commentId}` shape so the guard compares a single key
1464
1536
  // form. Falls back to the bare id when `_type` is absent (defensive;
1465
1537
  // every entry above sets it, but the pollPrHumanComments path in
1466
- // engine/ado.js writes the legacy format too).
1538
+ // engine/ado/index.js writes the legacy format too).
1467
1539
  lastProcessedCommentKey: newComments[newComments.length - 1].commentKey || _entryCommentKey(newComments[newComments.length - 1]),
1468
1540
  pendingFix: true,
1469
1541
  feedbackContent,
@@ -1567,7 +1639,8 @@ async function reconcilePrs(config) {
1567
1639
  id: prId,
1568
1640
  prNumber: ghPr.number,
1569
1641
  title: (ghPr.title || `PR #${ghPr.number}`).slice(0, 120),
1570
- agent: (linkedItem?.dispatched_to || ghPr.user?.login || 'unknown').toLowerCase(),
1642
+ agent: (linkedItem?.dispatched_to || shared.PR_AGENT_HUMAN).toLowerCase(),
1643
+ author: shared.buildGithubAuthorIdentity(ghPr.user),
1571
1644
  branch,
1572
1645
  reviewStatus: REVIEW_STATUS.PENDING,
1573
1646
  status: PR_STATUS.ACTIVE,
@@ -1611,7 +1684,8 @@ async function reconcilePrs(config) {
1611
1684
  id: prId,
1612
1685
  prNumber: ghPr.number,
1613
1686
  title: (ghPr.title || `PR #${ghPr.number}`).slice(0, 120),
1614
- agent: (linkedItem?.dispatched_to || ghPr.user?.login || 'unknown').toLowerCase(),
1687
+ agent: (linkedItem?.dispatched_to || shared.PR_AGENT_HUMAN).toLowerCase(),
1688
+ author: shared.buildGithubAuthorIdentity(ghPr.user),
1615
1689
  branch,
1616
1690
  reviewStatus: REVIEW_STATUS.PENDING,
1617
1691
  status: PR_STATUS.ACTIVE,
@@ -1883,12 +1957,12 @@ async function checkLiveBuildAndConflict(pr, project) {
1883
1957
  // ─── One-Shot Startup Reconciliation for Abandoned PRs (W-mp60tw0u000j3931) ───
1884
1958
  //
1885
1959
  // Pairs with the W-mp5trwh60008386d 404-hardening fix in pollPrStatus.
1886
- // pollPrStatus only iterates PR_POLLABLE_STATUSES = {active, linked} — once a
1887
- // PR has status: 'abandoned' it is terminal for the regular poll loop. That
1888
- // behavior is correct (no per-tick re-probing of dead PRs), but it leaves any
1889
- // historical false-flips (e.g. the 16 PRs marked abandoned by the 2026-05-14
1890
- // gh-auth-flip incident) permanently stuck unless something explicitly
1891
- // re-probes the abandoned set.
1960
+ // W-msav5edk00g81710 gave `abandoned` records a BOUNDED re-probe window in the
1961
+ // normal poll (`shared.isPrPollable`), so a recently-abandoned PR that gets
1962
+ // reopened now self-heals within one poll cycle. This pass remains the only
1963
+ // heal path for records OUTSIDE that window — historical false-flips (e.g. the
1964
+ // 16 PRs marked abandoned by the 2026-05-14 gh-auth-flip incident) and anything
1965
+ // abandoned longer ago than `engine.abandonedPrRepollWindowMs`.
1892
1966
  //
1893
1967
  // This function is the explicit re-probe. Same base-repo probe gate as
1894
1968
  // pollPrStatus (cached per slug for the duration of the pass), then a single
@@ -1897,7 +1971,7 @@ async function checkLiveBuildAndConflict(pr, project) {
1897
1971
  // `_reconciliation404Confirmed: true` so we don't re-probe deleted PRs on
1898
1972
  // every future restart.
1899
1973
  //
1900
- // Caller: engine/abandoned-pr-reconciliation.js (version-gated).
1974
+ // Caller: engine/providers/abandoned-pr-reconciliation.js (version-gated).
1901
1975
  async function reconcileAbandonedPrs(config) {
1902
1976
  const projects = shared.getProjects(config).filter(isGitHub);
1903
1977
  let flipped = 0, confirmedDeleted = 0, skipped = 0, errored = 0;
@@ -2010,6 +2084,9 @@ async function reconcileAbandonedPrs(config) {
2010
2084
  delete pr._consecutive404s;
2011
2085
  delete pr._404Count;
2012
2086
  delete pr._404FirstAt;
2087
+ // W-msav5edk00g81710: the record left `abandoned`, so drop the
2088
+ // bounded-re-probe anchor with it.
2089
+ if (upd.newStatus !== PR_STATUS.ABANDONED) shared.clearPrAbandonedAt(pr);
2013
2090
  pr._reconciledAt = reconciledAt;
2014
2091
  pr._reconciledFrom = 'startup-pass';
2015
2092
  } else if (upd.action === 'confirm404') {
@@ -2035,7 +2112,7 @@ module.exports = {
2035
2112
  dismissPriorViewerChangesRequestedReviews, // W-mp7b1g8q000fea45 — clear stale CHANGES_REQUESTED on verdict flip
2036
2113
  isGhThrottled,
2037
2114
  getGhThrottleState,
2038
- ghApi, // P-8c1b6e45 — used by engine/shared-branch-pr-reconcile.js to list open PRs on a feature branch
2115
+ ghApi, // P-8c1b6e45 — used by engine/providers/shared-branch-pr-reconcile.js to list open PRs on a feature branch
2039
2116
  prExists, // issue #246 — confirm a loose ref points at a PR (not an issue) before stamping
2040
2117
  getPrChangedFiles, // W-mr9jo2n2000667ad — stale-PR-pruner scope-overlap verification
2041
2118
  // Exported for testing
@@ -1,20 +1,20 @@
1
1
  /**
2
- * engine/pr-action.js — Projectless PR action validator/handle + read-only
2
+ * engine/providers/pr-action.js — Projectless PR action validator/handle + read-only
3
3
  * dispatch path.
4
4
  *
5
5
  * Plan: projectless-pr-actions, items P-ppa10002 (endpoint + handle) and
6
6
  * P-ppa10003 (the read-only projectless dispatch path). Backs the
7
7
  * user-initiated `POST /api/pr-action { url, action }` endpoint (dashboard.js).
8
8
  * It validates the action against an explicit allowlist, resolves the PR
9
- * reference via the P-ppa10001 resolver (engine/pr-resolve.js#normalizePrRef),
9
+ * reference via the P-ppa10001 resolver (engine/providers/pr-resolve.js#normalizePrRef),
10
10
  * and mints a dispatch handle the caller (CC, dashboard) can observe.
11
11
  *
12
12
  * P-ppa10003 — the read-only dispatch path — lives in `runPrAction`:
13
13
  * - fetches `{ title, body, diff, comments, author }` with NO configured
14
- * project and NO clone (engine/pr-resolve.js#fetchPrPayload),
14
+ * project and NO clone (engine/providers/pr-resolve.js#fetchPrPayload),
15
15
  * - fences all external PR content (body / diff / comments) via
16
- * engine/untrusted-fence.js#wrapUntrusted so it is treated as data,
17
- * - runs a read-only DIRECT LLM call (engine/llm.js#callLLM `{ direct:true }`)
16
+ * engine/core/untrusted-fence.js#wrapUntrusted so it is treated as data,
17
+ * - runs a read-only DIRECT LLM call (engine/agents/llm.js#callLLM `{ direct:true }`)
18
18
  * — no worktree, no repo cloning, no engine.spawnAgent — and
19
19
  * - returns a lightweight dispatch record (status / output / failure_class)
20
20
  * for dashboard observability, mapping a reported injection attempt to a
@@ -29,15 +29,15 @@
29
29
  * (P-ppa10005+) and intentionally NOT reachable from here.
30
30
  */
31
31
 
32
- const shared = require('./shared');
32
+ const shared = require('../core/shared');
33
33
  const prResolve = require('./pr-resolve');
34
34
  const ghComment = require('./gh-comment');
35
- const adoToken = require('./ado-token');
36
- const { wrapUntrusted, buildSource } = require('./untrusted-fence');
35
+ const adoToken = require('../ado/token');
36
+ const { wrapUntrusted, buildSource } = require('../core/untrusted-fence');
37
37
 
38
38
  // Marker attribution for a posted `comment` action. The drafted text is posted
39
39
  // under the shared gh identity, so it MUST carry the minions marker (agentId +
40
- // kind) so engine/github.js classifies it as minions-authored rather than a
40
+ // kind) so engine/providers/github.js classifies it as minions-authored rather than a
41
41
  // human comment. gh-comment validates these against /^[a-z][a-z0-9-]{0,30}$/.
42
42
  const PR_ACTION_COMMENT_AGENT_ID = 'minions';
43
43
  const PR_ACTION_COMMENT_KIND = 'pr-action';
@@ -121,7 +121,7 @@ function buildPrActionFollowups(record, opts = {}) {
121
121
  const messages = {
122
122
  'comment': `Post a PR comment on ${target} — call POST /api/pr-action with {"url":"${target}","action":"comment","execute":true}; this drafts the comment AND posts it to the PR, then show me what was posted (and the comment URL if returned). To preview without posting, add "draftOnly":true.`,
123
123
  'fix-once': `Fix ${target} once — call POST /api/pr-action/fix with {"url":"${target}"}. If it returns status "awaiting-target-choice", show me the execution-target options (clone-keep / temp-clone / remote-patch / devbox) and DO NOT clone until I pick one; if it returns "project-ready", say it will use the existing project fix path. Once I choose, call the same endpoint again with {"url":"${target}","target":"<choice>"}.`,
124
- 'track-auto-fix': `Track ${target} for auto-fix — call POST /api/pr-action/track with {"url":"${target}"}. If it returns status "awaiting-target-choice", show me the execution-target options (Clone & keep is recommended for ongoing tracking — temp clone is discarded after one push) and DO NOT clone until I pick; once "Clone & keep" registers the project, call POST /api/pr-action/track again to finish enrollment. If it returns status "enrolled", tell me the PR is now auto-managed (contextOnly:false) and the existing pollers → review/fix/re-review → auto-merge loop will track it automatically.`,
124
+ 'track-auto-fix': `Track ${target} for auto-fix — call POST /api/pr-action/track with {"url":"${target}"}. If it returns status "awaiting-target-choice", show me the execution-target options (Clone & keep is recommended for ongoing tracking — temp clone is discarded after one push) and DO NOT clone until I pick; once "Clone & keep" registers the project, call POST /api/pr-action/track again to finish enrollment. If it returns status "enrolled", tell me the PR now has Auto Fix and Auto Review enabled and the existing pollers → review/fix/re-review → auto-merge loop will track it automatically.`,
125
125
  };
126
126
  return PR_ACTION_FOLLOWUPS.map((f) => ({
127
127
  kind: f.kind,
@@ -393,7 +393,7 @@ function buildPrActionPrompt(action, payload) {
393
393
  /**
394
394
  * POST a single PR thread comment to ADO. ADO has no `gh`-equivalent CLI for PR
395
395
  * comments, so this hits the REST threads endpoint directly with a bearer token
396
- * from engine/ado-token.js. The body is marker-prepended by the caller.
396
+ * from engine/ado/token.js. The body is marker-prepended by the caller.
397
397
  * `opts._adoPost` / `opts.adoToken` / `opts._acquireAdoToken` are test seams.
398
398
  */
399
399
  async function _defaultAdoPost(url, token, payload) {
@@ -546,7 +546,7 @@ async function runPrAction(input = {}, opts = {}) {
546
546
  }
547
547
 
548
548
  const prompt = buildPrActionPrompt(act, payload);
549
- const callLLM = opts.callLLM || (() => require('./llm').callLLM)();
549
+ const callLLM = opts.callLLM || (() => require('../agents/llm').callLLM)();
550
550
 
551
551
  let res;
552
552
  try {
@@ -555,6 +555,7 @@ async function runPrAction(input = {}, opts = {}) {
555
555
  label: `pr-action:${act}`,
556
556
  maxTurns: 1,
557
557
  allowedTools: '', // read-only: no tools, only the fenced payload
558
+ disableTools: true,
558
559
  timeout: opts.timeout || 180000,
559
560
  engineConfig: opts.engineConfig,
560
561
  });
@@ -1,10 +1,10 @@
1
1
  /**
2
- * engine/pr-clone-keep.js — Clone & keep fix executor: auto-clone a PR's repo and
2
+ * engine/providers/pr-clone-keep.js — Clone & keep fix executor: auto-clone a PR's repo and
3
3
  * register it as a PERSISTENT project so ongoing tracking / auto-fix works.
4
4
  *
5
5
  * Plan: projectless-pr-actions, Phase 2, item P-ppa10007. This is the second
6
6
  * mutating executor in the chain (after the ephemeral temp-clone, P-ppa10006).
7
- * The execution-target choice surface (engine/pr-fix-target.js) routes
7
+ * The execution-target choice surface (engine/providers/pr-fix-target.js) routes
8
8
  * `clone-keep` here once the user has explicitly picked it — the "never clone
9
9
  * silently" invariant lives upstream; by the time we run, the user has chosen to
10
10
  * clone AND keep.
@@ -20,7 +20,7 @@
20
20
  * cloned.
21
21
  * executeCloneKeep(plan, opts) — clone the BASE repo into the persistent dir
22
22
  * and register it as a project via
23
- * engine/projects.js#addProject. The repo then
23
+ * engine/planning/projects.js#addProject. The repo then
24
24
  * becomes `project-ready` so the fix itself
25
25
  * flows through the engine's normal project fix
26
26
  * path. On registration failure the partial
@@ -51,7 +51,7 @@
51
51
  const fs = require('fs');
52
52
  const path = require('path');
53
53
 
54
- const shared = require('./shared');
54
+ const shared = require('../core/shared');
55
55
  const prResolve = require('./pr-resolve');
56
56
  const ghToken = require('./gh-token');
57
57
  const prFixTarget = require('./pr-fix-target');
@@ -107,7 +107,7 @@ function _baseCloneUrl(ref, branches) {
107
107
  * the persistent project dir + name. NOTHING is created or cloned here.
108
108
  *
109
109
  * `input` — `{ url }`, a normalized ref, or a routed plan from
110
- * engine/pr-fix-target.js#resolvePrFixTarget.
110
+ * engine/providers/pr-fix-target.js#resolvePrFixTarget.
111
111
  * `opts.fetchPrBranches` — override the branch fetch (test seam).
112
112
  * `opts.prUrl` — original user URL (preferred for surfacing).
113
113
  * `opts.cloneRoot` — override the persistent-clone root.
@@ -158,7 +158,7 @@ async function _resolveToken(plan, opts = {}) {
158
158
  return resolve(plan.ref.slug) || null;
159
159
  }
160
160
  if (plan.ref.host === 'ado') {
161
- const acquire = opts._acquireAdoToken || require('./ado-token').acquireAdoToken;
161
+ const acquire = opts._acquireAdoToken || require('../ado/token').acquireAdoToken;
162
162
  const acquired = await acquire();
163
163
  return (acquired && acquired.token) || null;
164
164
  }
@@ -296,7 +296,7 @@ async function executeCloneKeep(planOrInput = {}, opts = {}) {
296
296
  }
297
297
 
298
298
  const gitClone = opts.gitClone || ((p, token) => _defaultGitClone(p, token, opts));
299
- const addProject = opts.addProject || ((target, registerOptions) => require('./projects').addProject(target, registerOptions));
299
+ const addProject = opts.addProject || ((target, registerOptions) => require('../planning/projects').addProject(target, registerOptions));
300
300
  const mkdir = opts._mkdir || ((dir) => fs.promises.mkdir(dir, { recursive: true }));
301
301
 
302
302
  const rec = _record(plan, {});
@@ -1,10 +1,10 @@
1
1
  /**
2
- * engine/pr-devbox.js — DevBox execution target (the "never touches your machine"
2
+ * engine/providers/pr-devbox.js — DevBox execution target (the "never touches your machine"
3
3
  * option for a projectless one-off PR fix).
4
4
  *
5
5
  * Plan: projectless-pr-actions, Phase 4 (stretch), item P-ppa10011. Like
6
- * engine/pr-temp-clone.js this is an EXECUTOR the choice surface
7
- * (engine/pr-fix-target.js) routes to once the user has EXPLICITLY picked it —
6
+ * engine/providers/pr-temp-clone.js this is an EXECUTOR the choice surface
7
+ * (engine/providers/pr-fix-target.js) routes to once the user has EXPLICITLY picked it —
8
8
  * the "never clone silently" invariant lives upstream; by the time we run, the
9
9
  * user has chosen the DevBox target.
10
10
  *
@@ -39,10 +39,10 @@
39
39
  * success, failure, or noop — so nothing persists on the DevBox either.
40
40
  * - EXPLICIT CONSENT ONLY: DevBox is never the default target and is never
41
41
  * auto-selected; it only runs when the user picks `devbox` at the choice
42
- * surface (engine/pr-fix-target.js).
42
+ * surface (engine/providers/pr-fix-target.js).
43
43
  */
44
44
 
45
- const shared = require('./shared');
45
+ const shared = require('../core/shared');
46
46
  const prResolve = require('./pr-resolve');
47
47
  const { PrActionError } = require('./pr-action');
48
48
 
@@ -86,7 +86,7 @@ function _remoteJoin(root, name) {
86
86
  * created on the local machine and NOTHING is cloned here.
87
87
  *
88
88
  * `input` — `{ url }`, a normalized ref, or a routed plan from
89
- * engine/pr-fix-target.js#resolvePrFixTarget.
89
+ * engine/providers/pr-fix-target.js#resolvePrFixTarget.
90
90
  * `opts.fetchPrBranches` — override the branch fetch (test seam).
91
91
  * `opts.prUrl` — original user URL (preferred for surfacing).
92
92
  * `opts.remoteTmpRoot` — override the DevBox-side ephemeral-clone root.