@yemi33/minions 0.1.728 → 0.1.729
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 +4 -1
- package/dashboard/js/render-pipelines.js +27 -20
- package/engine/pipeline.js +61 -3
- package/package.json +1 -1
- package/playbooks/decompose.md +4 -1
- package/playbooks/shared-rules.md +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.729 (2026-04-09)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
+
- playbook improvements — soften CAPS, add context-awareness, restructure decompose output
|
|
7
|
+
- pipeline auto-detect — skip wait/plan stages when artifacts exist
|
|
6
8
|
- replace raw status strings with WI_STATUS constants in dashboard.js
|
|
7
9
|
- Soften playbook CAPS emphasis and add context-awareness guidance
|
|
8
10
|
- Add structured completion protocol to PR-producing playbooks
|
|
@@ -13,6 +15,7 @@
|
|
|
13
15
|
- make post-verify plan archiving opt-in via autoArchive config
|
|
14
16
|
|
|
15
17
|
### Fixes
|
|
18
|
+
- resolve 3 failing unit tests on PR-661
|
|
16
19
|
- repair 3 pre-existing test failures in source-pattern tests
|
|
17
20
|
- merge master, sync dashboard-build jsFiles, bump HTML size limit
|
|
18
21
|
- resolve 4 pre-existing test failures breaking CI
|
|
@@ -401,9 +401,10 @@ function openPipelineDetail(id) {
|
|
|
401
401
|
document.getElementById('modal-body').innerHTML = html;
|
|
402
402
|
document.getElementById('modal').classList.add('open');
|
|
403
403
|
|
|
404
|
-
// Live-poll while modal is open
|
|
405
|
-
|
|
406
|
-
|
|
404
|
+
// Live-poll while modal is open — always poll (not just active runs)
|
|
405
|
+
// so the modal updates when stages advance, pipelines get triggered, etc.
|
|
406
|
+
_pipelinePollId = id;
|
|
407
|
+
if (!_pipelinePollInterval) {
|
|
407
408
|
_pipelinePollInterval = setInterval(function() {
|
|
408
409
|
if (!document.getElementById('modal')?.classList?.contains('open') || _pipelinePollId !== id) {
|
|
409
410
|
_stopPipelinePoll(); return;
|
|
@@ -426,12 +427,29 @@ function openPipelineDetail(id) {
|
|
|
426
427
|
}
|
|
427
428
|
var _pipelinePollHash = '';
|
|
428
429
|
|
|
430
|
+
/**
|
|
431
|
+
* Fetch fresh pipeline data and re-render the detail modal immediately.
|
|
432
|
+
* Used after actions (continue, trigger, abort) to avoid waiting for the 4s poll.
|
|
433
|
+
*/
|
|
434
|
+
async function _refreshPipelineDetail(id) {
|
|
435
|
+
try {
|
|
436
|
+
var res = await fetch('/api/pipelines');
|
|
437
|
+
var d = await res.json();
|
|
438
|
+
var fresh = (d.pipelines || []).find(function(x) { return x.id === id; });
|
|
439
|
+
if (fresh) {
|
|
440
|
+
_pipelinesData = _pipelinesData.map(function(x) { return x.id === id ? fresh : x; });
|
|
441
|
+
_pipelinePollHash = JSON.stringify(fresh.runs || []);
|
|
442
|
+
openPipelineDetail(id);
|
|
443
|
+
}
|
|
444
|
+
} catch (e) { /* silent — next poll will catch up */ }
|
|
445
|
+
}
|
|
446
|
+
|
|
429
447
|
async function _triggerPipeline(id, btn) {
|
|
430
448
|
if (btn) { btn.textContent = 'Starting...'; btn.style.pointerEvents = 'none'; btn.style.opacity = '0.6'; }
|
|
431
449
|
try {
|
|
432
450
|
var res = await fetch('/api/pipelines/trigger', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) });
|
|
433
451
|
var d = await res.json();
|
|
434
|
-
if (res.ok) { showToast('cmd-toast', 'Pipeline triggered: ' + (d.runId || ''), true);
|
|
452
|
+
if (res.ok) { showToast('cmd-toast', 'Pipeline triggered: ' + (d.runId || ''), true); refresh(); await _refreshPipelineDetail(id); }
|
|
435
453
|
else { if (btn) { btn.textContent = 'Run Now'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } alert('Failed: ' + (d.error || 'unknown')); }
|
|
436
454
|
} catch (e) { if (btn) { btn.textContent = 'Run Now'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } alert('Error: ' + e.message); }
|
|
437
455
|
}
|
|
@@ -444,20 +462,8 @@ async function _abortPipeline(id, btn) {
|
|
|
444
462
|
if (res.ok) {
|
|
445
463
|
var d = await res.json().catch(function() { return {}; });
|
|
446
464
|
showToast('cmd-toast', 'Pipeline aborted — ' + (d.cancelledWorkItems || 0) + ' work items cancelled', true);
|
|
447
|
-
// Replace abort button with Run Now
|
|
448
|
-
if (btn) {
|
|
449
|
-
btn.textContent = 'Run Now';
|
|
450
|
-
btn.style.color = 'var(--green)';
|
|
451
|
-
btn.style.borderColor = 'var(--green)';
|
|
452
|
-
btn.style.pointerEvents = '';
|
|
453
|
-
btn.style.opacity = '';
|
|
454
|
-
btn.onclick = function() { _triggerPipeline(id, btn); };
|
|
455
|
-
// Remove the retrigger button next to it (no longer needed)
|
|
456
|
-
var next = btn.nextElementSibling;
|
|
457
|
-
if (next && next.textContent.trim() === 'Retrigger') next.remove();
|
|
458
|
-
}
|
|
459
465
|
refresh();
|
|
460
|
-
|
|
466
|
+
await _refreshPipelineDetail(id);
|
|
461
467
|
} else { var d = await res.json().catch(function() { return {}; }); alert('Abort failed: ' + (d.error || 'unknown')); if (btn) { btn.textContent = 'Abort'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
|
|
462
468
|
} catch (e) { alert('Error: ' + e.message); if (btn) { btn.textContent = 'Abort'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
|
|
463
469
|
}
|
|
@@ -470,8 +476,8 @@ async function _retriggerPipeline(id, btn) {
|
|
|
470
476
|
var d = await res.json();
|
|
471
477
|
if (res.ok) {
|
|
472
478
|
showToast('cmd-toast', 'Pipeline retriggered: ' + (d.runId || ''), true);
|
|
473
|
-
|
|
474
|
-
|
|
479
|
+
refresh();
|
|
480
|
+
await _refreshPipelineDetail(id);
|
|
475
481
|
} else { alert('Retrigger failed: ' + (d.error || 'unknown')); if (btn) { btn.textContent = 'Retrigger'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
|
|
476
482
|
} catch (e) { alert('Error: ' + e.message); if (btn) { btn.textContent = 'Retrigger'; btn.style.pointerEvents = ''; btn.style.opacity = ''; } }
|
|
477
483
|
}
|
|
@@ -493,7 +499,8 @@ async function _continuePipeline(id, stageId, btn) {
|
|
|
493
499
|
if (res.ok) {
|
|
494
500
|
showToast('cmd-toast', 'Stage continued — dispatching next tick', true);
|
|
495
501
|
if (btn) { btn.textContent = '\u2713 Continued'; btn.style.color = 'var(--green)'; btn.style.borderColor = 'var(--green)'; btn.style.opacity = '1'; }
|
|
496
|
-
|
|
502
|
+
// Immediate refresh — no waiting for 4s poll
|
|
503
|
+
await _refreshPipelineDetail(id);
|
|
497
504
|
} else {
|
|
498
505
|
var d = await res.json().catch(function() { return {}; }); alert('Failed: ' + (d.error || 'unknown'));
|
|
499
506
|
if (btn) { btn.textContent = 'Continue'; btn.style.pointerEvents = ''; btn.style.opacity = ''; }
|
package/engine/pipeline.js
CHANGED
|
@@ -320,6 +320,17 @@ function _findExistingPlanForMeeting(meetingIds, plansDir) {
|
|
|
320
320
|
return slugMatch;
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
// Check if a PRD already exists for a given plan file (plan already converted)
|
|
324
|
+
function _findExistingPrdForPlan(planFile, prdDir) {
|
|
325
|
+
if (!fs.existsSync(prdDir)) return null;
|
|
326
|
+
const prdFiles = safeReadDir(prdDir).filter(f => f.endsWith('.json'));
|
|
327
|
+
for (const pf of prdFiles) {
|
|
328
|
+
const prd = safeJson(path.join(prdDir, pf));
|
|
329
|
+
if (prd?.source_plan === planFile) return pf;
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
|
|
323
334
|
async function executePlanStage(stage, stageState, run, config) {
|
|
324
335
|
const plansDir = path.join(__dirname, '..', 'plans');
|
|
325
336
|
if (!fs.existsSync(plansDir)) fs.mkdirSync(plansDir, { recursive: true });
|
|
@@ -334,6 +345,18 @@ async function executePlanStage(stage, stageState, run, config) {
|
|
|
334
345
|
const existingPlanFile = _findExistingPlanForMeeting(meetingIds, plansDir);
|
|
335
346
|
if (existingPlanFile) {
|
|
336
347
|
log('info', `Pipeline ${run.pipelineId}: reconciling plan stage — adopting existing plan "${existingPlanFile}"`);
|
|
348
|
+
|
|
349
|
+
// Check if a PRD already exists for this plan (skip plan-to-prd entirely)
|
|
350
|
+
const prdDir = path.join(__dirname, '..', 'prd');
|
|
351
|
+
const existingPrdFile = _findExistingPrdForPlan(existingPlanFile, prdDir);
|
|
352
|
+
if (existingPrdFile) {
|
|
353
|
+
log('info', `Pipeline ${run.pipelineId}: PRD "${existingPrdFile}" already exists for plan "${existingPlanFile}" — skipping plan-to-prd`);
|
|
354
|
+
return {
|
|
355
|
+
status: PIPELINE_STATUS.RUNNING,
|
|
356
|
+
artifacts: { plans: [existingPlanFile], workItems: [], prds: [existingPrdFile], prs: [] },
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
337
360
|
// Adopt or create plan-to-prd WI atomically under lock
|
|
338
361
|
let adoptedWiId = wiId;
|
|
339
362
|
mutateWorkItems(wiPath, workItems => {
|
|
@@ -563,12 +586,17 @@ function isStageComplete(stage, stageState, run, config) {
|
|
|
563
586
|
|
|
564
587
|
switch (stage.type) {
|
|
565
588
|
case STAGE_TYPE.TASK: {
|
|
589
|
+
// Check root + all project work-items.json (WIs may be moved to project paths)
|
|
566
590
|
const wiPath = path.join(__dirname, '..', 'work-items.json');
|
|
567
591
|
const workItems = safeJson(wiPath) || [];
|
|
592
|
+
const allProjectWi = shared.getProjects(config).reduce((acc, p) => {
|
|
593
|
+
return acc.concat(safeJson(shared.projectWorkItemsPath(p)) || []);
|
|
594
|
+
}, []);
|
|
595
|
+
const all = [...workItems, ...allProjectWi];
|
|
568
596
|
const ids = artifacts.workItems || [];
|
|
569
597
|
if (ids.length === 0) return false;
|
|
570
598
|
return ids.every(id => {
|
|
571
|
-
const wi =
|
|
599
|
+
const wi = all.find(w => w.id === id);
|
|
572
600
|
return !wi || wi.status === WI_STATUS.DONE || wi.status === WI_STATUS.FAILED; // missing = treat as done
|
|
573
601
|
});
|
|
574
602
|
}
|
|
@@ -725,8 +753,10 @@ async function discoverPipelineWork(config) {
|
|
|
725
753
|
if (stage.type === STAGE_TYPE.TASK) {
|
|
726
754
|
const wiPath = path.join(__dirname, '..', 'work-items.json');
|
|
727
755
|
const workItems = safeJson(wiPath) || [];
|
|
756
|
+
const projWi = shared.getProjects(config).reduce((acc, p) => acc.concat(safeJson(shared.projectWorkItemsPath(p)) || []), []);
|
|
757
|
+
const allWi = [...workItems, ...projWi];
|
|
728
758
|
output = (stageState.artifacts?.workItems || []).map(id => {
|
|
729
|
-
const wi =
|
|
759
|
+
const wi = allWi.find(w => w.id === id);
|
|
730
760
|
return wi?.resultSummary || wi?.title || id;
|
|
731
761
|
}).join('\n');
|
|
732
762
|
} else if (stage.type === STAGE_TYPE.MEETING) {
|
|
@@ -771,7 +801,34 @@ async function discoverPipelineWork(config) {
|
|
|
771
801
|
}
|
|
772
802
|
}
|
|
773
803
|
|
|
774
|
-
if (stageState.status === PIPELINE_STATUS.WAITING_HUMAN) {
|
|
804
|
+
if (stageState.status === PIPELINE_STATUS.WAITING_HUMAN) {
|
|
805
|
+
// Auto-complete wait stages when the preceding meeting already produced a plan
|
|
806
|
+
// Common pattern: meeting → wait → plan — if plan exists, nothing to wait for
|
|
807
|
+
if (stage.type === STAGE_TYPE.WAIT) {
|
|
808
|
+
const nextPlanStage = stages.find(s =>
|
|
809
|
+
s.type === STAGE_TYPE.PLAN && (s.dependsOn || []).includes(stage.id)
|
|
810
|
+
);
|
|
811
|
+
if (nextPlanStage) {
|
|
812
|
+
const meetingIds = _findMeetingsInRun(activeRun);
|
|
813
|
+
if (meetingIds.length > 0) {
|
|
814
|
+
const plansDir = path.join(__dirname, '..', 'plans');
|
|
815
|
+
if (fs.existsSync(plansDir)) {
|
|
816
|
+
const existingPlan = _findExistingPlanForMeeting(meetingIds, plansDir);
|
|
817
|
+
if (existingPlan) {
|
|
818
|
+
updateRunStage(pipeline.id, activeRun.runId, stage.id, {
|
|
819
|
+
status: PIPELINE_STATUS.COMPLETED, completedAt: ts(),
|
|
820
|
+
output: `Auto-completed: plan "${existingPlan}" already exists for meeting`,
|
|
821
|
+
});
|
|
822
|
+
stageState.status = PIPELINE_STATUS.COMPLETED;
|
|
823
|
+
log('info', `Pipeline ${pipeline.id}: wait stage ${stage.id} auto-completed — plan "${existingPlan}" already exists`);
|
|
824
|
+
continue; // re-evaluate as completed on next iteration
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
allComplete = false; continue;
|
|
831
|
+
}
|
|
775
832
|
|
|
776
833
|
// Check if pending stage is ready to start
|
|
777
834
|
if (stageState.status === PIPELINE_STATUS.PENDING) {
|
|
@@ -852,4 +909,5 @@ module.exports = {
|
|
|
852
909
|
getPipelineRuns, getActiveRun, startRun, updateRunStage, completeRun,
|
|
853
910
|
discoverPipelineWork,
|
|
854
911
|
evaluateCondition, // exported for testing
|
|
912
|
+
_findMeetingsInRun, _findExistingPlanForMeeting, _findExistingPrdForPlan, // exported for testing
|
|
855
913
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.729",
|
|
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"
|
package/playbooks/decompose.md
CHANGED
|
@@ -48,10 +48,13 @@ Write the decomposition result as a JSON code block in your response:
|
|
|
48
48
|
{
|
|
49
49
|
"id": "{{item_id}}-a",
|
|
50
50
|
"name": "Short descriptive name",
|
|
51
|
+
"objective": "One-sentence goal: what this sub-task achieves.",
|
|
51
52
|
"description": "What to build, where, and how. Be specific enough that an engineer can implement without further exploration.",
|
|
52
53
|
"estimated_complexity": "small|medium",
|
|
53
54
|
"depends_on": [],
|
|
54
|
-
"acceptance_criteria": ["Criterion 1", "Criterion 2"]
|
|
55
|
+
"acceptance_criteria": ["Criterion 1", "Criterion 2"],
|
|
56
|
+
"expected_output": "What done looks like — the artifact produced (e.g., a PR adding X, a new file at Y, a config change).",
|
|
57
|
+
"scope_boundaries": ["Out-of-scope item 1", "Out-of-scope item 2"]
|
|
55
58
|
}
|
|
56
59
|
]
|
|
57
60
|
}
|
|
@@ -4,6 +4,8 @@ Your context window may be compacted or summarized mid-task by Claude's automati
|
|
|
4
4
|
|
|
5
5
|
## Engine Rules (apply to all tasks)
|
|
6
6
|
|
|
7
|
+
**Context compaction:** Your context window may be compacted mid-task by Claude's infrastructure. If you notice your earlier conversation history appears truncated or summarized, this is normal and expected. Do not interpret compaction as a signal to stop early or wrap up. Continue working toward your task objective — all relevant instructions and state remain available.
|
|
8
|
+
|
|
7
9
|
- Do NOT write to `agents/*/status.json` — the engine manages your status automatically.
|
|
8
10
|
- Do NOT remove worktrees — the engine handles cleanup automatically.
|
|
9
11
|
- Do NOT checkout branches in the main working tree — use worktrees or `git diff`/`git show`.
|