@yemi33/minions 0.1.356 → 0.1.358

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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.358 (2026-04-06)
4
+
5
+ ### Features
6
+ - pipeline plan stage uses LLM to generate structured plan from meeting
7
+ - show 'Converting to PRD' status instead of 'In Progress' during plan conversion
8
+
3
9
  ## 0.1.356 (2026-04-06)
4
10
 
5
11
  ### Fixes
@@ -83,7 +83,8 @@ function derivePlanStatus(prdFile, mdFile, prdJsonStatus, workItems) {
83
83
 
84
84
  // Derive from work item progress
85
85
  if (allDone && !hasActiveWork) return 'completed';
86
- if (hasActiveWork || hasPendingPrd) return 'dispatched';
86
+ if (hasActiveWork) return 'dispatched';
87
+ if (hasPendingPrd) return 'converting';
87
88
  if (hasFailed && !hasActiveWork) return 'has-failures';
88
89
 
89
90
  if (prdJsonStatus === 'awaiting-approval' && implementWi.length === 0) return 'awaiting-approval';
@@ -192,9 +193,10 @@ function renderPlans(plans) {
192
193
  const effectiveStatus = isArchived ? 'completed' : derivePlanStatus(prdFile, p.file, prdJsonStatus, allWi);
193
194
 
194
195
  const statusLabelsMap = {
195
- 'completed': 'Completed', 'dispatched': 'In Progress', 'paused': 'Paused',
196
- 'awaiting-approval': 'Awaiting Approval', 'approved': 'Approved', 'rejected': 'Rejected',
197
- 'revision-requested': 'Revision Requested', 'has-failures': 'Has Failures', 'active': 'Active'
196
+ 'completed': 'Completed', 'dispatched': 'In Progress', 'converting': 'Converting to PRD',
197
+ 'paused': 'Paused', 'awaiting-approval': 'Awaiting Approval', 'approved': 'Approved',
198
+ 'rejected': 'Rejected', 'revision-requested': 'Revision Requested',
199
+ 'has-failures': 'Has Failures', 'active': 'Active'
198
200
  };
199
201
  const label = statusLabelsMap[effectiveStatus] || effectiveStatus;
200
202
  const needsAction = (effectiveStatus === 'awaiting-approval' || effectiveStatus === 'paused') && !isArchived;
@@ -246,8 +248,8 @@ function renderPlans(plans) {
246
248
  'onclick="event.stopPropagation();planDelete(\'' + escHtml(p.file) + '\')">Delete</button>' : '';
247
249
 
248
250
  const versionBadge = p.version ? ' <span style="font-size:9px;font-weight:700;padding:1px 5px;border-radius:3px;background:rgba(56,139,253,0.15);color:var(--blue);vertical-align:middle">v' + p.version + '</span>' : '';
249
- const statusColors = { 'completed': 'var(--green)', 'dispatched': 'var(--blue)', 'paused': 'var(--muted)', 'awaiting-approval': 'var(--yellow)', 'approved': 'var(--green)', 'rejected': 'var(--red)', 'has-failures': 'var(--red)', 'revision-requested': 'var(--purple,#a855f7)', 'active': 'var(--muted)' };
250
- const cardClass = effectiveStatus === 'dispatched' ? 'working' : effectiveStatus === 'awaiting-approval' || effectiveStatus === 'paused' ? 'awaiting' : effectiveStatus;
251
+ const statusColors = { 'completed': 'var(--green)', 'dispatched': 'var(--blue)', 'converting': 'var(--yellow)', 'paused': 'var(--muted)', 'awaiting-approval': 'var(--yellow)', 'approved': 'var(--green)', 'rejected': 'var(--red)', 'has-failures': 'var(--red)', 'revision-requested': 'var(--purple,#a855f7)', 'active': 'var(--muted)' };
252
+ const cardClass = effectiveStatus === 'dispatched' || effectiveStatus === 'converting' ? 'working' : effectiveStatus === 'awaiting-approval' || effectiveStatus === 'paused' ? 'awaiting' : effectiveStatus;
251
253
  return '<div class="plan-card ' + cardClass + '" data-file="plans/' + escHtml(p.file) + '" style="cursor:pointer' + (isArchived ? ';opacity:0.7' : '') + '" onclick="planView(\'' + escHtml(p.file) + '\')">' +
252
254
  '<div class="plan-card-header">' +
253
255
  '<div><div class="plan-card-title">' + escHtml(p.summary || p.file) + versionBadge + '</div>' +
@@ -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 } = shared;
10
+ const { safeJson, safeWrite, safeRead, safeReadDir, uid, log, ts, dateStamp, mutateJsonFileLocked, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS, PIPELINE_STATUS, MEETING_STATUS } = shared;
11
11
  const { parseCronExpr, shouldRunNow } = require('./scheduler');
12
12
 
13
13
  const PIPELINES_DIR = path.join(__dirname, '..', 'pipelines');
@@ -53,16 +53,16 @@ function savePipelineRuns(runs) {
53
53
  function getActiveRun(pipelineId) {
54
54
  const runs = getPipelineRuns();
55
55
  const pipelineRuns = runs[pipelineId] || [];
56
- return pipelineRuns.find(r => r.status === 'running' || r.status === 'paused');
56
+ return pipelineRuns.find(r => r.status === PIPELINE_STATUS.RUNNING || r.status === PIPELINE_STATUS.PAUSED);
57
57
  }
58
58
 
59
59
  function startRun(pipelineId, pipeline) {
60
60
  const runId = `run-${uid()}`;
61
61
  const stages = {};
62
62
  for (const stage of (pipeline.stages || [])) {
63
- stages[stage.id] = { status: 'pending', artifacts: {} };
63
+ stages[stage.id] = { status: PIPELINE_STATUS.PENDING, artifacts: {} };
64
64
  }
65
- const run = { runId, pipelineId, startedAt: ts(), status: 'running', stages };
65
+ const run = { runId, pipelineId, startedAt: ts(), status: PIPELINE_STATUS.RUNNING, stages };
66
66
 
67
67
  mutateJsonFileLocked(PIPELINE_RUNS_PATH, (data) => {
68
68
  if (!data[pipelineId]) data[pipelineId] = [];
@@ -125,7 +125,7 @@ function resolveStageConfig(stage, run) {
125
125
 
126
126
  // ── Stage Execution ──────────────────────────────────────────────────────────
127
127
 
128
- function executeStage(stage, run, pipeline, config) {
128
+ async function executeStage(stage, run, pipeline, config) {
129
129
  const resolved = resolveStageConfig(stage, run);
130
130
  const stageState = run.stages[stage.id];
131
131
 
@@ -144,12 +144,12 @@ function executeStage(stage, run, pipeline, config) {
144
144
  return executeScheduleStage(resolved, stageState, config);
145
145
  case 'wait':
146
146
  // wait stages just sit in waiting-human status until continued via API
147
- return { status: 'waiting-human' };
147
+ return { status: PIPELINE_STATUS.WAITING_HUMAN };
148
148
  case 'parallel':
149
149
  return executeParallelStage(resolved, stageState, run, pipeline, config);
150
150
  default:
151
151
  log('warn', `Pipeline: unknown stage type '${resolved.type}' in stage ${stage.id}`);
152
- return { status: 'failed', error: 'unknown stage type' };
152
+ return { status: PIPELINE_STATUS.FAILED, error: 'unknown stage type' };
153
153
  }
154
154
  }
155
155
 
@@ -183,7 +183,7 @@ function executeTaskStage(stage, stageState, run, config) {
183
183
  }
184
184
 
185
185
  safeWrite(wiPath, workItems);
186
- return { status: 'running', artifacts: { workItems: createdIds } };
186
+ return { status: PIPELINE_STATUS.RUNNING, artifacts: { workItems: createdIds } };
187
187
  }
188
188
 
189
189
  function executeMeetingStage(stage, stageState, run, config) {
@@ -205,10 +205,10 @@ function executeMeetingStage(stage, stageState, run, config) {
205
205
  createdIds.push(meeting.id);
206
206
  }
207
207
 
208
- return { status: 'running', artifacts: { meetings: createdIds } };
208
+ return { status: PIPELINE_STATUS.RUNNING, artifacts: { meetings: createdIds } };
209
209
  }
210
210
 
211
- function executePlanStage(stage, stageState, run, config) {
211
+ async function executePlanStage(stage, stageState, run, config) {
212
212
  // Create a plan .md file from the stage config + previous stage output
213
213
  const plansDir = path.join(__dirname, '..', 'plans');
214
214
  if (!fs.existsSync(plansDir)) fs.mkdirSync(plansDir, { recursive: true });
@@ -217,39 +217,62 @@ function executePlanStage(stage, stageState, run, config) {
217
217
  const filename = `${slug}-${dateStamp()}.md`;
218
218
  const filePath = shared.uniquePath(path.join(plansDir, filename));
219
219
 
220
- let content = `# ${stage.title}\n\n`;
221
- content += `**Created by:** Pipeline ${run.pipelineId}\n`;
222
- content += `**Date:** ${dateStamp()}\n\n---\n\n`;
223
-
224
- // Include output from dependency stages (for meetings, include full transcript)
220
+ // Build meeting context from dependency stages
221
+ let meetingContext = '';
225
222
  if (stage.dependsOn) {
226
223
  for (const depId of stage.dependsOn) {
227
224
  const depStage = run.stages[depId];
228
225
  if (!depStage) continue;
229
226
 
230
- // If dependency is a meeting, include the conclusion (which should be concrete and actionable)
231
227
  const meetingId = depStage.artifacts?.meetings?.[0];
232
228
  if (meetingId) {
233
229
  try {
234
230
  const mtgPath = path.join(__dirname, '..', 'meetings', meetingId + '.json');
235
231
  const mtg = safeJson(mtgPath);
236
232
  if (mtg) {
237
- const conclusion = typeof mtg.conclusion === 'string' ? mtg.conclusion : mtg.conclusion?.content || '';
238
- if (conclusion) {
239
- content += `## Meeting Conclusion: ${mtg.title || depId}\n\n${conclusion}\n\n`;
240
- continue;
241
- }
233
+ // Build full meeting document (same as dashboard "Create Plan from Meeting")
234
+ const transcript = (mtg.transcript || []).map(t =>
235
+ '### ' + (t.agent || 'agent') + ' (' + (t.type || '') + ', Round ' + (t.round || '?') + ')\n\n' + (t.content || '')
236
+ ).join('\n\n---\n\n');
237
+ meetingContext += '# Meeting: ' + (mtg.title || depId) + '\n\n**Agenda:** ' + (mtg.agenda || '') + '\n\n' + transcript + '\n\n';
238
+ continue;
242
239
  }
243
- } catch { /* fall through to output-only */ }
240
+ } catch { /* fall through */ }
244
241
  }
245
242
 
246
243
  if (depStage.output) {
247
- content += `## From: ${depId}\n\n${depStage.output}\n\n`;
244
+ meetingContext += '## From: ' + depId + '\n\n' + depStage.output + '\n\n';
248
245
  }
249
246
  }
250
247
  }
251
248
 
252
- if (stage.description) content += stage.description + '\n';
249
+ // Use LLM to generate a structured plan (same approach as dashboard "Create Plan from Meeting" button)
250
+ let content = '';
251
+ try {
252
+ const llm = require('./llm');
253
+ const planPrompt = 'Create an actionable implementation plan from this meeting. ' +
254
+ 'Extract concrete action items from the conclusion and debates. ' +
255
+ 'For each item include: what to do, which files/areas to change, priority (high/medium/low), and estimated complexity (small/medium/large). ' +
256
+ 'Structure it as a plan ready for execution. Do NOT include preamble — start with the plan title.' +
257
+ (stage.description ? '\n\nAdditional instructions: ' + stage.description : '');
258
+ const fullPrompt = meetingContext + '\n\n---\n\n' + planPrompt;
259
+ const result = await llm.callLLM(fullPrompt, '', {
260
+ timeout: 120000, label: 'pipeline-plan', model: 'sonnet', maxTurns: 1,
261
+ });
262
+ if (result.text) {
263
+ content = result.text;
264
+ log('info', `Pipeline plan: LLM generated ${content.length} chars from meeting context`);
265
+ }
266
+ } catch (e) { log('warn', `Pipeline plan LLM failed: ${e.message} — falling back to raw meeting context`); }
267
+
268
+ // Fallback: raw meeting context if LLM failed
269
+ if (!content) {
270
+ content = `# ${stage.title}\n\n`;
271
+ content += `**Created by:** Pipeline ${run.pipelineId}\n`;
272
+ content += `**Date:** ${dateStamp()}\n\n---\n\n`;
273
+ content += meetingContext;
274
+ if (stage.description) content += stage.description + '\n';
275
+ }
253
276
 
254
277
  safeWrite(filePath, content);
255
278
 
@@ -303,7 +326,7 @@ function executeApiStage(stage, stageState, run) {
303
326
  req.end();
304
327
  } catch (e) { log('warn', `Pipeline API call failed: ${e.message}`); }
305
328
  }
306
- return { status: 'completed', completedAt: ts() };
329
+ return { status: PIPELINE_STATUS.COMPLETED, completedAt: ts() };
307
330
  }
308
331
 
309
332
  function executeMergePrsStage(stage, stageState, run, config) {
@@ -313,11 +336,11 @@ function executeMergePrsStage(stage, stageState, run, config) {
313
336
  if (s.artifacts?.prs) prIds.push(...s.artifacts.prs);
314
337
  }
315
338
  if (prIds.length === 0) {
316
- return { status: 'completed', completedAt: ts(), output: 'No PRs to merge' };
339
+ return { status: PIPELINE_STATUS.COMPLETED, completedAt: ts(), output: 'No PRs to merge' };
317
340
  }
318
341
  // The actual merge will be handled by the PR polling/merge logic
319
342
  // We just need to track which PRs to watch
320
- return { status: 'running', artifacts: { prs: prIds } };
343
+ return { status: PIPELINE_STATUS.RUNNING, artifacts: { prs: prIds } };
321
344
  }
322
345
 
323
346
  function executeScheduleStage(stage, stageState, config) {
@@ -340,7 +363,7 @@ function executeParallelStage(stage, stageState, run, pipeline, config) {
340
363
  const subResults = {};
341
364
  for (const sub of subStages) {
342
365
  if (!run.stages[sub.id] || run.stages[sub.id].status === 'pending') {
343
- const result = executeStage(sub, run, pipeline, config);
366
+ const result = await executeStage(sub, run, pipeline, config);
344
367
  subResults[sub.id] = result;
345
368
  run.stages[sub.id] = { ...run.stages[sub.id] || {}, ...result, startedAt: ts() };
346
369
  }
@@ -560,7 +583,7 @@ function discoverPipelineWork(config) {
560
583
  stageState.status = 'failed';
561
584
  anyFailed = true;
562
585
  } else if (depsReady) {
563
- const result = executeStage(stage, activeRun, pipeline, config);
586
+ const result = await executeStage(stage, activeRun, pipeline, config);
564
587
  updateRunStage(pipeline.id, activeRun.runId, stage.id, { ...result, startedAt: ts() });
565
588
  Object.assign(stageState, result, { startedAt: ts() });
566
589
  if (result.status === 'running') anyRunning = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.356",
3
+ "version": "0.1.358",
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"