@yemi33/minions 0.1.290 → 0.1.292
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 +5 -1
- package/dashboard/js/render-plans.js +19 -4
- package/engine/lifecycle.js +54 -19
- package/engine/shared.js +37 -5
- package/engine.js +2 -0
- package/package.json +1 -1
- package/playbooks/verify.md +103 -68
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.292 (2026-04-03)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
+
- harden shared.js — backup verification, lock TOCTOU, docs
|
|
6
7
|
- all doc-chats use Sonnet with full tools (agent change)
|
|
7
8
|
|
|
8
9
|
### Fixes
|
|
10
|
+
- add behavioral tests for CRITICAL propagation and stale lock ENOENT
|
|
11
|
+
- CRITICAL errors in safeJson now propagate to callers
|
|
12
|
+
- defer plan archiving until verify completes, add 20 verify tests
|
|
9
13
|
- enforce worktree isolation — 4 code paths fixed
|
|
10
14
|
- ' not 'Evaluate:'
|
|
11
15
|
- cross-platform compatibility — signal handling, paths, home dir
|
|
@@ -227,7 +227,9 @@ function renderPlans(plans) {
|
|
|
227
227
|
'onclick="event.stopPropagation();planExecute(\'' + escHtml(p.file) + '\',\'' + escHtml(p.project) + '\',this)">Execute</button>' : '';
|
|
228
228
|
const showPause = effectiveStatus === 'in-progress' && prdFile && !isArchived;
|
|
229
229
|
const showResume = (effectiveStatus === 'paused' || effectiveStatus === 'awaiting-approval') && prdFile && !isArchived;
|
|
230
|
-
const
|
|
230
|
+
const verifyWi = allWi.find(w => w.itemType === 'verify' && w.sourcePlan === prdFile);
|
|
231
|
+
const hasVerifyWi = !!verifyWi;
|
|
232
|
+
const showVerify = effectiveStatus === 'completed' && prdFile && !isArchived && !hasVerifyWi;
|
|
231
233
|
const pauseBtn = showPause ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--yellow)" ' +
|
|
232
234
|
'onclick="event.stopPropagation();planPause(\'' + escHtml(prdFile) + '\',this)">Pause</button>' : '';
|
|
233
235
|
const resumeBtn = showResume
|
|
@@ -256,7 +258,7 @@ function renderPlans(plans) {
|
|
|
256
258
|
(p.updatedAt ? '<span title="Last updated: ' + p.updatedAt + '">Updated ' + timeAgo(p.updatedAt) + '</span>' : '') +
|
|
257
259
|
(p.completedAt ? '<span>' + p.completedAt.slice(0, 10) + '</span>' : '') +
|
|
258
260
|
(p.generatedBy ? '<span>by ' + escHtml(p.generatedBy) + '</span>' : '') +
|
|
259
|
-
executeBtn + pauseBtn + resumeBtn + verifyBtn + archiveBtn + deleteBtn +
|
|
261
|
+
executeBtn + pauseBtn + resumeBtn + verifyBtn + (hasVerifyWi ? _renderVerifyBadge(verifyWi) : '') + archiveBtn + deleteBtn +
|
|
260
262
|
'</div>' +
|
|
261
263
|
'</div>' +
|
|
262
264
|
'</div>' +
|
|
@@ -442,13 +444,15 @@ function _renderPlanModal(normalizedFile, raw, lastMod) {
|
|
|
442
444
|
'onclick="planPause(\'' + escHtml(normalizedFile) + '\',this)">Pause</button>' : '';
|
|
443
445
|
const modalResumeBtn = isPaused ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green)" ' +
|
|
444
446
|
'onclick="planApprove(\'' + escHtml(normalizedFile) + '\',this)">Resume</button>' : '';
|
|
445
|
-
const
|
|
447
|
+
const modalVerifyWi = (window._lastWorkItems || []).find(w => w.itemType === 'verify' && w.sourcePlan === normalizedFile);
|
|
448
|
+
const modalVerifyBtn = isModalCompleted && !modalVerifyWi ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green)" ' +
|
|
446
449
|
'onclick="triggerVerify(\'' + escHtml(normalizedFile) + '\',this)">Verify</button>' : '';
|
|
450
|
+
const modalVerifyInfo = modalVerifyWi ? _renderVerifyBadge(modalVerifyWi) : '';
|
|
447
451
|
const modalArchiveBtn = '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--muted)" ' +
|
|
448
452
|
'onclick="planArchive(\'' + escHtml(normalizedFile) + '\')">Archive</button>';
|
|
449
453
|
const lastModLabel = lastMod ? '<div style="font-size:10px;color:var(--muted);font-weight:400;margin-top:2px">Last updated: ' + new Date(lastMod).toLocaleString() + '</div>' : '';
|
|
450
454
|
const actionBtns = '<div style="display:flex;gap:4px;flex-wrap:wrap;margin-top:4px">' +
|
|
451
|
-
(modalCompletedLabel || '') + (modalInProgressLabel || '') + (modalExecuteBtn || '') + (modalPauseBtn || '') + (modalResumeBtn || '') + (modalVerifyBtn || '') +
|
|
455
|
+
(modalCompletedLabel || '') + (modalInProgressLabel || '') + (modalExecuteBtn || '') + (modalPauseBtn || '') + (modalResumeBtn || '') + (modalVerifyBtn || '') + (modalVerifyInfo || '') +
|
|
452
456
|
' ' + modalArchiveBtn +
|
|
453
457
|
' <button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--red)" ' +
|
|
454
458
|
'onclick="planDelete(\'' + escHtml(normalizedFile) + '\')">Delete</button>' +
|
|
@@ -664,6 +668,17 @@ async function planRegeneratePRD(source) {
|
|
|
664
668
|
} catch (e) { alert('Error: ' + e.message); }
|
|
665
669
|
}
|
|
666
670
|
|
|
671
|
+
function _renderVerifyBadge(verifyWi) {
|
|
672
|
+
const statusColors = { pending: 'var(--muted)', dispatched: 'var(--blue)', done: 'var(--green)', failed: 'var(--red)' };
|
|
673
|
+
const color = statusColors[verifyWi.status] || 'var(--muted)';
|
|
674
|
+
const label = verifyWi.status === 'dispatched' ? 'Verifying...' : verifyWi.status === 'done' ? 'Verified' : verifyWi.status === 'failed' ? 'Verify failed' : 'Verify pending';
|
|
675
|
+
const allPrs = (window._lastStatus?.pullRequests) || [];
|
|
676
|
+
const verifyPr = allPrs.find(pr => (pr.prdItems || []).includes(verifyWi.id));
|
|
677
|
+
const prLink = verifyPr?.url ? ' <a href="' + escHtml(verifyPr.url) + '" target="_blank" onclick="event.stopPropagation()" style="color:var(--blue);text-decoration:none;font-size:9px">E2E PR</a>' : '';
|
|
678
|
+
const branchInfo = verifyPr?.branch ? ' <span style="font-size:8px;color:var(--muted)" title="' + escHtml(verifyPr.branch) + '">(' + escHtml(verifyPr.branch.slice(0, 25)) + ')</span>' : '';
|
|
679
|
+
return '<span style="font-size:9px;font-weight:600;color:' + color + ';padding:0 4px">' + label + '</span>' + prLink + branchInfo;
|
|
680
|
+
}
|
|
681
|
+
|
|
667
682
|
async function openVerifyGuide(file) {
|
|
668
683
|
try {
|
|
669
684
|
const normalizedFile = normalizePlanFile(file);
|
package/engine/lifecycle.js
CHANGED
|
@@ -148,7 +148,10 @@ function checkPlanCompletion(meta, config) {
|
|
|
148
148
|
// existingPrItem/existingVerify guards, so the flag does NOT block crash recovery of those.
|
|
149
149
|
plan._completionNotified = true;
|
|
150
150
|
mutateJsonFileLocked(planPath, (data) => {
|
|
151
|
+
data.status = 'completed';
|
|
152
|
+
data.completedAt = plan.completedAt;
|
|
151
153
|
data._completionNotified = true;
|
|
154
|
+
if (plan._timing) data._timing = plan._timing;
|
|
152
155
|
return data;
|
|
153
156
|
});
|
|
154
157
|
|
|
@@ -227,9 +230,10 @@ function checkPlanCompletion(meta, config) {
|
|
|
227
230
|
).join('\n');
|
|
228
231
|
|
|
229
232
|
// List projects and their worktree paths for the agent
|
|
230
|
-
const projectWorktrees = Object.entries(projectPrs).map(([name, { project: p }]) =>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
+
const projectWorktrees = Object.entries(projectPrs).map(([name, { project: p }]) => {
|
|
234
|
+
const lp = p.localPath.replace(/\\/g, '/');
|
|
235
|
+
return `- **${name}**: see setup commands below (\`${lp}/../worktrees/verify-${name}-${planSlug}-*\`)`;
|
|
236
|
+
}).join('\n');
|
|
233
237
|
|
|
234
238
|
const description = [
|
|
235
239
|
`Verification task for completed plan \`${planFile}\`.`,
|
|
@@ -274,19 +278,30 @@ function checkPlanCompletion(meta, config) {
|
|
|
274
278
|
log('info', `Created verification work item ${verifyId} for plan ${planFile}`);
|
|
275
279
|
}
|
|
276
280
|
|
|
277
|
-
// 5. Archive
|
|
281
|
+
// 5. Archive deferred until verify completes (see runPostCompletionHooks).
|
|
282
|
+
// Plan stays active until verification finishes so artifacts are visible.
|
|
283
|
+
|
|
284
|
+
log('info', `PRD ${planFile} completed: ${doneItems.length} done, ${failedItems.length} failed, runtime ${runtimeMin}m`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ─── Plan Archiving (called after verify completes) ─────────────────────────
|
|
288
|
+
|
|
289
|
+
function archivePlan(planFile, plan, projects, config) {
|
|
290
|
+
const planPath = path.join(PRD_DIR, planFile);
|
|
291
|
+
const projectName = plan.project || '';
|
|
292
|
+
|
|
293
|
+
// Archive PRD .json to prd/archive/
|
|
278
294
|
const prdArchiveDir = path.join(PRD_DIR, 'archive');
|
|
279
295
|
if (!fs.existsSync(prdArchiveDir)) fs.mkdirSync(prdArchiveDir, { recursive: true });
|
|
280
|
-
|
|
281
|
-
try {
|
|
282
|
-
fs.renameSync(planPath, path.join(prdArchiveDir, planFile));
|
|
283
|
-
log('info', `Archived completed PRD: prd/archive/${planFile}`);
|
|
284
|
-
} catch (err) {
|
|
285
|
-
log('warn', `Failed to archive PRD ${planFile}: ${err.message}`);
|
|
296
|
+
if (fs.existsSync(planPath)) {
|
|
286
297
|
shared.safeWrite(planPath, plan);
|
|
298
|
+
try {
|
|
299
|
+
fs.renameSync(planPath, path.join(prdArchiveDir, planFile));
|
|
300
|
+
log('info', `Archived completed PRD: prd/archive/${planFile}`);
|
|
301
|
+
} catch (err) { log('warn', `Failed to archive PRD ${planFile}: ${err.message}`); }
|
|
287
302
|
}
|
|
288
303
|
|
|
289
|
-
//
|
|
304
|
+
// Archive the source .md plan
|
|
290
305
|
const planArchiveDir = path.join(PLANS_DIR, 'archive');
|
|
291
306
|
if (!fs.existsSync(planArchiveDir)) fs.mkdirSync(planArchiveDir, { recursive: true });
|
|
292
307
|
if (plan.source_plan) {
|
|
@@ -298,7 +313,6 @@ function checkPlanCompletion(meta, config) {
|
|
|
298
313
|
} catch (err) { log('warn', `Failed to archive source plan ${plan.source_plan}: ${err.message}`); }
|
|
299
314
|
}
|
|
300
315
|
} else {
|
|
301
|
-
// Fallback: scan for matching .md files (legacy PRDs without source_plan)
|
|
302
316
|
try {
|
|
303
317
|
const mdFiles = fs.readdirSync(PLANS_DIR).filter(f => f.endsWith('.md'));
|
|
304
318
|
for (const md of mdFiles) {
|
|
@@ -314,16 +328,25 @@ function checkPlanCompletion(meta, config) {
|
|
|
314
328
|
} catch (err) { log('warn', `Plan archive scan: ${err.message}`); }
|
|
315
329
|
}
|
|
316
330
|
|
|
317
|
-
//
|
|
331
|
+
// Clean up ALL worktrees created for this plan's work items (shared-branch + per-item)
|
|
318
332
|
try {
|
|
319
|
-
|
|
333
|
+
let allWi = [];
|
|
334
|
+
for (const p of projects) {
|
|
335
|
+
try { allWi = allWi.concat(safeJson(shared.projectWorkItemsPath(p)) || []); } catch {}
|
|
336
|
+
}
|
|
337
|
+
const planWi = allWi.filter(w => w.sourcePlan === planFile && w.itemType !== 'verify');
|
|
338
|
+
const allPrs = [];
|
|
339
|
+
for (const p of projects) {
|
|
340
|
+
try { allPrs.push(...(safeJson(shared.projectPrPath(p)) || [])); } catch {}
|
|
341
|
+
}
|
|
342
|
+
|
|
320
343
|
const branchSlugs = new Set();
|
|
321
344
|
if (plan.feature_branch) branchSlugs.add(shared.sanitizeBranch(plan.feature_branch).toLowerCase());
|
|
322
|
-
for (const w of
|
|
345
|
+
for (const w of planWi) {
|
|
323
346
|
if (w.branch) branchSlugs.add(shared.sanitizeBranch(w.branch).toLowerCase());
|
|
324
347
|
if (w.id) branchSlugs.add(w.id.toLowerCase());
|
|
325
348
|
}
|
|
326
|
-
for (const pr of
|
|
349
|
+
for (const pr of allPrs.filter(pr => (pr.prdItems || []).some(id => planWi.find(w => w.id === id)))) {
|
|
327
350
|
if (pr.branch) branchSlugs.add(shared.sanitizeBranch(pr.branch).toLowerCase());
|
|
328
351
|
}
|
|
329
352
|
|
|
@@ -345,10 +368,8 @@ function checkPlanCompletion(meta, config) {
|
|
|
345
368
|
}
|
|
346
369
|
}
|
|
347
370
|
}
|
|
348
|
-
if (cleanedWt > 0) log('info', `Plan
|
|
371
|
+
if (cleanedWt > 0) log('info', `Plan archive: cleaned ${cleanedWt} worktree(s)`);
|
|
349
372
|
} catch (err) { log('warn', `Worktree cleanup: ${err.message}`); }
|
|
350
|
-
|
|
351
|
-
log('info', `PRD ${planFile} completed: ${doneItems.length} done, ${failedItems.length} failed, runtime ${runtimeMin}m`);
|
|
352
373
|
}
|
|
353
374
|
|
|
354
375
|
// ─── Plan → PRD Chaining ─────────────────────────────────────────────────────
|
|
@@ -1424,6 +1445,19 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
|
|
|
1424
1445
|
let prsCreatedCount = 0;
|
|
1425
1446
|
if (isSuccess) prsCreatedCount = syncPrsFromOutput(stdout, agentId, meta, config) || 0;
|
|
1426
1447
|
|
|
1448
|
+
// Archive plan after verify task completes (AFTER PR sync so E2E PR is linked)
|
|
1449
|
+
if (meta?.item?.itemType === 'verify' && meta?.item?.sourcePlan) {
|
|
1450
|
+
try {
|
|
1451
|
+
const vPlanFile = meta.item.sourcePlan;
|
|
1452
|
+
const vPlanPath = path.join(PRD_DIR, vPlanFile);
|
|
1453
|
+
const vPlan = safeJson(vPlanPath);
|
|
1454
|
+
if (vPlan) {
|
|
1455
|
+
const vProjects = shared.getProjects(config);
|
|
1456
|
+
archivePlan(vPlanFile, vPlan, vProjects, config);
|
|
1457
|
+
}
|
|
1458
|
+
} catch (err) { log('warn', `Verify archive: ${err.message}`); }
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1427
1461
|
// Clean up worktree for non-shared-branch tasks after completion
|
|
1428
1462
|
if (meta?.branch && meta?.branchStrategy !== 'shared-branch') {
|
|
1429
1463
|
try {
|
|
@@ -1551,6 +1585,7 @@ function syncPrdFromPrs(config) {
|
|
|
1551
1585
|
|
|
1552
1586
|
module.exports = {
|
|
1553
1587
|
checkPlanCompletion,
|
|
1588
|
+
archivePlan,
|
|
1554
1589
|
updateWorkItemStatus,
|
|
1555
1590
|
syncPrdItemStatus,
|
|
1556
1591
|
syncPrsFromOutput,
|
package/engine/shared.js
CHANGED
|
@@ -52,14 +52,37 @@ function safeJson(p) {
|
|
|
52
52
|
const backupData = JSON.parse(fs.readFileSync(backupPath, 'utf8'));
|
|
53
53
|
// Backup is valid — restore it to the primary file (atomic via safeWrite)
|
|
54
54
|
console.log(`[safeJson] restored ${path.basename(p)} from .backup sidecar`);
|
|
55
|
-
try {
|
|
55
|
+
try {
|
|
56
|
+
safeWrite(p, backupData);
|
|
57
|
+
// Verify the restored file matches expected content
|
|
58
|
+
const verifyData = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
59
|
+
if (JSON.stringify(verifyData) !== JSON.stringify(backupData)) {
|
|
60
|
+
const errMsg = `[safeJson] CRITICAL: backup restore verification failed for ${p} — written data does not match backup`;
|
|
61
|
+
console.error(errMsg);
|
|
62
|
+
throw new Error(errMsg);
|
|
63
|
+
}
|
|
64
|
+
} catch (restoreErr) {
|
|
65
|
+
// Re-throw CRITICAL errors so they propagate to callers
|
|
66
|
+
if (restoreErr.message && restoreErr.message.includes('CRITICAL')) throw restoreErr;
|
|
67
|
+
const errMsg = `[safeJson] CRITICAL: backup restore failed for ${p}: ${restoreErr.message}`;
|
|
68
|
+
console.error(errMsg);
|
|
69
|
+
throw new Error(errMsg);
|
|
70
|
+
}
|
|
56
71
|
return backupData;
|
|
57
|
-
} catch {
|
|
72
|
+
} catch (outerErr) {
|
|
73
|
+
// Let CRITICAL errors propagate — callers must know about data integrity failures
|
|
74
|
+
if (outerErr.message && outerErr.message.includes('CRITICAL')) throw outerErr;
|
|
58
75
|
return null;
|
|
59
76
|
}
|
|
60
77
|
}
|
|
61
78
|
}
|
|
62
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Monotonic counter for generating unique temp file names within this process.
|
|
82
|
+
* Assumes single-thread execution (no worker_threads). If worker_threads are
|
|
83
|
+
* introduced, this must be replaced with an atomic or thread-safe counter to
|
|
84
|
+
* avoid temp file name collisions.
|
|
85
|
+
*/
|
|
63
86
|
let _tmpCounter = 0;
|
|
64
87
|
|
|
65
88
|
function safeWrite(p, data) {
|
|
@@ -129,10 +152,19 @@ function withFileLock(lockPath, fn, {
|
|
|
129
152
|
try {
|
|
130
153
|
const stat = fs.statSync(lockPath);
|
|
131
154
|
if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
|
|
132
|
-
try {
|
|
133
|
-
|
|
155
|
+
try {
|
|
156
|
+
fs.unlinkSync(lockPath);
|
|
157
|
+
} catch (unlinkErr) {
|
|
158
|
+
// ENOENT: another process deleted the lock between stat and unlink — safe to retry
|
|
159
|
+
if (unlinkErr.code !== 'ENOENT') throw unlinkErr;
|
|
160
|
+
}
|
|
161
|
+
sleepMs(retryDelayMs); // avoid busy-loop on contention
|
|
162
|
+
continue;
|
|
134
163
|
}
|
|
135
|
-
} catch {
|
|
164
|
+
} catch (staleErr) {
|
|
165
|
+
// ENOENT from statSync: lock file disappeared between EEXIST and stat — retry will succeed
|
|
166
|
+
if (staleErr.code !== 'ENOENT') throw staleErr;
|
|
167
|
+
}
|
|
136
168
|
sleepMs(retryDelayMs);
|
|
137
169
|
}
|
|
138
170
|
}
|
package/engine.js
CHANGED
|
@@ -1461,6 +1461,8 @@ function discoverFromWorkItems(config, project) {
|
|
|
1461
1461
|
task_description: item.title + (item.description ? '\n\n' + item.description : ''),
|
|
1462
1462
|
task_id: item.id,
|
|
1463
1463
|
work_type: workType,
|
|
1464
|
+
source_plan: item.sourcePlan || '',
|
|
1465
|
+
plan_slug: (item.sourcePlan || '').replace('.json', ''),
|
|
1464
1466
|
additional_context: item.prompt ? `## Additional Context\n\n${item.prompt}` : '',
|
|
1465
1467
|
scope_section: `## Scope: Project — ${project?.name || 'default'}\n\nThis task is scoped to a single project.`,
|
|
1466
1468
|
branch_name: branchName,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.292",
|
|
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/verify.md
CHANGED
|
@@ -13,11 +13,14 @@ Repo: {{repo_name}} | Org: {{ado_org}} | ADO Project: {{ado_project}}
|
|
|
13
13
|
|
|
14
14
|
## Your Task
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
Verify that a set of related changes work correctly together. You must **figure out** how to build, test, and run this specific project — do not assume any particular language, framework, or tooling. Your job is to:
|
|
17
|
+
|
|
18
|
+
1. **Set up worktrees** with all PR branches merged
|
|
19
|
+
2. **Understand the project** — read its docs to learn how to build, test, and run it
|
|
20
|
+
3. **Build and test** from each worktree
|
|
21
|
+
4. **Start the application** if applicable (keep it running detached)
|
|
22
|
+
5. **Write a transparent verification report and testing guide**
|
|
23
|
+
6. **Create E2E pull requests**
|
|
21
24
|
|
|
22
25
|
## Step 1: Set Up Worktrees
|
|
23
26
|
|
|
@@ -29,100 +32,130 @@ If any merge conflicts occur:
|
|
|
29
32
|
|
|
30
33
|
After setup, all changes for a project are in a single directory — no switching between branches.
|
|
31
34
|
|
|
32
|
-
## Step 2:
|
|
35
|
+
## Step 2: Understand the Project
|
|
33
36
|
|
|
34
|
-
For each project worktree
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
For each project worktree, **read its documentation** to understand:
|
|
38
|
+
- What language/framework it uses
|
|
39
|
+
- How to install dependencies
|
|
40
|
+
- How to build it
|
|
41
|
+
- How to run tests
|
|
42
|
+
- How to start it (if it has a runnable application)
|
|
43
|
+
|
|
44
|
+
Check these files: `CLAUDE.md`, `README.md`, `package.json`, `Makefile`, `Cargo.toml`, `pyproject.toml`, `build.gradle`, `CMakeLists.txt`, `docker-compose.yml`, `Podfile`, `build.gradle.kts`, `*.xcodeproj`, `*.xcworkspace`, or whatever build system the project uses.
|
|
40
45
|
|
|
41
|
-
|
|
46
|
+
**Do not assume any specific platform.** The project could be a web app, mobile app (Android/iOS/React Native/Flutter), backend service, CLI tool, library, monorepo, or anything else. Adapt your verification approach to what the project actually is.
|
|
42
47
|
|
|
43
|
-
## Step 3:
|
|
48
|
+
## Step 3: Build and Test
|
|
49
|
+
|
|
50
|
+
For each project worktree:
|
|
51
|
+
1. `cd` into the worktree path
|
|
52
|
+
2. Install dependencies using whatever the project requires
|
|
53
|
+
3. Run the build using the project's build system
|
|
54
|
+
4. Run the test suite
|
|
55
|
+
5. Record: PASS or FAIL with error output, test counts (passed/failed/skipped)
|
|
44
56
|
|
|
45
|
-
|
|
46
|
-
1. Run the test suite from the worktree
|
|
47
|
-
2. Record passed/failed/skipped counts
|
|
57
|
+
If a build or test fails, **do NOT fix it** — report the exact error and continue with other projects.
|
|
48
58
|
|
|
49
|
-
## Step 4: Start the
|
|
59
|
+
## Step 4: Start the Application (if applicable)
|
|
50
60
|
|
|
51
|
-
Determine
|
|
52
|
-
- Check for `dev`, `start`, `serve` scripts in package.json
|
|
53
|
-
- Look for web frameworks (React, Next.js, TanStack, Vite, etc.)
|
|
61
|
+
Determine if the project has a **runnable application** (web server, API, desktop app, mobile emulator, etc.) by reading its documentation and build config. For mobile apps, check if an emulator/simulator can be launched or if building an APK/IPA is the appropriate verification step.
|
|
54
62
|
|
|
55
63
|
If found:
|
|
56
|
-
1. Start
|
|
64
|
+
1. Start it **detached from your process** so it survives after you exit. Use the platform-appropriate method:
|
|
57
65
|
```bash
|
|
58
66
|
cd <worktree-path>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const fs = require('fs');
|
|
62
|
-
const child = spawn('cmd', ['/c', '<start-command>'], {
|
|
63
|
-
cwd: process.cwd(),
|
|
64
|
-
detached: true,
|
|
65
|
-
stdio: ['ignore', fs.openSync('dev-server.log', 'w'), fs.openSync('dev-server.log', 'w')]
|
|
66
|
-
});
|
|
67
|
-
child.unref();
|
|
68
|
-
fs.writeFileSync('dev-server.pid', String(child.pid));
|
|
69
|
-
console.log('Server started, PID:', child.pid);
|
|
70
|
-
"
|
|
67
|
+
nohup <start-command> > app-server.log 2>&1 &
|
|
68
|
+
echo $! > app-server.pid
|
|
71
69
|
```
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
On Windows, use `spawn` with `detached: true` and `child.unref()`.
|
|
71
|
+
|
|
72
|
+
2. Wait a few seconds, then verify it's responding (e.g. `curl -s -o /dev/null -w "%{http_code}" http://localhost:<PORT>`)
|
|
73
|
+
3. Note the URL, port, and PID
|
|
74
74
|
4. Output the exact restart command with **absolute worktree paths**
|
|
75
|
-
5. Include stop command
|
|
75
|
+
5. Include the stop command (e.g. `kill <PID>` or `taskkill /PID <PID> /F` on Windows)
|
|
76
|
+
|
|
77
|
+
If the project has no runnable application, skip this step and note that in the guide.
|
|
78
|
+
|
|
79
|
+
## Step 5: Write the Verification Report and Testing Guide
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
Create the guide in TWO locations:
|
|
82
|
+
1. **Permanent location** (linked from dashboard): `{{team_root}}/prd/guides/verify-{{plan_slug}}.md`
|
|
83
|
+
2. **Inbox copy** (for team consolidation): `{{team_root}}/notes/inbox/verify-{{plan_slug}}.md`
|
|
78
84
|
|
|
79
|
-
|
|
80
|
-
1. **Permanent location** (linked from dashboard): `{{team_root}}/prd/guides/verify-{{date}}.md`
|
|
81
|
-
2. **Inbox copy** (for team consolidation): `{{team_root}}/notes/inbox/verify-{{date}}.md`
|
|
85
|
+
**Be transparent.** The guide must clearly state what was built, what was tested, what passed, what failed, and what still needs human verification.
|
|
82
86
|
|
|
83
87
|
Structure:
|
|
84
88
|
|
|
85
89
|
```markdown
|
|
86
|
-
#
|
|
90
|
+
# Verification Report & Testing Guide
|
|
87
91
|
|
|
88
92
|
**Date:** {{date}}
|
|
89
|
-
**Plan:**
|
|
90
|
-
**
|
|
91
|
-
|
|
93
|
+
**Plan:** {{source_plan}}
|
|
94
|
+
**Verified by:** {{agent_name}}
|
|
95
|
+
|
|
96
|
+
## What Was Built
|
|
97
|
+
|
|
98
|
+
For each completed plan item, summarize:
|
|
99
|
+
- **Item ID:** what it implements
|
|
100
|
+
- **Key changes:** files modified, features added, behaviors changed
|
|
101
|
+
- **PR:** link to the individual PR
|
|
92
102
|
|
|
93
|
-
##
|
|
103
|
+
## Verification Results
|
|
104
|
+
|
|
105
|
+
### Build Status
|
|
94
106
|
|
|
95
107
|
| Project | Worktree Path | Build | Tests | Notes |
|
|
96
108
|
|---------|--------------|-------|-------|-------|
|
|
97
|
-
| name | path | PASS/FAIL | X pass, Y fail |
|
|
109
|
+
| name | path | PASS/FAIL | X pass, Y fail, Z skip | error details if any |
|
|
110
|
+
|
|
111
|
+
### Automated Test Results
|
|
112
|
+
- Total: X passed, Y failed, Z skipped
|
|
113
|
+
- Notable failures: (list any, with error messages)
|
|
114
|
+
- Test coverage notes: (are the new features covered by tests?)
|
|
115
|
+
|
|
116
|
+
### What Was Verified
|
|
117
|
+
For each plan item, state what you actually checked:
|
|
118
|
+
- Did the build pass with this change included?
|
|
119
|
+
- Did existing tests pass?
|
|
120
|
+
- Were there new tests for the new functionality?
|
|
121
|
+
- Any runtime errors observed?
|
|
122
|
+
|
|
123
|
+
### What Could NOT Be Verified Automatically
|
|
124
|
+
List anything that requires human judgment:
|
|
125
|
+
- UI/UX changes that need visual inspection
|
|
126
|
+
- Behaviors that depend on external services
|
|
127
|
+
- Performance characteristics
|
|
128
|
+
- Edge cases not covered by tests
|
|
129
|
+
|
|
130
|
+
## Manual Testing Guide
|
|
98
131
|
|
|
99
|
-
|
|
132
|
+
**How to run:** (server URL, emulator command, APK path, or N/A)
|
|
133
|
+
**Restart Command:** `cd <absolute-worktree-path> && <command>` (if applicable)
|
|
100
134
|
|
|
101
135
|
### <Feature Name> (Plan Item ID)
|
|
102
136
|
**What changed:** brief description
|
|
103
137
|
**How to test:**
|
|
104
|
-
1.
|
|
105
|
-
2.
|
|
106
|
-
3.
|
|
138
|
+
1. Step-by-step instructions
|
|
139
|
+
2. With concrete actions (URLs, buttons, inputs)
|
|
140
|
+
3. And expected outcomes
|
|
107
141
|
|
|
108
|
-
**
|
|
109
|
-
- (from acceptance criteria)
|
|
110
|
-
- (from acceptance criteria)
|
|
142
|
+
**Acceptance criteria check:**
|
|
143
|
+
- [ ] (from plan item acceptance criteria)
|
|
144
|
+
- [ ] (from plan item acceptance criteria)
|
|
111
145
|
|
|
112
146
|
### <Next Feature> ...
|
|
113
147
|
|
|
114
148
|
## Integration Points
|
|
115
149
|
|
|
116
|
-
Cross-project interactions to verify:
|
|
117
|
-
- e.g., "
|
|
118
|
-
- e.g., "Progression UI updates in real-time as WebSocket messages arrive"
|
|
150
|
+
Cross-project or cross-feature interactions to verify:
|
|
151
|
+
- e.g., "Service A calls Service B — verify the API contract"
|
|
119
152
|
|
|
120
153
|
## Known Issues
|
|
121
154
|
- Build warnings, test failures, merge conflicts, unimplemented items
|
|
122
155
|
|
|
123
156
|
## Quick Smoke Test
|
|
124
157
|
A minimal 5-step checklist to verify the core functionality:
|
|
125
|
-
1.
|
|
158
|
+
1. ...
|
|
126
159
|
2. ...
|
|
127
160
|
3. ...
|
|
128
161
|
4. ...
|
|
@@ -131,24 +164,24 @@ A minimal 5-step checklist to verify the core functionality:
|
|
|
131
164
|
|
|
132
165
|
## Step 6: Create E2E Pull Requests
|
|
133
166
|
|
|
134
|
-
For each project that has changes, create a single **aggregate PR** that combines all the plan's branches into one. This gives the human reviewer a single diff showing the full picture
|
|
167
|
+
For each project that has changes, create a single **aggregate PR** that combines all the plan's branches into one. This gives the human reviewer a single diff showing the full picture.
|
|
135
168
|
|
|
136
169
|
For each project worktree:
|
|
137
170
|
|
|
138
|
-
1.
|
|
171
|
+
1. Push the combined branch:
|
|
139
172
|
```bash
|
|
140
173
|
cd <worktree-path>
|
|
141
|
-
git checkout -b e2e
|
|
142
|
-
git push origin e2e
|
|
174
|
+
git checkout -b e2e/{{plan_slug}}
|
|
175
|
+
git push origin e2e/{{plan_slug}}
|
|
143
176
|
```
|
|
144
177
|
|
|
145
178
|
2. Create a PR targeting the project's main branch using `mcp__azure-ado__repo_create_pull_request` (or `gh pr create` for GitHub):
|
|
146
179
|
- **Title:** `[E2E] <plan summary>`
|
|
147
180
|
- **Description:** Include:
|
|
148
181
|
- The plan summary
|
|
149
|
-
- List of all individual PRs
|
|
150
|
-
-
|
|
151
|
-
-
|
|
182
|
+
- List of all individual PRs merged into this branch
|
|
183
|
+
- Build/test status from Step 3
|
|
184
|
+
- Link to the testing guide
|
|
152
185
|
- **Target branch:** the project's main branch (e.g., `main` or `master`)
|
|
153
186
|
- **Do NOT auto-complete** — this is for review only
|
|
154
187
|
- **Mark as draft** if the option is available
|
|
@@ -163,7 +196,7 @@ For each project worktree:
|
|
|
163
196
|
id: 'PR-<number>',
|
|
164
197
|
title: '[E2E] <plan summary>',
|
|
165
198
|
agent: '{{agent_name}}',
|
|
166
|
-
branch: 'e2e
|
|
199
|
+
branch: 'e2e/{{plan_slug}}',
|
|
167
200
|
reviewStatus: 'pending',
|
|
168
201
|
status: 'active',
|
|
169
202
|
created: new Date().toISOString().slice(0,10),
|
|
@@ -178,12 +211,14 @@ For each project worktree:
|
|
|
178
211
|
|
|
179
212
|
## Rules
|
|
180
213
|
|
|
214
|
+
- **Read the project docs first** — never assume a build system, language, or framework
|
|
181
215
|
- Base testing steps on the **acceptance criteria** from each plan item
|
|
182
|
-
- Include **concrete steps** — URLs, buttons to click, inputs to type, expected
|
|
216
|
+
- Include **concrete steps** — URLs, buttons to click, inputs to type, expected results
|
|
217
|
+
- Be **transparent** — clearly separate what you verified vs what needs human review
|
|
183
218
|
- If a project doesn't build, still document what SHOULD be testable once fixed
|
|
184
219
|
- Do NOT fix code — only report issues
|
|
185
220
|
- Leave all worktrees in place for the user to inspect
|
|
186
|
-
- The
|
|
221
|
+
- The application MUST be started **detached** so it keeps running after your process exits
|
|
187
222
|
- Use absolute paths everywhere so the user can copy-paste commands
|
|
188
223
|
- E2E PRs are for review only — do NOT auto-complete or merge them
|
|
189
224
|
|