@yemi33/minions 0.1.643 → 0.1.645
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 +5 -1
- package/dashboard/js/render-meetings.js +17 -7
- package/dashboard/styles.css +1 -1
- package/engine/pipeline.js +77 -25
- package/engine.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.645 (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
|
|
|
9
|
+
### Fixes
|
|
10
|
+
- use startsWith for inbox note matching to prevent false matches
|
|
11
|
+
|
|
8
12
|
## 0.1.642 (2026-04-09)
|
|
9
13
|
|
|
10
14
|
### Features
|
|
@@ -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,91 @@ 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, planSlug, plansDir) {
|
|
300
|
+
const files = safeReadDir(plansDir).filter(f => f.endsWith('.md'));
|
|
301
|
+
const slugPrefix = planSlug.slice(0, 30);
|
|
302
|
+
let slugMatch = null;
|
|
303
|
+
for (const file of files) {
|
|
304
|
+
// Fallback: title-slug filename match (checked first to avoid unnecessary reads)
|
|
305
|
+
if (!slugMatch && file.startsWith(slugPrefix)) slugMatch = file;
|
|
306
|
+
// Primary: explicit Source Meeting header (written by dashboard /api/plans/create)
|
|
307
|
+
const content = safeRead(path.join(plansDir, file));
|
|
308
|
+
if (!content) continue;
|
|
309
|
+
for (const mid of meetingIds) {
|
|
310
|
+
if (content.includes('**Source Meeting:** ' + mid)) return file;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return slugMatch;
|
|
314
|
+
}
|
|
315
|
+
|
|
287
316
|
async function executePlanStage(stage, stageState, run, config) {
|
|
288
|
-
// Create a plan .md file from the stage config + previous stage output
|
|
289
317
|
const plansDir = path.join(__dirname, '..', 'plans');
|
|
290
318
|
if (!fs.existsSync(plansDir)) fs.mkdirSync(plansDir, { recursive: true });
|
|
291
319
|
|
|
292
320
|
const slug = (stage.title || 'pipeline-plan').toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 50);
|
|
293
|
-
const
|
|
294
|
-
const
|
|
321
|
+
const wiPath = path.join(__dirname, '..', 'work-items.json');
|
|
322
|
+
const wiId = `PL-${run.runId.slice(4, 12)}-${stage.id}-prd`;
|
|
295
323
|
|
|
296
|
-
//
|
|
324
|
+
// ── Reconciliation: check if a plan already exists for a meeting in this run ──
|
|
325
|
+
const meetingIds = _findMeetingsInRun(run);
|
|
326
|
+
if (meetingIds.length > 0) {
|
|
327
|
+
const existingPlanFile = _findExistingPlanForMeeting(meetingIds, slug, plansDir);
|
|
328
|
+
if (existingPlanFile) {
|
|
329
|
+
log('info', `Pipeline ${run.pipelineId}: reconciling plan stage — adopting existing plan "${existingPlanFile}"`);
|
|
330
|
+
// Adopt or create plan-to-prd WI atomically under lock
|
|
331
|
+
let adoptedWiId = wiId;
|
|
332
|
+
mutateWorkItems(wiPath, workItems => {
|
|
333
|
+
const existing = workItems.find(w => w.type === WORK_TYPE.PLAN_TO_PRD && w.planFile === existingPlanFile);
|
|
334
|
+
if (existing) {
|
|
335
|
+
existing._pipelineRun = run.runId;
|
|
336
|
+
existing._pipelineStage = stage.id;
|
|
337
|
+
adoptedWiId = existing.id;
|
|
338
|
+
} else if (!workItems.some(w => w.id === wiId)) {
|
|
339
|
+
workItems.push({
|
|
340
|
+
id: wiId, title: `Convert plan to PRD: ${existingPlanFile}`,
|
|
341
|
+
type: WORK_TYPE.PLAN_TO_PRD, priority: 'high', status: WI_STATUS.PENDING,
|
|
342
|
+
planFile: existingPlanFile, created: ts(), createdBy: 'pipeline:' + run.pipelineId,
|
|
343
|
+
branch: `pipeline/${run.pipelineId}/${stage.id}`, _pipelineRun: run.runId, _pipelineStage: stage.id,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
return {
|
|
348
|
+
status: PIPELINE_STATUS.RUNNING,
|
|
349
|
+
artifacts: { plans: [existingPlanFile], workItems: [adoptedWiId], prds: [], prs: [] },
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ── No existing plan — build meeting context from ALL run stages (not just direct deps) ──
|
|
297
355
|
let meetingContext = '';
|
|
356
|
+
for (const mid of meetingIds) {
|
|
357
|
+
try {
|
|
358
|
+
const mtg = safeJson(path.join(__dirname, '..', 'meetings', mid + '.json'));
|
|
359
|
+
if (mtg) {
|
|
360
|
+
const transcript = (mtg.transcript || []).map(t =>
|
|
361
|
+
'### ' + (t.agent || 'agent') + ' (' + (t.type || '') + ', Round ' + (t.round || '?') + ')\n\n' + (t.content || '')
|
|
362
|
+
).join('\n\n---\n\n');
|
|
363
|
+
meetingContext += '# Meeting: ' + (mtg.title || mid) + '\n\n**Agenda:** ' + (mtg.agenda || '') + '\n\n' + transcript + '\n\n';
|
|
364
|
+
}
|
|
365
|
+
} catch (e) { log('warn', `Pipeline plan: failed to read meeting ${mid}: ${e.message}`); }
|
|
366
|
+
}
|
|
367
|
+
// Also include direct dep output (for non-meeting stages)
|
|
298
368
|
if (stage.dependsOn) {
|
|
299
369
|
for (const depId of stage.dependsOn) {
|
|
300
370
|
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) {
|
|
371
|
+
if (depStage?.output && !depStage.artifacts?.meetings?.length) {
|
|
320
372
|
meetingContext += '## From: ' + depId + '\n\n' + depStage.output + '\n\n';
|
|
321
373
|
}
|
|
322
374
|
}
|
|
@@ -350,11 +402,11 @@ async function executePlanStage(stage, stageState, run, config) {
|
|
|
350
402
|
if (stage.description) content += stage.description + '\n';
|
|
351
403
|
}
|
|
352
404
|
|
|
405
|
+
const filename = `${slug}-${dateStamp()}.md`;
|
|
406
|
+
const filePath = shared.uniquePath(path.join(plansDir, filename));
|
|
353
407
|
safeWrite(filePath, content);
|
|
354
408
|
|
|
355
409
|
// 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
410
|
mutateWorkItems(wiPath, workItems => {
|
|
359
411
|
if (!workItems.some(w => w.id === wiId)) {
|
|
360
412
|
workItems.push({
|
package/engine.js
CHANGED
|
@@ -795,7 +795,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
795
795
|
const _artToday = shared.dateStamp();
|
|
796
796
|
const _artInboxDir = path.join(MINIONS_DIR, 'notes', 'inbox');
|
|
797
797
|
let _artNotes = [];
|
|
798
|
-
try { _artNotes = shared.safeReadDir(_artInboxDir).filter(f => f.
|
|
798
|
+
try { _artNotes = shared.safeReadDir(_artInboxDir).filter(f => f.startsWith(agentId + '-') && f.includes(_artToday)); } catch {}
|
|
799
799
|
|
|
800
800
|
mutateJsonFileLocked(artWiPath, data => {
|
|
801
801
|
if (!Array.isArray(data)) return data;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.645",
|
|
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"
|