@yemi33/minions 0.1.2447 → 0.1.2449
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +66 -7
- package/dashboard/js/detail-panel.js +36 -0
- package/dashboard/js/memory-panel.js +59 -12
- package/dashboard/js/qa.js +186 -45
- 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 +52 -6
- 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
|
@@ -1,39 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* engine/ado.js — Azure DevOps integration for Minions engine.
|
|
2
|
+
* engine/ado/index.js — Azure DevOps integration for Minions engine.
|
|
3
3
|
* Extracted from engine.js: ADO token management, PR status polling, human comment polling.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const childProcess = require('child_process');
|
|
8
|
-
const shared = require('
|
|
8
|
+
const shared = require('../core/shared');
|
|
9
9
|
const { exec, execAsync, getAdoOrgBase, log, ts, dateStamp, PR_STATUS, BUILD_STATUS, REVIEW_STATUS, FETCH_TIMEOUT_MS, ADO_TOKEN_REFRESH_MAX_RETRIES, createThrottleTracker, createBackoffTracker, writeToInbox } = shared;
|
|
10
|
-
const { getPrs } = require('
|
|
10
|
+
const { getPrs } = require('../core/queries');
|
|
11
11
|
const { mutateJsonFileLocked } = shared;
|
|
12
|
-
const { acquireAdoToken } = require('./
|
|
12
|
+
const { acquireAdoToken } = require('./token');
|
|
13
13
|
const {
|
|
14
|
-
hasMinionsMarker,
|
|
15
14
|
hasVerdictPrefix,
|
|
15
|
+
classifyMarkerAuthorship,
|
|
16
|
+
isMarkerAuthored,
|
|
17
|
+
MARKER_AUTHORSHIP,
|
|
16
18
|
classifyNonActionableAutomationComment,
|
|
17
|
-
} = require('
|
|
18
|
-
const { wrapUntrusted, buildSource } = require('
|
|
19
|
-
const reviewLearning = require('
|
|
19
|
+
} = require('../providers/comment-classifier');
|
|
20
|
+
const { wrapUntrusted, buildSource } = require('../core/untrusted-fence');
|
|
21
|
+
const reviewLearning = require('../memory/review-learning');
|
|
20
22
|
|
|
21
23
|
// Lazy require to avoid circular dependency — only needed for engine().handlePostMerge
|
|
22
24
|
let _engine = null;
|
|
23
25
|
function engine() {
|
|
24
|
-
if (!_engine) _engine = require('
|
|
26
|
+
if (!_engine) _engine = require('../../engine');
|
|
25
27
|
return _engine;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
// Lazy require for dispatch module (avoids circular dependency via engine)
|
|
29
31
|
let _dispatch = null;
|
|
30
|
-
function dispatchModule() { if (!_dispatch) _dispatch = require('
|
|
32
|
+
function dispatchModule() { if (!_dispatch) _dispatch = require('../orchestration/dispatch'); return _dispatch; }
|
|
31
33
|
|
|
32
34
|
// Lazy require for lifecycle module — only needed for the stale-noopfix sweep
|
|
33
|
-
// in pollPrStatus (W-mpx44p05000ze8d8). lifecycle.js lazy-requires ado
|
|
35
|
+
// in pollPrStatus (W-mpx44p05000ze8d8). lifecycle.js lazy-requires ado/ back,
|
|
34
36
|
// so this MUST stay lazy to avoid a circular import at module-load time.
|
|
35
37
|
let _lifecycle = null;
|
|
36
|
-
function lifecycleModule() { if (!_lifecycle) _lifecycle = require('
|
|
38
|
+
function lifecycleModule() { if (!_lifecycle) _lifecycle = require('../orchestration/lifecycle'); return _lifecycle; }
|
|
37
39
|
|
|
38
40
|
const stripRefsHeads = s => (s || '').replace('refs/heads/', '');
|
|
39
41
|
const getAdoPrUrl = (project, prNumber) => {
|
|
@@ -408,10 +410,17 @@ function applyAdoPrMetadata(pr, prData) {
|
|
|
408
410
|
updated = true;
|
|
409
411
|
}
|
|
410
412
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
413
|
+
// Populate the real author identity from the ADO PR creator. Never overwrite
|
|
414
|
+
// pr.agent (the Minions author-agent) with a human display name.
|
|
415
|
+
{
|
|
416
|
+
const adoAuthor = shared.buildAdoAuthorIdentity(prData.createdBy);
|
|
417
|
+
if (adoAuthor) {
|
|
418
|
+
const mergedAuthor = shared.mergePrAuthorIdentity(pr.author, adoAuthor);
|
|
419
|
+
if (JSON.stringify(mergedAuthor) !== JSON.stringify(pr.author || null)) {
|
|
420
|
+
pr.author = mergedAuthor;
|
|
421
|
+
updated = true;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
415
424
|
}
|
|
416
425
|
|
|
417
426
|
// W-mpej044m00076d63 / W-mrezh0yb0007f733: keep `pr.created` aligned with
|
|
@@ -462,26 +471,103 @@ function classifyBuildStatus(prBuilds) {
|
|
|
462
471
|
return BUILD_STATUS.NONE;
|
|
463
472
|
}
|
|
464
473
|
|
|
474
|
+
/**
|
|
475
|
+
* Recency ordering key for one ADO build record, compared as a tuple so a
|
|
476
|
+
* weaker signal never outranks a stronger one:
|
|
477
|
+
* queueTime (→ startTime → finishTime) → numeric build id → response order.
|
|
478
|
+
* ADO always stamps `queueTime` and returns builds newest-first, but neither
|
|
479
|
+
* is guaranteed by the API contract, so the key degrades instead of picking an
|
|
480
|
+
* arbitrary generation.
|
|
481
|
+
*/
|
|
482
|
+
function buildRecencyKey(build, index) {
|
|
483
|
+
const time = Date.parse(build?.queueTime || build?.startTime || build?.finishTime || '');
|
|
484
|
+
const id = Number(build?.id);
|
|
485
|
+
return [
|
|
486
|
+
Number.isFinite(time) ? time : -Infinity,
|
|
487
|
+
Number.isFinite(id) ? id : -Infinity,
|
|
488
|
+
-index,
|
|
489
|
+
];
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function compareBuildRecency(a, b) {
|
|
493
|
+
for (let i = 0; i < a.length; i++) {
|
|
494
|
+
if (a[i] > b[i]) return 1;
|
|
495
|
+
if (a[i] < b[i]) return -1;
|
|
496
|
+
}
|
|
497
|
+
return 0;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Narrow a stale build set to the NEWEST merge commit it covers
|
|
502
|
+
* (W-msaux21y00g79c48).
|
|
503
|
+
*
|
|
504
|
+
* `refs/pull/<n>/merge` accumulates builds for EVERY push to the PR and the
|
|
505
|
+
* poller reads `$top=25` of them, so classifying that whole list ORs a single
|
|
506
|
+
* historical failure across the PR's entire life: once any build failed, every
|
|
507
|
+
* later stale evaluation reported 'failing' forever, even after the newest
|
|
508
|
+
* merge commit built green and the failures had been fixed on-branch. Group by
|
|
509
|
+
* `sourceVersion` and keep only the most recently built generation, so the
|
|
510
|
+
* stale classification says "the most recent thing CI actually told us about
|
|
511
|
+
* this PR" instead of OR-ing across already-superseded history.
|
|
512
|
+
*
|
|
513
|
+
* Draft PRs feel this hardest: ADO does not auto-run branch policies on drafts
|
|
514
|
+
* (see queueDraftPrValidationBuilds), so a draft spends most of its life on an
|
|
515
|
+
* unbuilt merge commit — i.e. inside this stale path.
|
|
516
|
+
*/
|
|
517
|
+
function selectNewestMergeCommitBuilds(allBuilds) {
|
|
518
|
+
const builds = Array.isArray(allBuilds) ? allBuilds : [];
|
|
519
|
+
if (builds.length < 2) return builds.slice();
|
|
520
|
+
const generations = new Map(); // sourceVersion → { key, builds }
|
|
521
|
+
builds.forEach((build, index) => {
|
|
522
|
+
const sourceVersion = String(build?.sourceVersion ?? '');
|
|
523
|
+
const key = buildRecencyKey(build, index);
|
|
524
|
+
const generation = generations.get(sourceVersion);
|
|
525
|
+
if (!generation) {
|
|
526
|
+
generations.set(sourceVersion, { key, builds: [build] });
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
generation.builds.push(build);
|
|
530
|
+
if (compareBuildRecency(key, generation.key) > 0) generation.key = key;
|
|
531
|
+
});
|
|
532
|
+
if (generations.size < 2) return builds.slice();
|
|
533
|
+
let newest = null;
|
|
534
|
+
for (const generation of generations.values()) {
|
|
535
|
+
if (!newest || compareBuildRecency(generation.key, newest.key) > 0) newest = generation;
|
|
536
|
+
}
|
|
537
|
+
return newest.builds;
|
|
538
|
+
}
|
|
539
|
+
|
|
465
540
|
/**
|
|
466
541
|
* Stale merge-commit fallback classifier (Issue #2747). When ADO returns
|
|
467
542
|
* builds on a PR's merge ref but none target the current merge commit
|
|
468
543
|
* (ADO recomputed merge-commit after a target-branch advance / rebase
|
|
469
|
-
* without re-queueing), classify the stale set
|
|
544
|
+
* without re-queueing), classify the NEWEST merge commit in that stale set
|
|
545
|
+
* (W-msaux21y00g79c48 — older generations are superseded history):
|
|
470
546
|
* - Stale-failing → 'failing' + buildStaleMergeCommit:true. A stale
|
|
471
547
|
* failing signal is strictly more actionable than 'none'; a fix WI
|
|
472
548
|
* either does real work (failure still in the branch) or verifies
|
|
473
549
|
* green via push (rebase resolved it).
|
|
550
|
+
* - Stale-succeeded that contradicts a cached 'failing' → 'none' +
|
|
551
|
+
* supersededCachedFailure:true. The current merge commit is unbuilt, so
|
|
552
|
+
* the cached failure can only have come from an OLDER signal than the
|
|
553
|
+
* green one we just read; re-asserting it pins the PR to 'failing'
|
|
554
|
+
* forever and keeps dispatching build fixes against a green branch.
|
|
555
|
+
* 'none' (unknown) is the honest answer — 'passing' would be false
|
|
556
|
+
* confidence about a merge commit nothing has built.
|
|
474
557
|
* - Stale-succeeded/other → preserve cached pr.buildStatus (or 'none')
|
|
475
558
|
* and do NOT set the stale-merge flag. Succeeded-on-pre-rebase is
|
|
476
559
|
* ambiguous and could be false confidence.
|
|
477
|
-
* Returns { buildStatus, buildStaleMergeCommit }.
|
|
560
|
+
* Returns { buildStatus, buildStaleMergeCommit, supersededCachedFailure }.
|
|
478
561
|
*/
|
|
479
562
|
function classifyStaleBuilds(allBuilds, cachedBuildStatus) {
|
|
480
|
-
const staleStatus = classifyBuildStatus(allBuilds);
|
|
563
|
+
const staleStatus = classifyBuildStatus(selectNewestMergeCommitBuilds(allBuilds));
|
|
481
564
|
if (staleStatus === BUILD_STATUS.FAILING) {
|
|
482
|
-
return { buildStatus: BUILD_STATUS.FAILING, buildStaleMergeCommit: true };
|
|
565
|
+
return { buildStatus: BUILD_STATUS.FAILING, buildStaleMergeCommit: true, supersededCachedFailure: false };
|
|
566
|
+
}
|
|
567
|
+
if (staleStatus === BUILD_STATUS.PASSING && cachedBuildStatus === BUILD_STATUS.FAILING) {
|
|
568
|
+
return { buildStatus: BUILD_STATUS.NONE, buildStaleMergeCommit: false, supersededCachedFailure: true };
|
|
483
569
|
}
|
|
484
|
-
return { buildStatus: cachedBuildStatus || BUILD_STATUS.NONE, buildStaleMergeCommit: false };
|
|
570
|
+
return { buildStatus: cachedBuildStatus || BUILD_STATUS.NONE, buildStaleMergeCommit: false, supersededCachedFailure: false };
|
|
485
571
|
}
|
|
486
572
|
|
|
487
573
|
/**
|
|
@@ -526,6 +612,130 @@ async function queueFreshAdoBuild({ orgBase, project, prNumber, definitionId, to
|
|
|
526
612
|
return adoFetch(url, token, { method: 'POST', body });
|
|
527
613
|
}
|
|
528
614
|
|
|
615
|
+
// ─── Draft-PR branch-policy build queueing (W-ms7kklp8002zad24) ───────────────
|
|
616
|
+
//
|
|
617
|
+
// On Azure DevOps, branch build policies do NOT run automatically while a PR is
|
|
618
|
+
// a draft — the required policy builds sit un-queued, so a Minions-managed draft
|
|
619
|
+
// PR never gets a build status and the existing build-fix loop has nothing to
|
|
620
|
+
// act on. GitHub already fires CI on drafts, so this whole path is ADO-only.
|
|
621
|
+
//
|
|
622
|
+
// For a draft PR with buildStatus === NONE there are NO existing builds to read
|
|
623
|
+
// `build.definition.id` from, so the stale-requeue path cannot supply a
|
|
624
|
+
// definitionId. We instead DISCOVER the required build definition(s) from the
|
|
625
|
+
// target branch's ADO branch-policy configurations.
|
|
626
|
+
|
|
627
|
+
// ADO "Build" policy type id — every branch build policy carries this type.id.
|
|
628
|
+
const ADO_BUILD_POLICY_TYPE_ID = '0609b952-1397-4640-95ec-e00a01b2c241';
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Does an ADO policy `settings.scope[]` entry apply to this PR's target branch
|
|
632
|
+
* (and repo)? ADO scope entries carry `{ repositoryId, refName, matchKind }`:
|
|
633
|
+
* - `repositoryId` null/empty → the policy applies to every repo in the
|
|
634
|
+
* project; a concrete GUID must match this PR's repo (case-insensitive).
|
|
635
|
+
* - `matchKind` 'Exact' (default) → target refName must equal the scope
|
|
636
|
+
* refName; 'Prefix' → target refName must start with the scope refName
|
|
637
|
+
* (ADO's prefix branch policies, e.g. scope `refs/heads/release/` covering
|
|
638
|
+
* `refs/heads/release/x`).
|
|
639
|
+
*/
|
|
640
|
+
function adoPolicyScopeMatches(scopeEntry, targetRefName, repositoryId) {
|
|
641
|
+
if (!scopeEntry || typeof scopeEntry !== 'object') return false;
|
|
642
|
+
const scopeRepo = String(scopeEntry.repositoryId || '').trim();
|
|
643
|
+
if (scopeRepo) {
|
|
644
|
+
const prRepo = String(repositoryId || '').trim();
|
|
645
|
+
if (!prRepo || scopeRepo.toLowerCase() !== prRepo.toLowerCase()) return false;
|
|
646
|
+
}
|
|
647
|
+
const scopeRef = String(scopeEntry.refName || '').trim();
|
|
648
|
+
if (!scopeRef) return false; // no branch scope → not a branch-targeted policy we can match
|
|
649
|
+
const target = String(targetRefName || '').trim();
|
|
650
|
+
if (!target) return false;
|
|
651
|
+
const matchKind = String(scopeEntry.matchKind || 'exact').toLowerCase();
|
|
652
|
+
if (matchKind === 'prefix') return target === scopeRef || target.startsWith(scopeRef);
|
|
653
|
+
return target === scopeRef; // Exact (default)
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Parse an ADO policy/configurations `value[]` into the DISTINCT set of required
|
|
658
|
+
* build definition ids that apply to `targetRefName` on `repositoryId`. Only
|
|
659
|
+
* enabled + blocking (required) Build policies whose scope matches the target
|
|
660
|
+
* branch + repo are considered. Pure/testable — no network. Returns number[].
|
|
661
|
+
*/
|
|
662
|
+
function extractDraftPrBuildDefinitionIds(policyConfigs, targetRefName, repositoryId) {
|
|
663
|
+
const ids = [];
|
|
664
|
+
const seen = new Set();
|
|
665
|
+
if (!Array.isArray(policyConfigs)) return ids;
|
|
666
|
+
for (const cfg of policyConfigs) {
|
|
667
|
+
if (!cfg || typeof cfg !== 'object') continue;
|
|
668
|
+
if (String(cfg?.type?.id || '').toLowerCase() !== ADO_BUILD_POLICY_TYPE_ID) continue;
|
|
669
|
+
if (cfg.isEnabled !== true || cfg.isBlocking !== true) continue;
|
|
670
|
+
const settings = cfg.settings || {};
|
|
671
|
+
const rawDefId = settings.buildDefinitionId;
|
|
672
|
+
if (rawDefId == null || rawDefId === '') continue;
|
|
673
|
+
if (typeof rawDefId !== 'number' && typeof rawDefId !== 'string') continue;
|
|
674
|
+
// ADO normally serializes buildDefinitionId as a number, but a string id
|
|
675
|
+
// would silently defeat a raw Set dedupe (1234 !== '1234') and queue the
|
|
676
|
+
// SAME pipeline twice. Normalize before comparing and before emitting.
|
|
677
|
+
const defId = Number(rawDefId);
|
|
678
|
+
if (!Number.isFinite(defId)) continue;
|
|
679
|
+
const scopes = Array.isArray(settings.scope) ? settings.scope : [];
|
|
680
|
+
if (!scopes.some(s => adoPolicyScopeMatches(s, targetRefName, repositoryId))) continue;
|
|
681
|
+
if (seen.has(defId)) continue;
|
|
682
|
+
seen.add(defId);
|
|
683
|
+
ids.push(defId);
|
|
684
|
+
}
|
|
685
|
+
return ids;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Discover the required branch-policy build definitions for a draft PR's target
|
|
690
|
+
* branch and queue a fresh validation build for each distinct definition against
|
|
691
|
+
* refs/pull/<prNumber>/merge. Non-fatal: the policy-config fetch and every queue
|
|
692
|
+
* POST are wrapped so a failure logs a warn and returns without throwing (mirrors
|
|
693
|
+
* queueFreshAdoBuild's non-fatal contract).
|
|
694
|
+
*
|
|
695
|
+
* Returns `{ queued, discovered }`:
|
|
696
|
+
* - `queued` — how many builds were actually POSTed.
|
|
697
|
+
* - `discovered` — whether policy discovery COMPLETED and produced a definitive
|
|
698
|
+
* answer for this branch. True even when zero definitions matched, because
|
|
699
|
+
* "this branch has no required Build policies" is a stable result: the caller
|
|
700
|
+
* uses it to close the 30-min rate-limit window so it stops re-GETting
|
|
701
|
+
* policy/configurations on every single poll. False only when we short-circuit
|
|
702
|
+
* on a malformed PR or the policy fetch itself failed (transient — the caller
|
|
703
|
+
* leaves the window open and retries on the next poll).
|
|
704
|
+
*/
|
|
705
|
+
async function queueDraftPrValidationBuilds(pr, project, token, orgBase) {
|
|
706
|
+
const prNumber = pr?.prNumber;
|
|
707
|
+
const targetRefName = pr?.targetRefName;
|
|
708
|
+
if (!prNumber || !targetRefName) return { queued: 0, discovered: false };
|
|
709
|
+
const repositoryId = getConfiguredAdoRepositoryGuid(project) || getAdoRepositoryId(project);
|
|
710
|
+
|
|
711
|
+
let defIds = [];
|
|
712
|
+
try {
|
|
713
|
+
const url = `${orgBase}/${project.adoProject}/_apis/policy/configurations?api-version=7.1`;
|
|
714
|
+
const data = await adoFetch(url, token);
|
|
715
|
+
defIds = extractDraftPrBuildDefinitionIds(data?.value || [], targetRefName, repositoryId);
|
|
716
|
+
} catch (e) {
|
|
717
|
+
log('warn', `PR ${pr.id} draft-build: failed to fetch policy configurations: ${e.message}`);
|
|
718
|
+
return { queued: 0, discovered: false };
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (defIds.length === 0) {
|
|
722
|
+
log('info', `PR ${pr.id} draft-build: no required Build policies matched target ${targetRefName}; nothing to queue`);
|
|
723
|
+
return { queued: 0, discovered: true };
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
let queued = 0;
|
|
727
|
+
for (const definitionId of defIds) {
|
|
728
|
+
try {
|
|
729
|
+
await queueFreshAdoBuild({ orgBase, project, prNumber, definitionId, token });
|
|
730
|
+
queued++;
|
|
731
|
+
log('info', `PR ${pr.id} draft-build: queued validation build for definition id=${definitionId} on refs/pull/${prNumber}/merge (draft PR — ADO does not auto-trigger policy builds)`);
|
|
732
|
+
} catch (e) {
|
|
733
|
+
log('warn', `PR ${pr.id} draft-build: failed to queue validation build for definition id=${definitionId}: ${e.message}`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return { queued, discovered: true };
|
|
737
|
+
}
|
|
738
|
+
|
|
529
739
|
/** Map ADO reviewer vote array to a review status string. */
|
|
530
740
|
function votesToReviewStatus(votes) {
|
|
531
741
|
if (votes.some(v => v === -10)) return REVIEW_STATUS.CHANGES_REQUESTED;
|
|
@@ -589,8 +799,8 @@ function _voteSnapshotsPath() {
|
|
|
589
799
|
// Resolve lazily so tests that swap MINIONS_DIR via createTestMinionsDir()
|
|
590
800
|
// see the fresh value. shared.MINIONS_DIR is reassigned when MINIONS_TEST_DIR
|
|
591
801
|
// is set, but only when shared is re-required — which the helper does via
|
|
592
|
-
// `require('
|
|
593
|
-
return path.join(require('
|
|
802
|
+
// `require('../core/shared').MINIONS_DIR`.
|
|
803
|
+
return path.join(require('../core/shared').MINIONS_DIR, 'engine', VOTE_SNAPSHOT_FILE);
|
|
594
804
|
}
|
|
595
805
|
|
|
596
806
|
function _normalizeSnapshotKey(prId, reviewerId) {
|
|
@@ -760,7 +970,7 @@ function _scanStuckVote({ pr, reviewerId, vote, sourceCommit, snapshot, now }) {
|
|
|
760
970
|
function _hasPendingReReviewWi(pr) {
|
|
761
971
|
if (!pr?.id) return false;
|
|
762
972
|
try {
|
|
763
|
-
const dispatchData = require('
|
|
973
|
+
const dispatchData = require('../core/queries').getDispatch();
|
|
764
974
|
const all = [...(dispatchData?.pending || []), ...(dispatchData?.active || [])];
|
|
765
975
|
return all.some(d => {
|
|
766
976
|
if (d?.type !== 'review') return false;
|
|
@@ -1203,16 +1413,26 @@ async function fetchAdoBuildErrorLog(orgBase, project, failedStatus, token, pr,
|
|
|
1203
1413
|
/**
|
|
1204
1414
|
* Iterate active PRs across all projects. Calls `callback(project, pr, prNum, orgBase, adoRepositoryId)`
|
|
1205
1415
|
* for each active PR. If callback returns truthy, the PR file is saved after the project loop.
|
|
1416
|
+
*
|
|
1417
|
+
* @param {object} [options]
|
|
1418
|
+
* @param {number} [options.abandonedRepollWindowMs] — W-msav5edk00g81710. Mirror of
|
|
1419
|
+
* engine/providers/github.js:forEachActiveGhPr. Opt-in bounded re-probe of
|
|
1420
|
+
* recently-abandoned records so a reopened PR can leave the terminal stamp.
|
|
1421
|
+
* Defaults to 0 (strict PR_POLLABLE_STATUSES) — only the status poll opts in.
|
|
1206
1422
|
*/
|
|
1207
|
-
async function forEachActivePr(config, token, callback) {
|
|
1423
|
+
async function forEachActivePr(config, token, callback, options = {}) {
|
|
1208
1424
|
const projects = shared.getProjects(config);
|
|
1209
1425
|
let totalUpdated = 0;
|
|
1426
|
+
const pollOpts = {
|
|
1427
|
+
windowMs: Number(options.abandonedRepollWindowMs) || 0,
|
|
1428
|
+
now: Date.now(),
|
|
1429
|
+
};
|
|
1210
1430
|
|
|
1211
1431
|
for (const project of projects) {
|
|
1212
1432
|
if (isGitHubProject(project)) continue;
|
|
1213
1433
|
// W-mp625n27000m6e78 — Honor per-project workSources.pullRequests.enabled.
|
|
1214
1434
|
// Default-ON: only skip when explicitly disabled (=== false). Same gate as
|
|
1215
|
-
// engine/github.js:forEachActiveGhPr — without this, disabling PR polling
|
|
1435
|
+
// engine/providers/github.js:forEachActiveGhPr — without this, disabling PR polling
|
|
1216
1436
|
// for an ADO project via the dashboard Settings UI was cosmetic.
|
|
1217
1437
|
const src = project?.workSources?.pullRequests || config?.workSources?.pullRequests;
|
|
1218
1438
|
if (src && src.enabled === false) continue;
|
|
@@ -1221,7 +1441,7 @@ async function forEachActivePr(config, token, callback) {
|
|
|
1221
1441
|
if (!project.adoOrg || !project.adoProject) continue;
|
|
1222
1442
|
|
|
1223
1443
|
const prs = getPrs(project);
|
|
1224
|
-
const activePrs = prs.filter(pr => shared.
|
|
1444
|
+
const activePrs = prs.filter(pr => shared.isPrPollable(pr, pollOpts)
|
|
1225
1445
|
&& shared.isPrCompatibleWithProject(project, pr, pr.url || ''));
|
|
1226
1446
|
if (activePrs.length === 0) continue;
|
|
1227
1447
|
|
|
@@ -1309,13 +1529,13 @@ async function forEachActivePr(config, token, callback) {
|
|
|
1309
1529
|
}
|
|
1310
1530
|
|
|
1311
1531
|
// BUG-C2 — Also poll manually linked ADO PRs from the central PR store.
|
|
1312
|
-
// Mirror of engine/github.js:540-633. Without this block, any ADO PR enrolled
|
|
1532
|
+
// Mirror of engine/providers/github.js:540-633. Without this block, any ADO PR enrolled
|
|
1313
1533
|
// into the central PR store via the dashboard attach UI or
|
|
1314
1534
|
// lifecycle.enrollPrFromCanonicalId is never polled (status, build, review,
|
|
1315
1535
|
// comments, abandoned-reconciliation all freeze permanently).
|
|
1316
1536
|
//
|
|
1317
1537
|
// MIRROR-SYMMETRY CONTRACT: any future divergence between
|
|
1318
|
-
// engine/github.js:540-633 and this branch is itself a bug class. The
|
|
1538
|
+
// engine/providers/github.js:540-633 and this branch is itself a bug class. The
|
|
1319
1539
|
// canonical 'mirror-ado-github-poll-fix' skill applies. Keep filter, gating,
|
|
1320
1540
|
// per-PR iteration, and merge-back semantics symmetric.
|
|
1321
1541
|
//
|
|
@@ -1340,7 +1560,7 @@ async function forEachActivePr(config, token, callback) {
|
|
|
1340
1560
|
const configuredAdoProjects = projects.filter(p => !isGitHubProject(p) && p.adoOrg && p.adoProject);
|
|
1341
1561
|
|
|
1342
1562
|
const activeCentral = centralPrs.filter(pr => {
|
|
1343
|
-
if (!shared.
|
|
1563
|
+
if (!shared.isPrPollable(pr, pollOpts)) return false;
|
|
1344
1564
|
if (String(pr.repoHost || 'ado').toLowerCase() !== 'ado') return false;
|
|
1345
1565
|
if (!parseCanonicalAdoPrId(pr.id, 'central ADO PR poll')) return false;
|
|
1346
1566
|
// Only defer to the project loop when the PR is ACTUALLY present in that
|
|
@@ -1473,13 +1693,13 @@ async function pollPrStatus(config) {
|
|
|
1473
1693
|
|
|
1474
1694
|
let newStatus = pr.status;
|
|
1475
1695
|
// W-mp5trwh60008386d: ADO does NOT need the `prAbandonConfirmCount` confirmation logic
|
|
1476
|
-
// that engine/github.js uses. ADO only flips a PR to `abandoned` when a *successful*
|
|
1696
|
+
// that engine/providers/github.js uses. ADO only flips a PR to `abandoned` when a *successful*
|
|
1477
1697
|
// adoFetch returns `prData.status === 'abandoned'` (below). On 404/auth failure adoFetch
|
|
1478
1698
|
// throws, the throw propagates to forEachActivePr's `Promise.allSettled`, and the PR
|
|
1479
1699
|
// record is left untouched — equivalent to "infinite confirmations required". The shared
|
|
1480
|
-
// ENGINE_DEFAULTS.prAbandonConfirmCount constant lives in engine/shared.js so a future
|
|
1700
|
+
// ENGINE_DEFAULTS.prAbandonConfirmCount constant lives in engine/core/shared.js so a future
|
|
1481
1701
|
// ADO change can adopt the same semantics if the failure model ever maps 404 to
|
|
1482
|
-
// abandonment directly. See engine/github.js pollPrStatus for the GitHub implementation.
|
|
1702
|
+
// abandonment directly. See engine/providers/github.js pollPrStatus for the GitHub implementation.
|
|
1483
1703
|
if (prData.status === 'completed') newStatus = PR_STATUS.MERGED;
|
|
1484
1704
|
else if (prData.status === 'abandoned') newStatus = PR_STATUS.ABANDONED;
|
|
1485
1705
|
else if (prData.status === 'active') newStatus = PR_STATUS.ACTIVE;
|
|
@@ -1501,6 +1721,12 @@ async function pollPrStatus(config) {
|
|
|
1501
1721
|
if ((newStatus === PR_STATUS.ABANDONED || newStatus === PR_STATUS.CLOSED) && !pr.closedAt) {
|
|
1502
1722
|
pr.closedAt = prData.closedDate || ts();
|
|
1503
1723
|
}
|
|
1724
|
+
// W-msav5edk00g81710: anchor the bounded re-probe window (mirror of
|
|
1725
|
+
// engine/providers/github.js). ADO abandonment is fully reversible via
|
|
1726
|
+
// "Reactivate", so the record must stay observable for a bounded window.
|
|
1727
|
+
if (newStatus === PR_STATUS.ABANDONED) {
|
|
1728
|
+
shared.stampPrAbandonedAt(pr, prData.closedDate || pr.closedAt || ts());
|
|
1729
|
+
}
|
|
1504
1730
|
|
|
1505
1731
|
if (newStatus === PR_STATUS.MERGED || newStatus === PR_STATUS.ABANDONED) {
|
|
1506
1732
|
if (pr.reviewStatus === REVIEW_STATUS.WAITING) {
|
|
@@ -1537,7 +1763,7 @@ async function pollPrStatus(config) {
|
|
|
1537
1763
|
// references it. Invariant #14 ('approved is permanent terminal') is
|
|
1538
1764
|
// preserved: we keep reviewStatus when it was 'approved' rather than
|
|
1539
1765
|
// carving a wasReopened exception into the 8+ guard sites in
|
|
1540
|
-
// engine/lifecycle.js, engine.js, engine/github.js, and engine/ado.js.
|
|
1766
|
+
// engine/orchestration/lifecycle.js, engine.js, engine/providers/github.js, and engine/ado/index.js.
|
|
1541
1767
|
// Re-review safety on new commits is handled by the pre-existing
|
|
1542
1768
|
// pr.lastPushedAt > pr.lastReviewedAt path (engine.js alreadyReviewed
|
|
1543
1769
|
// gate) plus the changes-requested override in lifecycle.js.
|
|
@@ -1550,6 +1776,10 @@ async function pollPrStatus(config) {
|
|
|
1550
1776
|
// F6 not yet shipped (field absent).
|
|
1551
1777
|
delete pr.humanFeedback.editsSeen;
|
|
1552
1778
|
pr.fixDispatched = false;
|
|
1779
|
+
// W-msav5edk00g81710: record left `abandoned` — drop the re-probe
|
|
1780
|
+
// anchor and the stale close timestamp it falls back to.
|
|
1781
|
+
shared.clearPrAbandonedAt(pr);
|
|
1782
|
+
delete pr.closedAt;
|
|
1553
1783
|
if (pr.reviewStatus !== REVIEW_STATUS.APPROVED) pr.reviewStatus = REVIEW_STATUS.PENDING;
|
|
1554
1784
|
log('info', `PR ${pr.id} reopened — reset transient state (reviewStatus=${pr.reviewStatus})`);
|
|
1555
1785
|
}
|
|
@@ -1578,7 +1808,7 @@ async function pollPrStatus(config) {
|
|
|
1578
1808
|
}
|
|
1579
1809
|
|
|
1580
1810
|
// P-w1a3f9b2 — Phase 1.1: plumb mergeable / isDraft / mergeStatus /
|
|
1581
|
-
// headRefOid onto the PR object so watches captureState (engine/watches.js)
|
|
1811
|
+
// headRefOid onto the PR object so watches captureState (engine/watches/index.js)
|
|
1582
1812
|
// and future predicates (Phase 2.1: head-commit-change, mergeable-flipped,
|
|
1583
1813
|
// ready-for-merge, draft-flipped) can read them.
|
|
1584
1814
|
//
|
|
@@ -1787,15 +2017,21 @@ async function pollPrStatus(config) {
|
|
|
1787
2017
|
// builds for this PR's merge ref but none target the current
|
|
1788
2018
|
// `mergeCommitId` — most likely the target branch moved (rebase
|
|
1789
2019
|
// or push), ADO recomputed the merge commit, but no new
|
|
1790
|
-
// source-side changes triggered a rebuild. Classify the
|
|
1791
|
-
//
|
|
1792
|
-
//
|
|
1793
|
-
//
|
|
2020
|
+
// source-side changes triggered a rebuild. Classify the NEWEST
|
|
2021
|
+
// merge commit in that stale set (W-msaux21y00g79c48): stale-
|
|
2022
|
+
// failing flips to 'failing' + buildStaleMergeCommit (strictly
|
|
2023
|
+
// more actionable than 'none'); stale-succeeded/other preserves
|
|
2024
|
+
// cached state (no false confidence) unless it contradicts a
|
|
2025
|
+
// cached failure, which it then drops.
|
|
1794
2026
|
const stale = classifyStaleBuilds(allBuilds, pr.buildStatus);
|
|
1795
2027
|
buildStatus = stale.buildStatus;
|
|
1796
2028
|
buildStaleMergeCommit = stale.buildStaleMergeCommit;
|
|
2029
|
+
// Same narrowing classifyStaleBuilds applies, so the reported
|
|
2030
|
+
// reason and the re-queued definitions describe the generation we
|
|
2031
|
+
// actually classified rather than a superseded one.
|
|
2032
|
+
const newestStaleBuilds = selectNewestMergeCommitBuilds(allBuilds);
|
|
1797
2033
|
if (buildStaleMergeCommit) {
|
|
1798
|
-
const failed =
|
|
2034
|
+
const failed = newestStaleBuilds.find(b => b.result === 'failed') || newestStaleBuilds.find(b => b.result === 'canceled');
|
|
1799
2035
|
buildFailReason = failed?.definition?.name || 'Build failed (stale merge commit)';
|
|
1800
2036
|
buildFailureSignature = shared.safeSlugComponent([
|
|
1801
2037
|
failed?.definition?.name,
|
|
@@ -1805,7 +2041,7 @@ async function pollPrStatus(config) {
|
|
|
1805
2041
|
].filter(Boolean).join('\n') || buildFailReason, 80);
|
|
1806
2042
|
// Collect PR-validation definitions with failures for fresh-build queue.
|
|
1807
2043
|
const seenDefIds = new Set();
|
|
1808
|
-
for (const b of
|
|
2044
|
+
for (const b of newestStaleBuilds) {
|
|
1809
2045
|
if ((b.result === 'failed' || b.result === 'canceled') && isPrValidationBuild(b)) {
|
|
1810
2046
|
const defId = b?.definition?.id;
|
|
1811
2047
|
if (defId != null && !seenDefIds.has(defId)) {
|
|
@@ -1817,8 +2053,8 @@ async function pollPrStatus(config) {
|
|
|
1817
2053
|
} else if (pr.buildFailReason && buildStatus === pr.buildStatus) {
|
|
1818
2054
|
buildFailReason = pr.buildFailReason;
|
|
1819
2055
|
}
|
|
1820
|
-
const sampleSv = (
|
|
1821
|
-
log('warn', `PR ${pr.id} build: merge-commit mismatch — ${allBuilds.length} build(s) on merge ref, none target current merge commit ${String(mergeCommitId).slice(0, 8)} (
|
|
2056
|
+
const sampleSv = (newestStaleBuilds[0]?.sourceVersion || '').slice(0, 8);
|
|
2057
|
+
log('warn', `PR ${pr.id} build: merge-commit mismatch — ${allBuilds.length} build(s) on merge ref, none target current merge commit ${String(mergeCommitId).slice(0, 8)} (newest built merge commit ${sampleSv}, ${newestStaleBuilds.length} build(s)); classified '${buildStatus}' from that generation${buildStaleMergeCommit ? ' (stale failing — will queue fresh build if rate limit allows)' : ''}${stale.supersededCachedFailure ? ` (dropped cached '${BUILD_STATUS.FAILING}' — superseded by the newer green build)` : ''}`);
|
|
1822
2058
|
}
|
|
1823
2059
|
} catch (e) {
|
|
1824
2060
|
buildStatusResolved = false;
|
|
@@ -1930,6 +2166,43 @@ async function pollPrStatus(config) {
|
|
|
1930
2166
|
}
|
|
1931
2167
|
}
|
|
1932
2168
|
|
|
2169
|
+
// Draft-PR branch-policy build queueing (W-ms7kklp8002zad24). ADO does NOT
|
|
2170
|
+
// auto-trigger branch build policies while a PR is a draft, so an
|
|
2171
|
+
// auto-managed draft PR sits at buildStatus === NONE forever and the
|
|
2172
|
+
// build-fix loop has nothing to act on. Discover the required Build
|
|
2173
|
+
// policies from the target branch and queue a fresh build per distinct
|
|
2174
|
+
// definition against refs/pull/<n>/merge; the next poll then picks up
|
|
2175
|
+
// RUNNING/FAILING/PASSING via the existing classifyBuildStatus path and the
|
|
2176
|
+
// existing auto-fix loop iterates. Gated by engine.adoQueueDraftPrBuilds
|
|
2177
|
+
// (default true). Rate-limited via the shared _lastBuildRequeueAt window and
|
|
2178
|
+
// restricted to auto-managed (non-context-only) PRs. Non-fatal by contract.
|
|
2179
|
+
const adoQueueDraftPrBuilds = config?.engine?.adoQueueDraftPrBuilds ?? shared.ENGINE_DEFAULTS.adoQueueDraftPrBuilds;
|
|
2180
|
+
if (adoQueueDraftPrBuilds && buildStatusResolved && buildStatus === BUILD_STATUS.NONE
|
|
2181
|
+
&& pr.isDraft === true && shared.isAutoManagedPrRecord(pr)) {
|
|
2182
|
+
if (shouldRequeueStaleBuild(pr)) {
|
|
2183
|
+
try {
|
|
2184
|
+
const { queued, discovered } = await queueDraftPrValidationBuilds(pr, project, token, orgBase);
|
|
2185
|
+
// Close the shared rate-limit window whenever policy discovery
|
|
2186
|
+
// COMPLETED — including the zero-match case. A target branch with no
|
|
2187
|
+
// required Build policies is a stable answer, so stamping only on
|
|
2188
|
+
// queued > 0 left every subsequent poll re-GETting
|
|
2189
|
+
// policy/configurations for that PR forever (PR #1099 review). A
|
|
2190
|
+
// transient policy-fetch failure reports discovered:false and
|
|
2191
|
+
// deliberately leaves the window open so the next poll retries.
|
|
2192
|
+
if (discovered) {
|
|
2193
|
+
pr._lastBuildRequeueAt = Date.now();
|
|
2194
|
+
updated = true; // persist the stamp, otherwise the back-off is lost
|
|
2195
|
+
}
|
|
2196
|
+
} catch (e) {
|
|
2197
|
+
log('warn', `PR ${pr.id} draft-build: queueDraftPrValidationBuilds threw unexpectedly: ${e.message}`);
|
|
2198
|
+
}
|
|
2199
|
+
} else {
|
|
2200
|
+
const lastAt = Number(pr._lastBuildRequeueAt) || 0;
|
|
2201
|
+
const ageMin = Math.floor((Date.now() - lastAt) / 60000);
|
|
2202
|
+
log('info', `PR ${pr.id} draft-build: buildStatus none on draft PR but skipping queue — last requeue ${ageMin}m ago (rate limit: 30 min)`);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
|
|
1933
2206
|
// Auto-complete: set auto-complete on PR when builds green + review approved
|
|
1934
2207
|
if (pr.status === PR_STATUS.ACTIVE && pr.reviewStatus === REVIEW_STATUS.APPROVED && pr.buildStatus === BUILD_STATUS.PASSING && !pr._autoCompleted) {
|
|
1935
2208
|
const autoComplete = config.engine?.autoCompletePrs === true; // opt-in
|
|
@@ -1995,6 +2268,11 @@ async function pollPrStatus(config) {
|
|
|
1995
2268
|
}
|
|
1996
2269
|
throw err; // re-throw non-auth errors for forEachActivePr to handle
|
|
1997
2270
|
}
|
|
2271
|
+
}, {
|
|
2272
|
+
// W-msav5edk00g81710 — only the STATUS poll re-probes recently-abandoned
|
|
2273
|
+
// records (mirror of engine/providers/github.js#pollPrStatus). ADO's
|
|
2274
|
+
// "Reactivate" is the exact analogue of a GitHub reopen.
|
|
2275
|
+
abandonedRepollWindowMs: shared.resolveAbandonedPrRepollWindowMs(config),
|
|
1998
2276
|
});
|
|
1999
2277
|
|
|
2000
2278
|
if (totalUpdated > 0) {
|
|
@@ -2004,6 +2282,32 @@ async function pollPrStatus(config) {
|
|
|
2004
2282
|
|
|
2005
2283
|
// ─── Poll Human Comments on PRs ──────────────────────────────────────────────
|
|
2006
2284
|
|
|
2285
|
+
// W-msbf1kf6033f46cb — acceptance criterion 2: an unconfigured
|
|
2286
|
+
// `config.engine.minionsAdoIdentities` leaves the marker spoof guard inactive
|
|
2287
|
+
// (marker-presence alone then classifies as agent-authored). That is the safe
|
|
2288
|
+
// default, but it must not be SILENT — an operator who assumed the spoof guard
|
|
2289
|
+
// was live deserves to be told it is not. Latched once per process because
|
|
2290
|
+
// pollPrHumanComments runs every `prPollCommentsEvery` ticks over every active
|
|
2291
|
+
// ADO PR; warning per comment would bury the engine log.
|
|
2292
|
+
let _adoMarkerSpoofGuardWarned = false;
|
|
2293
|
+
|
|
2294
|
+
function _warnAdoMarkerSpoofGuardInactiveOnce() {
|
|
2295
|
+
if (_adoMarkerSpoofGuardWarned) return;
|
|
2296
|
+
_adoMarkerSpoofGuardWarned = true;
|
|
2297
|
+
log('warn',
|
|
2298
|
+
'ADO comment classifier: config.engine.minionsAdoIdentities is empty — the Minions marker '
|
|
2299
|
+
+ 'spoof guard is INACTIVE. Marker-bearing comments are trusted as agent-authored without an '
|
|
2300
|
+
+ 'author cross-check (fail-safe: prevents agent fix-summaries from re-triggering fix '
|
|
2301
|
+
+ 'dispatches). Populate engine.minionsAdoIdentities with the ADO identities Minions posts '
|
|
2302
|
+
+ 'under to re-enable the cross-check.');
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
// Test hook: clears the once-per-process latch so a test can assert the
|
|
2306
|
+
// warning fires exactly once.
|
|
2307
|
+
function _resetAdoMarkerSpoofGuardWarningForTest() {
|
|
2308
|
+
_adoMarkerSpoofGuardWarned = false;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2007
2311
|
async function pollPrHumanComments(config) {
|
|
2008
2312
|
// #3758 — inert when no project actually uses ADO.
|
|
2009
2313
|
if (!hasAdoProjectConfigured(config)) return;
|
|
@@ -2024,7 +2328,7 @@ async function pollPrHumanComments(config) {
|
|
|
2024
2328
|
// including bot/CI bodies and explicitly-ignored authors that get filtered
|
|
2025
2329
|
// out before reaching `allHumanComments`. We need their dates so the
|
|
2026
2330
|
// cutoff can advance past them on every successful poll, mirroring the
|
|
2027
|
-
// GitHub poller convention (engine/github.js: `allCommentDates`). Without
|
|
2331
|
+
// GitHub poller convention (engine/providers/github.js: `allCommentDates`). Without
|
|
2028
2332
|
// this, ignored/CI comments are re-fetched and re-filtered every tick.
|
|
2029
2333
|
const allHumanComments = [];
|
|
2030
2334
|
const newHumanComments = [];
|
|
@@ -2042,20 +2346,28 @@ async function pollPrHumanComments(config) {
|
|
|
2042
2346
|
// P-f23classifier (F3, security note): ADO has no `viewerDidAuthor`
|
|
2043
2347
|
// equivalent. A human pasting our `<!-- minions:agent=... -->` marker
|
|
2044
2348
|
// (e.g. by copy-paste from a fix-summary) would otherwise be
|
|
2045
|
-
// misclassified as agent-authored.
|
|
2046
|
-
//
|
|
2047
|
-
//
|
|
2048
|
-
//
|
|
2049
|
-
//
|
|
2050
|
-
//
|
|
2051
|
-
//
|
|
2349
|
+
// misclassified as agent-authored. `config.engine.minionsAdoIdentities`
|
|
2350
|
+
// (uniqueName / displayName / id / descriptor) is the ADO stand-in for
|
|
2351
|
+
// that identity signal.
|
|
2352
|
+
//
|
|
2353
|
+
// W-msbf1kf6033f46cb: an EMPTY allowlist means the identity signal is
|
|
2354
|
+
// UNAVAILABLE, not that the author is someone else. Those two used to
|
|
2355
|
+
// collapse to the same answer here, so with the default empty allowlist
|
|
2356
|
+
// `markerAuthored` was ALWAYS false and the hidden marker had zero
|
|
2357
|
+
// filtering power on ADO. Every agent fix-summary was then reclassified
|
|
2358
|
+
// as new human feedback and re-triggered a fix dispatch on the same PR
|
|
2359
|
+
// (ADO PR 5530936: ten consecutive zero-change dispatches). Resolution
|
|
2360
|
+
// now runs through the shared `classifyMarkerAuthorship` seam, which
|
|
2361
|
+
// fails SAFE on an unavailable signal while keeping the spoof guard at
|
|
2362
|
+
// full strength on a real mismatch. See the module header of
|
|
2363
|
+
// engine/providers/comment-classifier.js for the cost asymmetry.
|
|
2052
2364
|
const minionsAdoIdentities = new Set(
|
|
2053
2365
|
(Array.isArray(config.engine?.minionsAdoIdentities) ? config.engine.minionsAdoIdentities : [])
|
|
2054
2366
|
.map(s => String(s || '').toLowerCase())
|
|
2055
2367
|
.filter(Boolean)
|
|
2056
2368
|
);
|
|
2369
|
+
const adoIdentityAvailable = minionsAdoIdentities.size > 0;
|
|
2057
2370
|
const isAdoMinionsAuthor = (comment) => {
|
|
2058
|
-
if (minionsAdoIdentities.size === 0) return false;
|
|
2059
2371
|
const author = comment?.author || {};
|
|
2060
2372
|
const candidates = [author.uniqueName, author.displayName, author.id, author.descriptor];
|
|
2061
2373
|
for (const cand of candidates) {
|
|
@@ -2071,7 +2383,7 @@ async function pollPrHumanComments(config) {
|
|
|
2071
2383
|
// fresh human replies (humans do reply on resolved threads after a
|
|
2072
2384
|
// teammate marks them done). Classification below — ignoredAuthors,
|
|
2073
2385
|
// CI/coverage report regex, agent (Minions) detection — runs on every
|
|
2074
|
-
// collected comment, mirroring engine/github.js pollPrHumanComments
|
|
2386
|
+
// collected comment, mirroring engine/providers/github.js pollPrHumanComments
|
|
2075
2387
|
// which has never filtered by thread state.
|
|
2076
2388
|
for (const comment of (thread.comments || [])) {
|
|
2077
2389
|
if (!comment.content || comment.commentType === 'system') continue;
|
|
@@ -2110,13 +2422,28 @@ async function pollPrHumanComments(config) {
|
|
|
2110
2422
|
if (automationReason) continue;
|
|
2111
2423
|
// P-f23classifier (F3): detect agent comments (included in context
|
|
2112
2424
|
// but don't trigger fix). Structurally symmetric to
|
|
2113
|
-
// engine/github.js _isAgentComment + _isMinionsAuthoredComment:
|
|
2114
|
-
// - marker
|
|
2425
|
+
// engine/providers/github.js _isAgentComment + _isMinionsAuthoredComment:
|
|
2426
|
+
// - marker authorship via the shared classifyMarkerAuthorship seam
|
|
2427
|
+
// (marker + ADO identity signal, fail-safe when unconfigured), OR
|
|
2115
2428
|
// - legacy body patterns (Minions(...), by Minions, [minions]), OR
|
|
2116
2429
|
// - hasVerdictPrefix(content) — engine-owned structured-output
|
|
2117
2430
|
// contract emitted by playbooks/review.md.
|
|
2118
|
-
|
|
2119
|
-
|
|
2431
|
+
//
|
|
2432
|
+
// W-msbf1kf6033f46cb: the legacy patterns are a weak fallback, not a
|
|
2433
|
+
// safety net. `/\bMinions\s*\(/i` does NOT match `[Minions](url)` and
|
|
2434
|
+
// `/\bby\s+Minions\b/i` does NOT match `by [Minions]` (the bracket
|
|
2435
|
+
// intervenes), so on ADO only a literal `[Minions]` ever fires. An
|
|
2436
|
+
// agent posting through the raw `az`/REST fallback omits that brand
|
|
2437
|
+
// sign-off entirely — the marker is what has to carry the weight.
|
|
2438
|
+
const markerAuthorship = classifyMarkerAuthorship({
|
|
2439
|
+
body: content,
|
|
2440
|
+
identityAvailable: adoIdentityAvailable,
|
|
2441
|
+
identityMatches: adoIdentityAvailable && isAdoMinionsAuthor(comment),
|
|
2442
|
+
});
|
|
2443
|
+
if (markerAuthorship === MARKER_AUTHORSHIP.IDENTITY_UNAVAILABLE) {
|
|
2444
|
+
_warnAdoMarkerSpoofGuardInactiveOnce();
|
|
2445
|
+
}
|
|
2446
|
+
const isAgent = isMarkerAuthored(markerAuthorship)
|
|
2120
2447
|
|| /\bMinions\s*\(/i.test(content)
|
|
2121
2448
|
|| /\bby\s+Minions\b/i.test(content)
|
|
2122
2449
|
|| /\[minions\]/i.test(content)
|
|
@@ -2359,7 +2686,8 @@ async function reconcilePrs(config) {
|
|
|
2359
2686
|
id: prId,
|
|
2360
2687
|
prNumber: adoPr.pullRequestId,
|
|
2361
2688
|
title: (adoPr.title || `PR #${adoPr.pullRequestId}`).slice(0, 120),
|
|
2362
|
-
agent: (linkedItem?.dispatched_to ||
|
|
2689
|
+
agent: (linkedItem?.dispatched_to || shared.PR_AGENT_HUMAN).toLowerCase(),
|
|
2690
|
+
author: shared.buildAdoAuthorIdentity(adoPr.createdBy),
|
|
2363
2691
|
branch,
|
|
2364
2692
|
reviewStatus: REVIEW_STATUS.PENDING,
|
|
2365
2693
|
status: PR_STATUS.ACTIVE,
|
|
@@ -2403,7 +2731,8 @@ async function reconcilePrs(config) {
|
|
|
2403
2731
|
id: prId,
|
|
2404
2732
|
prNumber: adoPr.pullRequestId,
|
|
2405
2733
|
title: (adoPr.title || `PR #${adoPr.pullRequestId}`).slice(0, 120),
|
|
2406
|
-
agent: (linkedItem?.dispatched_to ||
|
|
2734
|
+
agent: (linkedItem?.dispatched_to || shared.PR_AGENT_HUMAN).toLowerCase(),
|
|
2735
|
+
author: shared.buildAdoAuthorIdentity(adoPr.createdBy),
|
|
2407
2736
|
branch,
|
|
2408
2737
|
reviewStatus: REVIEW_STATUS.PENDING,
|
|
2409
2738
|
status: PR_STATUS.ACTIVE,
|
|
@@ -2502,7 +2831,7 @@ async function checkLiveReviewStatus(pr, project) {
|
|
|
2502
2831
|
*
|
|
2503
2832
|
* Ensures the *authenticated reviewer's own* vote is positive (+10) when an agent
|
|
2504
2833
|
* flips their verdict from request_changes → approved on a re-review. Mirrors the
|
|
2505
|
-
* existing target-branch re-approval pattern
|
|
2834
|
+
* existing target-branch re-approval pattern in engine/ado/index.js (PUT
|
|
2506
2835
|
* reviewers/{myId} {vote:10}). Covers both:
|
|
2507
2836
|
* - prior negative vote (-5 wait-for-author, -10 rejected) → cleared to +10, and
|
|
2508
2837
|
* - prior neutral vote (0 — never cast, or reset by another path and never
|
|
@@ -2604,7 +2933,10 @@ async function resetReviewerNegativeVote(pr, project) {
|
|
|
2604
2933
|
* buildStatus" semantics from issue #1233; the caller must trust the cached
|
|
2605
2934
|
* value). When the stale set IS failing, buildStatus flips to 'failing' and
|
|
2606
2935
|
* `buildStaleMergeCommit: true` is returned (Issue #2747) — a stale failing
|
|
2607
|
-
* signal is strictly more actionable than cache fallback.
|
|
2936
|
+
* signal is strictly more actionable than cache fallback. When the newest built
|
|
2937
|
+
* merge commit PASSED but the cached status is 'failing', buildStatus is 'none'
|
|
2938
|
+
* (W-msaux21y00g79c48): the cached failure is provably older than that green
|
|
2939
|
+
* build, so the caller must skip the build-fix instead of falling back to it.
|
|
2608
2940
|
*/
|
|
2609
2941
|
async function checkLiveBuildAndConflict(pr, project) {
|
|
2610
2942
|
try {
|
|
@@ -2668,6 +3000,14 @@ async function checkLiveBuildAndConflict(pr, project) {
|
|
|
2668
3000
|
if (stale.buildStaleMergeCommit) {
|
|
2669
3001
|
buildStatus = stale.buildStatus;
|
|
2670
3002
|
buildStaleMergeCommit = true;
|
|
3003
|
+
} else if (stale.supersededCachedFailure) {
|
|
3004
|
+
// W-msaux21y00g79c48: the newest built merge commit passed, so
|
|
3005
|
+
// the cached 'failing' the caller would otherwise fall back to
|
|
3006
|
+
// is a superseded signal. Report 'none' (not null) so the
|
|
3007
|
+
// pre-dispatch gate skips the build-fix and clears the cache
|
|
3008
|
+
// instead of re-dispatching against a green branch. Not
|
|
3009
|
+
// 'passing' — the current merge commit still has no build.
|
|
3010
|
+
buildStatus = stale.buildStatus;
|
|
2671
3011
|
}
|
|
2672
3012
|
// else: leave buildStatus null — caller falls back to cached state (issue #1233).
|
|
2673
3013
|
}
|
|
@@ -2710,6 +3050,9 @@ async function fetchAdoPrMetadata(prNum, adoOrg, adoProj, adoRepo, project) {
|
|
|
2710
3050
|
description: pr.description || '',
|
|
2711
3051
|
branch: stripRefsHeads(pr.sourceRefName),
|
|
2712
3052
|
author: pr.createdBy?.displayName || '',
|
|
3053
|
+
// Structured author identity (provider/id/descriptor/login/displayName/url)
|
|
3054
|
+
// for callers that persist pr.author separately from pr.agent.
|
|
3055
|
+
authorIdentity: shared.buildAdoAuthorIdentity(pr.createdBy),
|
|
2713
3056
|
// W-mrezh0yb0007f733 — surface ADO's real creationDate so manual-link paths
|
|
2714
3057
|
// can seed `pr.created` with the true value instead of link-time now().
|
|
2715
3058
|
created: pr.creationDate || '',
|
|
@@ -2771,7 +3114,7 @@ async function getPrChangedFiles(project, prNumber) {
|
|
|
2771
3114
|
|
|
2772
3115
|
/**
|
|
2773
3116
|
* Fetch live PR and build status for a single PR number.
|
|
2774
|
-
* Used by engine/ado
|
|
3117
|
+
* Used by engine/ado/status.js so agents can check CI without raw curl calls.
|
|
2775
3118
|
* Returns { prNumber, title, branch, status, reviewStatus, buildStatus,
|
|
2776
3119
|
* buildStatusStale?, buildStatusDetail?, buildErrorLog?,
|
|
2777
3120
|
* mergeConflict, url, project } or null on auth failure.
|
|
@@ -2988,7 +3331,7 @@ function _setAdoTokenAuthNoteWriterForTest(fn) {
|
|
|
2988
3331
|
|
|
2989
3332
|
// ─── One-Shot Startup Reconciliation for Abandoned PRs (W-mp60tw0u000j3931) ───
|
|
2990
3333
|
//
|
|
2991
|
-
// ADO equivalent of engine/github.js reconcileAbandonedPrs. Same shape:
|
|
3334
|
+
// ADO equivalent of engine/providers/github.js reconcileAbandonedPrs. Same shape:
|
|
2992
3335
|
// per-(org, project, repo) base probe, cached for the duration of the pass;
|
|
2993
3336
|
// then per-PR re-probe via adoFetch. ADO PRs use `prData.status` directly
|
|
2994
3337
|
// ('active' | 'abandoned' | 'completed'), which we map back to PR_STATUS.
|
|
@@ -3126,6 +3469,9 @@ async function reconcileAbandonedPrs(config) {
|
|
|
3126
3469
|
delete pr._consecutive404s;
|
|
3127
3470
|
delete pr._404Count;
|
|
3128
3471
|
delete pr._404FirstAt;
|
|
3472
|
+
// W-msav5edk00g81710: the record left `abandoned` — drop the
|
|
3473
|
+
// bounded-re-probe anchor with it.
|
|
3474
|
+
if (upd.newStatus !== shared.PR_STATUS.ABANDONED) shared.clearPrAbandonedAt(pr);
|
|
3129
3475
|
pr._reconciledAt = reconciledAt;
|
|
3130
3476
|
pr._reconciledFrom = 'startup-pass';
|
|
3131
3477
|
} else if (upd.action === 'confirm404') {
|
|
@@ -3177,6 +3523,7 @@ module.exports = {
|
|
|
3177
3523
|
_resetAdoThrottle, // exported for testing
|
|
3178
3524
|
_setAdoThrottleForTest, // exported for testing
|
|
3179
3525
|
_setAdoTokenForTest, // exported for testing
|
|
3526
|
+
_resetAdoMarkerSpoofGuardWarningForTest, // exported for testing
|
|
3180
3527
|
_setAdoFetchForTest, // exported for testing
|
|
3181
3528
|
_resetAdoFetchForTest, // exported for testing
|
|
3182
3529
|
// Exported for unit tests — engine code MUST go through pollPrStatus / reconcilePrs.
|
|
@@ -3203,9 +3550,16 @@ module.exports = {
|
|
|
3203
3550
|
// Issue #2747 — stale merge-commit classifier + auto-queue helpers, exported for unit tests.
|
|
3204
3551
|
classifyBuildStatus,
|
|
3205
3552
|
classifyStaleBuilds,
|
|
3553
|
+
// W-msaux21y00g79c48 — newest-merge-commit narrowing for stale build sets.
|
|
3554
|
+
selectNewestMergeCommitBuilds,
|
|
3206
3555
|
isPrValidationBuild,
|
|
3207
3556
|
shouldRequeueStaleBuild,
|
|
3208
3557
|
PR_BUILD_REQUEUE_INTERVAL_MS,
|
|
3558
|
+
// W-ms7kklp8002zad24 — draft-PR branch-policy build queueing, exported for unit tests.
|
|
3559
|
+
extractDraftPrBuildDefinitionIds,
|
|
3560
|
+
adoPolicyScopeMatches,
|
|
3561
|
+
queueDraftPrValidationBuilds,
|
|
3562
|
+
ADO_BUILD_POLICY_TYPE_ID,
|
|
3209
3563
|
// #3758 — exported for unit tests of GitHub-only-config inertness.
|
|
3210
3564
|
isGitHubProject,
|
|
3211
3565
|
hasAdoProjectConfigured,
|