gsd-pi 2.22.0 → 2.24.0

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 (228) hide show
  1. package/README.md +25 -1
  2. package/dist/cli.js +74 -7
  3. package/dist/headless.d.ts +25 -0
  4. package/dist/headless.js +454 -0
  5. package/dist/help-text.js +47 -0
  6. package/dist/mcp-server.d.ts +20 -3
  7. package/dist/mcp-server.js +21 -1
  8. package/dist/models-resolver.d.ts +32 -0
  9. package/dist/models-resolver.js +50 -0
  10. package/dist/resource-loader.js +64 -9
  11. package/dist/resources/extensions/bg-shell/output-formatter.ts +36 -16
  12. package/dist/resources/extensions/bg-shell/process-manager.ts +6 -4
  13. package/dist/resources/extensions/bg-shell/types.ts +33 -1
  14. package/dist/resources/extensions/browser-tools/capture.ts +18 -16
  15. package/dist/resources/extensions/browser-tools/index.ts +20 -0
  16. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
  17. package/dist/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
  18. package/dist/resources/extensions/browser-tools/tools/codegen.ts +274 -0
  19. package/dist/resources/extensions/browser-tools/tools/device.ts +183 -0
  20. package/dist/resources/extensions/browser-tools/tools/extract.ts +229 -0
  21. package/dist/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
  22. package/dist/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
  23. package/dist/resources/extensions/browser-tools/tools/pdf.ts +92 -0
  24. package/dist/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
  25. package/dist/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
  26. package/dist/resources/extensions/browser-tools/tools/zoom.ts +104 -0
  27. package/dist/resources/extensions/gsd/auto-dashboard.ts +2 -0
  28. package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
  29. package/dist/resources/extensions/gsd/auto-prompts.ts +73 -0
  30. package/dist/resources/extensions/gsd/auto-recovery.ts +51 -2
  31. package/dist/resources/extensions/gsd/auto-worktree.ts +15 -3
  32. package/dist/resources/extensions/gsd/auto.ts +560 -52
  33. package/dist/resources/extensions/gsd/captures.ts +49 -0
  34. package/dist/resources/extensions/gsd/commands.ts +194 -11
  35. package/dist/resources/extensions/gsd/complexity.ts +1 -0
  36. package/dist/resources/extensions/gsd/dashboard-overlay.ts +54 -2
  37. package/dist/resources/extensions/gsd/diff-context.ts +73 -80
  38. package/dist/resources/extensions/gsd/doctor.ts +76 -12
  39. package/dist/resources/extensions/gsd/exit-command.ts +2 -2
  40. package/dist/resources/extensions/gsd/forensics.ts +95 -52
  41. package/dist/resources/extensions/gsd/gitignore.ts +1 -0
  42. package/dist/resources/extensions/gsd/guided-flow.ts +85 -5
  43. package/dist/resources/extensions/gsd/index.ts +34 -1
  44. package/dist/resources/extensions/gsd/mcp-server.ts +33 -12
  45. package/dist/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  46. package/dist/resources/extensions/gsd/parallel-merge.ts +156 -0
  47. package/dist/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  48. package/dist/resources/extensions/gsd/post-unit-hooks.ts +2 -1
  49. package/dist/resources/extensions/gsd/preferences.ts +65 -1
  50. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  51. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -0
  52. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
  53. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
  54. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  55. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  56. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
  57. package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
  58. package/dist/resources/extensions/gsd/roadmap-slices.ts +41 -1
  59. package/dist/resources/extensions/gsd/session-forensics.ts +36 -2
  60. package/dist/resources/extensions/gsd/session-status-io.ts +197 -0
  61. package/dist/resources/extensions/gsd/state.ts +72 -30
  62. package/dist/resources/extensions/gsd/templates/milestone-validation.md +62 -0
  63. package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  64. package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  65. package/dist/resources/extensions/gsd/tests/auto-lock-creation.test.ts +186 -0
  66. package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  67. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
  68. package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
  69. package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  70. package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  71. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  72. package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  73. package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  74. package/dist/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  75. package/dist/resources/extensions/gsd/tests/doctor.test.ts +58 -0
  76. package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
  77. package/dist/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
  78. package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  79. package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  80. package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  81. package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  82. package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  83. package/dist/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -1
  84. package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
  85. package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
  86. package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  87. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
  88. package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  89. package/dist/resources/extensions/gsd/triage-resolution.ts +83 -0
  90. package/dist/resources/extensions/gsd/types.ts +15 -1
  91. package/dist/resources/extensions/gsd/visualizer-overlay.ts +8 -1
  92. package/dist/resources/extensions/gsd/workspace-index.ts +34 -6
  93. package/dist/resources/extensions/subagent/index.ts +5 -0
  94. package/dist/resources/extensions/subagent/worker-registry.ts +99 -0
  95. package/dist/update-check.d.ts +9 -0
  96. package/dist/update-check.js +97 -0
  97. package/package.json +6 -1
  98. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  99. package/packages/pi-ai/dist/providers/anthropic.js +16 -7
  100. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  101. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  102. package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -4
  103. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  104. package/packages/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/google-vertex.js +21 -9
  106. package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  108. package/packages/pi-ai/dist/providers/openai-completions.js +12 -4
  109. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  110. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  111. package/packages/pi-ai/dist/providers/openai-responses.js +12 -4
  112. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  113. package/packages/pi-ai/src/providers/anthropic.ts +21 -8
  114. package/packages/pi-ai/src/providers/azure-openai-responses.ts +16 -4
  115. package/packages/pi-ai/src/providers/google-vertex.ts +32 -17
  116. package/packages/pi-ai/src/providers/openai-completions.ts +16 -4
  117. package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
  118. package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
  119. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  121. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.d.ts +10 -0
  123. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.d.ts.map +1 -0
  124. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js +79 -0
  125. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js.map +1 -0
  126. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +18 -0
  127. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/tools/bash.js +77 -1
  129. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +1 -1
  131. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/tools/index.js +1 -1
  133. package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  135. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/index.js +1 -1
  137. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  138. package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
  139. package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
  140. package/packages/pi-coding-agent/src/core/tools/bash-background.test.ts +91 -0
  141. package/packages/pi-coding-agent/src/core/tools/bash.ts +83 -1
  142. package/packages/pi-coding-agent/src/core/tools/index.ts +1 -0
  143. package/packages/pi-coding-agent/src/index.ts +1 -0
  144. package/scripts/postinstall.js +7 -109
  145. package/src/resources/extensions/bg-shell/output-formatter.ts +36 -16
  146. package/src/resources/extensions/bg-shell/process-manager.ts +6 -4
  147. package/src/resources/extensions/bg-shell/types.ts +33 -1
  148. package/src/resources/extensions/browser-tools/capture.ts +18 -16
  149. package/src/resources/extensions/browser-tools/index.ts +20 -0
  150. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
  151. package/src/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
  152. package/src/resources/extensions/browser-tools/tools/codegen.ts +274 -0
  153. package/src/resources/extensions/browser-tools/tools/device.ts +183 -0
  154. package/src/resources/extensions/browser-tools/tools/extract.ts +229 -0
  155. package/src/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
  156. package/src/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
  157. package/src/resources/extensions/browser-tools/tools/pdf.ts +92 -0
  158. package/src/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
  159. package/src/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
  160. package/src/resources/extensions/browser-tools/tools/zoom.ts +104 -0
  161. package/src/resources/extensions/gsd/auto-dashboard.ts +2 -0
  162. package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
  163. package/src/resources/extensions/gsd/auto-prompts.ts +73 -0
  164. package/src/resources/extensions/gsd/auto-recovery.ts +51 -2
  165. package/src/resources/extensions/gsd/auto-worktree.ts +15 -3
  166. package/src/resources/extensions/gsd/auto.ts +560 -52
  167. package/src/resources/extensions/gsd/captures.ts +49 -0
  168. package/src/resources/extensions/gsd/commands.ts +194 -11
  169. package/src/resources/extensions/gsd/complexity.ts +1 -0
  170. package/src/resources/extensions/gsd/dashboard-overlay.ts +54 -2
  171. package/src/resources/extensions/gsd/diff-context.ts +73 -80
  172. package/src/resources/extensions/gsd/doctor.ts +76 -12
  173. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  174. package/src/resources/extensions/gsd/forensics.ts +95 -52
  175. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  176. package/src/resources/extensions/gsd/guided-flow.ts +85 -5
  177. package/src/resources/extensions/gsd/index.ts +34 -1
  178. package/src/resources/extensions/gsd/mcp-server.ts +33 -12
  179. package/src/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  180. package/src/resources/extensions/gsd/parallel-merge.ts +156 -0
  181. package/src/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  182. package/src/resources/extensions/gsd/post-unit-hooks.ts +2 -1
  183. package/src/resources/extensions/gsd/preferences.ts +65 -1
  184. package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  185. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -0
  186. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
  187. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
  188. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  189. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  190. package/src/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
  191. package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
  192. package/src/resources/extensions/gsd/roadmap-slices.ts +41 -1
  193. package/src/resources/extensions/gsd/session-forensics.ts +36 -2
  194. package/src/resources/extensions/gsd/session-status-io.ts +197 -0
  195. package/src/resources/extensions/gsd/state.ts +72 -30
  196. package/src/resources/extensions/gsd/templates/milestone-validation.md +62 -0
  197. package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  198. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  199. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +186 -0
  200. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  201. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
  202. package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
  203. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  204. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  205. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  206. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  207. package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  208. package/src/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  209. package/src/resources/extensions/gsd/tests/doctor.test.ts +58 -0
  210. package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
  211. package/src/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
  212. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  213. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  214. package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  215. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  216. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  217. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -1
  218. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
  219. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
  220. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  221. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
  222. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  223. package/src/resources/extensions/gsd/triage-resolution.ts +83 -0
  224. package/src/resources/extensions/gsd/types.ts +15 -1
  225. package/src/resources/extensions/gsd/visualizer-overlay.ts +8 -1
  226. package/src/resources/extensions/gsd/workspace-index.ts +34 -6
  227. package/src/resources/extensions/subagent/index.ts +5 -0
  228. package/src/resources/extensions/subagent/worker-registry.ts +99 -0
@@ -248,31 +248,24 @@ async function main(): Promise<void> {
248
248
  }
249
249
  }
250
250
 
251
- // ─── Test 5: Requirements counting from DB content ────────────────────
252
- console.log('\n=== derive-state-db: requirements from DB content ===');
251
+ // ─── Test 5: Requirements counting from disk (DB no longer used for content)
252
+ console.log('\n=== derive-state-db: requirements from disk content ===');
253
253
  {
254
254
  const base = createFixtureBase();
255
255
  try {
256
256
  // Write minimal milestone dir (needed for milestone discovery)
257
257
  mkdirSync(join(base, '.gsd', 'milestones', 'M001'), { recursive: true });
258
- // Do NOT write REQUIREMENTS.md to disk only in DB
259
-
260
- openDatabase(':memory:');
261
- insertArtifactRow('REQUIREMENTS.md', REQUIREMENTS_CONTENT, {
262
- artifact_type: 'requirements',
263
- });
258
+ // Write REQUIREMENTS.md to disk (DB content is no longer used by deriveState)
259
+ writeFile(base, 'REQUIREMENTS.md', REQUIREMENTS_CONTENT);
264
260
 
265
261
  invalidateStateCache();
266
262
  const state = await deriveState(base);
267
263
 
268
- // Requirements should come from DB
269
- assertEq(state.requirements?.active, 2, 'req-from-db: requirements.active = 2');
270
- assertEq(state.requirements?.validated, 1, 'req-from-db: requirements.validated = 1');
271
- assertEq(state.requirements?.total, 3, 'req-from-db: requirements.total = 3');
272
-
273
- closeDatabase();
264
+ // Requirements should come from disk
265
+ assertEq(state.requirements?.active, 2, 'req-from-disk: requirements.active = 2');
266
+ assertEq(state.requirements?.validated, 1, 'req-from-disk: requirements.validated = 1');
267
+ assertEq(state.requirements?.total, 3, 'req-from-disk: requirements.total = 3');
274
268
  } finally {
275
- closeDatabase();
276
269
  cleanup(base);
277
270
  }
278
271
  }
@@ -310,6 +303,7 @@ async function main(): Promise<void> {
310
303
  mkdirSync(join(base, '.gsd', 'milestones', 'M001'), { recursive: true });
311
304
  mkdirSync(join(base, '.gsd', 'milestones', 'M002'), { recursive: true });
312
305
  writeFile(base, 'milestones/M001/M001-ROADMAP.md', completedRoadmap);
306
+ writeFile(base, 'milestones/M001/M001-VALIDATION.md', `---\nverdict: pass\nremediation_round: 0\n---\n\n# Validation\nPassed.`);
313
307
  writeFile(base, 'milestones/M001/M001-SUMMARY.md', summaryContent);
314
308
  writeFile(base, 'milestones/M002/M002-ROADMAP.md', activeRoadmap);
315
309
 
@@ -26,6 +26,12 @@ function writeMilestoneSummary(base: string, mid: string, content: string): void
26
26
  writeFileSync(join(dir, `${mid}-SUMMARY.md`), content);
27
27
  }
28
28
 
29
+ function writeMilestoneValidation(base: string, mid: string): void {
30
+ const dir = join(base, '.gsd', 'milestones', mid);
31
+ mkdirSync(dir, { recursive: true });
32
+ writeFileSync(join(dir, `${mid}-VALIDATION.md`), `---\nverdict: pass\nremediation_round: 0\n---\n\n# Validation\nPassed.`);
33
+ }
34
+
29
35
  /**
30
36
  * Creates M00x-CONTEXT.md with a valid YAML frontmatter block.
31
37
  * frontmatter is the raw YAML lines between the --- delimiters.
@@ -120,6 +126,7 @@ async function main(): Promise<void> {
120
126
  - [x] **S01: Done** \`risk:low\` \`depends:[]\`
121
127
  > After this: Done.
122
128
  `);
129
+ writeMilestoneValidation(base, 'M001');
123
130
  writeMilestoneSummary(base, 'M001', '# M001 Summary\n\nFirst milestone is complete.');
124
131
 
125
132
  // M002: depends on M001, now unblocked
@@ -252,6 +259,7 @@ async function main(): Promise<void> {
252
259
  - [x] **S01: Done** \`risk:low\` \`depends:[]\`
253
260
  > After this: Done.
254
261
  `);
262
+ writeMilestoneValidation(base, 'M002');
255
263
  writeMilestoneSummary(base, 'M002', '# M002 Summary\n\nSecond milestone is complete.');
256
264
 
257
265
  const state = await deriveState(base);
@@ -321,6 +329,7 @@ async function main(): Promise<void> {
321
329
  - [x] **S01: Done** \`risk:low\` \`depends:[]\`
322
330
  > After this: Done.
323
331
  `);
332
+ writeMilestoneValidation(base, 'M004-0zjrg0');
324
333
  writeMilestoneSummary(base, 'M004-0zjrg0', '# M004-0zjrg0 Summary\n\nComplete.');
325
334
 
326
335
  // M005-b0m2hl: depends on M004-0zjrg0 (lowercase hex suffix)
@@ -54,6 +54,12 @@ function writeMilestoneSummary(base: string, mid: string, content: string): void
54
54
  writeFileSync(join(dir, `${mid}-SUMMARY.md`), content);
55
55
  }
56
56
 
57
+ function writeMilestoneValidation(base: string, mid: string): void {
58
+ const dir = join(base, '.gsd', 'milestones', mid);
59
+ mkdirSync(dir, { recursive: true });
60
+ writeFileSync(join(dir, `${mid}-VALIDATION.md`), `---\nverdict: pass\nremediation_round: 0\n---\n\n# Validation\nPassed.`);
61
+ }
62
+
57
63
  function cleanup(base: string): void {
58
64
  rmSync(base, { recursive: true, force: true });
59
65
  }
@@ -143,6 +149,7 @@ async function main(): Promise<void> {
143
149
  - [x] **S01: Done** \`risk:low\` \`depends:[]\`
144
150
  > After this: Done.
145
151
  `);
152
+ writeMilestoneValidation(base, 'M001');
146
153
  writeMilestoneSummary(base, 'M001', '# M001 Summary\n\nFirst milestone complete.');
147
154
 
148
155
  // M002: only CONTEXT-DRAFT.md
@@ -178,6 +185,7 @@ async function main(): Promise<void> {
178
185
  - [x] **S01: Done** \`risk:low\` \`depends:[]\`
179
186
  > After this: Done.
180
187
  `);
188
+ writeMilestoneValidation(base, 'M001');
181
189
  writeMilestoneSummary(base, 'M001', '# M001 Summary\n\nComplete.');
182
190
 
183
191
  // M002: draft only — should become active with needs-discussion
@@ -38,6 +38,12 @@ function writeMilestoneSummary(base: string, mid: string, content: string): void
38
38
  writeFileSync(join(dir, `${mid}-SUMMARY.md`), content);
39
39
  }
40
40
 
41
+ function writeMilestoneValidation(base: string, mid: string, verdict: string = 'pass'): void {
42
+ const dir = join(base, '.gsd', 'milestones', mid);
43
+ mkdirSync(dir, { recursive: true });
44
+ writeFileSync(join(dir, `${mid}-VALIDATION.md`), `---\nverdict: ${verdict}\nremediation_round: 0\n---\n\n# Validation\nValidated.`);
45
+ }
46
+
41
47
  function writeRequirements(base: string, content: string): void {
42
48
  writeFileSync(join(base, '.gsd', 'REQUIREMENTS.md'), content);
43
49
  }
@@ -285,6 +291,7 @@ Continue from step 2.
285
291
  > After this: Done.
286
292
  `);
287
293
 
294
+ writeMilestoneValidation(base, 'M001');
288
295
  writeMilestoneSummary(base, 'M001', `# M001 Summary\n\nMilestone complete.`);
289
296
 
290
297
  const state = await deriveState(base);
@@ -381,6 +388,7 @@ Continue from step 2.
381
388
  > After this: Done.
382
389
  `);
383
390
 
391
+ writeMilestoneValidation(base, 'M001');
384
392
  writeMilestoneSummary(base, 'M001', `# M001 Summary\n\nFirst milestone complete.`);
385
393
 
386
394
  // M002: active (has incomplete slices)
@@ -486,6 +494,8 @@ Continue from step 2.
486
494
  > After this: S02 complete.
487
495
  `);
488
496
 
497
+ writeMilestoneValidation(base, 'M001');
498
+
489
499
  const state = await deriveState(base);
490
500
 
491
501
  assertEq(state.phase, 'completing-milestone', 'completing-ms: phase is completing-milestone');
@@ -521,6 +531,7 @@ Continue from step 2.
521
531
  > After this: Done.
522
532
  `);
523
533
 
534
+ writeMilestoneValidation(base, 'M001');
524
535
  writeMilestoneSummary(base, 'M001', `# M001 Summary\n\nMilestone is complete.`);
525
536
 
526
537
  const state = await deriveState(base);
@@ -550,6 +561,7 @@ Continue from step 2.
550
561
  - [x] **S01: Done** \`risk:low\` \`depends:[]\`
551
562
  > After this: Done.
552
563
  `);
564
+ writeMilestoneValidation(base, 'M001');
553
565
  writeMilestoneSummary(base, 'M001', `# M001 Summary\n\nFirst milestone complete.`);
554
566
 
555
567
  // M002: all slices done, no summary → completing-milestone
@@ -566,6 +578,8 @@ Continue from step 2.
566
578
  > After this: Done.
567
579
  `);
568
580
 
581
+ writeMilestoneValidation(base, 'M002');
582
+
569
583
  // M003: has incomplete slices → pending (M002 is active)
570
584
  writeRoadmap(base, 'M003', `# M003: Third Milestone
571
585
 
@@ -585,6 +585,64 @@ Discovered an issue.
585
585
  rmSync(dtBase, { recursive: true, force: true });
586
586
  }
587
587
 
588
+ // ─── unresolvable_dependency: range syntax dep warns ─────────────────
589
+ console.log("\n=== doctor: unresolvable_dependency warns for leftover range ID ===");
590
+ {
591
+ // Simulate a roadmap where expandDependencies did NOT expand (pre-fix stored artifact)
592
+ // by writing a dep that looks like a range but doesn't match any real slice.
593
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-udep-"));
594
+ const mDir2 = join(base, ".gsd", "milestones", "M001");
595
+ const sDir2 = join(mDir2, "slices", "S01");
596
+ const tDir2 = join(sDir2, "tasks");
597
+ mkdirSync(tDir2, { recursive: true });
598
+ writeFileSync(join(mDir2, "M001-ROADMAP.md"), [
599
+ "# M001: Test",
600
+ "",
601
+ "## Slices",
602
+ "- [x] **S01: Done** `risk:low` `depends:[]`",
603
+ " > After this: done",
604
+ "- [ ] **S02: Blocked** `risk:low` `depends:[S99]`",
605
+ " > After this: also done",
606
+ ].join("\n") + "\n");
607
+ writeFileSync(join(sDir2, "S01-PLAN.md"), "# S01\n\n**Goal:** g\n**Demo:** d\n\n## Tasks\n- [x] **T01: t** `est:5m`\n");
608
+ writeFileSync(join(tDir2, "T01-SUMMARY.md"), "---\nid: T01\nparent: S01\nmilestone: M001\n---\n# T01\n## What Happened\nDone.\n");
609
+
610
+ const r = await runGSDDoctor(base, { fix: false });
611
+ const udepIssues = r.issues.filter(i => i.code === "unresolvable_dependency");
612
+ assertTrue(udepIssues.length > 0, "unresolvable_dependency fires for unknown dep S99");
613
+ assertEq(udepIssues[0]?.severity, "warning", "severity is warning");
614
+ assertTrue(udepIssues[0]?.message.includes("S99"), "message names the bad dep");
615
+
616
+ rmSync(base, { recursive: true, force: true });
617
+ }
618
+
619
+ // ─── unresolvable_dependency: valid deps do not warn ─────────────────
620
+ console.log("\n=== doctor: no unresolvable_dependency for valid deps ===");
621
+ {
622
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-udep-ok-"));
623
+ const mDir2 = join(base, ".gsd", "milestones", "M001");
624
+ const sDir2 = join(mDir2, "slices", "S01");
625
+ const tDir2 = join(sDir2, "tasks");
626
+ mkdirSync(tDir2, { recursive: true });
627
+ writeFileSync(join(mDir2, "M001-ROADMAP.md"), [
628
+ "# M001: Test",
629
+ "",
630
+ "## Slices",
631
+ "- [x] **S01: Done** `risk:low` `depends:[]`",
632
+ " > After this: done",
633
+ "- [ ] **S02: Next** `risk:low` `depends:[S01]`",
634
+ " > After this: next done",
635
+ ].join("\n") + "\n");
636
+ writeFileSync(join(sDir2, "S01-PLAN.md"), "# S01\n\n**Goal:** g\n**Demo:** d\n\n## Tasks\n- [x] **T01: t** `est:5m`\n");
637
+ writeFileSync(join(tDir2, "T01-SUMMARY.md"), "---\nid: T01\nparent: S01\nmilestone: M001\n---\n# T01\n## What Happened\nDone.\n");
638
+
639
+ const r = await runGSDDoctor(base, { fix: false });
640
+ const udepIssues = r.issues.filter(i => i.code === "unresolvable_dependency");
641
+ assertEq(udepIssues.length, 0, "no unresolvable_dependency for valid S01 dep");
642
+
643
+ rmSync(base, { recursive: true, force: true });
644
+ }
645
+
588
646
  report();
589
647
  }
590
648
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * In-flight tool tracking tests — verifies that markToolStart/markToolEnd
3
- * correctly manage the in-flight tools set used by the idle watchdog to
3
+ * correctly manage the in-flight tools map used by the idle watchdog to
4
4
  * distinguish "agent waiting on long-running tool" from "agent is idle".
5
5
  *
6
6
  * Background: The idle watchdog checks every 15s for agent progress. Without
@@ -8,12 +8,15 @@
8
8
  * can run 20+ minutes for evaluations, deployments, test suites) are falsely
9
9
  * declared idle and interrupted by recovery steering messages.
10
10
  *
11
- * The fix hooks tool_execution_start/end events to track active tool calls.
12
- * When tools are in-flight, the watchdog resets lastProgressAt instead of
13
- * triggering idle recovery.
11
+ * The fix hooks tool_execution_start/end events to track active tool calls
12
+ * with start timestamps. When tools are in-flight and started recently
13
+ * (< idleTimeoutMs), the watchdog resets lastProgressAt instead of triggering
14
+ * idle recovery. When a tool has been in-flight for longer than idleTimeoutMs,
15
+ * it is treated as stuck (e.g., `command &` keeping stdout open) and recovery
16
+ * proceeds anyway.
14
17
  */
15
18
 
16
- import { markToolStart, markToolEnd, isAutoActive } from "../auto.ts";
19
+ import { markToolStart, markToolEnd, isAutoActive, getOldestInFlightToolAgeMs } from "../auto.ts";
17
20
  import { createTestContext } from './test-helpers.ts';
18
21
 
19
22
  const { assertEq, assertTrue, report } = createTestContext();
@@ -49,9 +52,17 @@ const { assertEq, assertTrue, report } = createTestContext();
49
52
  // ═══ Integration contract: expected exports from auto.ts ═════════════════════
50
53
 
51
54
  {
52
- console.log("\n=== auto.ts exports markToolStart and markToolEnd ===");
55
+ console.log("\n=== auto.ts exports markToolStart, markToolEnd, and getOldestInFlightToolAgeMs ===");
53
56
  assertEq(typeof markToolStart, "function", "markToolStart should be a function");
54
57
  assertEq(typeof markToolEnd, "function", "markToolEnd should be a function");
58
+ assertEq(typeof getOldestInFlightToolAgeMs, "function", "getOldestInFlightToolAgeMs should be a function");
59
+ }
60
+
61
+ {
62
+ console.log("\n=== getOldestInFlightToolAgeMs: returns 0 when no tools in-flight ===");
63
+ // When auto-mode is inactive, inFlightTools map is empty → age is 0
64
+ const age = getOldestInFlightToolAgeMs();
65
+ assertEq(age, 0, "should return 0 when no tools are in-flight");
55
66
  }
56
67
 
57
68
  {