@yemi33/minions 0.1.948 → 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 CHANGED
@@ -1,8 +1,9 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.948 (2026-04-14)
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
@@ -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
@@ -782,6 +782,84 @@ async function fetchAdoPrMetadata(prNum, adoOrg, adoProj, adoRepo) {
782
782
  };
783
783
  }
784
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
+
785
863
  module.exports = {
786
864
  getAdoToken,
787
865
  adoFetch,
@@ -792,5 +870,6 @@ module.exports = {
792
870
  needsAdoPollRetry,
793
871
  isAdoAuthError, // exported for testing
794
872
  fetchAdoPrMetadata,
873
+ fetchSinglePrBuildStatus,
795
874
  };
796
875
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.948",
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.