@yemi33/minions 0.1.1654 → 0.1.1656

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,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1656 (2026-05-01)
4
+
5
+ ### Features
6
+ - ADO manual PR link metadata and branch race
7
+ - gate non-terminal task completions
8
+
3
9
  ## 0.1.1654 (2026-05-01)
4
10
 
5
11
  ### Fixes
package/dashboard.js CHANGED
@@ -75,7 +75,42 @@ function getWorkItemIdFromPrLinkContext(context, workItemId) {
75
75
  return null;
76
76
  }
77
77
 
78
- function linkPullRequestForTracking({ url, title, project: projectName, autoObserve, context, workItemId }, config = CONFIG) {
78
+ function decodeUrlSegment(segment) {
79
+ try { return decodeURIComponent(segment); } catch { return segment; }
80
+ }
81
+
82
+ function parseAdoPrMetadataTarget(url) {
83
+ const raw = String(url || '');
84
+ const devAzure = raw.match(/https?:\/\/dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/]+)\/pullrequest\/(\d+)/i);
85
+ if (devAzure) {
86
+ return {
87
+ adoOrg: decodeUrlSegment(devAzure[1]),
88
+ adoProj: decodeUrlSegment(devAzure[2]),
89
+ adoRepo: decodeUrlSegment(devAzure[3]),
90
+ prNum: devAzure[4],
91
+ };
92
+ }
93
+ const visualStudio = raw.match(/https?:\/\/([^/.]+)\.visualstudio\.com\/(?:DefaultCollection\/)?([^/]+)\/_git\/([^/]+)\/pullrequest\/(\d+)/i);
94
+ if (!visualStudio) return null;
95
+ return {
96
+ adoOrg: `${decodeUrlSegment(visualStudio[1])}.visualstudio.com`,
97
+ adoProj: decodeUrlSegment(visualStudio[2]),
98
+ adoRepo: decodeUrlSegment(visualStudio[3]),
99
+ prNum: visualStudio[4],
100
+ };
101
+ }
102
+
103
+ function normalizePrMetadata(metadata) {
104
+ if (!metadata || typeof metadata !== 'object') return null;
105
+ return {
106
+ title: typeof metadata.title === 'string' ? metadata.title.trim() : '',
107
+ description: typeof metadata.description === 'string' ? metadata.description : '',
108
+ branch: typeof metadata.branch === 'string' ? metadata.branch.trim().replace(/^refs\/heads\//i, '') : '',
109
+ author: typeof metadata.author === 'string' ? metadata.author.trim() : '',
110
+ };
111
+ }
112
+
113
+ function linkPullRequestForTracking({ url, title, project: projectName, autoObserve, context, workItemId }, config = CONFIG, options = {}) {
79
114
  if (!url) {
80
115
  const err = new Error('url required');
81
116
  err.statusCode = 400;
@@ -90,13 +125,14 @@ function linkPullRequestForTracking({ url, title, project: projectName, autoObse
90
125
  const prId = shared.getCanonicalPrId(targetProject, prNum, url);
91
126
  const linkedWorkItemId = getWorkItemIdFromPrLinkContext(context, workItemId);
92
127
  const contextText = typeof context === 'string' ? context : (context == null ? '' : JSON.stringify(context));
128
+ const metadata = normalizePrMetadata(options.metadata);
93
129
  const result = shared.upsertPullRequestRecord(prPath, {
94
130
  id: prId,
95
131
  prNumber: parseInt(prNum, 10) || null,
96
- title: (title || 'PR #' + prNum + ' (polling...)').slice(0, 120),
97
- description: '',
98
- agent: 'human',
99
- branch: '',
132
+ title: (metadata?.title || title || 'PR #' + prNum + ' (polling...)').slice(0, 120),
133
+ description: metadata?.description ? metadata.description.slice(0, 500) : '',
134
+ agent: metadata?.author || 'human',
135
+ branch: metadata?.branch || '',
100
136
  reviewStatus: 'pending',
101
137
  status: 'active',
102
138
  created: new Date().toISOString(),
@@ -5994,7 +6030,16 @@ What would you like to discuss or change? When you're happy, say "approve" and I
5994
6030
  if (!url) return jsonReply(res, 400, { error: 'url required' });
5995
6031
 
5996
6032
  reloadConfig();
5997
- const { id: prId, prPath, targetProject, prNum, created, linked } = linkPullRequestForTracking(body, CONFIG);
6033
+ const adoTarget = parseAdoPrMetadataTarget(url);
6034
+ let initialPrData = null;
6035
+ if (adoTarget) {
6036
+ try {
6037
+ initialPrData = await ado.fetchAdoPrMetadata(adoTarget.prNum, adoTarget.adoOrg, adoTarget.adoProj, adoTarget.adoRepo);
6038
+ } catch (e) {
6039
+ shared.log('warn', `ADO PR link metadata fetch failed for ${url}: ${e.message}`);
6040
+ }
6041
+ }
6042
+ const { id: prId, prPath, prNum, created, linked } = linkPullRequestForTracking(body, CONFIG, { metadata: initialPrData });
5998
6043
  invalidateStatusCache();
5999
6044
  jsonReply(res, 200, { ok: true, id: prId, created, linked });
6000
6045
 
@@ -6003,16 +6048,14 @@ What would you like to discuss or change? When you're happy, say "approve" and I
6003
6048
  try {
6004
6049
  let prData = null;
6005
6050
  const ghMatch = url.match(/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+)/);
6006
- const adoMatch = url.match(/dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/]+)\/pullrequest\/(\d+)/);
6007
6051
  if (ghMatch) {
6008
6052
  const slug = ghMatch[1];
6009
6053
  const result = await shared.execAsync(`gh api "repos/${slug}/pulls/${prNum}"`, { timeout: 15000, encoding: 'utf-8' });
6010
6054
  const d = JSON.parse(result);
6011
6055
  prData = { title: d.title, description: d.body, branch: d.head?.ref, author: d.user?.login };
6012
- } else if (adoMatch) {
6013
- const [, adoOrg, adoProj, adoRepo] = adoMatch;
6056
+ } else if (adoTarget && !initialPrData) {
6014
6057
  try {
6015
- prData = await ado.fetchAdoPrMetadata(prNum, adoOrg, adoProj, adoRepo);
6058
+ prData = await ado.fetchAdoPrMetadata(adoTarget.prNum, adoTarget.adoOrg, adoTarget.adoProj, adoTarget.adoRepo);
6016
6059
  } catch { /* ADO token may not be available */ }
6017
6060
  }
6018
6061
  if (!prData) return;
package/engine/ado.js CHANGED
@@ -138,6 +138,48 @@ function clearBuildStatusStale(pr) {
138
138
  if (pr._buildStatusDetail) delete pr._buildStatusDetail;
139
139
  }
140
140
 
141
+ function isPlaceholderPrTitle(title, prNum) {
142
+ const text = String(title || '').trim();
143
+ if (!text) return true;
144
+ if (/\bpolling\.\.\./i.test(text)) return true;
145
+ if (String(prNum || '') && (text === `PR #${prNum}` || text === `PR-${prNum}`)) return true;
146
+ if (/[{}"\[\]]/.test(text)) return true;
147
+ return /^[0-9a-f-]{8,}$/i.test(text);
148
+ }
149
+
150
+ function applyAdoPrMetadata(pr, prData, prNum) {
151
+ if (!pr || !prData) return false;
152
+ let updated = false;
153
+
154
+ const sourceBranch = stripRefsHeads(prData.sourceRefName);
155
+ if (sourceBranch && (pr.branch !== sourceBranch || pr._branchResolutionError || pr._pendingReason === shared.PR_PENDING_REASON.MISSING_BRANCH)) {
156
+ pr.branch = sourceBranch;
157
+ if (pr._branchResolutionError) delete pr._branchResolutionError;
158
+ if (pr._pendingReason === shared.PR_PENDING_REASON.MISSING_BRANCH) delete pr._pendingReason;
159
+ updated = true;
160
+ }
161
+
162
+ const title = String(prData.title || '').trim();
163
+ if (title && isPlaceholderPrTitle(pr.title, prNum) && pr.title !== title.slice(0, 120)) {
164
+ pr.title = title.slice(0, 120);
165
+ updated = true;
166
+ }
167
+
168
+ const description = typeof prData.description === 'string' ? prData.description : '';
169
+ if (description && (pr.description == null || pr.description === '')) {
170
+ pr.description = description.slice(0, 500);
171
+ updated = true;
172
+ }
173
+
174
+ const author = String(prData.createdBy?.displayName || '').trim();
175
+ if (author && pr.agent === 'human') {
176
+ pr.agent = author;
177
+ updated = true;
178
+ }
179
+
180
+ return updated;
181
+ }
182
+
141
183
  // ── Build/Review Status Helpers ───────────────────────────────────────────────
142
184
 
143
185
  /** Classify an array of ADO build records into a single status string. */
@@ -454,13 +496,7 @@ async function pollPrStatus(config) {
454
496
 
455
497
  const prData = await adoFetch(`${repoBase}?api-version=7.1`, token);
456
498
 
457
- const sourceBranch = stripRefsHeads(prData.sourceRefName);
458
- if (sourceBranch && pr.branch !== sourceBranch) {
459
- pr.branch = sourceBranch;
460
- if (pr._branchResolutionError) delete pr._branchResolutionError;
461
- if (pr._pendingReason === 'missing_pr_branch') delete pr._pendingReason;
462
- updated = true;
463
- }
499
+ if (applyAdoPrMetadata(pr, prData, prNum)) updated = true;
464
500
 
465
501
  let newStatus = pr.status;
466
502
  if (prData.status === 'completed') newStatus = PR_STATUS.MERGED;
@@ -1121,7 +1157,8 @@ async function checkLiveBuildAndConflict(pr, project) {
1121
1157
  async function fetchAdoPrMetadata(prNum, adoOrg, adoProj, adoRepo) {
1122
1158
  const token = await getAdoToken();
1123
1159
  if (!token) return null;
1124
- const url = `https://dev.azure.com/${adoOrg}/${adoProj}/_apis/git/repositories/${adoRepo}/pullrequests/${prNum}?api-version=7.1`;
1160
+ const orgBase = getAdoOrgBase({ adoOrg });
1161
+ const url = `${orgBase}/${encodeURIComponent(adoProj)}/_apis/git/repositories/${encodeURIComponent(adoRepo)}/pullrequests/${encodeURIComponent(String(prNum))}?api-version=7.1`;
1125
1162
  const pr = await adoFetch(url, token);
1126
1163
  if (!pr) return null;
1127
1164
  return {
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-01T04:15:15.814Z"
4
+ "cachedAt": "2026-05-01T04:21:06.784Z"
5
5
  }
@@ -2260,7 +2260,7 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
2260
2260
  if (nonTerminalCompletion) {
2261
2261
  skipDoneStatus = true;
2262
2262
  const reason = deferNonTerminalCompletion(meta, nonTerminalCompletion);
2263
- completionContractFailure = { reason, itemId: meta.item.id, nonTerminal: true };
2263
+ completionContractFailure = { reason, itemId: meta.item.id, nonTerminal: true, processWorkItemFailure: false };
2264
2264
  if (!nonCleanReportWritten) {
2265
2265
  writeNonCleanAgentReport(dispatchItem, agentId, 'partial', structuredCompletion, completionGateSummary, code);
2266
2266
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1654",
3
+ "version": "0.1.1656",
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"