@yemi33/minions 0.1.2040 → 0.1.2041
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/engine.js +205 -97
- package/package.json +1 -1
package/engine.js
CHANGED
|
@@ -698,6 +698,35 @@ async function runWorktreeAdd(rootDir, worktreePath, addArgs, gitOpts, worktreeC
|
|
|
698
698
|
if (lastErr) throw lastErr;
|
|
699
699
|
}
|
|
700
700
|
|
|
701
|
+
// W-mphnm6a1000281b8: probe whether origin already advertises a head for
|
|
702
|
+
// `branchName`. Used by the fresh-create worktree path to choose between
|
|
703
|
+
// `git worktree add <path> <branch>` (checkout, local-track origin) and
|
|
704
|
+
// `git worktree add -b <branch> origin/<mainRef>` (fresh branch off main).
|
|
705
|
+
// Without this probe, PR-targeted fix/review/test/verify dispatches whose
|
|
706
|
+
// source branch is N commits ahead of main upstream (mirror branches like
|
|
707
|
+
// `sync/yemi33-master`, force-pushed PR branches) start their worktree on
|
|
708
|
+
// origin/<mainRef>; the stale-HEAD guard at the top of spawn-agent then
|
|
709
|
+
// trips on every dispatch and the cooldown machinery starves the PR.
|
|
710
|
+
//
|
|
711
|
+
// Returns true only on a confirmed advertised head; ls-remote exit code 2
|
|
712
|
+
// (no matching ref) and any other failure (network/auth) return false so
|
|
713
|
+
// the caller falls back to the existing `-b origin/<mainRef>` path.
|
|
714
|
+
async function probeBranchOnRemote(rootDir, branchName, gitOpts) {
|
|
715
|
+
if (!branchName || !rootDir) return false;
|
|
716
|
+
try {
|
|
717
|
+
await shared.shellSafeGit(
|
|
718
|
+
['ls-remote', '--exit-code', '--heads', 'origin', branchName],
|
|
719
|
+
{ ...gitOpts, cwd: rootDir, timeout: 10000 },
|
|
720
|
+
);
|
|
721
|
+
return true;
|
|
722
|
+
} catch (e) {
|
|
723
|
+
if (e && e.code !== 2) {
|
|
724
|
+
log('warn', `probeBranchOnRemote: ls-remote --heads origin ${branchName} failed: ${(e.message || '').split('\n')[0].slice(0, 200)} — treating as not-on-remote`);
|
|
725
|
+
}
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
701
730
|
// Detect and remove worktree registrations whose backing directory is missing
|
|
702
731
|
// on disk. `git worktree add` fails with "branch is already used by worktree
|
|
703
732
|
// at <path>" when a prior crash left such an entry behind, sometimes still in
|
|
@@ -1188,84 +1217,143 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1188
1217
|
} else {
|
|
1189
1218
|
log('info', `Creating worktree: ${worktreePath} on branch ${branchName}`);
|
|
1190
1219
|
const mainRef = sanitizeBranch(shared.resolveMainBranch(rootDir, project.mainBranch));
|
|
1191
|
-
|
|
1192
|
-
//
|
|
1193
|
-
//
|
|
1194
|
-
//
|
|
1195
|
-
//
|
|
1196
|
-
//
|
|
1197
|
-
//
|
|
1198
|
-
//
|
|
1199
|
-
//
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
log('error', `Worktree -b retry also failed for ${branchName}: ${e1b.message?.split('\n')[0]}`);
|
|
1223
|
-
throw e1b;
|
|
1224
|
-
}
|
|
1225
|
-
} else {
|
|
1226
|
-
// Branch already exists — try checkout without -b
|
|
1227
|
-
try { await shared.shellSafeGit(['fetch', 'origin', branchName], { ..._gitOpts, cwd: rootDir }); } catch (e) { log('warn', 'git: ' + e.message); }
|
|
1228
|
-
try {
|
|
1229
|
-
await runWorktreeAdd(rootDir, worktreePath, [branchName], _worktreeGitOpts, worktreeCreateRetries);
|
|
1230
|
-
log('info', `Reusing existing branch: ${branchName}`);
|
|
1231
|
-
} catch (e2) {
|
|
1232
|
-
// "already checked out" or "already used by worktree" — find and reuse or recover
|
|
1233
|
-
const alreadyUsed = e2.message?.includes('already checked out') || e2.message?.includes('already used by worktree')
|
|
1234
|
-
|| e1.message?.includes('already checked out') || e1.message?.includes('already used by worktree');
|
|
1235
|
-
if (alreadyUsed) {
|
|
1236
|
-
const existingWtPath = await findExistingWorktree(rootDir, branchName);
|
|
1237
|
-
if (existingWtPath && fs.existsSync(existingWtPath)) {
|
|
1238
|
-
// Bug fix: read dispatch under file lock so check-and-act is atomic
|
|
1239
|
-
let activelyUsed = false;
|
|
1240
|
-
mutateDispatch((dp) => {
|
|
1241
|
-
activelyUsed = (dp.active || []).some(d => {
|
|
1242
|
-
const dBranch = d.meta?.branch ? sanitizeBranch(d.meta.branch) : '';
|
|
1243
|
-
return dBranch === branchName && d.id !== id;
|
|
1244
|
-
});
|
|
1245
|
-
return dp;
|
|
1220
|
+
|
|
1221
|
+
// W-mphnm6a1000281b8: probe whether origin already has this branch.
|
|
1222
|
+
// If yes, prefer `git worktree add <path> <branch>` (checkout +
|
|
1223
|
+
// local-track origin/<branch>) over `-b <branch> origin/<mainRef>`.
|
|
1224
|
+
// The shared-branch path (~line 1158) already does this. PR-targeted
|
|
1225
|
+
// fix/review/test/verify dispatches hit this path with branchName =
|
|
1226
|
+
// the PR's source branch; branching off main when the PR branch is
|
|
1227
|
+
// N commits ahead upstream guarantees the stale-HEAD guard
|
|
1228
|
+
// (~line 1777) trips and the dispatch errors. Live repro:
|
|
1229
|
+
// opg-microsoft/minions PR #57 (sync/yemi33-master, 109 commits
|
|
1230
|
+
// ahead of main) — two consecutive fix dispatches errored on
|
|
1231
|
+
// STALE_HEAD over 10 min and the engine then starved the PR for
|
|
1232
|
+
// the cooldown window (60 min effective).
|
|
1233
|
+
const _branchOnRemote = await probeBranchOnRemote(rootDir, branchName, _gitOpts);
|
|
1234
|
+
|
|
1235
|
+
if (_branchOnRemote) {
|
|
1236
|
+
// Mirror shared-branch fetch+add (~line 1157-1159).
|
|
1237
|
+
log('info', `origin/${branchName} exists — checking out remote branch instead of -b from ${mainRef}`);
|
|
1238
|
+
try { await shared.shellSafeGit(['fetch', 'origin', branchName], { ..._gitOpts, cwd: rootDir, timeout: 30000 }); } catch (e) { log('warn', 'git: ' + e.message); }
|
|
1239
|
+
try {
|
|
1240
|
+
await runWorktreeAdd(rootDir, worktreePath, [branchName], _worktreeGitOpts, worktreeCreateRetries);
|
|
1241
|
+
} catch (eRemote) {
|
|
1242
|
+
const alreadyUsed = eRemote.message?.includes('already used by worktree') || eRemote.message?.includes('already checked out');
|
|
1243
|
+
if (alreadyUsed) {
|
|
1244
|
+
const existingWtPath = await findExistingWorktree(rootDir, branchName);
|
|
1245
|
+
if (existingWtPath && fs.existsSync(existingWtPath)) {
|
|
1246
|
+
let activelyUsed = false;
|
|
1247
|
+
mutateDispatch((dp) => {
|
|
1248
|
+
activelyUsed = (dp.active || []).some(d => {
|
|
1249
|
+
const dBranch = d.meta?.branch ? sanitizeBranch(d.meta.branch) : '';
|
|
1250
|
+
return dBranch === branchName && d.id !== id;
|
|
1246
1251
|
});
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1252
|
+
return dp;
|
|
1253
|
+
});
|
|
1254
|
+
if (activelyUsed) {
|
|
1255
|
+
log('warn', `Branch ${branchName} actively used by another agent at ${existingWtPath} — cannot create worktree`);
|
|
1256
|
+
throw eRemote;
|
|
1257
|
+
}
|
|
1258
|
+
try { shared.assertWorktreeOutsideProject(existingWtPath, rootDir); }
|
|
1259
|
+
catch (assertErr) {
|
|
1260
|
+
if (assertErr?.code === 'WORKTREE_NESTED_IN_PROJECT') { _failWorktreePreflight(assertErr); return null; }
|
|
1261
|
+
throw assertErr;
|
|
1262
|
+
}
|
|
1263
|
+
log('info', `Branch ${branchName} already checked out at ${existingWtPath} — reusing`);
|
|
1264
|
+
worktreePath = existingWtPath;
|
|
1265
|
+
} else {
|
|
1266
|
+
const pruned = await pruneStaleWorktreeForBranch(rootDir, branchName, _gitOpts);
|
|
1267
|
+
if (pruned > 0) {
|
|
1268
|
+
log('info', `Pruned ${pruned} stale worktree entry(ies) for ${branchName}; retrying worktree add`);
|
|
1269
|
+
await runWorktreeAdd(rootDir, worktreePath, [branchName], _worktreeGitOpts, 0);
|
|
1270
|
+
} else { throw eRemote; }
|
|
1271
|
+
}
|
|
1272
|
+
} else { throw eRemote; }
|
|
1273
|
+
}
|
|
1274
|
+
} else {
|
|
1275
|
+
// Branch isn't on origin (or probe failed) — start a fresh local
|
|
1276
|
+
// branch from origin/<mainRef>. This is the dominant path for new
|
|
1277
|
+
// implement/decompose dispatches.
|
|
1278
|
+
// W-mph6n4p00006ce38: mirror the pool-borrow path (~line 1110-1114)
|
|
1279
|
+
// — fetch fresh origin/<mainRef> and start the new branch off it,
|
|
1280
|
+
// not the local ref. Without this, fresh-create dispatches inherit
|
|
1281
|
+
// whatever stale local master the engine clone happens to be on
|
|
1282
|
+
// (most painful: long-lived engine processes between restarts).
|
|
1283
|
+
// Non-fatal: if the fetch fails (network blip, transient auth),
|
|
1284
|
+
// fall back to local mainRef so the dispatch still progresses;
|
|
1285
|
+
// the dep-merge phase's own fetch + the on-failure
|
|
1286
|
+
// `git reset --hard origin/<mainRef>` recovery remain as safety nets.
|
|
1287
|
+
let _freshCreateBase = mainRef;
|
|
1288
|
+
try {
|
|
1289
|
+
await shared.shellSafeGit(['fetch', 'origin', mainRef], { ..._gitOpts, cwd: rootDir, timeout: 30000 });
|
|
1290
|
+
_freshCreateBase = `origin/${mainRef}`;
|
|
1291
|
+
} catch (mainFetchErr) {
|
|
1292
|
+
log('warn', `Failed to fetch origin/${mainRef} before fresh-create worktree for ${branchName}: ${mainFetchErr.message} — falling back to local ${mainRef}`);
|
|
1293
|
+
}
|
|
1294
|
+
try {
|
|
1295
|
+
await runWorktreeAdd(rootDir, worktreePath, ['-b', branchName, _freshCreateBase], _worktreeGitOpts, worktreeCreateRetries);
|
|
1296
|
+
} catch (e1) {
|
|
1297
|
+
const branchExists = e1.message?.includes('already exists');
|
|
1298
|
+
log('warn', `Worktree -b failed for ${branchName}: ${e1.message?.split('\n')[0]}`);
|
|
1299
|
+
if (!branchExists) {
|
|
1300
|
+
// Transient error (lock, timeout) — prune, clean, and retry -b once more
|
|
1301
|
+
log('info', `Retrying -b create after prune for ${branchName}`);
|
|
1302
|
+
try { await shared.shellSafeGit(['worktree', 'prune'], { ..._gitOpts, cwd: rootDir, timeout: 15000 }); } catch { /* optional */ }
|
|
1303
|
+
removeStaleIndexLock(rootDir);
|
|
1304
|
+
// Clean up partial worktree directory from failed attempt
|
|
1305
|
+
try { if (fs.existsSync(worktreePath)) fs.rmSync(worktreePath, { recursive: true, force: true }); } catch { /* optional */ }
|
|
1306
|
+
try {
|
|
1307
|
+
await runWorktreeAdd(rootDir, worktreePath, ['-b', branchName, _freshCreateBase], _worktreeGitOpts, 0);
|
|
1308
|
+
} catch (e1b) {
|
|
1309
|
+
log('error', `Worktree -b retry also failed for ${branchName}: ${e1b.message?.split('\n')[0]}`);
|
|
1310
|
+
throw e1b;
|
|
1311
|
+
}
|
|
1312
|
+
} else {
|
|
1313
|
+
// Branch already exists — try checkout without -b
|
|
1314
|
+
try { await shared.shellSafeGit(['fetch', 'origin', branchName], { ..._gitOpts, cwd: rootDir }); } catch (e) { log('warn', 'git: ' + e.message); }
|
|
1315
|
+
try {
|
|
1316
|
+
await runWorktreeAdd(rootDir, worktreePath, [branchName], _worktreeGitOpts, worktreeCreateRetries);
|
|
1317
|
+
log('info', `Reusing existing branch: ${branchName}`);
|
|
1318
|
+
} catch (e2) {
|
|
1319
|
+
// "already checked out" or "already used by worktree" — find and reuse or recover
|
|
1320
|
+
const alreadyUsed = e2.message?.includes('already checked out') || e2.message?.includes('already used by worktree')
|
|
1321
|
+
|| e1.message?.includes('already checked out') || e1.message?.includes('already used by worktree');
|
|
1322
|
+
if (alreadyUsed) {
|
|
1323
|
+
const existingWtPath = await findExistingWorktree(rootDir, branchName);
|
|
1324
|
+
if (existingWtPath && fs.existsSync(existingWtPath)) {
|
|
1325
|
+
// Bug fix: read dispatch under file lock so check-and-act is atomic
|
|
1326
|
+
let activelyUsed = false;
|
|
1327
|
+
mutateDispatch((dp) => {
|
|
1328
|
+
activelyUsed = (dp.active || []).some(d => {
|
|
1329
|
+
const dBranch = d.meta?.branch ? sanitizeBranch(d.meta.branch) : '';
|
|
1330
|
+
return dBranch === branchName && d.id !== id;
|
|
1331
|
+
});
|
|
1332
|
+
return dp;
|
|
1333
|
+
});
|
|
1334
|
+
if (activelyUsed) {
|
|
1335
|
+
log('warn', `Branch ${branchName} actively used by another agent at ${existingWtPath} — cannot create worktree`);
|
|
1336
|
+
throw e2;
|
|
1337
|
+
}
|
|
1338
|
+
try { shared.assertWorktreeOutsideProject(existingWtPath, rootDir); }
|
|
1339
|
+
catch (assertErr) {
|
|
1340
|
+
if (assertErr?.code === 'WORKTREE_NESTED_IN_PROJECT') { _failWorktreePreflight(assertErr); return null; }
|
|
1341
|
+
throw assertErr;
|
|
1342
|
+
}
|
|
1343
|
+
log('info', `Branch ${branchName} already checked out at ${existingWtPath} — reusing`);
|
|
1344
|
+
worktreePath = existingWtPath;
|
|
1345
|
+
} else if (existingWtPath && !fs.existsSync(existingWtPath)) {
|
|
1346
|
+
log('warn', `Branch ${branchName} tracked in missing dir ${existingWtPath} — pruning and recreating`);
|
|
1347
|
+
try { await shared.shellSafeGit(['worktree', 'prune'], { ..._gitOpts, cwd: rootDir, timeout: 10000 }); } catch (e) { log('warn', 'git: ' + e.message); }
|
|
1348
|
+
await runWorktreeAdd(rootDir, worktreePath, [branchName], _worktreeGitOpts, worktreeCreateRetries);
|
|
1349
|
+
log('info', `Recovered worktree for ${branchName} after stale entry prune`);
|
|
1350
|
+
} else {
|
|
1351
|
+
try { await shared.shellSafeGit(['worktree', 'prune'], { ..._gitOpts, cwd: rootDir, timeout: 10000 }); } catch (e) { log('warn', 'git: ' + e.message); }
|
|
1352
|
+
await runWorktreeAdd(rootDir, worktreePath, [branchName], _worktreeGitOpts, worktreeCreateRetries);
|
|
1255
1353
|
}
|
|
1256
|
-
log('info', `Branch ${branchName} already checked out at ${existingWtPath} — reusing`);
|
|
1257
|
-
worktreePath = existingWtPath;
|
|
1258
|
-
} else if (existingWtPath && !fs.existsSync(existingWtPath)) {
|
|
1259
|
-
log('warn', `Branch ${branchName} tracked in missing dir ${existingWtPath} — pruning and recreating`);
|
|
1260
|
-
try { await shared.shellSafeGit(['worktree', 'prune'], { ..._gitOpts, cwd: rootDir, timeout: 10000 }); } catch (e) { log('warn', 'git: ' + e.message); }
|
|
1261
|
-
await runWorktreeAdd(rootDir, worktreePath, [branchName], _worktreeGitOpts, worktreeCreateRetries);
|
|
1262
|
-
log('info', `Recovered worktree for ${branchName} after stale entry prune`);
|
|
1263
1354
|
} else {
|
|
1264
|
-
|
|
1265
|
-
await runWorktreeAdd(rootDir, worktreePath, [branchName], _worktreeGitOpts, worktreeCreateRetries);
|
|
1355
|
+
throw e2;
|
|
1266
1356
|
}
|
|
1267
|
-
} else {
|
|
1268
|
-
throw e2;
|
|
1269
1357
|
}
|
|
1270
1358
|
}
|
|
1271
1359
|
}
|
|
@@ -4502,29 +4590,48 @@ async function discoverFromPrs(config, project) {
|
|
|
4502
4590
|
&& !isPrNoOpFixCauseSuppressed(pr, shared.PR_FIX_CAUSE.REVIEW_FEEDBACK)) {
|
|
4503
4591
|
const reviewCauseKey = getPrAutomationCauseKey('review-feedback', pr);
|
|
4504
4592
|
const key = getPrAutomationDispatchKey(`fix-${project?.name || 'default'}-${prDisplayId}`, reviewCauseKey);
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4593
|
+
// W-mphnm6a1000281b8: cause-local skip mirroring the human-feedback
|
|
4594
|
+
// (#2632) and build-failure (#2632 audit) guards above. A previous bare
|
|
4595
|
+
// `continue` here aborted the entire PR iteration when the
|
|
4596
|
+
// review-feedback dispatch was throttled, on cooldown, or already
|
|
4597
|
+
// dispatched — starving the build-fix and conflict-fix blocks below
|
|
4598
|
+
// even though their own dedupe keys had not been hit. Live repro on
|
|
4599
|
+
// opg-microsoft/minions PR #57: two STALE_HEAD-errored review-feedback
|
|
4600
|
+
// dispatches stamped a 60-min effective cooldown, and the PR received
|
|
4601
|
+
// zero build-fix dispatches for the rest of the cooldown window even
|
|
4602
|
+
// though buildStatus stayed `failing` and the build-fix key was clean.
|
|
4603
|
+
// Skip ONLY this cause; let iteration fall through to downstream
|
|
4604
|
+
// blocks (re-review already ran above; build-fix + conflict-fix run
|
|
4605
|
+
// below).
|
|
4606
|
+
const skipReviewFeedback =
|
|
4607
|
+
isPrAutomationCauseHandledOrPending(project, pr, reviewCauseKey)
|
|
4608
|
+
|| fixThrottled
|
|
4609
|
+
|| isAlreadyDispatched(key)
|
|
4610
|
+
|| isOnCooldown(key, cooldownMs);
|
|
4611
|
+
if (!skipReviewFeedback) {
|
|
4612
|
+
const agentId = resolveAgent('fix', config, { authorAgent: pr.agent });
|
|
4613
|
+
if (agentId) {
|
|
4614
|
+
const prBranch = ensurePrBranchForDispatch(project, pr, 'fix');
|
|
4615
|
+
if (prBranch) {
|
|
4616
|
+
const item = buildPrDispatch(agentId, config, project, pr, 'fix', {
|
|
4617
|
+
pr_id: pr.id, pr_branch: prBranch,
|
|
4618
|
+
review_note: pr.minionsReview?.note || pr.reviewNote || 'See PR thread comments',
|
|
4619
|
+
}, `Fix ${pr.id}: ${pr.title || ''} — review feedback`, {
|
|
4620
|
+
dispatchKey: key, cooldownKey: key, automationCauseKey: reviewCauseKey, source: 'pr', pr, branch: prBranch, project: projMeta,
|
|
4621
|
+
// W-mpg58wv3 — closure-loop binding. Carries the originating minion review
|
|
4622
|
+
// WI id (and any ADO thread ids it cited) onto the fix WI so the
|
|
4623
|
+
// post-completion path in lifecycle.js can auto-dispatch a re-review
|
|
4624
|
+
// against the same PR. Both fields fall through to null/[] when the
|
|
4625
|
+
// upstream review didn't expose them.
|
|
4626
|
+
addresses_review_wi: pr.minionsReview?.sourceItem || null,
|
|
4627
|
+
addresses_threads: Array.isArray(pr.minionsReview?.threads) ? pr.minionsReview.threads.slice() : [],
|
|
4628
|
+
});
|
|
4629
|
+
if (item) {
|
|
4630
|
+
newWork.push(item); fixDispatched = true;
|
|
4631
|
+
}
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
} // end if (!skipReviewFeedback) — cause-local guard for W-mphnm6a1000281b8
|
|
4528
4635
|
}
|
|
4529
4636
|
|
|
4530
4637
|
// PRs with build failures — route to author (has session context from implementing)
|
|
@@ -6872,6 +6979,7 @@ module.exports = {
|
|
|
6872
6979
|
isWorktreeRetryableError, removeStaleIndexLock, syncReusedWorktree, assertCleanSharedWorktree, // exported for testing
|
|
6873
6980
|
pruneStaleWorktreeForBranch, // exported for testing
|
|
6874
6981
|
findExistingWorktree, // exported for testing
|
|
6982
|
+
probeBranchOnRemote, // exported for testing (W-mphnm6a1000281b8)
|
|
6875
6983
|
_maxTurnsForType, buildProjectContext, normalizeAc, _buildAgentSpawnFlags, _classifyAgentFailure, // exported for testing
|
|
6876
6984
|
promoteCheckpointSteeringForClose, // exported for testing
|
|
6877
6985
|
normalizePrBranch, resolvePrBranch, prCausePart, getPrCauseHead, getPrCauseBase, getPrAutomationCauseKey, getPrAutomationDispatchKey, // exported for testing
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2041",
|
|
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"
|