@yemi33/minions 0.1.299 → 0.1.301
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 +7 -1
- package/engine/lifecycle.js +103 -43
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.301 (2026-04-03)
|
|
4
4
|
|
|
5
5
|
### Fixes
|
|
6
|
+
- PR dedup strict equality, work-items parse logging, undefined minionsVerdict
|
|
7
|
+
|
|
8
|
+
## 0.1.300 (2026-04-03)
|
|
9
|
+
|
|
10
|
+
### Fixes
|
|
11
|
+
- re-apply verify workflow — defer archiving until verify completes
|
|
6
12
|
- work item agent column falls back to item.agent field
|
|
7
13
|
- CC workType descriptions restored + verify for maintenance/merge tasks
|
|
8
14
|
|
package/engine/lifecycle.js
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
8
9
|
const shared = require('./shared');
|
|
9
|
-
const { safeRead, safeJson, safeWrite, execSilent, projectPrPath, getPrLinks, addPrLink,
|
|
10
|
+
const { safeRead, safeJson, safeWrite, mutateJsonFileLocked, execSilent, projectPrPath, getPrLinks, addPrLink,
|
|
10
11
|
log, ts, dateStamp } = shared;
|
|
11
12
|
const { trackEngineUsage } = require('./llm');
|
|
12
13
|
const queries = require('./queries');
|
|
@@ -21,7 +22,10 @@ function checkPlanCompletion(meta, config) {
|
|
|
21
22
|
const planPath = path.join(PRD_DIR, planFile);
|
|
22
23
|
const plan = safeJson(planPath);
|
|
23
24
|
if (!plan?.missing_features) return;
|
|
24
|
-
if (plan.status === 'completed')
|
|
25
|
+
if (plan.status === 'completed') {
|
|
26
|
+
if (plan._completionNotified) return;
|
|
27
|
+
// Crash recovery: status=completed but _completionNotified not set — fall through
|
|
28
|
+
}
|
|
25
29
|
|
|
26
30
|
const projects = shared.getProjects(config);
|
|
27
31
|
|
|
@@ -132,10 +136,20 @@ function checkPlanCompletion(meta, config) {
|
|
|
132
136
|
...uniquePrs.map(pr => `- ${pr.id}: ${pr.title || ''} ${pr.url || ''}`),
|
|
133
137
|
].filter(Boolean).join('\n');
|
|
134
138
|
|
|
135
|
-
// Write summary to notes/inbox
|
|
136
|
-
const
|
|
137
|
-
shared.
|
|
138
|
-
log('info', `PRD completion summary written to notes/inbox
|
|
139
|
+
// Write summary to notes/inbox (slug-based dedup prevents duplicates on same day)
|
|
140
|
+
const slug = `prd-completion-${planFile.replace('.json', '')}`;
|
|
141
|
+
const wrote = shared.writeToInbox('engine', slug, summary);
|
|
142
|
+
if (wrote) log('info', `PRD completion summary written to notes/inbox/`);
|
|
143
|
+
|
|
144
|
+
// Persist status and _completionNotified atomically BEFORE creating work items
|
|
145
|
+
plan._completionNotified = true;
|
|
146
|
+
mutateJsonFileLocked(planPath, (data) => {
|
|
147
|
+
data.status = 'completed';
|
|
148
|
+
data.completedAt = plan.completedAt;
|
|
149
|
+
data._completionNotified = true;
|
|
150
|
+
if (plan._timing) data._timing = plan._timing;
|
|
151
|
+
return data;
|
|
152
|
+
});
|
|
139
153
|
|
|
140
154
|
// Resolve the primary project for writing new work items (PR, verify)
|
|
141
155
|
const projectName = plan.project;
|
|
@@ -186,11 +200,12 @@ function checkPlanCompletion(meta, config) {
|
|
|
186
200
|
|
|
187
201
|
// Build per-project checkout commands: one worktree, merge all PR branches into it
|
|
188
202
|
const checkoutBlocks = Object.entries(projectPrs).map(([name, { project: p, prs, mainBranch }]) => {
|
|
189
|
-
const
|
|
203
|
+
const localPath = p.localPath.replace(/\\/g, '/');
|
|
204
|
+
const wtPath = `${localPath}/../worktrees/verify-${name}-${planSlug}-${shared.uid()}`;
|
|
190
205
|
const branches = prs.map(pr => pr.branch).filter(Boolean);
|
|
191
206
|
const lines = [
|
|
192
207
|
`# ${name} — merge ${branches.length} PR branch(es) into one worktree`,
|
|
193
|
-
`cd "${
|
|
208
|
+
`cd "${localPath}"`,
|
|
194
209
|
`git fetch origin ${branches.map(b => `"${b}"`).join(' ')} "${mainBranch}"`,
|
|
195
210
|
`git worktree add "${wtPath}" "origin/${mainBranch}" 2>/dev/null || (cd "${wtPath}" && git checkout "${mainBranch}" && git pull origin "${mainBranch}")`,
|
|
196
211
|
`cd "${wtPath}"`,
|
|
@@ -211,9 +226,10 @@ function checkPlanCompletion(meta, config) {
|
|
|
211
226
|
).join('\n');
|
|
212
227
|
|
|
213
228
|
// List projects and their worktree paths for the agent
|
|
214
|
-
const projectWorktrees = Object.entries(projectPrs).map(([name, { project: p }]) =>
|
|
215
|
-
|
|
216
|
-
|
|
229
|
+
const projectWorktrees = Object.entries(projectPrs).map(([name, { project: p }]) => {
|
|
230
|
+
const lp = p.localPath.replace(/\\/g, '/');
|
|
231
|
+
return `- **${name}**: see setup commands below (\`${lp}/../worktrees/verify-${name}-${planSlug}-*\`)`;
|
|
232
|
+
}).join('\n');
|
|
217
233
|
|
|
218
234
|
const description = [
|
|
219
235
|
`Verification task for completed plan \`${planFile}\`.`,
|
|
@@ -258,46 +274,75 @@ function checkPlanCompletion(meta, config) {
|
|
|
258
274
|
log('info', `Created verification work item ${verifyId} for plan ${planFile}`);
|
|
259
275
|
}
|
|
260
276
|
|
|
261
|
-
// 5. Archive
|
|
277
|
+
// 5. Archive deferred until verify completes (see runPostCompletionHooks).
|
|
278
|
+
// Plan stays active until verification finishes so artifacts are visible.
|
|
279
|
+
|
|
280
|
+
log('info', `PRD ${planFile} completed: ${doneItems.length} done, ${failedItems.length} failed, runtime ${runtimeMin}m`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ─── Plan Archiving (called after verify completes) ─────────────────────────
|
|
284
|
+
|
|
285
|
+
function archivePlan(planFile, plan, projects, config) {
|
|
286
|
+
const planPath = path.join(PRD_DIR, planFile);
|
|
287
|
+
const projectName = plan.project || '';
|
|
288
|
+
|
|
289
|
+
// Archive PRD .json to prd/archive/
|
|
262
290
|
const prdArchiveDir = path.join(PRD_DIR, 'archive');
|
|
263
291
|
if (!fs.existsSync(prdArchiveDir)) fs.mkdirSync(prdArchiveDir, { recursive: true });
|
|
264
|
-
|
|
265
|
-
try {
|
|
266
|
-
fs.renameSync(planPath, path.join(prdArchiveDir, planFile));
|
|
267
|
-
log('info', `Archived completed PRD: prd/archive/${planFile}`);
|
|
268
|
-
} catch (err) {
|
|
269
|
-
log('warn', `Failed to archive PRD ${planFile}: ${err.message}`);
|
|
292
|
+
if (fs.existsSync(planPath)) {
|
|
270
293
|
shared.safeWrite(planPath, plan);
|
|
294
|
+
try {
|
|
295
|
+
fs.renameSync(planPath, path.join(prdArchiveDir, planFile));
|
|
296
|
+
log('info', `Archived completed PRD: prd/archive/${planFile}`);
|
|
297
|
+
} catch (err) { log('warn', `Failed to archive PRD ${planFile}: ${err.message}`); }
|
|
271
298
|
}
|
|
272
299
|
|
|
273
|
-
//
|
|
300
|
+
// Archive the source .md plan
|
|
274
301
|
const planArchiveDir = path.join(PLANS_DIR, 'archive');
|
|
275
302
|
if (!fs.existsSync(planArchiveDir)) fs.mkdirSync(planArchiveDir, { recursive: true });
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
fs.renameSync(path.join(PLANS_DIR, md), path.join(planArchiveDir, md));
|
|
284
|
-
log('info', `Archived source plan: plans/archive/${md}`);
|
|
285
|
-
} catch (err) { log('warn', `Failed to archive plan ${md}: ${err.message}`); }
|
|
286
|
-
break;
|
|
287
|
-
}
|
|
303
|
+
if (plan.source_plan) {
|
|
304
|
+
const mdPath = path.join(PLANS_DIR, plan.source_plan);
|
|
305
|
+
if (fs.existsSync(mdPath)) {
|
|
306
|
+
try {
|
|
307
|
+
fs.renameSync(mdPath, path.join(planArchiveDir, plan.source_plan));
|
|
308
|
+
log('info', `Archived source plan: plans/archive/${plan.source_plan}`);
|
|
309
|
+
} catch (err) { log('warn', `Failed to archive source plan ${plan.source_plan}: ${err.message}`); }
|
|
288
310
|
}
|
|
289
|
-
}
|
|
311
|
+
} else {
|
|
312
|
+
try {
|
|
313
|
+
const mdFiles = fs.readdirSync(PLANS_DIR).filter(f => f.endsWith('.md'));
|
|
314
|
+
for (const md of mdFiles) {
|
|
315
|
+
const mdContent = shared.safeRead(path.join(PLANS_DIR, md)) || '';
|
|
316
|
+
if (mdContent.includes(projectName) || mdContent.includes(plan.plan_summary?.slice(0, 40) || '___nomatch___')) {
|
|
317
|
+
try {
|
|
318
|
+
fs.renameSync(path.join(PLANS_DIR, md), path.join(planArchiveDir, md));
|
|
319
|
+
log('info', `Archived source plan: plans/archive/${md}`);
|
|
320
|
+
} catch (err) { log('warn', `Failed to archive plan ${md}: ${err.message}`); }
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
} catch (err) { log('warn', `Plan archive scan: ${err.message}`); }
|
|
325
|
+
}
|
|
290
326
|
|
|
291
|
-
//
|
|
327
|
+
// Clean up ALL worktrees created for this plan's work items (shared-branch + per-item)
|
|
292
328
|
try {
|
|
293
|
-
|
|
329
|
+
let allWi = [];
|
|
330
|
+
for (const p of projects) {
|
|
331
|
+
try { allWi = allWi.concat(safeJson(shared.projectWorkItemsPath(p)) || []); } catch {}
|
|
332
|
+
}
|
|
333
|
+
const planWi = allWi.filter(w => w.sourcePlan === planFile && w.itemType !== 'verify');
|
|
334
|
+
const allPrs = [];
|
|
335
|
+
for (const p of projects) {
|
|
336
|
+
try { allPrs.push(...(safeJson(shared.projectPrPath(p)) || [])); } catch {}
|
|
337
|
+
}
|
|
338
|
+
|
|
294
339
|
const branchSlugs = new Set();
|
|
295
340
|
if (plan.feature_branch) branchSlugs.add(shared.sanitizeBranch(plan.feature_branch).toLowerCase());
|
|
296
|
-
for (const w of
|
|
341
|
+
for (const w of planWi) {
|
|
297
342
|
if (w.branch) branchSlugs.add(shared.sanitizeBranch(w.branch).toLowerCase());
|
|
298
343
|
if (w.id) branchSlugs.add(w.id.toLowerCase());
|
|
299
344
|
}
|
|
300
|
-
for (const pr of
|
|
345
|
+
for (const pr of allPrs.filter(pr => (pr.prdItems || []).some(id => planWi.find(w => w.id === id)))) {
|
|
301
346
|
if (pr.branch) branchSlugs.add(shared.sanitizeBranch(pr.branch).toLowerCase());
|
|
302
347
|
}
|
|
303
348
|
|
|
@@ -319,10 +364,8 @@ function checkPlanCompletion(meta, config) {
|
|
|
319
364
|
}
|
|
320
365
|
}
|
|
321
366
|
}
|
|
322
|
-
if (cleanedWt > 0) log('info', `Plan
|
|
367
|
+
if (cleanedWt > 0) log('info', `Plan archive: cleaned ${cleanedWt} worktree(s)`);
|
|
323
368
|
} catch (err) { log('warn', `Worktree cleanup: ${err.message}`); }
|
|
324
|
-
|
|
325
|
-
log('info', `PRD ${planFile} completed: ${doneItems.length} done, ${failedItems.length} failed, runtime ${runtimeMin}m`);
|
|
326
369
|
}
|
|
327
370
|
|
|
328
371
|
// ─── Plan → PRD Chaining ─────────────────────────────────────────────────────
|
|
@@ -393,7 +436,10 @@ function chainPlanToPrd(dispatchItem, meta, config) {
|
|
|
393
436
|
log('info', `Plan chaining: queuing plan-to-prd for next tick (chained from ${dispatchItem.id})`);
|
|
394
437
|
const wiPath = path.join(MINIONS_DIR, 'work-items.json');
|
|
395
438
|
let items = [];
|
|
396
|
-
try { items = JSON.parse(fs.readFileSync(wiPath, 'utf8')); } catch {
|
|
439
|
+
try { items = JSON.parse(fs.readFileSync(wiPath, 'utf8')); } catch (err) {
|
|
440
|
+
log('warn', `Failed to parse ${wiPath}: ${err.message} — creating .bak and starting fresh`);
|
|
441
|
+
try { fs.copyFileSync(wiPath, wiPath + '.bak'); } catch {}
|
|
442
|
+
}
|
|
397
443
|
items.push({
|
|
398
444
|
id: 'W-' + shared.uid(),
|
|
399
445
|
title: `Convert plan to PRD: ${meta?.item?.title || planFile.name}`,
|
|
@@ -580,7 +626,7 @@ function syncPrsFromOutput(output, agentId, meta, config) {
|
|
|
580
626
|
dirtyTargets.set(targetName, { prs: safeJson(prPath) || [], prPath });
|
|
581
627
|
}
|
|
582
628
|
const entry = dirtyTargets.get(targetName);
|
|
583
|
-
if (entry.prs.some(p => p.id === fullId || String(p.id)
|
|
629
|
+
if (entry.prs.some(p => p.id === fullId || String(p.id) === String(prId))) continue;
|
|
584
630
|
|
|
585
631
|
let title = meta?.item?.title || '';
|
|
586
632
|
const titleMatch = output.match(new RegExp(`${prId}[^\\n]*?[—–-]\\s*([^\\n]+)`, 'i'));
|
|
@@ -651,7 +697,7 @@ function updatePrAfterReview(agentId, pr, project) {
|
|
|
651
697
|
}
|
|
652
698
|
|
|
653
699
|
shared.safeWrite(project ? shared.projectPrPath(project) : path.join(path.resolve(MINIONS_DIR, '..'), '.minions', 'pull-requests.json'), prs);
|
|
654
|
-
log('info', `Updated ${pr.id} → minions review: ${
|
|
700
|
+
log('info', `Updated ${pr.id} → minions review: ${target.reviewStatus} by ${reviewerName}`);
|
|
655
701
|
createReviewFeedbackForAuthor(agentId, { ...pr, ...target }, config);
|
|
656
702
|
}
|
|
657
703
|
|
|
@@ -839,7 +885,7 @@ function extractSkillsFromOutput(output, agentId, dispatchItem, config) {
|
|
|
839
885
|
}
|
|
840
886
|
} else {
|
|
841
887
|
// Write in Claude Code native format: ~/.claude/skills/<name>/SKILL.md
|
|
842
|
-
const claudeSkillsDir = path.join(
|
|
888
|
+
const claudeSkillsDir = path.join(os.homedir(), '.claude', 'skills');
|
|
843
889
|
const skillDir = path.join(claudeSkillsDir, name.replace(/[^a-z0-9-]/g, '-'));
|
|
844
890
|
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
845
891
|
if (!fs.existsSync(skillPath)) {
|
|
@@ -1126,6 +1172,19 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1126
1172
|
let prsCreatedCount = 0;
|
|
1127
1173
|
if (isSuccess) prsCreatedCount = syncPrsFromOutput(stdout, agentId, meta, config) || 0;
|
|
1128
1174
|
|
|
1175
|
+
// Archive plan after verify task completes (AFTER PR sync so E2E PR is linked)
|
|
1176
|
+
if (meta?.item?.itemType === 'verify' && meta?.item?.sourcePlan) {
|
|
1177
|
+
try {
|
|
1178
|
+
const vPlanFile = meta.item.sourcePlan;
|
|
1179
|
+
const vPlanPath = path.join(PRD_DIR, vPlanFile);
|
|
1180
|
+
const vPlan = safeJson(vPlanPath);
|
|
1181
|
+
if (vPlan) {
|
|
1182
|
+
const vProjects = shared.getProjects(config);
|
|
1183
|
+
archivePlan(vPlanFile, vPlan, vProjects, config);
|
|
1184
|
+
}
|
|
1185
|
+
} catch (err) { log('warn', `Verify archive: ${err.message}`); }
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1129
1188
|
// Clean up worktree for non-shared-branch tasks after completion
|
|
1130
1189
|
if (meta?.branch && meta?.branchStrategy !== 'shared-branch') {
|
|
1131
1190
|
try {
|
|
@@ -1252,6 +1311,7 @@ function syncPrdFromPrs(config) {
|
|
|
1252
1311
|
|
|
1253
1312
|
module.exports = {
|
|
1254
1313
|
checkPlanCompletion,
|
|
1314
|
+
archivePlan,
|
|
1255
1315
|
updateWorkItemStatus,
|
|
1256
1316
|
syncPrdItemStatus,
|
|
1257
1317
|
syncPrsFromOutput,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.301",
|
|
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"
|