@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
@@ -0,0 +1,336 @@
1
+ /**
2
+ * watch-plugins/ado-author-prs.js — Watch an ADO repo for open PRs by author(s)
3
+ *
4
+ * Mirrors watch-plugins/gh-author-prs.js but speaks the Azure DevOps REST API
5
+ * via engine/ado/index.js (`adoFetch`) + engine/ado/token.js (`acquireAdoToken`).
6
+ *
7
+ * ── Target syntax ───────────────────────────────────────────────────────────
8
+ * target: { projectName: 'my-project', author?: 'alice@example.com' }
9
+ * → resolves `project` from state.config.projects by name. When
10
+ * `author` is omitted, the plugin uses every entry in
11
+ * `project.observeAuthors`. When `author` is supplied, only that
12
+ * author is observed.
13
+ *
14
+ * target: 'projectName:author' — bare colon-delimited shorthand.
15
+ *
16
+ * ── Fail-closed gate (W-mq8li79a000889fa) ──────────────────────────────────
17
+ * fetchEntity inspects the resolved project on every call. When the project
18
+ * is missing `repositoryId` (required for the ADO PR list endpoint) OR has
19
+ * an empty effective author list, the plugin returns an entity carrying
20
+ * a clear `error` string. The error propagates into the watch's
21
+ * `_lastState.lastError` so the dashboard surface it ("Watch errored: …")
22
+ * instead of silently swallowing the misconfiguration.
23
+ *
24
+ * ── Conditions ──────────────────────────────────────────────────────────────
25
+ * new-pr — fires once per newly-observed open PR id. Drafts are ignored.
26
+ *
27
+ * Burst handling: same as gh-author-prs — captureState advances by exactly
28
+ * one new PR per tick so a burst drains losslessly across ticks (one fire
29
+ * per tick).
30
+ *
31
+ * ── Fetch model ─────────────────────────────────────────────────────────────
32
+ * checkWatches is synchronous and runs inside mutateWatches's lock callback,
33
+ * so the plugin maintains a module-level background-fetch cache identical
34
+ * to gh-author-prs. `fetchEntity` is sync: it returns the cached entity and,
35
+ * when the cached value is older than 60s (or absent), kicks off a
36
+ * background ADO REST refresh.
37
+ *
38
+ * Tests can short-circuit the background fetch entirely by pre-populating
39
+ * `state.adoAuthorPrs[targetKey]` with the entity shape used here
40
+ * (`{ prs, fetchedAt, error? }`). Tests can also override the underlying
41
+ * lister via `setLister(fn)` to drive the cache path.
42
+ *
43
+ * Auth: ADO tokens via `engine/ado/token.js#acquireAdoToken` (az CLI first,
44
+ * azureauth fallback). NEVER `az login` from this plugin — auth setup is
45
+ * the user's responsibility; we fail closed when the token isn't available.
46
+ */
47
+
48
+ function ts() { return new Date().toISOString(); }
49
+ function _warn(msg) { try { console.warn(`[${ts()}] [warn] ${msg}`); } catch {} }
50
+
51
+ const CACHE_FRESHNESS_MS = 60 * 1000;
52
+ const SEEN_NUMBERS_CAP = 500;
53
+
54
+ // Module-level caches. Survive across ticks; reset on engine restart.
55
+ const _entityCache = new Map();
56
+ const _inFlight = new Set();
57
+
58
+ // Override-able lister — default uses the real ADO REST endpoint.
59
+ let _lister = _defaultAdoPrList;
60
+ function setLister(fn) { _lister = typeof fn === 'function' ? fn : _defaultAdoPrList; }
61
+ function _resetCache() { _entityCache.clear(); _inFlight.clear(); }
62
+
63
+ function _findProjectByName(projects, name) {
64
+ if (!Array.isArray(projects) || !name) return null;
65
+ const needle = String(name).toLowerCase();
66
+ return projects.find(p => p && String(p.name || '').toLowerCase() === needle) || null;
67
+ }
68
+
69
+ function _validateTarget(target) {
70
+ if (typeof target === 'string') {
71
+ const value = target.trim();
72
+ if (!value) return 'target must include a project name';
73
+ const separator = value.indexOf(':');
74
+ if (separator >= 0
75
+ && (!value.slice(0, separator).trim() || !value.slice(separator + 1).trim())) {
76
+ return 'target shorthand must be "projectName:author"';
77
+ }
78
+ return null;
79
+ }
80
+ if (!target || typeof target !== 'object' || Array.isArray(target)) {
81
+ return 'target must be a project string or an object with projectName';
82
+ }
83
+ const projectName = target.projectName ?? target.project;
84
+ if (typeof projectName !== 'string' || !projectName.trim()) {
85
+ return 'target.projectName must be a non-empty string';
86
+ }
87
+ if (target.author !== undefined
88
+ && (typeof target.author !== 'string' || !target.author.trim())) {
89
+ return 'target.author must be a non-empty string when provided';
90
+ }
91
+ return null;
92
+ }
93
+
94
+ // Resolve { project, authors[] } from a watch target + state.config.
95
+ // Returns { error: '...' } when the target is malformed, the project is
96
+ // unknown, or the project is missing required fields.
97
+ function _resolveContext(target, state) {
98
+ let projectName, author;
99
+ if (typeof target === 'string') {
100
+ const idx = target.indexOf(':');
101
+ if (idx > 0) {
102
+ projectName = target.slice(0, idx).trim();
103
+ author = target.slice(idx + 1).trim() || null;
104
+ } else {
105
+ projectName = target.trim();
106
+ author = null;
107
+ }
108
+ } else if (target && typeof target === 'object') {
109
+ projectName = String(target.projectName || target.project || '').trim();
110
+ author = target.author ? String(target.author).trim() : null;
111
+ } else {
112
+ return { error: 'invalid target: expected "projectName:author" or { projectName, author? }' };
113
+ }
114
+ if (!projectName) return { error: 'invalid target: projectName required' };
115
+
116
+ const project = _findProjectByName(state && state.config && state.config.projects, projectName);
117
+ if (!project) {
118
+ return { error: `unknown project "${projectName}" — not present in config.projects` };
119
+ }
120
+ if (String(project.repoHost || '').toLowerCase() !== 'ado') {
121
+ return { error: `project "${projectName}" is not an ADO project (repoHost=${project.repoHost})` };
122
+ }
123
+
124
+ const missing = [];
125
+ if (!String(project.repositoryId || '').trim()) missing.push('repositoryId');
126
+ if (!String(project.adoOrg || '').trim()) missing.push('adoOrg');
127
+ if (!String(project.adoProject || '').trim()) missing.push('adoProject');
128
+ if (missing.length > 0) {
129
+ return { error: `project "${projectName}" missing required field(s): ${missing.join(', ')}` };
130
+ }
131
+
132
+ let authors;
133
+ if (author) {
134
+ authors = [author];
135
+ } else {
136
+ authors = Array.isArray(project.observeAuthors)
137
+ ? project.observeAuthors.map(v => String(v || '').trim()).filter(Boolean)
138
+ : [];
139
+ }
140
+ if (authors.length === 0) {
141
+ return { error: `project "${projectName}" missing observeAuthors (and no inline author supplied in watch target)` };
142
+ }
143
+
144
+ return { project, authors };
145
+ }
146
+
147
+ function _targetKey(target, project) {
148
+ if (project && project.repositoryId) {
149
+ const author = (typeof target === 'object' && target.author) ? target.author : '*';
150
+ return `${project.repositoryId}::${author}`;
151
+ }
152
+ if (typeof target === 'string') return target;
153
+ try { return JSON.stringify(target); } catch { return String(target); }
154
+ }
155
+
156
+ // Default lister — uses the ADO REST endpoint via engine/ado/index.js +
157
+ // engine/ado/token.js. Returns { prs, fetchedAt, error? }.
158
+ async function _defaultAdoPrList(project, authors) {
159
+ let adoToken, adoFetch;
160
+ try {
161
+ ({ acquireAdoToken: adoToken } = require('../engine/ado/token'));
162
+ ({ adoFetch } = require('../engine/ado'));
163
+ } catch (e) {
164
+ return { prs: [], error: `ado-author-prs: required engine modules unavailable: ${e.message}`, fetchedAt: ts() };
165
+ }
166
+ let tokenInfo;
167
+ try {
168
+ tokenInfo = await adoToken({});
169
+ } catch (e) {
170
+ return { prs: [], error: `ado-author-prs: token acquisition failed: ${e.message}`, fetchedAt: ts() };
171
+ }
172
+ if (!tokenInfo || !tokenInfo.token) {
173
+ return { prs: [], error: 'ado-author-prs: no ADO token available', fetchedAt: ts() };
174
+ }
175
+
176
+ const orgBase = `https://dev.azure.com/${encodeURIComponent(project.adoOrg)}`;
177
+ const url = `${orgBase}/${encodeURIComponent(project.adoProject)}/_apis/git/repositories/${encodeURIComponent(project.repositoryId)}/pullrequests?searchCriteria.status=active&$top=200&api-version=7.1`;
178
+ let data;
179
+ try {
180
+ data = await adoFetch(url, tokenInfo.token, { timeout: 15000 });
181
+ } catch (e) {
182
+ return { prs: [], error: `ado-author-prs: ADO REST failed: ${e.message}`, fetchedAt: ts() };
183
+ }
184
+ const all = Array.isArray(data && data.value) ? data.value : [];
185
+ const authorSet = new Set(authors.map(a => a.toLowerCase()));
186
+ const prs = all
187
+ .filter(pr => {
188
+ if (!pr || typeof pr.pullRequestId !== 'number') return false;
189
+ if (pr.isDraft) return false;
190
+ const c = pr.createdBy || {};
191
+ const identifiers = [c.uniqueName, c.mailAddress, c.displayName, c.principalName, c.id]
192
+ .map(v => String(v || '').toLowerCase());
193
+ return identifiers.some(id => authorSet.has(id));
194
+ })
195
+ .map(pr => ({
196
+ number: pr.pullRequestId,
197
+ title: String(pr.title || ''),
198
+ url: String(pr.url || pr._links?.web?.href || ''),
199
+ branch: String(pr.sourceRefName || '').replace(/^refs\/heads\//, ''),
200
+ author: String(pr.createdBy?.uniqueName || pr.createdBy?.mailAddress || pr.createdBy?.displayName || ''),
201
+ createdAt: String(pr.creationDate || ''),
202
+ sourceCommitId: String(pr.lastMergeSourceCommit?.commitId || ''),
203
+ isDraft: Boolean(pr.isDraft),
204
+ }));
205
+ return { prs, fetchedAt: ts() };
206
+ }
207
+
208
+ function _scheduleRefresh(target, state, key, ctx) {
209
+ if (_inFlight.has(key)) return;
210
+ _inFlight.add(key);
211
+ Promise.resolve()
212
+ .then(() => _lister(ctx.project, ctx.authors))
213
+ .then((entity) => { _entityCache.set(key, entity); })
214
+ .catch((e) => {
215
+ _entityCache.set(key, { prs: [], error: e && e.message ? e.message : String(e), fetchedAt: ts() });
216
+ })
217
+ .then(() => { _inFlight.delete(key); });
218
+ }
219
+
220
+ function fetchEntity(target, state) {
221
+ const ctx = _resolveContext(target, state);
222
+ if (ctx.error) {
223
+ // Surface validation errors directly — never reach the network when
224
+ // the project shape is invalid. Tests assert this path.
225
+ return { prs: [], error: ctx.error, fetchedAt: ts(), _target: target };
226
+ }
227
+ const key = _targetKey(target, ctx.project);
228
+ // Test/external override path.
229
+ if (state && state.adoAuthorPrs && Object.prototype.hasOwnProperty.call(state.adoAuthorPrs, key)) {
230
+ const inj = state.adoAuthorPrs[key];
231
+ return inj ? { ...inj, _target: target, _project: ctx.project } : null;
232
+ }
233
+ const cached = _entityCache.get(key);
234
+ const cachedAge = cached && cached.fetchedAt
235
+ ? Date.now() - new Date(cached.fetchedAt).getTime()
236
+ : Infinity;
237
+ if (!cached || cachedAge > CACHE_FRESHNESS_MS) {
238
+ _scheduleRefresh(target, state, key, ctx);
239
+ }
240
+ return cached ? { ...cached, _target: target, _project: ctx.project } : null;
241
+ }
242
+
243
+ // captureState — same burst-drain semantics as gh-author-prs: at most one
244
+ // new PR id advances per tick so a backlog of N opens fires once per tick
245
+ // for N ticks rather than swallowing N-1 PRs.
246
+ function captureState(entity, prevState) {
247
+ const prevNumbers = Array.isArray(prevState && prevState.numbers) ? prevState.numbers : [];
248
+ if (!entity) return { numbers: prevNumbers };
249
+ const seen = new Set(prevNumbers);
250
+ const currentReady = (entity.prs || [])
251
+ .filter(p => !p.isDraft)
252
+ .map(p => p.number)
253
+ .sort((a, b) => a - b);
254
+ const newOnes = currentReady.filter(n => !seen.has(n));
255
+ let next;
256
+ if (newOnes.length === 0) {
257
+ next = prevNumbers.slice();
258
+ } else {
259
+ next = [...seen, newOnes[0]].sort((a, b) => a - b);
260
+ }
261
+ if (next.length > SEEN_NUMBERS_CAP) {
262
+ next = next.slice(next.length - SEEN_NUMBERS_CAP);
263
+ }
264
+ return {
265
+ numbers: next,
266
+ lastFetchedAt: entity.fetchedAt || null,
267
+ lastError: entity.error || null,
268
+ };
269
+ }
270
+
271
+ function _firstUnseenReadyPr(entity, prevState) {
272
+ if (!entity) return null;
273
+ const prevNumbers = Array.isArray(prevState && prevState.numbers) ? prevState.numbers : [];
274
+ const seen = new Set(prevNumbers);
275
+ const candidates = (entity.prs || [])
276
+ .filter(p => !p.isDraft && !seen.has(p.number))
277
+ .sort((a, b) => a.number - b.number);
278
+ return candidates[0] || null;
279
+ }
280
+
281
+ function evaluate(condition, entity, prevState, target) {
282
+ if (condition !== 'new-pr') return { triggered: false, message: '' };
283
+ if (!entity) return { triggered: false, message: '' };
284
+ if (entity.error) return { triggered: false, message: '' };
285
+ const newPr = _firstUnseenReadyPr(entity, prevState);
286
+ if (!newPr) return { triggered: false, message: '' };
287
+ const projectName = (entity._project && entity._project.name)
288
+ || (typeof target === 'object' && target.projectName)
289
+ || (typeof target === 'string' && target.split(':')[0])
290
+ || '<unknown>';
291
+ return {
292
+ triggered: true,
293
+ message: `New ADO PR on ${projectName} #${newPr.number}: ${newPr.title}`,
294
+ };
295
+ }
296
+
297
+ function contextVars(entity, prevState, _newState) {
298
+ const newPr = _firstUnseenReadyPr(entity, prevState);
299
+ if (!newPr) return {};
300
+ const project = entity && entity._project;
301
+ return {
302
+ prNumber: newPr.number,
303
+ prTitle: newPr.title,
304
+ prUrl: newPr.url,
305
+ prBranch: newPr.branch,
306
+ prAuthor: newPr.author || '',
307
+ prHeadSha: newPr.sourceCommitId || '',
308
+ prCreatedAt: newPr.createdAt || '',
309
+ prIsDraft: false,
310
+ projectName: project ? project.name : '',
311
+ adoOrg: project ? project.adoOrg : '',
312
+ adoProject: project ? project.adoProject : '',
313
+ repositoryId: project ? project.repositoryId : '',
314
+ };
315
+ }
316
+
317
+ module.exports = {
318
+ name: 'ado-author-prs',
319
+ spec: {
320
+ label: 'ADO PRs by Author',
321
+ description: 'Poll an ADO repo for non-draft active PRs by configured authors. Requires project.repositoryId and project.observeAuthors (or inline author in target). Fires once per newly-observed PR id.',
322
+ conditions: ['new-pr'],
323
+ absoluteConditions: [],
324
+ validateTarget: _validateTarget,
325
+ fetchEntity,
326
+ captureState,
327
+ evaluate,
328
+ contextVars,
329
+ },
330
+ // Exported for tests
331
+ _resolveContext,
332
+ _targetKey,
333
+ _firstUnseenReadyPr,
334
+ setLister,
335
+ _resetCache,
336
+ };