gsd-pi 2.67.0-dev.2142d3e → 2.67.0-dev.2367d7e

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 (233) hide show
  1. package/README.md +1 -1
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +152 -70
  3. package/dist/resources/extensions/gsd/auto/session.js +10 -0
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
  5. package/dist/resources/extensions/gsd/auto-start.js +16 -30
  6. package/dist/resources/extensions/gsd/auto-worktree.js +62 -15
  7. package/dist/resources/extensions/gsd/auto.js +121 -59
  8. package/dist/resources/extensions/gsd/bootstrap/system-context.js +7 -2
  9. package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
  10. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
  11. package/dist/resources/extensions/gsd/commands-mcp-status.js +43 -7
  12. package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -4
  13. package/dist/resources/extensions/gsd/doctor-proactive.js +3 -3
  14. package/dist/resources/extensions/gsd/doctor.js +8 -4
  15. package/dist/resources/extensions/gsd/gsd-db.js +11 -0
  16. package/dist/resources/extensions/gsd/guided-flow.js +40 -31
  17. package/dist/resources/extensions/gsd/init-wizard.js +15 -12
  18. package/dist/resources/extensions/gsd/interrupted-session.js +146 -0
  19. package/dist/resources/extensions/gsd/mcp-project-config.js +83 -0
  20. package/dist/resources/extensions/gsd/state.js +7 -2
  21. package/dist/resources/extensions/gsd/workflow-mcp.js +90 -19
  22. package/dist/web/standalone/.next/BUILD_ID +1 -1
  23. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  24. package/dist/web/standalone/.next/build-manifest.json +3 -3
  25. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  26. package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
  27. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/index.html +1 -1
  46. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  47. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  48. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  49. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  54. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  57. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  58. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  59. package/dist/web/standalone/.next/static/chunks/2826.821e01b07d92e948.js +9 -0
  60. package/dist/web/standalone/.next/static/chunks/app/{page-0c485498795110d6.js → page-f1e30ab6bb269149.js} +1 -1
  61. package/dist/web/standalone/.next/static/chunks/{webpack-b49b09f97429b5d0.js → webpack-6e4d7e9a4f57bed4.js} +1 -1
  62. package/package.json +4 -2
  63. package/packages/mcp-server/dist/cli.d.ts +9 -0
  64. package/packages/mcp-server/dist/cli.d.ts.map +1 -0
  65. package/packages/mcp-server/dist/cli.js +58 -0
  66. package/packages/mcp-server/dist/cli.js.map +1 -0
  67. package/packages/mcp-server/dist/index.d.ts +20 -0
  68. package/packages/mcp-server/dist/index.d.ts.map +1 -0
  69. package/packages/mcp-server/dist/index.js +14 -0
  70. package/packages/mcp-server/dist/index.js.map +1 -0
  71. package/packages/mcp-server/dist/readers/captures.d.ts +25 -0
  72. package/packages/mcp-server/dist/readers/captures.d.ts.map +1 -0
  73. package/packages/mcp-server/dist/readers/captures.js +67 -0
  74. package/packages/mcp-server/dist/readers/captures.js.map +1 -0
  75. package/packages/mcp-server/dist/readers/doctor-lite.d.ts +20 -0
  76. package/packages/mcp-server/dist/readers/doctor-lite.d.ts.map +1 -0
  77. package/packages/mcp-server/dist/readers/doctor-lite.js +173 -0
  78. package/packages/mcp-server/dist/readers/doctor-lite.js.map +1 -0
  79. package/packages/mcp-server/dist/readers/index.d.ts +14 -0
  80. package/packages/mcp-server/dist/readers/index.d.ts.map +1 -0
  81. package/packages/mcp-server/dist/readers/index.js +10 -0
  82. package/packages/mcp-server/dist/readers/index.js.map +1 -0
  83. package/packages/mcp-server/dist/readers/knowledge.d.ts +18 -0
  84. package/packages/mcp-server/dist/readers/knowledge.d.ts.map +1 -0
  85. package/packages/mcp-server/dist/readers/knowledge.js +82 -0
  86. package/packages/mcp-server/dist/readers/knowledge.js.map +1 -0
  87. package/packages/mcp-server/dist/readers/metrics.d.ts +32 -0
  88. package/packages/mcp-server/dist/readers/metrics.d.ts.map +1 -0
  89. package/packages/mcp-server/dist/readers/metrics.js +74 -0
  90. package/packages/mcp-server/dist/readers/metrics.js.map +1 -0
  91. package/packages/mcp-server/dist/readers/paths.d.ts +42 -0
  92. package/packages/mcp-server/dist/readers/paths.d.ts.map +1 -0
  93. package/packages/mcp-server/dist/readers/paths.js +199 -0
  94. package/packages/mcp-server/dist/readers/paths.js.map +1 -0
  95. package/packages/mcp-server/dist/readers/roadmap.d.ts +26 -0
  96. package/packages/mcp-server/dist/readers/roadmap.d.ts.map +1 -0
  97. package/packages/mcp-server/dist/readers/roadmap.js +194 -0
  98. package/packages/mcp-server/dist/readers/roadmap.js.map +1 -0
  99. package/packages/mcp-server/dist/readers/state.d.ts +43 -0
  100. package/packages/mcp-server/dist/readers/state.d.ts.map +1 -0
  101. package/packages/mcp-server/dist/readers/state.js +184 -0
  102. package/packages/mcp-server/dist/readers/state.js.map +1 -0
  103. package/packages/mcp-server/dist/server.d.ts +28 -0
  104. package/packages/mcp-server/dist/server.d.ts.map +1 -0
  105. package/packages/mcp-server/dist/server.js +319 -0
  106. package/packages/mcp-server/dist/server.js.map +1 -0
  107. package/packages/mcp-server/dist/session-manager.d.ts +54 -0
  108. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -0
  109. package/packages/mcp-server/dist/session-manager.js +284 -0
  110. package/packages/mcp-server/dist/session-manager.js.map +1 -0
  111. package/packages/mcp-server/dist/types.d.ts +61 -0
  112. package/packages/mcp-server/dist/types.d.ts.map +1 -0
  113. package/packages/mcp-server/dist/types.js +11 -0
  114. package/packages/mcp-server/dist/types.js.map +1 -0
  115. package/packages/mcp-server/dist/workflow-tools.d.ts +9 -0
  116. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -0
  117. package/packages/mcp-server/dist/workflow-tools.js +532 -0
  118. package/packages/mcp-server/dist/workflow-tools.js.map +1 -0
  119. package/packages/mcp-server/src/workflow-tools.ts +13 -2
  120. package/packages/mcp-server/tsconfig.json +1 -1
  121. package/packages/pi-agent-core/dist/agent-loop.js +14 -6
  122. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  123. package/packages/pi-agent-core/src/agent-loop.test.ts +53 -0
  124. package/packages/pi-agent-core/src/agent-loop.ts +20 -6
  125. package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts +43 -0
  126. package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts.map +1 -0
  127. package/packages/pi-coding-agent/dist/core/contextual-tips.js +208 -0
  128. package/packages/pi-coding-agent/dist/core/contextual-tips.js.map +1 -0
  129. package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts +2 -0
  130. package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/core/contextual-tips.test.js +227 -0
  132. package/packages/pi-coding-agent/dist/core/contextual-tips.test.js.map +1 -0
  133. package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
  134. package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/index.js +1 -0
  136. package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts +2 -0
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts.map +1 -0
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +28 -0
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -0
  141. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  142. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -12
  144. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  145. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  146. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +19 -0
  147. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +4 -0
  149. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +14 -0
  151. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
  153. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  154. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -12
  155. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  156. package/packages/pi-coding-agent/src/core/contextual-tips.test.ts +259 -0
  157. package/packages/pi-coding-agent/src/core/contextual-tips.ts +232 -0
  158. package/packages/pi-coding-agent/src/core/index.ts +2 -0
  159. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +54 -0
  160. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -12
  161. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +21 -0
  162. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +19 -0
  163. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +19 -15
  164. package/packages/rpc-client/dist/index.d.ts +10 -0
  165. package/packages/rpc-client/dist/index.d.ts.map +1 -0
  166. package/packages/rpc-client/dist/index.js +9 -0
  167. package/packages/rpc-client/dist/index.js.map +1 -0
  168. package/packages/rpc-client/dist/jsonl.d.ts +17 -0
  169. package/packages/rpc-client/dist/jsonl.d.ts.map +1 -0
  170. package/packages/rpc-client/dist/jsonl.js +54 -0
  171. package/packages/rpc-client/dist/jsonl.js.map +1 -0
  172. package/packages/rpc-client/dist/rpc-client.d.ts +259 -0
  173. package/packages/rpc-client/dist/rpc-client.d.ts.map +1 -0
  174. package/packages/rpc-client/dist/rpc-client.js +541 -0
  175. package/packages/rpc-client/dist/rpc-client.js.map +1 -0
  176. package/packages/rpc-client/dist/rpc-client.test.d.ts +2 -0
  177. package/packages/rpc-client/dist/rpc-client.test.d.ts.map +1 -0
  178. package/packages/rpc-client/dist/rpc-client.test.js +477 -0
  179. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -0
  180. package/packages/rpc-client/dist/rpc-types.d.ts +566 -0
  181. package/packages/rpc-client/dist/rpc-types.d.ts.map +1 -0
  182. package/packages/rpc-client/dist/rpc-types.js +12 -0
  183. package/packages/rpc-client/dist/rpc-types.js.map +1 -0
  184. package/scripts/ensure-workspace-builds.cjs +2 -0
  185. package/scripts/link-workspace-packages.cjs +21 -14
  186. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +190 -93
  187. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +89 -116
  188. package/src/resources/extensions/gsd/auto/session.ts +10 -0
  189. package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
  190. package/src/resources/extensions/gsd/auto-start.ts +23 -55
  191. package/src/resources/extensions/gsd/auto-worktree.ts +59 -15
  192. package/src/resources/extensions/gsd/auto.ts +133 -64
  193. package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -2
  194. package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
  195. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
  196. package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
  197. package/src/resources/extensions/gsd/doctor-git-checks.ts +4 -4
  198. package/src/resources/extensions/gsd/doctor-proactive.ts +3 -3
  199. package/src/resources/extensions/gsd/doctor.ts +9 -5
  200. package/src/resources/extensions/gsd/gsd-db.ts +12 -0
  201. package/src/resources/extensions/gsd/guided-flow.ts +42 -36
  202. package/src/resources/extensions/gsd/init-wizard.ts +17 -11
  203. package/src/resources/extensions/gsd/interrupted-session.ts +224 -0
  204. package/src/resources/extensions/gsd/mcp-project-config.ts +128 -0
  205. package/src/resources/extensions/gsd/state.ts +7 -1
  206. package/src/resources/extensions/gsd/tests/auto-project-root-env.test.ts +29 -0
  207. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +668 -2
  208. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +14 -4
  209. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +21 -0
  210. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +380 -2
  211. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +30 -0
  212. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +12 -0
  213. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +2 -2
  214. package/src/resources/extensions/gsd/tests/integration/doctor-fixlevel.test.ts +52 -1
  215. package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +2 -9
  216. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +0 -33
  217. package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +169 -0
  218. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +146 -0
  219. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +136 -0
  220. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +85 -0
  221. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +15 -0
  222. package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +11 -0
  223. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +212 -13
  224. package/src/resources/extensions/gsd/workflow-mcp.ts +106 -19
  225. package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
  226. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts +0 -13
  227. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts.map +0 -1
  228. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js +0 -27
  229. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js.map +0 -1
  230. package/packages/pi-coding-agent/src/modes/interactive/provider-auth-setup.ts +0 -40
  231. package/src/resources/extensions/gsd/tests/init-bootstrap-completeness.test.ts +0 -121
  232. /package/dist/web/standalone/.next/static/{xR6qurkuYSvyjBjRyJLxG → WMDT_0C0XDkBKtsAI_AX4}/_buildManifest.js +0 -0
  233. /package/dist/web/standalone/.next/static/{xR6qurkuYSvyjBjRyJLxG → WMDT_0C0XDkBKtsAI_AX4}/_ssgManifest.js +0 -0
@@ -18,22 +18,33 @@ const { assertTrue, report } = createTestContext();
18
18
 
19
19
  const autoSrc = readFileSync(join(import.meta.dirname, "..", "auto.ts"), "utf-8");
20
20
 
21
- console.log("\n=== #2940: resume path opens DB before rebuildState/deriveState ===");
21
+ console.log("\n=== resume path refreshes resources and opens DB before rebuildState/deriveState ===");
22
22
 
23
23
  // The resume block is the `if (s.paused) { ... }` section that calls rebuildState/deriveState.
24
24
  // Locate the resume section by finding `s.paused = false;` followed by `rebuildState`.
25
25
  const resumeSectionStart = autoSrc.indexOf("if (s.paused) {", autoSrc.indexOf("// If resuming from paused state"));
26
26
  assertTrue(resumeSectionStart > 0, "auto.ts has the paused-session resume block");
27
27
 
28
- const resumeSection = autoSrc.slice(resumeSectionStart, resumeSectionStart + 3000);
28
+ const resumeSectionEnd = autoSrc.indexOf("await autoLoop(", resumeSectionStart);
29
+ assertTrue(resumeSectionEnd > resumeSectionStart, "resume block reaches autoLoop");
29
30
 
30
- // The resume path must open the DB before rebuildState/deriveState
31
+ const resumeSection = autoSrc.slice(resumeSectionStart, resumeSectionEnd);
32
+
33
+ // The resume path must refresh managed resources and open the DB before
34
+ // rebuildState/deriveState so resumed auto-mode uses current extension code.
31
35
  const rebuildIdx = resumeSection.indexOf("rebuildState(");
32
36
  assertTrue(rebuildIdx > 0, "resume block calls rebuildState");
33
37
 
34
38
  const deriveIdx = resumeSection.indexOf("deriveState(");
35
39
  assertTrue(deriveIdx > 0, "resume block calls deriveState");
36
40
 
41
+ const preDeriveSection = resumeSection.slice(0, rebuildIdx);
42
+
43
+ assertTrue(
44
+ preDeriveSection.includes("initResources("),
45
+ "resume path must refresh managed resources before rebuildState/deriveState (#3761)",
46
+ );
47
+
37
48
  // There must be a DB open call before the first rebuildState call
38
49
  const dbOpenPatterns = [
39
50
  "openProjectDbIfPresent(",
@@ -41,7 +52,6 @@ const dbOpenPatterns = [
41
52
  "ensureDbOpen(",
42
53
  ];
43
54
 
44
- const preDeriveSection = resumeSection.slice(0, rebuildIdx);
45
55
  const hasDbOpen = dbOpenPatterns.some(pat => preDeriveSection.includes(pat));
46
56
  assertTrue(
47
57
  hasDbOpen,
@@ -0,0 +1,21 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+
6
+ test("copyPlanningArtifacts skips when source and destination .gsd resolve to the same path", () => {
7
+ const srcPath = join(import.meta.dirname, "..", "auto-worktree.ts");
8
+ const src = readFileSync(srcPath, "utf-8");
9
+
10
+ const fnIdx = src.indexOf("function copyPlanningArtifacts");
11
+ assert.ok(fnIdx !== -1, "copyPlanningArtifacts function exists");
12
+
13
+ const fnBody = src.slice(fnIdx, fnIdx + 2400);
14
+
15
+ const guardIdx = fnBody.indexOf("if (isSamePath(srcGsd, dstGsd)) return;");
16
+ const copyIdx = fnBody.indexOf("safeCopyRecursive(join(srcGsd, \"milestones\")");
17
+
18
+ assert.ok(guardIdx !== -1, "copyPlanningArtifacts should guard same-path .gsd copies");
19
+ assert.ok(copyIdx !== -1, "copyPlanningArtifacts should still copy milestones when paths differ");
20
+ assert.ok(guardIdx < copyIdx, "same-path guard should run before any copy attempt");
21
+ });
@@ -1,6 +1,6 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { mkdirSync, existsSync, readFileSync, rmSync } from "node:fs";
3
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import { randomUUID } from "node:crypto";
@@ -13,6 +13,14 @@ import {
13
13
  formatCrashInfo,
14
14
  type LockData,
15
15
  } from "../crash-recovery.ts";
16
+ import {
17
+ assessInterruptedSession,
18
+ hasResumableDerivedState,
19
+ isBootstrapCrashLock,
20
+ readPausedSessionMetadata,
21
+ } from "../interrupted-session.ts";
22
+ import { gsdRoot } from "../paths.ts";
23
+ import type { GSDState } from "../types.ts";
16
24
 
17
25
  function makeTmpBase(): string {
18
26
  const base = join(tmpdir(), `gsd-test-${randomUUID()}`);
@@ -24,6 +32,376 @@ function cleanup(base: string): void {
24
32
  try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
25
33
  }
26
34
 
35
+ function writeTestLock(
36
+ base: string,
37
+ unitType: string,
38
+ unitId: string,
39
+ sessionFile?: string,
40
+ ): void {
41
+ writeFileSync(
42
+ join(gsdRoot(base), "auto.lock"),
43
+ JSON.stringify({
44
+ pid: 999999999,
45
+ startedAt: new Date().toISOString(),
46
+ unitType,
47
+ unitId,
48
+ unitStartedAt: new Date().toISOString(),
49
+ sessionFile,
50
+ }, null, 2),
51
+ "utf-8",
52
+ );
53
+ }
54
+
55
+ function writeRoadmap(base: string, checked = false): void {
56
+ const milestoneDir = join(base, ".gsd", "milestones", "M001");
57
+ mkdirSync(join(milestoneDir, "slices", "S01", "tasks"), { recursive: true });
58
+ writeFileSync(
59
+ join(milestoneDir, "M001-ROADMAP.md"),
60
+ [
61
+ "# M001: Test Milestone",
62
+ "",
63
+ "## Vision",
64
+ "",
65
+ "Test milestone.",
66
+ "",
67
+ "## Success Criteria",
68
+ "",
69
+ "- It works.",
70
+ "",
71
+ "## Slices",
72
+ "",
73
+ `- [${checked ? "x" : " "}] **S01: Test slice** \`risk:low\``,
74
+ " After this: Demo",
75
+ "",
76
+ "## Boundary Map",
77
+ "",
78
+ "- S01 → terminal",
79
+ " - Produces: done",
80
+ " - Consumes: nothing",
81
+ ].join("\n"),
82
+ "utf-8",
83
+ );
84
+ }
85
+
86
+ function writeCompleteSliceArtifacts(base: string): void {
87
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
88
+ mkdirSync(sliceDir, { recursive: true });
89
+ writeFileSync(join(sliceDir, "S01-SUMMARY.md"), "# Summary\nDone.\n", "utf-8");
90
+ writeFileSync(join(sliceDir, "S01-UAT.md"), "# UAT\nPassed.\n", "utf-8");
91
+ }
92
+
93
+ function writeCompleteMilestoneSummary(base: string): void {
94
+ const milestoneDir = join(base, ".gsd", "milestones", "M001");
95
+ mkdirSync(milestoneDir, { recursive: true });
96
+ writeFileSync(join(milestoneDir, "M001-SUMMARY.md"), "# Milestone Summary\nDone.\n", "utf-8");
97
+ }
98
+
99
+ function writePausedSession(
100
+ base: string,
101
+ milestoneId = "M001",
102
+ stepMode = false,
103
+ worktreePath?: string,
104
+ unitType?: string,
105
+ unitId?: string,
106
+ ): void {
107
+ const runtimeDir = join(base, ".gsd", "runtime");
108
+ mkdirSync(runtimeDir, { recursive: true });
109
+ writeFileSync(
110
+ join(runtimeDir, "paused-session.json"),
111
+ JSON.stringify({ milestoneId, originalBasePath: base, stepMode, worktreePath, unitType, unitId }, null, 2),
112
+ "utf-8",
113
+ );
114
+ }
115
+
116
+ function writeActivityLog(base: string, entries: Record<string, unknown>[]): void {
117
+ const activityDir = join(base, ".gsd", "activity");
118
+ mkdirSync(activityDir, { recursive: true });
119
+ writeFileSync(
120
+ join(activityDir, "001-execute-task-M001-S01-T01.jsonl"),
121
+ entries.map((entry) => JSON.stringify(entry)).join("\n") + "\n",
122
+ "utf-8",
123
+ );
124
+ }
125
+
126
+ function makeState(phase: GSDState["phase"], activeMilestone = true): GSDState {
127
+ return {
128
+ activeMilestone: activeMilestone ? { id: "M001", title: "Test" } : null,
129
+ activeSlice: null,
130
+ activeTask: null,
131
+ phase,
132
+ recentDecisions: [],
133
+ blockers: [],
134
+ nextAction: "",
135
+ registry: [],
136
+ };
137
+ }
138
+
139
+ // ─── interrupted-session helpers ───────────────────────────────────────────
140
+
141
+ test("hasResumableDerivedState treats only unfinished active work as resumable", () => {
142
+ assert.equal(hasResumableDerivedState(makeState("executing")), true);
143
+ assert.equal(hasResumableDerivedState(makeState("complete")), false);
144
+ assert.equal(hasResumableDerivedState(makeState("pre-planning", false)), false);
145
+ });
146
+
147
+ test("isBootstrapCrashLock detects starting/bootstrap special case", () => {
148
+ const bootstrap: LockData = {
149
+ pid: 999999999,
150
+ startedAt: new Date().toISOString(),
151
+ unitType: "starting",
152
+ unitId: "bootstrap",
153
+ unitStartedAt: new Date().toISOString(),
154
+ };
155
+ assert.equal(isBootstrapCrashLock(bootstrap), true);
156
+ assert.equal(isBootstrapCrashLock({ ...bootstrap, unitType: "execute-task" }), false);
157
+ });
158
+
159
+ test("readPausedSessionMetadata reads paused-session metadata when present", () => {
160
+ const base = makeTmpBase();
161
+ try {
162
+ writePausedSession(base, "M009");
163
+ const meta = readPausedSessionMetadata(base);
164
+ assert.equal(meta?.milestoneId, "M009");
165
+ } finally {
166
+ cleanup(base);
167
+ }
168
+ });
169
+
170
+ test("readPausedSessionMetadata preserves unitType and unitId through round-trip", () => {
171
+ const base = makeTmpBase();
172
+ try {
173
+ writePausedSession(base, "M001", false, undefined, "execute-task", "M001/S01/T02");
174
+ const meta = readPausedSessionMetadata(base);
175
+ assert.equal(meta?.unitType, "execute-task");
176
+ assert.equal(meta?.unitId, "M001/S01/T02");
177
+ } finally {
178
+ cleanup(base);
179
+ }
180
+ });
181
+
182
+ test("readPausedSessionMetadata handles legacy metadata without unitType/unitId", () => {
183
+ const base = makeTmpBase();
184
+ try {
185
+ // Write metadata without unitType/unitId (simulates older version)
186
+ const runtimeDir = join(base, ".gsd", "runtime");
187
+ mkdirSync(runtimeDir, { recursive: true });
188
+ writeFileSync(
189
+ join(runtimeDir, "paused-session.json"),
190
+ JSON.stringify({ milestoneId: "M001", originalBasePath: base }),
191
+ "utf-8",
192
+ );
193
+ const meta = readPausedSessionMetadata(base);
194
+ assert.equal(meta?.milestoneId, "M001");
195
+ assert.equal(meta?.unitType, undefined);
196
+ assert.equal(meta?.unitId, undefined);
197
+ } finally {
198
+ cleanup(base);
199
+ }
200
+ });
201
+
202
+ test("assessInterruptedSession returns none when no lock and no paused session exist", async () => {
203
+ const base = makeTmpBase();
204
+ try {
205
+ const assessment = await assessInterruptedSession(base);
206
+ assert.equal(assessment.classification, "none");
207
+ assert.equal(assessment.lock, null);
208
+ assert.equal(assessment.pausedSession, null);
209
+ assert.equal(assessment.state, null);
210
+ assert.equal(assessment.recovery, null);
211
+ assert.equal(assessment.recoveryPrompt, null);
212
+ assert.equal(assessment.recoveryToolCallCount, 0);
213
+ assert.equal(assessment.artifactSatisfied, false);
214
+ assert.equal(assessment.hasResumableDiskState, false);
215
+ assert.equal(assessment.isBootstrapCrash, false);
216
+ } finally {
217
+ cleanup(base);
218
+ }
219
+ });
220
+
221
+ test("assessInterruptedSession classifies stale complete repo as stale and suppresses recovery", async () => {
222
+ const base = makeTmpBase();
223
+ try {
224
+ writeRoadmap(base, true);
225
+ writeCompleteSliceArtifacts(base);
226
+ writeCompleteMilestoneSummary(base);
227
+ writeTestLock(base, "execute-task", "M001/S01/T01");
228
+
229
+ const assessment = await assessInterruptedSession(base);
230
+ assert.equal(assessment.classification, "stale");
231
+ assert.equal(assessment.hasResumableDiskState, false);
232
+ assert.equal(assessment.recoveryPrompt, null);
233
+ } finally {
234
+ cleanup(base);
235
+ }
236
+ });
237
+
238
+ test("assessInterruptedSession suppresses prompt when expected artifact already exists and no resumable state remains", async () => {
239
+ const base = makeTmpBase();
240
+ try {
241
+ writeRoadmap(base, true);
242
+ writeCompleteSliceArtifacts(base);
243
+ writeCompleteMilestoneSummary(base);
244
+ writeTestLock(base, "complete-slice", "M001/S01");
245
+
246
+ const assessment = await assessInterruptedSession(base);
247
+ assert.equal(assessment.classification, "stale");
248
+ assert.equal(assessment.artifactSatisfied, true);
249
+ } finally {
250
+ cleanup(base);
251
+ }
252
+ });
253
+
254
+ test("assessInterruptedSession keeps paused-session resume recoverable when disk state is unfinished", async () => {
255
+ const base = makeTmpBase();
256
+ try {
257
+ writeRoadmap(base, false);
258
+ writePausedSession(base);
259
+ writeTestLock(base, "execute-task", "M001/S01/T01");
260
+
261
+ const assessment = await assessInterruptedSession(base);
262
+ assert.equal(assessment.classification, "recoverable");
263
+ assert.equal(assessment.pausedSession?.milestoneId, "M001");
264
+ } finally {
265
+ cleanup(base);
266
+ }
267
+ });
268
+
269
+ test("assessInterruptedSession marks stale paused-session metadata as stale when no work remains", async () => {
270
+ const base = makeTmpBase();
271
+ try {
272
+ writeRoadmap(base, true);
273
+ writeCompleteSliceArtifacts(base);
274
+ writeCompleteMilestoneSummary(base);
275
+ writePausedSession(base, "M999");
276
+
277
+ const assessment = await assessInterruptedSession(base);
278
+ assert.equal(assessment.classification, "stale");
279
+ assert.equal(assessment.hasResumableDiskState, false);
280
+ } finally {
281
+ cleanup(base);
282
+ }
283
+ });
284
+
285
+ test("assessInterruptedSession classifies paused session without lock as recoverable when disk state is resumable", async () => {
286
+ const base = makeTmpBase();
287
+ try {
288
+ writeRoadmap(base, false);
289
+ writePausedSession(base, "M001", true);
290
+
291
+ const assessment = await assessInterruptedSession(base);
292
+ assert.equal(assessment.classification, "recoverable");
293
+ assert.equal(assessment.lock, null);
294
+ assert.equal(assessment.pausedSession?.milestoneId, "M001");
295
+ assert.equal(assessment.hasResumableDiskState, true);
296
+ assert.equal(assessment.isBootstrapCrash, false);
297
+ } finally {
298
+ cleanup(base);
299
+ }
300
+ });
301
+
302
+ test("assessInterruptedSession falls back to basePath when worktreePath no longer exists", async () => {
303
+ const base = makeTmpBase();
304
+ try {
305
+ writeRoadmap(base, false);
306
+ // Reference a worktree that doesn't exist on disk
307
+ writePausedSession(base, "M001", false, "/nonexistent/worktree");
308
+
309
+ const assessment = await assessInterruptedSession(base);
310
+ // Should use basePath (which has an unfinished roadmap) instead of the missing worktree
311
+ assert.equal(assessment.classification, "recoverable");
312
+ assert.equal(assessment.hasResumableDiskState, true);
313
+ } finally {
314
+ cleanup(base);
315
+ }
316
+ });
317
+
318
+ test("assessInterruptedSession prefers paused worktree state when worktreePath is recorded", async () => {
319
+ const base = makeTmpBase();
320
+ const worktree = join(base, "worktree-copy");
321
+ try {
322
+ writeRoadmap(base, false);
323
+ writeRoadmap(worktree, true);
324
+ writeCompleteSliceArtifacts(worktree);
325
+ writeCompleteMilestoneSummary(worktree);
326
+ writePausedSession(base, "M001", false, worktree);
327
+
328
+ const assessment = await assessInterruptedSession(base);
329
+ assert.equal(assessment.classification, "stale");
330
+ assert.equal(assessment.hasResumableDiskState, false);
331
+ } finally {
332
+ cleanup(base);
333
+ }
334
+ });
335
+
336
+ test("assessInterruptedSession keeps unfinished derived state recoverable without trace", async () => {
337
+ const base = makeTmpBase();
338
+ try {
339
+ writeRoadmap(base, false);
340
+ writeTestLock(base, "plan-slice", "M001/S01");
341
+
342
+ const assessment = await assessInterruptedSession(base);
343
+ assert.equal(assessment.classification, "recoverable");
344
+ assert.equal(assessment.hasResumableDiskState, true);
345
+ assert.equal(assessment.recoveryPrompt, null);
346
+ } finally {
347
+ cleanup(base);
348
+ }
349
+ });
350
+
351
+ test("assessInterruptedSession preserves crash trace when activity log has tool calls", async () => {
352
+ const base = makeTmpBase();
353
+ try {
354
+ writeRoadmap(base, false);
355
+ writeTestLock(base, "execute-task", "M001/S01/T01");
356
+ writeActivityLog(base, [
357
+ {
358
+ type: "message",
359
+ message: {
360
+ role: "assistant",
361
+ content: [
362
+ {
363
+ type: "toolCall",
364
+ id: "1",
365
+ name: "bash",
366
+ arguments: { command: "npm test" },
367
+ },
368
+ ],
369
+ },
370
+ },
371
+ {
372
+ type: "message",
373
+ message: {
374
+ role: "toolResult",
375
+ toolCallId: "1",
376
+ toolName: "bash",
377
+ isError: false,
378
+ content: [{ type: "text", text: "ok" }],
379
+ },
380
+ },
381
+ ]);
382
+
383
+ const assessment = await assessInterruptedSession(base);
384
+ assert.equal(assessment.classification, "recoverable");
385
+ assert.ok(assessment.recoveryToolCallCount > 0);
386
+ assert.ok(assessment.recoveryPrompt?.includes("Recovery Briefing"));
387
+ } finally {
388
+ cleanup(base);
389
+ }
390
+ });
391
+
392
+ test("assessInterruptedSession treats bootstrap crash as stale without paused metadata", async () => {
393
+ const base = makeTmpBase();
394
+ try {
395
+ writeTestLock(base, "starting", "bootstrap");
396
+
397
+ const assessment = await assessInterruptedSession(base);
398
+ assert.equal(assessment.classification, "stale");
399
+ assert.equal(assessment.isBootstrapCrash, true);
400
+ } finally {
401
+ cleanup(base);
402
+ }
403
+ });
404
+
27
405
  // ─── writeLock / readCrashLock ────────────────────────────────────────────
28
406
 
29
407
  test("writeLock creates lock file and readCrashLock reads it", (t) => {
@@ -84,7 +462,7 @@ test("#2470: isLockProcessAlive returns true for own PID (we hold the lock)", ()
84
462
 
85
463
  test("isLockProcessAlive returns false for dead PID", () => {
86
464
  const lock: LockData = {
87
- pid: 999999999, // almost certainly not running
465
+ pid: 999999999,
88
466
  startedAt: new Date().toISOString(),
89
467
  unitType: "execute-task",
90
468
  unitId: "M001/S01/T01",
@@ -126,4 +126,34 @@ describe("forensics context persistence (#2941)", () => {
126
126
  // Should not throw
127
127
  clearForensicsMarker(join(tmpBase, "nonexistent"));
128
128
  });
129
+
130
+ it("buildForensicsContextInjection keeps marker for low-entropy resume prompts", async () => {
131
+ const { buildForensicsContextInjection } = await import("../bootstrap/system-context.ts");
132
+
133
+ const markerPath = join(tmpBase, ".gsd", "runtime", "active-forensics.json");
134
+ writeFileSync(markerPath, JSON.stringify({
135
+ reportPath: "/some/report.md",
136
+ promptContent: "forensics prompt",
137
+ createdAt: new Date().toISOString(),
138
+ }), "utf-8");
139
+
140
+ const result = buildForensicsContextInjection(tmpBase, "continue");
141
+ assert.equal(result, "forensics prompt");
142
+ assert.ok(existsSync(markerPath), "resume-like follow-up should keep marker intact");
143
+ });
144
+
145
+ it("buildForensicsContextInjection clears marker on unrelated user prompts", async () => {
146
+ const { buildForensicsContextInjection } = await import("../bootstrap/system-context.ts");
147
+
148
+ const markerPath = join(tmpBase, ".gsd", "runtime", "active-forensics.json");
149
+ writeFileSync(markerPath, JSON.stringify({
150
+ reportPath: "/some/report.md",
151
+ promptContent: "forensics prompt",
152
+ createdAt: new Date().toISOString(),
153
+ }), "utf-8");
154
+
155
+ const result = buildForensicsContextInjection(tmpBase, "please summarize the README");
156
+ assert.equal(result, null);
157
+ assert.ok(!existsSync(markerPath), "unrelated follow-up should clear the stale marker");
158
+ });
129
159
  });
@@ -7,6 +7,7 @@ import {
7
7
  openDatabase,
8
8
  closeDatabase,
9
9
  isDbAvailable,
10
+ wasDbOpenAttempted,
10
11
  getDbProvider,
11
12
  insertDecision,
12
13
  getDecisionById,
@@ -346,6 +347,17 @@ describe('gsd-db', () => {
346
347
  assert.deepStrictEqual(ar, [], 'getActiveRequirements returns [] when DB closed');
347
348
  });
348
349
 
350
+ test('gsd-db: wasDbOpenAttempted tracks openDatabase calls', () => {
351
+ // wasDbOpenAttempted should return true once openDatabase has been called
352
+ // (previous tests in this suite already called openDatabase, so the flag is set)
353
+ assert.ok(wasDbOpenAttempted(), 'wasDbOpenAttempted should be true after openDatabase was called');
354
+
355
+ // Verify the flag persists even after closeDatabase
356
+ closeDatabase();
357
+ assert.ok(!isDbAvailable(), 'DB should not be available after close');
358
+ assert.ok(wasDbOpenAttempted(), 'wasDbOpenAttempted should remain true after closeDatabase');
359
+ });
360
+
349
361
  // ─── Final Report ──────────────────────────────────────────────────────────
350
362
 
351
363
  });
@@ -100,7 +100,7 @@ describe("#2985 Bug 4 — getDiscussionMilestoneId must be keyed by basePath", (
100
100
  });
101
101
  });
102
102
 
103
- test("checkAutoStartAfterDiscuss fails closed when a multi-milestone manifest is missing", () => {
103
+ test("checkAutoStartAfterDiscuss ignores missing manifest for single-milestone discuss on established project", () => {
104
104
  const base = mkdtempSync(join(tmpdir(), "gsd-auto-start-manifest-"));
105
105
  try {
106
106
  const gsdDir = join(base, ".gsd");
@@ -123,7 +123,7 @@ test("checkAutoStartAfterDiscuss fails closed when a multi-milestone manifest is
123
123
  });
124
124
 
125
125
  const started = checkAutoStartAfterDiscuss();
126
- assert.equal(started, false, "auto-start should fail closed without the manifest");
126
+ assert.equal(started, true, "project history alone should not require a manifest");
127
127
  } finally {
128
128
  clearPendingAutoStart();
129
129
  rmSync(base, { recursive: true, force: true });
@@ -15,7 +15,7 @@ import { tmpdir } from "node:os";
15
15
  import test from "node:test";
16
16
  import assert from "node:assert/strict";
17
17
  import { runGSDDoctor } from "../../doctor.ts";
18
- import { closeDatabase } from "../../gsd-db.ts";
18
+ import { closeDatabase, insertMilestone, insertSlice, openDatabase } from "../../gsd-db.ts";
19
19
 
20
20
  function makeTmp(name: string): string {
21
21
  const dir = join(tmpdir(), `doctor-fixlevel-${name}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
@@ -177,6 +177,57 @@ test("legacy roadmap fallback: future slices are treated as pending, active slic
177
177
  );
178
178
  });
179
179
 
180
+ test("db skipped slices do not report missing directories", async (t) => {
181
+ const tmp = makeTmp("skipped-slice-dir");
182
+ t.after(() => {
183
+ try { closeDatabase(); } catch { /* noop */ }
184
+ rmSync(tmp, { recursive: true, force: true });
185
+ });
186
+
187
+ const gsd = join(tmp, ".gsd");
188
+ const m = join(gsd, "milestones", "M001");
189
+ mkdirSync(m, { recursive: true });
190
+
191
+ writeFileSync(join(m, "M001-ROADMAP.md"), `# M001: Test
192
+
193
+ ## Slices
194
+
195
+ - [ ] **S05: Skipped Slice** \`risk:low\` \`depends:[]\`
196
+ > Intentionally skipped
197
+ `);
198
+
199
+ openDatabase(join(gsd, "gsd.db"));
200
+ insertMilestone({ id: "M001", title: "Test", status: "active" });
201
+ insertSlice({ id: "S05", milestoneId: "M001", title: "Skipped Slice", status: "skipped", sequence: 5 });
202
+
203
+ const report = await runGSDDoctor(tmp, { scope: "M001" });
204
+ const missingDirIssues = report.issues.filter(
205
+ i =>
206
+ (i.code === "missing_slice_dir" || i.code === "missing_tasks_dir") &&
207
+ i.unitId === "M001/S05",
208
+ );
209
+
210
+ assert.deepStrictEqual(
211
+ missingDirIssues,
212
+ [],
213
+ "skipped slices should not require slice or tasks directories",
214
+ );
215
+ });
216
+
217
+ test("doctor source treats skipped DB slices as closed and directory-optional", () => {
218
+ const doctorSource = readFileSync(join(process.cwd(), "src/resources/extensions/gsd/doctor.ts"), "utf8");
219
+ assert.match(
220
+ doctorSource,
221
+ /done:\s*isClosedStatus\(s\.status\)/,
222
+ "doctor should normalize skipped DB slices through isClosedStatus()",
223
+ );
224
+ assert.match(
225
+ doctorSource,
226
+ /if \(slice\.pending \|\| slice\.skipped\) continue;/,
227
+ "doctor should skip missing-directory checks for skipped slices",
228
+ );
229
+ });
230
+
180
231
  test("fixLevel:all — delimiter_in_title still fixable", async (t) => {
181
232
  const tmp = makeTmp("delimiter-fix");
182
233
  t.after(() => rmSync(tmp, { recursive: true, force: true }));
@@ -661,10 +661,9 @@ describe('doctor-git', async () => {
661
661
  env: { ...process.env, GIT_COMMITTER_DATE: pastDate },
662
662
  });
663
663
 
664
- // Modify a tracked file and create a new untracked file. The snapshot
665
- // must preserve both, not just tracked changes.
664
+ // Modify an already-tracked file (nativeAddTracked uses git add -u,
665
+ // which only stages tracked files new untracked files are not staged)
666
666
  writeFileSync(join(dir, "README.md"), "# test\nmodified content\n");
667
- writeFileSync(join(dir, "new-untracked.ts"), "export const preserved = true;\n");
668
667
 
669
668
  const detect = await runGSDDoctor(dir);
670
669
  const staleIssues = detect.issues.filter(i => i.code === "stale_uncommitted_changes");
@@ -682,12 +681,6 @@ describe('doctor-git', async () => {
682
681
  // Verify the snapshot commit was created with the gsd snapshot tag
683
682
  const log = run("git log -1 --oneline", dir);
684
683
  assert.ok(log.includes("gsd snapshot"), "commit is tagged with gsd snapshot");
685
-
686
- const files = run("git show --name-only --format= HEAD", dir);
687
- assert.ok(files.includes("README.md"), "snapshot keeps tracked modifications");
688
- assert.ok(files.includes("new-untracked.ts"), "snapshot also includes new untracked files");
689
- const status = run("git status --short", dir);
690
- assert.ok(!status.includes("new-untracked.ts"), "snapshot does not leave the new source file untracked");
691
684
  });
692
685
 
693
686
  // ─── Test: stale_uncommitted_changes NOT flagged when recent commit ──