@yemi33/minions 0.1.314 → 0.1.316
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -1
- package/dashboard/js/render-plans.js +1 -1
- package/engine/cleanup.js +1 -1
- package/engine/dispatch.js +13 -11
- package/engine/lifecycle.js +25 -20
- package/engine/shared.js +22 -0
- package/engine.js +3 -2
- package/package.json +1 -1
- package/playbooks/evaluate.md +0 -114
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.316 (2026-04-03)
|
|
4
4
|
|
|
5
5
|
### Fixes
|
|
6
|
+
- deduplicate PRs in pull-requests.json on write
|
|
6
7
|
- show reviewer names in dashboard Signed Off By column
|
|
7
8
|
|
|
8
9
|
### Other
|
|
10
|
+
- refactor: extract status/type/result constants to shared.js
|
|
11
|
+
- cleanup: remove evaluate.md (re-created by agents), fix stale references
|
|
9
12
|
- perf: CC message handling — debounce localStorage, cap array, batch scroll
|
|
10
13
|
|
|
11
14
|
## 0.1.312 (2026-04-03)
|
|
@@ -70,7 +70,7 @@ function derivePlanStatus(prdFile, mdFile, prdJsonStatus, workItems) {
|
|
|
70
70
|
w.sourcePlan === prdFile || w.sourcePlan === mdFile ||
|
|
71
71
|
(w.type === 'plan-to-prd' && (w.planFile === prdFile || w.planFile === mdFile))
|
|
72
72
|
);
|
|
73
|
-
const implementWi = wi.filter(w => w.type !== 'plan-to-prd' && w.type !== 'verify'
|
|
73
|
+
const implementWi = wi.filter(w => w.type !== 'plan-to-prd' && w.type !== 'verify');
|
|
74
74
|
const hasPendingPrd = wi.some(w => w.type === 'plan-to-prd' && (w.status === 'pending' || w.status === 'dispatched'));
|
|
75
75
|
const hasActiveWork = implementWi.some(w => w.status === 'pending' || w.status === 'dispatched');
|
|
76
76
|
const allDone = implementWi.length > 0 && implementWi.every(w => w.status === 'done');
|
package/engine/cleanup.js
CHANGED
|
@@ -433,7 +433,7 @@ function runCleanup(config, verbose = false) {
|
|
|
433
433
|
|
|
434
434
|
// 6. Migrate legacy work-item statuses to canonical values
|
|
435
435
|
// in-pr, implemented, complete → done (one-time correction per item)
|
|
436
|
-
const LEGACY_DONE_STATUSES =
|
|
436
|
+
const LEGACY_DONE_STATUSES = shared.DONE_STATUSES;
|
|
437
437
|
for (const project of projects) {
|
|
438
438
|
try {
|
|
439
439
|
const wiPath = projectWorkItemsPath(project);
|
package/engine/dispatch.js
CHANGED
|
@@ -10,7 +10,8 @@ const queries = require('./queries');
|
|
|
10
10
|
const { setCooldownFailure } = require('./cooldown');
|
|
11
11
|
|
|
12
12
|
const { safeJson, safeWrite, safeReadDir, mutateJsonFileLocked,
|
|
13
|
-
getProjects, projectWorkItemsPath, log, ts, dateStamp
|
|
13
|
+
getProjects, projectWorkItemsPath, log, ts, dateStamp,
|
|
14
|
+
WI_STATUS, DISPATCH_RESULT, ENGINE_DEFAULTS } = shared;
|
|
14
15
|
const { getConfig, getDispatch, DISPATCH_PATH, INBOX_DIR } = queries;
|
|
15
16
|
|
|
16
17
|
const MINIONS_DIR = shared.MINIONS_DIR;
|
|
@@ -65,7 +66,7 @@ function isRetryableFailureReason(reason = '') {
|
|
|
65
66
|
|
|
66
67
|
// ─── Complete Dispatch ───────────────────────────────────────────────────────
|
|
67
68
|
|
|
68
|
-
function completeDispatch(id, result =
|
|
69
|
+
function completeDispatch(id, result = DISPATCH_RESULT.SUCCESS, reason = '', resultSummary = '', opts = {}) {
|
|
69
70
|
const { processWorkItemFailure = true } = opts;
|
|
70
71
|
let item = null;
|
|
71
72
|
|
|
@@ -98,9 +99,9 @@ function completeDispatch(id, result = 'success', reason = '', resultSummary = '
|
|
|
98
99
|
|
|
99
100
|
// Update source work item status on failure + auto-retry with backoff
|
|
100
101
|
const retryableFailure = isRetryableFailureReason(reason);
|
|
101
|
-
if (result ===
|
|
102
|
+
if (result === DISPATCH_RESULT.ERROR && item.meta?.dispatchKey && retryableFailure) setCooldownFailure(item.meta.dispatchKey);
|
|
102
103
|
|
|
103
|
-
if (processWorkItemFailure && result ===
|
|
104
|
+
if (processWorkItemFailure && result === DISPATCH_RESULT.ERROR && item.meta?.item?.id) {
|
|
104
105
|
let retries = (item.meta.item._retryCount || 0);
|
|
105
106
|
try {
|
|
106
107
|
const wiPath = item.meta.source === 'central-work-item' || item.meta.source === 'central-work-item-fanout'
|
|
@@ -112,8 +113,9 @@ function completeDispatch(id, result = 'success', reason = '', resultSummary = '
|
|
|
112
113
|
if (wi) retries = wi._retryCount || 0;
|
|
113
114
|
}
|
|
114
115
|
} catch (e) { log('warn', 'read retry count: ' + e.message); }
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
const maxRetries = ENGINE_DEFAULTS.maxRetries;
|
|
117
|
+
if (retryableFailure && retries < maxRetries) {
|
|
118
|
+
log('info', `Dispatch error for ${item.meta.item.id} — auto-retry ${retries + 1}/${maxRetries}`);
|
|
117
119
|
lifecycle().updateWorkItemStatus(item.meta, 'pending', '');
|
|
118
120
|
// Remove this dispatch key from completed so dedupe doesn't block immediate redispatch.
|
|
119
121
|
if (item.meta?.dispatchKey) {
|
|
@@ -132,9 +134,9 @@ function completeDispatch(id, result = 'success', reason = '', resultSummary = '
|
|
|
132
134
|
if (wiPath) {
|
|
133
135
|
const items = safeJson(wiPath) || [];
|
|
134
136
|
const wi = items.find(i => i.id === item.meta.item.id);
|
|
135
|
-
if (wi && wi.status !==
|
|
137
|
+
if (wi && wi.status !== WI_STATUS.PAUSED) {
|
|
136
138
|
wi._retryCount = retries + 1;
|
|
137
|
-
wi.status =
|
|
139
|
+
wi.status = WI_STATUS.PENDING;
|
|
138
140
|
wi._lastRetryReason = reason || '';
|
|
139
141
|
wi._lastRetryAt = ts();
|
|
140
142
|
delete wi.failReason;
|
|
@@ -148,7 +150,7 @@ function completeDispatch(id, result = 'success', reason = '', resultSummary = '
|
|
|
148
150
|
} else {
|
|
149
151
|
const finalReason = !retryableFailure
|
|
150
152
|
? `Non-retryable failure: ${reason || 'Unknown error'}`
|
|
151
|
-
: (reason ||
|
|
153
|
+
: (reason || `Failed after ${maxRetries} retries`);
|
|
152
154
|
lifecycle().updateWorkItemStatus(item.meta, 'failed', finalReason);
|
|
153
155
|
// Alert: find items blocked by this failure and write inbox note
|
|
154
156
|
try {
|
|
@@ -157,11 +159,11 @@ function completeDispatch(id, result = 'success', reason = '', resultSummary = '
|
|
|
157
159
|
const blockedItems = [];
|
|
158
160
|
for (const p of getProjects(config)) {
|
|
159
161
|
const items = safeJson(projectWorkItemsPath(p)) || [];
|
|
160
|
-
items.filter(w => w.status ===
|
|
162
|
+
items.filter(w => w.status === WI_STATUS.PENDING && (w.depends_on || []).includes(failedId))
|
|
161
163
|
.forEach(w => blockedItems.push(`- \`${w.id}\` — ${w.title}`));
|
|
162
164
|
}
|
|
163
165
|
const centralItems = safeJson(path.join(MINIONS_DIR, 'work-items.json')) || [];
|
|
164
|
-
centralItems.filter(w => w.status ===
|
|
166
|
+
centralItems.filter(w => w.status === WI_STATUS.PENDING && (w.depends_on || []).includes(failedId))
|
|
165
167
|
.forEach(w => blockedItems.push(`- \`${w.id}\` — ${w.title}`));
|
|
166
168
|
|
|
167
169
|
writeInboxAlert(`failed-${failedId}`,
|
package/engine/lifecycle.js
CHANGED
|
@@ -8,7 +8,8 @@ const path = require('path');
|
|
|
8
8
|
const os = require('os');
|
|
9
9
|
const shared = require('./shared');
|
|
10
10
|
const { safeRead, safeJson, safeWrite, mutateJsonFileLocked, execSilent, projectPrPath, getPrLinks, addPrLink,
|
|
11
|
-
log, ts, dateStamp
|
|
11
|
+
log, ts, dateStamp, WI_STATUS, DONE_STATUSES, WORK_TYPE, PLAN_STATUS, PR_STATUS, DISPATCH_RESULT,
|
|
12
|
+
ENGINE_DEFAULTS } = shared;
|
|
12
13
|
const { trackEngineUsage } = require('./llm');
|
|
13
14
|
const queries = require('./queries');
|
|
14
15
|
const { getConfig, getInboxFiles, getNotes, getPrs, getDispatch,
|
|
@@ -57,7 +58,7 @@ function checkPlanCompletion(meta, config) {
|
|
|
57
58
|
const unmaterialized = [...planFeatureIds].filter(id => {
|
|
58
59
|
if (workItemById[id]) return false;
|
|
59
60
|
const prdItem = (plan.missing_features || []).find(f => f.id === id);
|
|
60
|
-
return !(prdItem && (prdItem.status
|
|
61
|
+
return !(prdItem && DONE_STATUSES.has(prdItem.status));
|
|
61
62
|
});
|
|
62
63
|
if (unmaterialized.length > 0) {
|
|
63
64
|
log('info', `Plan ${planFile}: ${unmaterialized.length}/${planFeatureIds.size} feature(s) not yet materialized as work items: ${unmaterialized.join(', ')}`);
|
|
@@ -67,17 +68,17 @@ function checkPlanCompletion(meta, config) {
|
|
|
67
68
|
// Check 2: every feature's work item must be done (or PRD item marked done externally)
|
|
68
69
|
const notDone = [...planFeatureIds].filter(id => {
|
|
69
70
|
const w = workItemById[id];
|
|
70
|
-
if (w && (w.status
|
|
71
|
+
if (w && DONE_STATUSES.has(w.status)) return false;
|
|
71
72
|
const prdItem = (plan.missing_features || []).find(f => f.id === id);
|
|
72
|
-
return !(prdItem && (prdItem.status
|
|
73
|
+
return !(prdItem && DONE_STATUSES.has(prdItem.status));
|
|
73
74
|
});
|
|
74
75
|
if (notDone.length > 0) {
|
|
75
76
|
log('info', `Plan ${planFile}: waiting for done on ${notDone.length}/${planFeatureIds.size} item(s): ${notDone.join(', ')}`);
|
|
76
77
|
return;
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
const doneItems = planItems.filter(w =>
|
|
80
|
-
const failedItems = planItems.filter(w => w.status ===
|
|
80
|
+
const doneItems = planItems.filter(w => DONE_STATUSES.has(w.status));
|
|
81
|
+
const failedItems = planItems.filter(w => w.status === WI_STATUS.FAILED);
|
|
81
82
|
|
|
82
83
|
// 1. Mark plan as completed
|
|
83
84
|
plan.status = 'completed';
|
|
@@ -490,20 +491,20 @@ function updateWorkItemStatus(meta, status, reason) {
|
|
|
490
491
|
target.agentResults[agent] = { status, completedAt: ts(), reason: reason || undefined };
|
|
491
492
|
|
|
492
493
|
const results = Object.values(target.agentResults);
|
|
493
|
-
const anySuccess = results.some(r => r.status ===
|
|
494
|
+
const anySuccess = results.some(r => r.status === WI_STATUS.DONE);
|
|
494
495
|
const allDone = Array.isArray(target.fanOutAgents) && target.fanOutAgents.length > 0 ? results.length >= target.fanOutAgents.length : false;
|
|
495
496
|
const dispatchAge = target.dispatched_at ? Date.now() - new Date(target.dispatched_at).getTime() : 0;
|
|
496
497
|
const timedOut = !allDone && dispatchAge > 6 * 60 * 60 * 1000 && results.length > 0;
|
|
497
498
|
|
|
498
499
|
if (anySuccess) {
|
|
499
|
-
target.status =
|
|
500
|
+
target.status = WI_STATUS.DONE;
|
|
500
501
|
delete target.failReason;
|
|
501
502
|
delete target.failedAt;
|
|
502
503
|
target.completedAgents = Object.entries(target.agentResults)
|
|
503
|
-
.filter(([, r]) => r.status ===
|
|
504
|
+
.filter(([, r]) => r.status === WI_STATUS.DONE)
|
|
504
505
|
.map(([a]) => a);
|
|
505
506
|
} else if (allDone || timedOut) {
|
|
506
|
-
target.status =
|
|
507
|
+
target.status = WI_STATUS.FAILED;
|
|
507
508
|
target.failReason = timedOut
|
|
508
509
|
? `Fan-out timed out: ${results.length}/${(target.fanOutAgents || []).length} agents reported (all failed)`
|
|
509
510
|
: 'All fan-out agents failed';
|
|
@@ -511,11 +512,11 @@ function updateWorkItemStatus(meta, status, reason) {
|
|
|
511
512
|
}
|
|
512
513
|
} else {
|
|
513
514
|
target.status = status;
|
|
514
|
-
if (status ===
|
|
515
|
+
if (status === WI_STATUS.DONE) {
|
|
515
516
|
delete target.failReason;
|
|
516
517
|
delete target.failedAt;
|
|
517
518
|
target.completedAt = ts();
|
|
518
|
-
} else if (status ===
|
|
519
|
+
} else if (status === WI_STATUS.FAILED) {
|
|
519
520
|
if (reason) target.failReason = reason;
|
|
520
521
|
target.failedAt = ts();
|
|
521
522
|
}
|
|
@@ -643,6 +644,9 @@ function syncPrsFromOutput(output, agentId, meta, config) {
|
|
|
643
644
|
for (const [name, { prPath, prIds }] of targetPrIds) {
|
|
644
645
|
mutateJsonFileLocked(prPath, (prs) => {
|
|
645
646
|
if (!Array.isArray(prs)) prs = [];
|
|
647
|
+
// Deduplicate any existing entries with same id (case-insensitive agent name race)
|
|
648
|
+
const seen = new Set();
|
|
649
|
+
prs = prs.filter(p => { const k = String(p.id); if (seen.has(k)) return false; seen.add(k); return true; });
|
|
646
650
|
for (const { prId, fullId } of prIds) {
|
|
647
651
|
if (prs.some(p => p.id === fullId || String(p.id) === String(prId))) continue;
|
|
648
652
|
|
|
@@ -981,7 +985,7 @@ function updateMetrics(agentId, dispatchItem, result, taskUsage, prsCreatedCount
|
|
|
981
985
|
m.lastTask = dispatchItem.task;
|
|
982
986
|
m.lastCompleted = ts();
|
|
983
987
|
if (model) m.model = model;
|
|
984
|
-
if (result ===
|
|
988
|
+
if (result === DISPATCH_RESULT.SUCCESS) {
|
|
985
989
|
m.tasksCompleted++;
|
|
986
990
|
if (prsCreatedCount > 0) m.prsCreated = (m.prsCreated || 0) + prsCreatedCount;
|
|
987
991
|
if (dispatchItem.type === 'review') m.reviewsDone++;
|
|
@@ -1105,7 +1109,7 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1105
1109
|
const type = dispatchItem.type;
|
|
1106
1110
|
const meta = dispatchItem.meta;
|
|
1107
1111
|
const isSuccess = code === 0;
|
|
1108
|
-
const result = isSuccess ?
|
|
1112
|
+
const result = isSuccess ? DISPATCH_RESULT.SUCCESS : DISPATCH_RESULT.ERROR;
|
|
1109
1113
|
const { resultSummary, taskUsage, sessionId, model } = parseAgentOutput(stdout);
|
|
1110
1114
|
|
|
1111
1115
|
// Save session for potential resume on next dispatch
|
|
@@ -1141,9 +1145,10 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1141
1145
|
}
|
|
1142
1146
|
} catch { /* optional */ }
|
|
1143
1147
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1148
|
+
const maxRetries = ENGINE_DEFAULTS.maxRetries;
|
|
1149
|
+
if (retries < maxRetries) {
|
|
1150
|
+
log('info', `Agent failed for ${meta.item.id} — auto-retry ${retries + 1}/${maxRetries}`);
|
|
1151
|
+
updateWorkItemStatus(meta, WI_STATUS.PENDING, '');
|
|
1147
1152
|
try {
|
|
1148
1153
|
const wiPath = meta.source === 'central-work-item' || meta.source === 'central-work-item-fanout'
|
|
1149
1154
|
? path.join(MINIONS_DIR, 'work-items.json')
|
|
@@ -1152,14 +1157,14 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1152
1157
|
const items = safeJson(wiPath) || [];
|
|
1153
1158
|
const wi = items.find(i => i.id === meta.item.id);
|
|
1154
1159
|
if (wi) {
|
|
1155
|
-
wi._retryCount = retries + 1; wi.status =
|
|
1156
|
-
if (type ===
|
|
1160
|
+
wi._retryCount = retries + 1; wi.status = WI_STATUS.PENDING; delete wi.dispatched_at; delete wi.dispatched_to;
|
|
1161
|
+
if (type === WORK_TYPE.DECOMPOSE) delete wi._decomposing;
|
|
1157
1162
|
shared.safeWrite(wiPath, items);
|
|
1158
1163
|
}
|
|
1159
1164
|
}
|
|
1160
1165
|
} catch (err) { log('warn', `Retry update: ${err.message}`); }
|
|
1161
1166
|
} else {
|
|
1162
|
-
updateWorkItemStatus(meta,
|
|
1167
|
+
updateWorkItemStatus(meta, WI_STATUS.FAILED, `Agent failed (${maxRetries} retries exhausted)`);
|
|
1163
1168
|
}
|
|
1164
1169
|
// Clear _decomposing flag on failure so item doesn't get permanently stuck
|
|
1165
1170
|
if (type === 'decompose') {
|
package/engine/shared.js
CHANGED
|
@@ -386,8 +386,29 @@ const ENGINE_DEFAULTS = {
|
|
|
386
386
|
evalLoop: true, // enable review→fix loop after implementation completes
|
|
387
387
|
evalMaxIterations: 3, // max review→fix cycles before escalating to human
|
|
388
388
|
evalMaxCost: null, // USD ceiling per work item across all eval iterations; null = no limit (gather baseline data first)
|
|
389
|
+
maxRetries: 3, // max dispatch retries before marking work item as failed
|
|
389
390
|
};
|
|
390
391
|
|
|
392
|
+
// ─── Status & Type Constants ─────────────────────────────────────────────────
|
|
393
|
+
|
|
394
|
+
const WI_STATUS = {
|
|
395
|
+
PENDING: 'pending', DISPATCHED: 'dispatched', DONE: 'done', FAILED: 'failed',
|
|
396
|
+
PAUSED: 'paused', QUEUED: 'queued', NEEDS_REVIEW: 'needs-human-review', DECOMPOSED: 'decomposed',
|
|
397
|
+
};
|
|
398
|
+
const DONE_STATUSES = new Set([WI_STATUS.DONE, 'in-pr', 'implemented', 'complete']); // includes legacy aliases
|
|
399
|
+
const WORK_TYPE = {
|
|
400
|
+
IMPLEMENT: 'implement', IMPLEMENT_LARGE: 'implement:large', FIX: 'fix', REVIEW: 'review',
|
|
401
|
+
VERIFY: 'verify', PLAN: 'plan', PLAN_TO_PRD: 'plan-to-prd', DECOMPOSE: 'decompose',
|
|
402
|
+
MEETING: 'meeting', EXPLORE: 'explore', ASK: 'ask', TEST: 'test', DOCS: 'docs',
|
|
403
|
+
};
|
|
404
|
+
const PLAN_STATUS = {
|
|
405
|
+
ACTIVE: 'active', AWAITING_APPROVAL: 'awaiting-approval', APPROVED: 'approved',
|
|
406
|
+
PAUSED: 'paused', REJECTED: 'rejected', COMPLETED: 'completed',
|
|
407
|
+
REVISION_REQUESTED: 'revision-requested',
|
|
408
|
+
};
|
|
409
|
+
const PR_STATUS = { ACTIVE: 'active', MERGED: 'merged', ABANDONED: 'abandoned', CLOSED: 'closed' };
|
|
410
|
+
const DISPATCH_RESULT = { SUCCESS: 'success', ERROR: 'error', TIMEOUT: 'timeout' };
|
|
411
|
+
|
|
391
412
|
const DEFAULT_AGENTS = {
|
|
392
413
|
ripley: { name: 'Ripley', emoji: '\u{1F3D7}\uFE0F', role: 'Lead / Explorer', skills: ['architecture', 'codebase-exploration', 'design-review'] },
|
|
393
414
|
dallas: { name: 'Dallas', emoji: '\u{1F527}', role: 'Engineer', skills: ['implementation', 'typescript', 'docker', 'testing'] },
|
|
@@ -617,6 +638,7 @@ module.exports = {
|
|
|
617
638
|
KB_CATEGORIES,
|
|
618
639
|
classifyInboxItem,
|
|
619
640
|
ENGINE_DEFAULTS,
|
|
641
|
+
WI_STATUS, DONE_STATUSES, WORK_TYPE, PLAN_STATUS, PR_STATUS, DISPATCH_RESULT,
|
|
620
642
|
DEFAULT_AGENTS,
|
|
621
643
|
DEFAULT_CLAUDE,
|
|
622
644
|
getProjects,
|
package/engine.js
CHANGED
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
const fs = require('fs');
|
|
25
25
|
const path = require('path');
|
|
26
26
|
const shared = require('./engine/shared');
|
|
27
|
-
const { exec, execSilent, runFile, ENGINE_DEFAULTS: DEFAULTS
|
|
27
|
+
const { exec, execSilent, runFile, ENGINE_DEFAULTS: DEFAULTS,
|
|
28
|
+
WI_STATUS, DONE_STATUSES, WORK_TYPE, PLAN_STATUS, DISPATCH_RESULT } = shared;
|
|
28
29
|
const queries = require('./engine/queries');
|
|
29
30
|
|
|
30
31
|
// ─── Paths ──────────────────────────────────────────────────────────────────
|
|
@@ -754,7 +755,7 @@ function areDependenciesMet(item, config) {
|
|
|
754
755
|
} catch (e) { log('warn', 'read project work items for deps: ' + e.message); }
|
|
755
756
|
}
|
|
756
757
|
// PRD item statuses that count as "done" for dep resolution
|
|
757
|
-
const PRD_MET_STATUSES =
|
|
758
|
+
const PRD_MET_STATUSES = DONE_STATUSES;
|
|
758
759
|
|
|
759
760
|
for (const depId of deps) {
|
|
760
761
|
const depItem = allWorkItems.find(w => w.id === depId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.316",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|
package/playbooks/evaluate.md
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# Evaluate: {{item_name}}
|
|
2
|
-
|
|
3
|
-
> Agent: {{agent_name}} ({{agent_role}}) | Team root: {{team_root}}
|
|
4
|
-
|
|
5
|
-
## Context
|
|
6
|
-
|
|
7
|
-
Project: {{project_name}}
|
|
8
|
-
Repo: {{repo_name}} | Org: {{ado_org}} | ADO Project: {{ado_project}}
|
|
9
|
-
PR: {{pr_url}}
|
|
10
|
-
Work Item: {{item_id}}
|
|
11
|
-
|
|
12
|
-
## Acceptance Criteria
|
|
13
|
-
|
|
14
|
-
{{acceptance_criteria}}
|
|
15
|
-
|
|
16
|
-
## Task Description
|
|
17
|
-
|
|
18
|
-
{{task_description}}
|
|
19
|
-
|
|
20
|
-
## Your Task
|
|
21
|
-
|
|
22
|
-
You are the **Evaluator** in the Planner-Generator-Evaluator pattern. Your job is to independently verify whether the implementation in the PR branch meets the acceptance criteria. You are NOT the implementer — you are the skeptic.
|
|
23
|
-
|
|
24
|
-
**Mindset: Do not pass unless build succeeds AND all acceptance criteria are demonstrably met.** Assume the implementation is incomplete or wrong until proven otherwise. Look for edge cases, missing requirements, and silent failures.
|
|
25
|
-
|
|
26
|
-
## Step 1: Check Out the PR Branch
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
cd {{project_path}}
|
|
30
|
-
git fetch origin
|
|
31
|
-
git checkout {{branch_name}}
|
|
32
|
-
git pull origin {{branch_name}}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Step 2: Build
|
|
36
|
-
|
|
37
|
-
Run the project build. Check `CLAUDE.md`, `package.json`, or `README` for build instructions.
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
# Typical:
|
|
41
|
-
npm install && npm run build
|
|
42
|
-
# Or whatever the project uses
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Record: **PASS** or **FAIL** with error output.
|
|
46
|
-
|
|
47
|
-
If the build fails, **stop here** — the verdict is `pass: false`. Include the build error in feedback.
|
|
48
|
-
|
|
49
|
-
## Step 3: Run Tests
|
|
50
|
-
|
|
51
|
-
Run the full test suite:
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
npm test
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Record: **X passed / Y failed / Z skipped**.
|
|
58
|
-
|
|
59
|
-
If any tests fail, note which ones and whether they are related to the changes.
|
|
60
|
-
|
|
61
|
-
## Step 4: Diff Review Against Acceptance Criteria
|
|
62
|
-
|
|
63
|
-
Review the actual code changes:
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
git diff {{main_branch}}...{{branch_name}} --stat
|
|
67
|
-
git diff {{main_branch}}...{{branch_name}}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
For **each** acceptance criterion, determine:
|
|
71
|
-
- **Met**: The diff demonstrably satisfies this criterion. Cite the specific file/line.
|
|
72
|
-
- **Not met**: The diff does not satisfy this criterion, or satisfies it only partially. Explain what's missing.
|
|
73
|
-
|
|
74
|
-
Be precise. "Looks good" is not an evaluation — cite file paths and line numbers.
|
|
75
|
-
|
|
76
|
-
## Step 5: Output Structured Verdict
|
|
77
|
-
|
|
78
|
-
After completing your evaluation, output the following JSON block as your final output. This MUST be valid JSON wrapped in a `json` fenced code block:
|
|
79
|
-
|
|
80
|
-
```json
|
|
81
|
-
{
|
|
82
|
-
"pass": false,
|
|
83
|
-
"build": true,
|
|
84
|
-
"tests": "42/42",
|
|
85
|
-
"criteria_met": [
|
|
86
|
-
"criterion 1 — met because X (source: path/to/file.js:42)"
|
|
87
|
-
],
|
|
88
|
-
"criteria_failed": [
|
|
89
|
-
"criterion 2 — not met because Y is missing"
|
|
90
|
-
],
|
|
91
|
-
"feedback": "Summary of what needs to change for this to pass. Be specific — file names, line numbers, what to add/fix."
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Field definitions:
|
|
96
|
-
- `pass`: `true` only if build succeeds AND **all** acceptance criteria are met. Otherwise `false`.
|
|
97
|
-
- `build`: `true` if the build completed without errors, `false` otherwise.
|
|
98
|
-
- `tests`: String in format `"passed/total"` (e.g., `"38/40"`). Use `"N/A"` if no test suite exists.
|
|
99
|
-
- `criteria_met`: Array of strings — one per criterion that IS met. Include source references.
|
|
100
|
-
- `criteria_failed`: Array of strings — one per criterion that is NOT met. Explain why.
|
|
101
|
-
- `feedback`: Actionable feedback for the implementer. Be specific about what to fix. If `pass` is `true`, use this for minor suggestions or "LGTM".
|
|
102
|
-
|
|
103
|
-
## Rules
|
|
104
|
-
|
|
105
|
-
- **No Playwright / browser testing** — this phase evaluates build, tests, and code review only.
|
|
106
|
-
- **Do NOT fix code** — only evaluate and report. You are the evaluator, not the implementer.
|
|
107
|
-
- **Do NOT rubber-stamp** — if a criterion is ambiguous, evaluate conservatively (fail it and explain).
|
|
108
|
-
- **Build failure is an automatic fail** — do not evaluate criteria if the build doesn't pass.
|
|
109
|
-
- **Every criterion must be addressed** — `criteria_met` + `criteria_failed` should cover all acceptance criteria.
|
|
110
|
-
- **Cite sources** — reference file paths and line numbers for every met/failed criterion.
|
|
111
|
-
|
|
112
|
-
{{references}}
|
|
113
|
-
|
|
114
|
-
**Note:** Do NOT write to `agents/*/status.json` — the engine manages your status automatically.
|