@yemi33/minions 0.1.227 → 0.1.229
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 +15 -1
- package/dashboard/js/render-other.js +136 -5
- package/dashboard/layout.html +2 -2
- package/dashboard.js +65 -11
- package/engine/cleanup.js +5 -2
- package/engine/lifecycle.js +76 -50
- package/engine/playbook.js +1 -1
- package/package.json +1 -1
- package/playbooks/evaluate.md +149 -0
- package/routing.md +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.229 (2026-04-03)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
- consolidate temp agent metrics into one row
|
|
7
|
+
|
|
8
|
+
## 0.1.228 (2026-04-03)
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
- scan for projects UI — modal with checkbox multi-select
|
|
12
|
+
|
|
13
|
+
### Fixes
|
|
14
|
+
- CC system prompt now defaults to delegating work to agents
|
|
15
|
+
|
|
16
|
+
## 0.1.226 (2026-04-03)
|
|
4
17
|
|
|
5
18
|
### Features
|
|
19
|
+
- add evaluate type to routing and playbooks (#70)
|
|
6
20
|
- fix cooldown context accumulation bloat (#71)
|
|
7
21
|
- wire dead test and extract progress bar helper (#64)
|
|
8
22
|
|
|
@@ -16,7 +16,8 @@ function renderProjects(projects) {
|
|
|
16
16
|
(p.description ? '<span style="color:var(--muted);font-weight:400;margin-left:6px;font-size:10px">' + escHtml(p.description.slice(0, 60)) + (p.description.length > 60 ? '...' : '') + '</span>' : '') +
|
|
17
17
|
'</span>'
|
|
18
18
|
).join('') +
|
|
19
|
-
'<span onclick="addProject()" style="background:var(--surface2);border:1px solid var(--border);border-radius:4px;padding:3px 10px;color:var(--muted);font-weight:500;cursor:pointer;border-style:dashed">+ Add</span>'
|
|
19
|
+
'<span onclick="addProject()" style="background:var(--surface2);border:1px solid var(--border);border-radius:4px;padding:3px 10px;color:var(--muted);font-weight:500;cursor:pointer;border-style:dashed">+ Add</span>' +
|
|
20
|
+
'<span onclick="openScanProjectsModal()" style="background:var(--surface2);border:1px solid var(--border);border-radius:4px;padding:3px 10px;color:var(--blue);font-weight:500;cursor:pointer;border-style:dashed;font-size:10px">Scan</span>';
|
|
20
21
|
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -47,8 +48,26 @@ function renderMetrics(metrics) {
|
|
|
47
48
|
renderContextPressure(metrics);
|
|
48
49
|
return;
|
|
49
50
|
}
|
|
51
|
+
// Consolidate temp-* agents into one row
|
|
52
|
+
var permanent = agents.filter(function(a) { return !a[0].startsWith('temp-'); });
|
|
53
|
+
var temps = agents.filter(function(a) { return a[0].startsWith('temp-'); });
|
|
54
|
+
var rows = permanent.slice();
|
|
55
|
+
if (temps.length > 0) {
|
|
56
|
+
var merged = { tasksCompleted: 0, tasksErrored: 0, prsCreated: 0, prsApproved: 0, prsRejected: 0, reviewsDone: 0 };
|
|
57
|
+
for (var t = 0; t < temps.length; t++) {
|
|
58
|
+
var tm = temps[t][1];
|
|
59
|
+
merged.tasksCompleted += tm.tasksCompleted || 0;
|
|
60
|
+
merged.tasksErrored += tm.tasksErrored || 0;
|
|
61
|
+
merged.prsCreated += tm.prsCreated || 0;
|
|
62
|
+
merged.prsApproved += tm.prsApproved || 0;
|
|
63
|
+
merged.prsRejected += tm.prsRejected || 0;
|
|
64
|
+
merged.reviewsDone += tm.reviewsDone || 0;
|
|
65
|
+
}
|
|
66
|
+
rows.push(['Temp Agents (' + temps.length + ')', merged]);
|
|
67
|
+
}
|
|
68
|
+
|
|
50
69
|
let html = '<table class="pr-table"><thead><tr><th>Agent</th><th>Done</th><th>Errors</th><th>PRs</th><th>Approved</th><th>Rejected</th><th>Rate</th><th>Reviews</th></tr></thead><tbody>';
|
|
51
|
-
for (const [id, m] of
|
|
70
|
+
for (const [id, m] of rows) {
|
|
52
71
|
const rate = m.prsCreated > 0 ? Math.round((m.prsApproved / m.prsCreated) * 100) + '%' : '-';
|
|
53
72
|
const rateColor = m.prsCreated > 0 ? (m.prsApproved / m.prsCreated >= 0.7 ? 'var(--green)' : 'var(--red)') : 'var(--muted)';
|
|
54
73
|
html += '<tr>' +
|
|
@@ -140,8 +159,25 @@ function renderTokenUsage(metrics) {
|
|
|
140
159
|
html += '</div>';
|
|
141
160
|
}
|
|
142
161
|
|
|
143
|
-
// Per-agent token table
|
|
144
|
-
|
|
162
|
+
// Per-agent token table (consolidate temp agents)
|
|
163
|
+
var permAgents = agents.filter(function(a) { return !a[0].startsWith('temp-'); });
|
|
164
|
+
var tempAgents = agents.filter(function(a) { return a[0].startsWith('temp-'); });
|
|
165
|
+
var tokenRows = permAgents.slice();
|
|
166
|
+
if (tempAgents.length > 0) {
|
|
167
|
+
var tempMerged = { totalCostUsd: 0, totalInputTokens: 0, totalOutputTokens: 0, totalCacheRead: 0, tasksCompleted: 0, tasksErrored: 0, model: '' };
|
|
168
|
+
for (var ti = 0; ti < tempAgents.length; ti++) {
|
|
169
|
+
var tm = tempAgents[ti][1];
|
|
170
|
+
tempMerged.totalCostUsd += tm.totalCostUsd || 0;
|
|
171
|
+
tempMerged.totalInputTokens += tm.totalInputTokens || 0;
|
|
172
|
+
tempMerged.totalOutputTokens += tm.totalOutputTokens || 0;
|
|
173
|
+
tempMerged.totalCacheRead += tm.totalCacheRead || 0;
|
|
174
|
+
tempMerged.tasksCompleted += tm.tasksCompleted || 0;
|
|
175
|
+
tempMerged.tasksErrored += tm.tasksErrored || 0;
|
|
176
|
+
if (!tempMerged.model && tm.model) tempMerged.model = tm.model;
|
|
177
|
+
}
|
|
178
|
+
tokenRows.push(['Temp Agents (' + tempAgents.length + ')', tempMerged]);
|
|
179
|
+
}
|
|
180
|
+
var agentsWithUsage = tokenRows.filter(function(a) { return (a[1].totalCostUsd || 0) > 0; });
|
|
145
181
|
if (agentsWithUsage.length > 0) {
|
|
146
182
|
html += '<div style="font-size:10px;color:var(--muted);margin:12px 0 4px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px">Agent Usage</div>';
|
|
147
183
|
html += '<table class="token-agent-table"><thead><tr><th>Agent</th><th>Model</th><th>Cost</th><th>Input</th><th>Output</th><th>Cache</th><th>$/task</th></tr></thead><tbody>';
|
|
@@ -206,4 +242,99 @@ function renderContextPressure(metrics) {
|
|
|
206
242
|
el.innerHTML = html;
|
|
207
243
|
}
|
|
208
244
|
|
|
209
|
-
|
|
245
|
+
async function openScanProjectsModal() {
|
|
246
|
+
document.getElementById('modal-title').textContent = 'Scan for Projects';
|
|
247
|
+
document.getElementById('modal-body').innerHTML =
|
|
248
|
+
'<div style="display:flex;flex-direction:column;gap:12px">' +
|
|
249
|
+
'<div style="display:flex;gap:8px;align-items:flex-end">' +
|
|
250
|
+
'<label style="flex:1;color:var(--text);font-size:var(--text-md)">Directory to scan' +
|
|
251
|
+
'<input id="scan-path" value="' + escHtml((typeof os !== 'undefined' ? os.homedir() : '~') || '~') + '" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md)">' +
|
|
252
|
+
'</label>' +
|
|
253
|
+
'<label style="width:60px;color:var(--text);font-size:var(--text-md)">Depth' +
|
|
254
|
+
'<input id="scan-depth" type="number" value="3" min="1" max="6" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md)">' +
|
|
255
|
+
'</label>' +
|
|
256
|
+
'<button onclick="_runProjectScan()" style="padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer;white-space:nowrap">Scan</button>' +
|
|
257
|
+
'</div>' +
|
|
258
|
+
'<div id="scan-results" style="color:var(--muted);font-size:12px">Click Scan to find git repos in the directory.</div>' +
|
|
259
|
+
'</div>';
|
|
260
|
+
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
261
|
+
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
262
|
+
document.getElementById('modal').classList.add('open');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function _runProjectScan() {
|
|
266
|
+
var scanPath = document.getElementById('scan-path')?.value?.trim();
|
|
267
|
+
var depth = document.getElementById('scan-depth')?.value || '3';
|
|
268
|
+
var resultsEl = document.getElementById('scan-results');
|
|
269
|
+
if (!scanPath) { alert('Enter a path'); return; }
|
|
270
|
+
resultsEl.innerHTML = '<span style="color:var(--blue)">Scanning...</span>';
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
var res = await fetch('/api/projects/scan', {
|
|
274
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
275
|
+
body: JSON.stringify({ path: scanPath, depth: Number(depth) })
|
|
276
|
+
});
|
|
277
|
+
var data = await res.json();
|
|
278
|
+
if (!res.ok) { resultsEl.innerHTML = '<span style="color:var(--red)">Error: ' + escHtml(data.error) + '</span>'; return; }
|
|
279
|
+
var repos = data.repos || [];
|
|
280
|
+
if (repos.length === 0) { resultsEl.innerHTML = '<span style="color:var(--muted)">No git repos found in ' + escHtml(scanPath) + '</span>'; return; }
|
|
281
|
+
|
|
282
|
+
var html = '<div style="margin-bottom:8px;font-size:11px;color:var(--muted)">' + repos.length + ' repos found — select to add:</div>';
|
|
283
|
+
html += '<div style="max-height:400px;overflow-y:auto;display:flex;flex-direction:column;gap:4px">';
|
|
284
|
+
repos.forEach(function(r, i) {
|
|
285
|
+
var linked = r.linked;
|
|
286
|
+
var hostBadge = '<span style="font-size:9px;padding:1px 5px;border-radius:3px;background:' +
|
|
287
|
+
(r.host === 'GitHub' ? 'rgba(88,166,255,0.15);color:var(--blue)' : r.host === 'ADO' ? 'rgba(188,140,255,0.15);color:var(--purple)' : 'var(--surface2);color:var(--muted)') +
|
|
288
|
+
'">' + escHtml(r.host) + '</span>';
|
|
289
|
+
html += '<label style="display:flex;align-items:center;gap:8px;padding:6px 10px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;cursor:' + (linked ? 'default' : 'pointer') + ';opacity:' + (linked ? '0.5' : '1') + '">' +
|
|
290
|
+
'<input type="checkbox" data-scan-idx="' + i + '" ' + (linked ? 'disabled checked' : '') + ' style="accent-color:var(--blue);width:16px;height:16px">' +
|
|
291
|
+
'<div style="flex:1;min-width:0">' +
|
|
292
|
+
'<div style="font-weight:600;font-size:12px">' + escHtml(r.name) + ' ' + hostBadge + (linked ? ' <span style="font-size:9px;color:var(--green)">linked</span>' : '') + '</div>' +
|
|
293
|
+
'<div style="font-size:10px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + escHtml(r.path) + '</div>' +
|
|
294
|
+
(r.description ? '<div style="font-size:10px;color:var(--muted)">' + escHtml(r.description) + '</div>' : '') +
|
|
295
|
+
'</div>' +
|
|
296
|
+
'</label>';
|
|
297
|
+
});
|
|
298
|
+
html += '</div>';
|
|
299
|
+
html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px">' +
|
|
300
|
+
'<div style="display:flex;gap:8px">' +
|
|
301
|
+
'<span onclick="_scanSelectAll()" style="font-size:10px;color:var(--blue);cursor:pointer;text-decoration:underline">Select all</span>' +
|
|
302
|
+
'<span onclick="_scanSelectNone()" style="font-size:10px;color:var(--muted);cursor:pointer;text-decoration:underline">Clear</span>' +
|
|
303
|
+
'</div>' +
|
|
304
|
+
'<button onclick="_addSelectedProjects()" style="padding:6px 16px;background:var(--green);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer">Add Selected</button>' +
|
|
305
|
+
'</div>';
|
|
306
|
+
resultsEl.innerHTML = html;
|
|
307
|
+
window._scanRepos = repos;
|
|
308
|
+
} catch (e) { resultsEl.innerHTML = '<span style="color:var(--red)">Error: ' + escHtml(e.message) + '</span>'; }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function _scanSelectAll() {
|
|
312
|
+
document.querySelectorAll('[data-scan-idx]').forEach(function(cb) { if (!cb.disabled) cb.checked = true; });
|
|
313
|
+
}
|
|
314
|
+
function _scanSelectNone() {
|
|
315
|
+
document.querySelectorAll('[data-scan-idx]').forEach(function(cb) { if (!cb.disabled) cb.checked = false; });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function _addSelectedProjects() {
|
|
319
|
+
var checkboxes = document.querySelectorAll('[data-scan-idx]:checked:not(:disabled)');
|
|
320
|
+
if (checkboxes.length === 0) { alert('Select at least one repo'); return; }
|
|
321
|
+
var repos = window._scanRepos || [];
|
|
322
|
+
var added = 0;
|
|
323
|
+
for (var cb of checkboxes) {
|
|
324
|
+
var repo = repos[parseInt(cb.dataset.scanIdx)];
|
|
325
|
+
if (!repo) continue;
|
|
326
|
+
try {
|
|
327
|
+
var res = await fetch('/api/projects/add', {
|
|
328
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
329
|
+
body: JSON.stringify({ path: repo.path })
|
|
330
|
+
});
|
|
331
|
+
if (res.ok) { added++; cb.disabled = true; cb.closest('label').style.opacity = '0.5'; }
|
|
332
|
+
} catch { /* continue with next */ }
|
|
333
|
+
}
|
|
334
|
+
if (added > 0) {
|
|
335
|
+
showToast('cmd-toast', added + ' project(s) added', true);
|
|
336
|
+
refresh();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
window.MinionsOther = { renderProjects, renderMcpServers, renderMetrics, renderTokenUsage, renderContextPressure, openScanProjectsModal };
|
package/dashboard/layout.html
CHANGED
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
<div style="font-size:18px;margin-bottom:6px;text-align:center">Getting Started</div>
|
|
53
53
|
<div style="color:var(--muted);font-size:13px;margin-bottom:12px;text-align:center">No projects linked yet. Link a project so agents know what code to work on.</div>
|
|
54
54
|
<div style="display:flex;flex-direction:column;gap:8px;align-items:center">
|
|
55
|
-
<
|
|
56
|
-
<div style="font-size:11px;color:var(--muted)">
|
|
55
|
+
<button onclick="openScanProjectsModal()" style="padding:8px 20px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer;font-size:13px;font-weight:600">Scan for Projects</button>
|
|
56
|
+
<div style="font-size:11px;color:var(--muted)">Or via CLI: <code style="background:var(--bg);padding:2px 6px;border-radius:3px;font-size:11px">minions add <path-to-your-repo></code></div>
|
|
57
57
|
</div>
|
|
58
58
|
</div>
|
|
59
59
|
|
package/dashboard.js
CHANGED
|
@@ -340,19 +340,26 @@ ${MINIONS_DIR}/
|
|
|
340
340
|
|
|
341
341
|
Projects are configured in \`config.json\` under \`projects[]\`. Per-project state lives centrally in \`${MINIONS_DIR}/projects/{name}/\` — NOT inside project repos. There are no \`.minions/\` folders inside project repos.
|
|
342
342
|
|
|
343
|
-
##
|
|
343
|
+
## Default: Delegate to Agents
|
|
344
344
|
|
|
345
|
-
|
|
346
|
-
- **Build & run projects** — \`cd <project> && npm install && npm run dev\`
|
|
347
|
-
- **Inspect code** — read files, grep, explore
|
|
348
|
-
- **Edit project files** — fix configs, update docs, tweak settings
|
|
349
|
-
- **Git operations** — fetch, checkout, merge, diff (but do NOT push without the user confirming)
|
|
350
|
-
- **Start dev servers** — for long-running servers, use detached processes so they survive after you finish
|
|
345
|
+
Your primary job is to **orchestrate**, not implement. For most requests, dispatch work to agents rather than doing it yourself. Agents have full Claude Code sessions with project context, worktrees, and MCP tools — they are better equipped for real work.
|
|
351
346
|
|
|
352
|
-
**
|
|
353
|
-
-
|
|
354
|
-
-
|
|
355
|
-
-
|
|
347
|
+
**Delegate to agents (default):**
|
|
348
|
+
- Code changes of any size — even "small" fixes
|
|
349
|
+
- Bug fixes, feature implementation, refactoring
|
|
350
|
+
- PR creation and code review
|
|
351
|
+
- Testing and verification
|
|
352
|
+
- Codebase exploration and architecture analysis
|
|
353
|
+
- Plan creation and execution
|
|
354
|
+
|
|
355
|
+
**Do it yourself (only these):**
|
|
356
|
+
- Reading a specific file or status to answer a question
|
|
357
|
+
- Quick lookups (check a config value, find a file, read an error log)
|
|
358
|
+
- Creating notes, editing plans, updating routing/charters
|
|
359
|
+
- Starting a dev server or running a build command the user asked for
|
|
360
|
+
- Git operations the user explicitly asked YOU to do
|
|
361
|
+
|
|
362
|
+
When in doubt, **dispatch an agent**. The user is talking to you because they want work delegated, not because they want you to be the one coding.
|
|
356
363
|
|
|
357
364
|
## Minions Actions (Delegation)
|
|
358
365
|
|
|
@@ -2904,6 +2911,52 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
2904
2911
|
} catch (e) { return jsonReply(res, 400, { error: e.message }); }
|
|
2905
2912
|
}
|
|
2906
2913
|
|
|
2914
|
+
async function handleProjectsScan(req, res) {
|
|
2915
|
+
try {
|
|
2916
|
+
const body = await readBody(req);
|
|
2917
|
+
const scanRoot = path.resolve(body.path || os.homedir());
|
|
2918
|
+
const maxDepth = Math.min(Number(body.depth) || 3, 6);
|
|
2919
|
+
if (!fs.existsSync(scanRoot)) return jsonReply(res, 400, { error: 'path does not exist' });
|
|
2920
|
+
|
|
2921
|
+
// Find git repos recursively (same logic as minions.js findGitRepos)
|
|
2922
|
+
const skipDirs = new Set(['node_modules', '.git', '.hg', 'AppData', '$Recycle.Bin', 'Windows',
|
|
2923
|
+
'Program Files', 'Program Files (x86)', '.cache', '.npm', '.yarn', '.nuget', 'worktrees', '.minions', '.squad']);
|
|
2924
|
+
const repos = [];
|
|
2925
|
+
function walk(dir, depth) {
|
|
2926
|
+
if (depth > maxDepth) return;
|
|
2927
|
+
let entries;
|
|
2928
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
2929
|
+
for (const e of entries) {
|
|
2930
|
+
if (!e.isDirectory() || (e.name.startsWith('.') && e.name !== '.git') || skipDirs.has(e.name)) continue;
|
|
2931
|
+
if (e.name === '.git') { repos.push(dir); return; }
|
|
2932
|
+
walk(path.join(dir, e.name), depth + 1);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
walk(scanRoot, 0);
|
|
2936
|
+
|
|
2937
|
+
// Enrich each repo with metadata
|
|
2938
|
+
const existingPaths = new Set(PROJECTS.map(p => path.resolve(p.localPath)));
|
|
2939
|
+
const results = repos.map(repoPath => {
|
|
2940
|
+
const result = { path: repoPath.replace(/\\/g, '/'), name: path.basename(repoPath), host: 'git', linked: existingPaths.has(path.resolve(repoPath)) };
|
|
2941
|
+
try {
|
|
2942
|
+
const remoteUrl = require('child_process').execSync('git remote get-url origin', { cwd: repoPath, encoding: 'utf8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
2943
|
+
const gh = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
|
2944
|
+
const ado = remoteUrl.match(/dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/\s]+)/) || remoteUrl.match(/([^.]+)\.visualstudio\.com.*?\/([^/]+)\/_git\/([^/\s]+)/);
|
|
2945
|
+
if (gh) { result.host = 'GitHub'; result.org = gh[1]; result.name = gh[2]; }
|
|
2946
|
+
else if (ado) { result.host = 'ADO'; result.org = ado[1]; result.name = ado[3] || ado[2]; }
|
|
2947
|
+
} catch { /* no remote */ }
|
|
2948
|
+
try {
|
|
2949
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(repoPath, 'package.json'), 'utf8'));
|
|
2950
|
+
if (pkg.name) result.name = pkg.name.replace(/@[^/]+\//, '');
|
|
2951
|
+
if (pkg.description) result.description = pkg.description.slice(0, 100);
|
|
2952
|
+
} catch { /* no package.json */ }
|
|
2953
|
+
return result;
|
|
2954
|
+
});
|
|
2955
|
+
|
|
2956
|
+
return jsonReply(res, 200, { repos: results });
|
|
2957
|
+
} catch (e) { return jsonReply(res, 500, { error: e.message }); }
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2907
2960
|
async function handleCommandCenterNewSession(req, res) {
|
|
2908
2961
|
ccSession = { sessionId: null, createdAt: null, lastActiveAt: null, turnCount: 0 };
|
|
2909
2962
|
ccInFlight = false; // Reset concurrency guard so a stuck request doesn't block new sessions
|
|
@@ -3444,6 +3497,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3444
3497
|
|
|
3445
3498
|
// Projects
|
|
3446
3499
|
{ method: 'POST', path: '/api/projects/browse', desc: 'Open folder picker dialog, return selected path', handler: handleProjectsBrowse },
|
|
3500
|
+
{ method: 'POST', path: '/api/projects/scan', desc: 'Scan a directory for git repos', params: 'path?, depth?', handler: handleProjectsScan },
|
|
3447
3501
|
{ method: 'POST', path: '/api/projects/add', desc: 'Auto-discover and add a project to config', params: 'path, name?', handler: handleProjectsAdd },
|
|
3448
3502
|
|
|
3449
3503
|
// Command Center
|
package/engine/cleanup.js
CHANGED
|
@@ -35,20 +35,23 @@ function runCleanup(config, verbose = false) {
|
|
|
35
35
|
const projects = getProjects(config);
|
|
36
36
|
let cleaned = { tempFiles: 0, liveOutputs: 0, worktrees: 0, zombies: 0 };
|
|
37
37
|
|
|
38
|
-
// 1. Clean stale temp prompt/sysprompt files (older than 1 hour)
|
|
38
|
+
// 1. Clean stale temp prompt/sysprompt files and orphaned safeWrite .tmp.* files (older than 1 hour)
|
|
39
39
|
const oneHourAgo = Date.now() - 3600000;
|
|
40
40
|
try {
|
|
41
41
|
const tmpDir = path.join(ENGINE_DIR, 'tmp');
|
|
42
42
|
const scanDirs = [ENGINE_DIR, ...(fs.existsSync(tmpDir) ? [tmpDir] : [])];
|
|
43
43
|
for (const dir of scanDirs) {
|
|
44
44
|
for (const f of fs.readdirSync(dir)) {
|
|
45
|
-
|
|
45
|
+
const isPromptTemp = f.startsWith('prompt-') || f.startsWith('sysprompt-') || f.startsWith('tmp-sysprompt-');
|
|
46
|
+
const isSafeWriteTemp = /\.tmp\.\d+\.\d+$/.test(f);
|
|
47
|
+
if (isPromptTemp || isSafeWriteTemp) {
|
|
46
48
|
const fp = path.join(dir, f);
|
|
47
49
|
try {
|
|
48
50
|
const stat = fs.statSync(fp);
|
|
49
51
|
if (stat.mtimeMs < oneHourAgo) {
|
|
50
52
|
fs.unlinkSync(fp);
|
|
51
53
|
cleaned.tempFiles++;
|
|
54
|
+
if (isSafeWriteTemp) log('info', `Cleaned orphaned temp file: ${f}`);
|
|
52
55
|
}
|
|
53
56
|
} catch { /* cleanup */ }
|
|
54
57
|
}
|
package/engine/lifecycle.js
CHANGED
|
@@ -630,7 +630,22 @@ function syncPrsFromOutput(output, agentId, meta, config) {
|
|
|
630
630
|
}
|
|
631
631
|
|
|
632
632
|
for (const [name, entry] of dirtyTargets) {
|
|
633
|
-
|
|
633
|
+
mutateJsonFileLocked(entry.prPath, (existingPrs) => {
|
|
634
|
+
// Merge new PRs into existing data to avoid losing concurrent writes
|
|
635
|
+
for (const pr of entry.prs) {
|
|
636
|
+
const idx = existingPrs.findIndex(p => p.id === pr.id);
|
|
637
|
+
if (idx >= 0) {
|
|
638
|
+
// Backfill prdItems if needed
|
|
639
|
+
if (pr.prdItems?.length) {
|
|
640
|
+
const merged = new Set([...(existingPrs[idx].prdItems || []), ...pr.prdItems]);
|
|
641
|
+
existingPrs[idx].prdItems = [...merged];
|
|
642
|
+
}
|
|
643
|
+
} else {
|
|
644
|
+
existingPrs.push(pr);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return existingPrs;
|
|
648
|
+
}, { defaultValue: [] });
|
|
634
649
|
log('info', `Synced PR(s) from ${agentName}'s output to ${name === '_central' ? 'central' : name}/pull-requests.json`);
|
|
635
650
|
}
|
|
636
651
|
return added;
|
|
@@ -668,11 +683,12 @@ function updatePrAfterReview(agentId, pr, project) {
|
|
|
668
683
|
const authorAgentId = (pr.agent || '').toLowerCase();
|
|
669
684
|
if (authorAgentId && config.agents?.[authorAgentId]) {
|
|
670
685
|
const metricsPath = path.join(ENGINE_DIR, 'metrics.json');
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
686
|
+
mutateJsonFileLocked(metricsPath, (metrics) => {
|
|
687
|
+
if (!metrics[authorAgentId]) metrics[authorAgentId] = { tasksCompleted:0, tasksErrored:0, prsCreated:0, prsApproved:0, prsRejected:0, reviewsDone:0, lastTask:null, lastCompleted:null };
|
|
688
|
+
if (!metrics[agentId]) metrics[agentId] = { tasksCompleted:0, tasksErrored:0, prsCreated:0, prsApproved:0, prsRejected:0, reviewsDone:0, lastTask:null, lastCompleted:null };
|
|
689
|
+
metrics[agentId].reviewsDone = (metrics[agentId].reviewsDone || 0) + 1;
|
|
690
|
+
return metrics;
|
|
691
|
+
});
|
|
676
692
|
}
|
|
677
693
|
|
|
678
694
|
shared.safeWrite(project ? shared.projectPrPath(project) : path.join(path.resolve(MINIONS_DIR, '..'), '.minions', 'pull-requests.json'), prs);
|
|
@@ -747,12 +763,16 @@ async function handlePostMerge(pr, project, config, newStatus) {
|
|
|
747
763
|
const planFiles = fs.readdirSync(prdDir).filter(f => f.endsWith('.json'));
|
|
748
764
|
let updated = 0;
|
|
749
765
|
for (const pf of planFiles) {
|
|
750
|
-
const
|
|
766
|
+
const prdPath = path.join(prdDir, pf);
|
|
767
|
+
const plan = safeJson(prdPath);
|
|
751
768
|
if (!plan?.missing_features) continue;
|
|
752
769
|
const feature = plan.missing_features.find(f => f.id === mergedItemId);
|
|
753
770
|
if (feature && feature.status !== 'implemented') {
|
|
754
|
-
|
|
755
|
-
|
|
771
|
+
mutateJsonFileLocked(prdPath, (data) => {
|
|
772
|
+
const feat = data.missing_features?.find(f => f.id === mergedItemId);
|
|
773
|
+
if (feat && feat.status !== 'implemented') feat.status = 'implemented';
|
|
774
|
+
return data;
|
|
775
|
+
});
|
|
756
776
|
updated++;
|
|
757
777
|
}
|
|
758
778
|
}
|
|
@@ -764,17 +784,19 @@ async function handlePostMerge(pr, project, config, newStatus) {
|
|
|
764
784
|
for (const p of shared.getProjects(config)) wiPaths.push(shared.projectWorkItemsPath(p));
|
|
765
785
|
for (const wiPath of wiPaths) {
|
|
766
786
|
try {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
787
|
+
let found = false;
|
|
788
|
+
mutateJsonFileLocked(wiPath, (items) => {
|
|
789
|
+
const item = items.find(i => i.id === mergedItemId);
|
|
790
|
+
if (item && item.status !== 'done') {
|
|
791
|
+
log('info', `Post-merge: marking work item ${mergedItemId} as done (was ${item.status}) for ${pr.id}`);
|
|
792
|
+
item.status = 'done';
|
|
793
|
+
item.completedAt = ts();
|
|
794
|
+
item._mergedVia = pr.id;
|
|
795
|
+
found = true;
|
|
796
|
+
}
|
|
797
|
+
return items;
|
|
798
|
+
}, { defaultValue: [] });
|
|
799
|
+
if (found) break;
|
|
778
800
|
} catch (err) { log('warn', `Post-merge work item update: ${err.message}`); }
|
|
779
801
|
}
|
|
780
802
|
}
|
|
@@ -782,10 +804,11 @@ async function handlePostMerge(pr, project, config, newStatus) {
|
|
|
782
804
|
const agentId = (pr.agent || '').toLowerCase();
|
|
783
805
|
if (agentId && config.agents?.[agentId]) {
|
|
784
806
|
const metricsPath = path.join(ENGINE_DIR, 'metrics.json');
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
807
|
+
mutateJsonFileLocked(metricsPath, (metrics) => {
|
|
808
|
+
if (!metrics[agentId]) metrics[agentId] = { tasksCompleted:0, tasksErrored:0, prsCreated:0, prsApproved:0, prsRejected:0, prsMerged:0, reviewsDone:0, lastTask:null, lastCompleted:null };
|
|
809
|
+
metrics[agentId].prsMerged = (metrics[agentId].prsMerged || 0) + 1;
|
|
810
|
+
return metrics;
|
|
811
|
+
});
|
|
789
812
|
}
|
|
790
813
|
|
|
791
814
|
const teamsUrl = process.env.TEAMS_PLAN_FLOW_URL;
|
|
@@ -1229,14 +1252,15 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1229
1252
|
try {
|
|
1230
1253
|
const wiPath = resolveWiPath(meta);
|
|
1231
1254
|
if (wiPath) {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1255
|
+
mutateJsonFileLocked(wiPath, (items) => {
|
|
1256
|
+
// For fix items, target the original implement parent; for implement, target self
|
|
1257
|
+
const evalTargetId = type === 'fix' ? meta.item._evalParentId : meta.item.id;
|
|
1258
|
+
// Dedup: skip if an evaluate item already exists for this parent
|
|
1259
|
+
const existing = items.find(i => i._evalParentId === evalTargetId && i.type === 'evaluate' && i.status === 'pending');
|
|
1260
|
+
if (existing) {
|
|
1261
|
+
log('info', `Eval loop: evaluate item ${existing.id} already exists for ${evalTargetId}, skipping`);
|
|
1262
|
+
return items;
|
|
1263
|
+
}
|
|
1240
1264
|
const parentItem = items.find(i => i.id === evalTargetId);
|
|
1241
1265
|
const evalItem = {
|
|
1242
1266
|
id: 'W-' + shared.uid(),
|
|
@@ -1256,9 +1280,9 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1256
1280
|
// Mark parent as eval-dispatched before writing to prevent duplicates on re-run
|
|
1257
1281
|
if (parentItem) parentItem._evalDispatched = true;
|
|
1258
1282
|
items.push(evalItem);
|
|
1259
|
-
shared.safeWrite(wiPath, items);
|
|
1260
1283
|
log('info', `Eval loop: created ${evalItem.id} for completed ${type} ${meta.item.id} (parent: ${evalTargetId})`);
|
|
1261
|
-
|
|
1284
|
+
return items;
|
|
1285
|
+
}, { defaultValue: [] });
|
|
1262
1286
|
}
|
|
1263
1287
|
} catch (err) {
|
|
1264
1288
|
log('warn', `Eval loop dispatch error: ${err.message}`);
|
|
@@ -1276,9 +1300,9 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1276
1300
|
if (verdict && !verdict.pass && evalLoop) {
|
|
1277
1301
|
const wiPath = resolveWiPath(meta);
|
|
1278
1302
|
if (wiPath) {
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1303
|
+
mutateJsonFileLocked(wiPath, (items) => {
|
|
1304
|
+
const parent = items.find(i => i.id === meta.item._evalParentId);
|
|
1305
|
+
if (!parent) return items;
|
|
1282
1306
|
const iterations = (parent._evalIterations || 0) + 1;
|
|
1283
1307
|
parent._evalIterations = iterations;
|
|
1284
1308
|
|
|
@@ -1286,7 +1310,6 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1286
1310
|
// Max iterations reached — escalate to human review
|
|
1287
1311
|
parent.status = 'needs-human-review';
|
|
1288
1312
|
parent._evalEscalatedAt = ts();
|
|
1289
|
-
shared.safeWrite(wiPath, items);
|
|
1290
1313
|
log('info', `Eval loop: ${parent.id} reached ${iterations}/${maxIter} iterations — escalated to needs-human-review`);
|
|
1291
1314
|
} else {
|
|
1292
1315
|
// Create fix work item with evaluator feedback
|
|
@@ -1312,10 +1335,10 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1312
1335
|
// Parent stays 'done' — fix item picks up from here
|
|
1313
1336
|
parent.status = 'done';
|
|
1314
1337
|
items.push(fixItem);
|
|
1315
|
-
shared.safeWrite(wiPath, items);
|
|
1316
1338
|
log('info', `Eval loop: created fix ${fixItem.id} for failed eval on ${parent.id} (iteration ${iterations}/${maxIter})`);
|
|
1317
1339
|
}
|
|
1318
|
-
|
|
1340
|
+
return items;
|
|
1341
|
+
}, { defaultValue: [] });
|
|
1319
1342
|
}
|
|
1320
1343
|
}
|
|
1321
1344
|
} catch (err) {
|
|
@@ -1345,13 +1368,14 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1345
1368
|
? path.join(MINIONS_DIR, 'work-items.json')
|
|
1346
1369
|
: meta.project?.name ? path.join(MINIONS_DIR, 'projects', meta.project.name, 'work-items.json') : null;
|
|
1347
1370
|
if (wiPath) {
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1371
|
+
mutateJsonFileLocked(wiPath, (items) => {
|
|
1372
|
+
const wi = items.find(i => i.id === meta.item.id);
|
|
1373
|
+
if (wi) {
|
|
1374
|
+
wi._retryCount = retries + 1; wi.status = 'pending'; delete wi.dispatched_at; delete wi.dispatched_to;
|
|
1375
|
+
if (type === 'decompose') delete wi._decomposing; // clear so item can retry decomposition
|
|
1376
|
+
}
|
|
1377
|
+
return items;
|
|
1378
|
+
}, { defaultValue: [] });
|
|
1355
1379
|
}
|
|
1356
1380
|
} catch (err) { log('warn', `Retry update: ${err.message}`); }
|
|
1357
1381
|
} else {
|
|
@@ -1364,9 +1388,11 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1364
1388
|
? path.join(MINIONS_DIR, 'work-items.json')
|
|
1365
1389
|
: meta.project?.name ? path.join(MINIONS_DIR, 'projects', meta.project.name, 'work-items.json') : null;
|
|
1366
1390
|
if (wiPath) {
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1391
|
+
mutateJsonFileLocked(wiPath, (items) => {
|
|
1392
|
+
const wi = items.find(i => i.id === meta.item.id);
|
|
1393
|
+
if (wi) { delete wi._decomposing; }
|
|
1394
|
+
return items;
|
|
1395
|
+
}, { defaultValue: [] });
|
|
1370
1396
|
}
|
|
1371
1397
|
} catch (err) { log('warn', `Decompose cleanup: ${err.message}`); }
|
|
1372
1398
|
}
|
package/engine/playbook.js
CHANGED
|
@@ -481,7 +481,7 @@ function selectPlaybook(workType, item) {
|
|
|
481
481
|
if (workType === 'review' && !item?._pr && !item?.pr_id) {
|
|
482
482
|
return 'work-item';
|
|
483
483
|
}
|
|
484
|
-
const typeSpecificPlaybooks = ['explore', 'review', 'test', 'plan-to-prd', 'plan', 'ask', 'verify', 'decompose', 'meeting-investigate', 'meeting-debate', 'meeting-conclude'];
|
|
484
|
+
const typeSpecificPlaybooks = ['explore', 'review', 'test', 'plan-to-prd', 'plan', 'ask', 'verify', 'decompose', 'evaluate', 'meeting-investigate', 'meeting-debate', 'meeting-conclude'];
|
|
485
485
|
return typeSpecificPlaybooks.includes(workType) ? workType : 'work-item';
|
|
486
486
|
}
|
|
487
487
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.229",
|
|
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"
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Playbook: Evaluate
|
|
2
|
+
|
|
3
|
+
You are {{agent_name}}, the {{agent_role}} on the {{project_name}} project.
|
|
4
|
+
TEAM ROOT: {{team_root}}
|
|
5
|
+
|
|
6
|
+
## Your Task
|
|
7
|
+
|
|
8
|
+
Evaluate the implementation quality of a completed work item against its acceptance criteria and code quality standards.
|
|
9
|
+
|
|
10
|
+
## Work Item Under Evaluation
|
|
11
|
+
|
|
12
|
+
- **ID:** {{item_id}}
|
|
13
|
+
- **Title:** {{item_title}}
|
|
14
|
+
- **Description:** {{item_description}}
|
|
15
|
+
- **Branch:** `{{branch_name}}`
|
|
16
|
+
- **Project:** {{project_name}} (`{{project_path}}`)
|
|
17
|
+
|
|
18
|
+
{{#acceptance_criteria}}
|
|
19
|
+
## Acceptance Criteria
|
|
20
|
+
|
|
21
|
+
{{acceptance_criteria}}
|
|
22
|
+
{{/acceptance_criteria}}
|
|
23
|
+
|
|
24
|
+
{{#references}}
|
|
25
|
+
## References
|
|
26
|
+
|
|
27
|
+
{{references}}
|
|
28
|
+
{{/references}}
|
|
29
|
+
|
|
30
|
+
## Evaluation Rubric
|
|
31
|
+
|
|
32
|
+
Score each category on a 1-5 scale. A category **passes** at 3 or above.
|
|
33
|
+
|
|
34
|
+
### 1. Correctness (weight: 30%)
|
|
35
|
+
|
|
36
|
+
Does the implementation do what the task description and acceptance criteria require?
|
|
37
|
+
|
|
38
|
+
- **5 — Excellent:** All acceptance criteria met, edge cases handled, no functional gaps
|
|
39
|
+
- **4 — Good:** All core criteria met, minor edge cases not handled
|
|
40
|
+
- **3 — Adequate:** Most criteria met, one minor gap that doesn't block usage
|
|
41
|
+
- **2 — Deficient:** One or more acceptance criteria not met
|
|
42
|
+
- **1 — Failing:** Core functionality missing or broken
|
|
43
|
+
|
|
44
|
+
**Pass threshold:** 3
|
|
45
|
+
|
|
46
|
+
### 2. Completeness (weight: 25%)
|
|
47
|
+
|
|
48
|
+
Is the implementation finished end-to-end? No TODO stubs, no half-wired features, no missing integration points.
|
|
49
|
+
|
|
50
|
+
- **5 — Excellent:** Fully integrated, no loose ends, documentation updated if applicable
|
|
51
|
+
- **4 — Good:** Feature complete, minor polish items remain (comments, naming)
|
|
52
|
+
- **3 — Adequate:** Core feature works, one non-critical integration point incomplete
|
|
53
|
+
- **2 — Deficient:** Significant pieces missing or stubbed out
|
|
54
|
+
- **1 — Failing:** Skeleton or partial implementation only
|
|
55
|
+
|
|
56
|
+
**Pass threshold:** 3
|
|
57
|
+
|
|
58
|
+
### 3. Code Quality (weight: 25%)
|
|
59
|
+
|
|
60
|
+
Does the code follow existing project patterns, naming conventions, and architectural decisions?
|
|
61
|
+
|
|
62
|
+
- **5 — Excellent:** Clean, idiomatic, follows all project conventions, well-structured
|
|
63
|
+
- **4 — Good:** Follows conventions, minor style inconsistencies
|
|
64
|
+
- **3 — Adequate:** Generally follows patterns, one area deviates without justification
|
|
65
|
+
- **2 — Deficient:** Multiple convention violations, poor structure
|
|
66
|
+
- **1 — Failing:** Ignores project patterns, introduces anti-patterns
|
|
67
|
+
|
|
68
|
+
**Pass threshold:** 3
|
|
69
|
+
|
|
70
|
+
### 4. Test Coverage (weight: 20%)
|
|
71
|
+
|
|
72
|
+
Are there tests for the new functionality? Do existing tests still pass?
|
|
73
|
+
|
|
74
|
+
- **5 — Excellent:** Comprehensive tests for happy path and edge cases, all passing
|
|
75
|
+
- **4 — Good:** Tests cover core functionality, existing tests pass
|
|
76
|
+
- **3 — Adequate:** At least one test for the main feature, no regressions
|
|
77
|
+
- **2 — Deficient:** No new tests, but existing tests pass
|
|
78
|
+
- **1 — Failing:** No tests, or existing tests broken
|
|
79
|
+
|
|
80
|
+
**Pass threshold:** 3
|
|
81
|
+
|
|
82
|
+
## Evaluation Steps
|
|
83
|
+
|
|
84
|
+
1. **Fetch and review the diff:**
|
|
85
|
+
```bash
|
|
86
|
+
git fetch origin
|
|
87
|
+
git diff {{main_branch}}...origin/{{branch_name}}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
2. **Check acceptance criteria** one by one — mark each as MET or NOT MET with evidence
|
|
91
|
+
|
|
92
|
+
3. **Review code quality** — check for pattern adherence, naming, structure
|
|
93
|
+
|
|
94
|
+
4. **Verify tests:**
|
|
95
|
+
```bash
|
|
96
|
+
cd {{project_path}}
|
|
97
|
+
npm test
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
5. **Calculate scores** using the rubric above
|
|
101
|
+
|
|
102
|
+
6. **Determine verdict:**
|
|
103
|
+
- **PASS** — all four categories score 3 or above
|
|
104
|
+
- **FAIL** — any category scores below 3
|
|
105
|
+
|
|
106
|
+
## Output Format
|
|
107
|
+
|
|
108
|
+
Structure your evaluation result as follows:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
## Evaluation Result
|
|
112
|
+
|
|
113
|
+
**Item:** {{item_id}} — {{item_title}}
|
|
114
|
+
**Verdict:** PASS | FAIL
|
|
115
|
+
**Weighted Score:** X.X / 5.0
|
|
116
|
+
|
|
117
|
+
### Scores
|
|
118
|
+
|
|
119
|
+
| Category | Score | Pass | Notes |
|
|
120
|
+
|----------|-------|------|-------|
|
|
121
|
+
| Correctness | X/5 | YES/NO | ... |
|
|
122
|
+
| Completeness | X/5 | YES/NO | ... |
|
|
123
|
+
| Code Quality | X/5 | YES/NO | ... |
|
|
124
|
+
| Test Coverage | X/5 | YES/NO | ... |
|
|
125
|
+
|
|
126
|
+
### Acceptance Criteria Checklist
|
|
127
|
+
|
|
128
|
+
- [x] Criterion 1 — evidence
|
|
129
|
+
- [ ] Criterion 2 — what's missing
|
|
130
|
+
|
|
131
|
+
### Issues Found
|
|
132
|
+
|
|
133
|
+
1. **[severity]** Description (file:line)
|
|
134
|
+
|
|
135
|
+
### Recommendations
|
|
136
|
+
|
|
137
|
+
- What to fix before merging (if FAIL)
|
|
138
|
+
- Suggestions for improvement (if PASS)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Rules
|
|
142
|
+
|
|
143
|
+
- Base your evaluation on **evidence from the diff and test output** — not assumptions
|
|
144
|
+
- If acceptance criteria are missing, evaluate against the task description
|
|
145
|
+
- A FAIL verdict should include actionable feedback — what specifically needs to change
|
|
146
|
+
- Do NOT modify any code — this is a read-only evaluation
|
|
147
|
+
- NEVER checkout branches in the main working tree — use `git diff` and `git show` only
|
|
148
|
+
|
|
149
|
+
**Note:** Do NOT write to `agents/*/status.json` — the engine manages your status automatically.
|
package/routing.md
CHANGED
|
@@ -17,6 +17,7 @@ How the engine decides who handles what. Parsed by engine.js — keep the table
|
|
|
17
17
|
| test | dallas | ralph |
|
|
18
18
|
| ask | ripley | rebecca |
|
|
19
19
|
| verify | dallas | ralph |
|
|
20
|
+
| evaluate | ripley | rebecca |
|
|
20
21
|
| decompose | ripley | rebecca |
|
|
21
22
|
| meeting | ripley | rebecca |
|
|
22
23
|
|