@yemi33/minions 0.1.1662 → 0.1.1664

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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1664 (2026-05-01)
4
+
5
+ ### Features
6
+ - prevent duplicate PR fix dispatch (#1953)
7
+
3
8
  ## 0.1.1662 (2026-05-01)
4
9
 
5
10
  ### Other
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-01T15:02:53.143Z"
4
+ "cachedAt": "2026-05-01T19:29:49.074Z"
5
5
  }
@@ -59,16 +59,52 @@ function mutateDispatch(mutator) {
59
59
 
60
60
  // ─── Add to Dispatch ─────────────────────────────────────────────────────────
61
61
 
62
- function getPrDispatchDedupeKey(entry) {
63
- if (!entry?.meta?.pr || !entry?.meta?.project || !entry?.type) return null;
64
- const type = entry.type === WORK_TYPE.FIX ? WORK_TYPE.FIX : entry.type;
62
+ function getDispatchProjectKey(project) {
63
+ if (!project) return '';
64
+ return project.name || (project.localPath ? path.resolve(project.localPath).toLowerCase() : '');
65
+ }
66
+
67
+ function getPrDispatchTargetKey(entry) {
68
+ if (!entry?.meta?.pr || !entry?.meta?.project) return null;
65
69
  const project = entry.meta.project;
66
- const projectKey = project.name
67
- || (project.localPath ? path.resolve(project.localPath).toLowerCase() : '');
70
+ const projectKey = getDispatchProjectKey(project);
68
71
  if (!projectKey) return null;
69
72
  const prKey = shared.getCanonicalPrId(project, entry.meta.pr, entry.meta.pr?.url || '');
70
73
  if (!prKey) return null;
71
- return `${projectKey}:${type}:${prKey}`;
74
+ return `${projectKey}:${prKey}`;
75
+ }
76
+
77
+ function getPrDispatchDedupeKey(entry) {
78
+ if (!entry?.type) return null;
79
+ const targetKey = getPrDispatchTargetKey(entry);
80
+ if (!targetKey) return null;
81
+ const type = entry.type === WORK_TYPE.FIX ? WORK_TYPE.FIX : entry.type;
82
+ return `${targetKey}:${type}`;
83
+ }
84
+
85
+ function getBranchDispatchLockKey(entry) {
86
+ const branch = entry?.meta?.branch || entry?.meta?.pr?.branch || '';
87
+ if (!branch) return null;
88
+ const normalizedBranch = shared.sanitizeBranch(branch);
89
+ if (!normalizedBranch) return null;
90
+ const projectKey = getDispatchProjectKey(entry?.meta?.project) || 'default';
91
+ return `${projectKey}:${normalizedBranch}`;
92
+ }
93
+
94
+ function findActivePrOrBranchLock(dispatch, item) {
95
+ if (item?.type !== WORK_TYPE.FIX) return null;
96
+ const active = dispatch.active || [];
97
+ const prTargetKey = getPrDispatchTargetKey(item);
98
+ if (prTargetKey) {
99
+ const existing = active.find(d => getPrDispatchTargetKey(d) === prTargetKey);
100
+ if (existing) return { existing, reason: `active PR dispatch ${prTargetKey}` };
101
+ }
102
+
103
+ const branchLockKey = getBranchDispatchLockKey(item);
104
+ if (!branchLockKey) return null;
105
+ const existing = active.find(d => getBranchDispatchLockKey(d) === branchLockKey);
106
+ if (!existing) return null;
107
+ return { existing, reason: `active branch dispatch ${branchLockKey}` };
72
108
  }
73
109
 
74
110
  function addToDispatch(item) {
@@ -106,6 +142,11 @@ function addToDispatch(item) {
106
142
  return dispatch;
107
143
  }
108
144
  }
145
+ const activeLock = findActivePrOrBranchLock(dispatch, item);
146
+ if (activeLock) {
147
+ log('info', `Dedup: skipping ${item.id} — ${activeLock.reason} already in ${activeLock.existing.id}`);
148
+ return dispatch;
149
+ }
109
150
  dispatch.pending.push(item);
110
151
  added = true;
111
152
  return dispatch;
package/engine/shared.js CHANGED
@@ -1275,9 +1275,11 @@ function projectPrPath(project) {
1275
1275
 
1276
1276
  function resolveProjectForPrPath(filePath, config = null) {
1277
1277
  const resolvedPath = path.resolve(filePath);
1278
- for (const project of getProjects(config)) {
1278
+ const projects = getProjects(config);
1279
+ for (const project of projects) {
1279
1280
  if (path.resolve(projectPrPath(project)) === resolvedPath) return project;
1280
1281
  }
1282
+ if (projects.length === 1) return projects[0];
1281
1283
  return null;
1282
1284
  }
1283
1285
 
@@ -1673,7 +1675,9 @@ function normalizePrScopeSegment(value) {
1673
1675
  function parseCanonicalPrId(value) {
1674
1676
  const match = String(value || '').trim().match(/^(github|ado):(.+?)#(\d+)$/i);
1675
1677
  if (!match) return null;
1676
- const scope = `${match[1].toLowerCase()}:${match[2].split('/').map(normalizePrScopeSegment).join('/')}`;
1678
+ const normalizedParts = match[2].split('/').map(normalizePrScopeSegment);
1679
+ if (!normalizedParts.some(Boolean)) return null;
1680
+ const scope = `${match[1].toLowerCase()}:${normalizedParts.join('/')}`;
1677
1681
  return { scope, prNumber: parseInt(match[3], 10) };
1678
1682
  }
1679
1683
 
@@ -2429,9 +2433,14 @@ module.exports = {
2429
2433
  projectStateDir,
2430
2434
  projectWorkItemsPath,
2431
2435
  projectPrPath,
2436
+ resolveProjectForPrPath, // exported for testing
2432
2437
  getPrLinks,
2433
2438
  addPrLink,
2439
+ normalizePrScopeSegment, // exported for testing
2434
2440
  parseCanonicalPrId,
2441
+ parseGitHubPrUrl, // exported for testing
2442
+ parseAdoPrUrl, // exported for testing
2443
+ parsePrUrl, // exported for testing
2435
2444
  getProjectPrScope,
2436
2445
  getPrNumber,
2437
2446
  getPrDisplayId,
@@ -2444,6 +2453,8 @@ module.exports = {
2444
2453
  applyPrFieldDelta,
2445
2454
  normalizePrRecord,
2446
2455
  normalizePrRecords,
2456
+ normalizePrLinkItems, // exported for testing
2457
+ mergePrLinkItems, // exported for testing
2447
2458
  upsertPullRequestRecord,
2448
2459
  nextWorkItemId,
2449
2460
  getAdoOrgBase,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1662",
3
+ "version": "0.1.1664",
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"