gsd-pi 2.67.0-dev.1cd1e0f → 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 (257) hide show
  1. package/README.md +1 -1
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +155 -70
  3. package/dist/resources/extensions/gsd/auto/phases.js +17 -0
  4. package/dist/resources/extensions/gsd/auto/session.js +10 -0
  5. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +12 -0
  6. package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
  7. package/dist/resources/extensions/gsd/auto-start.js +16 -30
  8. package/dist/resources/extensions/gsd/auto-worktree.js +62 -15
  9. package/dist/resources/extensions/gsd/auto.js +121 -59
  10. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +11 -435
  11. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -4
  12. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +7 -64
  13. package/dist/resources/extensions/gsd/bootstrap/system-context.js +7 -2
  14. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +88 -8
  15. package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
  16. package/dist/resources/extensions/gsd/commands/handlers/core.js +39 -25
  17. package/dist/resources/extensions/gsd/commands/index.js +8 -1
  18. package/dist/resources/extensions/gsd/commands-mcp-status.js +43 -7
  19. package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -4
  20. package/dist/resources/extensions/gsd/doctor-proactive.js +3 -3
  21. package/dist/resources/extensions/gsd/doctor.js +8 -4
  22. package/dist/resources/extensions/gsd/gsd-db.js +11 -0
  23. package/dist/resources/extensions/gsd/guided-flow.js +56 -31
  24. package/dist/resources/extensions/gsd/init-wizard.js +37 -0
  25. package/dist/resources/extensions/gsd/interrupted-session.js +146 -0
  26. package/dist/resources/extensions/gsd/mcp-project-config.js +83 -0
  27. package/dist/resources/extensions/gsd/state.js +7 -2
  28. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +508 -0
  29. package/dist/resources/extensions/gsd/workflow-logger.js +18 -3
  30. package/dist/resources/extensions/gsd/workflow-mcp.js +261 -0
  31. package/dist/web/standalone/.next/BUILD_ID +1 -1
  32. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  33. package/dist/web/standalone/.next/build-manifest.json +3 -3
  34. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  35. package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
  36. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/index.html +1 -1
  55. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  56. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  57. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  58. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  63. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  66. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  67. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  68. package/dist/web/standalone/.next/static/chunks/2826.821e01b07d92e948.js +9 -0
  69. package/dist/web/standalone/.next/static/chunks/app/{page-0c485498795110d6.js → page-f1e30ab6bb269149.js} +1 -1
  70. package/dist/web/standalone/.next/static/chunks/{webpack-b49b09f97429b5d0.js → webpack-6e4d7e9a4f57bed4.js} +1 -1
  71. package/package.json +4 -2
  72. package/packages/mcp-server/README.md +38 -0
  73. package/packages/mcp-server/dist/cli.d.ts +9 -0
  74. package/packages/mcp-server/dist/cli.d.ts.map +1 -0
  75. package/packages/mcp-server/dist/cli.js +58 -0
  76. package/packages/mcp-server/dist/cli.js.map +1 -0
  77. package/packages/mcp-server/dist/index.d.ts +20 -0
  78. package/packages/mcp-server/dist/index.d.ts.map +1 -0
  79. package/packages/mcp-server/dist/index.js +14 -0
  80. package/packages/mcp-server/dist/index.js.map +1 -0
  81. package/packages/mcp-server/dist/readers/captures.d.ts +25 -0
  82. package/packages/mcp-server/dist/readers/captures.d.ts.map +1 -0
  83. package/packages/mcp-server/dist/readers/captures.js +67 -0
  84. package/packages/mcp-server/dist/readers/captures.js.map +1 -0
  85. package/packages/mcp-server/dist/readers/doctor-lite.d.ts +20 -0
  86. package/packages/mcp-server/dist/readers/doctor-lite.d.ts.map +1 -0
  87. package/packages/mcp-server/dist/readers/doctor-lite.js +173 -0
  88. package/packages/mcp-server/dist/readers/doctor-lite.js.map +1 -0
  89. package/packages/mcp-server/dist/readers/index.d.ts +14 -0
  90. package/packages/mcp-server/dist/readers/index.d.ts.map +1 -0
  91. package/packages/mcp-server/dist/readers/index.js +10 -0
  92. package/packages/mcp-server/dist/readers/index.js.map +1 -0
  93. package/packages/mcp-server/dist/readers/knowledge.d.ts +18 -0
  94. package/packages/mcp-server/dist/readers/knowledge.d.ts.map +1 -0
  95. package/packages/mcp-server/dist/readers/knowledge.js +82 -0
  96. package/packages/mcp-server/dist/readers/knowledge.js.map +1 -0
  97. package/packages/mcp-server/dist/readers/metrics.d.ts +32 -0
  98. package/packages/mcp-server/dist/readers/metrics.d.ts.map +1 -0
  99. package/packages/mcp-server/dist/readers/metrics.js +74 -0
  100. package/packages/mcp-server/dist/readers/metrics.js.map +1 -0
  101. package/packages/mcp-server/dist/readers/paths.d.ts +42 -0
  102. package/packages/mcp-server/dist/readers/paths.d.ts.map +1 -0
  103. package/packages/mcp-server/dist/readers/paths.js +199 -0
  104. package/packages/mcp-server/dist/readers/paths.js.map +1 -0
  105. package/packages/mcp-server/dist/readers/roadmap.d.ts +26 -0
  106. package/packages/mcp-server/dist/readers/roadmap.d.ts.map +1 -0
  107. package/packages/mcp-server/dist/readers/roadmap.js +194 -0
  108. package/packages/mcp-server/dist/readers/roadmap.js.map +1 -0
  109. package/packages/mcp-server/dist/readers/state.d.ts +43 -0
  110. package/packages/mcp-server/dist/readers/state.d.ts.map +1 -0
  111. package/packages/mcp-server/dist/readers/state.js +184 -0
  112. package/packages/mcp-server/dist/readers/state.js.map +1 -0
  113. package/packages/mcp-server/dist/server.d.ts +28 -0
  114. package/packages/mcp-server/dist/server.d.ts.map +1 -0
  115. package/packages/mcp-server/dist/server.js +319 -0
  116. package/packages/mcp-server/dist/server.js.map +1 -0
  117. package/packages/mcp-server/dist/session-manager.d.ts +54 -0
  118. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -0
  119. package/packages/mcp-server/dist/session-manager.js +284 -0
  120. package/packages/mcp-server/dist/session-manager.js.map +1 -0
  121. package/packages/mcp-server/dist/types.d.ts +61 -0
  122. package/packages/mcp-server/dist/types.d.ts.map +1 -0
  123. package/packages/mcp-server/dist/types.js +11 -0
  124. package/packages/mcp-server/dist/types.js.map +1 -0
  125. package/packages/mcp-server/dist/workflow-tools.d.ts +9 -0
  126. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -0
  127. package/packages/mcp-server/dist/workflow-tools.js +532 -0
  128. package/packages/mcp-server/dist/workflow-tools.js.map +1 -0
  129. package/packages/mcp-server/src/server.ts +6 -2
  130. package/packages/mcp-server/src/workflow-tools.test.ts +976 -0
  131. package/packages/mcp-server/src/workflow-tools.ts +997 -0
  132. package/packages/mcp-server/tsconfig.json +1 -1
  133. package/packages/pi-agent-core/dist/agent-loop.js +14 -6
  134. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  135. package/packages/pi-agent-core/src/agent-loop.test.ts +53 -0
  136. package/packages/pi-agent-core/src/agent-loop.ts +20 -6
  137. package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts +43 -0
  138. package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts.map +1 -0
  139. package/packages/pi-coding-agent/dist/core/contextual-tips.js +208 -0
  140. package/packages/pi-coding-agent/dist/core/contextual-tips.js.map +1 -0
  141. package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts +2 -0
  142. package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts.map +1 -0
  143. package/packages/pi-coding-agent/dist/core/contextual-tips.test.js +227 -0
  144. package/packages/pi-coding-agent/dist/core/contextual-tips.test.js.map +1 -0
  145. package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
  146. package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/index.js +1 -0
  148. package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts +2 -0
  150. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts.map +1 -0
  151. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +28 -0
  152. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -0
  153. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  155. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -12
  156. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  158. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +19 -0
  159. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  160. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +4 -0
  161. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +14 -0
  163. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
  165. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  166. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -12
  167. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  168. package/packages/pi-coding-agent/src/core/contextual-tips.test.ts +259 -0
  169. package/packages/pi-coding-agent/src/core/contextual-tips.ts +232 -0
  170. package/packages/pi-coding-agent/src/core/index.ts +2 -0
  171. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +54 -0
  172. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -12
  173. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +21 -0
  174. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +19 -0
  175. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +19 -15
  176. package/packages/rpc-client/dist/index.d.ts +10 -0
  177. package/packages/rpc-client/dist/index.d.ts.map +1 -0
  178. package/packages/rpc-client/dist/index.js +9 -0
  179. package/packages/rpc-client/dist/index.js.map +1 -0
  180. package/packages/rpc-client/dist/jsonl.d.ts +17 -0
  181. package/packages/rpc-client/dist/jsonl.d.ts.map +1 -0
  182. package/packages/rpc-client/dist/jsonl.js +54 -0
  183. package/packages/rpc-client/dist/jsonl.js.map +1 -0
  184. package/packages/rpc-client/dist/rpc-client.d.ts +259 -0
  185. package/packages/rpc-client/dist/rpc-client.d.ts.map +1 -0
  186. package/packages/rpc-client/dist/rpc-client.js +541 -0
  187. package/packages/rpc-client/dist/rpc-client.js.map +1 -0
  188. package/packages/rpc-client/dist/rpc-client.test.d.ts +2 -0
  189. package/packages/rpc-client/dist/rpc-client.test.d.ts.map +1 -0
  190. package/packages/rpc-client/dist/rpc-client.test.js +477 -0
  191. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -0
  192. package/packages/rpc-client/dist/rpc-types.d.ts +566 -0
  193. package/packages/rpc-client/dist/rpc-types.d.ts.map +1 -0
  194. package/packages/rpc-client/dist/rpc-types.js +12 -0
  195. package/packages/rpc-client/dist/rpc-types.js.map +1 -0
  196. package/scripts/ensure-workspace-builds.cjs +2 -0
  197. package/scripts/link-workspace-packages.cjs +21 -14
  198. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +193 -93
  199. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +173 -79
  200. package/src/resources/extensions/gsd/auto/phases.ts +25 -0
  201. package/src/resources/extensions/gsd/auto/session.ts +10 -0
  202. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +20 -0
  203. package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
  204. package/src/resources/extensions/gsd/auto-start.ts +23 -55
  205. package/src/resources/extensions/gsd/auto-worktree.ts +59 -15
  206. package/src/resources/extensions/gsd/auto.ts +133 -64
  207. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +22 -435
  208. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -5
  209. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +7 -72
  210. package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -2
  211. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +122 -6
  212. package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
  213. package/src/resources/extensions/gsd/commands/handlers/core.ts +53 -26
  214. package/src/resources/extensions/gsd/commands/index.ts +7 -1
  215. package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
  216. package/src/resources/extensions/gsd/doctor-git-checks.ts +4 -4
  217. package/src/resources/extensions/gsd/doctor-proactive.ts +3 -3
  218. package/src/resources/extensions/gsd/doctor.ts +9 -5
  219. package/src/resources/extensions/gsd/gsd-db.ts +12 -0
  220. package/src/resources/extensions/gsd/guided-flow.ts +66 -36
  221. package/src/resources/extensions/gsd/init-wizard.ts +40 -0
  222. package/src/resources/extensions/gsd/interrupted-session.ts +224 -0
  223. package/src/resources/extensions/gsd/mcp-project-config.ts +128 -0
  224. package/src/resources/extensions/gsd/state.ts +7 -1
  225. package/src/resources/extensions/gsd/tests/auto-project-root-env.test.ts +29 -0
  226. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +668 -2
  227. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +14 -4
  228. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +21 -0
  229. package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +101 -0
  230. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +380 -2
  231. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +66 -0
  232. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +30 -0
  233. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +12 -0
  234. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +2 -2
  235. package/src/resources/extensions/gsd/tests/integration/doctor-fixlevel.test.ts +52 -1
  236. package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +2 -9
  237. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +0 -33
  238. package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +169 -0
  239. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +146 -0
  240. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +136 -0
  241. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +85 -0
  242. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +15 -0
  243. package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +11 -0
  244. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +16 -0
  245. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +500 -0
  246. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +625 -0
  247. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +629 -0
  248. package/src/resources/extensions/gsd/workflow-logger.ts +19 -3
  249. package/src/resources/extensions/gsd/workflow-mcp.ts +320 -0
  250. package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
  251. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts +0 -13
  252. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts.map +0 -1
  253. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js +0 -27
  254. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js.map +0 -1
  255. package/packages/pi-coding-agent/src/modes/interactive/provider-auth-setup.ts +0 -40
  256. /package/dist/web/standalone/.next/static/{PHqEommYRR8CRn3i84CGM → WMDT_0C0XDkBKtsAI_AX4}/_buildManifest.js +0 -0
  257. /package/dist/web/standalone/.next/static/{PHqEommYRR8CRn3i84CGM → WMDT_0C0XDkBKtsAI_AX4}/_ssgManifest.js +0 -0
@@ -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 ──
@@ -219,39 +219,6 @@ describe('doctor-proactive', async () => {
219
219
  assert.ok(result.fixesApplied.some((f: string) => f.includes("STATE.md")), "reports STATE.md status as info");
220
220
  });
221
221
 
222
- test('health gate: pre-dispatch snapshot includes new untracked files', async () => {
223
- const dir = createRepoWithActiveMilestone();
224
- cleanups.push(dir);
225
-
226
- const pastDate = new Date(Date.now() - 45 * 60 * 1000).toISOString();
227
- run(`git commit --amend --no-edit --date="${pastDate}"`, dir);
228
- execSync(`git commit --amend --no-edit`, {
229
- cwd: dir,
230
- stdio: ["ignore", "pipe", "pipe"],
231
- encoding: "utf-8",
232
- env: { ...process.env, GIT_COMMITTER_DATE: pastDate },
233
- });
234
-
235
- writeFileSync(join(dir, "README.md"), "# test\nmodified content\n");
236
- writeFileSync(join(dir, "new-untracked.ts"), "export const preserved = true;\n");
237
-
238
- const result = await preDispatchHealthGate(dir);
239
- assert.ok(result.proceed, "dispatch still proceeds after snapshotting");
240
- assert.ok(
241
- result.fixesApplied.some((f: string) => f.includes("gsd snapshot")),
242
- "pre-dispatch gate creates a snapshot commit",
243
- );
244
-
245
- const log = run("git log -1 --oneline", dir);
246
- assert.ok(log.includes("gsd snapshot"), "snapshot commit is created");
247
-
248
- const files = run("git show --name-only --format= HEAD", dir);
249
- assert.ok(files.includes("README.md"), "snapshot keeps tracked modifications");
250
- assert.ok(files.includes("new-untracked.ts"), "snapshot also includes new untracked files");
251
- const status = run("git status --short", dir);
252
- assert.ok(!status.includes("new-untracked.ts"), "snapshot does not leave the new source file untracked");
253
- });
254
-
255
222
  test('health gate: stale crash lock auto-cleared', async () => {
256
223
  const dir = realpathSync(mkdtempSync(join(tmpdir(), "doc-proactive-")));
257
224
  cleanups.push(dir);
@@ -0,0 +1,169 @@
1
+ /**
2
+ * GSD-2 — Regression tests for merge cwd restore (#2929)
3
+ * merge-cwd-restore.test.ts — Regression tests for #2929.
4
+ *
5
+ * Verifies:
6
+ * 1. MergeConflictError restores process.cwd() to the pre-merge directory.
7
+ * 2. autoCommitDirtyState does not run on the integration branch when cwd
8
+ * leaked there from a prior failed merge (parallel mode).
9
+ *
10
+ * Bug: PR #2298 added a stash lifecycle around mergeMilestoneToMain but the
11
+ * MergeConflictError throw path omitted the process.chdir(previousCwd) that
12
+ * the dirty-working-tree and divergence handlers both include. In parallel
13
+ * merge sequences, this left cwd on the integration branch, causing the next
14
+ * merge's autoCommitDirtyState to commit dirty files from OTHER milestones
15
+ * onto main.
16
+ */
17
+
18
+ import { describe, test, beforeEach, afterEach } from "node:test";
19
+ import assert from "node:assert/strict";
20
+ import {
21
+ mkdtempSync,
22
+ mkdirSync,
23
+ writeFileSync,
24
+ rmSync,
25
+ realpathSync,
26
+ } from "node:fs";
27
+ import { join } from "node:path";
28
+ import { tmpdir } from "node:os";
29
+ import { execSync } from "node:child_process";
30
+
31
+ import { mergeMilestoneToMain } from "../../auto-worktree.ts";
32
+ import { MergeConflictError } from "../../git-service.ts";
33
+
34
+ function run(cmd: string, cwd: string): string {
35
+ return execSync(cmd, {
36
+ cwd,
37
+ stdio: ["ignore", "pipe", "pipe"],
38
+ encoding: "utf-8",
39
+ }).trim();
40
+ }
41
+
42
+ function createTempRepo(): string {
43
+ const dir = realpathSync(
44
+ mkdtempSync(join(tmpdir(), "merge-cwd-restore-test-")),
45
+ );
46
+ run("git init -b main", dir);
47
+ run("git config user.email test@test.com", dir);
48
+ run("git config user.name Test", dir);
49
+ writeFileSync(join(dir, "README.md"), "# test\n");
50
+ writeFileSync(join(dir, ".gitignore"), ".gsd/worktrees/\n");
51
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
52
+ writeFileSync(join(dir, ".gsd", "STATE.md"), "# State\n");
53
+ run("git add .", dir);
54
+ run("git commit -m init", dir);
55
+ return dir;
56
+ }
57
+
58
+ function makeRoadmap(mid: string, title: string): string {
59
+ return [
60
+ `# ${mid}: Test milestone`,
61
+ "",
62
+ "## Slices",
63
+ "- [x] **S01: Test slice**",
64
+ ].join("\n");
65
+ }
66
+
67
+ describe("merge cwd restore (#2929)", () => {
68
+ let repo: string;
69
+ let savedCwd: string;
70
+
71
+ beforeEach(() => {
72
+ savedCwd = process.cwd();
73
+ repo = createTempRepo();
74
+ });
75
+
76
+ afterEach(() => {
77
+ process.chdir(savedCwd);
78
+ try { run("git reset --hard HEAD", repo); } catch { /* */ }
79
+ rmSync(repo, { recursive: true, force: true });
80
+ });
81
+
82
+ // ─────────────────────────────────────────────────────────────────────────
83
+ // Test 1: MergeConflictError restores cwd (#2929 bug 2)
84
+ // ─────────────────────────────────────────────────────────────────────────
85
+
86
+ test("MergeConflictError restores cwd to pre-merge directory", () => {
87
+ // Create milestone branch that modifies README.md
88
+ run("git checkout -b milestone/M010", repo);
89
+ writeFileSync(join(repo, "README.md"), "# M010 version\n");
90
+ run("git add .", repo);
91
+ run('git commit -m "M010 changes README"', repo);
92
+ run("git checkout main", repo);
93
+
94
+ // Modify README.md on main to create a conflict
95
+ writeFileSync(join(repo, "README.md"), "# main version (diverged)\n");
96
+ run("git add .", repo);
97
+ run('git commit -m "main diverges README"', repo);
98
+
99
+ // cwd must be repo root (simulates parallel-merge calling from project root)
100
+ process.chdir(repo);
101
+ const cwdBefore = process.cwd();
102
+
103
+ let caught: unknown = null;
104
+ try {
105
+ mergeMilestoneToMain(repo, "M010", makeRoadmap("M010", "Conflict test"));
106
+ } catch (err) {
107
+ caught = err;
108
+ }
109
+
110
+ // Should have thrown a MergeConflictError
111
+ assert.ok(caught instanceof MergeConflictError, "expected MergeConflictError");
112
+
113
+ // Critical: cwd must be restored to where it was before the merge
114
+ const cwdAfter = process.cwd();
115
+ assert.equal(
116
+ cwdAfter,
117
+ cwdBefore,
118
+ "cwd should be restored after MergeConflictError — was left on integration branch before fix",
119
+ );
120
+ });
121
+
122
+ // ─────────────────────────────────────────────────────────────────────────
123
+ // Test 2: autoCommitDirtyState skipped when on integration branch (#2929 bug 1)
124
+ // ─────────────────────────────────────────────────────────────────────────
125
+
126
+ test("autoCommitDirtyState does not commit on integration branch in worktree mode", () => {
127
+ // Create milestone branch with real work
128
+ run("git checkout -b milestone/M010", repo);
129
+ writeFileSync(join(repo, "m010.ts"), "export const m010 = true;\n");
130
+ run("git add .", repo);
131
+ run('git commit -m "M010 work"', repo);
132
+ run("git checkout main", repo);
133
+
134
+ // Simulate the parallel-mode state: cwd is on main with dirty files
135
+ // from another milestone (as if a prior merge's MergeConflictError
136
+ // left cwd on main and syncStateToProjectRoot wrote these files).
137
+ writeFileSync(join(repo, "dirty-from-m020.txt"), "should not be committed\n");
138
+
139
+ // Set up roadmap so mergeMilestoneToMain can find milestone metadata
140
+ mkdirSync(join(repo, ".gsd", "milestones", "M010"), { recursive: true });
141
+ writeFileSync(
142
+ join(repo, ".gsd", "milestones", "M010", "M010-ROADMAP.md"),
143
+ makeRoadmap("M010", "First milestone"),
144
+ );
145
+
146
+ process.chdir(repo);
147
+
148
+ const result = mergeMilestoneToMain(
149
+ repo,
150
+ "M010",
151
+ makeRoadmap("M010", "First milestone"),
152
+ );
153
+
154
+ assert.ok(result.commitMessage.includes("M010"), "commit should be for M010");
155
+
156
+ // Verify the squash merge brought M010's work file
157
+ const mergeLog = run("git log --oneline --diff-filter=A -- m010.ts", repo);
158
+ assert.ok(mergeLog.length > 0, "m010.ts should be in a commit on main");
159
+
160
+ // The dirty file should NOT appear in the squash merge commit.
161
+ const squashCommit = run("git log --format=%H --grep='GSD-Milestone: M010' -1", repo);
162
+ assert.ok(squashCommit.length > 0, "should find the squash merge commit");
163
+ const filesInSquash = run(`git diff-tree --no-commit-id --name-only -r ${squashCommit}`, repo);
164
+ assert.ok(
165
+ !filesInSquash.includes("dirty-from-m020.txt"),
166
+ "dirty-from-m020.txt should NOT be in the squash merge commit",
167
+ );
168
+ });
169
+ });
@@ -0,0 +1,146 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ import { randomUUID } from "node:crypto";
7
+
8
+ import { assessInterruptedSession } from "../interrupted-session.ts";
9
+
10
+ function makeTmpBase(): string {
11
+ const base = join(tmpdir(), `gsd-auto-interrupted-${randomUUID()}`);
12
+ mkdirSync(join(base, ".gsd"), { recursive: true });
13
+ return base;
14
+ }
15
+
16
+ function cleanup(base: string): void {
17
+ try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
18
+ }
19
+
20
+ function writeRoadmap(base: string, checked = false): void {
21
+ const milestoneDir = join(base, ".gsd", "milestones", "M001");
22
+ mkdirSync(join(milestoneDir, "slices", "S01", "tasks"), { recursive: true });
23
+ writeFileSync(
24
+ join(milestoneDir, "M001-ROADMAP.md"),
25
+ [
26
+ "# M001: Test Milestone",
27
+ "",
28
+ "## Vision",
29
+ "",
30
+ "Test milestone.",
31
+ "",
32
+ "## Success Criteria",
33
+ "",
34
+ "- It works.",
35
+ "",
36
+ "## Slices",
37
+ "",
38
+ `- [${checked ? "x" : " "}] **S01: Test slice** \`risk:low\``,
39
+ " After this: Demo",
40
+ "",
41
+ "## Boundary Map",
42
+ "",
43
+ "- S01 → terminal",
44
+ " - Produces: done",
45
+ " - Consumes: nothing",
46
+ ].join("\n"),
47
+ "utf-8",
48
+ );
49
+ }
50
+
51
+ function writeCompleteArtifacts(base: string): void {
52
+ const milestoneDir = join(base, ".gsd", "milestones", "M001");
53
+ const sliceDir = join(milestoneDir, "slices", "S01");
54
+ mkdirSync(sliceDir, { recursive: true });
55
+ writeFileSync(join(sliceDir, "S01-SUMMARY.md"), "# Summary\nDone.\n", "utf-8");
56
+ writeFileSync(join(sliceDir, "S01-UAT.md"), "# UAT\nPassed.\n", "utf-8");
57
+ writeFileSync(join(milestoneDir, "M001-SUMMARY.md"), "# Milestone Summary\nDone.\n", "utf-8");
58
+ }
59
+
60
+ function writeLock(base: string, unitType: string, unitId: string): void {
61
+ writeFileSync(
62
+ join(base, ".gsd", "auto.lock"),
63
+ JSON.stringify({
64
+ pid: 999999999,
65
+ startedAt: new Date().toISOString(),
66
+ unitType,
67
+ unitId,
68
+ unitStartedAt: new Date().toISOString(),
69
+ }, null, 2),
70
+ "utf-8",
71
+ );
72
+ }
73
+
74
+ function writePausedSession(base: string, milestoneId = "M001", stepMode = false): void {
75
+ const runtimeDir = join(base, ".gsd", "runtime");
76
+ mkdirSync(runtimeDir, { recursive: true });
77
+ writeFileSync(
78
+ join(runtimeDir, "paused-session.json"),
79
+ JSON.stringify({ milestoneId, originalBasePath: base, stepMode }, null, 2),
80
+ "utf-8",
81
+ );
82
+ }
83
+
84
+ test("direct /gsd auto stale complete repo yields stale classification with no recovery payload", async () => {
85
+ const base = makeTmpBase();
86
+ try {
87
+ writeRoadmap(base, true);
88
+ writeCompleteArtifacts(base);
89
+ writeLock(base, "execute-task", "M001/S01/T01");
90
+
91
+ const assessment = await assessInterruptedSession(base);
92
+ assert.equal(assessment.classification, "stale");
93
+ assert.equal(assessment.recoveryPrompt, null);
94
+ assert.equal(assessment.hasResumableDiskState, false);
95
+ } finally {
96
+ cleanup(base);
97
+ }
98
+ });
99
+
100
+ test("direct /gsd auto paused-session metadata remains recoverable when work is unfinished", async () => {
101
+ const base = makeTmpBase();
102
+ try {
103
+ writeRoadmap(base, false);
104
+ writePausedSession(base, "M001", false);
105
+ writeLock(base, "execute-task", "M001/S01/T01");
106
+
107
+ const assessment = await assessInterruptedSession(base);
108
+ assert.equal(assessment.classification, "recoverable");
109
+ assert.equal(assessment.pausedSession?.milestoneId, "M001");
110
+ } finally {
111
+ cleanup(base);
112
+ }
113
+ });
114
+
115
+ test("direct /gsd auto stale paused-session metadata is treated as stale when no resumable work remains", async () => {
116
+ const base = makeTmpBase();
117
+ try {
118
+ writeRoadmap(base, true);
119
+ writeCompleteArtifacts(base);
120
+ writePausedSession(base, "M999", true);
121
+
122
+ const assessment = await assessInterruptedSession(base);
123
+ assert.equal(assessment.classification, "stale");
124
+ assert.equal(assessment.hasResumableDiskState, false);
125
+ } finally {
126
+ cleanup(base);
127
+ }
128
+ });
129
+
130
+ test("direct /gsd auto source only resumes paused-session metadata for recoverable state with real recovery signals", async () => {
131
+ const source = await import(`node:fs/promises`).then((fs) =>
132
+ fs.readFile(new URL("../auto.ts", import.meta.url), "utf-8")
133
+ );
134
+ assert.ok(source.includes('const shouldResumePausedSession ='));
135
+ assert.ok(source.includes('freshStartAssessment.classification === "recoverable"'));
136
+ assert.ok(source.includes('&& ('));
137
+ assert.ok(source.includes('freshStartAssessment.hasResumableDiskState'));
138
+ assert.ok(source.includes('|| !!freshStartAssessment.recoveryPrompt'));
139
+ assert.ok(source.includes('|| !!freshStartAssessment.lock'));
140
+ });
141
+
142
+ test("auto module imports successfully after interrupted-session changes", async () => {
143
+ const mod = await import(`../auto.ts?ts=${Date.now()}-${Math.random()}`);
144
+ assert.equal(typeof mod.startAuto, "function");
145
+ assert.equal(typeof mod.pauseAuto, "function");
146
+ });
@@ -0,0 +1,136 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ import { randomUUID } from "node:crypto";
7
+
8
+ import { assessInterruptedSession } from "../interrupted-session.ts";
9
+
10
+ function makeTmpBase(): string {
11
+ const base = join(tmpdir(), `gsd-smart-entry-${randomUUID()}`);
12
+ mkdirSync(join(base, ".gsd"), { recursive: true });
13
+ return base;
14
+ }
15
+
16
+ function cleanup(base: string): void {
17
+ try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
18
+ }
19
+
20
+ function writeRoadmap(base: string, checked = false): void {
21
+ const milestoneDir = join(base, ".gsd", "milestones", "M001");
22
+ mkdirSync(join(milestoneDir, "slices", "S01", "tasks"), { recursive: true });
23
+ writeFileSync(
24
+ join(milestoneDir, "M001-ROADMAP.md"),
25
+ [
26
+ "# M001: Test Milestone",
27
+ "",
28
+ "## Vision",
29
+ "",
30
+ "Test milestone.",
31
+ "",
32
+ "## Success Criteria",
33
+ "",
34
+ "- It works.",
35
+ "",
36
+ "## Slices",
37
+ "",
38
+ `- [${checked ? "x" : " "}] **S01: Test slice** \`risk:low\``,
39
+ " After this: Demo",
40
+ "",
41
+ "## Boundary Map",
42
+ "",
43
+ "- S01 → terminal",
44
+ " - Produces: done",
45
+ " - Consumes: nothing",
46
+ ].join("\n"),
47
+ "utf-8",
48
+ );
49
+ }
50
+
51
+ function writeCompleteArtifacts(base: string): void {
52
+ const milestoneDir = join(base, ".gsd", "milestones", "M001");
53
+ const sliceDir = join(milestoneDir, "slices", "S01");
54
+ mkdirSync(sliceDir, { recursive: true });
55
+ writeFileSync(join(sliceDir, "S01-SUMMARY.md"), "# Summary\nDone.\n", "utf-8");
56
+ writeFileSync(join(sliceDir, "S01-UAT.md"), "# UAT\nPassed.\n", "utf-8");
57
+ writeFileSync(join(milestoneDir, "M001-SUMMARY.md"), "# Milestone Summary\nDone.\n", "utf-8");
58
+ }
59
+
60
+ function writePausedSession(base: string, milestoneId = "M001", stepMode = false): void {
61
+ const runtimeDir = join(base, ".gsd", "runtime");
62
+ mkdirSync(runtimeDir, { recursive: true });
63
+ writeFileSync(
64
+ join(runtimeDir, "paused-session.json"),
65
+ JSON.stringify({ milestoneId, originalBasePath: base, stepMode }, null, 2),
66
+ "utf-8",
67
+ );
68
+ }
69
+
70
+ function writeLock(base: string, unitType: string, unitId: string): void {
71
+ writeFileSync(
72
+ join(base, ".gsd", "auto.lock"),
73
+ JSON.stringify({
74
+ pid: 999999999,
75
+ startedAt: new Date().toISOString(),
76
+ unitType,
77
+ unitId,
78
+ unitStartedAt: new Date().toISOString(),
79
+ }, null, 2),
80
+ "utf-8",
81
+ );
82
+ }
83
+
84
+ test("guided-flow stale complete scenario classifies as stale so the resume prompt can be suppressed", async () => {
85
+ const base = makeTmpBase();
86
+ try {
87
+ writeRoadmap(base, true);
88
+ writeCompleteArtifacts(base);
89
+ writeLock(base, "execute-task", "M001/S01/T01");
90
+
91
+ const assessment = await assessInterruptedSession(base);
92
+ assert.equal(assessment.classification, "stale");
93
+ assert.equal(assessment.recoveryPrompt, null);
94
+ } finally {
95
+ cleanup(base);
96
+ }
97
+ });
98
+
99
+ test("guided-flow paused-session scenario classifies as recoverable so resume remains available", async () => {
100
+ const base = makeTmpBase();
101
+ try {
102
+ writeRoadmap(base, false);
103
+ writePausedSession(base);
104
+ writeLock(base, "execute-task", "M001/S01/T01");
105
+
106
+ const assessment = await assessInterruptedSession(base);
107
+ assert.equal(assessment.classification, "recoverable");
108
+ assert.equal(assessment.pausedSession?.milestoneId, "M001");
109
+ } finally {
110
+ cleanup(base);
111
+ }
112
+ });
113
+
114
+ test("guided-flow stale paused-session scenario is suppressed when no resumable work remains", async () => {
115
+ const base = makeTmpBase();
116
+ try {
117
+ writeRoadmap(base, true);
118
+ writeCompleteArtifacts(base);
119
+ writePausedSession(base, "M999", true);
120
+
121
+ const assessment = await assessInterruptedSession(base);
122
+ assert.equal(assessment.classification, "stale");
123
+ assert.equal(assessment.hasResumableDiskState, false);
124
+ } finally {
125
+ cleanup(base);
126
+ }
127
+ });
128
+
129
+ test("guided-flow source uses step-aware resume and clears stale paused metadata without changing discuss handoff semantics", () => {
130
+ const source = readFileSync(join(import.meta.dirname, "..", "guided-flow.ts"), "utf-8");
131
+ assert.ok(source.includes('const interrupted = await assessInterruptedSession(basePath);'));
132
+ assert.ok(source.includes('resumeLabel = interrupted.pausedSession?.stepMode'));
133
+ assert.ok(source.includes('step: interrupted.pausedSession?.stepMode ?? false'));
134
+ assert.ok(source.includes('unlinkSync(join(gsdRoot(basePath), "runtime", "paused-session.json"))'));
135
+ assert.ok(source.includes('pendingAutoStartMap.set(basePath,'));
136
+ });