@yemi33/minions 0.1.644 → 0.1.646
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 +3 -1
- package/dashboard/js/render-meetings.js +17 -7
- package/dashboard/styles.css +1 -1
- package/engine/pipeline.js +84 -25
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.646 (2026-04-09)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
+
- pipeline plan stage reconciliation and UX fixes
|
|
6
7
|
- track notes, plans, and PRDs as work item artifacts
|
|
7
8
|
|
|
8
9
|
### Fixes
|
|
10
|
+
- pipeline plan reconciliation slug match used wrong naming convention
|
|
9
11
|
- use startsWith for inbox note matching to prevent false matches
|
|
10
12
|
|
|
11
13
|
## 0.1.642 (2026-04-09)
|
|
@@ -380,12 +380,22 @@ function _viewPlanWithBack(file, meetingId) {
|
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
function _findLinkedPlan(meeting) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
383
|
+
var plans = window._lastStatus?.plans || [];
|
|
384
|
+
// Check 1: regex in conclusion text (agent may reference plan path)
|
|
385
|
+
if (meeting?.conclusion?.content) {
|
|
386
|
+
var match = meeting.conclusion.content.match(/plans\/([\w-]+\.md)/);
|
|
387
|
+
if (match) {
|
|
388
|
+
var file = match[1];
|
|
389
|
+
return plans.find(function(p) { return p.file === file; }) || { file, summary: file };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// Check 2: title-slug filename match (dashboard naming convention: "meeting-follow-up-{title}")
|
|
393
|
+
if (meeting?.title) {
|
|
394
|
+
var titleSlug = ('Meeting follow-up: ' + meeting.title).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 50);
|
|
395
|
+
var found = plans.find(function(p) { return p.file && p.file.startsWith(titleSlug); });
|
|
396
|
+
if (found) return found;
|
|
397
|
+
}
|
|
398
|
+
return null;
|
|
389
399
|
}
|
|
390
400
|
|
|
391
401
|
async function _createPlanFromMeeting(id, btn) {
|
|
@@ -435,7 +445,7 @@ async function _createPlanFromMeeting(id, btn) {
|
|
|
435
445
|
const title = 'Meeting follow-up: ' + (m.title || id);
|
|
436
446
|
const planRes = await fetch('/api/plans/create', {
|
|
437
447
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
438
|
-
body: JSON.stringify({ title, content: planContent })
|
|
448
|
+
body: JSON.stringify({ title, content: planContent, meetingId: id })
|
|
439
449
|
});
|
|
440
450
|
const planData = await planRes.json();
|
|
441
451
|
if (planRes.ok && planData.ok) {
|
package/dashboard/styles.css
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
.timestamp { color: var(--muted); font-size: var(--text-md); font-variant-numeric: tabular-nums; }
|
|
40
40
|
|
|
41
41
|
.layout { display: none; } /* Replaced by page-layout */
|
|
42
|
-
section { padding: var(--space-8) var(--space-9); border-bottom: 1px solid var(--border); overflow:
|
|
42
|
+
section { padding: var(--space-8) var(--space-9); border-bottom: 1px solid var(--border); overflow: visible; min-width: 0; }
|
|
43
43
|
|
|
44
44
|
/* Sidebar navigation */
|
|
45
45
|
.page-layout { display: flex; flex: 1; min-height: 0; overflow: hidden; }
|
package/engine/pipeline.js
CHANGED
|
@@ -284,39 +284,98 @@ function executeMeetingStage(stage, stageState, run, config) {
|
|
|
284
284
|
return { status: PIPELINE_STATUS.RUNNING, artifacts: { meetings: createdIds } };
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
// Find meeting artifacts from any stage in the run (not just direct deps)
|
|
288
|
+
function _findMeetingsInRun(run) {
|
|
289
|
+
const meetings = [];
|
|
290
|
+
for (const [, stageState] of Object.entries(run.stages || {})) {
|
|
291
|
+
for (const mid of (stageState.artifacts?.meetings || [])) {
|
|
292
|
+
if (!meetings.includes(mid)) meetings.push(mid);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return meetings;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Check if a plan already exists for a given meeting (created manually via dashboard)
|
|
299
|
+
function _findExistingPlanForMeeting(meetingIds, plansDir) {
|
|
300
|
+
const files = safeReadDir(plansDir).filter(f => f.endsWith('.md'));
|
|
301
|
+
// Build slug prefixes for both pipeline and dashboard naming conventions
|
|
302
|
+
const slugPrefixes = [];
|
|
303
|
+
for (const mid of meetingIds) {
|
|
304
|
+
const mtg = safeJson(path.join(__dirname, '..', 'meetings', mid + '.json'));
|
|
305
|
+
if (mtg?.title) {
|
|
306
|
+
// Dashboard convention: "Meeting follow-up: {title}" → slug
|
|
307
|
+
const dashSlug = ('meeting-follow-up-' + mtg.title).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 50);
|
|
308
|
+
slugPrefixes.push(dashSlug.slice(0, 30));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
let slugMatch = null;
|
|
312
|
+
for (const file of files) {
|
|
313
|
+
if (!slugMatch && slugPrefixes.some(p => file.startsWith(p))) slugMatch = file;
|
|
314
|
+
const content = safeRead(path.join(plansDir, file));
|
|
315
|
+
if (!content) continue;
|
|
316
|
+
for (const mid of meetingIds) {
|
|
317
|
+
if (content.includes('**Source Meeting:** ' + mid)) return file;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return slugMatch;
|
|
321
|
+
}
|
|
322
|
+
|
|
287
323
|
async function executePlanStage(stage, stageState, run, config) {
|
|
288
|
-
// Create a plan .md file from the stage config + previous stage output
|
|
289
324
|
const plansDir = path.join(__dirname, '..', 'plans');
|
|
290
325
|
if (!fs.existsSync(plansDir)) fs.mkdirSync(plansDir, { recursive: true });
|
|
291
326
|
|
|
292
327
|
const slug = (stage.title || 'pipeline-plan').toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 50);
|
|
293
|
-
const
|
|
294
|
-
const
|
|
328
|
+
const wiPath = path.join(__dirname, '..', 'work-items.json');
|
|
329
|
+
const wiId = `PL-${run.runId.slice(4, 12)}-${stage.id}-prd`;
|
|
295
330
|
|
|
296
|
-
//
|
|
331
|
+
// ── Reconciliation: check if a plan already exists for a meeting in this run ──
|
|
332
|
+
const meetingIds = _findMeetingsInRun(run);
|
|
333
|
+
if (meetingIds.length > 0) {
|
|
334
|
+
const existingPlanFile = _findExistingPlanForMeeting(meetingIds, plansDir);
|
|
335
|
+
if (existingPlanFile) {
|
|
336
|
+
log('info', `Pipeline ${run.pipelineId}: reconciling plan stage — adopting existing plan "${existingPlanFile}"`);
|
|
337
|
+
// Adopt or create plan-to-prd WI atomically under lock
|
|
338
|
+
let adoptedWiId = wiId;
|
|
339
|
+
mutateWorkItems(wiPath, workItems => {
|
|
340
|
+
const existing = workItems.find(w => w.type === WORK_TYPE.PLAN_TO_PRD && w.planFile === existingPlanFile);
|
|
341
|
+
if (existing) {
|
|
342
|
+
existing._pipelineRun = run.runId;
|
|
343
|
+
existing._pipelineStage = stage.id;
|
|
344
|
+
adoptedWiId = existing.id;
|
|
345
|
+
} else if (!workItems.some(w => w.id === wiId)) {
|
|
346
|
+
workItems.push({
|
|
347
|
+
id: wiId, title: `Convert plan to PRD: ${existingPlanFile}`,
|
|
348
|
+
type: WORK_TYPE.PLAN_TO_PRD, priority: 'high', status: WI_STATUS.PENDING,
|
|
349
|
+
planFile: existingPlanFile, created: ts(), createdBy: 'pipeline:' + run.pipelineId,
|
|
350
|
+
branch: `pipeline/${run.pipelineId}/${stage.id}`, _pipelineRun: run.runId, _pipelineStage: stage.id,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
return {
|
|
355
|
+
status: PIPELINE_STATUS.RUNNING,
|
|
356
|
+
artifacts: { plans: [existingPlanFile], workItems: [adoptedWiId], prds: [], prs: [] },
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ── No existing plan — build meeting context from ALL run stages (not just direct deps) ──
|
|
297
362
|
let meetingContext = '';
|
|
363
|
+
for (const mid of meetingIds) {
|
|
364
|
+
try {
|
|
365
|
+
const mtg = safeJson(path.join(__dirname, '..', 'meetings', mid + '.json'));
|
|
366
|
+
if (mtg) {
|
|
367
|
+
const transcript = (mtg.transcript || []).map(t =>
|
|
368
|
+
'### ' + (t.agent || 'agent') + ' (' + (t.type || '') + ', Round ' + (t.round || '?') + ')\n\n' + (t.content || '')
|
|
369
|
+
).join('\n\n---\n\n');
|
|
370
|
+
meetingContext += '# Meeting: ' + (mtg.title || mid) + '\n\n**Agenda:** ' + (mtg.agenda || '') + '\n\n' + transcript + '\n\n';
|
|
371
|
+
}
|
|
372
|
+
} catch (e) { log('warn', `Pipeline plan: failed to read meeting ${mid}: ${e.message}`); }
|
|
373
|
+
}
|
|
374
|
+
// Also include direct dep output (for non-meeting stages)
|
|
298
375
|
if (stage.dependsOn) {
|
|
299
376
|
for (const depId of stage.dependsOn) {
|
|
300
377
|
const depStage = run.stages[depId];
|
|
301
|
-
if (!depStage)
|
|
302
|
-
|
|
303
|
-
const meetingId = depStage.artifacts?.meetings?.[0];
|
|
304
|
-
if (meetingId) {
|
|
305
|
-
try {
|
|
306
|
-
const mtgPath = path.join(__dirname, '..', 'meetings', meetingId + '.json');
|
|
307
|
-
const mtg = safeJson(mtgPath);
|
|
308
|
-
if (mtg) {
|
|
309
|
-
// Build full meeting document (same as dashboard "Create Plan from Meeting")
|
|
310
|
-
const transcript = (mtg.transcript || []).map(t =>
|
|
311
|
-
'### ' + (t.agent || 'agent') + ' (' + (t.type || '') + ', Round ' + (t.round || '?') + ')\n\n' + (t.content || '')
|
|
312
|
-
).join('\n\n---\n\n');
|
|
313
|
-
meetingContext += '# Meeting: ' + (mtg.title || depId) + '\n\n**Agenda:** ' + (mtg.agenda || '') + '\n\n' + transcript + '\n\n';
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
} catch { /* fall through */ }
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (depStage.output) {
|
|
378
|
+
if (depStage?.output && !depStage.artifacts?.meetings?.length) {
|
|
320
379
|
meetingContext += '## From: ' + depId + '\n\n' + depStage.output + '\n\n';
|
|
321
380
|
}
|
|
322
381
|
}
|
|
@@ -350,11 +409,11 @@ async function executePlanStage(stage, stageState, run, config) {
|
|
|
350
409
|
if (stage.description) content += stage.description + '\n';
|
|
351
410
|
}
|
|
352
411
|
|
|
412
|
+
const filename = `${slug}-${dateStamp()}.md`;
|
|
413
|
+
const filePath = shared.uniquePath(path.join(plansDir, filename));
|
|
353
414
|
safeWrite(filePath, content);
|
|
354
415
|
|
|
355
416
|
// Create plan-to-prd work item — atomic write to prevent race with dispatch status updates
|
|
356
|
-
const wiPath = path.join(__dirname, '..', 'work-items.json');
|
|
357
|
-
const wiId = `PL-${run.runId.slice(4, 12)}-${stage.id}-prd`;
|
|
358
417
|
mutateWorkItems(wiPath, workItems => {
|
|
359
418
|
if (!workItems.some(w => w.id === wiId)) {
|
|
360
419
|
workItems.push({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.646",
|
|
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"
|