@yemi33/minions 0.1.739 → 0.1.741
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 +8 -0
- package/engine/ado.js +14 -1
- package/engine/github.js +14 -1
- package/engine/lifecycle.js +1 -0
- package/engine/shared.js +55 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/engine/ado.js
CHANGED
|
@@ -561,10 +561,14 @@ async function reconcilePrs(config) {
|
|
|
561
561
|
const confirmedItemId = linkedItem ? linkedItemId : null;
|
|
562
562
|
|
|
563
563
|
if (existingIds.has(prId)) {
|
|
564
|
+
const existing = existingPrs.find(p => p.id === prId);
|
|
565
|
+
// Backfill prNumber for existing records missing it
|
|
566
|
+
if (existing && existing.prNumber == null) {
|
|
567
|
+
existing.prNumber = adoPr.pullRequestId;
|
|
568
|
+
}
|
|
564
569
|
// PR already tracked — write link to pr-links.json if we can extract an ID
|
|
565
570
|
if (confirmedItemId) {
|
|
566
571
|
addPrLink(prId, confirmedItemId);
|
|
567
|
-
const existing = existingPrs.find(p => p.id === prId);
|
|
568
572
|
if (existing && !(existing.prdItems || []).includes(confirmedItemId)) {
|
|
569
573
|
existing.prdItems = Array.isArray(existing.prdItems) ? existing.prdItems : [];
|
|
570
574
|
existing.prdItems.push(confirmedItemId);
|
|
@@ -582,6 +586,7 @@ async function reconcilePrs(config) {
|
|
|
582
586
|
const prUrl = project.prUrlBase ? project.prUrlBase + adoPr.pullRequestId : '';
|
|
583
587
|
existingPrs.push({
|
|
584
588
|
id: prId,
|
|
589
|
+
prNumber: adoPr.pullRequestId,
|
|
585
590
|
title: (adoPr.title || `PR #${adoPr.pullRequestId}`).slice(0, 120),
|
|
586
591
|
agent: (linkedItem?.dispatched_to || adoPr.createdBy?.displayName || 'unknown').toLowerCase(),
|
|
587
592
|
branch,
|
|
@@ -597,6 +602,14 @@ async function reconcilePrs(config) {
|
|
|
597
602
|
log('info', `PR reconciliation: added ${prId} (branch: ${branch}, linked to ${confirmedItemId}) to ${project.name}`);
|
|
598
603
|
}
|
|
599
604
|
|
|
605
|
+
// Backfill prNumber from pr.id for any PR missing it (e.g. created before prNumber was stored)
|
|
606
|
+
for (const pr of existingPrs) {
|
|
607
|
+
if (pr.prNumber == null) {
|
|
608
|
+
const derived = parseInt((pr.id || '').replace(/^PR-/, ''), 10);
|
|
609
|
+
if (derived) pr.prNumber = derived;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
600
613
|
// Backfill prdItems from pr-links for any PR with empty array
|
|
601
614
|
const prLinks = shared.getPrLinks();
|
|
602
615
|
let backfilled = 0;
|
package/engine/github.js
CHANGED
|
@@ -562,9 +562,13 @@ async function reconcilePrs(config) {
|
|
|
562
562
|
const confirmedItemId = linkedItem ? linkedItemId : null;
|
|
563
563
|
|
|
564
564
|
if (existingIds.has(prId)) {
|
|
565
|
+
const existing = currentPrs.find(p => p.id === prId);
|
|
566
|
+
// Backfill prNumber for existing records missing it
|
|
567
|
+
if (existing && existing.prNumber == null) {
|
|
568
|
+
existing.prNumber = ghPr.number;
|
|
569
|
+
}
|
|
565
570
|
if (confirmedItemId) {
|
|
566
571
|
addPrLink(prId, confirmedItemId);
|
|
567
|
-
const existing = currentPrs.find(p => p.id === prId);
|
|
568
572
|
if (existing && !(existing.prdItems || []).includes(confirmedItemId)) {
|
|
569
573
|
existing.prdItems = Array.isArray(existing.prdItems) ? existing.prdItems : [];
|
|
570
574
|
existing.prdItems.push(confirmedItemId);
|
|
@@ -580,6 +584,7 @@ async function reconcilePrs(config) {
|
|
|
580
584
|
|
|
581
585
|
currentPrs.push({
|
|
582
586
|
id: prId,
|
|
587
|
+
prNumber: ghPr.number,
|
|
583
588
|
title: (ghPr.title || `PR #${ghPr.number}`).slice(0, 120),
|
|
584
589
|
agent: (linkedItem?.dispatched_to || ghPr.user?.login || 'unknown').toLowerCase(),
|
|
585
590
|
branch,
|
|
@@ -596,6 +601,14 @@ async function reconcilePrs(config) {
|
|
|
596
601
|
log('info', `GitHub PR reconciliation: added ${prId} (branch: ${branch}, linked to ${confirmedItemId}) to ${project.name}`);
|
|
597
602
|
}
|
|
598
603
|
|
|
604
|
+
// Backfill prNumber from pr.id for any PR missing it (e.g. created before prNumber was stored)
|
|
605
|
+
for (const pr of currentPrs) {
|
|
606
|
+
if (pr.prNumber == null) {
|
|
607
|
+
const derived = parseInt((pr.id || '').replace(/^PR-/, ''), 10);
|
|
608
|
+
if (derived) pr.prNumber = derived;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
599
612
|
// Backfill prdItems from pr-links for any PR with empty array
|
|
600
613
|
const prLinks = getPrLinks();
|
|
601
614
|
let backfilled = 0;
|
package/engine/lifecycle.js
CHANGED
|
@@ -666,6 +666,7 @@ function syncPrsFromOutput(output, agentId, meta, config) {
|
|
|
666
666
|
prId, fullId,
|
|
667
667
|
entry: {
|
|
668
668
|
id: fullId,
|
|
669
|
+
prNumber: parseInt(prId, 10) || prId,
|
|
669
670
|
title: (title || `PR created by ${agentName}`).slice(0, 120),
|
|
670
671
|
agent: agentName,
|
|
671
672
|
branch: meta?.branch || '',
|
package/engine/shared.js
CHANGED
|
@@ -857,8 +857,49 @@ function mutatePullRequests(filePath, mutator) {
|
|
|
857
857
|
* Remove a git worktree, falling back to fs.rmSync if git fails (e.g., locked on Windows).
|
|
858
858
|
* Only removes directories under worktreeRoot to prevent accidental deletion.
|
|
859
859
|
* Tracks persistent failures to avoid retrying locked paths every cleanup cycle.
|
|
860
|
+
*
|
|
861
|
+
* On Windows, reserved device-name files (NUL, CON, PRN, AUX, etc.) can appear in
|
|
862
|
+
* worktree directories when shell redirections run under Git Bash/WSL. These block
|
|
863
|
+
* git worktree remove, fs.rmSync, and PowerShell Remove-Item. Two mitigations:
|
|
864
|
+
* 1. _purgeReservedFiles() deletes them via the \\?\ extended path prefix before removal
|
|
865
|
+
* 2. cmd /c rd /s /q as final fallback handles any remaining reserved names
|
|
860
866
|
*/
|
|
861
867
|
const _removeWorktreeFailures = new Map(); // path → { count, lastAttempt }
|
|
868
|
+
|
|
869
|
+
// Windows reserved device names that cannot be deleted via normal paths
|
|
870
|
+
const _WIN_RESERVED_NAMES = new Set([
|
|
871
|
+
'CON', 'PRN', 'AUX', 'NUL',
|
|
872
|
+
'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
|
|
873
|
+
'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9',
|
|
874
|
+
]);
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Recursively purge Windows reserved-name pseudo-files (NUL, CON, PRN, AUX, etc.)
|
|
878
|
+
* using the \\?\ extended path prefix that bypasses reserved-name interpretation.
|
|
879
|
+
* Called before normal deletion attempts on Windows to unblock git/fs operations.
|
|
880
|
+
*/
|
|
881
|
+
function _purgeReservedFiles(dirPath) {
|
|
882
|
+
let entries;
|
|
883
|
+
try { entries = fs.readdirSync(dirPath, { withFileTypes: true }); } catch { return; }
|
|
884
|
+
for (const entry of entries) {
|
|
885
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
886
|
+
try {
|
|
887
|
+
if (entry.isDirectory()) {
|
|
888
|
+
_purgeReservedFiles(fullPath);
|
|
889
|
+
} else {
|
|
890
|
+
// Match NUL, NUL.txt, con, con.log, etc.
|
|
891
|
+
const baseName = entry.name.toUpperCase().split('.')[0];
|
|
892
|
+
if (_WIN_RESERVED_NAMES.has(baseName)) {
|
|
893
|
+
// \\?\ prefix bypasses Win32 reserved-name interpretation
|
|
894
|
+
fs.unlinkSync('\\\\?\\' + fullPath);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
} catch {
|
|
898
|
+
// Best-effort: file may already be gone or inaccessible
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
862
903
|
function removeWorktree(wtPath, gitRoot, worktreeRoot) {
|
|
863
904
|
const resolved = path.resolve(wtPath);
|
|
864
905
|
const resolvedRoot = path.resolve(worktreeRoot) + path.sep;
|
|
@@ -870,6 +911,11 @@ function removeWorktree(wtPath, gitRoot, worktreeRoot) {
|
|
|
870
911
|
const prior = _removeWorktreeFailures.get(resolved);
|
|
871
912
|
if (prior && prior.count >= 3 && Date.now() - prior.lastAttempt < 3600000) return false;
|
|
872
913
|
|
|
914
|
+
// Windows: purge reserved-name pseudo-files (NUL, CON, etc.) that block normal deletion
|
|
915
|
+
if (process.platform === 'win32') {
|
|
916
|
+
_purgeReservedFiles(resolved);
|
|
917
|
+
}
|
|
918
|
+
|
|
873
919
|
try {
|
|
874
920
|
exec(`git worktree remove "${wtPath}" --force`, { cwd: gitRoot, stdio: 'pipe', timeout: 15000, windowsHide: true });
|
|
875
921
|
_removeWorktreeFailures.delete(resolved);
|
|
@@ -881,14 +927,17 @@ function removeWorktree(wtPath, gitRoot, worktreeRoot) {
|
|
|
881
927
|
_removeWorktreeFailures.delete(resolved);
|
|
882
928
|
return true;
|
|
883
929
|
} catch (rmErr) {
|
|
884
|
-
// Windows
|
|
885
|
-
|
|
930
|
+
// Windows: try cmd /c rd /s /q for any error — handles reserved device names,
|
|
931
|
+
// locked files, and partially-deleted directories (not just EPERM)
|
|
932
|
+
if (process.platform === 'win32') {
|
|
886
933
|
try {
|
|
887
|
-
exec(`rd /s /q "${resolved}"`, { stdio: 'pipe', timeout: 15000, windowsHide: true });
|
|
934
|
+
exec(`cmd /c rd /s /q "${resolved}"`, { stdio: 'pipe', timeout: 15000, windowsHide: true });
|
|
888
935
|
try { exec('git worktree prune', { cwd: gitRoot, stdio: 'pipe', timeout: 10000, windowsHide: true }); } catch {}
|
|
889
936
|
_removeWorktreeFailures.delete(resolved);
|
|
890
937
|
return true;
|
|
891
|
-
} catch {
|
|
938
|
+
} catch (rdErr) {
|
|
939
|
+
log('warn', `removeWorktree: rd /s /q fallback failed for ${wtPath}: ${rdErr.message}`);
|
|
940
|
+
}
|
|
892
941
|
}
|
|
893
942
|
const fail = _removeWorktreeFailures.get(resolved) || { count: 0, lastAttempt: 0 };
|
|
894
943
|
fail.count++;
|
|
@@ -963,6 +1012,8 @@ module.exports = {
|
|
|
963
1012
|
killGracefully,
|
|
964
1013
|
killImmediate,
|
|
965
1014
|
removeWorktree,
|
|
1015
|
+
_purgeReservedFiles, // exported for testing
|
|
1016
|
+
_WIN_RESERVED_NAMES, // exported for testing
|
|
966
1017
|
LOCK_STALE_MS,
|
|
967
1018
|
flushLogs,
|
|
968
1019
|
slugify,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.741",
|
|
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"
|