@yemi33/minions 0.1.299 → 0.1.300
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 +2 -1
- package/engine/lifecycle.js +97 -40
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.300 (2026-04-03)
|
|
4
4
|
|
|
5
5
|
### Fixes
|
|
6
|
+
- re-apply verify workflow — defer archiving until verify completes
|
|
6
7
|
- work item agent column falls back to item.agent field
|
|
7
8
|
- CC workType descriptions restored + verify for maintenance/merge tasks
|
|
8
9
|
|
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 ─────────────────────────────────────────────────────
|
|
@@ -839,7 +882,7 @@ function extractSkillsFromOutput(output, agentId, dispatchItem, config) {
|
|
|
839
882
|
}
|
|
840
883
|
} else {
|
|
841
884
|
// Write in Claude Code native format: ~/.claude/skills/<name>/SKILL.md
|
|
842
|
-
const claudeSkillsDir = path.join(
|
|
885
|
+
const claudeSkillsDir = path.join(os.homedir(), '.claude', 'skills');
|
|
843
886
|
const skillDir = path.join(claudeSkillsDir, name.replace(/[^a-z0-9-]/g, '-'));
|
|
844
887
|
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
845
888
|
if (!fs.existsSync(skillPath)) {
|
|
@@ -1126,6 +1169,19 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1126
1169
|
let prsCreatedCount = 0;
|
|
1127
1170
|
if (isSuccess) prsCreatedCount = syncPrsFromOutput(stdout, agentId, meta, config) || 0;
|
|
1128
1171
|
|
|
1172
|
+
// Archive plan after verify task completes (AFTER PR sync so E2E PR is linked)
|
|
1173
|
+
if (meta?.item?.itemType === 'verify' && meta?.item?.sourcePlan) {
|
|
1174
|
+
try {
|
|
1175
|
+
const vPlanFile = meta.item.sourcePlan;
|
|
1176
|
+
const vPlanPath = path.join(PRD_DIR, vPlanFile);
|
|
1177
|
+
const vPlan = safeJson(vPlanPath);
|
|
1178
|
+
if (vPlan) {
|
|
1179
|
+
const vProjects = shared.getProjects(config);
|
|
1180
|
+
archivePlan(vPlanFile, vPlan, vProjects, config);
|
|
1181
|
+
}
|
|
1182
|
+
} catch (err) { log('warn', `Verify archive: ${err.message}`); }
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1129
1185
|
// Clean up worktree for non-shared-branch tasks after completion
|
|
1130
1186
|
if (meta?.branch && meta?.branchStrategy !== 'shared-branch') {
|
|
1131
1187
|
try {
|
|
@@ -1252,6 +1308,7 @@ function syncPrdFromPrs(config) {
|
|
|
1252
1308
|
|
|
1253
1309
|
module.exports = {
|
|
1254
1310
|
checkPlanCompletion,
|
|
1311
|
+
archivePlan,
|
|
1255
1312
|
updateWorkItemStatus,
|
|
1256
1313
|
syncPrdItemStatus,
|
|
1257
1314
|
syncPrsFromOutput,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.300",
|
|
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"
|