gsd-pi 2.47.0 → 2.48.0-dev.2e7390c

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.
Files changed (174) hide show
  1. package/dist/resources/extensions/gsd/auto-start.js +8 -1
  2. package/dist/resources/extensions/gsd/forensics.js +292 -1
  3. package/dist/resources/extensions/gsd/guided-flow.js +85 -3
  4. package/dist/resources/extensions/gsd/prompts/forensics.md +37 -5
  5. package/dist/resources/extensions/gsd/session-forensics.js +10 -1
  6. package/dist/web/standalone/.next/BUILD_ID +1 -1
  7. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  8. package/dist/web/standalone/.next/build-manifest.json +3 -3
  9. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  10. package/dist/web/standalone/.next/required-server-files.json +3 -3
  11. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  12. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  13. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  14. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  15. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  16. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  22. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  24. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  25. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  26. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  28. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  32. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  33. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  34. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  35. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  36. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  37. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  38. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  39. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  40. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  41. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  42. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  43. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  44. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  45. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  46. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  47. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  49. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
  76. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  82. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  96. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  98. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  100. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  102. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/index.html +1 -1
  112. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  113. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  114. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  115. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  117. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app/page.js +2 -2
  119. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  121. package/dist/web/standalone/.next/server/chunks/229.js +1 -1
  122. package/dist/web/standalone/.next/server/chunks/471.js +3 -3
  123. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/middleware.js +2 -2
  125. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  127. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  128. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  129. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  130. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  131. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  132. package/dist/web/standalone/.next/static/chunks/app/page-6654a8cca61a3d1c.js +1 -0
  133. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  134. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  135. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  136. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  137. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  138. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  139. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  140. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  141. package/dist/web/standalone/server.js +1 -1
  142. package/package.json +1 -1
  143. package/packages/pi-agent-core/dist/agent-loop.js +3 -2
  144. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  145. package/packages/pi-agent-core/src/agent-loop.ts +3 -2
  146. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +43 -0
  147. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  148. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/model-registry.js +26 -3
  150. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  151. package/packages/pi-coding-agent/package.json +1 -1
  152. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +70 -0
  153. package/packages/pi-coding-agent/src/core/model-registry.ts +29 -2
  154. package/packages/pi-tui/dist/components/box.d.ts +1 -0
  155. package/packages/pi-tui/dist/components/box.d.ts.map +1 -1
  156. package/packages/pi-tui/dist/components/box.js +10 -0
  157. package/packages/pi-tui/dist/components/box.js.map +1 -1
  158. package/packages/pi-tui/src/components/box.ts +10 -0
  159. package/pkg/package.json +1 -1
  160. package/src/resources/extensions/gsd/auto-start.ts +7 -1
  161. package/src/resources/extensions/gsd/forensics.ts +329 -2
  162. package/src/resources/extensions/gsd/guided-flow.ts +105 -3
  163. package/src/resources/extensions/gsd/prompts/forensics.md +37 -5
  164. package/src/resources/extensions/gsd/session-forensics.ts +11 -1
  165. package/src/resources/extensions/gsd/tests/discuss-queued-milestones.test.ts +241 -0
  166. package/src/resources/extensions/gsd/tests/forensics-error-filter.test.ts +121 -0
  167. package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +162 -0
  168. package/src/resources/extensions/gsd/tests/preflight-context-draft-filter.test.ts +115 -0
  169. package/src/resources/extensions/gsd/tests/stale-milestone-id-reservation.test.ts +79 -0
  170. package/dist/web/standalone/.next/static/chunks/app/page-12dd5ece0df4badc.js +0 -1
  171. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  172. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  173. /package/dist/web/standalone/.next/static/{VPcLnRF4BL8VoJEilBwlB → uTZ196cPUij3KcIDCweR6}/_buildManifest.js +0 -0
  174. /package/dist/web/standalone/.next/static/{VPcLnRF4BL8VoJEilBwlB → uTZ196cPUij3KcIDCweR6}/_ssgManifest.js +0 -0
@@ -37,7 +37,7 @@ import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./comm
37
37
  // ─── Types ────────────────────────────────────────────────────────────────────
38
38
 
39
39
  interface ForensicAnomaly {
40
- type: "stuck-loop" | "cost-spike" | "timeout" | "missing-artifact" | "crash" | "doctor-issue" | "error-trace";
40
+ type: "stuck-loop" | "cost-spike" | "timeout" | "missing-artifact" | "crash" | "doctor-issue" | "error-trace" | "journal-stuck" | "journal-guard-block" | "journal-rapid-iterations" | "journal-worktree-failure";
41
41
  severity: "info" | "warning" | "error";
42
42
  unitType?: string;
43
43
  unitId?: string;
@@ -54,6 +54,37 @@ interface UnitTrace {
54
54
  mtime: number;
55
55
  }
56
56
 
57
+ /** Summary of .gsd/activity/ directory metadata. */
58
+ interface ActivityLogMeta {
59
+ fileCount: number;
60
+ totalSizeBytes: number;
61
+ oldestFile: string | null;
62
+ newestFile: string | null;
63
+ }
64
+
65
+ /**
66
+ * Summary of .gsd/journal/ data for forensic investigation.
67
+ *
68
+ * To avoid loading huge journal histories into memory, only the most recent
69
+ * daily files are fully parsed. Older files are line-counted for totals.
70
+ * Event counts and flow IDs reflect only recent files.
71
+ */
72
+ interface JournalSummary {
73
+ /** Total journal entries across all files (recent parsed + older line-counted) */
74
+ totalEntries: number;
75
+ /** Distinct flow IDs from recent files (each = one auto-mode iteration) */
76
+ flowCount: number;
77
+ /** Event counts by type (from recent files only) */
78
+ eventCounts: Record<string, number>;
79
+ /** Most recent journal entries (last 20) for context */
80
+ recentEvents: { ts: string; flowId: string; eventType: string; rule?: string; unitId?: string }[];
81
+ /** Date range of journal data */
82
+ oldestEntry: string | null;
83
+ newestEntry: string | null;
84
+ /** Daily file count */
85
+ fileCount: number;
86
+ }
87
+
57
88
  interface ForensicReport {
58
89
  gsdVersion: string;
59
90
  timestamp: string;
@@ -68,6 +99,8 @@ interface ForensicReport {
68
99
  doctorIssues: DoctorIssue[];
69
100
  anomalies: ForensicAnomaly[];
70
101
  recentUnits: { type: string; id: string; cost: number; duration: number; model: string; finishedAt: number }[];
102
+ journalSummary: JournalSummary | null;
103
+ activityLogMeta: ActivityLogMeta | null;
71
104
  }
72
105
 
73
106
  // ─── Duplicate Detection ──────────────────────────────────────────────────────
@@ -276,7 +309,13 @@ export async function buildForensicReport(basePath: string): Promise<ForensicRep
276
309
  // from import.meta.url would resolve to ~/package.json (wrong on every system).
277
310
  const gsdVersion = process.env.GSD_VERSION || "unknown";
278
311
 
279
- // 9. Run anomaly detectors
312
+ // 9. Scan journal for flow timeline and structured events
313
+ const journalSummary = scanJournalForForensics(basePath);
314
+
315
+ // 10. Gather activity log directory metadata
316
+ const activityLogMeta = gatherActivityLogMeta(basePath, activeMilestone);
317
+
318
+ // 11. Run anomaly detectors
280
319
  if (metrics?.units) detectStuckLoops(metrics.units, anomalies);
281
320
  if (metrics?.units) detectCostSpikes(metrics.units, anomalies);
282
321
  detectTimeouts(unitTraces, anomalies);
@@ -284,6 +323,7 @@ export async function buildForensicReport(basePath: string): Promise<ForensicRep
284
323
  detectCrash(crashLock, anomalies);
285
324
  detectDoctorIssues(doctorIssues, anomalies);
286
325
  detectErrorTraces(unitTraces, anomalies);
326
+ detectJournalAnomalies(journalSummary, anomalies);
287
327
 
288
328
  return {
289
329
  gsdVersion,
@@ -299,6 +339,8 @@ export async function buildForensicReport(basePath: string): Promise<ForensicRep
299
339
  doctorIssues,
300
340
  anomalies,
301
341
  recentUnits,
342
+ journalSummary,
343
+ activityLogMeta,
302
344
  };
303
345
  }
304
346
 
@@ -306,6 +348,9 @@ export async function buildForensicReport(basePath: string): Promise<ForensicRep
306
348
 
307
349
  const ACTIVITY_FILENAME_RE = /^(\d+)-(.+?)-(.+)\.jsonl$/;
308
350
 
351
+ /** Threshold below which iteration cadence is considered rapid (thrashing). */
352
+ const RAPID_ITERATION_THRESHOLD_MS = 5000;
353
+
309
354
  function scanActivityLogs(basePath: string, activeMilestone?: string | null): UnitTrace[] {
310
355
  const activityDirs = resolveActivityDirs(basePath, activeMilestone);
311
356
  const allTraces: UnitTrace[] = [];
@@ -380,6 +425,154 @@ function resolveActivityDirs(basePath: string, activeMilestone?: string | null):
380
425
  return dirs;
381
426
  }
382
427
 
428
+ // ─── Journal Scanner ──────────────────────────────────────────────────────────
429
+
430
+ /**
431
+ * Max recent journal files to fully parse for event counts and recent events.
432
+ * Older files are line-counted only to avoid loading huge amounts of data.
433
+ */
434
+ const MAX_JOURNAL_RECENT_FILES = 3;
435
+
436
+ /** Max recent events to extract for the forensic report timeline. */
437
+ const MAX_JOURNAL_RECENT_EVENTS = 20;
438
+
439
+ /**
440
+ * Intelligently scan journal files for forensic summary.
441
+ *
442
+ * Journal files can be huge (thousands of JSONL entries over weeks of auto-mode).
443
+ * Instead of loading all entries into memory:
444
+ * - Only fully parse the most recent N daily files (event counts, flow tracking)
445
+ * - Line-count older files for approximate totals (no JSON parsing)
446
+ * - Extract only the last 20 events for the timeline
447
+ */
448
+ function scanJournalForForensics(basePath: string): JournalSummary | null {
449
+ try {
450
+ const journalDir = join(gsdRoot(basePath), "journal");
451
+ if (!existsSync(journalDir)) return null;
452
+
453
+ const files = readdirSync(journalDir).filter(f => f.endsWith(".jsonl")).sort();
454
+ if (files.length === 0) return null;
455
+
456
+ // Split into recent (fully parsed) and older (line-counted only)
457
+ const recentFiles = files.slice(-MAX_JOURNAL_RECENT_FILES);
458
+ const olderFiles = files.slice(0, -MAX_JOURNAL_RECENT_FILES);
459
+
460
+ // Line-count older files without parsing — avoids loading megabytes of JSON
461
+ let olderEntryCount = 0;
462
+ let oldestEntry: string | null = null;
463
+ for (const file of olderFiles) {
464
+ try {
465
+ const raw = readFileSync(join(journalDir, file), "utf-8");
466
+ const lines = raw.split("\n");
467
+ for (const line of lines) {
468
+ if (!line.trim()) continue;
469
+ olderEntryCount++;
470
+ // Extract only the timestamp from the first non-empty line of the oldest file
471
+ if (!oldestEntry) {
472
+ try {
473
+ const parsed = JSON.parse(line) as { ts?: string };
474
+ if (parsed.ts) oldestEntry = parsed.ts;
475
+ } catch { /* skip malformed */ }
476
+ }
477
+ }
478
+ } catch { /* skip unreadable files */ }
479
+ }
480
+
481
+ // Fully parse recent files for event counts and timeline
482
+ const eventCounts: Record<string, number> = {};
483
+ const flowIds = new Set<string>();
484
+ const recentParsedEntries: { ts: string; flowId: string; eventType: string; rule?: string; unitId?: string }[] = [];
485
+ let recentEntryCount = 0;
486
+
487
+ for (const file of recentFiles) {
488
+ try {
489
+ const raw = readFileSync(join(journalDir, file), "utf-8");
490
+ for (const line of raw.split("\n")) {
491
+ if (!line.trim()) continue;
492
+ try {
493
+ const entry = JSON.parse(line) as { ts: string; flowId: string; eventType: string; rule?: string; data?: Record<string, unknown> };
494
+ recentEntryCount++;
495
+ eventCounts[entry.eventType] = (eventCounts[entry.eventType] ?? 0) + 1;
496
+ flowIds.add(entry.flowId);
497
+
498
+ if (!oldestEntry) oldestEntry = entry.ts;
499
+
500
+ // Keep a rolling window of last N events — avoids accumulating unbounded arrays
501
+ recentParsedEntries.push({
502
+ ts: entry.ts,
503
+ flowId: entry.flowId,
504
+ eventType: entry.eventType,
505
+ rule: entry.rule,
506
+ unitId: entry.data?.unitId as string | undefined,
507
+ });
508
+ if (recentParsedEntries.length > MAX_JOURNAL_RECENT_EVENTS) {
509
+ recentParsedEntries.shift();
510
+ }
511
+ } catch { /* skip malformed lines */ }
512
+ }
513
+ } catch { /* skip unreadable files */ }
514
+ }
515
+
516
+ const totalEntries = olderEntryCount + recentEntryCount;
517
+ if (totalEntries === 0) return null;
518
+
519
+ const newestEntry = recentParsedEntries.length > 0
520
+ ? recentParsedEntries[recentParsedEntries.length - 1]!.ts
521
+ : null;
522
+
523
+ return {
524
+ totalEntries,
525
+ flowCount: flowIds.size,
526
+ eventCounts,
527
+ recentEvents: recentParsedEntries,
528
+ oldestEntry,
529
+ newestEntry,
530
+ fileCount: files.length,
531
+ };
532
+ } catch {
533
+ return null;
534
+ }
535
+ }
536
+
537
+ // ─── Activity Log Metadata ────────────────────────────────────────────────────
538
+
539
+ function gatherActivityLogMeta(basePath: string, activeMilestone?: string | null): ActivityLogMeta | null {
540
+ try {
541
+ const activityDirs = resolveActivityDirs(basePath, activeMilestone);
542
+ let fileCount = 0;
543
+ let totalSizeBytes = 0;
544
+ let oldestFile: string | null = null;
545
+ let newestFile: string | null = null;
546
+ let oldestMtime = Infinity;
547
+ let newestMtime = 0;
548
+
549
+ for (const activityDir of activityDirs) {
550
+ if (!existsSync(activityDir)) continue;
551
+ const files = readdirSync(activityDir).filter(f => f.endsWith(".jsonl"));
552
+ for (const file of files) {
553
+ const filePath = join(activityDir, file);
554
+ const stat = statSync(filePath, { throwIfNoEntry: false });
555
+ if (!stat) continue;
556
+ fileCount++;
557
+ totalSizeBytes += stat.size;
558
+ if (stat.mtimeMs < oldestMtime) {
559
+ oldestMtime = stat.mtimeMs;
560
+ oldestFile = file;
561
+ }
562
+ if (stat.mtimeMs > newestMtime) {
563
+ newestMtime = stat.mtimeMs;
564
+ newestFile = file;
565
+ }
566
+ }
567
+ }
568
+
569
+ if (fileCount === 0) return null;
570
+ return { fileCount, totalSizeBytes, oldestFile, newestFile };
571
+ } catch {
572
+ return null;
573
+ }
574
+ }
575
+
383
576
  // ─── Completed Keys Loader ────────────────────────────────────────────────────
384
577
 
385
578
  function loadCompletedKeys(basePath: string): string[] {
@@ -524,6 +717,66 @@ function detectErrorTraces(traces: UnitTrace[], anomalies: ForensicAnomaly[]): v
524
717
  }
525
718
  }
526
719
 
720
+ function detectJournalAnomalies(journal: JournalSummary | null, anomalies: ForensicAnomaly[]): void {
721
+ if (!journal) return;
722
+
723
+ // Detect stuck-detected events from the journal
724
+ const stuckCount = journal.eventCounts["stuck-detected"] ?? 0;
725
+ if (stuckCount > 0) {
726
+ anomalies.push({
727
+ type: "journal-stuck",
728
+ severity: stuckCount >= 3 ? "error" : "warning",
729
+ summary: `Journal recorded ${stuckCount} stuck-detected event(s)`,
730
+ details: `The auto-mode loop detected it was stuck ${stuckCount} time(s). Check journal events for flow IDs and causal chains to trace the root cause.`,
731
+ });
732
+ }
733
+
734
+ // Detect guard-block events (dispatch was blocked by a guard)
735
+ const guardCount = journal.eventCounts["guard-block"] ?? 0;
736
+ if (guardCount > 0) {
737
+ anomalies.push({
738
+ type: "journal-guard-block",
739
+ severity: guardCount >= 5 ? "warning" : "info",
740
+ summary: `Journal recorded ${guardCount} guard-block event(s)`,
741
+ details: `Dispatch was blocked by a guard condition ${guardCount} time(s). This may indicate a persistent blocking condition preventing progress.`,
742
+ });
743
+ }
744
+
745
+ // Detect rapid iterations (many flows in short time = likely thrashing)
746
+ if (journal.flowCount > 0 && journal.oldestEntry && journal.newestEntry) {
747
+ const oldest = new Date(journal.oldestEntry).getTime();
748
+ const newest = new Date(journal.newestEntry).getTime();
749
+ const spanMs = newest - oldest;
750
+ if (spanMs > 0 && journal.flowCount > 10) {
751
+ const avgMs = spanMs / journal.flowCount;
752
+ if (avgMs < RAPID_ITERATION_THRESHOLD_MS) {
753
+ anomalies.push({
754
+ type: "journal-rapid-iterations",
755
+ severity: "warning",
756
+ summary: `${journal.flowCount} iterations in ${formatDuration(spanMs)} (avg ${formatDuration(avgMs)}/iteration)`,
757
+ details: `Unusually rapid iteration cadence suggests the loop may be thrashing without making progress. Review recent journal events for dispatch-stop or terminal events.`,
758
+ });
759
+ }
760
+ }
761
+ }
762
+
763
+ // Detect worktree failures from journal events
764
+ const wtCreateFailed = journal.eventCounts["worktree-create-failed"] ?? 0;
765
+ const wtMergeFailed = journal.eventCounts["worktree-merge-failed"] ?? 0;
766
+ const wtFailures = wtCreateFailed + wtMergeFailed;
767
+ if (wtFailures > 0) {
768
+ const parts: string[] = [];
769
+ if (wtCreateFailed > 0) parts.push(`${wtCreateFailed} create failure(s)`);
770
+ if (wtMergeFailed > 0) parts.push(`${wtMergeFailed} merge failure(s)`);
771
+ anomalies.push({
772
+ type: "journal-worktree-failure",
773
+ severity: "warning",
774
+ summary: `Worktree failures: ${parts.join(", ")}`,
775
+ details: `Journal recorded worktree operation failures. These may indicate git state corruption or conflicting branches.`,
776
+ });
777
+ }
778
+ }
779
+
527
780
  // ─── Report Persistence ───────────────────────────────────────────────────────
528
781
 
529
782
  function saveForensicReport(basePath: string, report: ForensicReport, problemDescription: string): string {
@@ -600,6 +853,45 @@ function saveForensicReport(basePath: string, report: ForensicReport, problemDes
600
853
  sections.push(redact(formatCrashInfo(report.crashLock)), ``);
601
854
  }
602
855
 
856
+ // Activity log metadata
857
+ if (report.activityLogMeta) {
858
+ const meta = report.activityLogMeta;
859
+ sections.push(`## Activity Log Metadata`, ``);
860
+ sections.push(`- Files: ${meta.fileCount}`);
861
+ sections.push(`- Total size: ${(meta.totalSizeBytes / 1024).toFixed(1)} KB`);
862
+ if (meta.oldestFile) sections.push(`- Oldest: ${meta.oldestFile}`);
863
+ if (meta.newestFile) sections.push(`- Newest: ${meta.newestFile}`);
864
+ sections.push(``);
865
+ }
866
+
867
+ // Journal summary
868
+ if (report.journalSummary) {
869
+ const js = report.journalSummary;
870
+ sections.push(`## Journal Summary`, ``);
871
+ sections.push(`- Total entries: ${js.totalEntries}`);
872
+ sections.push(`- Distinct flows (iterations): ${js.flowCount}`);
873
+ sections.push(`- Daily files: ${js.fileCount}`);
874
+ if (js.oldestEntry) sections.push(`- Date range: ${js.oldestEntry} — ${js.newestEntry}`);
875
+ sections.push(``);
876
+ sections.push(`### Event Type Distribution`, ``);
877
+ sections.push(`| Event Type | Count |`);
878
+ sections.push(`|------------|-------|`);
879
+ for (const [evType, count] of Object.entries(js.eventCounts).sort((a, b) => b[1] - a[1])) {
880
+ sections.push(`| ${evType} | ${count} |`);
881
+ }
882
+ sections.push(``);
883
+ if (js.recentEvents.length > 0) {
884
+ sections.push(`### Recent Journal Events (last ${js.recentEvents.length})`, ``);
885
+ for (const ev of js.recentEvents) {
886
+ const parts = [`${ev.ts} [${ev.eventType}] flow=${ev.flowId.slice(0, 8)}`];
887
+ if (ev.rule) parts.push(`rule=${ev.rule}`);
888
+ if (ev.unitId) parts.push(`unit=${ev.unitId}`);
889
+ sections.push(`- ${parts.join(" ")}`);
890
+ }
891
+ sections.push(``);
892
+ }
893
+ }
894
+
603
895
  writeFileSync(filePath, sections.join("\n"), "utf-8");
604
896
  return filePath;
605
897
  }
@@ -681,6 +973,41 @@ function formatReportForPrompt(report: ForensicReport): string {
681
973
  sections.push("");
682
974
  }
683
975
 
976
+ // Activity log metadata
977
+ if (report.activityLogMeta) {
978
+ const meta = report.activityLogMeta;
979
+ sections.push("### Activity Log Overview");
980
+ sections.push(`- Files: ${meta.fileCount}, Total size: ${(meta.totalSizeBytes / 1024).toFixed(1)} KB`);
981
+ if (meta.oldestFile) sections.push(`- Oldest: ${meta.oldestFile}`);
982
+ if (meta.newestFile) sections.push(`- Newest: ${meta.newestFile}`);
983
+ sections.push("");
984
+ }
985
+
986
+ // Journal summary — structured event timeline
987
+ if (report.journalSummary) {
988
+ const js = report.journalSummary;
989
+ sections.push("### Journal Summary (Iteration Event Log)");
990
+ sections.push(`- Total entries: ${js.totalEntries}, Distinct flows: ${js.flowCount}, Daily files: ${js.fileCount}`);
991
+ if (js.oldestEntry) sections.push(`- Date range: ${js.oldestEntry} — ${js.newestEntry}`);
992
+
993
+ // Event type distribution (compact)
994
+ const eventPairs = Object.entries(js.eventCounts).sort((a, b) => b[1] - a[1]);
995
+ sections.push(`- Events: ${eventPairs.map(([t, c]) => `${t}(${c})`).join(", ")}`);
996
+
997
+ // Recent events timeline (for tracing what just happened)
998
+ if (js.recentEvents.length > 0) {
999
+ sections.push("");
1000
+ sections.push(`**Recent Journal Events (last ${js.recentEvents.length}):**`);
1001
+ for (const ev of js.recentEvents) {
1002
+ const parts = [`${ev.ts} [${ev.eventType}] flow=${ev.flowId.slice(0, 8)}`];
1003
+ if (ev.rule) parts.push(`rule=${ev.rule}`);
1004
+ if (ev.unitId) parts.push(`unit=${ev.unitId}`);
1005
+ sections.push(`- ${parts.join(" ")}`);
1006
+ }
1007
+ }
1008
+ sections.push("");
1009
+ }
1010
+
684
1011
  // Completed keys count
685
1012
  sections.push(`### Completed Keys: ${report.completedKeys.length}`);
686
1013
  sections.push(`### GSD Version: ${report.gsdVersion}`);
@@ -35,7 +35,7 @@ import { showProjectInit, offerMigration } from "./init-wizard.js";
35
35
  import { validateDirectory } from "./validate-directory.js";
36
36
  import { showConfirm } from "../shared/tui.js";
37
37
  import { debugLog } from "./debug-logger.js";
38
- import { findMilestoneIds, nextMilestoneId, reserveMilestoneId, getReservedMilestoneIds } from "./milestone-ids.js";
38
+ import { findMilestoneIds, nextMilestoneId, reserveMilestoneId, getReservedMilestoneIds, clearReservedMilestoneIds } from "./milestone-ids.js";
39
39
  import { parkMilestone, discardMilestone } from "./milestone-actions.js";
40
40
  import { resolveModelWithFallbacksForUnit } from "./preferences-models.js";
41
41
 
@@ -373,6 +373,9 @@ export async function showHeadlessMilestoneCreation(
373
373
  basePath: string,
374
374
  seedContext: string,
375
375
  ): Promise<void> {
376
+ // Clear stale reservations from previous cancelled sessions (#2488)
377
+ clearReservedMilestoneIds();
378
+
376
379
  // Ensure .gsd/ is bootstrapped
377
380
  bootstrapGsdProject(basePath);
378
381
 
@@ -511,9 +514,14 @@ export async function showDiscuss(
511
514
 
512
515
  const state = await deriveState(basePath);
513
516
 
514
- // Guard: no active milestone
517
+ // No active milestone — check for pending milestones to discuss instead
515
518
  if (!state.activeMilestone) {
516
- ctx.ui.notify("No active milestone. Run /gsd to create one first.", "warning");
519
+ const pendingMilestones = state.registry.filter(m => m.status === "pending");
520
+ if (pendingMilestones.length === 0) {
521
+ ctx.ui.notify("No active milestone. Run /gsd to create one first.", "warning");
522
+ return;
523
+ }
524
+ await showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones);
517
525
  return;
518
526
  }
519
527
 
@@ -648,6 +656,17 @@ export async function showDiscuss(
648
656
  };
649
657
  });
650
658
 
659
+ // Offer access to queued milestones when any exist
660
+ const pendingMilestones = state.registry.filter(m => m.status === "pending");
661
+ if (pendingMilestones.length > 0) {
662
+ actions.push({
663
+ id: "discuss_queued_milestone",
664
+ label: "Discuss a queued milestone",
665
+ description: `Refine context for ${pendingMilestones.length} queued milestone(s). Does not affect current execution.`,
666
+ recommended: false,
667
+ });
668
+ }
669
+
651
670
  const choice = await showNextAction(ctx, {
652
671
  title: "GSD — Discuss a slice",
653
672
  summary: [
@@ -660,6 +679,11 @@ export async function showDiscuss(
660
679
 
661
680
  if (choice === "not_yet") return;
662
681
 
682
+ if (choice === "discuss_queued_milestone") {
683
+ await showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones);
684
+ return;
685
+ }
686
+
663
687
  const chosen = pendingSlices.find(s => s.id === choice);
664
688
  if (!chosen) return;
665
689
 
@@ -689,6 +713,79 @@ export async function showDiscuss(
689
713
  }
690
714
  }
691
715
 
716
+ // ─── Queued Milestone Discussion ─────────────────────────────────────────────
717
+
718
+ /**
719
+ * Show a picker of queued (pending) milestones and dispatch a discuss flow for
720
+ * the chosen one. Discussing a queued milestone does NOT activate it — it only
721
+ * refines the CONTEXT.md artifact so it is better prepared when auto-mode
722
+ * eventually reaches it.
723
+ */
724
+ async function showDiscussQueuedMilestone(
725
+ ctx: ExtensionCommandContext,
726
+ pi: ExtensionAPI,
727
+ basePath: string,
728
+ pendingMilestones: Array<{ id: string; title: string; status: string }>,
729
+ ): Promise<void> {
730
+ const actions = pendingMilestones.map((m, i) => {
731
+ const hasContext = !!resolveMilestoneFile(basePath, m.id, "CONTEXT");
732
+ const hasDraft = !hasContext && !!resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
733
+ const contextStatus = hasContext ? "context ✓" : hasDraft ? "draft context" : "no context yet";
734
+ return {
735
+ id: m.id,
736
+ label: `${m.id}: ${m.title}`,
737
+ description: `[queued] · ${contextStatus}`,
738
+ recommended: i === 0,
739
+ };
740
+ });
741
+
742
+ const choice = await showNextAction(ctx, {
743
+ title: "GSD — Discuss a queued milestone",
744
+ summary: [
745
+ "Select a queued milestone to discuss.",
746
+ "Discussing will update its context file. It will not be activated.",
747
+ ],
748
+ actions,
749
+ notYetMessage: "Run /gsd discuss when ready.",
750
+ });
751
+
752
+ if (choice === "not_yet") return;
753
+
754
+ const chosen = pendingMilestones.find(m => m.id === choice);
755
+ if (!chosen) return;
756
+
757
+ await dispatchDiscussForMilestone(ctx, pi, basePath, chosen.id, chosen.title);
758
+ }
759
+
760
+ /**
761
+ * Dispatch the guided-discuss-milestone prompt for a milestone without
762
+ * setting pendingAutoStart — so discussing a queued milestone does not
763
+ * implicitly activate it when the session ends.
764
+ */
765
+ async function dispatchDiscussForMilestone(
766
+ ctx: ExtensionCommandContext,
767
+ pi: ExtensionAPI,
768
+ basePath: string,
769
+ mid: string,
770
+ milestoneTitle: string,
771
+ ): Promise<void> {
772
+ const draftFile = resolveMilestoneFile(basePath, mid, "CONTEXT-DRAFT");
773
+ const draftContent = draftFile ? await loadFile(draftFile) : null;
774
+ const discussMilestoneTemplates = inlineTemplate("context", "Context");
775
+ const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
776
+ const basePrompt = loadPrompt("guided-discuss-milestone", {
777
+ milestoneId: mid,
778
+ milestoneTitle,
779
+ inlinedTemplates: discussMilestoneTemplates,
780
+ structuredQuestionsAvailable,
781
+ commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
782
+ });
783
+ const prompt = draftContent
784
+ ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
785
+ : basePrompt;
786
+ await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "plan-milestone");
787
+ }
788
+
692
789
  // ─── Smart Entry Point ────────────────────────────────────────────────────────
693
790
 
694
791
  /**
@@ -842,6 +939,11 @@ export async function showSmartEntry(
842
939
  ): Promise<void> {
843
940
  const stepMode = options?.step;
844
941
 
942
+ // ── Clear stale milestone ID reservations from previous cancelled sessions ──
943
+ // Reservations only need to survive within a single /gsd interaction.
944
+ // Without this, each cancelled session permanently bumps the next ID. (#2488)
945
+ clearReservedMilestoneIds();
946
+
845
947
  // ── Directory safety check — refuse to operate in system/home dirs ───
846
948
  const dirCheck = validateDirectory(basePath);
847
949
  if (dirCheck.severity === "blocked") {
@@ -36,6 +36,8 @@ GSD extension source code is at: `{{gsdSourceDir}}`
36
36
  ├── doctor-history.jsonl — doctor check history
37
37
  ├── activity/ — session activity logs (JSONL per unit)
38
38
  │ └── {seq}-{unitType}-{unitId}.jsonl
39
+ ├── journal/ — structured event journal (JSONL per day)
40
+ │ └── YYYY-MM-DD.jsonl
39
41
  ├── runtime/
40
42
  │ ├── paused-session.json — serialized session when auto pauses
41
43
  │ └── headless-context.md — headless resume context
@@ -60,6 +62,32 @@ GSD extension source code is at: `{{gsdSourceDir}}`
60
62
  - `usage` field on assistant messages: `input`, `output`, `cacheRead`, `cacheWrite`, `totalTokens`, `cost`
61
63
  - **To trace a failure**: find the last activity log, search for `isError: true` tool results, then read the agent's reasoning text preceding that error
62
64
 
65
+ ### Journal Format (`.gsd/journal/`)
66
+
67
+ The journal is a structured event log for auto-mode iterations. Each daily file contains JSONL entries:
68
+
69
+ ```
70
+ { ts: "ISO-8601", flowId: "UUID", seq: 0, eventType: "iteration-start", rule?: "rule-name", causedBy?: { flowId, seq }, data?: { unitId, status, ... } }
71
+ ```
72
+
73
+ **Key event types:**
74
+ - `iteration-start` / `iteration-end` — marks loop iteration boundaries
75
+ - `dispatch-match` / `dispatch-stop` — what the auto-mode decided to do (or not do)
76
+ - `unit-start` / `unit-end` — lifecycle of individual work units
77
+ - `terminal` — auto-mode reached a terminal state (all done, budget exceeded, etc.)
78
+ - `guard-block` — dispatch was blocked by a guard condition (e.g. needs user input)
79
+ - `stuck-detected` — the loop detected it was stuck (same unit repeatedly dispatched)
80
+ - `milestone-transition` — a milestone was promoted or completed
81
+ - `worktree-enter` / `worktree-create-failed` / `worktree-merge-start` / `worktree-merge-failed` — worktree operations
82
+
83
+ **Key concepts:**
84
+ - **flowId**: UUID grouping all events in one iteration. Use to reconstruct what happened in a single loop pass.
85
+ - **causedBy**: Cross-reference to a prior event (same or different flow). Enables causal chain tracing.
86
+ - **seq**: Monotonically increasing within a flow. Reconstruct event order within an iteration.
87
+
88
+ **To trace a stuck loop**: filter for `stuck-detected` events, then follow `flowId` to see the surrounding dispatch and unit events.
89
+ **To trace a guard block**: filter for `guard-block` events, check `data.reason` for why dispatch was blocked.
90
+
63
91
  ### Crash Lock Format (`auto.lock`)
64
92
 
65
93
  JSON with fields: `pid`, `startedAt`, `unitType`, `unitId`, `unitStartedAt`, `completedUnits`, `sessionFile`
@@ -78,20 +106,24 @@ A unit dispatched more than once (`type/id` appears multiple times) indicates a
78
106
 
79
107
  1. **Start with the pre-parsed forensic report** above. The anomaly section contains automated findings — treat these as leads, not conclusions.
80
108
 
81
- 2. **Form hypotheses** about which module and code path is responsible. Use the source map to identify candidate files.
109
+ 2. **Check the journal timeline** if present. The journal events show the auto-mode's decision sequence (dispatches, guards, stuck detection, worktree operations). Use flow IDs to group related events and trace causal chains.
110
+
111
+ 3. **Cross-reference activity logs and journal**. Activity logs show *what the LLM did* (tool calls, reasoning, errors). Journal events show *what auto-mode decided* (dispatch rules, iteration boundaries, state transitions). Together they reveal the full picture.
112
+
113
+ 4. **Form hypotheses** about which module and code path is responsible. Use the source map to identify candidate files.
82
114
 
83
- 3. **Read the actual GSD source code** at `{{gsdSourceDir}}` to confirm or deny each hypothesis. Do not guess what code does — read it.
115
+ 5. **Read the actual GSD source code** at `{{gsdSourceDir}}` to confirm or deny each hypothesis. Do not guess what code does — read it.
84
116
 
85
- 4. **Trace the code path** from the entry point (usually `auto-loop.ts` dispatch or `auto-dispatch.ts`) through to the failure point. Follow function calls across files.
117
+ 6. **Trace the code path** from the entry point (usually `auto-loop.ts` dispatch or `auto-dispatch.ts`) through to the failure point. Follow function calls across files.
86
118
 
87
- 5. **Identify the specific file and line** where the bug lives. Determine what kind of defect it is:
119
+ 7. **Identify the specific file and line** where the bug lives. Determine what kind of defect it is:
88
120
  - Missing edge case / unhandled condition
89
121
  - Wrong boolean logic or comparison
90
122
  - Race condition or ordering issue
91
123
  - State corruption (e.g. completed-units.json out of sync with artifacts)
92
124
  - Timeout / recovery logic not triggering correctly
93
125
 
94
- 6. **Clarify if needed.** Use ask_user_questions (max 2 questions) only if the report is genuinely insufficient. Do not ask questions you can answer from the data or source code.
126
+ 8. **Clarify if needed.** Use ask_user_questions (max 2 questions) only if the report is genuinely insufficient. Do not ask questions you can answer from the data or source code.
95
127
 
96
128
  ## Output
97
129
 
@@ -172,7 +172,17 @@ export function extractTrace(entries: unknown[]): ExecutionTrace {
172
172
  }
173
173
 
174
174
  if (isError && resultText) {
175
- errors.push(resultText.slice(0, 300));
175
+ // Filter out benign "errors" that are normal during code exploration:
176
+ // - grep/rg/find returning exit code 1 (no matches) is expected POSIX behavior
177
+ // - User interrupts (Escape/skip) are intentional, not failures
178
+ const trimmed = resultText.trim();
179
+ const isBenignNoMatch = pending?.name === "bash" &&
180
+ /^\(no output\)\s*\n\s*Command exited with code 1$/m.test(trimmed);
181
+ const isUserSkip = /^Skipped due to queued user message/i.test(trimmed);
182
+
183
+ if (!isBenignNoMatch && !isUserSkip) {
184
+ errors.push(resultText.slice(0, 300));
185
+ }
176
186
  }
177
187
  }
178
188
  }