@yemi33/minions 0.1.1038 → 0.1.1040

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,5 +1,54 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1040 (2026-04-16)
4
+
5
+ ### Other
6
+ - Harden CC stream resilience
7
+
8
+ ## 0.1.1039 (2026-04-16)
9
+
10
+ ### Features
11
+ - Doc-chat performance — debounced persistence, session TTL, smart disk reads (#1164)
12
+ - split getStatus() into fast/slow state tiers with separate TTLs (#1170)
13
+ - Cache _countWorktrees() with 30s TTL (#1166)
14
+ - Pre-cache gzipped status buffer alongside JSON cache (#1171)
15
+ - parallelize ADO and GitHub PR polling with Promise.allSettled (#1172)
16
+ - route implement items to dedicated implement playbook (#1115)
17
+
18
+ ### Fixes
19
+ - allow test tasks to create PRs when files are modified
20
+ - sidecar oversized dispatch prompts to prevent dashboard OOM (closes #1167) (#1183)
21
+ - use locked write for failed-with-PR reconciliation in cleanup.js (#1169)
22
+ - prevent test from corrupting live meeting state
23
+ - scheduler enabled field falsy check treats undefined as disabled (#1160)
24
+ - update branch-dedup tests to use canonical PR IDs after merge with master (#1146)
25
+ - auto-review not firing for manually-linked PRs with autoObserve=true
26
+ - mark PR abandoned on 404 instead of silently retrying each tick
27
+ - replace undefined PROJECTS with config.projects in checkWatches (#1108)
28
+ - write permission for publish workflow
29
+ - run tests inline and post check runs for publish PRs
30
+ - add maxBuffer to all GitHub CLI spawns + paginate PR list (#1130)
31
+ - add required CI checks for PRs + update publish for auto-merge
32
+ - revert to PR merge now that stale status check is removed
33
+ - guard live review check against undefined vote/state values (#1132)
34
+ - push version bump directly to master instead of via PR
35
+ - add push-triggered CI for chore/publish branches
36
+ - publish workflow chore PRs failing to merge
37
+ - harden KB ordering
38
+ - harden audited state transitions
39
+
40
+ ### Other
41
+ - Tighten playbook guidance
42
+ - Fix audit cleanup and test isolation
43
+ - docs(sched-weekly-docs-cleanup-1776355200664): weekly documentation cleanup (#1181)
44
+ - [E2E] Dashboard & engine perf: gzip cache, tiered status, lock backoff, polling parallelization (#1174)
45
+ - Fix doc chat session isolation
46
+ - test(pipeline): add unit tests for CRUD, stage execution, run lifecycle (#1162)
47
+ - chore: test publish after removing stale status check
48
+ - chore: trigger publish test
49
+ - chore: test publish workflow fix
50
+ - chore: trigger publish workflow test
51
+
3
52
  ## 0.1.1038 (2026-04-16)
4
53
 
5
54
  ### Features
@@ -54,6 +54,26 @@ function _ccActiveTab() {
54
54
  return _ccTabs.find(function(t) { return t.id === _ccActiveTabId; }) || null;
55
55
  }
56
56
 
57
+ async function _ccDashboardHealth() {
58
+ var controller = new AbortController();
59
+ var timer = setTimeout(function() { controller.abort(); }, 3000);
60
+ try {
61
+ var res = await fetch('/api/status', { signal: controller.signal, cache: 'no-store' });
62
+ if (!(res && res.ok)) return { reachable: false, restarted: false };
63
+ var data = await res.json().catch(function() { return null; });
64
+ var currentDashId = data && data.version ? data.version.dashboardStartedAt : null;
65
+ var knownDashId = window._lastStatus && window._lastStatus.version ? window._lastStatus.version.dashboardStartedAt : null;
66
+ return {
67
+ reachable: true,
68
+ restarted: !!(currentDashId && knownDashId && currentDashId !== knownDashId)
69
+ };
70
+ } catch {
71
+ return { reachable: false, restarted: false };
72
+ } finally {
73
+ clearTimeout(timer);
74
+ }
75
+ }
76
+
57
77
  function _ccFindPinTarget(query) {
58
78
  for (var i = 0; i < (inboxData || []).length; i++) {
59
79
  if (inboxData[i].name.toLowerCase().includes(query)) {
@@ -532,6 +552,16 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
532
552
  if (activeTab._queue && activeTab._queue.length > 0) _renderQueueIndicator();
533
553
  if (msgs.scrollHeight - msgs.scrollTop - msgs.clientHeight < 150) msgs.scrollTop = msgs.scrollHeight;
534
554
  }
555
+ function _ccElapsedFooter(label) {
556
+ var seconds = Math.round((Date.now() - ccStartTime) / 1000);
557
+ return '<div style="font-size:9px;color:var(--muted);margin-top:6px;display:flex;justify-content:flex-end;padding-right:30px">' + label.replace('{seconds}', seconds) + '</div>';
558
+ }
559
+ function _ccRetryControls(extraHtml, showReload) {
560
+ return (extraHtml || '') +
561
+ '<button onclick="ccRetryLast()" style="margin-top:6px;padding:4px 12px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;color:var(--blue);cursor:pointer;font-size:11px">Retry</button>' +
562
+ (showReload ? ' <button onclick="location.reload()" style="margin-top:6px;padding:4px 12px;background:var(--orange);color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px">Reload Page</button>' : '') +
563
+ ' <button onclick="ccNewTab()" style="margin-top:6px;padding:4px 12px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;color:var(--muted);cursor:pointer;font-size:11px">New Session</button>';
564
+ }
535
565
  // Start phase timer immediately so thinking text updates while waiting for SSE
536
566
  var phaseTimer = setInterval(updateStreamDiv, 1000);
537
567
  updateStreamDiv(); // render proper layout immediately (not raw "Thinking..." text)
@@ -563,6 +593,7 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
563
593
  var reader = res.body.getReader();
564
594
  var decoder = new TextDecoder();
565
595
  var buf = '';
596
+ var terminalEventSeen = false;
566
597
 
567
598
  while (true) {
568
599
  var readResult = await reader.read();
@@ -579,21 +610,23 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
579
610
  streamedText = evt.text;
580
611
  if (activeTab) activeTab._streamedText = streamedText;
581
612
  updateStreamDiv();
613
+ } else if (evt.type === 'heartbeat') {
614
+ continue;
582
615
  } else if (evt.type === 'tool') {
583
616
  toolsUsed.push({ name: evt.name, input: evt.input || {} });
584
617
  if (activeTab) activeTab._toolsUsed = toolsUsed.slice();
585
618
  updateStreamDiv();
586
619
  if (msgs.scrollHeight - msgs.scrollTop - msgs.clientHeight < 150) msgs.scrollTop = msgs.scrollHeight;
587
620
  } else if (evt.type === 'done') {
621
+ terminalEventSeen = true;
588
622
  _cleanupStreamDiv();
589
623
  // If system prompt changed, show a notice before the response
590
624
  if (evt.sessionReset) {
591
625
  addMsg('system', '<div style="text-align:center;padding:6px 12px;font-size:11px;color:var(--muted);background:var(--surface2);border-radius:6px;margin:4px 0">Minions was updated — started a fresh session with latest context.</div>', false, activeTabId);
592
626
  }
593
627
  // placeholder was added with skipSave=true — nothing to pop
594
- var ccElapsed = Math.round((Date.now() - ccStartTime) / 1000);
595
628
  var rendered = renderMd(evt.text || streamedText || '');
596
- addMsg('assistant', rendered + '<div style="font-size:9px;color:var(--muted);margin-top:6px;display:flex;justify-content:flex-end;padding-right:30px">' + ccElapsed + 's</div>');
629
+ addMsg('assistant', rendered + _ccElapsedFooter('{seconds}s'));
597
630
  if (evt.sessionId !== undefined) {
598
631
  // Save session to the originating tab, not whichever tab is active now
599
632
  var originTab = _ccTabs.find(function(t) { return t.id === activeTabId; });
@@ -605,6 +638,7 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
605
638
  for (var ai = 0; ai < evt.actions.length; ai++) { await ccExecuteAction(evt.actions[ai], activeTabId); }
606
639
  }
607
640
  } else if (evt.type === 'error') {
641
+ terminalEventSeen = true;
608
642
  _cleanupStreamDiv();
609
643
  // placeholder was skipSave — no pop needed
610
644
  addMsg('assistant', '<span style="color:var(--red)">' + escHtml(evt.error) + '</span>');
@@ -621,11 +655,11 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
621
655
  try {
622
656
  var revt = JSON.parse(rline.slice(6));
623
657
  if (revt.type === 'done') {
658
+ terminalEventSeen = true;
624
659
  _cleanupStreamDiv();
625
660
  // placeholder was skipSave — no pop needed
626
- var ccElapsed2 = Math.round((Date.now() - ccStartTime) / 1000);
627
661
  var rendered2 = renderMd(revt.text || streamedText || '');
628
- addMsg('assistant', rendered2 + '<div style="font-size:9px;color:var(--muted);margin-top:6px;display:flex;justify-content:flex-end;padding-right:30px">' + ccElapsed2 + 's</div>');
662
+ addMsg('assistant', rendered2 + _ccElapsedFooter('{seconds}s'));
629
663
  if (revt.sessionId !== undefined) {
630
664
  var originTab2 = _ccTabs.find(function(t) { return t.id === activeTabId; });
631
665
  if (originTab2) { originTab2.sessionId = revt.sessionId || null; }
@@ -635,6 +669,12 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
635
669
  _tagServerExecuted(revt.actions, revt.actionResults);
636
670
  for (var ai2 = 0; ai2 < revt.actions.length; ai2++) { await ccExecuteAction(revt.actions[ai2], activeTabId); }
637
671
  }
672
+ } else if (revt.type === 'heartbeat') {
673
+ continue;
674
+ } else if (revt.type === 'error') {
675
+ terminalEventSeen = true;
676
+ _cleanupStreamDiv();
677
+ addMsg('assistant', '<span style="color:var(--red)">' + escHtml(revt.error) + '</span>');
638
678
  } else if (revt.type === 'chunk') {
639
679
  streamedText = revt.text;
640
680
  updateStreamDiv();
@@ -643,11 +683,13 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
643
683
  }
644
684
  }
645
685
  // If stream ended without a 'done' event, finalize with whatever we have
646
- if (streamDiv.parentNode || document.getElementById('cc-restore-thinking') || document.querySelector('[data-stream-tab="' + activeTabId + '"]')) {
686
+ if (!terminalEventSeen && (streamDiv.parentNode || document.getElementById('cc-restore-thinking') || document.querySelector('[data-stream-tab="' + activeTabId + '"]'))) {
647
687
  _cleanupStreamDiv();
688
+ var streamEndedHint = '<div style="font-size:10px;color:var(--muted);margin-top:4px">The response stream ended before completion. Retry to continue from the last user message.</div>';
648
689
  if (streamedText) {
649
- var ccElapsed3 = Math.round((Date.now() - ccStartTime) / 1000);
650
- addMsg('assistant', renderMd(streamedText) + '<div style="font-size:9px;color:var(--muted);margin-top:6px;display:flex;justify-content:flex-end;padding-right:30px">' + ccElapsed3 + 's</div>');
690
+ addMsg('assistant', renderMd(streamedText) + _ccElapsedFooter('Stream interrupted after {seconds}s') + _ccRetryControls(streamEndedHint, false));
691
+ } else {
692
+ addMsg('assistant', '<span style="color:var(--red)">The response stream ended before completion.</span>' + _ccRetryControls(streamEndedHint, false));
651
693
  }
652
694
  }
653
695
  } catch (e) {
@@ -655,19 +697,24 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
655
697
  if (e.name === 'AbortError') {
656
698
  _wasAborted = true;
657
699
  if (streamedText) {
658
- var ccElapsed4 = Math.round((Date.now() - ccStartTime) / 1000);
659
- addMsg('assistant', renderMd(streamedText) + '<div style="font-size:9px;color:var(--muted);margin-top:6px;display:flex;justify-content:flex-end;padding-right:30px">Stopped after ' + ccElapsed4 + 's</div>');
700
+ addMsg('assistant', renderMd(streamedText) + _ccElapsedFooter('Stopped after {seconds}s'));
660
701
  } else {
661
702
  addMsg('assistant', '<span style="color:var(--red);font-size:11px">Stopped</span>');
662
703
  }
663
704
  } else {
664
- var retryId = 'cc-retry-' + Date.now();
665
705
  var isNetworkError = e.message === 'Failed to fetch' || e.message.includes('NetworkError');
666
- addMsg('assistant', '<span style="color:var(--red)">Error: ' + escHtml(e.message) + '</span>' +
667
- (isNetworkError ? '<div style="font-size:10px;color:var(--muted);margin-top:4px">Dashboard connection lost. Reload the page to reconnect.</div>' : '') +
668
- '<button id="' + retryId + '" onclick="ccRetryLast()" style="margin-top:6px;padding:4px 12px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;color:var(--blue);cursor:pointer;font-size:11px">Retry</button>' +
669
- (isNetworkError ? ' <button onclick="location.reload()" style="margin-top:6px;padding:4px 12px;background:var(--orange);color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px">Reload Page</button>' : '') +
670
- ' <button onclick="ccNewTab()" style="margin-top:6px;padding:4px 12px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;color:var(--muted);cursor:pointer;font-size:11px">New Session</button>');
706
+ var dashboardHealth = isNetworkError ? await _ccDashboardHealth() : { reachable: false, restarted: false };
707
+ var connectionHint = '';
708
+ if (isNetworkError) {
709
+ connectionHint = dashboardHealth.restarted
710
+ ? '<div style="font-size:10px;color:var(--muted);margin-top:4px">Dashboard restarted while this response was streaming. Reload the page to reconnect to the new instance.</div>'
711
+ : dashboardHealth.reachable
712
+ ? '<div style="font-size:10px;color:var(--muted);margin-top:4px">The request stream was interrupted, but the dashboard is still reachable. Retry or start a new session.</div>'
713
+ : '<div style="font-size:10px;color:var(--muted);margin-top:4px">Dashboard connection lost. Reload the page to reconnect.</div>';
714
+ }
715
+ addMsg('assistant', (streamedText ? renderMd(streamedText) + _ccElapsedFooter('Stream interrupted after {seconds}s') : '') +
716
+ '<span style="color:var(--red)">Error: ' + escHtml(e.message) + '</span>' +
717
+ _ccRetryControls(connectionHint, isNetworkError && (!dashboardHealth.reachable || dashboardHealth.restarted)));
671
718
  }
672
719
  } finally {
673
720
  if (activeTab) { activeTab._sending = false; activeTab._abortController = null; activeTab._429retries = 0; delete activeTab._streamedText; delete activeTab._toolsUsed; delete activeTab._sendStartedAt; }
package/dashboard.js CHANGED
@@ -541,6 +541,7 @@ const ccInFlightTabs = new Map(); // tabId → timestamp — per-tab in-flight t
541
541
  const ccInFlightAborts = new Map(); // tabId → abortFn — lets a new request kill the stale LLM
542
542
  const CC_INFLIGHT_TIMEOUT_MS = 2 * 60 * 1000; // 2 minutes — auto-release if request hangs
543
543
  const CC_LOCK_WAIT_MS = 200; // grace period for previous handler's finally to release lock
544
+ const CC_STREAM_HEARTBEAT_MS = 15000; // keep streaming responses alive across proxies/restart races
544
545
  function _releaseCCTab(tabId) { ccInFlightTabs.delete(tabId); ccInFlightAborts.delete(tabId); }
545
546
  function _ccTabIsInFlight(tabId) {
546
547
  if (!ccInFlightTabs.has(tabId)) return false;
@@ -3791,6 +3792,23 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3791
3792
  async function handleCommandCenterStream(req, res) {
3792
3793
  if (checkRateLimit('command-center', 10)) { res.statusCode = 429; res.end('Rate limited'); return; }
3793
3794
  let tabId;
3795
+ let _ccStreamAbort = null;
3796
+ let _ccStreamEnded = false;
3797
+ let _ccHeartbeatTimer = null;
3798
+ const writeCcEvent = (payload) => {
3799
+ try {
3800
+ res.write('data: ' + JSON.stringify(payload) + '\n\n');
3801
+ return true;
3802
+ } catch {
3803
+ return false;
3804
+ }
3805
+ };
3806
+ const stopCcHeartbeat = () => {
3807
+ if (_ccHeartbeatTimer) {
3808
+ clearInterval(_ccHeartbeatTimer);
3809
+ _ccHeartbeatTimer = null;
3810
+ }
3811
+ };
3794
3812
  try {
3795
3813
  const body = await readBody(req);
3796
3814
  if (!body.message) { res.statusCode = 400; res.end('message required'); return; }
@@ -3807,13 +3825,20 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3807
3825
  ccInFlightTabs.set(tabId, Date.now());
3808
3826
 
3809
3827
  res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
3810
- let _ccStreamAbort = null;
3811
- let _ccStreamEnded = false;
3828
+ writeCcEvent({ type: 'heartbeat' }); // flush headers quickly and keep intermediaries from idling out
3829
+ _ccHeartbeatTimer = setInterval(() => {
3830
+ if (_ccStreamEnded) {
3831
+ stopCcHeartbeat();
3832
+ return;
3833
+ }
3834
+ if (!writeCcEvent({ type: 'heartbeat' })) stopCcHeartbeat();
3835
+ }, CC_STREAM_HEARTBEAT_MS);
3812
3836
  // Kill LLM process immediately if client disconnects mid-stream.
3813
3837
  // Guard with !_ccStreamEnded: when the stream ends normally, finally already released the lock;
3814
3838
  // without the guard, this close event (which fires after res.end) could wipe a new request's lock.
3815
3839
  req.on('close', () => {
3816
3840
  if (!_ccStreamEnded) {
3841
+ stopCcHeartbeat();
3817
3842
  _releaseCCTab(tabId);
3818
3843
  if (_ccStreamAbort) {
3819
3844
  console.log(`[CC-stream] Client disconnected for tab ${tabId} — aborting LLM`);
@@ -3851,10 +3876,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3851
3876
  onChunk: (text) => {
3852
3877
  const actIdx = findCCActionsDelimiter(text);
3853
3878
  const display = actIdx >= 0 ? text.slice(0, actIdx).trim() : text;
3854
- try { res.write('data: ' + JSON.stringify({ type: 'chunk', text: display }) + '\n\n'); } catch {}
3879
+ writeCcEvent({ type: 'chunk', text: display });
3855
3880
  },
3856
3881
  onToolUse: (name, input) => {
3857
- try { res.write('data: ' + JSON.stringify({ type: 'tool', name, input: _lightToolInput(input) }) + '\n\n'); } catch {}
3882
+ writeCcEvent({ type: 'tool', name, input: _lightToolInput(input) });
3858
3883
  }
3859
3884
  });
3860
3885
  _ccStreamAbort = llmPromise.abort;
@@ -3875,10 +3900,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3875
3900
  onChunk: (text) => {
3876
3901
  const actIdx = findCCActionsDelimiter(text);
3877
3902
  const display = actIdx >= 0 ? text.slice(0, actIdx).trim() : text;
3878
- try { res.write('data: ' + JSON.stringify({ type: 'chunk', text: display }) + '\n\n'); } catch {}
3903
+ writeCcEvent({ type: 'chunk', text: display });
3879
3904
  },
3880
3905
  onToolUse: (name, input) => {
3881
- try { res.write('data: ' + JSON.stringify({ type: 'tool', name, input: _lightToolInput(input) }) + '\n\n'); } catch {}
3906
+ writeCcEvent({ type: 'tool', name, input: _lightToolInput(input) });
3882
3907
  }
3883
3908
  });
3884
3909
  _ccStreamAbort = retryPromise.abort;
@@ -3896,7 +3921,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3896
3921
  const stderrTail = (result.stderr || '').trim().split('\n').filter(Boolean).slice(-3).join(' | ');
3897
3922
  console.error(`[CC-stream] Failed: code=${result.code}, stderr=${(result.stderr || '').slice(0, 500)}, stdout_tail=${(result.raw || '').slice(-500)}`);
3898
3923
  const retryHint = 'Send your message again to retry.';
3899
- res.write('data: ' + JSON.stringify({ type: 'done', text: `I had trouble processing that ${debugInfo}. ${stderrTail ? 'Detail: ' + stderrTail : ''}\n\n${retryHint}`, actions: [], sessionId: null }) + '\n\n');
3924
+ writeCcEvent({ type: 'done', text: `I had trouble processing that ${debugInfo}. ${stderrTail ? 'Detail: ' + stderrTail : ''}\n\n${retryHint}`, actions: [], sessionId: null });
3900
3925
  _ccStreamEnded = true; res.end();
3901
3926
  return;
3902
3927
  }
@@ -3939,7 +3964,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3939
3964
  }
3940
3965
  const donePayload = { type: 'done', text: displayText, actions, actionResults, sessionId: responseSessionId, newSession: !wasResume };
3941
3966
  if (sessionReset) donePayload.sessionReset = true;
3942
- res.write('data: ' + JSON.stringify(donePayload) + '\n\n');
3967
+ writeCcEvent(donePayload);
3943
3968
 
3944
3969
  // Mirror CC response to Teams (non-blocking, skip Teams-originated)
3945
3970
  const _streamTabId = body.tabId || 'default';
@@ -3949,11 +3974,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3949
3974
 
3950
3975
  _ccStreamEnded = true; res.end();
3951
3976
  } finally {
3977
+ stopCcHeartbeat();
3952
3978
  _releaseCCTab(tabId);
3953
3979
  }
3954
3980
  } catch (e) {
3981
+ stopCcHeartbeat();
3955
3982
  _releaseCCTab(tabId);
3956
- try { res.write('data: ' + JSON.stringify({ type: 'error', error: e.message }) + '\n\n'); } catch {}
3983
+ writeCcEvent({ type: 'error', error: e.message });
3957
3984
  _ccStreamEnded = true; try { res.end(); } catch {}
3958
3985
  }
3959
3986
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1038",
3
+ "version": "0.1.1040",
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"
@@ -33,11 +33,11 @@ git log --oneline {{main_branch}}..HEAD
33
33
 
34
34
  Do ALL work in the worktree.
35
35
 
36
- **Do NOT:**
36
+ **Shared branch workflow — do NOT:**
37
37
  - Create a new branch — use `{{branch_name}}`
38
- - Create a PR — one will be created automatically when all plan items complete
39
- - Remove the worktree — the next plan item needs it
40
38
  - Create a new worktree — one already exists at `{{worktree_path}}`
39
+ - Remove the worktree — the engine cleans it up after all plan items complete
40
+ - Create a PR — one will be created automatically when all plan items complete
41
41
 
42
42
  ## Health Check
43
43
 
@@ -51,19 +51,13 @@ Do NOT remove the worktree — the engine handles cleanup automatically.
51
51
 
52
52
  ## Build & Test (MANDATORY before pushing)
53
53
 
54
- After implementation, verify everything works before pushing:
55
-
56
- 1. **Build** the project using its build system (check CLAUDE.md, package.json, README, Makefile). If the build fails:
57
- - Read the error, fix the issue, re-build
58
- - If it fails 3 times, report the errors in your findings and stop
59
- 2. **Run the full test suite** using whatever command the project specifies (check CLAUDE.md, agent.md, README, or package.json scripts).
60
- 3. If any tests fail:
61
- - Determine if YOUR changes caused the failure
62
- - Fix any regressions you introduced
63
- - Re-run tests until all pass
64
- 4. If tests were already failing before your changes (pre-existing), note them in the PR description but do NOT block on them
65
- 5. **Run any other checks** the repo defines (linting, type checking, formatting) — read project docs for the full list
66
- 6. Do NOT push code with failing tests or a broken build that you introduced
54
+ Build and test before pushing:
55
+
56
+ 1. **Build** the project using its build system (check CLAUDE.md, package.json, README, or Makefile). Retry up to 3 times; if it still fails, report the errors and stop.
57
+ 2. **Run the full test suite** using the command the project defines. Fix regressions you introduced and re-run until your changes are green.
58
+ 3. If tests were already failing before your changes, note that in the PR description but do not block on pre-existing failures.
59
+ 4. **Run any other checks** the repo defines (linting, type checking, formatting).
60
+ 5. Do NOT push code with a broken build or failing tests that you introduced.
67
61
 
68
62
  ## Push & Create PR
69
63
 
@@ -83,4 +77,4 @@ Include build/test status and run instructions in the PR description. If the pro
83
77
 
84
78
  Your task is complete once you have: (1) confirmed build and tests pass, (2) pushed your branch, and (3) created the PR. Your final message MUST include the PR URL so the engine can track it. Stop immediately after.
85
79
 
86
- **NEVER run `gh pr merge` or any merge command on your own PR.** The engine reviews and merges PRs through a separate review cycle. Self-merging bypasses code review entirely and is prohibited regardless of circumstances.
80
+ Do NOT run `gh pr merge` or any other merge command on your own PR. The engine reviews and merges PRs through a separate review cycle. Self-merging is prohibited.
@@ -16,7 +16,7 @@ A user has provided a plan. Analyze it against the codebase and produce a struct
16
16
  1. **Read the plan carefully** — understand the goals, scope, and requirements
17
17
  2. **Check for an existing PRD** — if the engine provides `existing_prd_json` below, a PRD already exists for this plan. See "Reusing an Existing PRD" section for how to preserve item IDs and done statuses. If no existing PRD is provided, this is a fresh run — all items start as `"missing"`.
18
18
  3. **Explore the codebase** at `{{project_path}}` — understand the existing structure to write accurate descriptions and acceptance criteria. Do NOT use observations about existing PRs or partial work to set item statuses — status is determined only by existing PRD items (step 2), not codebase state
19
- 4. **Break the plan into discrete, implementable items** — each should be a single PR's worth of work
19
+ 4. **Break the plan into discrete, implementable items** — each should be a single PR's worth of work, with enough detail for another agent to implement it directly
20
20
  5. **Estimate complexity** — `small` (< 1 file), `medium` (2-5 files), `large` (6+ files or cross-cutting)
21
21
  6. **Order by dependency** — items that others depend on come first
22
22
  7. **Use unique item IDs** — generate a short uuid for each item (e.g. `P-a3f9b2c1`). Do not use sequential `P001`/`P002` — IDs must be globally unique across all PRDs to avoid collisions. **If reusing an existing PRD, keep all existing IDs — only generate new UUIDs for genuinely new items.**
@@ -88,7 +88,7 @@ Rules for items:
88
88
  - **Do NOT include a "verify" or "test" or "integration test" item** — the engine automatically creates a verify task when all PRD items are done. Adding one manually creates a duplicate that blocks plan completion.
89
89
  - **`project` field is REQUIRED** — set it to the project name where the code changes go (e.g., `"OfficeAgent"`, `"office-bohemia"`). Cross-repo plans must route each item to the correct project. The engine materializes items into that project's work queue.
90
90
  - `depends_on` lists IDs of items that must be done first
91
- - Keep descriptions actionable — an implementing agent should know exactly what to build
91
+ - Keep descriptions actionable — name the files, functions, patterns, or integration points the implementing agent should touch whenever the plan makes them clear
92
92
  - Include `acceptance_criteria` so reviewers know when it's done
93
93
  - Aim for 5-25 items depending on plan scope. If more than 25, group related work
94
94
 
@@ -13,7 +13,7 @@ Repo: {{repo_name}} | Org: {{ado_org}} | ADO Project: {{ado_project}}
13
13
 
14
14
  ## Your Task
15
15
 
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:
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. Follow this process:
17
17
 
18
18
  1. **Set up worktrees** with all PR branches merged
19
19
  2. **Understand the project** — read its docs to learn how to build, test, and run it
@@ -43,15 +43,17 @@ For each project worktree, **read its documentation** to understand:
43
43
 
44
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.
45
45
 
46
+ Treat the repository's own docs, scripts, and config as the source of truth for build, test, and run commands. If the repo provides multiple options, choose the standard local verification path it recommends.
47
+
46
48
  **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.
47
49
 
48
50
  ## Step 3: Build and Test
49
51
 
50
52
  For each project worktree:
51
53
  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
54
+ 2. Install dependencies using the command or workflow the repo specifies
55
+ 3. Run the build using the repo's documented build command
56
+ 4. Run the test suite the repo defines for full verification
55
57
  5. Record: PASS or FAIL with error output, test counts (passed/failed/skipped)
56
58
 
57
59
  If a build or test fails, **do NOT fix it** — report the exact error and continue with other projects.
@@ -61,18 +63,13 @@ If a build or test fails, **do NOT fix it** — report the exact error and conti
61
63
  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.
62
64
 
63
65
  If found:
64
- 1. Start it **detached from your process** so it survives after you exit. Use the platform-appropriate method:
65
- ```bash
66
- cd <worktree-path>
67
- nohup <start-command> > app-server.log 2>&1 &
68
- echo $! > app-server.pid
69
- ```
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
66
+ 1. Start it **detached from your process** so it survives after you exit.
67
+ - If the repo docs provide a local run or background-start command, use that.
68
+ - Otherwise, use the detached-process mechanism that fits the current environment. Do not assume Bash, PowerShell, or any specific shell unless the repo or runtime clearly provides it.
69
+ 2. Wait a few seconds, then verify it using the repo's documented smoke test, health check, startup output, or the lightest project-appropriate manual check.
70
+ 3. Note the URL, port, process identifier, or equivalent runtime details the repo exposes
74
71
  4. Output the exact restart command with **absolute worktree paths**
75
- 5. Include the stop command (e.g. `kill <PID>` or `taskkill /PID <PID> /F` on Windows)
72
+ 5. Include the stop command or shutdown procedure that matches how you started it
76
73
 
77
74
  If the project has no runnable application, skip this step and note that in the guide.
78
75
 
@@ -144,11 +141,12 @@ For each project worktree:
144
141
 
145
142
  ## Working Style
146
143
 
147
- Use subagents only for genuinely parallel, independent tasks (e.g., building separate project worktrees simultaneously). For sequential steps like reading docs, running tests, and writing the report, work directly — do not spawn subagents.
144
+ Use subagents only for genuinely parallel, independent build/test tasks on separate project worktrees. For sequential work (docs build test → report), and for starting detached servers, work directly — do not spawn subagents.
148
145
 
149
146
  ## Rules
150
147
 
151
148
  - **Read the project docs first** — never assume a build system, language, or framework
149
+ - Treat repo-provided docs, scripts, and config as the source of truth for build/test/run commands
152
150
  - Base testing steps on the **acceptance criteria** from each plan item
153
151
  - Include **concrete steps** — URLs, buttons to click, inputs to type, expected results
154
152
  - Be **transparent** — clearly separate what you verified vs what needs human review
@@ -59,4 +59,4 @@ If you encounter merge conflicts during push or PR creation:
59
59
 
60
60
  Your task is complete once you have: (1) confirmed build and tests pass, (2) pushed your branch, and (3) created the PR. Do NOT continue beyond the task description. Stop immediately.
61
61
 
62
- **NEVER run `gh pr merge` or any merge command on your own PR.** The engine reviews and merges PRs through a separate review cycle. Self-merging bypasses code review entirely and is prohibited regardless of circumstances.
62
+ Do NOT run `gh pr merge` or any other merge command on your own PR. The engine reviews and merges PRs through a separate review cycle. Self-merging is prohibited.