@yemi33/minions 0.1.949 → 0.1.951
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/CHANGELOG.md +3 -2
- package/dashboard/js/settings.js +2 -0
- package/engine/ado-status.js +15 -23
- package/engine/ado.js +55 -57
- package/engine/shared.js +1 -0
- package/engine.js +3 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.951 (2026-04-14)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
+
- gate auto-fix conflict dispatch behind autoFixConflicts flag
|
|
6
7
|
- add PR/build status CLI shim and agent guidance
|
|
7
8
|
- make ADO poll frequency configurable and ungate reconcilePrs
|
|
8
9
|
- update render-work-items.js output viewer to use renderAgentOutput
|
|
@@ -22,7 +23,6 @@
|
|
|
22
23
|
- fix dispatch.js mutator fallback to use nullish coalescing (#966)
|
|
23
24
|
- fix stall recovery nested lock violation (#965)
|
|
24
25
|
- fix pending-rebases.json race condition with file locking (#964)
|
|
25
|
-
- add missing resolveWorkItemPath import in engine.js (#963)
|
|
26
26
|
|
|
27
27
|
### Fixes
|
|
28
28
|
- tighten build query guard to require mergeCommitId
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
- prioritize error_max_turns over permission-blocked in classifyFailure (#1001)
|
|
48
48
|
|
|
49
49
|
### Other
|
|
50
|
+
- refactor(ado): simplify ado-status and extract build/review status helpers
|
|
50
51
|
- refactor(ado): extract stripRefsHeads helper, deduplicate refs/heads/ stripping
|
|
51
52
|
- refactor(ado): simplify comments, fix declaration order, promote ado import
|
|
52
53
|
- refactor(ado): move PR enrichment fetch into fetchAdoPrMetadata helper
|
package/dashboard/js/settings.js
CHANGED
|
@@ -49,6 +49,7 @@ async function openSettings() {
|
|
|
49
49
|
settingsToggle('Auto-decompose', 'set-autoDecompose', e.autoDecompose !== false, 'Large implement items are auto-split into sub-tasks') +
|
|
50
50
|
settingsToggle('Allow Temp Agents', 'set-allowTempAgents', !!e.allowTempAgents, 'Spawn ephemeral agents when all permanent agents are busy') +
|
|
51
51
|
settingsToggle('Auto-archive Plans', 'set-autoArchive', !!e.autoArchive, 'Automatically archive plans after verify completes (off = manual archive via dashboard)') +
|
|
52
|
+
settingsToggle('Auto-fix Conflicts', 'set-autoFixConflicts', e.autoFixConflicts !== false, 'Auto-dispatch fix agents when a PR has merge conflicts') +
|
|
52
53
|
settingsToggle('ADO Polling', 'set-adoPollEnabled', e.adoPollEnabled !== false, 'Poll ADO PR status and comments each tick (reconciliation always runs)') +
|
|
53
54
|
settingsToggle('GitHub Polling', 'set-ghPollEnabled', e.ghPollEnabled !== false, 'Poll GitHub PR status and comments each tick (reconciliation always runs)') +
|
|
54
55
|
'</div>' +
|
|
@@ -237,6 +238,7 @@ async function saveSettings() {
|
|
|
237
238
|
autoDecompose: document.getElementById('set-autoDecompose').checked,
|
|
238
239
|
allowTempAgents: document.getElementById('set-allowTempAgents').checked,
|
|
239
240
|
autoArchive: document.getElementById('set-autoArchive').checked,
|
|
241
|
+
autoFixConflicts: document.getElementById('set-autoFixConflicts').checked,
|
|
240
242
|
adoPollEnabled: document.getElementById('set-adoPollEnabled').checked,
|
|
241
243
|
ghPollEnabled: document.getElementById('set-ghPollEnabled').checked,
|
|
242
244
|
adoPollStatusEvery: document.getElementById('set-adoPollStatusEvery').value,
|
package/engine/ado-status.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
const path = require('path');
|
|
25
25
|
const shared = require('./shared');
|
|
26
|
-
const { safeJsonArr, projectPrPath, getProjects } = shared;
|
|
26
|
+
const { safeJson, safeJsonArr, projectPrPath, getProjects } = shared;
|
|
27
27
|
|
|
28
28
|
// ── Arg parsing ──────────────────────────────────────────────────────────────
|
|
29
29
|
|
|
@@ -54,13 +54,6 @@ const prNumber = parseInt(prNumberArg, 10);
|
|
|
54
54
|
|
|
55
55
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
56
56
|
|
|
57
|
-
function getConfig() {
|
|
58
|
-
// Avoid pulling in queries.js to keep startup fast; read config directly.
|
|
59
|
-
const configPath = path.join(__dirname, '..', 'config.json');
|
|
60
|
-
try { return JSON.parse(require('fs').readFileSync(configPath, 'utf8')); }
|
|
61
|
-
catch { return {}; }
|
|
62
|
-
}
|
|
63
|
-
|
|
64
57
|
function findInCache(projects) {
|
|
65
58
|
for (const project of projects) {
|
|
66
59
|
const prs = safeJsonArr(projectPrPath(project));
|
|
@@ -88,7 +81,8 @@ function findInCache(projects) {
|
|
|
88
81
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
89
82
|
|
|
90
83
|
async function main() {
|
|
91
|
-
|
|
84
|
+
// Use safeJson directly — pulling in queries.js would load all cached state unnecessarily
|
|
85
|
+
const config = safeJson(path.join(__dirname, '..', 'config.json')) || {};
|
|
92
86
|
const allProjects = getProjects(config).filter(p => p.repoHost === 'ado' || !p.repoHost);
|
|
93
87
|
const projects = projectName
|
|
94
88
|
? allProjects.filter(p => p.name === projectName)
|
|
@@ -103,7 +97,6 @@ async function main() {
|
|
|
103
97
|
}
|
|
104
98
|
|
|
105
99
|
if (!live) {
|
|
106
|
-
// Fast path: read engine-maintained pull-requests.json
|
|
107
100
|
const result = findInCache(projects);
|
|
108
101
|
if (result) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
109
102
|
console.error(`PR #${prNumber} not found in cached pull-requests.json`);
|
|
@@ -111,26 +104,25 @@ async function main() {
|
|
|
111
104
|
process.exit(1);
|
|
112
105
|
}
|
|
113
106
|
|
|
114
|
-
// Live path:
|
|
107
|
+
// Live path: query all projects in parallel, return first match
|
|
115
108
|
const ado = require('./ado');
|
|
109
|
+
const settled = await Promise.allSettled(
|
|
110
|
+
projects.map(p => ado.fetchSinglePrBuildStatus(p, prNumber))
|
|
111
|
+
);
|
|
116
112
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (!e.message?.includes('404') && !e.message?.includes('not found')) {
|
|
125
|
-
console.error(`Error querying ${project.name}: ${e.message}`);
|
|
126
|
-
}
|
|
113
|
+
const found = settled.find(r => r.status === 'fulfilled' && r.value);
|
|
114
|
+
if (found) { console.log(JSON.stringify(found.value, null, 2)); return; }
|
|
115
|
+
|
|
116
|
+
// Log unexpected errors (not 404s — those just mean PR isn't in that project)
|
|
117
|
+
for (const { status, reason } of settled) {
|
|
118
|
+
if (status === 'rejected' && !reason?.message?.includes('404') && !reason?.message?.includes('not found')) {
|
|
119
|
+
console.error(`ADO query error: ${reason.message}`);
|
|
127
120
|
}
|
|
128
121
|
}
|
|
129
122
|
|
|
130
|
-
//
|
|
123
|
+
// Fall back to cache with a note that live lookup failed
|
|
131
124
|
const cached = findInCache(projects);
|
|
132
125
|
if (cached) {
|
|
133
|
-
cached._liveFailed = true;
|
|
134
126
|
cached.source = 'cached (live lookup failed — check ADO auth)';
|
|
135
127
|
console.log(JSON.stringify(cached, null, 2));
|
|
136
128
|
return;
|
package/engine/ado.js
CHANGED
|
@@ -18,6 +18,30 @@ function engine() {
|
|
|
18
18
|
|
|
19
19
|
const stripRefsHeads = s => (s || '').replace('refs/heads/', '');
|
|
20
20
|
|
|
21
|
+
// ── Build/Review Status Helpers ───────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
/** Classify an array of ADO build records into a single status string. */
|
|
24
|
+
function classifyBuildStatus(prBuilds) {
|
|
25
|
+
if (!prBuilds.length) return 'none';
|
|
26
|
+
// partiallySucceeded = warnings, not failures — counts as passing
|
|
27
|
+
const hasFailed = prBuilds.some(b => b.result === 'failed' || b.result === 'canceled');
|
|
28
|
+
const allDone = prBuilds.every(b => b.status === 'completed');
|
|
29
|
+
const allPassed = prBuilds.every(b => b.result === 'succeeded' || b.result === 'partiallySucceeded');
|
|
30
|
+
const hasRunning = prBuilds.some(b => b.status === 'inProgress' || b.status === 'notStarted');
|
|
31
|
+
if (hasFailed && allDone) return 'failing';
|
|
32
|
+
if (allDone && allPassed) return 'passing';
|
|
33
|
+
if (hasRunning) return 'running';
|
|
34
|
+
return 'none';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Map ADO reviewer vote array to a review status string. */
|
|
38
|
+
function votesToReviewStatus(votes) {
|
|
39
|
+
if (votes.some(v => v === -10)) return 'changes-requested';
|
|
40
|
+
if (votes.some(v => v >= 5)) return 'approved';
|
|
41
|
+
if (votes.some(v => v === -5)) return 'waiting';
|
|
42
|
+
return 'pending';
|
|
43
|
+
}
|
|
44
|
+
|
|
21
45
|
// ─── ADO Token Cache ─────────────────────────────────────────────────────────
|
|
22
46
|
|
|
23
47
|
let _adoTokenCache = { token: null, expiresAt: 0 };
|
|
@@ -390,14 +414,8 @@ async function pollPrStatus(config) {
|
|
|
390
414
|
const prBuilds = (buildsData?.value || []).filter(b => b.sourceVersion === mergeCommitId);
|
|
391
415
|
|
|
392
416
|
if (prBuilds.length > 0) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const allDone = prBuilds.every(b => b.status === 'completed');
|
|
396
|
-
const allPassed = prBuilds.every(b => b.result === 'succeeded' || b.result === 'partiallySucceeded');
|
|
397
|
-
const hasRunning = prBuilds.some(b => b.status === 'inProgress' || b.status === 'notStarted');
|
|
398
|
-
|
|
399
|
-
if (hasFailed && allDone) {
|
|
400
|
-
buildStatus = 'failing';
|
|
417
|
+
buildStatus = classifyBuildStatus(prBuilds);
|
|
418
|
+
if (buildStatus === 'failing') {
|
|
401
419
|
const failed = prBuilds.find(b => b.result === 'failed');
|
|
402
420
|
buildFailReason = failed?.definition?.name || 'Build failed';
|
|
403
421
|
// Build fake status objects for error log fetching
|
|
@@ -405,10 +423,6 @@ async function pollPrStatus(config) {
|
|
|
405
423
|
state: 'failed', targetUrl: `${orgBase}/${project.adoProject}/_build/results?buildId=${b.id}`,
|
|
406
424
|
_buildId: String(b.id),
|
|
407
425
|
}));
|
|
408
|
-
} else if (allDone && allPassed) {
|
|
409
|
-
buildStatus = 'passing';
|
|
410
|
-
} else if (hasRunning) {
|
|
411
|
-
buildStatus = 'running';
|
|
412
426
|
}
|
|
413
427
|
}
|
|
414
428
|
} catch (e) { log('warn', `ADO build query for ${pr.id}: ${e.message}`); }
|
|
@@ -758,10 +772,7 @@ async function checkLiveReviewStatus(pr, project) {
|
|
|
758
772
|
const prData = JSON.parse(result);
|
|
759
773
|
const votes = (prData.reviewers || []).map(r => r.vote).filter(v => v !== undefined);
|
|
760
774
|
if (votes.length === 0) return 'pending';
|
|
761
|
-
|
|
762
|
-
if (votes.some(v => v >= 5)) return 'approved';
|
|
763
|
-
if (votes.some(v => v === -5)) return 'waiting';
|
|
764
|
-
return 'pending';
|
|
775
|
+
return votesToReviewStatus(votes);
|
|
765
776
|
} catch (e) {
|
|
766
777
|
log('warn', `Live review check for ${pr.id}: ${e.message}`);
|
|
767
778
|
return null;
|
|
@@ -794,55 +805,42 @@ async function fetchSinglePrBuildStatus(project, prNumber) {
|
|
|
794
805
|
|
|
795
806
|
const orgBase = getAdoOrgBase(project);
|
|
796
807
|
const repoBase = `${orgBase}/${project.adoProject}/_apis/git/repositories/${project.repositoryId}`;
|
|
797
|
-
const
|
|
808
|
+
const mergeRef = encodeURIComponent(`refs/pull/${prNumber}/merge`);
|
|
809
|
+
const buildsUrl = `${orgBase}/${project.adoProject}/_apis/build/builds?branchName=${mergeRef}&repositoryId=${project.repositoryId}&repositoryType=TfsGit&$top=25&api-version=7.1`;
|
|
810
|
+
|
|
811
|
+
// Fetch PR metadata and builds in parallel
|
|
812
|
+
const [prData, buildsData] = await Promise.all([
|
|
813
|
+
adoFetch(`${repoBase}/pullrequests/${prNumber}?api-version=7.1`, token),
|
|
814
|
+
adoFetch(buildsUrl, token).catch(() => null),
|
|
815
|
+
]);
|
|
798
816
|
if (!prData) return null;
|
|
799
817
|
|
|
800
818
|
const mergeCommitId = prData.lastMergeCommit?.commitId;
|
|
801
|
-
|
|
819
|
+
const prBuilds = mergeCommitId
|
|
820
|
+
? (buildsData?.value || []).filter(b => b.sourceVersion === mergeCommitId)
|
|
821
|
+
: [];
|
|
822
|
+
|
|
823
|
+
let buildStatus = classifyBuildStatus(prBuilds);
|
|
802
824
|
let buildErrorLog = null;
|
|
803
825
|
|
|
804
|
-
if (
|
|
826
|
+
if (buildStatus === 'failing') {
|
|
805
827
|
try {
|
|
806
|
-
const
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
if (hasFailed && allDone) {
|
|
818
|
-
buildStatus = 'failing';
|
|
819
|
-
const failedBuilds = prBuilds.filter(b => b.result === 'failed').map(b => ({
|
|
820
|
-
state: 'failed', _buildId: String(b.id),
|
|
821
|
-
targetUrl: `${orgBase}/${project.adoProject}/_build/results?buildId=${b.id}`,
|
|
822
|
-
}));
|
|
823
|
-
const logParts = [];
|
|
824
|
-
const seenBuildIds = new Set();
|
|
825
|
-
for (const fb of failedBuilds.slice(0, 3)) {
|
|
826
|
-
const errorLog = await fetchAdoBuildErrorLog(orgBase, project, fb, token, { id: `PR-${prNumber}` }, seenBuildIds);
|
|
827
|
-
if (errorLog) logParts.push(errorLog);
|
|
828
|
-
}
|
|
829
|
-
if (logParts.length > 0) buildErrorLog = logParts.join('\n\n');
|
|
830
|
-
} else if (allDone && allPassed) {
|
|
831
|
-
buildStatus = 'passing';
|
|
832
|
-
} else if (hasRunning) {
|
|
833
|
-
buildStatus = 'running';
|
|
834
|
-
}
|
|
828
|
+
const failedBuilds = prBuilds.filter(b => b.result === 'failed').map(b => ({
|
|
829
|
+
state: 'failed', _buildId: String(b.id),
|
|
830
|
+
targetUrl: `${orgBase}/${project.adoProject}/_build/results?buildId=${b.id}`,
|
|
831
|
+
}));
|
|
832
|
+
const logParts = [];
|
|
833
|
+
const seenBuildIds = new Set();
|
|
834
|
+
const pr = { id: `PR-${prNumber}`, branch: stripRefsHeads(prData.sourceRefName) };
|
|
835
|
+
for (const fb of failedBuilds.slice(0, 3)) {
|
|
836
|
+
const errorLog = await fetchAdoBuildErrorLog(orgBase, project, fb, token, pr, seenBuildIds);
|
|
837
|
+
if (errorLog) logParts.push(errorLog);
|
|
835
838
|
}
|
|
836
|
-
|
|
839
|
+
if (logParts.length > 0) buildErrorLog = logParts.join('\n\n');
|
|
840
|
+
} catch (e) { log('warn', `fetchSinglePrBuildStatus error log for PR #${prNumber}: ${e.message}`); }
|
|
837
841
|
}
|
|
838
842
|
|
|
839
843
|
const votes = (prData.reviewers || []).map(r => r.vote);
|
|
840
|
-
let reviewStatus = 'pending';
|
|
841
|
-
if (votes.some(v => v === -10)) reviewStatus = 'changes-requested';
|
|
842
|
-
else if (votes.some(v => v >= 5)) reviewStatus = 'approved';
|
|
843
|
-
else if (votes.some(v => v === -5)) reviewStatus = 'waiting';
|
|
844
|
-
|
|
845
|
-
// Reconstruct a human-readable PR URL from the API URL
|
|
846
844
|
const prUrl = `https://dev.azure.com/${project.adoOrg}/${project.adoProject}/_git/${project.repositoryId}/pullrequest/${prNumber}`;
|
|
847
845
|
|
|
848
846
|
return {
|
|
@@ -850,9 +848,9 @@ async function fetchSinglePrBuildStatus(project, prNumber) {
|
|
|
850
848
|
title: prData.title || '',
|
|
851
849
|
branch: stripRefsHeads(prData.sourceRefName),
|
|
852
850
|
status: prData.status || 'unknown',
|
|
853
|
-
reviewStatus,
|
|
851
|
+
reviewStatus: votesToReviewStatus(votes),
|
|
854
852
|
buildStatus,
|
|
855
|
-
buildErrorLog
|
|
853
|
+
...(buildErrorLog ? { buildErrorLog } : {}),
|
|
856
854
|
mergeConflict: prData.mergeStatus === 'conflicts',
|
|
857
855
|
url: prUrl,
|
|
858
856
|
project: project.name,
|
package/engine/shared.js
CHANGED
|
@@ -533,6 +533,7 @@ const ENGINE_DEFAULTS = {
|
|
|
533
533
|
autoApprovePlans: false, // auto-approve PRDs without waiting for human approval
|
|
534
534
|
autoArchive: false, // opt-in: auto-archive plans after verify completes (false = mark ready, user archives manually)
|
|
535
535
|
autoReview: true, // auto-dispatch review agents for new PRs (disable for manual review workflow)
|
|
536
|
+
autoFixConflicts: true, // auto-dispatch fix agents when a PR has merge conflicts
|
|
536
537
|
meetingRoundTimeout: 600000, // 10min per meeting round before auto-advance
|
|
537
538
|
evalLoop: true, // enable review→fix loop after implementation completes
|
|
538
539
|
evalMaxIterations: 3, // max review→fix cycles before escalating to human
|
package/engine.js
CHANGED
|
@@ -2118,8 +2118,9 @@ async function discoverFromPrs(config, project) {
|
|
|
2118
2118
|
}
|
|
2119
2119
|
}
|
|
2120
2120
|
|
|
2121
|
-
// PRs with merge conflicts — dispatch fix to resolve
|
|
2122
|
-
|
|
2121
|
+
// PRs with merge conflicts — dispatch fix to resolve (gated by autoFixConflicts)
|
|
2122
|
+
const autoFixConflicts = config.engine?.autoFixConflicts ?? ENGINE_DEFAULTS.autoFixConflicts;
|
|
2123
|
+
if (autoFixConflicts && pr.status === PR_STATUS.ACTIVE && pr._mergeConflict && !fixDispatched) {
|
|
2123
2124
|
const key = `conflict-fix-${project?.name || 'default'}-${pr.id}`;
|
|
2124
2125
|
// Suppress re-dispatch for 10 min after last attempt — ADO/GitHub recomputes
|
|
2125
2126
|
// mergeStatus asynchronously (1–5 min lag), so the flag may stay set even after
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.951",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|