@yemi33/minions 0.1.547 → 0.1.549
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 +11 -1
- package/bin/minions.js +5 -0
- package/dashboard/js/render-plans.js +4 -1
- package/dashboard/js/render-prs.js +1 -1
- package/dashboard/js/utils.js +77 -2
- package/dashboard/styles.css +1 -0
- package/dashboard.js +64 -19
- package/engine/queries.js +1 -0
- package/engine.js +124 -132
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.549 (2026-04-07)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
- prefer git rev-parse over .minions-commit for disk version detection
|
|
7
|
+
|
|
8
|
+
## 0.1.548 (2026-04-07)
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
- auto-enrich linked PRs, progressive markdown rendering, KB sort
|
|
12
|
+
- extract buildWorkItemDispatchVars() from duplicated discovery code (#467)
|
|
13
|
+
- Replace JSON.parse(safeRead()) with safeJsonArr/safeJsonObj in dashboard.js (#465)
|
|
4
14
|
|
|
5
15
|
### Fixes
|
|
6
16
|
- remap sequential PRD item IDs to prevent cross-PRD collisions (#474)
|
package/bin/minions.js
CHANGED
|
@@ -217,6 +217,11 @@ function getInstalledVersion() {
|
|
|
217
217
|
|
|
218
218
|
function saveInstalledVersion(version) {
|
|
219
219
|
fs.writeFileSync(path.join(MINIONS_HOME, '.minions-version'), version);
|
|
220
|
+
// Persist source commit so dashboard can detect repo-based installs
|
|
221
|
+
try {
|
|
222
|
+
const commit = execSync('git rev-parse --short HEAD', { cwd: PKG_ROOT, encoding: 'utf8', timeout: 5000, windowsHide: true }).trim();
|
|
223
|
+
if (commit) fs.writeFileSync(path.join(MINIONS_HOME, '.minions-commit'), commit);
|
|
224
|
+
} catch {}
|
|
220
225
|
}
|
|
221
226
|
|
|
222
227
|
// ─── Init / Upgrade ─────────────────────────────────────────────────────────
|
|
@@ -381,10 +381,12 @@ function planHideRevise(file) {
|
|
|
381
381
|
|
|
382
382
|
let _planPollInterval = null;
|
|
383
383
|
let _planPollFile = null;
|
|
384
|
+
let _planPollLastRaw = null;
|
|
384
385
|
|
|
385
386
|
function _stopPlanPoll() {
|
|
386
387
|
if (_planPollInterval) { clearInterval(_planPollInterval); _planPollInterval = null; }
|
|
387
388
|
_planPollFile = null;
|
|
389
|
+
_planPollLastRaw = null;
|
|
388
390
|
}
|
|
389
391
|
|
|
390
392
|
function _renderPlanModal(normalizedFile, raw, lastMod) {
|
|
@@ -488,6 +490,7 @@ async function planView(file) {
|
|
|
488
490
|
const raw = await planRes.text();
|
|
489
491
|
|
|
490
492
|
const { title, text } = _renderPlanModal(normalizedFile, raw, lastMod);
|
|
493
|
+
_planPollLastRaw = raw;
|
|
491
494
|
|
|
492
495
|
_modalDocContext = { title, content: text, selection: '' };
|
|
493
496
|
_modalFilePath = resolvedPath || ((normalizedFile.endsWith('.json') ? 'prd/' : 'plans/') + normalizedFile); showModalQa();
|
|
@@ -502,7 +505,7 @@ async function planView(file) {
|
|
|
502
505
|
}
|
|
503
506
|
fetch('/api/plans/' + encodeURIComponent(normalizedFile))
|
|
504
507
|
.then(function(r) { return r.text().then(function(raw) { return { raw: raw, lastMod: r.headers.get('Last-Modified') }; }); })
|
|
505
|
-
.then(function(d) { if (_planPollFile === normalizedFile) _renderPlanModal(normalizedFile, d.raw, d.lastMod); })
|
|
508
|
+
.then(function(d) { if (_planPollFile === normalizedFile && d.raw !== _planPollLastRaw) { _planPollLastRaw = d.raw; _renderPlanModal(normalizedFile, d.raw, d.lastMod); } })
|
|
506
509
|
.catch(function() {});
|
|
507
510
|
}, 3000);
|
|
508
511
|
} catch (e) { console.error(e); }
|
|
@@ -20,7 +20,7 @@ function prRow(pr) {
|
|
|
20
20
|
const prId = pr.id || '—';
|
|
21
21
|
return '<tr>' +
|
|
22
22
|
'<td><span class="pr-id">' + escHtml(String(prId)) + '</span></td>' +
|
|
23
|
-
'<td><a class="pr-title" href="' + escHtml(safeUrl(url)) + '" target="_blank" rel="noopener">' + escHtml(pr.title || 'Untitled') + '</a
|
|
23
|
+
'<td><a class="pr-title" href="' + escHtml(safeUrl(url)) + '" target="_blank" rel="noopener">' + escHtml(pr.title || 'Untitled') + '</a>' + (pr.description ? '<div class="pr-desc">' + escHtml(pr.description.length > 120 ? pr.description.slice(0, 120) + '...' : pr.description) + '</div>' : '') + '</td>' +
|
|
24
24
|
'<td><span class="pr-agent">' + escHtml(pr.agent || '—') + '</span></td>' +
|
|
25
25
|
'<td><span class="pr-branch">' + escHtml(pr.branch || '—') + '</span></td>' +
|
|
26
26
|
'<td><span class="pr-badge ' + reviewClass + '">' + escHtml(reviewLabel) + '</span></td>' +
|
package/dashboard/js/utils.js
CHANGED
|
@@ -60,8 +60,11 @@ function copyLlmText(btn) {
|
|
|
60
60
|
*/
|
|
61
61
|
function renderMd(s) {
|
|
62
62
|
if (!s) return '';
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
if (s.length > 10000) return _renderMdChunked(s);
|
|
64
|
+
return _renderMdCore(s);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function _renderMdCore(s) {
|
|
65
68
|
let html = escHtml(s);
|
|
66
69
|
|
|
67
70
|
// 1. Extract code blocks and inline code into placeholders (protect from other transforms)
|
|
@@ -199,6 +202,78 @@ function renderMd(s) {
|
|
|
199
202
|
return '<div class="md-content">' + html + '</div>';
|
|
200
203
|
}
|
|
201
204
|
|
|
205
|
+
var MD_CHUNK_SIZE = 10000;
|
|
206
|
+
var _mdChunkUid = 0;
|
|
207
|
+
|
|
208
|
+
function _renderMdChunked(fullText) {
|
|
209
|
+
// Split at blank-line boundaries to avoid breaking code blocks and other multi-line elements
|
|
210
|
+
var chunks = [];
|
|
211
|
+
var pos = 0;
|
|
212
|
+
while (pos < fullText.length) {
|
|
213
|
+
if (pos + MD_CHUNK_SIZE >= fullText.length) {
|
|
214
|
+
chunks.push(fullText.slice(pos));
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
var target = pos + MD_CHUNK_SIZE;
|
|
218
|
+
var searchStart = Math.max(pos + Math.floor(MD_CHUNK_SIZE * 0.7), pos);
|
|
219
|
+
var searchEnd = Math.min(target + 500, fullText.length);
|
|
220
|
+
var slice = fullText.slice(searchStart, searchEnd);
|
|
221
|
+
var lastBlank = slice.lastIndexOf('\n\n');
|
|
222
|
+
var best;
|
|
223
|
+
if (lastBlank !== -1) {
|
|
224
|
+
best = searchStart + lastBlank + 2;
|
|
225
|
+
} else {
|
|
226
|
+
var nl = fullText.indexOf('\n', target);
|
|
227
|
+
best = (nl !== -1 && nl - target < 500) ? nl + 1 : target;
|
|
228
|
+
}
|
|
229
|
+
chunks.push(fullText.slice(pos, best));
|
|
230
|
+
pos = best;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
var firstHtml = _renderMdCore(chunks[0]);
|
|
234
|
+
var uid = '_mdChunk' + (++_mdChunkUid);
|
|
235
|
+
if (chunks.length <= 1) return firstHtml;
|
|
236
|
+
|
|
237
|
+
var sentinel = '<div id="' + uid + '" style="padding:12px 0;color:var(--muted);font-size:11px">Loading more content...</div>';
|
|
238
|
+
|
|
239
|
+
setTimeout(function() {
|
|
240
|
+
var el = document.getElementById(uid);
|
|
241
|
+
if (!el) return;
|
|
242
|
+
var scrollParent = el.closest('.modal-body') || el.parentElement;
|
|
243
|
+
var idx = 1;
|
|
244
|
+
var obs = null;
|
|
245
|
+
function loadNext() {
|
|
246
|
+
if (idx >= chunks.length) {
|
|
247
|
+
if (obs) obs.disconnect();
|
|
248
|
+
el.remove();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
el.insertAdjacentHTML('beforebegin', _renderMdCore(chunks[idx]));
|
|
252
|
+
idx++;
|
|
253
|
+
if (idx >= chunks.length) {
|
|
254
|
+
if (obs) obs.disconnect();
|
|
255
|
+
el.remove();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (typeof IntersectionObserver !== 'undefined') {
|
|
259
|
+
obs = new IntersectionObserver(function(entries) {
|
|
260
|
+
if (!entries[0].isIntersecting) return;
|
|
261
|
+
loadNext();
|
|
262
|
+
// Yield to browser for paint before re-observing, so rapid chunk loads don't block rendering
|
|
263
|
+
if (idx < chunks.length && el.parentNode) {
|
|
264
|
+
obs.unobserve(el);
|
|
265
|
+
requestAnimationFrame(function() { if (el.parentNode) obs.observe(el); });
|
|
266
|
+
}
|
|
267
|
+
}, { root: scrollParent, rootMargin: '200px' });
|
|
268
|
+
obs.observe(el);
|
|
269
|
+
} else {
|
|
270
|
+
var timer = setInterval(function() { loadNext(); if (idx >= chunks.length) clearInterval(timer); }, 100);
|
|
271
|
+
}
|
|
272
|
+
}, 0);
|
|
273
|
+
|
|
274
|
+
return firstHtml + sentinel;
|
|
275
|
+
}
|
|
276
|
+
|
|
202
277
|
function openBugReport() {
|
|
203
278
|
document.getElementById('modal-title').textContent = 'Report a Bug';
|
|
204
279
|
document.getElementById('modal-body').innerHTML =
|
package/dashboard/styles.css
CHANGED
|
@@ -236,6 +236,7 @@
|
|
|
236
236
|
.pr-table tr:hover { background: var(--surface2); }
|
|
237
237
|
.pr-title { color: var(--blue); text-decoration: none; font-weight: 500; }
|
|
238
238
|
.pr-title:hover { text-decoration: underline; }
|
|
239
|
+
.pr-desc { color: var(--muted); font-size: var(--text-sm); font-weight: 400; margin-top: 2px; line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 400px; }
|
|
239
240
|
.pr-id { color: var(--muted); font-family: Consolas, monospace; font-size: var(--text-base); }
|
|
240
241
|
.pr-agent { font-size: var(--text-base); color: var(--text); }
|
|
241
242
|
.pr-branch { font-family: Consolas, monospace; font-size: var(--text-sm); color: var(--muted); background: var(--bg); padding: var(--space-1) var(--space-3); border-radius: 3px; border: 1px solid var(--border); }
|
package/dashboard.js
CHANGED
|
@@ -230,7 +230,11 @@ function getDiskVersion() {
|
|
|
230
230
|
try { diskVersion = require('@yemi33/minions/package.json').version; } catch {}
|
|
231
231
|
}
|
|
232
232
|
let diskCommit = null;
|
|
233
|
+
// Prefer git (authoritative for repo-based dev), fall back to .minions-commit (installed copies)
|
|
233
234
|
try { diskCommit = require('child_process').execSync('git rev-parse --short HEAD', { cwd: MINIONS_DIR, encoding: 'utf8', timeout: 5000, windowsHide: true }).trim(); } catch {}
|
|
235
|
+
if (!diskCommit) {
|
|
236
|
+
try { diskCommit = fs.readFileSync(path.join(MINIONS_DIR, '.minions-commit'), 'utf8').trim() || null; } catch {}
|
|
237
|
+
}
|
|
234
238
|
_diskVersionCache = { diskVersion, diskCommit };
|
|
235
239
|
_diskVersionCacheTs = now;
|
|
236
240
|
return _diskVersionCache;
|
|
@@ -240,7 +244,7 @@ function getMcpServers() {
|
|
|
240
244
|
try {
|
|
241
245
|
const home = os.homedir();
|
|
242
246
|
const claudeJsonPath = path.join(home, '.claude.json');
|
|
243
|
-
const data =
|
|
247
|
+
const data = safeJsonObj(claudeJsonPath);
|
|
244
248
|
const servers = data.mcpServers || {};
|
|
245
249
|
return Object.entries(servers).map(([name, cfg]) => ({
|
|
246
250
|
name,
|
|
@@ -1082,7 +1086,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1082
1086
|
}) || PROJECTS[0] || null;
|
|
1083
1087
|
if (project) {
|
|
1084
1088
|
const wiPath = shared.projectWorkItemsPath(project);
|
|
1085
|
-
const items =
|
|
1089
|
+
const items = safeJsonArr(wiPath);
|
|
1086
1090
|
const verify = items.find(w => w.sourcePlan === body.file && w.itemType === 'verify');
|
|
1087
1091
|
if (verify) {
|
|
1088
1092
|
return jsonReply(res, 200, { ok: true, verifyId: verify.id });
|
|
@@ -1150,7 +1154,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1150
1154
|
// Clear cooldown so item isn't blocked by exponential backoff
|
|
1151
1155
|
try {
|
|
1152
1156
|
const cooldownPath = path.join(MINIONS_DIR, 'engine', 'cooldowns.json');
|
|
1153
|
-
const cooldowns =
|
|
1157
|
+
const cooldowns = safeJsonObj(cooldownPath);
|
|
1154
1158
|
if (cooldowns[dispatchKey]) {
|
|
1155
1159
|
delete cooldowns[dispatchKey];
|
|
1156
1160
|
safeWrite(cooldownPath, cooldowns);
|
|
@@ -1201,7 +1205,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1201
1205
|
// Clean cooldown entries so item can be re-created immediately
|
|
1202
1206
|
try {
|
|
1203
1207
|
const cooldownPath = path.join(MINIONS_DIR, 'engine', 'cooldowns.json');
|
|
1204
|
-
const cooldowns =
|
|
1208
|
+
const cooldowns = safeJsonObj(cooldownPath);
|
|
1205
1209
|
let cleaned = false;
|
|
1206
1210
|
for (const key of Object.keys(cooldowns)) {
|
|
1207
1211
|
if (key.includes(id)) { delete cooldowns[key]; cleaned = true; }
|
|
@@ -1525,7 +1529,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1525
1529
|
try {
|
|
1526
1530
|
const body = await readBody(req);
|
|
1527
1531
|
const dispatchPath = path.join(MINIONS_DIR, 'engine', 'dispatch.json');
|
|
1528
|
-
const dispatch =
|
|
1532
|
+
const dispatch = safeJsonObj(dispatchPath);
|
|
1529
1533
|
const active = dispatch.active || [];
|
|
1530
1534
|
const cancelled = [];
|
|
1531
1535
|
|
|
@@ -1537,7 +1541,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1537
1541
|
// Kill agent process
|
|
1538
1542
|
const statusPath = path.join(MINIONS_DIR, 'agents', d.agent, 'status.json');
|
|
1539
1543
|
try {
|
|
1540
|
-
const status =
|
|
1544
|
+
const status = safeJsonObj(statusPath);
|
|
1541
1545
|
if (status.pid) {
|
|
1542
1546
|
try {
|
|
1543
1547
|
const safePid = shared.validatePid(status.pid);
|
|
@@ -1907,7 +1911,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
1907
1911
|
{ dir: path.join(PRD_DIR, 'archive'), archived: true },
|
|
1908
1912
|
];
|
|
1909
1913
|
// Load work items to check for completed plan-to-prd conversions
|
|
1910
|
-
const centralWi =
|
|
1914
|
+
const centralWi = safeJsonArr(path.join(MINIONS_DIR, 'work-items.json'));
|
|
1911
1915
|
const completedPrdFiles = new Set(
|
|
1912
1916
|
centralWi.filter(w => w.type === 'plan-to-prd' && w.status === 'done' && w.planFile)
|
|
1913
1917
|
.map(w => w.planFile)
|
|
@@ -2048,7 +2052,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2048
2052
|
const body = await readBody(req);
|
|
2049
2053
|
if (!body.file) return jsonReply(res, 400, { error: 'file required' });
|
|
2050
2054
|
const planPath = resolvePlanPath(body.file);
|
|
2051
|
-
const plan =
|
|
2055
|
+
const plan = safeJsonObj(planPath);
|
|
2052
2056
|
plan.status = 'approved';
|
|
2053
2057
|
plan.approvedAt = new Date().toISOString();
|
|
2054
2058
|
plan.approvedBy = body.approvedBy || os.userInfo().username;
|
|
@@ -2101,7 +2105,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2101
2105
|
const body = await readBody(req);
|
|
2102
2106
|
if (!body.file) return jsonReply(res, 400, { error: 'file required' });
|
|
2103
2107
|
const planPath = resolvePlanPath(body.file);
|
|
2104
|
-
const plan =
|
|
2108
|
+
const plan = safeJsonObj(planPath);
|
|
2105
2109
|
plan.status = 'paused';
|
|
2106
2110
|
plan.pausedAt = new Date().toISOString();
|
|
2107
2111
|
safeWrite(planPath, plan);
|
|
@@ -2134,7 +2138,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2134
2138
|
if (activeEntry) {
|
|
2135
2139
|
const statusPath = path.join(MINIONS_DIR, 'agents', activeEntry.agent, 'status.json');
|
|
2136
2140
|
try {
|
|
2137
|
-
const agentStatus =
|
|
2141
|
+
const agentStatus = safeJsonObj(statusPath);
|
|
2138
2142
|
if (agentStatus.pid) {
|
|
2139
2143
|
try {
|
|
2140
2144
|
const safePid = shared.validatePid(agentStatus.pid);
|
|
@@ -2293,7 +2297,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2293
2297
|
const body = await readBody(req);
|
|
2294
2298
|
if (!body.file) return jsonReply(res, 400, { error: 'file required' });
|
|
2295
2299
|
const planPath = resolvePlanPath(body.file);
|
|
2296
|
-
const plan =
|
|
2300
|
+
const plan = safeJsonObj(planPath);
|
|
2297
2301
|
plan.status = 'rejected';
|
|
2298
2302
|
plan.rejectedAt = new Date().toISOString();
|
|
2299
2303
|
plan.rejectedBy = body.rejectedBy || os.userInfo().username;
|
|
@@ -2375,7 +2379,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2375
2379
|
// Read PRD content before deleting to get source_plan for cleanup
|
|
2376
2380
|
let prdSourcePlan = null;
|
|
2377
2381
|
if (body.file.endsWith('.json')) {
|
|
2378
|
-
try { prdSourcePlan =
|
|
2382
|
+
try { prdSourcePlan = safeJsonObj(planPath).source_plan || null; } catch {}
|
|
2379
2383
|
}
|
|
2380
2384
|
safeUnlink(planPath);
|
|
2381
2385
|
|
|
@@ -2442,7 +2446,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2442
2446
|
let archivedSource = null;
|
|
2443
2447
|
if (body.file.endsWith('.json')) {
|
|
2444
2448
|
try {
|
|
2445
|
-
const prd =
|
|
2449
|
+
const prd = safeJsonObj(archivePath);
|
|
2446
2450
|
prd.status = 'archived';
|
|
2447
2451
|
prd.archivedAt = new Date().toISOString();
|
|
2448
2452
|
safeWrite(archivePath, prd);
|
|
@@ -2501,7 +2505,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2501
2505
|
const body = await readBody(req);
|
|
2502
2506
|
if (!body.file || !body.feedback) return jsonReply(res, 400, { error: 'file and feedback required' });
|
|
2503
2507
|
const planPath = resolvePlanPath(body.file);
|
|
2504
|
-
const plan =
|
|
2508
|
+
const plan = safeJsonObj(planPath);
|
|
2505
2509
|
plan.status = 'revision-requested';
|
|
2506
2510
|
plan.revision_feedback = body.feedback;
|
|
2507
2511
|
plan.revisionRequestedAt = new Date().toISOString();
|
|
@@ -2546,7 +2550,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2546
2550
|
sourcePlanFile = body.sourcePlan;
|
|
2547
2551
|
} else {
|
|
2548
2552
|
// Heuristic: find .md plan by matching prefix or by reading PRD's generated_from field
|
|
2549
|
-
const prd =
|
|
2553
|
+
const prd = safeJsonObj(prdPath);
|
|
2550
2554
|
if (prd.source_plan) {
|
|
2551
2555
|
sourcePlanFile = prd.source_plan;
|
|
2552
2556
|
} else {
|
|
@@ -2596,7 +2600,7 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
|
|
|
2596
2600
|
safeWrite(sourcePlanPath, result.content);
|
|
2597
2601
|
|
|
2598
2602
|
// Step 2: Pause the old PRD so it stops materializing items
|
|
2599
|
-
const prd =
|
|
2603
|
+
const prd = safeJsonObj(prdPath);
|
|
2600
2604
|
prd.status = 'revision-requested';
|
|
2601
2605
|
prd.revision_feedback = body.instruction;
|
|
2602
2606
|
prd.revisionRequestedAt = new Date().toISOString();
|
|
@@ -2826,7 +2830,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
2826
2830
|
const wiPaths = [path.join(MINIONS_DIR, 'work-items.json')];
|
|
2827
2831
|
for (const proj of PROJECTS) wiPaths.push(shared.projectWorkItemsPath(proj));
|
|
2828
2832
|
const dispatchPath = path.join(MINIONS_DIR, 'engine', 'dispatch.json');
|
|
2829
|
-
const dispatch =
|
|
2833
|
+
const dispatch = safeJsonObj(dispatchPath);
|
|
2830
2834
|
const killedAgents = new Set();
|
|
2831
2835
|
const resetItemIds = new Set();
|
|
2832
2836
|
for (const wiPath of wiPaths) {
|
|
@@ -2841,7 +2845,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
2841
2845
|
if (activeEntry) {
|
|
2842
2846
|
const statusPath = path.join(MINIONS_DIR, 'agents', activeEntry.agent, 'status.json');
|
|
2843
2847
|
try {
|
|
2844
|
-
const agentStatus =
|
|
2848
|
+
const agentStatus = safeJsonObj(statusPath);
|
|
2845
2849
|
if (agentStatus.pid) {
|
|
2846
2850
|
try {
|
|
2847
2851
|
const safePid = shared.validatePid(agentStatus.pid);
|
|
@@ -3822,6 +3826,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3822
3826
|
prs.push({
|
|
3823
3827
|
id: prId,
|
|
3824
3828
|
title: (title || 'PR #' + prNum + ' (polling...)').slice(0, 120),
|
|
3829
|
+
description: '',
|
|
3825
3830
|
agent: 'human',
|
|
3826
3831
|
branch: '',
|
|
3827
3832
|
reviewStatus: autoObserve ? 'pending' : 'none',
|
|
@@ -3837,7 +3842,47 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3837
3842
|
}, { defaultValue: [] });
|
|
3838
3843
|
if (duplicate) return jsonReply(res, 400, { error: 'PR already tracked' });
|
|
3839
3844
|
invalidateStatusCache();
|
|
3840
|
-
|
|
3845
|
+
jsonReply(res, 200, { ok: true, id: prId });
|
|
3846
|
+
|
|
3847
|
+
// Async-enrich: fetch title, description, branch, author from GitHub/ADO API
|
|
3848
|
+
(async () => {
|
|
3849
|
+
try {
|
|
3850
|
+
let prData = null;
|
|
3851
|
+
const ghMatch = url.match(/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+)/);
|
|
3852
|
+
const adoMatch = url.match(/dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/]+)\/pullrequest\/(\d+)/);
|
|
3853
|
+
if (ghMatch) {
|
|
3854
|
+
const slug = ghMatch[1];
|
|
3855
|
+
const result = await shared.execAsync(`gh api "repos/${slug}/pulls/${prNum}"`, { timeout: 15000, encoding: 'utf-8' });
|
|
3856
|
+
const d = JSON.parse(result);
|
|
3857
|
+
prData = { title: d.title, description: d.body, branch: d.head?.ref, author: d.user?.login };
|
|
3858
|
+
} else if (adoMatch) {
|
|
3859
|
+
const [, adoOrg, adoProj, adoRepo] = adoMatch;
|
|
3860
|
+
try {
|
|
3861
|
+
const { getAdoToken } = require('./engine/ado');
|
|
3862
|
+
const token = await getAdoToken();
|
|
3863
|
+
if (token) {
|
|
3864
|
+
const apiUrl = `https://dev.azure.com/${adoOrg}/${adoProj}/_apis/git/repositories/${adoRepo}/pullrequests/${prNum}?api-version=7.1`;
|
|
3865
|
+
const result = await shared.execAsync(`curl -s --max-time 10 -H "Authorization: Bearer ${token}" "${apiUrl}"`, { encoding: 'utf-8', timeout: 15000, windowsHide: true });
|
|
3866
|
+
const d = JSON.parse(result);
|
|
3867
|
+
prData = { title: d.title, description: d.description, branch: d.sourceRefName?.replace('refs/heads/', ''), author: d.createdBy?.displayName };
|
|
3868
|
+
}
|
|
3869
|
+
} catch { /* ADO token may not be available */ }
|
|
3870
|
+
}
|
|
3871
|
+
if (!prData) return;
|
|
3872
|
+
mutateJsonFileLocked(prPath, (prs) => {
|
|
3873
|
+
const pr = prs.find(p => p.id === prId);
|
|
3874
|
+
if (!pr) return prs;
|
|
3875
|
+
if (!title && prData.title) pr.title = prData.title.slice(0, 120);
|
|
3876
|
+
if (prData.description) pr.description = prData.description.slice(0, 500);
|
|
3877
|
+
if (!pr.branch && prData.branch) pr.branch = prData.branch;
|
|
3878
|
+
if (pr.agent === 'human' && prData.author) pr.agent = prData.author;
|
|
3879
|
+
return prs;
|
|
3880
|
+
}, { defaultValue: [] });
|
|
3881
|
+
invalidateStatusCache();
|
|
3882
|
+
} catch (e) {
|
|
3883
|
+
shared.log('warn', `PR link enrichment failed for ${prId}: ${e.message}`);
|
|
3884
|
+
}
|
|
3885
|
+
})();
|
|
3841
3886
|
}},
|
|
3842
3887
|
|
|
3843
3888
|
{ method: 'POST', path: '/api/pull-requests/delete', desc: 'Remove a PR from tracking', params: 'id, project?', handler: async (req, res) => {
|
package/engine/queries.js
CHANGED
package/engine.js
CHANGED
|
@@ -1655,61 +1655,21 @@ function discoverFromWorkItems(config, project) {
|
|
|
1655
1655
|
commit_message: item.commitMessage || `feat: ${item.title || item.id}`,
|
|
1656
1656
|
notes_content: '',
|
|
1657
1657
|
};
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
vars.checkpoint_context = '';
|
|
1670
|
-
try {
|
|
1671
|
-
const wtPath = vars.worktree_path || root;
|
|
1672
|
-
const cpPath = path.join(wtPath, 'checkpoint.json');
|
|
1673
|
-
if (fs.existsSync(cpPath)) {
|
|
1674
|
-
const cpData = JSON.parse(fs.readFileSync(cpPath, 'utf8'));
|
|
1675
|
-
const cpCount = (item._checkpointCount || 0) + 1;
|
|
1676
|
-
if (cpCount > 3) {
|
|
1677
|
-
log('warn', `Work item ${item.id} exceeded 3 checkpoint-resumes — marking as needs-human-review`);
|
|
1678
|
-
item.status = WI_STATUS.NEEDS_REVIEW;
|
|
1679
|
-
item._checkpointCount = cpCount;
|
|
1680
|
-
needsWrite = true;
|
|
1681
|
-
continue;
|
|
1682
|
-
}
|
|
1683
|
-
item._checkpointCount = cpCount;
|
|
1684
|
-
needsWrite = true;
|
|
1685
|
-
const cpSummary = [
|
|
1686
|
-
`## Checkpoint (Resume #${cpCount}/3)`,
|
|
1687
|
-
'',
|
|
1688
|
-
'A previous agent run timed out but left a checkpoint. Continue from where it left off.',
|
|
1689
|
-
'',
|
|
1690
|
-
cpData.completed && cpData.completed.length > 0 ? `### Completed\n${cpData.completed.map(s => '- ' + s).join('\n')}` : '',
|
|
1691
|
-
cpData.remaining && cpData.remaining.length > 0 ? `### Remaining\n${cpData.remaining.map(s => '- ' + s).join('\n')}` : '',
|
|
1692
|
-
cpData.blockers && cpData.blockers.length > 0 ? `### Blockers\n${cpData.blockers.map(s => '- ' + s).join('\n')}` : '',
|
|
1693
|
-
cpData.branch_state ? `### Branch State\n${cpData.branch_state}` : '',
|
|
1694
|
-
].filter(Boolean).join('\n');
|
|
1695
|
-
vars.checkpoint_context = cpSummary;
|
|
1696
|
-
log('info', `Injecting checkpoint context for ${item.id} (resume #${cpCount})`);
|
|
1697
|
-
}
|
|
1698
|
-
} catch (e) { log('warn', `checkpoint read for ${item.id}: ${e.message}`); }
|
|
1699
|
-
|
|
1700
|
-
// Inject ask-specific variables for the ask playbook
|
|
1701
|
-
if (workType === WORK_TYPE.ASK) {
|
|
1702
|
-
vars.question = item.title + (item.description ? '\n\n' + item.description : '');
|
|
1703
|
-
vars.task_id = item.id;
|
|
1704
|
-
vars.notes_content = '';
|
|
1705
|
-
try { vars.notes_content = fs.readFileSync(path.join(MINIONS_DIR, 'notes.md'), 'utf8'); } catch { /* optional */ }
|
|
1658
|
+
// Build common vars: references, acceptance criteria, checkpoint, notes, task context
|
|
1659
|
+
const cpResult = buildWorkItemDispatchVars(item, vars, config, {
|
|
1660
|
+
worktreePath: vars.worktree_path || root,
|
|
1661
|
+
workType,
|
|
1662
|
+
});
|
|
1663
|
+
if (cpResult.needsReview) {
|
|
1664
|
+
log('warn', `Work item ${item.id} exceeded 3 checkpoint-resumes — marking as needs-human-review`);
|
|
1665
|
+
item.status = WI_STATUS.NEEDS_REVIEW;
|
|
1666
|
+
item._checkpointCount = cpResult.checkpointCount;
|
|
1667
|
+
needsWrite = true;
|
|
1668
|
+
continue;
|
|
1706
1669
|
}
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
if (resolvedCtx.additionalContext) {
|
|
1711
|
-
vars.additional_context = (vars.additional_context || '') + resolvedCtx.additionalContext;
|
|
1712
|
-
vars.task_description = vars.task_description + resolvedCtx.additionalContext;
|
|
1670
|
+
if (cpResult.checkpointCount !== null) {
|
|
1671
|
+
item._checkpointCount = cpResult.checkpointCount;
|
|
1672
|
+
needsWrite = true;
|
|
1713
1673
|
}
|
|
1714
1674
|
|
|
1715
1675
|
const playbookName = selectPlaybook(workType, item);
|
|
@@ -1795,6 +1755,90 @@ function normalizeAc(ac) {
|
|
|
1795
1755
|
return [];
|
|
1796
1756
|
}
|
|
1797
1757
|
|
|
1758
|
+
/**
|
|
1759
|
+
* Build common dispatch vars for a work item: references, acceptance criteria,
|
|
1760
|
+
* checkpoint context, notes content, and resolved task context.
|
|
1761
|
+
*
|
|
1762
|
+
* Consolidates duplicated patterns across discoverFromWorkItems, discoverCentralWorkItems
|
|
1763
|
+
* (normal + fan-out). Caller-specific vars (project_name, work_branch, plan vars) are NOT
|
|
1764
|
+
* handled here — they remain in the caller.
|
|
1765
|
+
*
|
|
1766
|
+
* @param {Object} item - Work item
|
|
1767
|
+
* @param {Object} vars - Mutable vars object to populate (must already have base vars)
|
|
1768
|
+
* @param {Object} config - Engine config
|
|
1769
|
+
* @param {Object} [options]
|
|
1770
|
+
* @param {string} [options.worktreePath] - Path for checkpoint lookup (omit to skip checkpoint)
|
|
1771
|
+
* @param {boolean} [options.includeNotes=true] - Whether to read notes.md into vars.notes_content
|
|
1772
|
+
* @param {string} [options.workType] - Work type (used for ASK-specific vars)
|
|
1773
|
+
* @returns {{ needsReview: boolean, checkpointCount: number|null }} checkpoint side-effect info
|
|
1774
|
+
*/
|
|
1775
|
+
function buildWorkItemDispatchVars(item, vars, config, options = {}) {
|
|
1776
|
+
const { worktreePath, includeNotes = true, workType } = options;
|
|
1777
|
+
|
|
1778
|
+
// Notes content (uses queries.getNotes instead of inline fs.readFileSync)
|
|
1779
|
+
if (includeNotes) {
|
|
1780
|
+
vars.notes_content = getNotes() || '';
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// References
|
|
1784
|
+
const refs = (item.references || []).filter(r => r && r.url).map(r =>
|
|
1785
|
+
'- [' + (r.title || r.url) + '](' + r.url + ')' + (r.type ? ' (' + r.type + ')' : '')
|
|
1786
|
+
).join('\n');
|
|
1787
|
+
vars.references = refs ? '## References\n\n' + refs : '';
|
|
1788
|
+
|
|
1789
|
+
// Acceptance criteria
|
|
1790
|
+
const ac = normalizeAc(item.acceptanceCriteria).map(c => '- [ ] ' + c).join('\n');
|
|
1791
|
+
vars.acceptance_criteria = ac ? '## Acceptance Criteria\n\n' + ac : '';
|
|
1792
|
+
|
|
1793
|
+
// Checkpoint context
|
|
1794
|
+
vars.checkpoint_context = '';
|
|
1795
|
+
const result = { needsReview: false, checkpointCount: null };
|
|
1796
|
+
if (worktreePath) {
|
|
1797
|
+
try {
|
|
1798
|
+
const cpPath = path.join(worktreePath, 'checkpoint.json');
|
|
1799
|
+
if (fs.existsSync(cpPath)) {
|
|
1800
|
+
const cpData = JSON.parse(fs.readFileSync(cpPath, 'utf8'));
|
|
1801
|
+
const cpCount = (item._checkpointCount || 0) + 1;
|
|
1802
|
+
result.checkpointCount = cpCount;
|
|
1803
|
+
if (cpCount > 3) {
|
|
1804
|
+
result.needsReview = true;
|
|
1805
|
+
} else {
|
|
1806
|
+
const cpSummary = [
|
|
1807
|
+
`## Checkpoint (Resume #${cpCount}/3)`,
|
|
1808
|
+
'',
|
|
1809
|
+
'A previous agent run timed out but left a checkpoint. Continue from where it left off.',
|
|
1810
|
+
'',
|
|
1811
|
+
cpData.completed && cpData.completed.length > 0 ? `### Completed\n${cpData.completed.map(s => '- ' + s).join('\n')}` : '',
|
|
1812
|
+
cpData.remaining && cpData.remaining.length > 0 ? `### Remaining\n${cpData.remaining.map(s => '- ' + s).join('\n')}` : '',
|
|
1813
|
+
cpData.blockers && cpData.blockers.length > 0 ? `### Blockers\n${cpData.blockers.map(s => '- ' + s).join('\n')}` : '',
|
|
1814
|
+
cpData.branch_state ? `### Branch State\n${cpData.branch_state}` : '',
|
|
1815
|
+
].filter(Boolean).join('\n');
|
|
1816
|
+
vars.checkpoint_context = cpSummary;
|
|
1817
|
+
log('info', `Injecting checkpoint context for ${item.id} (resume #${cpCount})`);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
} catch (e) { log('warn', `checkpoint read for ${item.id}: ${e.message}`); }
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
// ASK-specific variables
|
|
1824
|
+
if (workType === WORK_TYPE.ASK) {
|
|
1825
|
+
vars.question = item.title + (item.description ? '\n\n' + item.description : '');
|
|
1826
|
+
vars.task_id = item.id;
|
|
1827
|
+
vars.notes_content = getNotes() || '';
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
// Resolve implicit context references (e.g., "ripley's plan", "the latest plan")
|
|
1831
|
+
const resolvedCtx = resolveTaskContext(item, config);
|
|
1832
|
+
if (resolvedCtx.additionalContext) {
|
|
1833
|
+
vars.additional_context = (vars.additional_context || '') + resolvedCtx.additionalContext;
|
|
1834
|
+
if (vars.task_description !== undefined) {
|
|
1835
|
+
vars.task_description = vars.task_description + resolvedCtx.additionalContext;
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
return result;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1798
1842
|
function buildProjectContext(projects, assignedProject, isFanOut, agentName, agentRole) {
|
|
1799
1843
|
const projectList = projects.map(p => {
|
|
1800
1844
|
let line = `### ${p.name}\n`;
|
|
@@ -2028,25 +2072,11 @@ function discoverCentralWorkItems(config) {
|
|
|
2028
2072
|
project_path: ap?.localPath || '',
|
|
2029
2073
|
};
|
|
2030
2074
|
|
|
2031
|
-
//
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
const fanAc = normalizeAc(item.acceptanceCriteria).map(c => '- [ ] ' + c).join('\n');
|
|
2037
|
-
vars.acceptance_criteria = fanAc ? '## Acceptance Criteria\n\n' + fanAc : '';
|
|
2038
|
-
|
|
2039
|
-
if (workType === WORK_TYPE.ASK) {
|
|
2040
|
-
vars.question = item.title + (item.description ? '\n\n' + item.description : '');
|
|
2041
|
-
vars.task_id = item.id;
|
|
2042
|
-
vars.notes_content = '';
|
|
2043
|
-
try { vars.notes_content = fs.readFileSync(path.join(MINIONS_DIR, 'notes.md'), 'utf8'); } catch { /* optional */ }
|
|
2044
|
-
}
|
|
2045
|
-
|
|
2046
|
-
const resolvedCtx = resolveTaskContext(item, config);
|
|
2047
|
-
if (resolvedCtx.additionalContext) {
|
|
2048
|
-
vars.additional_context = (vars.additional_context || '') + resolvedCtx.additionalContext;
|
|
2049
|
-
}
|
|
2075
|
+
// Build common vars: references, acceptance criteria, notes (ASK only), task context
|
|
2076
|
+
buildWorkItemDispatchVars(item, vars, config, {
|
|
2077
|
+
includeNotes: false,
|
|
2078
|
+
workType,
|
|
2079
|
+
});
|
|
2050
2080
|
|
|
2051
2081
|
const playbookName = selectPlaybook(workType, item);
|
|
2052
2082
|
const prompt = renderPlaybook(playbookName, vars) || renderPlaybook('work-item', vars);
|
|
@@ -2103,49 +2133,25 @@ function discoverCentralWorkItems(config) {
|
|
|
2103
2133
|
additional_context: item.prompt ? `## Additional Context\n\n${item.prompt}` : '',
|
|
2104
2134
|
scope_section: buildProjectContext(projects, null, false, agentName, agentRole),
|
|
2105
2135
|
project_path: firstProject?.localPath || '',
|
|
2106
|
-
notes_content: '',
|
|
2107
2136
|
};
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
const
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
if (cpPath && fs.existsSync(cpPath)) {
|
|
2127
|
-
const cpData = JSON.parse(fs.readFileSync(cpPath, 'utf8'));
|
|
2128
|
-
const cpCount = (item._checkpointCount || 0) + 1;
|
|
2129
|
-
if (cpCount > 3) {
|
|
2130
|
-
log('warn', `Work item ${item.id} exceeded 3 checkpoint-resumes — marking as needs-human-review`);
|
|
2131
|
-
mutations.set(item.id, { status: WI_STATUS.NEEDS_REVIEW, _checkpointCount: cpCount });
|
|
2132
|
-
continue;
|
|
2133
|
-
}
|
|
2134
|
-
mutations.set(item.id, Object.assign(mutations.get(item.id) || {}, { _checkpointCount: cpCount }));
|
|
2135
|
-
const cpSummary = [
|
|
2136
|
-
`## Checkpoint (Resume #${cpCount}/3)`,
|
|
2137
|
-
'',
|
|
2138
|
-
'A previous agent run timed out but left a checkpoint. Continue from where it left off.',
|
|
2139
|
-
'',
|
|
2140
|
-
cpData.completed && cpData.completed.length > 0 ? `### Completed\n${cpData.completed.map(s => '- ' + s).join('\n')}` : '',
|
|
2141
|
-
cpData.remaining && cpData.remaining.length > 0 ? `### Remaining\n${cpData.remaining.map(s => '- ' + s).join('\n')}` : '',
|
|
2142
|
-
cpData.blockers && cpData.blockers.length > 0 ? `### Blockers\n${cpData.blockers.map(s => '- ' + s).join('\n')}` : '',
|
|
2143
|
-
cpData.branch_state ? `### Branch State\n${cpData.branch_state}` : '',
|
|
2144
|
-
].filter(Boolean).join('\n');
|
|
2145
|
-
vars.checkpoint_context = cpSummary;
|
|
2146
|
-
log('info', `Injecting checkpoint context for ${item.id} (resume #${cpCount})`);
|
|
2147
|
-
}
|
|
2148
|
-
} catch (e) { log('warn', `checkpoint read for ${item.id}: ${e.message}`); }
|
|
2137
|
+
|
|
2138
|
+
// Build common vars: references, acceptance criteria, checkpoint, notes, task context
|
|
2139
|
+
const centralBranch = item.branch || `work/${item.id}`;
|
|
2140
|
+
const centralWtPath = firstProject?.localPath
|
|
2141
|
+
? path.resolve(firstProject.localPath, config.engine?.worktreeRoot || '../worktrees', centralBranch)
|
|
2142
|
+
: '';
|
|
2143
|
+
const cpResult = buildWorkItemDispatchVars(item, vars, config, {
|
|
2144
|
+
worktreePath: centralWtPath || undefined,
|
|
2145
|
+
workType,
|
|
2146
|
+
});
|
|
2147
|
+
if (cpResult.needsReview) {
|
|
2148
|
+
log('warn', `Work item ${item.id} exceeded 3 checkpoint-resumes — marking as needs-human-review`);
|
|
2149
|
+
mutations.set(item.id, { status: WI_STATUS.NEEDS_REVIEW, _checkpointCount: cpResult.checkpointCount });
|
|
2150
|
+
continue;
|
|
2151
|
+
}
|
|
2152
|
+
if (cpResult.checkpointCount !== null) {
|
|
2153
|
+
mutations.set(item.id, Object.assign(mutations.get(item.id) || {}, { _checkpointCount: cpResult.checkpointCount }));
|
|
2154
|
+
}
|
|
2149
2155
|
|
|
2150
2156
|
// Inject plan-specific variables for the plan playbook
|
|
2151
2157
|
if (workType === WORK_TYPE.PLAN) {
|
|
@@ -2156,8 +2162,7 @@ function discoverCentralWorkItems(config) {
|
|
|
2156
2162
|
vars.plan_title = item.title;
|
|
2157
2163
|
vars.plan_file = planFileName;
|
|
2158
2164
|
vars.task_description = item.title;
|
|
2159
|
-
|
|
2160
|
-
try { vars.notes_content = fs.readFileSync(path.join(MINIONS_DIR, 'notes.md'), 'utf8'); } catch { /* optional */ }
|
|
2165
|
+
// Notes already populated by buildWorkItemDispatchVars — no need to re-read
|
|
2161
2166
|
// Track expected plan filename in meta for chainPlanToPrd
|
|
2162
2167
|
mutations.set(item.id, Object.assign(mutations.get(item.id) || {}, { _planFileName: planFileName }));
|
|
2163
2168
|
}
|
|
@@ -2191,20 +2196,7 @@ function discoverCentralWorkItems(config) {
|
|
|
2191
2196
|
: 'Choose the best strategy based on your analysis of item dependencies.';
|
|
2192
2197
|
}
|
|
2193
2198
|
|
|
2194
|
-
//
|
|
2195
|
-
if (workType === WORK_TYPE.ASK) {
|
|
2196
|
-
vars.question = item.title + (item.description ? '\n\n' + item.description : '');
|
|
2197
|
-
vars.task_id = item.id;
|
|
2198
|
-
vars.notes_content = '';
|
|
2199
|
-
try { vars.notes_content = fs.readFileSync(path.join(MINIONS_DIR, 'notes.md'), 'utf8'); } catch { /* optional */ }
|
|
2200
|
-
}
|
|
2201
|
-
|
|
2202
|
-
// Resolve implicit context references
|
|
2203
|
-
const resolvedCtx = resolveTaskContext(item, config);
|
|
2204
|
-
if (resolvedCtx.additionalContext) {
|
|
2205
|
-
vars.additional_context = (vars.additional_context || '') + resolvedCtx.additionalContext;
|
|
2206
|
-
vars.task_description = vars.task_description + resolvedCtx.additionalContext;
|
|
2207
|
-
}
|
|
2199
|
+
// ASK and resolveTaskContext already handled by buildWorkItemDispatchVars above
|
|
2208
2200
|
|
|
2209
2201
|
const playbookName = selectPlaybook(workType, item);
|
|
2210
2202
|
const prompt = renderPlaybook(playbookName, vars) || renderPlaybook('work-item', vars);
|
|
@@ -2733,7 +2725,7 @@ module.exports = {
|
|
|
2733
2725
|
reconcileItemsWithPrs, detectDependencyCycles,
|
|
2734
2726
|
|
|
2735
2727
|
// Playbooks
|
|
2736
|
-
renderPlaybook,
|
|
2728
|
+
renderPlaybook, buildWorkItemDispatchVars,
|
|
2737
2729
|
|
|
2738
2730
|
// Timeout / Steering / Idle (re-exported from engine/timeout.js)
|
|
2739
2731
|
checkTimeouts, checkSteering, checkIdleThreshold,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.549",
|
|
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"
|