@yemi33/minions 0.1.155 → 0.1.156
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 +13 -0
- package/dashboard/js/render-plans.js +44 -4
- package/dashboard/js/render-prd.js +1 -1
- package/engine/shared.js +50 -1
- package/engine.js +17 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.156 (2026-04-02)
|
|
4
|
+
|
|
5
|
+
### Engine
|
|
6
|
+
- engine.js
|
|
7
|
+
- engine/shared.js
|
|
8
|
+
|
|
9
|
+
### Dashboard
|
|
10
|
+
- dashboard/js/render-plans.js
|
|
11
|
+
- dashboard/js/render-prd.js
|
|
12
|
+
|
|
13
|
+
### Other
|
|
14
|
+
- test/unit.test.js
|
|
15
|
+
|
|
3
16
|
## 0.1.155 (2026-04-02)
|
|
4
17
|
|
|
5
18
|
### Engine
|
|
@@ -66,9 +66,7 @@ function derivePlanStatus(prdFile, mdFile, prdJsonStatus, workItems) {
|
|
|
66
66
|
const implementWi = wi.filter(w => w.type !== 'plan-to-prd' && w.type !== 'verify');
|
|
67
67
|
const hasPendingPrd = wi.some(w => w.type === 'plan-to-prd' && (w.status === 'pending' || w.status === 'dispatched'));
|
|
68
68
|
const hasActiveWork = implementWi.some(w => w.status === 'pending' || w.status === 'dispatched');
|
|
69
|
-
const allDone = implementWi.length > 0 && implementWi.every(w =>
|
|
70
|
-
w.status === 'done'
|
|
71
|
-
);
|
|
69
|
+
const allDone = implementWi.length > 0 && implementWi.every(w => w.status === 'done');
|
|
72
70
|
const hasFailed = implementWi.some(w => w.status === 'failed');
|
|
73
71
|
|
|
74
72
|
// User-set statuses take priority when no work has started
|
|
@@ -304,6 +302,48 @@ function openArchivedPlansModal() {
|
|
|
304
302
|
document.getElementById('modal').classList.add('open');
|
|
305
303
|
}
|
|
306
304
|
|
|
305
|
+
// Disable all PRD action buttons to prevent double-clicks
|
|
306
|
+
function qaDisablePrdButtons() {
|
|
307
|
+
const container = document.getElementById('qa-generate-prd-btn');
|
|
308
|
+
if (container) container.querySelectorAll('button').forEach(b => { b.disabled = true; b.style.opacity = '0.5'; });
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Show plan version action buttons (Run alongside / Replace / Just save)
|
|
312
|
+
function showPlanVersionActions(thread, newFile, originalFile) {
|
|
313
|
+
const esc = newFile.replace(/'/g, "\\'");
|
|
314
|
+
// Look up existing PRD for the original plan's project
|
|
315
|
+
const allPlans = window._lastStatus?.plans || [];
|
|
316
|
+
const origPlan = allPlans.find(p => p.file === originalFile);
|
|
317
|
+
const project = origPlan?.project || '';
|
|
318
|
+
const existingPrd = allPlans.find(p => p.file.endsWith('.json') && p.project === project && p.status !== 'completed');
|
|
319
|
+
|
|
320
|
+
const btn = document.createElement('div');
|
|
321
|
+
btn.id = 'qa-generate-prd-btn';
|
|
322
|
+
btn.style.cssText = 'margin:8px 0;padding:8px 12px;background:rgba(63,185,80,0.1);border:1px solid rgba(63,185,80,0.3);border-radius:6px;display:flex;flex-wrap:wrap;align-items:center;gap:8px';
|
|
323
|
+
|
|
324
|
+
if (existingPrd) {
|
|
325
|
+
btn.innerHTML = '<span style="color:var(--green);font-weight:600;font-size:12px;width:100%">New plan version created — existing PRD running</span>' +
|
|
326
|
+
'<button onclick="qaNewPrd(\'' + esc + '\')" style="background:var(--green);color:#fff;border:none;border-radius:4px;padding:4px 12px;font-size:11px;font-weight:600;cursor:pointer" title="Execute this plan as a separate PRD alongside the current one">Run alongside</button>' +
|
|
327
|
+
'<button onclick="qaReplacePrd(\'' + esc + '\')" style="background:var(--orange);color:#fff;border:none;border-radius:4px;padding:4px 12px;font-size:11px;font-weight:600;cursor:pointer" title="Pause existing PRD, clean pending items, execute this plan instead">Replace old PRD</button>' +
|
|
328
|
+
'<button onclick="qaJustSave(this)" style="background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:4px 12px;font-size:11px;cursor:pointer" title="Keep the new version saved without dispatching any work">Just save</button>' +
|
|
329
|
+
'<span style="color:var(--muted);font-size:10px;width:100%">Run alongside keeps current work going. Replace pauses it and starts fresh.</span>';
|
|
330
|
+
} else {
|
|
331
|
+
btn.innerHTML = '<span style="color:var(--green);font-weight:600;font-size:12px;width:100%">New plan version created</span>' +
|
|
332
|
+
'<button onclick="qaNewPrd(\'' + esc + '\')" style="background:var(--green);color:#fff;border:none;border-radius:4px;padding:4px 12px;font-size:11px;font-weight:600;cursor:pointer">Execute plan</button>' +
|
|
333
|
+
'<button onclick="qaJustSave(this)" style="background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:4px 12px;font-size:11px;cursor:pointer">Just save</button>' +
|
|
334
|
+
'<span style="color:var(--muted);font-size:10px">Execute dispatches an agent to create PRD items from this plan</span>';
|
|
335
|
+
}
|
|
336
|
+
// Remove any previous action buttons
|
|
337
|
+
const old = thread.querySelector('#qa-generate-prd-btn');
|
|
338
|
+
if (old) old.remove();
|
|
339
|
+
thread.appendChild(btn);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function qaJustSave(el) {
|
|
343
|
+
const container = el.closest('#qa-generate-prd-btn');
|
|
344
|
+
if (container) container.innerHTML = '<span style="color:var(--muted);font-size:11px">Saved. No work dispatched.</span>';
|
|
345
|
+
}
|
|
346
|
+
|
|
307
347
|
async function planExecute(file, project, btn) {
|
|
308
348
|
if (btn) { btn.textContent = 'Executing...'; btn.disabled = true; btn.style.color = 'var(--blue)'; }
|
|
309
349
|
try {
|
|
@@ -694,4 +734,4 @@ async function planUnarchive(file, btn) {
|
|
|
694
734
|
} catch (e) { resetBtn(); alert('Error: ' + e.message); }
|
|
695
735
|
}
|
|
696
736
|
|
|
697
|
-
window.MinionsPlans = { openCreatePlanModal, refreshPlans, derivePlanStatus, renderPlans, openArchivedPlansModal, planExecute, planSubmitRevise, planShowRevise, planHideRevise, planView, planApprove, planArchive, planUnarchive, planDelete, planPause, planReject, planDiscuss, planOpenInDocChat, planRegeneratePRD, openVerifyGuide, triggerVerify };
|
|
737
|
+
window.MinionsPlans = { openCreatePlanModal, refreshPlans, derivePlanStatus, renderPlans, openArchivedPlansModal, qaDisablePrdButtons, showPlanVersionActions, qaJustSave, planExecute, planSubmitRevise, planShowRevise, planHideRevise, planView, planApprove, planArchive, planUnarchive, planDelete, planPause, planReject, planDiscuss, planOpenInDocChat, planRegeneratePRD, openVerifyGuide, triggerVerify };
|
|
@@ -116,7 +116,7 @@ function renderPrdProgress(prog) {
|
|
|
116
116
|
'failed': 'background:rgba(248,81,73,0.15);color:var(--red)',
|
|
117
117
|
'paused': 'background:rgba(139,148,158,0.15);color:var(--muted)',
|
|
118
118
|
};
|
|
119
|
-
const labels = { 'done': 'DONE', 'in-progress': 'WIP', 'failed': 'FAIL', 'paused': 'PAUSED', 'missing': '
|
|
119
|
+
const labels = { 'done': 'DONE', 'in-progress': 'WIP', 'failed': 'FAIL', 'paused': 'PAUSED', 'missing': '\u2014' };
|
|
120
120
|
const style = styles[s] || 'background:var(--surface);color:var(--muted)';
|
|
121
121
|
const label = labels[s] || '—';
|
|
122
122
|
return '<span style="font-size:9px;font-weight:700;padding:2px 6px;border-radius:3px;letter-spacing:0.5px;white-space:nowrap;' + style + '">' + label + '</span>';
|
package/engine/shared.js
CHANGED
|
@@ -501,7 +501,27 @@ function parseSkillFrontmatter(content, filename) {
|
|
|
501
501
|
// Never touched by polling loops — only written when a PR is first linked to a PRD item.
|
|
502
502
|
|
|
503
503
|
function getPrLinks() {
|
|
504
|
-
|
|
504
|
+
// Derive from PR.prdItems (single source of truth) + legacy pr-links.json as fallback
|
|
505
|
+
const links = {};
|
|
506
|
+
try {
|
|
507
|
+
const projects = getProjects();
|
|
508
|
+
for (const project of projects) {
|
|
509
|
+
const prs = safeJson(projectPrPath(project)) || [];
|
|
510
|
+
for (const pr of prs) {
|
|
511
|
+
for (const itemId of (pr.prdItems || [])) {
|
|
512
|
+
if (!links[pr.id]) links[pr.id] = itemId;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
} catch { /* optional */ }
|
|
517
|
+
// Merge legacy pr-links.json for items not yet in PR.prdItems
|
|
518
|
+
try {
|
|
519
|
+
const legacy = JSON.parse(require('fs').readFileSync(PR_LINKS_PATH, 'utf8'));
|
|
520
|
+
for (const [prId, itemId] of Object.entries(legacy)) {
|
|
521
|
+
if (!links[prId]) links[prId] = itemId;
|
|
522
|
+
}
|
|
523
|
+
} catch { /* optional */ }
|
|
524
|
+
return links;
|
|
505
525
|
}
|
|
506
526
|
|
|
507
527
|
function addPrLink(prId, itemId) {
|
|
@@ -512,6 +532,33 @@ function addPrLink(prId, itemId) {
|
|
|
512
532
|
safeWrite(PR_LINKS_PATH, links);
|
|
513
533
|
}
|
|
514
534
|
|
|
535
|
+
/**
|
|
536
|
+
* Locked mutation of a project's pull-requests.json.
|
|
537
|
+
* Single source of truth for PR data including prdItems links.
|
|
538
|
+
*/
|
|
539
|
+
function mutatePrs(project, mutateFn) {
|
|
540
|
+
const prPath = projectPrPath(project);
|
|
541
|
+
return mutateJsonFileLocked(prPath, (prs) => {
|
|
542
|
+
return mutateFn(Array.isArray(prs) ? prs : []);
|
|
543
|
+
}, { defaultValue: [] });
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Link a PR to a work item via PR.prdItems (single source of truth).
|
|
548
|
+
* Uses file-locked mutation to prevent race conditions.
|
|
549
|
+
*/
|
|
550
|
+
function linkPrToItem(project, prId, itemId) {
|
|
551
|
+
if (!prId || !itemId) return;
|
|
552
|
+
mutatePrs(project, (prs) => {
|
|
553
|
+
const pr = prs.find(p => p.id === prId);
|
|
554
|
+
if (pr) {
|
|
555
|
+
pr.prdItems = Array.isArray(pr.prdItems) ? pr.prdItems : [];
|
|
556
|
+
if (!pr.prdItems.includes(itemId)) pr.prdItems.push(itemId);
|
|
557
|
+
}
|
|
558
|
+
return prs;
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
|
|
515
562
|
module.exports = {
|
|
516
563
|
MINIONS_DIR,
|
|
517
564
|
PR_LINKS_PATH,
|
|
@@ -549,6 +596,8 @@ module.exports = {
|
|
|
549
596
|
projectPrPath,
|
|
550
597
|
getPrLinks,
|
|
551
598
|
addPrLink,
|
|
599
|
+
mutatePrs,
|
|
600
|
+
linkPrToItem,
|
|
552
601
|
nextWorkItemId,
|
|
553
602
|
getAdoOrgBase,
|
|
554
603
|
sanitizePath,
|
package/engine.js
CHANGED
|
@@ -307,7 +307,7 @@ function spawnAgent(dispatchItem, config) {
|
|
|
307
307
|
|
|
308
308
|
if (isSharedBranch) {
|
|
309
309
|
log('info', `Creating worktree for shared branch: ${worktreePath} on ${branchName}`);
|
|
310
|
-
try { exec(`git fetch origin "${branchName}"`, { ..._gitOpts, cwd: rootDir }); } catch (e) { log('warn', 'git: ' + e.message); }
|
|
310
|
+
try { exec(`git fetch origin "${branchName}"`, { ..._gitOpts, cwd: rootDir }); } catch (e) { log('warn', 'git fetch: ' + e.message); }
|
|
311
311
|
try {
|
|
312
312
|
runWorktreeAdd(rootDir, worktreePath, `"${branchName}"`, _worktreeGitOpts, worktreeCreateRetries);
|
|
313
313
|
} catch (eShared) {
|
|
@@ -317,6 +317,11 @@ function spawnAgent(dispatchItem, config) {
|
|
|
317
317
|
log('info', `Shared branch ${branchName} already checked out at ${existingWtPath} — reusing`);
|
|
318
318
|
worktreePath = existingWtPath;
|
|
319
319
|
} else { throw eShared; }
|
|
320
|
+
} else if (eShared.message?.includes('invalid reference') || eShared.message?.includes('not a valid branch')) {
|
|
321
|
+
// Branch doesn't exist yet — create it from main
|
|
322
|
+
log('info', `Shared branch ${branchName} not found — creating from ${project.mainBranch || 'main'}`);
|
|
323
|
+
const mainRef = sanitizeBranch(project.mainBranch || 'main');
|
|
324
|
+
runWorktreeAdd(rootDir, worktreePath, `-b "${branchName}" ${mainRef}`, _worktreeGitOpts, worktreeCreateRetries);
|
|
320
325
|
} else { throw eShared; }
|
|
321
326
|
}
|
|
322
327
|
} else {
|
|
@@ -1190,9 +1195,17 @@ function materializePlansAsWorkItems(config) {
|
|
|
1190
1195
|
const root = path.resolve(firstProject.localPath);
|
|
1191
1196
|
const mainBranch = firstProject.mainBranch || 'main';
|
|
1192
1197
|
const branch = sanitizeBranch(plan.feature_branch);
|
|
1193
|
-
// Create branch from main
|
|
1194
|
-
|
|
1195
|
-
|
|
1198
|
+
// Create branch from main — verify it actually succeeded
|
|
1199
|
+
try {
|
|
1200
|
+
exec(`git branch "${branch}" "${mainBranch}"`, { cwd: root, stdio: 'pipe', windowsHide: true });
|
|
1201
|
+
} catch (e) {
|
|
1202
|
+
// Branch may already exist — that's fine
|
|
1203
|
+
if (!e.message?.includes('already exists')) throw e;
|
|
1204
|
+
}
|
|
1205
|
+
// Push to remote (best-effort — may not have a remote)
|
|
1206
|
+
try {
|
|
1207
|
+
exec(`git push -u origin "${branch}"`, { cwd: root, stdio: 'pipe', windowsHide: true, timeout: 15000 });
|
|
1208
|
+
} catch { /* no remote or push failed — branch still exists locally */ }
|
|
1196
1209
|
log('info', `Shared branch pre-created: ${branch} for plan ${file}`);
|
|
1197
1210
|
} catch (err) {
|
|
1198
1211
|
log('warn', `Failed to pre-create shared branch for ${file}: ${err.message}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.156",
|
|
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"
|