@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 CHANGED
@@ -1,8 +1,9 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.947 (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
@@ -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?.replace('refs/heads/', '');
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 || '').replace('refs/heads/', '');
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?.replace('refs/heads/', '') || '',
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.947",
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.