@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.
- package/bin/cli-api-client.js +1 -1
- package/bin/install-internal-minions.js +1382 -44
- package/bin/install-layout.js +150 -0
- package/bin/minions.js +460 -167
- package/dashboard/docs/typography.md +65 -12
- package/dashboard/js/command-center.js +58 -4
- package/dashboard/js/detail-panel.js +36 -0
- package/dashboard/js/memory-panel.js +59 -12
- package/dashboard/js/qa.js +179 -20
- package/dashboard/js/refresh.js +148 -12
- package/dashboard/js/render-dispatch.js +3 -4
- package/dashboard/js/render-inbox.js +2 -2
- package/dashboard/js/render-other.js +3 -3
- package/dashboard/js/render-pipelines.js +14 -0
- package/dashboard/js/render-plans.js +57 -9
- package/dashboard/js/render-prd.js +132 -23
- package/dashboard/js/render-prs.js +195 -166
- package/dashboard/js/render-schedules.js +63 -3
- package/dashboard/js/render-utils.js +3 -3
- package/dashboard/js/render-watches.js +19 -3
- package/dashboard/js/render-work-items.js +238 -30
- package/dashboard/js/settings.js +205 -54
- package/dashboard/js/utils.js +51 -1
- package/dashboard/pages/home.html +1 -1
- package/dashboard/pages/qa.html +1 -16
- package/dashboard/pages/work.html +40 -0
- package/dashboard/shared/cc-limits.js +79 -0
- package/dashboard/shared/pr-filters.js +21 -38
- package/dashboard/shared/project-git-summary.js +1 -1
- package/dashboard/shared/record-filters.js +169 -0
- package/dashboard/shared/watches-source.js +1 -1
- package/dashboard/shared/welcome-popup.js +1 -1
- package/dashboard/shared/wi-filters.js +302 -0
- package/dashboard/slim/body.html +1 -0
- package/dashboard/slim/js/command-send.js +26 -0
- package/dashboard/slim/js/modals-tiles.js +380 -39
- package/dashboard/slim/js/status.js +13 -21
- package/dashboard/slim/layout.html +1 -0
- package/dashboard/slim/panel-bootstrap.js +6 -2
- package/dashboard/slim/styles.css +38 -0
- package/dashboard/styles.css +159 -55
- package/dashboard-build.js +13 -2
- package/dashboard.js +956 -423
- package/docs/README.md +11 -6
- package/docs/api-errors.md +2 -2
- package/docs/architecture-review-2026-07-09.md +1 -1
- package/docs/architecture.excalidraw +2 -2
- package/docs/auto-discovery.md +18 -9
- package/docs/branch-derivation.md +4 -4
- package/docs/capture-demos.js +39 -2
- package/docs/ci-runner-canary.md +123 -0
- package/docs/claude-md-propagation.md +2 -2
- package/docs/cloud-agent-dispatch.md +204 -0
- package/docs/command-center.md +7 -7
- package/docs/completion-reports.md +43 -20
- package/docs/constants.md +10 -3
- package/docs/constellation-bridge.md +134 -6
- package/docs/constellation-style-telemetry.md +4 -4
- package/docs/contracts/capability-protocol.v1.json +165 -0
- package/docs/cooldown-merge-semantics.md +12 -12
- package/docs/copilot-cli-schema.md +7 -7
- package/docs/cross-repo-plans.md +10 -10
- package/docs/dead-code-audit-retractions.md +5 -5
- package/docs/default-branch-ci.md +173 -0
- package/docs/deprecated.json +31 -31
- package/docs/design-inbox-entries-schema.md +3 -3
- package/docs/design-language.md +1051 -0
- package/docs/design-state-storage.md +11 -11
- package/docs/diagnostics-crash-reports.md +9 -9
- package/docs/diagnostics-memory.md +5 -5
- package/docs/documentation-audit-2026-07-09.md +7 -7
- package/docs/engine-restart.md +90 -5
- package/docs/harness-mode.md +1 -1
- package/docs/internal-install.md +338 -39
- package/docs/kb-dedup-duplicate-pair-investigation.md +5 -5
- package/docs/kb-pr3223-cascade-archiving.md +1 -1
- package/docs/kb-pr696-merge-conflict-docs.md +6 -6
- package/docs/kb-sweep.md +35 -35
- package/docs/keep-processes.md +1 -1
- package/docs/live-checkout-mode.md +30 -30
- package/docs/managed-spawn.md +18 -14
- package/docs/named-agents.md +7 -7
- package/docs/plan-lifecycle.md +69 -2
- package/docs/pr-author-identity.md +114 -0
- package/docs/pr-auto-fix-dispatch.md +19 -4
- package/docs/pr-comment-followup.md +6 -6
- package/docs/pr-review-fix-loop.md +59 -10
- package/docs/process-termination.md +40 -0
- package/docs/proposals/repo-pool-for-live-checkout.md +13 -13
- package/docs/qa-runbook-lifecycle.md +367 -17
- package/docs/qa-runbooks.md +3 -3
- package/docs/rfc-completion-json.md +18 -18
- package/docs/runtime-adapters.md +26 -21
- package/docs/security.md +6 -6
- package/docs/self-improvement.md +4 -4
- package/docs/shared-lifecycle-module-map.md +473 -472
- package/docs/skills.md +52 -3
- package/docs/slim-ux/concepts.md +121 -116
- package/docs/specs/agent-configurability.md +18 -18
- package/docs/specs/agent-rename.md +18 -18
- package/docs/team-memory.md +38 -21
- package/docs/timeouts-and-liveness.md +118 -10
- package/docs/tutorials/01-install-and-connect.md +1 -1
- package/docs/watches.md +40 -39
- package/docs/workspace-manifests.md +4 -4
- package/docs/worktree-lifecycle.md +293 -14
- package/engine/README.md +46 -0
- package/engine/{ado-comment.js → ado/comment.js} +8 -8
- package/engine/{ado-git-auth.js → ado/git-auth.js} +4 -4
- package/engine/{ado.js → ado/index.js} +417 -63
- package/engine/{ado-status.js → ado/status.js} +6 -8
- package/engine/{ado-token.js → ado/token.js} +1 -1
- package/engine/{acp-transport.js → agents/acp-transport.js} +62 -22
- package/engine/{agent-worker-pool.js → agents/agent-worker-pool.js} +17 -8
- package/engine/{cc-worker-pool.js → agents/cc-worker-pool.js} +16 -6
- package/engine/{claude-md-context.js → agents/claude-md-context.js} +5 -5
- package/engine/{harness-context.js → agents/harness-context.js} +5 -5
- package/engine/{harness.js → agents/harness.js} +3 -3
- package/engine/{llm.js → agents/llm.js} +18 -14
- package/engine/{model-discovery.js → agents/model-discovery.js} +2 -2
- package/engine/{playbook.js → agents/playbook.js} +155 -22
- package/engine/{pooled-agent-process.js → agents/pooled-agent-process.js} +14 -12
- package/engine/{preflight.js → agents/preflight.js} +29 -10
- package/engine/{spawn-agent.js → agents/spawn-agent.js} +25 -14
- package/engine/{spawn-phase-watchdog.js → agents/spawn-phase-watchdog.js} +16 -7
- package/engine/{steering.js → agents/steering.js} +5 -5
- package/engine/{tools-inventory.js → agents/tools-inventory.js} +2 -2
- package/engine/{agent-api-validation.js → api/agent-api-validation.js} +2 -2
- package/engine/{api-validation.js → api/api-validation.js} +1 -1
- package/engine/api/bridge.js +787 -0
- package/engine/{cc-api-validation.js → api/cc-api-validation.js} +1 -1
- package/engine/api/companion.js +560 -0
- package/engine/{content-api-validation.js → api/content-api-validation.js} +2 -2
- package/engine/{pr-issue-validation.js → api/pr-issue-validation.js} +33 -6
- package/engine/{settings-validation.js → api/settings-validation.js} +32 -4
- package/engine/api-contracts/agent-content.js +4 -4
- package/engine/api-contracts/capability-manifest.js +236 -0
- package/engine/api-contracts/capability-protocol.js +333 -0
- package/engine/api-contracts/cc-ops.js +1 -1
- package/engine/api-contracts/config-runtime.js +5 -0
- package/engine/api-contracts/core.js +28 -1
- package/engine/api-contracts/index.js +100 -0
- package/engine/api-contracts/orchestration.js +18 -5
- package/engine/api-contracts/pull-requests.js +37 -6
- package/engine/api-contracts/qa-process.js +29 -6
- package/engine/api-contracts/work-plan-prd.js +21 -1
- package/engine/cloud/contract.js +212 -0
- package/engine/cloud/index.js +159 -0
- package/engine/{execution-model.js → core/execution-model.js} +1 -1
- package/engine/{features.js → core/features.js} +4 -4
- package/engine/{operator-identity.js → core/operator-identity.js} +1 -1
- package/engine/{queries.js → core/queries.js} +201 -36
- package/engine/{safe-expr.js → core/safe-expr.js} +1 -1
- package/engine/{shared.js → core/shared.js} +1637 -175
- package/engine/{stdio-timestamps.js → core/stdio-timestamps.js} +1 -1
- package/engine/{untrusted-fence.js → core/untrusted-fence.js} +3 -3
- package/engine/db/index.js +11 -2
- package/engine/db/migrations/002-dispatches.js +3 -3
- package/engine/db/migrations/003-work-items.js +1 -1
- package/engine/db/migrations/004-pull-requests.js +1 -1
- package/engine/db/migrations/006-metrics.js +1 -1
- package/engine/db/migrations/007-watches.js +2 -2
- package/engine/db/migrations/008-small-state.js +1 -1
- package/engine/db/migrations/009-qa.js +1 -1
- package/engine/db/migrations/010-pr-links.js +1 -1
- package/engine/db/migrations/011-remaining-state.js +1 -1
- package/engine/db/migrations/012-steering-deliveries.js +2 -2
- package/engine/db/migrations/013-backfill-broken-note-links.js +1 -1
- package/engine/db/migrations/014-pr-fix-target-prefs.js +2 -2
- package/engine/db/migrations/015-plans-prds.js +0 -0
- package/engine/db/migrations/018-sql-only-cutover.js +2 -2
- package/engine/db/migrations/021-archived-work-items.js +1 -1
- package/engine/db/migrations/022-global-cc-session.js +1 -1
- package/engine/db/migrations/023-engine-state.js +1 -1
- package/engine/db/migrations/025-malformed-work-item-phantoms.js +1 -1
- package/engine/db/migrations/027-review-learning-lifecycle.js +1 -1
- package/engine/db/migrations/029-repair-reused-versions.js +20 -0
- package/engine/db/migrations/031-pr-author-identity.js +137 -0
- package/engine/{consolidation.js → memory/consolidation.js} +6 -6
- package/engine/{kb-sweep-runner.js → memory/kb-sweep-runner.js} +2 -2
- package/engine/{kb-sweep.js → memory/kb-sweep.js} +9 -7
- package/engine/{memory-retrieval.js → memory/memory-retrieval.js} +46 -4
- package/engine/{memory-store.js → memory/memory-store.js} +3 -3
- package/engine/{promotion.js → memory/promotion.js} +3 -3
- package/engine/{review-learning-backfill.js → memory/review-learning-backfill.js} +6 -6
- package/engine/{review-learning.js → memory/review-learning.js} +10 -5
- package/engine/{diagnostics-memory.js → observability/diagnostics-memory.js} +1 -1
- package/engine/{logs-store.js → observability/logs-store.js} +5 -5
- package/engine/{metrics-store.js → observability/metrics-store.js} +4 -4
- package/engine/{check-status.js → operations/check-status.js} +3 -3
- package/engine/{cli.js → operations/cli.js} +271 -113
- package/engine/{distribution.js → operations/distribution.js} +5 -6
- package/engine/{cleanup.js → orchestration/cleanup.js} +72 -45
- package/engine/{cooldown.js → orchestration/cooldown.js} +5 -5
- package/engine/{dispatch-events.js → orchestration/dispatch-events.js} +2 -2
- package/engine/{dispatch.js → orchestration/dispatch.js} +129 -36
- package/engine/orchestration/failed-scheduled-cleanup.js +274 -0
- package/engine/{lifecycle.js → orchestration/lifecycle.js} +198 -90
- package/engine/{meeting.js → orchestration/meeting.js} +6 -16
- package/engine/{pipeline.js → orchestration/pipeline.js} +12 -12
- package/engine/{pre-dispatch-eval.js → orchestration/pre-dispatch-eval.js} +10 -9
- package/engine/{routing.js → orchestration/routing.js} +3 -3
- package/engine/{schedule-bootstrap.js → orchestration/schedule-bootstrap.js} +4 -4
- package/engine/{scheduler.js → orchestration/scheduler.js} +38 -8
- package/engine/{timeout.js → orchestration/timeout.js} +158 -109
- package/engine/{db-events.js → persistence/db-events.js} +2 -2
- package/engine/{dispatch-store.js → persistence/dispatch-store.js} +7 -7
- package/engine/{inbox-store.js → persistence/inbox-store.js} +2 -2
- package/engine/{note-link-backfill.js → persistence/note-link-backfill.js} +4 -4
- package/engine/{pr-fix-target-store.js → persistence/pr-fix-target-store.js} +8 -8
- package/engine/{pull-requests-store.js → persistence/pull-requests-store.js} +21 -7
- package/engine/{small-state-store.js → persistence/small-state-store.js} +31 -31
- package/engine/persistence/state-operations.js +350 -0
- package/engine/{steering-store.js → persistence/steering-store.js} +6 -6
- package/engine/{issues.js → planning/issues.js} +2 -2
- package/engine/{plan-prd-validation.js → planning/plan-prd-validation.js} +8 -2
- package/engine/planning/prd-result-sidecar.js +190 -0
- package/engine/{prd-store.js → planning/prd-store.js} +17 -17
- package/engine/{project-discovery.js → planning/project-discovery.js} +5 -5
- package/engine/{projects.js → planning/projects.js} +10 -10
- package/engine/{resolve-area.js → planning/resolve-area.js} +1 -1
- package/engine/{work-item-validation.js → planning/work-item-validation.js} +39 -3
- package/engine/{work-items-store.js → planning/work-items-store.js} +29 -21
- package/engine/{keep-process-sweep.js → processes/keep-process-sweep.js} +57 -17
- package/engine/{managed-spawn-launcher.js → processes/managed-spawn-launcher.js} +3 -3
- package/engine/{managed-spawn.js → processes/managed-spawn.js} +97 -46
- package/engine/{process-utils.js → processes/process-utils.js} +599 -55
- package/engine/{abandoned-pr-reconciliation.js → providers/abandoned-pr-reconciliation.js} +17 -7
- package/engine/{comment-classifier.js → providers/comment-classifier.js} +85 -17
- package/engine/{comment-format.js → providers/comment-format.js} +5 -5
- package/engine/{gh-comment.js → providers/gh-comment.js} +15 -15
- package/engine/{gh-token.js → providers/gh-token.js} +4 -4
- package/engine/{github.js → providers/github.js} +131 -54
- package/engine/{pr-action.js → providers/pr-action.js} +13 -12
- package/engine/{pr-clone-keep.js → providers/pr-clone-keep.js} +7 -7
- package/engine/{pr-devbox.js → providers/pr-devbox.js} +6 -6
- package/engine/{pr-fix-target.js → providers/pr-fix-target.js} +13 -13
- package/engine/{pr-remote-patch.js → providers/pr-remote-patch.js} +4 -4
- package/engine/{pr-resolve.js → providers/pr-resolve.js} +7 -7
- package/engine/{pr-temp-clone.js → providers/pr-temp-clone.js} +5 -5
- package/engine/{pr-track.js → providers/pr-track.js} +11 -13
- package/engine/{shared-branch-pr-reconcile.js → providers/shared-branch-pr-reconcile.js} +4 -4
- package/engine/qa/auto-prd-qa.js +313 -0
- package/engine/{qa-from-prd.js → qa/from-prd.js} +42 -12
- package/engine/qa/prd-session.js +240 -0
- package/engine/{qa-process-validation.js → qa/process-validation.js} +14 -9
- package/engine/{qa-runbooks.js → qa/runbooks.js} +1 -1
- package/engine/{qa-runs.js → qa/runs.js} +286 -15
- package/engine/{qa-sessions.js → qa/sessions.js} +595 -49
- package/engine/qa/visual-journey.js +654 -0
- package/engine/{qa-runners.js → qa-runners/index.js} +7 -7
- package/engine/qa-runners/maestro.js +3 -3
- package/engine/qa-runners/playwright.js +2 -2
- package/engine/{restart-health.js → recovery/restart-health.js} +48 -4
- package/engine/recovery/stop-stack.js +607 -0
- package/engine/{supervisor.js → recovery/supervisor.js} +105 -175
- package/engine/{watchdog.js → recovery/watchdog.js} +136 -13
- package/engine/runtimes/claude.js +14 -12
- package/engine/runtimes/codex.js +8 -6
- package/engine/runtimes/copilot.js +17 -16
- package/engine/{watch-actions.js → watches/actions.js} +13 -13
- package/engine/{watches.js → watches/index.js} +43 -32
- package/engine/{watches-store.js → watches/store.js} +4 -4
- package/engine/{create-pr-worktree.js → worktrees/create-pr.js} +1 -1
- package/engine/{worktree-gc.js → worktrees/gc.js} +70 -22
- package/engine/worktrees/inventory.js +671 -0
- package/engine/{live-checkout.js → worktrees/live-checkout.js} +4 -4
- package/engine/{worktree-pool.js → worktrees/pool.js} +2 -2
- package/engine/{worktree-preflight.js → worktrees/preflight.js} +1 -0
- package/engine/worktrees/quarantine-refs.js +173 -0
- package/engine.js +1137 -208
- package/minions.js +147 -77
- package/package.json +10 -6
- package/playbooks/_pr-description-audit.md +110 -78
- package/playbooks/build-fix-complex.md +2 -0
- package/playbooks/fix.md +16 -12
- package/playbooks/implement-shared.md +2 -0
- package/playbooks/implement.md +19 -20
- package/playbooks/plan-to-prd.md +18 -3
- package/playbooks/qa-session-draft.md +136 -1
- package/playbooks/qa-session-execute.md +80 -2
- package/playbooks/qa-session-setup.md +17 -1
- package/playbooks/qa-validate.md +1 -1
- package/playbooks/setup.md +2 -0
- package/playbooks/shared-rules.md +25 -32
- package/playbooks/templates/followup-dispatch.md +4 -3
- package/playbooks/verify.md +1 -1
- package/prompts/cc-system.md +19 -27
- package/watch-plugins/README.md +92 -0
- package/watch-plugins/ado-author-prs.js +336 -0
- package/watch-plugins/gh-author-prs.js +375 -0
- package/watch-plugins/http.js +474 -0
- package/watch-plugins/teams-channel.js +869 -0
- package/docs/dev-composite-workflow.md +0 -101
- package/docs/pr-screenshots/pr-886/after-single-header.png +0 -0
- package/docs/pr-screenshots/pr-886/before-duplicate-header.png +0 -0
- package/docs/pr-screenshots/pr-895/01-cancellation-reason-detail.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-901/projects-tab-default.png +0 -0
- package/docs/pr-screenshots/pr-901/projects-tab-fmf-selected.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-dropdown-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
- package/docs/visual-evidence-ci.md +0 -103
- package/engine/bridge.js +0 -379
- package/engine/quarantine-refs.js +0 -103
- package/engine/state-operations.js +0 -178
- /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
|
+
};
|