@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 CHANGED
@@ -1,8 +1,9 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.299 (2026-04-03)
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
 
@@ -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') return;
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 summaryFile = `prd-completion-${planFile.replace('.json', '')}-${ts().slice(0, 10)}.md`;
137
- shared.safeWrite(shared.uniquePath(path.join(MINIONS_DIR, 'notes', 'inbox', summaryFile)), summary);
138
- log('info', `PRD completion summary written to notes/inbox/${summaryFile}`);
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 wtPath = `${p.localPath}/../worktrees/verify-${name}-${planSlug}-${shared.uid()}`;
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 "${p.localPath}"`,
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
- `- **${name}**: \`${p.localPath}/../worktrees/verify-${planSlug}\``
216
- ).join('\n');
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: move PRD .json to prd/archive/ and source .md plan to plans/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
- shared.safeWrite(planPath, plan); // save completed status first
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
- // Also archive the source .md plan if it exists
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
- try {
277
- const mdFiles = fs.readdirSync(PLANS_DIR).filter(f => f.endsWith('.md'));
278
- for (const md of mdFiles) {
279
- const mdContent = shared.safeRead(path.join(PLANS_DIR, md)) || '';
280
- // Match by project name or plan summary appearing in the .md content
281
- if (mdContent.includes(projectName) || mdContent.includes(plan.plan_summary?.slice(0, 40) || '___nomatch___')) {
282
- try {
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
- } catch (err) { log('warn', `Plan archive scan: ${err.message}`); }
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
- // 6. Clean up ALL worktrees created for this plan's work items (shared-branch + per-item)
327
+ // Clean up ALL worktrees created for this plan's work items (shared-branch + per-item)
292
328
  try {
293
- // Collect all branch slugs: shared-branch + per-item branches + item IDs
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 doneItems) {
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 uniquePrs) {
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 completion: cleaned ${cleanedWt} worktree(s)`);
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(process.env.HOME || process.env.USERPROFILE || '', '.claude', 'skills');
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.299",
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"