gsd-pi 2.62.0 → 2.62.1-dev.1ae2b74

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 (244) hide show
  1. package/dist/resources/extensions/ask-user-questions.js +47 -3
  2. package/dist/resources/extensions/gsd/auto/loop.js +8 -1
  3. package/dist/resources/extensions/gsd/auto/phases.js +10 -3
  4. package/dist/resources/extensions/gsd/auto-post-unit.js +6 -4
  5. package/dist/resources/extensions/gsd/auto-start.js +11 -6
  6. package/dist/resources/extensions/gsd/auto-timers.js +8 -2
  7. package/dist/resources/extensions/gsd/auto-verification.js +14 -3
  8. package/dist/resources/extensions/gsd/auto-worktree.js +19 -0
  9. package/dist/resources/extensions/gsd/auto.js +24 -0
  10. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +4 -0
  11. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +11 -1
  12. package/dist/resources/extensions/gsd/commands-handlers.js +18 -7
  13. package/dist/resources/extensions/gsd/db-writer.js +64 -28
  14. package/dist/resources/extensions/gsd/preferences-models.js +74 -0
  15. package/dist/resources/extensions/gsd/preferences-skills.js +6 -1
  16. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  17. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  18. package/dist/resources/extensions/gsd/skill-catalog.js +6 -4
  19. package/dist/resources/extensions/gsd/skill-discovery.js +24 -6
  20. package/dist/resources/extensions/gsd/skill-health.js +7 -3
  21. package/dist/resources/extensions/gsd/skill-telemetry.js +5 -2
  22. package/dist/resources/extensions/gsd/state.js +1 -0
  23. package/dist/resources/extensions/gsd/tools/complete-slice.js +3 -3
  24. package/dist/resources/extensions/gsd/workflow-logger.js +13 -8
  25. package/dist/resources/extensions/gsd/workflow-reconcile.js +3 -1
  26. package/dist/web/standalone/.next/BUILD_ID +1 -1
  27. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  28. package/dist/web/standalone/.next/build-manifest.json +3 -3
  29. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  30. package/dist/web/standalone/.next/required-server-files.json +3 -3
  31. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  32. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  34. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  42. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  58. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  70. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  98. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  104. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  118. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  120. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  122. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  124. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/index.html +1 -1
  134. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  135. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  136. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  137. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  138. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  139. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  140. package/dist/web/standalone/.next/server/app/page.js +2 -2
  141. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  142. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  143. package/dist/web/standalone/.next/server/chunks/2229.js +1 -1
  144. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  145. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  146. package/dist/web/standalone/.next/server/middleware.js +2 -2
  147. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  148. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  149. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  150. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  151. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  152. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  153. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  154. package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
  155. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  156. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  157. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  158. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  159. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  160. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  161. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  162. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  163. package/dist/web/standalone/server.js +1 -1
  164. package/package.json +1 -1
  165. package/packages/mcp-server/src/cli.ts +1 -1
  166. package/packages/mcp-server/src/index.ts +15 -1
  167. package/packages/mcp-server/src/readers/captures.ts +119 -0
  168. package/packages/mcp-server/src/readers/doctor-lite.ts +225 -0
  169. package/packages/mcp-server/src/readers/index.ts +16 -0
  170. package/packages/mcp-server/src/readers/knowledge.ts +111 -0
  171. package/packages/mcp-server/src/readers/metrics.ts +118 -0
  172. package/packages/mcp-server/src/readers/paths.ts +217 -0
  173. package/packages/mcp-server/src/readers/readers.test.ts +509 -0
  174. package/packages/mcp-server/src/readers/roadmap.ts +263 -0
  175. package/packages/mcp-server/src/readers/state.ts +223 -0
  176. package/packages/mcp-server/src/server.ts +134 -3
  177. package/packages/pi-ai/dist/utils/repair-tool-json.d.ts +26 -6
  178. package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
  179. package/packages/pi-ai/dist/utils/repair-tool-json.js +67 -9
  180. package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
  181. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +73 -1
  182. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
  183. package/packages/pi-ai/src/utils/repair-tool-json.ts +74 -10
  184. package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +94 -1
  185. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.d.ts +2 -0
  186. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.d.ts.map +1 -0
  187. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +16 -0
  188. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -0
  189. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  190. package/packages/pi-coding-agent/dist/core/agent-session.js +4 -0
  191. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  192. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +3 -0
  193. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  194. package/packages/pi-coding-agent/dist/core/retry-handler.js +48 -16
  195. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +20 -3
  197. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  198. package/packages/pi-coding-agent/package.json +1 -1
  199. package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +21 -0
  200. package/packages/pi-coding-agent/src/core/agent-session.ts +4 -0
  201. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +30 -3
  202. package/packages/pi-coding-agent/src/core/retry-handler.ts +49 -16
  203. package/pkg/package.json +1 -1
  204. package/src/resources/extensions/ask-user-questions.ts +60 -4
  205. package/src/resources/extensions/gsd/auto/loop.ts +8 -1
  206. package/src/resources/extensions/gsd/auto/phases.ts +8 -6
  207. package/src/resources/extensions/gsd/auto-post-unit.ts +6 -3
  208. package/src/resources/extensions/gsd/auto-start.ts +11 -6
  209. package/src/resources/extensions/gsd/auto-timers.ts +8 -2
  210. package/src/resources/extensions/gsd/auto-verification.ts +14 -3
  211. package/src/resources/extensions/gsd/auto-worktree.ts +18 -0
  212. package/src/resources/extensions/gsd/auto.ts +25 -0
  213. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +4 -0
  214. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +13 -1
  215. package/src/resources/extensions/gsd/commands-handlers.ts +20 -7
  216. package/src/resources/extensions/gsd/db-writer.ts +67 -30
  217. package/src/resources/extensions/gsd/preferences-models.ts +78 -0
  218. package/src/resources/extensions/gsd/preferences-skills.ts +6 -1
  219. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  220. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  221. package/src/resources/extensions/gsd/skill-catalog.ts +6 -3
  222. package/src/resources/extensions/gsd/skill-discovery.ts +23 -6
  223. package/src/resources/extensions/gsd/skill-health.ts +7 -3
  224. package/src/resources/extensions/gsd/skill-telemetry.ts +5 -2
  225. package/src/resources/extensions/gsd/state.ts +1 -0
  226. package/src/resources/extensions/gsd/tests/ask-user-questions-dedup.test.ts +120 -0
  227. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +22 -2
  228. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +107 -0
  229. package/src/resources/extensions/gsd/tests/claude-skill-dirs.test.ts +51 -0
  230. package/src/resources/extensions/gsd/tests/db-writer.test.ts +41 -0
  231. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +75 -1
  232. package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +108 -0
  233. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +17 -4
  234. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +17 -41
  235. package/src/resources/extensions/gsd/tests/worktree-db-respawn-truncation.test.ts +81 -2
  236. package/src/resources/extensions/gsd/tools/complete-slice.ts +3 -5
  237. package/src/resources/extensions/gsd/workflow-logger.ts +13 -8
  238. package/src/resources/extensions/gsd/workflow-reconcile.ts +3 -1
  239. package/src/resources/extensions/shared/tests/ask-user-freetext.test.ts +6 -1
  240. package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
  241. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  242. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  243. /package/dist/web/standalone/.next/static/{F4rzqt_3m83A68ZRiU12r → erQZ_8_1lkclnPJLJnCxG}/_buildManifest.js +0 -0
  244. /package/dist/web/standalone/.next/static/{F4rzqt_3m83A68ZRiU12r → erQZ_8_1lkclnPJLJnCxG}/_ssgManifest.js +0 -0
@@ -0,0 +1,509 @@
1
+ // GSD MCP Server — reader tests
2
+ // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
+
4
+ import { describe, it, before, after } from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { tmpdir } from 'node:os';
9
+ import { randomBytes } from 'node:crypto';
10
+
11
+ import { readProgress } from './state.js';
12
+ import { readRoadmap } from './roadmap.js';
13
+ import { readHistory } from './metrics.js';
14
+ import { readCaptures } from './captures.js';
15
+ import { readKnowledge } from './knowledge.js';
16
+ import { runDoctorLite } from './doctor-lite.js';
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Test fixture helpers
20
+ // ---------------------------------------------------------------------------
21
+
22
+ function tmpProject(): string {
23
+ const dir = join(tmpdir(), `gsd-mcp-test-${randomBytes(4).toString('hex')}`);
24
+ mkdirSync(dir, { recursive: true });
25
+ return dir;
26
+ }
27
+
28
+ function writeFixture(base: string, relPath: string, content: string): void {
29
+ const full = join(base, relPath);
30
+ mkdirSync(join(full, '..'), { recursive: true });
31
+ writeFileSync(full, content, 'utf-8');
32
+ }
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // readProgress tests
36
+ // ---------------------------------------------------------------------------
37
+
38
+ describe('readProgress', () => {
39
+ let projectDir: string;
40
+
41
+ before(() => {
42
+ projectDir = tmpProject();
43
+
44
+ writeFixture(projectDir, '.gsd/STATE.md', `# GSD State
45
+
46
+ **Active Milestone:** M002: Auth System
47
+ **Active Slice:** S01: Login flow
48
+ **Phase:** execution
49
+ **Requirements Status:** 5 active · 2 validated · 1 deferred · 0 out of scope
50
+
51
+ ## Milestone Registry
52
+
53
+ - ☑ **M001:** Core Setup
54
+ - 🔄 **M002:** Auth System
55
+ - ⬜ **M003:** Dashboard
56
+
57
+ ## Blockers
58
+
59
+ - Waiting on OAuth provider approval
60
+
61
+ ## Next Action
62
+
63
+ Execute T02 in S01 — implement token refresh.
64
+ `);
65
+
66
+ // Create filesystem structure
67
+ const m1 = '.gsd/milestones/M001/slices/S01/tasks';
68
+ writeFixture(projectDir, `${m1}/T01-PLAN.md`, '# T01');
69
+ writeFixture(projectDir, `${m1}/T01-SUMMARY.md`, '# T01 done');
70
+
71
+ const m2 = '.gsd/milestones/M002/slices/S01/tasks';
72
+ writeFixture(projectDir, `${m2}/T01-PLAN.md`, '# T01');
73
+ writeFixture(projectDir, `${m2}/T01-SUMMARY.md`, '# T01 done');
74
+ writeFixture(projectDir, `${m2}/T02-PLAN.md`, '# T02');
75
+
76
+ mkdirSync(join(projectDir, '.gsd/milestones/M003'), { recursive: true });
77
+ });
78
+
79
+ after(() => rmSync(projectDir, { recursive: true, force: true }));
80
+
81
+ it('parses active milestone from STATE.md', () => {
82
+ const result = readProgress(projectDir);
83
+ assert.deepEqual(result.activeMilestone, { id: 'M002', title: 'Auth System' });
84
+ });
85
+
86
+ it('parses active slice', () => {
87
+ const result = readProgress(projectDir);
88
+ assert.deepEqual(result.activeSlice, { id: 'S01', title: 'Login flow' });
89
+ });
90
+
91
+ it('parses phase', () => {
92
+ const result = readProgress(projectDir);
93
+ assert.equal(result.phase, 'execute');
94
+ });
95
+
96
+ it('parses milestone counts from registry', () => {
97
+ const result = readProgress(projectDir);
98
+ assert.equal(result.milestones.total, 3);
99
+ assert.equal(result.milestones.done, 1);
100
+ assert.equal(result.milestones.active, 1);
101
+ assert.equal(result.milestones.pending, 1);
102
+ });
103
+
104
+ it('counts tasks from filesystem', () => {
105
+ const result = readProgress(projectDir);
106
+ assert.equal(result.tasks.total, 3);
107
+ assert.equal(result.tasks.done, 2);
108
+ assert.equal(result.tasks.pending, 1);
109
+ });
110
+
111
+ it('parses blockers', () => {
112
+ const result = readProgress(projectDir);
113
+ assert.equal(result.blockers.length, 1);
114
+ assert.ok(result.blockers[0].includes('OAuth'));
115
+ });
116
+
117
+ it('parses requirements', () => {
118
+ const result = readProgress(projectDir);
119
+ assert.equal(result.requirements?.active, 5);
120
+ assert.equal(result.requirements?.validated, 2);
121
+ assert.equal(result.requirements?.deferred, 1);
122
+ });
123
+
124
+ it('parses next action', () => {
125
+ const result = readProgress(projectDir);
126
+ assert.ok(result.nextAction.includes('T02'));
127
+ });
128
+
129
+ it('returns defaults for missing .gsd/', () => {
130
+ const empty = tmpProject();
131
+ const result = readProgress(empty);
132
+ assert.equal(result.phase, 'unknown');
133
+ assert.equal(result.milestones.total, 0);
134
+ rmSync(empty, { recursive: true, force: true });
135
+ });
136
+ });
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // readRoadmap tests
140
+ // ---------------------------------------------------------------------------
141
+
142
+ describe('readRoadmap', () => {
143
+ let projectDir: string;
144
+
145
+ before(() => {
146
+ projectDir = tmpProject();
147
+
148
+ writeFixture(projectDir, '.gsd/milestones/M001/M001-CONTEXT.md', '# M001: Core Setup\n');
149
+ writeFixture(projectDir, '.gsd/milestones/M001/M001-ROADMAP.md', `# M001: Core Setup
150
+
151
+ ## Vision
152
+
153
+ Build the foundation for the project.
154
+
155
+ ## Slice Overview
156
+
157
+ | ID | Slice | Risk | Depends | Done | After this |
158
+ |----|-------|------|---------|------|------------|
159
+ | S01 | Database schema | low | — | ☑ | DB ready |
160
+ | S02 | API endpoints | medium | S01 | 🟫 | REST API live |
161
+ `);
162
+
163
+ writeFixture(projectDir, '.gsd/milestones/M001/slices/S01/S01-PLAN.md', `# S01: Database schema
164
+
165
+ ## Tasks
166
+
167
+ - [x] **T01: Create migrations** — Set up schema
168
+ - [x] **T02: Seed data** — Initial seed
169
+ `);
170
+ writeFixture(projectDir, '.gsd/milestones/M001/slices/S01/tasks/T01-PLAN.md', '# T01');
171
+ writeFixture(projectDir, '.gsd/milestones/M001/slices/S01/tasks/T01-SUMMARY.md', '# T01 done');
172
+ writeFixture(projectDir, '.gsd/milestones/M001/slices/S01/tasks/T02-PLAN.md', '# T02');
173
+ writeFixture(projectDir, '.gsd/milestones/M001/slices/S01/tasks/T02-SUMMARY.md', '# T02 done');
174
+
175
+ writeFixture(projectDir, '.gsd/milestones/M001/slices/S02/S02-PLAN.md', `# S02: API endpoints
176
+
177
+ ## Tasks
178
+
179
+ - [ ] **T01: Auth routes** — Implement auth
180
+ - [ ] **T02: User routes** — CRUD users
181
+ `);
182
+ writeFixture(projectDir, '.gsd/milestones/M001/slices/S02/tasks/T01-PLAN.md', '# T01');
183
+ writeFixture(projectDir, '.gsd/milestones/M001/slices/S02/tasks/T02-PLAN.md', '# T02');
184
+ });
185
+
186
+ after(() => rmSync(projectDir, { recursive: true, force: true }));
187
+
188
+ it('returns milestone structure', () => {
189
+ const result = readRoadmap(projectDir);
190
+ assert.equal(result.milestones.length, 1);
191
+ assert.equal(result.milestones[0].id, 'M001');
192
+ assert.equal(result.milestones[0].title, 'Core Setup');
193
+ });
194
+
195
+ it('reads vision from roadmap', () => {
196
+ const result = readRoadmap(projectDir);
197
+ assert.ok(result.milestones[0].vision.includes('foundation'));
198
+ });
199
+
200
+ it('parses slices from roadmap table', () => {
201
+ const result = readRoadmap(projectDir);
202
+ const slices = result.milestones[0].slices;
203
+ assert.equal(slices.length, 2);
204
+ assert.equal(slices[0].id, 'S01');
205
+ assert.equal(slices[0].title, 'Database schema');
206
+ assert.equal(slices[1].id, 'S02');
207
+ });
208
+
209
+ it('derives slice status from task summaries', () => {
210
+ const result = readRoadmap(projectDir);
211
+ const slices = result.milestones[0].slices;
212
+ assert.equal(slices[0].status, 'done');
213
+ assert.equal(slices[1].status, 'pending');
214
+ });
215
+
216
+ it('includes tasks in slices', () => {
217
+ const result = readRoadmap(projectDir);
218
+ const s01Tasks = result.milestones[0].slices[0].tasks;
219
+ assert.equal(s01Tasks.length, 2);
220
+ assert.equal(s01Tasks[0].status, 'done');
221
+ });
222
+
223
+ it('filters by milestoneId', () => {
224
+ const result = readRoadmap(projectDir, 'M999');
225
+ assert.equal(result.milestones.length, 0);
226
+ });
227
+ });
228
+
229
+ // ---------------------------------------------------------------------------
230
+ // readHistory tests
231
+ // ---------------------------------------------------------------------------
232
+
233
+ describe('readHistory', () => {
234
+ let projectDir: string;
235
+
236
+ before(() => {
237
+ projectDir = tmpProject();
238
+ writeFixture(projectDir, '.gsd/metrics.json', JSON.stringify({
239
+ version: 1,
240
+ projectStartedAt: 1700000000000,
241
+ units: [
242
+ {
243
+ type: 'execute-task',
244
+ id: 'M001/S01/T01',
245
+ model: 'claude-sonnet-4',
246
+ startedAt: 1700001000000,
247
+ finishedAt: 1700002000000,
248
+ tokens: { input: 10000, output: 3000, cacheRead: 2000, cacheWrite: 1000, total: 16000 },
249
+ cost: 0.05,
250
+ toolCalls: 8,
251
+ apiRequests: 3,
252
+ },
253
+ {
254
+ type: 'execute-task',
255
+ id: 'M001/S01/T02',
256
+ model: 'claude-sonnet-4',
257
+ startedAt: 1700003000000,
258
+ finishedAt: 1700004000000,
259
+ tokens: { input: 15000, output: 5000, cacheRead: 3000, cacheWrite: 1500, total: 24500 },
260
+ cost: 0.08,
261
+ toolCalls: 12,
262
+ apiRequests: 5,
263
+ },
264
+ ],
265
+ }));
266
+ });
267
+
268
+ after(() => rmSync(projectDir, { recursive: true, force: true }));
269
+
270
+ it('returns all entries sorted by most recent', () => {
271
+ const result = readHistory(projectDir);
272
+ assert.equal(result.entries.length, 2);
273
+ assert.equal(result.entries[0].id, 'M001/S01/T02'); // most recent first
274
+ });
275
+
276
+ it('computes totals', () => {
277
+ const result = readHistory(projectDir);
278
+ assert.equal(result.totals.units, 2);
279
+ assert.equal(result.totals.cost, 0.13);
280
+ assert.equal(result.totals.tokens.total, 40500);
281
+ });
282
+
283
+ it('respects limit', () => {
284
+ const result = readHistory(projectDir, 1);
285
+ assert.equal(result.entries.length, 1);
286
+ assert.equal(result.totals.units, 2); // totals still reflect all
287
+ });
288
+
289
+ it('returns empty for missing metrics', () => {
290
+ const empty = tmpProject();
291
+ mkdirSync(join(empty, '.gsd'), { recursive: true });
292
+ const result = readHistory(empty);
293
+ assert.equal(result.entries.length, 0);
294
+ assert.equal(result.totals.units, 0);
295
+ rmSync(empty, { recursive: true, force: true });
296
+ });
297
+ });
298
+
299
+ // ---------------------------------------------------------------------------
300
+ // readCaptures tests
301
+ // ---------------------------------------------------------------------------
302
+
303
+ describe('readCaptures', () => {
304
+ let projectDir: string;
305
+
306
+ before(() => {
307
+ projectDir = tmpProject();
308
+ writeFixture(projectDir, '.gsd/CAPTURES.md', `# Captures
309
+
310
+ ### CAP-aaa11111
311
+
312
+ **Text:** Add rate limiting to API
313
+ **Captured:** 2026-04-01T10:00:00Z
314
+ **Status:** pending
315
+
316
+ ### CAP-bbb22222
317
+
318
+ **Text:** Refactor auth module
319
+ **Captured:** 2026-04-02T10:00:00Z
320
+ **Status:** resolved
321
+ **Classification:** inject
322
+ **Resolution:** Added to M003 roadmap
323
+ **Rationale:** Important for security
324
+ **Resolved:** 2026-04-03T10:00:00Z
325
+ **Milestone:** M003
326
+
327
+ ### CAP-ccc33333
328
+
329
+ **Text:** Nice to have: dark mode
330
+ **Captured:** 2026-04-02T11:00:00Z
331
+ **Status:** resolved
332
+ **Classification:** defer
333
+ **Resolution:** Deferred to future
334
+ **Rationale:** Not blocking
335
+ **Resolved:** 2026-04-03T11:00:00Z
336
+ `);
337
+ });
338
+
339
+ after(() => rmSync(projectDir, { recursive: true, force: true }));
340
+
341
+ it('reads all captures', () => {
342
+ const result = readCaptures(projectDir, 'all');
343
+ assert.equal(result.captures.length, 3);
344
+ assert.equal(result.counts.total, 3);
345
+ });
346
+
347
+ it('filters pending captures', () => {
348
+ const result = readCaptures(projectDir, 'pending');
349
+ assert.equal(result.captures.length, 1);
350
+ assert.equal(result.captures[0].id, 'CAP-aaa11111');
351
+ });
352
+
353
+ it('filters actionable captures (inject, replan, quick-task)', () => {
354
+ const result = readCaptures(projectDir, 'actionable');
355
+ assert.equal(result.captures.length, 1);
356
+ assert.equal(result.captures[0].id, 'CAP-bbb22222');
357
+ });
358
+
359
+ it('counts correctly regardless of filter', () => {
360
+ const result = readCaptures(projectDir, 'pending');
361
+ assert.equal(result.counts.total, 3);
362
+ assert.equal(result.counts.pending, 1);
363
+ assert.equal(result.counts.actionable, 1);
364
+ });
365
+
366
+ it('returns empty for missing CAPTURES.md', () => {
367
+ const empty = tmpProject();
368
+ mkdirSync(join(empty, '.gsd'), { recursive: true });
369
+ const result = readCaptures(empty);
370
+ assert.equal(result.captures.length, 0);
371
+ rmSync(empty, { recursive: true, force: true });
372
+ });
373
+ });
374
+
375
+ // ---------------------------------------------------------------------------
376
+ // readKnowledge tests
377
+ // ---------------------------------------------------------------------------
378
+
379
+ describe('readKnowledge', () => {
380
+ let projectDir: string;
381
+
382
+ before(() => {
383
+ projectDir = tmpProject();
384
+ writeFixture(projectDir, '.gsd/KNOWLEDGE.md', `# Project Knowledge
385
+
386
+ ## Rules
387
+
388
+ | # | Scope | Rule | Why | Added |
389
+ |---|-------|------|-----|-------|
390
+ | K001 | auth | Hash passwords with bcrypt | Security requirement | manual |
391
+ | K002 | db | Use transactions for multi-table | Data consistency | auto |
392
+
393
+ ## Patterns
394
+
395
+ | # | Pattern | Where | Notes |
396
+ |---|---------|-------|-------|
397
+ | P001 | Singleton services | services/ | Prevents duplication |
398
+
399
+ ## Lessons Learned
400
+
401
+ | # | What Happened | Root Cause | Fix | Scope |
402
+ |---|--------------|------------|-----|-------|
403
+ | L001 | CI tests failed | Env diff | Added setup script | testing |
404
+ `);
405
+ });
406
+
407
+ after(() => rmSync(projectDir, { recursive: true, force: true }));
408
+
409
+ it('reads all knowledge entries', () => {
410
+ const result = readKnowledge(projectDir);
411
+ assert.equal(result.entries.length, 4);
412
+ });
413
+
414
+ it('counts by type', () => {
415
+ const result = readKnowledge(projectDir);
416
+ assert.equal(result.counts.rules, 2);
417
+ assert.equal(result.counts.patterns, 1);
418
+ assert.equal(result.counts.lessons, 1);
419
+ });
420
+
421
+ it('parses rule fields correctly', () => {
422
+ const result = readKnowledge(projectDir);
423
+ const k001 = result.entries.find((e) => e.id === 'K001');
424
+ assert.ok(k001);
425
+ assert.equal(k001.type, 'rule');
426
+ assert.equal(k001.scope, 'auth');
427
+ assert.ok(k001.content.includes('bcrypt'));
428
+ });
429
+
430
+ it('returns empty for missing KNOWLEDGE.md', () => {
431
+ const empty = tmpProject();
432
+ mkdirSync(join(empty, '.gsd'), { recursive: true });
433
+ const result = readKnowledge(empty);
434
+ assert.equal(result.entries.length, 0);
435
+ rmSync(empty, { recursive: true, force: true });
436
+ });
437
+ });
438
+
439
+ // ---------------------------------------------------------------------------
440
+ // runDoctorLite tests
441
+ // ---------------------------------------------------------------------------
442
+
443
+ describe('runDoctorLite', () => {
444
+ let projectDir: string;
445
+
446
+ before(() => {
447
+ projectDir = tmpProject();
448
+
449
+ // M001: complete milestone (has summary)
450
+ writeFixture(projectDir, '.gsd/PROJECT.md', '# Test Project');
451
+ writeFixture(projectDir, '.gsd/STATE.md', '# GSD State');
452
+ writeFixture(projectDir, '.gsd/milestones/M001/M001-CONTEXT.md', '# M001');
453
+ writeFixture(projectDir, '.gsd/milestones/M001/M001-ROADMAP.md', '# Roadmap');
454
+ writeFixture(projectDir, '.gsd/milestones/M001/M001-SUMMARY.md', '# Done');
455
+ writeFixture(projectDir, '.gsd/milestones/M001/slices/S01/S01-PLAN.md', '# Plan');
456
+ writeFixture(projectDir, '.gsd/milestones/M001/slices/S01/tasks/T01-PLAN.md', '# T01');
457
+ writeFixture(projectDir, '.gsd/milestones/M001/slices/S01/tasks/T01-SUMMARY.md', '# T01 done');
458
+
459
+ // M002: incomplete — has all tasks done but no SUMMARY
460
+ writeFixture(projectDir, '.gsd/milestones/M002/M002-CONTEXT.md', '# M002');
461
+ writeFixture(projectDir, '.gsd/milestones/M002/M002-ROADMAP.md', '# Roadmap');
462
+ writeFixture(projectDir, '.gsd/milestones/M002/slices/S01/S01-PLAN.md', '# Plan');
463
+ writeFixture(projectDir, '.gsd/milestones/M002/slices/S01/tasks/T01-PLAN.md', '# T01');
464
+ writeFixture(projectDir, '.gsd/milestones/M002/slices/S01/tasks/T01-SUMMARY.md', '# T01 done');
465
+
466
+ // M003: empty — no context, no slices
467
+ mkdirSync(join(projectDir, '.gsd/milestones/M003'), { recursive: true });
468
+ });
469
+
470
+ after(() => rmSync(projectDir, { recursive: true, force: true }));
471
+
472
+ it('detects all-slices-done-missing-summary', () => {
473
+ const result = runDoctorLite(projectDir);
474
+ const issue = result.issues.find((i) => i.code === 'all_slices_done_missing_summary');
475
+ assert.ok(issue, 'Should detect M002 missing summary');
476
+ assert.equal(issue.unitId, 'M002');
477
+ });
478
+
479
+ it('detects missing context', () => {
480
+ const result = runDoctorLite(projectDir);
481
+ const issue = result.issues.find(
482
+ (i) => i.code === 'missing_context' && i.unitId === 'M003',
483
+ );
484
+ assert.ok(issue, 'Should detect M003 missing context');
485
+ });
486
+
487
+ it('scopes to a single milestone', () => {
488
+ const result = runDoctorLite(projectDir, 'M001');
489
+ const m002Issues = result.issues.filter((i) => i.unitId.startsWith('M002'));
490
+ assert.equal(m002Issues.length, 0, 'Should not include M002 when scoped to M001');
491
+ });
492
+
493
+ it('returns ok:true for healthy project', () => {
494
+ const healthy = tmpProject();
495
+ writeFixture(healthy, '.gsd/PROJECT.md', '# Project');
496
+ writeFixture(healthy, '.gsd/STATE.md', '# State');
497
+ const result = runDoctorLite(healthy);
498
+ assert.equal(result.ok, true);
499
+ rmSync(healthy, { recursive: true, force: true });
500
+ });
501
+
502
+ it('handles missing .gsd/ gracefully', () => {
503
+ const empty = tmpProject();
504
+ const result = runDoctorLite(empty);
505
+ assert.equal(result.ok, true);
506
+ assert.equal(result.issues[0].code, 'no_gsd_directory');
507
+ rmSync(empty, { recursive: true, force: true });
508
+ });
509
+ });