@yemi33/minions 0.1.527 → 0.1.528

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,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.528 (2026-04-07)
4
+
5
+ ### Features
6
+ - Convert engine/ safeWrite calls on work-items.json to mutateWorkItems() (#415)
7
+
3
8
  ## 0.1.527 (2026-04-07)
4
9
 
5
10
  ### Features
package/engine/cleanup.js CHANGED
@@ -9,7 +9,7 @@ const shared = require('./shared');
9
9
  const queries = require('./queries');
10
10
 
11
11
  const { exec, execSilent, log, ts } = shared;
12
- const { safeJson, safeWrite, safeReadDir, getProjects, projectWorkItemsPath, projectPrPath,
12
+ const { safeJson, safeWrite, safeReadDir, mutateWorkItems, getProjects, projectWorkItemsPath, projectPrPath,
13
13
  sanitizeBranch, KB_CATEGORIES } = shared;
14
14
  const { getDispatch, getAgentStatus } = queries;
15
15
 
@@ -454,17 +454,17 @@ function runCleanup(config, verbose = false) {
454
454
  for (const project of projects) {
455
455
  try {
456
456
  const wiPath = projectWorkItemsPath(project);
457
- const items = safeJson(wiPath) || [];
458
457
  let migrated = 0;
459
- for (const item of items) {
460
- if (LEGACY_DONE_ALIASES.has(item.status)) {
461
- item.status = shared.WI_STATUS.DONE;
462
- delete item._pendingReason;
463
- migrated++;
458
+ mutateWorkItems(wiPath, items => {
459
+ for (const item of items) {
460
+ if (LEGACY_DONE_ALIASES.has(item.status)) {
461
+ item.status = shared.WI_STATUS.DONE;
462
+ delete item._pendingReason;
463
+ migrated++;
464
+ }
464
465
  }
465
- }
466
+ });
466
467
  if (migrated > 0) {
467
- safeWrite(wiPath, items);
468
468
  log('info', `Migrated ${migrated} legacy status(es) → done in ${project.name} work items`);
469
469
  }
470
470
  } catch (e) { log('warn', 'migrate legacy statuses: ' + e.message); }
@@ -472,17 +472,17 @@ function runCleanup(config, verbose = false) {
472
472
  // Central work items
473
473
  try {
474
474
  const centralPath = path.join(MINIONS_DIR, 'work-items.json');
475
- const centralItems = safeJson(centralPath) || [];
476
475
  let migrated = 0;
477
- for (const item of centralItems) {
478
- if (LEGACY_DONE_ALIASES.has(item.status)) {
479
- item.status = shared.WI_STATUS.DONE;
480
- delete item._pendingReason;
481
- migrated++;
476
+ mutateWorkItems(centralPath, items => {
477
+ for (const item of items) {
478
+ if (LEGACY_DONE_ALIASES.has(item.status)) {
479
+ item.status = shared.WI_STATUS.DONE;
480
+ delete item._pendingReason;
481
+ migrated++;
482
+ }
482
483
  }
483
- }
484
+ });
484
485
  if (migrated > 0) {
485
- safeWrite(centralPath, centralItems);
486
486
  log('info', `Migrated ${migrated} legacy status(es) → done in central work items`);
487
487
  }
488
488
  } catch (e) { log('warn', 'migrate central legacy statuses: ' + e.message); }
package/engine/cli.js CHANGED
@@ -6,7 +6,7 @@
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
  const shared = require('./shared');
9
- const { safeRead, safeJson, safeWrite, ts, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS, DISPATCH_RESULT } = shared;
9
+ const { safeRead, safeJson, safeWrite, mutateWorkItems, ts, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS, DISPATCH_RESULT } = shared;
10
10
  const queries = require('./queries');
11
11
  const { getConfig, getControl, getDispatch, getAgentStatus,
12
12
  MINIONS_DIR, ENGINE_DIR, AGENTS_DIR, PLANS_DIR, PRD_DIR, CONTROL_PATH, DISPATCH_PATH } = queries;
@@ -165,18 +165,18 @@ const commands = {
165
165
  if (sj?.sessionId) sessionId = sj.sessionId;
166
166
  } catch {}
167
167
  e.activeProcesses.set(item.id, { proc: { pid: agentPid > 0 ? agentPid : null }, agentId, startedAt: item.created_at, reattached: true, sessionId });
168
- // Sync work item status to dispatched — direct file write to avoid lifecycle lazy init issues
168
+ // Sync work item status to dispatched — atomic write to avoid lifecycle lazy init issues
169
169
  if (item.meta?.item?.id && item.meta?.project?.localPath) {
170
170
  try {
171
171
  const wiPath = path.join(MINIONS_DIR, "projects", item.meta.project.name, "work-items.json");
172
- const wiItems = safeJson(wiPath) || [];
173
- const wi = wiItems.find(w => w.id === item.meta.item.id);
174
- if (wi && wi.status !== WI_STATUS.DISPATCHED) {
175
- wi.status = WI_STATUS.DISPATCHED;
176
- wi.dispatched_to = wi.dispatched_to || agentId;
177
- wi.dispatched_at = wi.dispatched_at || ts();
178
- safeWrite(wiPath, wiItems);
179
- }
172
+ mutateWorkItems(wiPath, items => {
173
+ const wi = items.find(w => w.id === item.meta.item.id);
174
+ if (wi && wi.status !== WI_STATUS.DISPATCHED) {
175
+ wi.status = WI_STATUS.DISPATCHED;
176
+ wi.dispatched_to = wi.dispatched_to || agentId;
177
+ wi.dispatched_at = wi.dispatched_at || ts();
178
+ }
179
+ });
180
180
  } catch (err) { console.log(` Warning: failed to sync work item status: ${err.message}`); }
181
181
  }
182
182
  reattached++;
@@ -264,19 +264,19 @@ const commands = {
264
264
  try {
265
265
  lifecycle.updateWorkItemStatus(item.meta, status, isSuccess ? '' : 'Completed while engine was down');
266
266
  } catch {
267
- // Direct file write fallback
267
+ // Atomic file write fallback
268
268
  try {
269
269
  const projName = item.meta.project?.name;
270
270
  if (projName) {
271
271
  const wiPath = path.join(MINIONS_DIR, 'projects', projName, 'work-items.json');
272
- const items = safeJson(wiPath) || [];
273
- const wi = items.find(w => w.id === item.meta.item.id);
274
- if (wi) {
275
- wi.status = status;
276
- if (isSuccess) { wi.completedAt = ts(); delete wi.failReason; }
277
- else { wi.failedAt = ts(); wi.failReason = 'Completed while engine was down'; }
278
- safeWrite(wiPath, items);
279
- }
272
+ mutateWorkItems(wiPath, items => {
273
+ const wi = items.find(w => w.id === item.meta.item.id);
274
+ if (wi) {
275
+ wi.status = status;
276
+ if (isSuccess) { wi.completedAt = ts(); delete wi.failReason; }
277
+ else { wi.failedAt = ts(); wi.failReason = 'Completed while engine was down'; }
278
+ }
279
+ });
280
280
  }
281
281
  } catch (err) { e.log('warn', `Orphan WI fallback: ${err.message}`); }
282
282
  }
@@ -321,19 +321,17 @@ const commands = {
321
321
  }
322
322
  for (const wiPath of allWiPaths) {
323
323
  try {
324
- const items = safeJson(wiPath) || [];
325
- let changed = false;
326
- for (const item of items) {
327
- if (item.status === WI_STATUS.DISPATCHED && !activeIds.has(item.id)) {
328
- item.status = WI_STATUS.PENDING;
329
- delete item.dispatched_at;
330
- delete item.dispatched_to;
331
- changed = true;
332
- fixes++;
333
- e.log('info', `Recovery: reset stuck item ${item.id} from dispatched → pending`);
324
+ mutateWorkItems(wiPath, items => {
325
+ for (const item of items) {
326
+ if (item.status === WI_STATUS.DISPATCHED && !activeIds.has(item.id)) {
327
+ item.status = WI_STATUS.PENDING;
328
+ delete item.dispatched_at;
329
+ delete item.dispatched_to;
330
+ fixes++;
331
+ e.log('info', `Recovery: reset stuck item ${item.id} from dispatched → pending`);
332
+ }
334
333
  }
335
- }
336
- if (changed) safeWrite(wiPath, items);
334
+ });
337
335
  } catch (err) { e.log('warn', `Recovery WI reset: ${err.message}`); }
338
336
  }
339
337
 
@@ -691,24 +689,23 @@ const commands = {
691
689
  ? projects.find(p => p.name?.toLowerCase() === opts.project?.toLowerCase()) || projects[0]
692
690
  : projects[0];
693
691
  const wiPath = projectWorkItemsPath(targetProject);
694
- const items = safeJson(wiPath) || [];
695
-
696
- const item = {
697
- id: `W${String(items.length + 1).padStart(3, '0')}`,
698
- title: title,
699
- type: opts.type || 'implement',
700
- status: WI_STATUS.QUEUED,
701
- priority: opts.priority || 'medium',
702
- complexity: opts.complexity || 'medium',
703
- description: opts.description || title,
704
- agent: opts.agent || null,
705
- branch: opts.branch || null,
706
- prompt: opts.prompt || null,
707
- created_at: e.ts()
708
- };
709
-
710
- items.push(item);
711
- safeWrite(wiPath, items);
692
+ let item;
693
+ mutateWorkItems(wiPath, items => {
694
+ item = {
695
+ id: `W${String(items.length + 1).padStart(3, '0')}`,
696
+ title: title,
697
+ type: opts.type || 'implement',
698
+ status: WI_STATUS.QUEUED,
699
+ priority: opts.priority || 'medium',
700
+ complexity: opts.complexity || 'medium',
701
+ description: opts.description || title,
702
+ agent: opts.agent || null,
703
+ branch: opts.branch || null,
704
+ prompt: opts.prompt || null,
705
+ created_at: e.ts()
706
+ };
707
+ items.push(item);
708
+ });
712
709
 
713
710
  console.log(`Queued work item: ${item.id} — ${item.title} (project: ${targetProject.name || 'default'})`);
714
711
  console.log(` Type: ${item.type} | Priority: ${item.priority} | Agent: ${item.agent || 'auto'}`);
@@ -903,16 +900,16 @@ const commands = {
903
900
  ? shared.projectWorkItemsPath({ localPath: item.meta.project.localPath, name: item.meta.project.name, workSources: config.projects?.find(p => p.name === item.meta.project.name)?.workSources })
904
901
  : null;
905
902
  if (wiPath) {
906
- const items = safeJson(wiPath) || [];
907
- const target = items.find(i => i.id === itemId);
908
- if (target) {
909
- target.status = WI_STATUS.PENDING;
910
- delete target.dispatched_at;
911
- delete target.dispatched_to;
912
- delete target.failReason;
913
- delete target.failedAt;
914
- safeWrite(wiPath, items);
915
- }
903
+ mutateWorkItems(wiPath, items => {
904
+ const target = items.find(i => i.id === itemId);
905
+ if (target) {
906
+ target.status = WI_STATUS.PENDING;
907
+ delete target.dispatched_at;
908
+ delete target.dispatched_to;
909
+ delete target.failReason;
910
+ delete target.failedAt;
911
+ }
912
+ });
916
913
  }
917
914
  }
918
915
  }
@@ -9,7 +9,7 @@ const shared = require('./shared');
9
9
  const queries = require('./queries');
10
10
  const { setCooldownFailure } = require('./cooldown');
11
11
 
12
- const { safeJson, safeWrite, safeReadDir, mutateJsonFileLocked,
12
+ const { safeJson, safeWrite, safeReadDir, mutateJsonFileLocked, mutateWorkItems,
13
13
  getProjects, projectWorkItemsPath, log, ts, dateStamp,
14
14
  WI_STATUS, DISPATCH_RESULT, ENGINE_DEFAULTS } = shared;
15
15
  const { getConfig, getDispatch, DISPATCH_PATH, INBOX_DIR } = queries;
@@ -146,20 +146,19 @@ function completeDispatch(id, result = DISPATCH_RESULT.SUCCESS, reason = '', res
146
146
  try {
147
147
  const wiPath = lifecycle().resolveWorkItemPath(item.meta);
148
148
  if (wiPath) {
149
- const items = safeJson(wiPath);
150
- if (!items || !Array.isArray(items)) throw new Error('work items unreadable');
151
- const wi = items.find(i => i.id === item.meta.item.id);
152
- if (wi && wi.status !== WI_STATUS.PAUSED && wi.status !== WI_STATUS.DONE && !wi.completedAt) {
153
- wi._retryCount = retries + 1;
154
- wi.status = WI_STATUS.PENDING;
155
- wi._lastRetryReason = reason || '';
156
- wi._lastRetryAt = ts();
157
- delete wi.failReason;
158
- delete wi.failedAt;
159
- delete wi.dispatched_at;
160
- delete wi.dispatched_to;
161
- safeWrite(wiPath, items);
162
- }
149
+ mutateWorkItems(wiPath, items => {
150
+ const wi = items.find(i => i.id === item.meta.item.id);
151
+ if (wi && wi.status !== WI_STATUS.PAUSED && wi.status !== WI_STATUS.DONE && !wi.completedAt) {
152
+ wi._retryCount = retries + 1;
153
+ wi.status = WI_STATUS.PENDING;
154
+ wi._lastRetryReason = reason || '';
155
+ wi._lastRetryAt = ts();
156
+ delete wi.failReason;
157
+ delete wi.failedAt;
158
+ delete wi.dispatched_at;
159
+ delete wi.dispatched_to;
160
+ }
161
+ });
163
162
  }
164
163
  } catch (e) { log('warn', 'increment retry counter: ' + e.message); }
165
164
  } else {
@@ -7,7 +7,7 @@ const fs = require('fs');
7
7
  const path = require('path');
8
8
  const os = require('os');
9
9
  const shared = require('./shared');
10
- const { safeRead, safeJson, safeWrite, mutateJsonFileLocked, execSilent, projectPrPath, getPrLinks, addPrLink,
10
+ const { safeRead, safeJson, safeWrite, mutateJsonFileLocked, mutateWorkItems, execSilent, projectPrPath, getPrLinks, addPrLink,
11
11
  log, ts, dateStamp, WI_STATUS, DONE_STATUSES, WORK_TYPE, PLAN_STATUS, PR_STATUS, DISPATCH_RESULT,
12
12
  ENGINE_DEFAULTS } = shared;
13
13
  const { trackEngineUsage } = require('./llm');
@@ -145,7 +145,6 @@ function checkPlanCompletion(meta, config) {
145
145
  return;
146
146
  }
147
147
  const wiPath = shared.projectWorkItemsPath(primaryProject);
148
- const workItems = safeJson(wiPath) || [];
149
148
 
150
149
  // 3. For shared-branch plans, create PR work item
151
150
  if (plan.branch_strategy === 'shared-branch' && plan.feature_branch && wiPath) {
@@ -155,15 +154,16 @@ function checkPlanCompletion(meta, config) {
155
154
  const featureBranch = plan.feature_branch;
156
155
  const mainBranch = shared.resolveMainBranch(primaryProject.localPath, primaryProject.mainBranch);
157
156
  const itemSummary = doneItems.map(w => '- ' + w.id + ': ' + w.title.replace('Implement: ', '')).join('\n');
158
- workItems.push({
159
- id, title: `Create PR for plan: ${plan.plan_summary || planFile}`,
160
- type: 'implement', priority: 'high',
161
- description: `All plan items from \`${planFile}\` are complete on branch \`${featureBranch}\`.\n\n**Branch:** \`${featureBranch}\`\n**Target:** \`${mainBranch}\`\n\n## Completed Items\n${itemSummary}`,
162
- status: WI_STATUS.PENDING, created: ts(), createdBy: 'engine:plan-completion',
163
- sourcePlan: planFile, itemType: 'pr',
164
- branch: featureBranch, branchStrategy: 'shared-branch', project: projectName,
157
+ mutateWorkItems(wiPath, workItems => {
158
+ workItems.push({
159
+ id, title: `Create PR for plan: ${plan.plan_summary || planFile}`,
160
+ type: 'implement', priority: 'high',
161
+ description: `All plan items from \`${planFile}\` are complete on branch \`${featureBranch}\`.\n\n**Branch:** \`${featureBranch}\`\n**Target:** \`${mainBranch}\`\n\n## Completed Items\n${itemSummary}`,
162
+ status: WI_STATUS.PENDING, created: ts(), createdBy: 'engine:plan-completion',
163
+ sourcePlan: planFile, itemType: 'pr',
164
+ branch: featureBranch, branchStrategy: 'shared-branch', project: projectName,
165
+ });
165
166
  });
166
- shared.safeWrite(wiPath, workItems);
167
167
  }
168
168
  }
169
169
 
@@ -244,20 +244,21 @@ function checkPlanCompletion(meta, config) {
244
244
  prSummary,
245
245
  ].join('\n');
246
246
 
247
- workItems.push({
248
- id: verifyId,
249
- title: `Verify plan: ${(plan.plan_summary || planFile).slice(0, 80)}`,
250
- type: 'verify',
251
- priority: 'high',
252
- description,
253
- status: WI_STATUS.PENDING,
254
- created: ts(),
255
- createdBy: 'engine:plan-verification',
256
- sourcePlan: planFile,
257
- itemType: 'verify',
258
- project: projectName,
247
+ mutateWorkItems(wiPath, workItems => {
248
+ workItems.push({
249
+ id: verifyId,
250
+ title: `Verify plan: ${(plan.plan_summary || planFile).slice(0, 80)}`,
251
+ type: 'verify',
252
+ priority: 'high',
253
+ description,
254
+ status: WI_STATUS.PENDING,
255
+ created: ts(),
256
+ createdBy: 'engine:plan-verification',
257
+ sourcePlan: planFile,
258
+ itemType: 'verify',
259
+ project: projectName,
260
+ });
259
261
  });
260
- shared.safeWrite(wiPath, workItems);
261
262
  log('info', `Created verification work item ${verifyId} for plan ${planFile}`);
262
263
  }
263
264
 
@@ -811,17 +812,18 @@ async function handlePostMerge(pr, project, config, newStatus) {
811
812
  for (const p of shared.getProjects(config)) wiPaths.push(shared.projectWorkItemsPath(p));
812
813
  for (const wiPath of wiPaths) {
813
814
  try {
814
- const items = safeJson(wiPath);
815
- if (!items) continue;
816
- const item = items.find(i => i.id === mergedItemId);
817
- if (item && item.status !== WI_STATUS.DONE) {
818
- log('info', `Post-merge: marking work item ${mergedItemId} as done (was ${item.status}) for ${pr.id}`);
819
- item.status = WI_STATUS.DONE;
820
- item.completedAt = ts();
821
- item._mergedVia = pr.id;
822
- shared.safeWrite(wiPath, items);
823
- break;
824
- }
815
+ let found = false;
816
+ mutateWorkItems(wiPath, items => {
817
+ const item = items.find(i => i.id === mergedItemId);
818
+ if (item && item.status !== WI_STATUS.DONE) {
819
+ log('info', `Post-merge: marking work item ${mergedItemId} as done (was ${item.status}) for ${pr.id}`);
820
+ item.status = WI_STATUS.DONE;
821
+ item.completedAt = ts();
822
+ item._mergedVia = pr.id;
823
+ found = true;
824
+ }
825
+ });
826
+ if (found) break;
825
827
  } catch (err) { log('warn', `Post-merge work item update: ${err.message}`); }
826
828
  }
827
829
  }
@@ -7,7 +7,7 @@
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
9
  const shared = require('./shared');
10
- const { safeJson, safeWrite, safeRead, safeReadDir, uid, log, ts, dateStamp, mutateJsonFileLocked, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS, PIPELINE_STATUS, STAGE_TYPE, MEETING_STATUS, ENGINE_DEFAULTS } = shared;
10
+ const { safeJson, safeWrite, safeRead, safeReadDir, uid, log, ts, dateStamp, mutateJsonFileLocked, mutateWorkItems, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS, PIPELINE_STATUS, STAGE_TYPE, MEETING_STATUS, ENGINE_DEFAULTS } = shared;
11
11
  const http = require('http');
12
12
  const { parseCronExpr, shouldRunNow } = require('./scheduler');
13
13
 
@@ -159,32 +159,32 @@ function executeTaskStage(stage, stageState, run, config) {
159
159
  const items = stage.items || [{ title: stage.title, description: stage.description || '', type: stage.taskType || 'explore', agent: stage.agent }];
160
160
  const count = stage.count || items.length;
161
161
  const wiPath = path.join(__dirname, '..', 'work-items.json');
162
- const workItems = safeJson(wiPath) || [];
163
162
  const createdIds = [];
164
163
 
165
- for (let i = 0; i < count; i++) {
166
- const item = items[i % items.length];
167
- const id = `PL-${run.runId.slice(4, 12)}-${stage.id}-${i}`;
168
- if (workItems.some(w => w.id === id)) { createdIds.push(id); continue; }
169
- workItems.push({
170
- id,
171
- title: item.title || stage.title,
172
- description: item.description || stage.description || '',
173
- type: item.type || stage.taskType || 'explore',
174
- priority: item.priority || stage.priority || 'medium',
175
- // Only set agent if explicitly specified — otherwise engine routing assigns any available agent
176
- ...(item.agent || stage.agent ? { agent: item.agent || stage.agent } : {}),
177
- status: WI_STATUS.PENDING,
178
- created: ts(),
179
- createdBy: 'pipeline:' + run.pipelineId,
180
- branch: `pipeline/${run.pipelineId}/${stage.id}`,
181
- _pipelineRun: run.runId,
182
- _pipelineStage: stage.id,
183
- });
184
- createdIds.push(id);
185
- }
164
+ mutateWorkItems(wiPath, workItems => {
165
+ for (let i = 0; i < count; i++) {
166
+ const item = items[i % items.length];
167
+ const id = `PL-${run.runId.slice(4, 12)}-${stage.id}-${i}`;
168
+ if (workItems.some(w => w.id === id)) { createdIds.push(id); continue; }
169
+ workItems.push({
170
+ id,
171
+ title: item.title || stage.title,
172
+ description: item.description || stage.description || '',
173
+ type: item.type || stage.taskType || 'explore',
174
+ priority: item.priority || stage.priority || 'medium',
175
+ // Only set agent if explicitly specified otherwise engine routing assigns any available agent
176
+ ...(item.agent || stage.agent ? { agent: item.agent || stage.agent } : {}),
177
+ status: WI_STATUS.PENDING,
178
+ created: ts(),
179
+ createdBy: 'pipeline:' + run.pipelineId,
180
+ branch: `pipeline/${run.pipelineId}/${stage.id}`,
181
+ _pipelineRun: run.runId,
182
+ _pipelineStage: stage.id,
183
+ });
184
+ createdIds.push(id);
185
+ }
186
+ });
186
187
 
187
- safeWrite(wiPath, workItems);
188
188
  return { status: PIPELINE_STATUS.RUNNING, artifacts: { workItems: createdIds } };
189
189
  }
190
190
 
@@ -280,24 +280,24 @@ async function executePlanStage(stage, stageState, run, config) {
280
280
 
281
281
  // Create plan-to-prd work item
282
282
  const wiPath = path.join(__dirname, '..', 'work-items.json');
283
- const workItems = safeJson(wiPath) || [];
284
283
  const wiId = `PL-${run.runId.slice(4, 12)}-${stage.id}-prd`;
285
- if (!workItems.some(w => w.id === wiId)) {
286
- workItems.push({
287
- id: wiId,
288
- title: `Convert plan to PRD: ${path.basename(filePath)}`,
289
- type: WORK_TYPE.PLAN_TO_PRD,
290
- priority: 'high',
291
- status: WI_STATUS.PENDING,
292
- planFile: path.basename(filePath),
293
- created: ts(),
294
- createdBy: 'pipeline:' + run.pipelineId,
295
- branch: `pipeline/${run.pipelineId}/${stage.id}`,
296
- _pipelineRun: run.runId,
297
- _pipelineStage: stage.id,
298
- });
299
- safeWrite(wiPath, workItems);
300
- }
284
+ mutateWorkItems(wiPath, workItems => {
285
+ if (!workItems.some(w => w.id === wiId)) {
286
+ workItems.push({
287
+ id: wiId,
288
+ title: `Convert plan to PRD: ${path.basename(filePath)}`,
289
+ type: WORK_TYPE.PLAN_TO_PRD,
290
+ priority: 'high',
291
+ status: WI_STATUS.PENDING,
292
+ planFile: path.basename(filePath),
293
+ created: ts(),
294
+ createdBy: 'pipeline:' + run.pipelineId,
295
+ branch: `pipeline/${run.pipelineId}/${stage.id}`,
296
+ _pipelineRun: run.runId,
297
+ _pipelineStage: stage.id,
298
+ });
299
+ }
300
+ });
301
301
 
302
302
  return {
303
303
  status: PIPELINE_STATUS.RUNNING,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.527",
3
+ "version": "0.1.528",
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"