@yemi33/minions 0.1.216 → 0.1.218
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 +16 -0
- package/bin/minions.js +2 -2
- package/engine/cli.js +1 -1
- package/engine/preflight.js +6 -5
- package/engine.js +66 -219
- package/minions.js +6 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.218 (2026-04-02)
|
|
4
|
+
|
|
5
|
+
### Engine
|
|
6
|
+
- engine.js
|
|
7
|
+
- engine/cli.js
|
|
8
|
+
|
|
9
|
+
### Other
|
|
10
|
+
- bin/minions.js
|
|
11
|
+
- minions.js
|
|
12
|
+
- team.md
|
|
13
|
+
|
|
14
|
+
## 0.1.217 (2026-04-02)
|
|
15
|
+
|
|
16
|
+
### Engine
|
|
17
|
+
- engine/preflight.js
|
|
18
|
+
|
|
3
19
|
## 0.1.216 (2026-04-02)
|
|
4
20
|
|
|
5
21
|
### Engine
|
package/bin/minions.js
CHANGED
|
@@ -438,8 +438,8 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
438
438
|
Minions — Central AI dev team manager
|
|
439
439
|
|
|
440
440
|
Setup:
|
|
441
|
-
minions init
|
|
442
|
-
minions init --force Upgrade engine code + add new files
|
|
441
|
+
minions init Bootstrap ~/.minions/ (first time)
|
|
442
|
+
minions init --force Upgrade engine code + add new files
|
|
443
443
|
minions version Show installed vs package version
|
|
444
444
|
minions doctor Check prerequisites and runtime health
|
|
445
445
|
minions add <project-dir> Link a project (interactive)
|
package/engine/cli.js
CHANGED
package/engine/preflight.js
CHANGED
|
@@ -222,12 +222,13 @@ function doctor(minionsHome) {
|
|
|
222
222
|
|
|
223
223
|
// Check playbooks
|
|
224
224
|
const playbooksDir = path.join(minionsHome, 'playbooks');
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
225
|
+
// Discover all .md playbooks in the directory — don't hardcode
|
|
226
|
+
let playbooks = [];
|
|
227
|
+
try { playbooks = fs.readdirSync(playbooksDir).filter(f => f.endsWith('.md')); } catch { /* dir may not exist */ }
|
|
228
|
+
if (playbooks.length > 0) {
|
|
229
|
+
runtimeResults.push({ name: 'Playbooks', ok: true, message: `${playbooks.length} playbooks found` });
|
|
229
230
|
} else {
|
|
230
|
-
runtimeResults.push({ name: 'Playbooks', ok: false, message:
|
|
231
|
+
runtimeResults.push({ name: 'Playbooks', ok: false, message: 'no playbooks found in playbooks/ — run: minions init --force' });
|
|
231
232
|
}
|
|
232
233
|
|
|
233
234
|
// Check port 7331 availability (only if dashboard isn't running)
|
package/engine.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
const fs = require('fs');
|
|
25
25
|
const path = require('path');
|
|
26
26
|
const shared = require('./engine/shared');
|
|
27
|
-
const { exec, execSilent, runFile, ENGINE_DEFAULTS: DEFAULTS
|
|
27
|
+
const { exec, execSilent, runFile, ENGINE_DEFAULTS: DEFAULTS } = shared;
|
|
28
28
|
const queries = require('./engine/queries');
|
|
29
29
|
|
|
30
30
|
// ─── Paths ──────────────────────────────────────────────────────────────────
|
|
@@ -120,7 +120,7 @@ const { getRouting, parseRoutingTable, getRoutingTableCached, getMonthlySpend,
|
|
|
120
120
|
|
|
121
121
|
// ─── Playbook, system prompt, agent context (extracted to engine/playbook.js) ─
|
|
122
122
|
|
|
123
|
-
const { renderPlaybook,
|
|
123
|
+
const { renderPlaybook, buildSystemPrompt, buildAgentContext, selectPlaybook,
|
|
124
124
|
buildBaseVars, buildPrDispatch, resolveTaskContext,
|
|
125
125
|
getRepoHostLabel, getRepoHostToolRule } = require('./engine/playbook');
|
|
126
126
|
|
|
@@ -307,7 +307,7 @@ function spawnAgent(dispatchItem, config) {
|
|
|
307
307
|
|
|
308
308
|
if (isSharedBranch) {
|
|
309
309
|
log('info', `Creating worktree for shared branch: ${worktreePath} on ${branchName}`);
|
|
310
|
-
try { exec(`git fetch origin "${branchName}"`, { ..._gitOpts, cwd: rootDir }); } catch (e) { log('warn', 'git
|
|
310
|
+
try { exec(`git fetch origin "${branchName}"`, { ..._gitOpts, cwd: rootDir }); } catch (e) { log('warn', 'git: ' + e.message); }
|
|
311
311
|
try {
|
|
312
312
|
runWorktreeAdd(rootDir, worktreePath, `"${branchName}"`, _worktreeGitOpts, worktreeCreateRetries);
|
|
313
313
|
} catch (eShared) {
|
|
@@ -317,11 +317,6 @@ function spawnAgent(dispatchItem, config) {
|
|
|
317
317
|
log('info', `Shared branch ${branchName} already checked out at ${existingWtPath} — reusing`);
|
|
318
318
|
worktreePath = existingWtPath;
|
|
319
319
|
} else { throw eShared; }
|
|
320
|
-
} else if (eShared.message?.includes('invalid reference') || eShared.message?.includes('not a valid branch')) {
|
|
321
|
-
// Branch doesn't exist yet — create it from main
|
|
322
|
-
log('info', `Shared branch ${branchName} not found — creating from ${project.mainBranch || 'main'}`);
|
|
323
|
-
const mainRef = sanitizeBranch(project.mainBranch || 'main');
|
|
324
|
-
runWorktreeAdd(rootDir, worktreePath, `-b "${branchName}" ${mainRef}`, _worktreeGitOpts, worktreeCreateRetries);
|
|
325
320
|
} else { throw eShared; }
|
|
326
321
|
}
|
|
327
322
|
} else {
|
|
@@ -396,7 +391,6 @@ function spawnAgent(dispatchItem, config) {
|
|
|
396
391
|
} else {
|
|
397
392
|
log('error', `Failed to create worktree for ${branchName}: ${err.message}${err.stderr ? '\n' + err.stderr.toString().slice(0, 500) : ''}`);
|
|
398
393
|
completeDispatch(id, 'error', 'Worktree creation failed: ' + (err.message || '').slice(0, 200));
|
|
399
|
-
try { updateMetrics(agentId, dispatchItem, 'error', null, 0, null); } catch { /* optional */ }
|
|
400
394
|
return null;
|
|
401
395
|
}
|
|
402
396
|
}
|
|
@@ -749,7 +743,7 @@ function areDependenciesMet(item, config) {
|
|
|
749
743
|
} catch (e) { log('warn', 'read project work items for deps: ' + e.message); }
|
|
750
744
|
}
|
|
751
745
|
// PRD item statuses that count as "done" for dep resolution
|
|
752
|
-
const PRD_MET_STATUSES = new Set(['done']);
|
|
746
|
+
const PRD_MET_STATUSES = new Set(['done', 'in-pr', 'implemented', 'complete']);
|
|
753
747
|
|
|
754
748
|
for (const depId of deps) {
|
|
755
749
|
const depItem = allWorkItems.find(w => w.id === depId);
|
|
@@ -788,7 +782,8 @@ function detectDependencyCycles(items) {
|
|
|
788
782
|
// writeInboxAlert — now in engine/dispatch.js
|
|
789
783
|
|
|
790
784
|
// Reconciles work items against known PRs.
|
|
791
|
-
// Primary linkage comes from prdItems in pull-requests.json
|
|
785
|
+
// Primary linkage comes from prdItems in pull-requests.json; fallback linkage
|
|
786
|
+
// uses engine/pr-links.json so matching does not depend on branch/title parsing.
|
|
792
787
|
// onlyIds: if provided, only items whose ID is in this Set are eligible.
|
|
793
788
|
function reconcileItemsWithPrs(items, allPrs, { onlyIds } = {}) {
|
|
794
789
|
const prLinks = shared.getPrLinks();
|
|
@@ -879,7 +874,7 @@ const { COOLDOWN_PATH, dispatchCooldowns, loadCooldowns, saveCooldowns,
|
|
|
879
874
|
// Auto-clean pending/failed work items for a PRD so they re-materialize with updated plan data
|
|
880
875
|
function autoCleanPrdWorkItems(prdFile, config) {
|
|
881
876
|
const allProjects = getProjects(config);
|
|
882
|
-
const wiPaths = [
|
|
877
|
+
const wiPaths = [path.join(MINIONS_DIR, 'work-items.json')];
|
|
883
878
|
for (const proj of allProjects) wiPaths.push(projectWorkItemsPath(proj));
|
|
884
879
|
const deletedIds = [];
|
|
885
880
|
for (const wiPath of wiPaths) {
|
|
@@ -982,7 +977,7 @@ function materializePlansAsWorkItems(config) {
|
|
|
982
977
|
log('info', `PRD ${file} invalidated (was awaiting-approval) — queuing regeneration from revised plan`);
|
|
983
978
|
|
|
984
979
|
// Collect completed items to carry over to new PRD
|
|
985
|
-
const completedStatuses = new Set(['done']);
|
|
980
|
+
const completedStatuses = new Set(['done', 'in-pr', 'implemented']); // in-pr kept for backward compat
|
|
986
981
|
const completedItems = (plan.missing_features || [])
|
|
987
982
|
.filter(f => completedStatuses.has(f.status))
|
|
988
983
|
.map(f => ({ id: f.id, name: f.name, status: f.status }));
|
|
@@ -1001,7 +996,8 @@ function materializePlansAsWorkItems(config) {
|
|
|
1001
996
|
const allProjects = getProjects(config);
|
|
1002
997
|
const targetProject = allProjects.find(p => p.name?.toLowerCase() === projectName.toLowerCase()) || allProjects[0];
|
|
1003
998
|
if (targetProject) {
|
|
1004
|
-
const
|
|
999
|
+
const centralWiPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
1000
|
+
const centralItems = safeJson(centralWiPath) || [];
|
|
1005
1001
|
const alreadyQueued = centralItems.some(w =>
|
|
1006
1002
|
w.type === 'plan-to-prd' && w.planFile === plan.source_plan && (w.status === 'pending' || w.status === 'dispatched')
|
|
1007
1003
|
);
|
|
@@ -1061,7 +1057,7 @@ function materializePlansAsWorkItems(config) {
|
|
|
1061
1057
|
const useCentral = !defaultProject;
|
|
1062
1058
|
|
|
1063
1059
|
const statusFilter = ['missing', 'planned'];
|
|
1064
|
-
// Also materialize done items that never got a work item (race with PR status sync)
|
|
1060
|
+
// Also materialize in-pr/done items that never got a work item (race with PR status sync)
|
|
1065
1061
|
const allExistingWiIds = new Set();
|
|
1066
1062
|
for (const p of allProjects) {
|
|
1067
1063
|
for (const w of (safeJson(projectWorkItemsPath(p)) || [])) {
|
|
@@ -1069,12 +1065,12 @@ function materializePlansAsWorkItems(config) {
|
|
|
1069
1065
|
}
|
|
1070
1066
|
}
|
|
1071
1067
|
// Also check central work-items.json
|
|
1072
|
-
for (const w of (safeJson(
|
|
1068
|
+
for (const w of (safeJson(path.join(MINIONS_DIR, 'work-items.json')) || [])) {
|
|
1073
1069
|
if (w.id) allExistingWiIds.add(w.id);
|
|
1074
1070
|
}
|
|
1075
1071
|
const items = plan.missing_features.filter(f =>
|
|
1076
1072
|
statusFilter.includes(f.status) ||
|
|
1077
|
-
(f.status === 'done' && f.id && !allExistingWiIds.has(f.id))
|
|
1073
|
+
((f.status === 'in-pr' || f.status === 'done') && f.id && !allExistingWiIds.has(f.id))
|
|
1078
1074
|
);
|
|
1079
1075
|
|
|
1080
1076
|
// Group items by target project (per-item project field overrides plan-level project)
|
|
@@ -1114,7 +1110,7 @@ function materializePlansAsWorkItems(config) {
|
|
|
1114
1110
|
|
|
1115
1111
|
let totalCreated = 0;
|
|
1116
1112
|
for (const [projName, { project, items: projItems }] of itemsByProject) {
|
|
1117
|
-
const wiPath = project ? projectWorkItemsPath(project) :
|
|
1113
|
+
const wiPath = project ? projectWorkItemsPath(project) : path.join(MINIONS_DIR, 'work-items.json');
|
|
1118
1114
|
const existingItems = safeJson(wiPath) || [];
|
|
1119
1115
|
let created = 0;
|
|
1120
1116
|
const newlyCreatedIds = new Set(); // tracks IDs created in this pass for reconciliation scoping
|
|
@@ -1135,7 +1131,7 @@ function materializePlansAsWorkItems(config) {
|
|
|
1135
1131
|
|
|
1136
1132
|
const id = item.id; // Work item ID = PRD item ID — no indirection
|
|
1137
1133
|
const complexity = item.estimated_complexity || 'medium';
|
|
1138
|
-
const criteria = (
|
|
1134
|
+
const criteria = (item.acceptance_criteria || []).map(c => `- ${c}`).join('\n');
|
|
1139
1135
|
|
|
1140
1136
|
const newItem = {
|
|
1141
1137
|
id,
|
|
@@ -1194,17 +1190,9 @@ function materializePlansAsWorkItems(config) {
|
|
|
1194
1190
|
const root = path.resolve(firstProject.localPath);
|
|
1195
1191
|
const mainBranch = firstProject.mainBranch || 'main';
|
|
1196
1192
|
const branch = sanitizeBranch(plan.feature_branch);
|
|
1197
|
-
// Create branch from main —
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
} catch (e) {
|
|
1201
|
-
// Branch may already exist — that's fine
|
|
1202
|
-
if (!e.message?.includes('already exists')) throw e;
|
|
1203
|
-
}
|
|
1204
|
-
// Push to remote (best-effort — may not have a remote)
|
|
1205
|
-
try {
|
|
1206
|
-
exec(`git push -u origin "${branch}"`, { cwd: root, stdio: 'pipe', windowsHide: true, timeout: 15000 });
|
|
1207
|
-
} catch { /* no remote or push failed — branch still exists locally */ }
|
|
1193
|
+
// Create branch from main (idempotent — ignores if exists)
|
|
1194
|
+
exec(`git branch "${branch}" "${mainBranch}" 2>/dev/null || true`, { cwd: root, stdio: 'pipe' });
|
|
1195
|
+
exec(`git push -u origin "${branch}" 2>/dev/null || true`, { cwd: root, stdio: 'pipe' });
|
|
1208
1196
|
log('info', `Shared branch pre-created: ${branch} for plan ${file}`);
|
|
1209
1197
|
} catch (err) {
|
|
1210
1198
|
log('warn', `Failed to pre-create shared branch for ${file}: ${err.message}`);
|
|
@@ -1258,9 +1246,9 @@ function discoverFromPrs(config, project) {
|
|
|
1258
1246
|
// minionsReview tracks metadata (reviewer, note) but not the authoritative status
|
|
1259
1247
|
const reviewStatus = pr.reviewStatus || 'pending';
|
|
1260
1248
|
|
|
1261
|
-
// PRs needing review:
|
|
1262
|
-
const
|
|
1263
|
-
const needsReview =
|
|
1249
|
+
// PRs needing review: pending or waiting (review dispatched but no verdict yet)
|
|
1250
|
+
const autoReview = config.engine?.autoReview !== false;
|
|
1251
|
+
const needsReview = autoReview && reviewStatus === 'pending';
|
|
1264
1252
|
if (needsReview) {
|
|
1265
1253
|
const key = `review-${project?.name || 'default'}-${pr.id}`;
|
|
1266
1254
|
if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
|
|
@@ -1268,8 +1256,8 @@ function discoverFromPrs(config, project) {
|
|
|
1268
1256
|
if (!agentId) continue;
|
|
1269
1257
|
|
|
1270
1258
|
const item = buildPrDispatch(agentId, config, project, pr, 'review', {
|
|
1271
|
-
pr_id: pr.id, pr_number: prNumber, pr_title: pr.title
|
|
1272
|
-
|
|
1259
|
+
pr_id: pr.id, pr_number: prNumber, pr_title: pr.title || '', pr_branch: pr.branch || '',
|
|
1260
|
+
pr_author: pr.agent || '', pr_url: pr.url || '',
|
|
1273
1261
|
}, `Review PR ${pr.id}: ${pr.title}`, { dispatchKey: key, source: 'pr', pr, branch: pr.branch, project: projMeta });
|
|
1274
1262
|
if (item) { newWork.push(item); setCooldown(key); }
|
|
1275
1263
|
}
|
|
@@ -1385,7 +1373,6 @@ function discoverFromWorkItems(config, project) {
|
|
|
1385
1373
|
}
|
|
1386
1374
|
}
|
|
1387
1375
|
|
|
1388
|
-
if (item.status === 'needs-human-review') continue; // Explicit skip — flagged for human attention
|
|
1389
1376
|
if (item.status !== 'queued' && item.status !== 'pending') continue;
|
|
1390
1377
|
|
|
1391
1378
|
// Dependency gate: skip items whose depends_on are not yet met; propagate failure
|
|
@@ -1484,47 +1471,9 @@ function discoverFromWorkItems(config, project) {
|
|
|
1484
1471
|
'- [' + (r.title || r.url) + '](' + r.url + ')' + (r.type ? ' (' + r.type + ')' : '')
|
|
1485
1472
|
).join('\n');
|
|
1486
1473
|
vars.references = refs ? '## References\n\n' + refs : '';
|
|
1487
|
-
const ac = (
|
|
1474
|
+
const ac = (item.acceptanceCriteria || []).map(c => '- [ ] ' + c).join('\n');
|
|
1488
1475
|
vars.acceptance_criteria = ac ? '## Acceptance Criteria\n\n' + ac : '';
|
|
1489
1476
|
|
|
1490
|
-
// Inject PR section — conditional based on skipPr flag
|
|
1491
|
-
vars.pr_section = item.skipPr
|
|
1492
|
-
? '## Push Branch\n\n**PR creation is skipped for this work item.** Push your branch and report the branch name.\n\n```bash\ngit push -u origin {{branch_name}}\n```\n\nInclude the branch name in your completion summary.'
|
|
1493
|
-
: '## Create PR (MANDATORY)\n\n**Your task is NOT complete until a pull request exists.** If PR creation fails, retry up to 3 times before reporting the error.\n\n{{pr_create_instructions}}\n- sourceRefName: `refs/heads/{{branch_name}}`\n- targetRefName: `refs/heads/{{main_branch}}`\n- title: `{{commit_message}}`\n- labels: `["minions:{{agent_id}}"]`\n\nInclude in the PR description:\n- What was built and why\n- Files changed\n- How to build and test, browser URL if applicable\n- Test plan\n\n## Post self-review on PR\n\n{{pr_comment_instructions}}\n- pullRequestId: `<from PR creation>`\n- Re-read your own diff critically before posting\n- Sign: `Built by Minions ({{agent_name}} — {{agent_role}})`';
|
|
1494
|
-
|
|
1495
|
-
// Inject checkpoint context if agent left a checkpoint.json from a prior run
|
|
1496
|
-
vars.checkpoint_context = '';
|
|
1497
|
-
try {
|
|
1498
|
-
const wtPath = vars.worktree_path || root;
|
|
1499
|
-
const cpPath = path.join(wtPath, 'checkpoint.json');
|
|
1500
|
-
if (fs.existsSync(cpPath)) {
|
|
1501
|
-
const cpData = JSON.parse(fs.readFileSync(cpPath, 'utf8'));
|
|
1502
|
-
const cpCount = (item._checkpointCount || 0) + 1;
|
|
1503
|
-
if (cpCount > 3) {
|
|
1504
|
-
log('warn', `Work item ${item.id} exceeded 3 checkpoint-resumes — marking as needs-human-review`);
|
|
1505
|
-
item.status = 'needs-human-review';
|
|
1506
|
-
item._checkpointCount = cpCount;
|
|
1507
|
-
needsWrite = true;
|
|
1508
|
-
continue;
|
|
1509
|
-
}
|
|
1510
|
-
item._checkpointCount = cpCount;
|
|
1511
|
-
needsWrite = true;
|
|
1512
|
-
const cpSummary = [
|
|
1513
|
-
`## Checkpoint (Resume #${cpCount}/3)`,
|
|
1514
|
-
'',
|
|
1515
|
-
'A previous agent run timed out but left a checkpoint. Continue from where it left off.',
|
|
1516
|
-
'',
|
|
1517
|
-
Array.isArray(cpData.completed) && cpData.completed.length > 0 ? `### Completed\n${cpData.completed.map(s => '- ' + s).join('\n')}` : '',
|
|
1518
|
-
Array.isArray(cpData.remaining) && cpData.remaining.length > 0 ? `### Remaining\n${cpData.remaining.map(s => '- ' + s).join('\n')}` : '',
|
|
1519
|
-
Array.isArray(cpData.blockers) && cpData.blockers.length > 0 ? `### Blockers\n${cpData.blockers.map(s => '- ' + s).join('\n')}` : '',
|
|
1520
|
-
cpData.branch_state ? `### Branch State\n${cpData.branch_state}` : '',
|
|
1521
|
-
].filter(Boolean).join('\n');
|
|
1522
|
-
vars.checkpoint_context = cpSummary;
|
|
1523
|
-
log('info', `Injecting checkpoint context for ${item.id} (resume #${cpCount})`);
|
|
1524
|
-
try { fs.unlinkSync(cpPath); } catch (ue) { log('warn', `checkpoint cleanup for ${item.id}: ${ue.message}`); }
|
|
1525
|
-
}
|
|
1526
|
-
} catch (e) { log('warn', `checkpoint read for ${item.id}: ${e.message}`); }
|
|
1527
|
-
|
|
1528
1477
|
// Inject ask-specific variables for the ask playbook
|
|
1529
1478
|
if (workType === 'ask') {
|
|
1530
1479
|
vars.question = item.title + (item.description ? '\n\n' + item.description : '');
|
|
@@ -1544,17 +1493,9 @@ function discoverFromWorkItems(config, project) {
|
|
|
1544
1493
|
if (playbookName === 'work-item' && workType === 'review') {
|
|
1545
1494
|
log('info', `Work item ${item.id} is type "review" but has no PR — using work-item playbook`);
|
|
1546
1495
|
}
|
|
1547
|
-
const
|
|
1548
|
-
const renderError = getLastRenderError();
|
|
1549
|
-
// If critical vars are missing, block dispatch entirely — don't fall through to work-item playbook
|
|
1550
|
-
const prompt = item.prompt || rendered || (renderError ? null : (renderPlaybook('work-item', vars) || item.description));
|
|
1496
|
+
const prompt = item.prompt || renderPlaybook(playbookName, vars) || renderPlaybook('work-item', vars) || item.description;
|
|
1551
1497
|
if (!prompt) {
|
|
1552
|
-
|
|
1553
|
-
log('warn', `Skipping ${item.id}: ${renderError.message}`);
|
|
1554
|
-
if (item._pendingReason !== 'critical_vars_missing') { item._pendingReason = 'critical_vars_missing'; needsWrite = true; }
|
|
1555
|
-
} else {
|
|
1556
|
-
log('warn', `No playbook rendered for ${item.id} (type: ${workType}, playbook: ${playbookName}) — skipping`);
|
|
1557
|
-
}
|
|
1498
|
+
log('warn', `No playbook rendered for ${item.id} (type: ${workType}, playbook: ${playbookName}) — skipping`);
|
|
1558
1499
|
continue;
|
|
1559
1500
|
}
|
|
1560
1501
|
|
|
@@ -1578,13 +1519,15 @@ function discoverFromWorkItems(config, project) {
|
|
|
1578
1519
|
setCooldown(key);
|
|
1579
1520
|
}
|
|
1580
1521
|
|
|
1581
|
-
// Write back updated statuses
|
|
1582
|
-
if (
|
|
1522
|
+
// Write back updated statuses (always, since we mark items dispatched before newWork check)
|
|
1523
|
+
if (newWork.length > 0) {
|
|
1583
1524
|
const workItemsPath = projectWorkItemsPath(project);
|
|
1584
1525
|
safeWrite(workItemsPath, items);
|
|
1585
1526
|
for (const s of prdSyncQueue) syncPrdItemStatus(s.id, 'dispatched', s.sourcePlan);
|
|
1586
1527
|
}
|
|
1587
1528
|
|
|
1529
|
+
if (needsWrite) safeWrite(projectWorkItemsPath(project), items);
|
|
1530
|
+
|
|
1588
1531
|
const skipTotal = skipped.gated + skipped.noAgent;
|
|
1589
1532
|
if (skipTotal > 0) {
|
|
1590
1533
|
log('debug', `Work item discovery (${project?.name}): skipped ${skipTotal} items (${skipped.gated} gated, ${skipped.noAgent} no agent)`);
|
|
@@ -1768,14 +1711,12 @@ function extractSpecInfo(filePath, projectRoot_) {
|
|
|
1768
1711
|
* Uses the shared work-item.md playbook with multi-project context injected.
|
|
1769
1712
|
*/
|
|
1770
1713
|
function discoverCentralWorkItems(config) {
|
|
1771
|
-
const centralPath =
|
|
1714
|
+
const centralPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
1772
1715
|
const items = safeJson(centralPath) || [];
|
|
1773
1716
|
const projects = getProjects(config);
|
|
1774
1717
|
const newWork = [];
|
|
1775
|
-
let needsWrite = false;
|
|
1776
1718
|
|
|
1777
1719
|
for (const item of items) {
|
|
1778
|
-
if (item.status === 'needs-human-review') continue; // Explicit skip — flagged for human attention
|
|
1779
1720
|
if (item.status !== 'queued' && item.status !== 'pending') continue;
|
|
1780
1721
|
|
|
1781
1722
|
const key = `central-work-${item.id}`;
|
|
@@ -1803,42 +1744,6 @@ function discoverCentralWorkItems(config) {
|
|
|
1803
1744
|
assignedProject: projects.length > 0 ? projects[i % projects.length] : null
|
|
1804
1745
|
}));
|
|
1805
1746
|
|
|
1806
|
-
// Inject checkpoint context if agent left a checkpoint.json from a prior run
|
|
1807
|
-
let fanOutCheckpointContext = '';
|
|
1808
|
-
try {
|
|
1809
|
-
const fanFirstProject = projects[0];
|
|
1810
|
-
const fanBranch = item.branch || `work/${item.id}`;
|
|
1811
|
-
const fanWtPath = fanFirstProject?.localPath
|
|
1812
|
-
? path.resolve(fanFirstProject.localPath, config.engine?.worktreeRoot || '../worktrees', fanBranch)
|
|
1813
|
-
: '';
|
|
1814
|
-
const fanCpPath = fanWtPath ? path.join(fanWtPath, 'checkpoint.json') : '';
|
|
1815
|
-
if (fanCpPath && fs.existsSync(fanCpPath)) {
|
|
1816
|
-
const fanCpData = JSON.parse(fs.readFileSync(fanCpPath, 'utf8'));
|
|
1817
|
-
const fanCpCount = (item._checkpointCount || 0) + 1;
|
|
1818
|
-
if (fanCpCount > 3) {
|
|
1819
|
-
log('warn', `Work item ${item.id} exceeded 3 checkpoint-resumes — marking as needs-human-review`);
|
|
1820
|
-
item.status = 'needs-human-review';
|
|
1821
|
-
item._checkpointCount = fanCpCount;
|
|
1822
|
-
needsWrite = true;
|
|
1823
|
-
continue;
|
|
1824
|
-
}
|
|
1825
|
-
item._checkpointCount = fanCpCount;
|
|
1826
|
-
const fanCpSummary = [
|
|
1827
|
-
`## Checkpoint (Resume #${fanCpCount}/3)`,
|
|
1828
|
-
'',
|
|
1829
|
-
'A previous agent run timed out but left a checkpoint. Continue from where it left off.',
|
|
1830
|
-
'',
|
|
1831
|
-
Array.isArray(fanCpData.completed) && fanCpData.completed.length > 0 ? `### Completed\n${fanCpData.completed.map(s => '- ' + s).join('\n')}` : '',
|
|
1832
|
-
Array.isArray(fanCpData.remaining) && fanCpData.remaining.length > 0 ? `### Remaining\n${fanCpData.remaining.map(s => '- ' + s).join('\n')}` : '',
|
|
1833
|
-
Array.isArray(fanCpData.blockers) && fanCpData.blockers.length > 0 ? `### Blockers\n${fanCpData.blockers.map(s => '- ' + s).join('\n')}` : '',
|
|
1834
|
-
fanCpData.branch_state ? `### Branch State\n${fanCpData.branch_state}` : '',
|
|
1835
|
-
].filter(Boolean).join('\n');
|
|
1836
|
-
fanOutCheckpointContext = fanCpSummary;
|
|
1837
|
-
log('info', `Injecting checkpoint context for ${item.id} (resume #${fanCpCount})`);
|
|
1838
|
-
try { fs.unlinkSync(fanCpPath); } catch (ue) { log('warn', `checkpoint cleanup for ${item.id}: ${ue.message}`); }
|
|
1839
|
-
}
|
|
1840
|
-
} catch (e) { log('warn', `checkpoint read for ${item.id}: ${e.message}`); }
|
|
1841
|
-
|
|
1842
1747
|
for (const { agent, assignedProject } of assignments) {
|
|
1843
1748
|
const fanKey = `${key}-${agent.id}`;
|
|
1844
1749
|
if (isAlreadyDispatched(fanKey)) continue;
|
|
@@ -1861,19 +1766,9 @@ function discoverCentralWorkItems(config) {
|
|
|
1861
1766
|
'- [' + (r.title || r.url) + '](' + r.url + ')' + (r.type ? ' (' + r.type + ')' : '')
|
|
1862
1767
|
).join('\n');
|
|
1863
1768
|
vars.references = fanRefs ? '## References\n\n' + fanRefs : '';
|
|
1864
|
-
const fanAc = (
|
|
1769
|
+
const fanAc = (item.acceptanceCriteria || []).map(c => '- [ ] ' + c).join('\n');
|
|
1865
1770
|
vars.acceptance_criteria = fanAc ? '## Acceptance Criteria\n\n' + fanAc : '';
|
|
1866
1771
|
|
|
1867
|
-
// Inject PR section — conditional based on skipPr flag
|
|
1868
|
-
const fanBranch = '{{branch_name}}';
|
|
1869
|
-
vars.pr_section = item.skipPr
|
|
1870
|
-
? '## Push Branch\n\n**PR creation is skipped for this work item.** Push your branch and report the branch name.\n\n```bash\ngit push -u origin ' + fanBranch + '\n```\n\nInclude the branch name in your completion summary.'
|
|
1871
|
-
: '## Create PR (MANDATORY)\n\n**Your task is NOT complete until a pull request exists.** If PR creation fails, retry up to 3 times before reporting the error.\n\n{{pr_create_instructions}}\n- sourceRefName: `refs/heads/' + fanBranch + '`\n- targetRefName: `refs/heads/{{main_branch}}`\n- title: `{{commit_message}}`\n- labels: `["minions:{{agent_id}}"]`\n\nInclude in the PR description:\n- What was built and why\n- Files changed\n- How to build and test, browser URL if applicable\n- Test plan\n\n## Post self-review on PR\n\n{{pr_comment_instructions}}\n- pullRequestId: `<from PR creation>`\n- Re-read your own diff critically before posting\n- Sign: `Built by Minions ({{agent_name}} — {{agent_role}})`';
|
|
1872
|
-
|
|
1873
|
-
// Inject checkpoint context (computed once above the loop)
|
|
1874
|
-
vars.checkpoint_context = fanOutCheckpointContext;
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
1772
|
if (workType === 'ask') {
|
|
1878
1773
|
vars.question = item.title + (item.description ? '\n\n' + item.description : '');
|
|
1879
1774
|
vars.task_id = item.id;
|
|
@@ -1887,16 +1782,9 @@ function discoverCentralWorkItems(config) {
|
|
|
1887
1782
|
}
|
|
1888
1783
|
|
|
1889
1784
|
const playbookName = selectPlaybook(workType, item);
|
|
1890
|
-
const
|
|
1891
|
-
const renderError = getLastRenderError();
|
|
1892
|
-
const prompt = rendered || (renderError ? null : renderPlaybook('work-item', vars));
|
|
1785
|
+
const prompt = renderPlaybook(playbookName, vars) || renderPlaybook('work-item', vars);
|
|
1893
1786
|
if (!prompt) {
|
|
1894
|
-
|
|
1895
|
-
log('warn', `Fan-out: ${item.id} → ${agent.id}: ${renderError.message}`);
|
|
1896
|
-
if (item._pendingReason !== 'critical_vars_missing') { item._pendingReason = 'critical_vars_missing'; needsWrite = true; }
|
|
1897
|
-
} else {
|
|
1898
|
-
log('warn', `Fan-out: playbook '${playbookName}' failed to render for ${item.id} → ${agent.id}, skipping`);
|
|
1899
|
-
}
|
|
1787
|
+
log('warn', `Fan-out: playbook '${playbookName}' failed to render for ${item.id} → ${agent.id}, skipping`);
|
|
1900
1788
|
continue;
|
|
1901
1789
|
}
|
|
1902
1790
|
|
|
@@ -1919,8 +1807,6 @@ function discoverCentralWorkItems(config) {
|
|
|
1919
1807
|
item.dispatched_to = idleAgents.map(a => a.id).join(', ');
|
|
1920
1808
|
item.scope = 'fan-out';
|
|
1921
1809
|
item.fanOutAgents = idleAgents.map(a => a.id);
|
|
1922
|
-
delete item._pendingReason;
|
|
1923
|
-
needsWrite = true;
|
|
1924
1810
|
setCooldown(key);
|
|
1925
1811
|
log('info', `Fan-out: ${item.id} dispatched to ${idleAgents.length} agents: ${idleAgents.map(a => a.name).join(', ')}`);
|
|
1926
1812
|
|
|
@@ -1955,43 +1841,9 @@ function discoverCentralWorkItems(config) {
|
|
|
1955
1841
|
'- [' + (r.title || r.url) + '](' + r.url + ')' + (r.type ? ' (' + r.type + ')' : '')
|
|
1956
1842
|
).join('\n');
|
|
1957
1843
|
vars.references = normRefs ? '## References\n\n' + normRefs : '';
|
|
1958
|
-
const normAc = (
|
|
1844
|
+
const normAc = (item.acceptanceCriteria || []).map(c => '- [ ] ' + c).join('\n');
|
|
1959
1845
|
vars.acceptance_criteria = normAc ? '## Acceptance Criteria\n\n' + normAc : '';
|
|
1960
1846
|
|
|
1961
|
-
// Inject checkpoint context if agent left a checkpoint.json from a prior run
|
|
1962
|
-
vars.checkpoint_context = '';
|
|
1963
|
-
try {
|
|
1964
|
-
const centralBranch = item.branch || `work/${item.id}`;
|
|
1965
|
-
const centralWtPath = firstProject?.localPath
|
|
1966
|
-
? path.resolve(firstProject.localPath, config.engine?.worktreeRoot || '../worktrees', centralBranch)
|
|
1967
|
-
: '';
|
|
1968
|
-
const cpPath = centralWtPath ? path.join(centralWtPath, 'checkpoint.json') : '';
|
|
1969
|
-
if (cpPath && fs.existsSync(cpPath)) {
|
|
1970
|
-
const cpData = JSON.parse(fs.readFileSync(cpPath, 'utf8'));
|
|
1971
|
-
const cpCount = (item._checkpointCount || 0) + 1;
|
|
1972
|
-
if (cpCount > 3) {
|
|
1973
|
-
log('warn', `Work item ${item.id} exceeded 3 checkpoint-resumes — marking as needs-human-review`);
|
|
1974
|
-
item.status = 'needs-human-review';
|
|
1975
|
-
item._checkpointCount = cpCount;
|
|
1976
|
-
continue;
|
|
1977
|
-
}
|
|
1978
|
-
item._checkpointCount = cpCount;
|
|
1979
|
-
const cpSummary = [
|
|
1980
|
-
`## Checkpoint (Resume #${cpCount}/3)`,
|
|
1981
|
-
'',
|
|
1982
|
-
'A previous agent run timed out but left a checkpoint. Continue from where it left off.',
|
|
1983
|
-
'',
|
|
1984
|
-
Array.isArray(cpData.completed) && cpData.completed.length > 0 ? `### Completed\n${cpData.completed.map(s => '- ' + s).join('\n')}` : '',
|
|
1985
|
-
Array.isArray(cpData.remaining) && cpData.remaining.length > 0 ? `### Remaining\n${cpData.remaining.map(s => '- ' + s).join('\n')}` : '',
|
|
1986
|
-
Array.isArray(cpData.blockers) && cpData.blockers.length > 0 ? `### Blockers\n${cpData.blockers.map(s => '- ' + s).join('\n')}` : '',
|
|
1987
|
-
cpData.branch_state ? `### Branch State\n${cpData.branch_state}` : '',
|
|
1988
|
-
].filter(Boolean).join('\n');
|
|
1989
|
-
vars.checkpoint_context = cpSummary;
|
|
1990
|
-
log('info', `Injecting checkpoint context for ${item.id} (resume #${cpCount})`);
|
|
1991
|
-
try { fs.unlinkSync(cpPath); } catch (ue) { log('warn', `checkpoint cleanup for ${item.id}: ${ue.message}`); }
|
|
1992
|
-
}
|
|
1993
|
-
} catch (e) { log('warn', `checkpoint read for ${item.id}: ${e.message}`); }
|
|
1994
|
-
|
|
1995
1847
|
// Inject plan-specific variables for the plan playbook
|
|
1996
1848
|
if (workType === 'plan') {
|
|
1997
1849
|
// Ensure plans directory exists before agent tries to write
|
|
@@ -2042,19 +1894,10 @@ function discoverCentralWorkItems(config) {
|
|
|
2042
1894
|
}
|
|
2043
1895
|
|
|
2044
1896
|
const playbookName = selectPlaybook(workType, item);
|
|
2045
|
-
const
|
|
2046
|
-
const renderError = getLastRenderError();
|
|
2047
|
-
const prompt = rendered || (renderError ? null : renderPlaybook('work-item', vars));
|
|
1897
|
+
const prompt = renderPlaybook(playbookName, vars) || renderPlaybook('work-item', vars);
|
|
2048
1898
|
if (!prompt) {
|
|
2049
|
-
|
|
2050
|
-
log('warn', `Dispatch: ${item.id}: ${renderError.message}`);
|
|
2051
|
-
item._pendingReason = 'critical_vars_missing';
|
|
2052
|
-
needsWrite = true;
|
|
2053
|
-
} else {
|
|
2054
|
-
log('warn', `Dispatch: playbook '${playbookName}' failed to render for ${item.id}, resetting to pending`);
|
|
2055
|
-
}
|
|
1899
|
+
log('warn', `Dispatch: playbook '${playbookName}' failed to render for ${item.id}, resetting to pending`);
|
|
2056
1900
|
item.status = 'pending';
|
|
2057
|
-
needsWrite = true;
|
|
2058
1901
|
continue;
|
|
2059
1902
|
}
|
|
2060
1903
|
|
|
@@ -2071,13 +1914,11 @@ function discoverCentralWorkItems(config) {
|
|
|
2071
1914
|
item.status = 'dispatched';
|
|
2072
1915
|
item.dispatched_at = ts();
|
|
2073
1916
|
item.dispatched_to = agentId;
|
|
2074
|
-
delete item._pendingReason;
|
|
2075
|
-
needsWrite = true;
|
|
2076
1917
|
setCooldown(key);
|
|
2077
1918
|
}
|
|
2078
1919
|
}
|
|
2079
1920
|
|
|
2080
|
-
if (
|
|
1921
|
+
if (newWork.length > 0) safeWrite(centralPath, items);
|
|
2081
1922
|
return newWork;
|
|
2082
1923
|
}
|
|
2083
1924
|
|
|
@@ -2123,14 +1964,23 @@ function discoverWork(config) {
|
|
|
2123
1964
|
const { discoverScheduledWork } = require('./engine/scheduler');
|
|
2124
1965
|
const scheduledWork = discoverScheduledWork(config);
|
|
2125
1966
|
if (scheduledWork.length > 0) {
|
|
2126
|
-
const
|
|
1967
|
+
const { createMeeting, getMeetings } = require('./engine/meeting');
|
|
1968
|
+
const centralPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
2127
1969
|
const items = safeJson(centralPath) || [];
|
|
2128
1970
|
let added = 0;
|
|
2129
1971
|
for (const item of scheduledWork) {
|
|
2130
|
-
if (
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
1972
|
+
if (item.type === 'meeting') {
|
|
1973
|
+
// Create a real multi-agent meeting instead of a single-agent work item
|
|
1974
|
+
const sched = (config.schedules || []).find(s => s.id === item._scheduleId);
|
|
1975
|
+
const participants = (sched && sched.participants) || [];
|
|
1976
|
+
const meeting = createMeeting({ title: item.title, agenda: item.description, participants });
|
|
1977
|
+
log('info', `Scheduled meeting created: ${item._scheduleId} → ${meeting.id} (${participants.length} participants)`);
|
|
1978
|
+
} else {
|
|
1979
|
+
if (!items.some(i => i._scheduleId === item._scheduleId && i.status !== 'done' && i.status !== 'failed')) {
|
|
1980
|
+
items.push(item);
|
|
1981
|
+
added++;
|
|
1982
|
+
log('info', `Scheduled task fired: ${item._scheduleId} → ${item.title}`);
|
|
1983
|
+
}
|
|
2134
1984
|
}
|
|
2135
1985
|
}
|
|
2136
1986
|
if (added > 0) safeWrite(centralPath, items);
|
|
@@ -2197,7 +2047,7 @@ function discoverWork(config) {
|
|
|
2197
2047
|
for (const item of allWork) {
|
|
2198
2048
|
addToDispatch(item);
|
|
2199
2049
|
if (item.meta?.source === 'pr-human-feedback') {
|
|
2200
|
-
clearPendingHumanFeedbackFlag(item.meta
|
|
2050
|
+
clearPendingHumanFeedbackFlag(item.meta.project, item.meta.pr?.id);
|
|
2201
2051
|
}
|
|
2202
2052
|
}
|
|
2203
2053
|
|
|
@@ -2221,15 +2071,12 @@ let tickRunning = false;
|
|
|
2221
2071
|
async function tick() {
|
|
2222
2072
|
if (tickRunning) return; // prevent overlapping ticks
|
|
2223
2073
|
tickRunning = true;
|
|
2224
|
-
const tickStart = Date.now();
|
|
2225
2074
|
try {
|
|
2226
2075
|
await tickInner();
|
|
2227
2076
|
} catch (e) {
|
|
2228
2077
|
log('error', `Tick error: ${e.message}`);
|
|
2229
2078
|
} finally {
|
|
2230
2079
|
tickRunning = false;
|
|
2231
|
-
const elapsed = Date.now() - tickStart;
|
|
2232
|
-
if (elapsed > 30000) log('warn', `Slow tick: ${(elapsed / 1000).toFixed(1)}s`);
|
|
2233
2080
|
}
|
|
2234
2081
|
}
|
|
2235
2082
|
|
|
@@ -2247,9 +2094,9 @@ async function tickInner() {
|
|
|
2247
2094
|
tickCount++;
|
|
2248
2095
|
|
|
2249
2096
|
// 1. Check for timed-out agents, steering messages, and idle threshold
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2097
|
+
checkTimeouts(config);
|
|
2098
|
+
checkSteering(config);
|
|
2099
|
+
checkIdleThreshold(config);
|
|
2253
2100
|
|
|
2254
2101
|
// 1b. Check for meeting round timeouts
|
|
2255
2102
|
try {
|
|
@@ -2264,11 +2111,11 @@ async function tickInner() {
|
|
|
2264
2111
|
}
|
|
2265
2112
|
|
|
2266
2113
|
// 2. Consolidate inbox
|
|
2267
|
-
|
|
2114
|
+
consolidateInbox(config);
|
|
2268
2115
|
|
|
2269
2116
|
// 2.5. Periodic cleanup + MCP sync (every 10 ticks = ~5 minutes)
|
|
2270
2117
|
if (tickCount % 10 === 0) {
|
|
2271
|
-
|
|
2118
|
+
runCleanup(config);
|
|
2272
2119
|
}
|
|
2273
2120
|
|
|
2274
2121
|
// 2.6. Poll PR status: build, review, merge (every 6 ticks = ~3 minutes)
|
|
@@ -2278,7 +2125,7 @@ async function tickInner() {
|
|
|
2278
2125
|
try { await ghPollPrStatus(config); } catch (err) { log('warn', `GitHub PR status poll error: ${err?.message || err}${err?.stack ? ' | ' + err.stack.split('\n')[1]?.trim() : ''}`); }
|
|
2279
2126
|
// Sync PR status back to PRD items (missing → done when active PR exists)
|
|
2280
2127
|
try { syncPrdFromPrs(config); } catch (err) { log('warn', `PRD sync error: ${err?.message || err}`); }
|
|
2281
|
-
// Check if any plans can be marked completed (all features done)
|
|
2128
|
+
// Check if any plans can be marked completed (all features done/in-pr)
|
|
2282
2129
|
try {
|
|
2283
2130
|
const prdFiles = safeReadDir(PRD_DIR).filter(f => f.endsWith('.json'));
|
|
2284
2131
|
for (const file of prdFiles) {
|
|
@@ -2399,15 +2246,14 @@ async function tickInner() {
|
|
|
2399
2246
|
}
|
|
2400
2247
|
|
|
2401
2248
|
// 3. Discover new work from sources
|
|
2402
|
-
|
|
2249
|
+
discoverWork(config);
|
|
2403
2250
|
|
|
2404
2251
|
// 4. Update snapshot
|
|
2405
|
-
|
|
2252
|
+
updateSnapshot(config);
|
|
2406
2253
|
|
|
2407
2254
|
// 5. Process pending dispatches — auto-spawn agents
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
activeCount = (dispatch.active || []).length;
|
|
2255
|
+
const dispatch = getDispatch();
|
|
2256
|
+
const activeCount = (dispatch.active || []).length;
|
|
2411
2257
|
const maxConcurrent = config.engine?.maxConcurrent || 5;
|
|
2412
2258
|
|
|
2413
2259
|
if (activeCount >= maxConcurrent) {
|
|
@@ -2458,7 +2304,9 @@ async function tickInner() {
|
|
|
2458
2304
|
// Defensive: ensure the work item is re-queued if completeDispatch didn't fire
|
|
2459
2305
|
if (item.meta?.item?.id) {
|
|
2460
2306
|
try {
|
|
2461
|
-
const wiPath =
|
|
2307
|
+
const wiPath = item.meta.source === 'central-work-item' || item.meta.source === 'central-work-item-fanout'
|
|
2308
|
+
? path.join(ENGINE_DIR, '..', 'work-items.json')
|
|
2309
|
+
: item.meta.project?.name ? projectWorkItemsPath({ name: item.meta.project.name, localPath: item.meta.project.localPath }) : null;
|
|
2462
2310
|
if (wiPath) {
|
|
2463
2311
|
const items = safeJson(wiPath) || [];
|
|
2464
2312
|
const wi = items.find(i => i.id === item.meta.item.id);
|
|
@@ -2539,7 +2387,6 @@ module.exports = {
|
|
|
2539
2387
|
|
|
2540
2388
|
// Playbooks
|
|
2541
2389
|
renderPlaybook,
|
|
2542
|
-
getLastRenderError,
|
|
2543
2390
|
|
|
2544
2391
|
// Timeout / Steering / Idle (re-exported from engine/timeout.js)
|
|
2545
2392
|
checkTimeouts, checkSteering, checkIdleThreshold,
|
package/minions.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Minions Init — Link a project to the central minions
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* minions add <project-dir> Add a project interactively
|
|
7
|
+
* minions remove <project-dir> Remove a project
|
|
8
|
+
* minions list List linked projects
|
|
9
9
|
*
|
|
10
10
|
* This adds the project to ~/.minions/config.json's projects array.
|
|
11
11
|
* The minions engine and dashboard run centrally from ~/.minions/.
|
|
@@ -228,7 +228,7 @@ function listProjects() {
|
|
|
228
228
|
const projects = config.projects || [];
|
|
229
229
|
console.log(`\n Minions Projects (${projects.length})\n`);
|
|
230
230
|
if (projects.length === 0) {
|
|
231
|
-
console.log(' No projects linked. Run:
|
|
231
|
+
console.log(' No projects linked. Run: minions add <project-dir>\n');
|
|
232
232
|
rl.close();
|
|
233
233
|
return;
|
|
234
234
|
}
|
|
@@ -406,7 +406,7 @@ async function scanAndAdd({ root, depth } = {}) {
|
|
|
406
406
|
|
|
407
407
|
saveConfig(config);
|
|
408
408
|
console.log(`\n Done. ${config.projects.length} total project(s) linked.`);
|
|
409
|
-
console.log(` Run "
|
|
409
|
+
console.log(` Run "minions list" to verify.\n`);
|
|
410
410
|
rl.close();
|
|
411
411
|
}
|
|
412
412
|
|
|
@@ -456,7 +456,7 @@ async function initMinions({ skipScan = false, scanRoot, scanDepth } = {}) {
|
|
|
456
456
|
}
|
|
457
457
|
|
|
458
458
|
if (skipScan) {
|
|
459
|
-
console.log('
|
|
459
|
+
console.log(' Run "minions scan" or "minions add <dir>" to link projects.\n');
|
|
460
460
|
rl.close();
|
|
461
461
|
} else {
|
|
462
462
|
// Auto-chain into scan (scanAndAdd closes rl)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.218",
|
|
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"
|