@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 CHANGED
@@ -1,11 +1,15 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.290 (2026-04-03)
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 showVerify = effectiveStatus === 'completed' && prdFile && !isArchived;
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 modalVerifyBtn = isModalCompleted ? '<button class="pr-pager-btn" style="font-size:10px;padding:2px 10px;color:var(--green)" ' +
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);
@@ -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
- `- **${name}**: \`${p.localPath}/../worktrees/verify-${planSlug}\``
232
- ).join('\n');
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: move PRD .json to prd/archive/ and source .md plan to plans/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
- shared.safeWrite(planPath, plan); // save completed status first
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
- // Also archive the source .md plan if it exists (use source_plan field, not content matching)
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
- // 6. Clean up ALL worktrees created for this plan's work items (shared-branch + per-item)
331
+ // Clean up ALL worktrees created for this plan's work items (shared-branch + per-item)
318
332
  try {
319
- // Collect all branch slugs: shared-branch + per-item branches + item IDs
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 doneItems) {
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 uniquePrs) {
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 completion: cleaned ${cleanedWt} worktree(s)`);
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 { safeWrite(p, backupData); } catch { /* best-effort restore */ }
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 { fs.unlinkSync(lockPath); } catch { /* race: another process removed it */ }
133
- continue; // retry immediately after removing stale lock
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 { /* lock file disappeared between EEXIST and stat — retry will succeed */ }
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.290",
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"
@@ -13,11 +13,14 @@ Repo: {{repo_name}} | Org: {{ado_org}} | ADO Project: {{ado_project}}
13
13
 
14
14
  ## Your Task
15
15
 
16
- Build, test, and verify a set of related changes. Your job is to:
17
- 1. **Set up one worktree per project** with all PR branches merged in
18
- 2. **Build and test** from each worktree
19
- 3. **Start the webapp** on localhost (keep it running **detached so it survives after your process exits**)
20
- 4. **Write a manual testing guide**
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: Build Each Project
35
+ ## Step 2: Understand the Project
33
36
 
34
- For each project worktree listed above:
35
- 1. `cd` into the worktree path
36
- 2. Read its CLAUDE.md / package.json / README for build instructions
37
- 3. Install dependencies (`yarn install`, `npm install`, etc.)
38
- 4. Run the build (`yarn build`, `npm run build`, etc.)
39
- 5. Record: PASS or FAIL with error output
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
- If a build fails, **do NOT fix it** report the error and continue with other projects.
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: Run Tests
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
- For each project that built successfully:
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 Webapp
59
+ ## Step 4: Start the Application (if applicable)
50
60
 
51
- Determine which project is the **user-facing webapp** (has a dev server, UI):
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 the dev server **detached from your process** so it survives after you exit:
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
- node -e "
60
- const { spawn } = require('child_process');
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
- 2. Wait a few seconds, then verify it's responding: `curl -s -o /dev/null -w "%{http_code}" http://localhost:<PORT>`
73
- 3. Note the localhost URL, port, and PID
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: `taskkill //PID <PID> //F`
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
- ## Step 5: Write the Manual Testing Guide
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
- Create the testing guide in TWO locations:
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
- # Manual Testing Guide
90
+ # Verification Report & Testing Guide
87
91
 
88
92
  **Date:** {{date}}
89
- **Plan:** <plan file>
90
- **Local Server:** http://localhost:XXXX (or N/A)
91
- **Restart Command:** `cd <absolute-worktree-path> && <command>`
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
- ## Build Status
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 | notes |
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
- ## What to Test
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. Navigate to http://localhost:XXXX/path
105
- 2. Click on / interact with ...
106
- 3. You should see ...
138
+ 1. Step-by-step instructions
139
+ 2. With concrete actions (URLs, buttons, inputs)
140
+ 3. And expected outcomes
107
141
 
108
- **Expected behavior:**
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., "Bebop sends message via AugLoop OfficeAgent receives and responds"
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. Open http://localhost:XXXX
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 of everything built.
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. You're already in the worktree with all branches merged. Push this combined branch:
171
+ 1. Push the combined branch:
139
172
  ```bash
140
173
  cd <worktree-path>
141
- git checkout -b e2e/<plan-slug>
142
- git push origin e2e/<plan-slug>
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 that are merged into this branch
150
- - The testing guide (copy from Step 5)
151
- - Build/test status from Step 2-3
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/<plan-slug>',
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 visual results
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 local server MUST be started **detached** (using `spawn` with `detached: true` + `child.unref()`) so it keeps running after your process exits. Save the PID to `dev-server.pid` in the worktree.
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