@yemi33/minions 0.1.947 → 0.1.949
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/engine/ado-status.js +143 -0
- package/engine/ado.js +84 -3
- package/package.json +1 -1
- package/playbooks/shared-rules.md +22 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.949 (2026-04-14)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
+
- add PR/build status CLI shim and agent guidance
|
|
6
7
|
- make ADO poll frequency configurable and ungate reconcilePrs
|
|
7
8
|
- update render-work-items.js output viewer to use renderAgentOutput
|
|
8
9
|
- update detail-panel.js Output Log tab to use renderAgentOutput
|
|
@@ -22,7 +23,6 @@
|
|
|
22
23
|
- fix stall recovery nested lock violation (#965)
|
|
23
24
|
- fix pending-rebases.json race condition with file locking (#964)
|
|
24
25
|
- add missing resolveWorkItemPath import in engine.js (#963)
|
|
25
|
-
- bump plan-to-prd max turns from 20 to 35
|
|
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): extract stripRefsHeads helper, deduplicate refs/heads/ stripping
|
|
50
51
|
- refactor(ado): simplify comments, fix declaration order, promote ado import
|
|
51
52
|
- refactor(ado): move PR enrichment fetch into fetchAdoPrMetadata helper
|
|
52
53
|
- refactor: Make renderLiveChatMessage a thin wrapper over renderAgentOutput
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* engine/ado-status.js — CLI shim for querying PR and build status.
|
|
4
|
+
*
|
|
5
|
+
* Agents steered to "check on the builds" or "is CI green for PR #123" should
|
|
6
|
+
* use this instead of raw curl + azureauth calls. All ADO auth and retry logic
|
|
7
|
+
* is handled by ado.js internally.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node engine/ado-status.js <prNumber>
|
|
11
|
+
* node engine/ado-status.js <prNumber> --live
|
|
12
|
+
* node engine/ado-status.js <prNumber> --project MyProject
|
|
13
|
+
* node engine/ado-status.js <prNumber> --live --project MyProject
|
|
14
|
+
*
|
|
15
|
+
* Output: JSON to stdout. Exit 0 on success, 1 on error/not-found.
|
|
16
|
+
*
|
|
17
|
+
* Cached (default): reads pull-requests.json maintained by the engine (~3 min stale).
|
|
18
|
+
* Live (--live): makes fresh ADO API calls — use when engine isn't running or you
|
|
19
|
+
* just pushed and need up-to-the-moment build status.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const shared = require('./shared');
|
|
26
|
+
const { safeJsonArr, projectPrPath, getProjects } = shared;
|
|
27
|
+
|
|
28
|
+
// ── Arg parsing ──────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
const args = process.argv.slice(2);
|
|
31
|
+
const prNumberArg = args.find(a => /^\d+$/.test(a));
|
|
32
|
+
const live = args.includes('--live');
|
|
33
|
+
const projectIdx = args.indexOf('--project');
|
|
34
|
+
const projectName = projectIdx >= 0 ? args[projectIdx + 1] : null;
|
|
35
|
+
|
|
36
|
+
if (!prNumberArg) {
|
|
37
|
+
console.error([
|
|
38
|
+
'Usage: node engine/ado-status.js <prNumber> [--live] [--project <name>]',
|
|
39
|
+
'',
|
|
40
|
+
' <prNumber> PR number to look up (required)',
|
|
41
|
+
' --live Make a fresh ADO API call instead of reading cached state',
|
|
42
|
+
' --project <name> Scope search to one project (optional)',
|
|
43
|
+
'',
|
|
44
|
+
'Output: JSON with fields: prNumber, title, branch, status, reviewStatus,',
|
|
45
|
+
' buildStatus, buildErrorLog (if failing), mergeConflict, url, project, source',
|
|
46
|
+
'',
|
|
47
|
+
'buildStatus values: passing | failing | running | none',
|
|
48
|
+
'reviewStatus values: approved | changes-requested | waiting | pending',
|
|
49
|
+
].join('\n'));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const prNumber = parseInt(prNumberArg, 10);
|
|
54
|
+
|
|
55
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
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
|
+
function findInCache(projects) {
|
|
65
|
+
for (const project of projects) {
|
|
66
|
+
const prs = safeJsonArr(projectPrPath(project));
|
|
67
|
+
const pr = prs.find(p => p.prNumber === prNumber || p.id === `PR-${prNumber}`);
|
|
68
|
+
if (!pr) continue;
|
|
69
|
+
const out = {
|
|
70
|
+
prNumber: pr.prNumber || prNumber,
|
|
71
|
+
id: pr.id,
|
|
72
|
+
title: pr.title || '',
|
|
73
|
+
branch: pr.branch || '',
|
|
74
|
+
status: pr.status || 'unknown',
|
|
75
|
+
reviewStatus: pr.reviewStatus || 'pending',
|
|
76
|
+
buildStatus: pr.buildStatus || 'none',
|
|
77
|
+
url: pr.url || '',
|
|
78
|
+
project: project.name,
|
|
79
|
+
source: 'cached',
|
|
80
|
+
};
|
|
81
|
+
if (pr.buildErrorLog) out.buildErrorLog = pr.buildErrorLog;
|
|
82
|
+
if (pr._mergeConflict) out.mergeConflict = true;
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
async function main() {
|
|
91
|
+
const config = getConfig();
|
|
92
|
+
const allProjects = getProjects(config).filter(p => p.repoHost === 'ado' || !p.repoHost);
|
|
93
|
+
const projects = projectName
|
|
94
|
+
? allProjects.filter(p => p.name === projectName)
|
|
95
|
+
: allProjects;
|
|
96
|
+
|
|
97
|
+
if (projects.length === 0) {
|
|
98
|
+
const msg = projectName
|
|
99
|
+
? `Project "${projectName}" not found in config.json`
|
|
100
|
+
: 'No ADO projects configured in config.json';
|
|
101
|
+
console.error(msg);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!live) {
|
|
106
|
+
// Fast path: read engine-maintained pull-requests.json
|
|
107
|
+
const result = findInCache(projects);
|
|
108
|
+
if (result) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
109
|
+
console.error(`PR #${prNumber} not found in cached pull-requests.json`);
|
|
110
|
+
console.error('Tip: try --live for a fresh ADO API call, or check the PR number is correct.');
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Live path: call ADO API via ado.js (handles auth, retry, circuit breaking)
|
|
115
|
+
const ado = require('./ado');
|
|
116
|
+
|
|
117
|
+
// Try each project until we find the PR
|
|
118
|
+
for (const project of projects) {
|
|
119
|
+
try {
|
|
120
|
+
const result = await ado.fetchSinglePrBuildStatus(project, prNumber);
|
|
121
|
+
if (result) { console.log(JSON.stringify(result, null, 2)); return; }
|
|
122
|
+
} catch (e) {
|
|
123
|
+
// 404 or similar — PR not in this project, try the next one
|
|
124
|
+
if (!e.message?.includes('404') && !e.message?.includes('not found')) {
|
|
125
|
+
console.error(`Error querying ${project.name}: ${e.message}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Live lookup failed — fall back to cache
|
|
131
|
+
const cached = findInCache(projects);
|
|
132
|
+
if (cached) {
|
|
133
|
+
cached._liveFailed = true;
|
|
134
|
+
cached.source = 'cached (live lookup failed — check ADO auth)';
|
|
135
|
+
console.log(JSON.stringify(cached, null, 2));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.error(`PR #${prNumber} not found (tried live ADO API and cached pull-requests.json).`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
main().catch(e => { console.error(e.message); process.exit(1); });
|
package/engine/ado.js
CHANGED
|
@@ -16,6 +16,8 @@ function engine() {
|
|
|
16
16
|
return _engine;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
const stripRefsHeads = s => (s || '').replace('refs/heads/', '');
|
|
20
|
+
|
|
19
21
|
// ─── ADO Token Cache ─────────────────────────────────────────────────────────
|
|
20
22
|
|
|
21
23
|
let _adoTokenCache = { token: null, expiresAt: 0 };
|
|
@@ -117,7 +119,7 @@ async function fetchAdoBuildErrorLog(orgBase, project, failedStatus, token, pr,
|
|
|
117
119
|
if (!buildId) {
|
|
118
120
|
// Fallback: query recent failed builds for this PR's source branch
|
|
119
121
|
try {
|
|
120
|
-
const branch = pr?.branch || pr?.sourceRefName
|
|
122
|
+
const branch = pr?.branch || stripRefsHeads(pr?.sourceRefName);
|
|
121
123
|
if (branch) {
|
|
122
124
|
const buildsUrl = `${orgBase}/${project.adoProject}/_apis/build/builds?branchName=refs/heads/${encodeURIComponent(branch)}&statusFilter=completed&resultFilter=failed&$top=3&api-version=7.1`;
|
|
123
125
|
const builds = await adoFetch(buildsUrl, token);
|
|
@@ -648,7 +650,7 @@ async function reconcilePrs(config) {
|
|
|
648
650
|
let projectUpdated = 0;
|
|
649
651
|
for (const adoPr of adoPrs) {
|
|
650
652
|
const prId = `PR-${adoPr.pullRequestId}`;
|
|
651
|
-
const branch = (adoPr.sourceRefName
|
|
653
|
+
const branch = stripRefsHeads(adoPr.sourceRefName);
|
|
652
654
|
const title = adoPr.title || '';
|
|
653
655
|
// Extract item ID from branch name or PR title (e.g., feat(P-2cafdc2a): ...)
|
|
654
656
|
const branchMatch = branch.match(/(P-[a-z0-9]{6,})/i) || branch.match(/(W-[a-z0-9]{6,})/i) || branch.match(/(PL-[a-z0-9]{6,})/i);
|
|
@@ -775,11 +777,89 @@ async function fetchAdoPrMetadata(prNum, adoOrg, adoProj, adoRepo) {
|
|
|
775
777
|
return {
|
|
776
778
|
title: pr.title || '',
|
|
777
779
|
description: pr.description || '',
|
|
778
|
-
branch: pr.sourceRefName
|
|
780
|
+
branch: stripRefsHeads(pr.sourceRefName),
|
|
779
781
|
author: pr.createdBy?.displayName || '',
|
|
780
782
|
};
|
|
781
783
|
}
|
|
782
784
|
|
|
785
|
+
/**
|
|
786
|
+
* Fetch live PR and build status for a single PR number.
|
|
787
|
+
* Used by engine/ado-status.js so agents can check CI without raw curl calls.
|
|
788
|
+
* Returns { prNumber, title, branch, status, reviewStatus, buildStatus, buildErrorLog?,
|
|
789
|
+
* mergeConflict, url, project } or null on auth failure.
|
|
790
|
+
*/
|
|
791
|
+
async function fetchSinglePrBuildStatus(project, prNumber) {
|
|
792
|
+
const token = await getAdoToken();
|
|
793
|
+
if (!token) return null;
|
|
794
|
+
|
|
795
|
+
const orgBase = getAdoOrgBase(project);
|
|
796
|
+
const repoBase = `${orgBase}/${project.adoProject}/_apis/git/repositories/${project.repositoryId}`;
|
|
797
|
+
const prData = await adoFetch(`${repoBase}/pullrequests/${prNumber}?api-version=7.1`, token);
|
|
798
|
+
if (!prData) return null;
|
|
799
|
+
|
|
800
|
+
const mergeCommitId = prData.lastMergeCommit?.commitId;
|
|
801
|
+
let buildStatus = 'none';
|
|
802
|
+
let buildErrorLog = null;
|
|
803
|
+
|
|
804
|
+
if (mergeCommitId) {
|
|
805
|
+
try {
|
|
806
|
+
const mergeRef = encodeURIComponent(`refs/pull/${prNumber}/merge`);
|
|
807
|
+
const buildsUrl = `${orgBase}/${project.adoProject}/_apis/build/builds?branchName=${mergeRef}&repositoryId=${project.repositoryId}&repositoryType=TfsGit&$top=25&api-version=7.1`;
|
|
808
|
+
const buildsData = await adoFetch(buildsUrl, token);
|
|
809
|
+
const prBuilds = (buildsData?.value || []).filter(b => b.sourceVersion === mergeCommitId);
|
|
810
|
+
|
|
811
|
+
if (prBuilds.length > 0) {
|
|
812
|
+
const hasFailed = prBuilds.some(b => b.result === 'failed' || b.result === 'canceled');
|
|
813
|
+
const allDone = prBuilds.every(b => b.status === 'completed');
|
|
814
|
+
const allPassed = prBuilds.every(b => b.result === 'succeeded' || b.result === 'partiallySucceeded');
|
|
815
|
+
const hasRunning = prBuilds.some(b => b.status === 'inProgress' || b.status === 'notStarted');
|
|
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
|
+
}
|
|
835
|
+
}
|
|
836
|
+
} catch (e) { log('warn', `fetchSinglePrBuildStatus build query for PR #${prNumber}: ${e.message}`); }
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
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
|
+
const prUrl = `https://dev.azure.com/${project.adoOrg}/${project.adoProject}/_git/${project.repositoryId}/pullrequest/${prNumber}`;
|
|
847
|
+
|
|
848
|
+
return {
|
|
849
|
+
prNumber,
|
|
850
|
+
title: prData.title || '',
|
|
851
|
+
branch: stripRefsHeads(prData.sourceRefName),
|
|
852
|
+
status: prData.status || 'unknown',
|
|
853
|
+
reviewStatus,
|
|
854
|
+
buildStatus,
|
|
855
|
+
buildErrorLog: buildErrorLog || undefined,
|
|
856
|
+
mergeConflict: prData.mergeStatus === 'conflicts',
|
|
857
|
+
url: prUrl,
|
|
858
|
+
project: project.name,
|
|
859
|
+
source: 'live',
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
|
|
783
863
|
module.exports = {
|
|
784
864
|
getAdoToken,
|
|
785
865
|
adoFetch,
|
|
@@ -790,5 +870,6 @@ module.exports = {
|
|
|
790
870
|
needsAdoPollRetry,
|
|
791
871
|
isAdoAuthError, // exported for testing
|
|
792
872
|
fetchAdoPrMetadata,
|
|
873
|
+
fetchSinglePrBuildStatus,
|
|
793
874
|
};
|
|
794
875
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.949",
|
|
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"
|
|
@@ -32,3 +32,25 @@ Your context window may be compacted or summarized mid-task by Claude's automati
|
|
|
32
32
|
````
|
|
33
33
|
The `name` and `description` fields are required. `scope` defaults to `minions` (global). Use `scope: project` + `project: ProjectName` for project-specific skills.
|
|
34
34
|
- Do TDD where it makes sense — write failing tests first, then implement, then verify tests pass. Especially for bug fixes (write a test that reproduces the bug) and new utility functions.
|
|
35
|
+
|
|
36
|
+
## Checking PR and Build Status
|
|
37
|
+
|
|
38
|
+
When asked to check build status, CI results, or review state for a PR:
|
|
39
|
+
|
|
40
|
+
**Preferred — read cached state (always fresh within ~3 min when engine is running):**
|
|
41
|
+
Find the PR in `projects/<project-name>/pull-requests.json` by `prNumber`. Key fields:
|
|
42
|
+
- `buildStatus` — `passing` | `failing` | `running` | `none`
|
|
43
|
+
- `buildErrorLog` — compiler/pipeline errors when `buildStatus` is `failing`
|
|
44
|
+
- `reviewStatus` — `approved` | `changes-requested` | `waiting` | `pending`
|
|
45
|
+
- `status` — `active` | `merged` | `abandoned`
|
|
46
|
+
- `url` — link to the PR in ADO
|
|
47
|
+
|
|
48
|
+
**Live status (when engine isn't running or you need up-to-the-moment results):**
|
|
49
|
+
```bash
|
|
50
|
+
node engine/ado-status.js <prNumber> # reads cached pull-requests.json
|
|
51
|
+
node engine/ado-status.js <prNumber> --live # fresh ADO API call
|
|
52
|
+
node engine/ado-status.js <prNumber> --live --project MyProject
|
|
53
|
+
```
|
|
54
|
+
Output is JSON with the same fields. Exit 0 on success, 1 if not found.
|
|
55
|
+
|
|
56
|
+
**Never make raw `curl` calls to ADO APIs directly.** Use `node engine/ado-status.js` which routes through `ado.js` — authenticated, retried, circuit-broken. Raw `azureauth` + curl bypasses all of that.
|