gsd-pi 2.42.0 → 2.43.0-dev.5717b75

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 (291) hide show
  1. package/README.md +23 -0
  2. package/dist/cli.js +18 -3
  3. package/dist/loader.js +3 -1
  4. package/dist/resource-loader.js +39 -6
  5. package/dist/resources/extensions/async-jobs/async-bash-tool.js +52 -4
  6. package/dist/resources/extensions/async-jobs/await-tool.js +5 -0
  7. package/dist/resources/extensions/async-jobs/index.js +2 -0
  8. package/dist/resources/extensions/gsd/auto/phases.js +1 -3
  9. package/dist/resources/extensions/gsd/auto-prompts.js +3 -16
  10. package/dist/resources/extensions/gsd/auto-start.js +8 -11
  11. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -5
  12. package/dist/resources/extensions/gsd/detection.js +19 -0
  13. package/dist/resources/extensions/gsd/doctor-checks.js +31 -1
  14. package/dist/resources/extensions/gsd/doctor-providers.js +10 -0
  15. package/dist/resources/extensions/gsd/forensics.js +84 -0
  16. package/dist/resources/extensions/gsd/git-constants.js +1 -0
  17. package/dist/resources/extensions/gsd/git-service.js +1 -1
  18. package/dist/resources/extensions/gsd/native-git-bridge.js +1 -0
  19. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  20. package/dist/resources/extensions/gsd/preferences.js +59 -8
  21. package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
  22. package/dist/resources/extensions/gsd/repo-identity.js +46 -5
  23. package/dist/resources/extensions/gsd/service-tier.js +13 -4
  24. package/dist/resources/extensions/gsd/session-lock.js +2 -2
  25. package/dist/resources/extensions/gsd/worktree-resolver.js +2 -2
  26. package/dist/resources/extensions/gsd/worktree.js +2 -2
  27. package/dist/resources/extensions/mcp-client/index.js +2 -1
  28. package/dist/resources/extensions/search-the-web/tool-search.js +3 -3
  29. package/dist/web/standalone/.next/BUILD_ID +1 -1
  30. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  31. package/dist/web/standalone/.next/build-manifest.json +3 -3
  32. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  33. package/dist/web/standalone/.next/required-server-files.json +3 -3
  34. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  35. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  37. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  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 +3 -3
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  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 +3 -3
  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/api/boot/route.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  61. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
  99. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  105. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  119. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  121. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  123. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app/index.html +1 -1
  133. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  134. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  135. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  136. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  137. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  138. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/page.js +2 -2
  140. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  142. package/dist/web/standalone/.next/server/chunks/229.js +2 -2
  143. package/dist/web/standalone/.next/server/chunks/471.js +3 -3
  144. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/middleware.js +2 -2
  146. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  148. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  149. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  150. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  151. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-f2a7482d42a5614b.js +1 -0
  152. package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +1 -0
  153. package/dist/web/standalone/.next/static/chunks/app/page-b9367c5ae13b99c6.js +1 -0
  154. package/dist/web/standalone/.next/static/chunks/{main-app-2f2ee7b85712c2bd.js → main-app-fdab67f7802d7832.js} +1 -1
  155. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  156. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  157. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  158. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  159. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  160. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  161. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  162. package/dist/web/standalone/server.js +1 -1
  163. package/dist/web-mode.d.ts +2 -0
  164. package/dist/web-mode.js +40 -4
  165. package/package.json +1 -1
  166. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  167. package/packages/pi-agent-core/dist/agent.js +2 -0
  168. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  169. package/packages/pi-agent-core/dist/types.d.ts +6 -0
  170. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  171. package/packages/pi-agent-core/dist/types.js.map +1 -1
  172. package/packages/pi-agent-core/src/agent.test.ts +53 -0
  173. package/packages/pi-agent-core/src/agent.ts +3 -0
  174. package/packages/pi-agent-core/src/types.ts +6 -0
  175. package/packages/pi-agent-core/tsconfig.json +1 -1
  176. package/packages/pi-ai/dist/models.d.ts +5 -3
  177. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  178. package/packages/pi-ai/dist/models.generated.d.ts +801 -1468
  179. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  180. package/packages/pi-ai/dist/models.generated.js +1135 -1588
  181. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  182. package/packages/pi-ai/dist/models.js.map +1 -1
  183. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  184. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +60 -2
  185. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  186. package/packages/pi-ai/scripts/generate-models.ts +1543 -0
  187. package/packages/pi-ai/src/models.generated.ts +1140 -1593
  188. package/packages/pi-ai/src/models.ts +7 -4
  189. package/packages/pi-ai/src/utils/oauth/github-copilot.ts +74 -2
  190. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/agent-session.js +8 -1
  192. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  193. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +7 -0
  194. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  195. package/packages/pi-coding-agent/dist/core/auth-storage.js +29 -2
  196. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  197. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +60 -0
  198. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  199. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  200. package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
  201. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  202. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  203. package/packages/pi-coding-agent/dist/core/lsp/client.js +23 -0
  204. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  206. package/packages/pi-coding-agent/dist/core/model-registry.js +2 -0
  207. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  208. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +6 -0
  209. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  210. package/packages/pi-coding-agent/dist/core/package-manager.js +63 -11
  211. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  212. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +9 -0
  213. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  214. package/packages/pi-coding-agent/dist/core/resource-loader.js +20 -6
  215. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  216. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  217. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -5
  218. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  219. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  220. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js +3 -0
  221. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js.map +1 -1
  222. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  223. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +9 -6
  224. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  225. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  226. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +30 -10
  227. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  228. package/packages/pi-coding-agent/package.json +1 -1
  229. package/packages/pi-coding-agent/src/core/agent-session.ts +7 -1
  230. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +68 -0
  231. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -2
  232. package/packages/pi-coding-agent/src/core/extensions/loader.ts +18 -0
  233. package/packages/pi-coding-agent/src/core/lsp/client.ts +29 -0
  234. package/packages/pi-coding-agent/src/core/model-registry.ts +3 -0
  235. package/packages/pi-coding-agent/src/core/package-manager.ts +99 -58
  236. package/packages/pi-coding-agent/src/core/resource-loader.ts +24 -6
  237. package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -5
  238. package/packages/pi-coding-agent/src/modes/interactive/components/extension-editor.ts +3 -0
  239. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +10 -6
  240. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +31 -11
  241. package/pkg/package.json +1 -1
  242. package/src/resources/extensions/async-jobs/async-bash-timeout.test.ts +122 -0
  243. package/src/resources/extensions/async-jobs/async-bash-tool.ts +40 -4
  244. package/src/resources/extensions/async-jobs/await-tool.test.ts +47 -0
  245. package/src/resources/extensions/async-jobs/await-tool.ts +5 -0
  246. package/src/resources/extensions/async-jobs/index.ts +1 -0
  247. package/src/resources/extensions/async-jobs/job-manager.ts +2 -0
  248. package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -1
  249. package/src/resources/extensions/gsd/auto/phases.ts +1 -3
  250. package/src/resources/extensions/gsd/auto-prompts.ts +2 -18
  251. package/src/resources/extensions/gsd/auto-start.ts +7 -10
  252. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -5
  253. package/src/resources/extensions/gsd/detection.ts +19 -0
  254. package/src/resources/extensions/gsd/doctor-checks.ts +32 -1
  255. package/src/resources/extensions/gsd/doctor-providers.ts +13 -0
  256. package/src/resources/extensions/gsd/doctor-types.ts +1 -0
  257. package/src/resources/extensions/gsd/forensics.ts +92 -0
  258. package/src/resources/extensions/gsd/git-constants.ts +1 -0
  259. package/src/resources/extensions/gsd/git-service.ts +0 -1
  260. package/src/resources/extensions/gsd/gitignore.ts +1 -1
  261. package/src/resources/extensions/gsd/native-git-bridge.ts +1 -0
  262. package/src/resources/extensions/gsd/preferences-types.ts +3 -0
  263. package/src/resources/extensions/gsd/preferences.ts +62 -6
  264. package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
  265. package/src/resources/extensions/gsd/repo-identity.ts +48 -5
  266. package/src/resources/extensions/gsd/service-tier.ts +17 -4
  267. package/src/resources/extensions/gsd/session-lock.ts +2 -2
  268. package/src/resources/extensions/gsd/tests/activity-log.test.ts +31 -69
  269. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +48 -0
  270. package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +43 -0
  271. package/src/resources/extensions/gsd/tests/git-locale.test.ts +133 -0
  272. package/src/resources/extensions/gsd/tests/git-service.test.ts +44 -0
  273. package/src/resources/extensions/gsd/tests/journal.test.ts +82 -127
  274. package/src/resources/extensions/gsd/tests/manifest-status.test.ts +73 -82
  275. package/src/resources/extensions/gsd/tests/service-tier.test.ts +30 -1
  276. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +56 -3
  277. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +151 -0
  278. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +156 -263
  279. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -78
  280. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +81 -74
  281. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +1 -2
  282. package/src/resources/extensions/gsd/worktree-resolver.ts +2 -3
  283. package/src/resources/extensions/gsd/worktree.ts +2 -2
  284. package/src/resources/extensions/mcp-client/index.ts +5 -1
  285. package/src/resources/extensions/search-the-web/tool-search.ts +3 -3
  286. package/dist/web/standalone/.next/static/chunks/app/_not-found/page-e07acdb7dd069836.js +0 -1
  287. package/dist/web/standalone/.next/static/chunks/app/layout-745c6ed5fea5fb06.js +0 -1
  288. package/dist/web/standalone/.next/static/chunks/app/page-801b53eff6e83579.js +0 -1
  289. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-e6255954dccfcf0a.js +0 -1
  290. /package/dist/web/standalone/.next/static/{QSuuaY2tP8h_zzhHcVxNj → 5ULZcR9XhHFzlAZFiSKRl}/_buildManifest.js +0 -0
  291. /package/dist/web/standalone/.next/static/{QSuuaY2tP8h_zzhHcVxNj → 5ULZcR9XhHFzlAZFiSKRl}/_ssgManifest.js +0 -0
@@ -8,7 +8,7 @@
8
8
  * Uses temp directories with real .gsd/milestones/M001/ structure.
9
9
  */
10
10
 
11
- import test from 'node:test';
11
+ import { describe, test, beforeEach, afterEach } from 'node:test';
12
12
  import assert from 'node:assert/strict';
13
13
  import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
14
14
  import { join } from 'node:path';
@@ -30,12 +30,21 @@ function writeManifest(base: string, content: string): void {
30
30
 
31
31
  // ─── Mixed statuses ──────────────────────────────────────────────────────────
32
32
 
33
- test('getManifestStatus: mixed statuses — categorizes entries correctly', async () => {
34
- const tmp = makeTempDir('manifest-mixed');
35
- const savedVal = process.env.GSD_TEST_EXISTING_KEY_001;
36
- try {
33
+ describe('getManifestStatus: mixed statuses', () => {
34
+ let tmp: string;
35
+ let savedVal: string | undefined;
36
+ beforeEach(() => {
37
+ tmp = makeTempDir('manifest-mixed');
38
+ savedVal = process.env.GSD_TEST_EXISTING_KEY_001;
37
39
  process.env.GSD_TEST_EXISTING_KEY_001 = 'some-value';
40
+ });
41
+ afterEach(() => {
42
+ delete process.env.GSD_TEST_EXISTING_KEY_001;
43
+ if (savedVal !== undefined) process.env.GSD_TEST_EXISTING_KEY_001 = savedVal;
44
+ rmSync(tmp, { recursive: true, force: true });
45
+ });
38
46
 
47
+ test('categorizes entries correctly', async () => {
39
48
  writeManifest(tmp, `# Secrets Manifest
40
49
 
41
50
  **Milestone:** M001
@@ -80,18 +89,17 @@ test('getManifestStatus: mixed statuses — categorizes entries correctly', asyn
80
89
  assert.deepStrictEqual(result!.collected, ['COLLECTED_KEY']);
81
90
  assert.deepStrictEqual(result!.skipped, ['SKIPPED_KEY']);
82
91
  assert.deepStrictEqual(result!.existing, ['GSD_TEST_EXISTING_KEY_001']);
83
- } finally {
84
- delete process.env.GSD_TEST_EXISTING_KEY_001;
85
- if (savedVal !== undefined) process.env.GSD_TEST_EXISTING_KEY_001 = savedVal;
86
- rmSync(tmp, { recursive: true, force: true });
87
- }
92
+ });
88
93
  });
89
94
 
90
95
  // ─── All pending ─────────────────────────────────────────────────────────────
91
96
 
92
- test('getManifestStatus: all pending 3 pending entries, none in env', async () => {
93
- const tmp = makeTempDir('manifest-pending');
94
- try {
97
+ describe('getManifestStatus: simple temp dir tests', () => {
98
+ let tmp: string;
99
+ beforeEach(() => { tmp = makeTempDir('manifest-test'); });
100
+ afterEach(() => { rmSync(tmp, { recursive: true, force: true }); });
101
+
102
+ test('all pending — 3 pending entries, none in env', async () => {
95
103
  // Ensure none of these are in process.env
96
104
  delete process.env.PEND_A;
97
105
  delete process.env.PEND_B;
@@ -133,16 +141,11 @@ test('getManifestStatus: all pending — 3 pending entries, none in env', async
133
141
  assert.deepStrictEqual(result!.collected, []);
134
142
  assert.deepStrictEqual(result!.skipped, []);
135
143
  assert.deepStrictEqual(result!.existing, []);
136
- } finally {
137
- rmSync(tmp, { recursive: true, force: true });
138
- }
139
- });
144
+ });
140
145
 
141
- // ─── All collected ───────────────────────────────────────────────────────────
146
+ // ─── All collected ───────────────────────────────────────────────────────────
142
147
 
143
- test('getManifestStatus: all collected — 2 collected entries, none in env', async () => {
144
- const tmp = makeTempDir('manifest-collected');
145
- try {
148
+ test('all collected — 2 collected entries, none in env', async () => {
146
149
  delete process.env.COLL_X;
147
150
  delete process.env.COLL_Y;
148
151
 
@@ -174,64 +177,19 @@ test('getManifestStatus: all collected — 2 collected entries, none in env', as
174
177
  assert.deepStrictEqual(result!.collected, ['COLL_X', 'COLL_Y']);
175
178
  assert.deepStrictEqual(result!.skipped, []);
176
179
  assert.deepStrictEqual(result!.existing, []);
177
- } finally {
178
- rmSync(tmp, { recursive: true, force: true });
179
- }
180
- });
181
-
182
- // ─── Key in env overrides manifest status ────────────────────────────────────
183
-
184
- test('getManifestStatus: key in env overrides manifest status — collected key in env goes to existing', async () => {
185
- const tmp = makeTempDir('manifest-override');
186
- const savedVal = process.env.GSD_TEST_OVERRIDE_KEY;
187
- try {
188
- process.env.GSD_TEST_OVERRIDE_KEY = 'already-here';
189
-
190
- writeManifest(tmp, `# Secrets Manifest
191
-
192
- **Milestone:** M001
193
- **Generated:** 2025-06-20T10:00:00Z
194
-
195
- ### GSD_TEST_OVERRIDE_KEY
196
-
197
- **Service:** Override
198
- **Status:** collected
199
- **Destination:** dotenv
200
-
201
- 1. Was collected but now in env
202
- `);
203
-
204
- const result = await getManifestStatus(tmp, 'M001');
205
- assert.notStrictEqual(result, null);
206
- assert.deepStrictEqual(result!.pending, []);
207
- assert.deepStrictEqual(result!.collected, []);
208
- assert.deepStrictEqual(result!.skipped, []);
209
- assert.deepStrictEqual(result!.existing, ['GSD_TEST_OVERRIDE_KEY']);
210
- } finally {
211
- delete process.env.GSD_TEST_OVERRIDE_KEY;
212
- if (savedVal !== undefined) process.env.GSD_TEST_OVERRIDE_KEY = savedVal;
213
- rmSync(tmp, { recursive: true, force: true });
214
- }
215
- });
180
+ });
216
181
 
217
- // ─── Missing manifest ────────────────────────────────────────────────────────
182
+ // ─── Missing manifest ────────────────────────────────────────────────────────
218
183
 
219
- test('getManifestStatus: missing manifest — returns null', async () => {
220
- const tmp = makeTempDir('manifest-missing');
221
- try {
184
+ test('missing manifest — returns null', async () => {
222
185
  // No .gsd directory at all
223
186
  const result = await getManifestStatus(tmp, 'M001');
224
187
  assert.strictEqual(result, null);
225
- } finally {
226
- rmSync(tmp, { recursive: true, force: true });
227
- }
228
- });
188
+ });
229
189
 
230
- // ─── Empty manifest (no entries) ─────────────────────────────────────────────
190
+ // ─── Empty manifest (no entries) ─────────────────────────────────────────────
231
191
 
232
- test('getManifestStatus: empty manifest — exists but no H3 sections', async () => {
233
- const tmp = makeTempDir('manifest-empty');
234
- try {
192
+ test('empty manifest — exists but no H3 sections', async () => {
235
193
  writeManifest(tmp, `# Secrets Manifest
236
194
 
237
195
  **Milestone:** M001
@@ -244,16 +202,11 @@ test('getManifestStatus: empty manifest — exists but no H3 sections', async ()
244
202
  assert.deepStrictEqual(result!.collected, []);
245
203
  assert.deepStrictEqual(result!.skipped, []);
246
204
  assert.deepStrictEqual(result!.existing, []);
247
- } finally {
248
- rmSync(tmp, { recursive: true, force: true });
249
- }
250
- });
205
+ });
251
206
 
252
- // ─── Env via .env file (not just process.env) ────────────────────────────────
207
+ // ─── Env via .env file (not just process.env) ────────────────────────────────
253
208
 
254
- test('getManifestStatus: key in .env file counts as existing', async () => {
255
- const tmp = makeTempDir('manifest-dotenv');
256
- try {
209
+ test('key in .env file counts as existing', async () => {
257
210
  delete process.env.DOTENV_ONLY_KEY;
258
211
 
259
212
  writeManifest(tmp, `# Secrets Manifest
@@ -277,7 +230,45 @@ test('getManifestStatus: key in .env file counts as existing', async () => {
277
230
  assert.notStrictEqual(result, null);
278
231
  assert.deepStrictEqual(result!.existing, ['DOTENV_ONLY_KEY']);
279
232
  assert.deepStrictEqual(result!.pending, []);
280
- } finally {
233
+ });
234
+ });
235
+
236
+ // ─── Key in env overrides manifest status ────────────────────────────────────
237
+
238
+ describe('getManifestStatus: key in env overrides manifest status', () => {
239
+ let tmp: string;
240
+ let savedVal: string | undefined;
241
+ beforeEach(() => {
242
+ tmp = makeTempDir('manifest-override');
243
+ savedVal = process.env.GSD_TEST_OVERRIDE_KEY;
244
+ process.env.GSD_TEST_OVERRIDE_KEY = 'already-here';
245
+ });
246
+ afterEach(() => {
247
+ delete process.env.GSD_TEST_OVERRIDE_KEY;
248
+ if (savedVal !== undefined) process.env.GSD_TEST_OVERRIDE_KEY = savedVal;
281
249
  rmSync(tmp, { recursive: true, force: true });
282
- }
250
+ });
251
+
252
+ test('collected key in env goes to existing', async () => {
253
+ writeManifest(tmp, `# Secrets Manifest
254
+
255
+ **Milestone:** M001
256
+ **Generated:** 2025-06-20T10:00:00Z
257
+
258
+ ### GSD_TEST_OVERRIDE_KEY
259
+
260
+ **Service:** Override
261
+ **Status:** collected
262
+ **Destination:** dotenv
263
+
264
+ 1. Was collected but now in env
265
+ `);
266
+
267
+ const result = await getManifestStatus(tmp, 'M001');
268
+ assert.notStrictEqual(result, null);
269
+ assert.deepStrictEqual(result!.pending, []);
270
+ assert.deepStrictEqual(result!.collected, []);
271
+ assert.deepStrictEqual(result!.skipped, []);
272
+ assert.deepStrictEqual(result!.existing, ['GSD_TEST_OVERRIDE_KEY']);
273
+ });
283
274
  });
@@ -4,8 +4,8 @@ import assert from "node:assert/strict";
4
4
  import {
5
5
  supportsServiceTier,
6
6
  formatServiceTierStatus,
7
+ formatServiceTierFooterStatus,
7
8
  resolveServiceTierIcon,
8
- type ServiceTierSetting,
9
9
  } from "../service-tier.ts";
10
10
 
11
11
  // ─── supportsServiceTier ─────────────────────────────────────────────────────
@@ -27,6 +27,14 @@ describe("supportsServiceTier", () => {
27
27
  assert.equal(supportsServiceTier("openai/gpt-5.4"), true);
28
28
  });
29
29
 
30
+ test("returns true for vibeproxy-openai/gpt-5.4 (proxy provider-prefixed)", () => {
31
+ assert.equal(supportsServiceTier("vibeproxy-openai/gpt-5.4"), true);
32
+ });
33
+
34
+ test("returns false for provider-only identifier without gpt-5.4 model suffix", () => {
35
+ assert.equal(supportsServiceTier("vibeproxy-openai"), false);
36
+ });
37
+
30
38
  test("returns false for claude-opus-4-6", () => {
31
39
  assert.equal(supportsServiceTier("claude-opus-4-6"), false);
32
40
  });
@@ -52,6 +60,11 @@ describe("formatServiceTierStatus", () => {
52
60
  assert.ok(output.includes("disabled"), `Expected 'disabled' in: ${output}`);
53
61
  });
54
62
 
63
+ test("mentions provider-agnostic model gating", () => {
64
+ const output = formatServiceTierStatus("priority");
65
+ assert.ok(output.includes("regardless of provider"), `Expected provider note in: ${output}`);
66
+ });
67
+
55
68
  test("shows priority when set to priority", () => {
56
69
  const output = formatServiceTierStatus("priority");
57
70
  assert.ok(output.includes("priority"), `Expected 'priority' in: ${output}`);
@@ -63,6 +76,22 @@ describe("formatServiceTierStatus", () => {
63
76
  });
64
77
  });
65
78
 
79
+ // ─── formatServiceTierFooterStatus ───────────────────────────────────────────
80
+
81
+ describe("formatServiceTierFooterStatus", () => {
82
+ test("returns priority footer status for supported model", () => {
83
+ assert.equal(formatServiceTierFooterStatus("priority", "vibeproxy-openai/gpt-5.4"), "fast: ⚡ priority");
84
+ });
85
+
86
+ test("returns undefined for unsupported model", () => {
87
+ assert.equal(formatServiceTierFooterStatus("priority", "claude-opus-4-6"), undefined);
88
+ });
89
+
90
+ test("returns undefined when tier is disabled", () => {
91
+ assert.equal(formatServiceTierFooterStatus(undefined, "gpt-5.4"), undefined);
92
+ });
93
+ });
94
+
66
95
  // ─── resolveServiceTierIcon ──────────────────────────────────────────────────
67
96
 
68
97
  describe("resolveServiceTierIcon", () => {
@@ -39,7 +39,7 @@ function buildBlock(
39
39
  });
40
40
  }
41
41
 
42
- test("buildSkillActivationBlock matches installed skills from task context", () => {
42
+ test("buildSkillActivationBlock does not auto-activate skills via broad context heuristic", () => {
43
43
  const base = makeTempBase();
44
44
  try {
45
45
  writeSkill(base, "react", "Use for React components, hooks, JSX, and frontend UI work.");
@@ -52,7 +52,29 @@ test("buildSkillActivationBlock matches installed skills from task context", ()
52
52
  taskTitle: "Implement React settings panel",
53
53
  });
54
54
 
55
- assert.match(result, /<skill_activation>/);
55
+ // Skills should not be activated just because their name appears in task context.
56
+ // Activation requires explicit preference sources (always_use, skill_rules, prefer_skills, skills_used).
57
+ assert.equal(result, "");
58
+ } finally {
59
+ cleanup(base);
60
+ }
61
+ });
62
+
63
+ test("buildSkillActivationBlock activates skills via prefer_skills when context matches", () => {
64
+ const base = makeTempBase();
65
+ try {
66
+ writeSkill(base, "react", "Use for React components, hooks, JSX, and frontend UI work.");
67
+ writeSkill(base, "swiftui", "Use for SwiftUI views, iOS layout, and Apple platform UI work.");
68
+ loadOnlyTestSkills(base);
69
+
70
+ const result = buildBlock(base, {
71
+ sliceTitle: "Build React dashboard",
72
+ taskId: "T01",
73
+ taskTitle: "Implement React settings panel",
74
+ }, {
75
+ prefer_skills: ["react"],
76
+ });
77
+
56
78
  assert.match(result, /Call Skill\('react'\)/);
57
79
  assert.doesNotMatch(result, /swiftui/);
58
80
  } finally {
@@ -105,7 +127,7 @@ test("buildSkillActivationBlock includes skill_rules matches and task-plan skill
105
127
  }
106
128
  });
107
129
 
108
- test("buildSkillActivationBlock honors avoid_skills", () => {
130
+ test("buildSkillActivationBlock honors avoid_skills against always_use_skills", () => {
109
131
  const base = makeTempBase();
110
132
  try {
111
133
  writeSkill(base, "react", "Use for React components and frontend UI work.");
@@ -114,6 +136,7 @@ test("buildSkillActivationBlock honors avoid_skills", () => {
114
136
  const result = buildBlock(base, {
115
137
  taskTitle: "Implement React settings panel",
116
138
  }, {
139
+ always_use_skills: ["react"],
117
140
  avoid_skills: ["react"],
118
141
  });
119
142
 
@@ -138,3 +161,33 @@ test("buildSkillActivationBlock falls back cleanly when nothing matches", () =>
138
161
  cleanup(base);
139
162
  }
140
163
  });
164
+
165
+ test("buildSkillActivationBlock does not activate skills from extraContext or taskPlanContent body", () => {
166
+ const base = makeTempBase();
167
+ try {
168
+ writeSkill(base, "xcode-build", "Use for Xcode build workflows and iOS compilation.");
169
+ writeSkill(base, "ableton-lom", "Use for Ableton Live Object Model scripting.");
170
+ writeSkill(base, "frontend-design", "Use for frontend design systems and UI components.");
171
+ loadOnlyTestSkills(base);
172
+
173
+ const taskPlan = [
174
+ "---",
175
+ "skills_used: []",
176
+ "---",
177
+ "# T01: Build the API endpoint",
178
+ "Use xcode-build patterns and frontend-design tokens.",
179
+ ].join("\n");
180
+
181
+ const result = buildBlock(base, {
182
+ taskTitle: "Build REST API",
183
+ extraContext: ["Build workflow for iOS and Ableton integration testing"],
184
+ taskPlanContent: taskPlan,
185
+ });
186
+
187
+ // None of these skills should activate — extraContext and taskPlanContent body
188
+ // must not be used for heuristic matching.
189
+ assert.equal(result, "");
190
+ } finally {
191
+ cleanup(base);
192
+ }
193
+ });
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Tests for macOS numbered symlink variant cleanup (#2205).
3
+ *
4
+ * macOS can rename `.gsd` to `.gsd 2`, `.gsd 3`, etc. when a directory
5
+ * already exists at the target path. ensureGsdSymlink() must detect and
6
+ * remove these numbered variants so the real `.gsd` symlink is always
7
+ * the one in use.
8
+ */
9
+
10
+ import {
11
+ mkdtempSync,
12
+ rmSync,
13
+ writeFileSync,
14
+ existsSync,
15
+ lstatSync,
16
+ realpathSync,
17
+ mkdirSync,
18
+ symlinkSync,
19
+ readlinkSync,
20
+ } from "node:fs";
21
+ import { join } from "node:path";
22
+ import { tmpdir } from "node:os";
23
+ import { execSync } from "node:child_process";
24
+
25
+ import { ensureGsdSymlink, externalGsdRoot } from "../repo-identity.ts";
26
+ import { createTestContext } from "./test-helpers.ts";
27
+
28
+ const { assertEq, assertTrue, report } = createTestContext();
29
+
30
+ function run(command: string, cwd: string): string {
31
+ return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
32
+ }
33
+
34
+ async function main(): Promise<void> {
35
+ const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-symlink-variants-")));
36
+ const stateDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-state-variants-")));
37
+
38
+ try {
39
+ process.env.GSD_STATE_DIR = stateDir;
40
+
41
+ // Set up a minimal git repo
42
+ run("git init -b main", base);
43
+ run('git config user.name "Pi Test"', base);
44
+ run('git config user.email "pi@example.com"', base);
45
+ run('git remote add origin git@github.com:example/repo.git', base);
46
+ writeFileSync(join(base, "README.md"), "# Test Repo\n", "utf-8");
47
+ run("git add README.md", base);
48
+ run('git commit -m "chore: init"', base);
49
+
50
+ const externalPath = externalGsdRoot(base);
51
+
52
+ // ── Test: numbered variant directories are cleaned up ──────────────
53
+ console.log("\n=== ensureGsdSymlink removes numbered .gsd variants (#2205) ===");
54
+ {
55
+ // Simulate macOS creating numbered variants: ".gsd 2", ".gsd 3"
56
+ mkdirSync(join(base, ".gsd 2"), { recursive: true });
57
+ mkdirSync(join(base, ".gsd 3"), { recursive: true });
58
+ mkdirSync(join(base, ".gsd 4"), { recursive: true });
59
+
60
+ const result = ensureGsdSymlink(base);
61
+ assertEq(result, externalPath, "ensureGsdSymlink returns external path");
62
+ assertTrue(existsSync(join(base, ".gsd")), ".gsd exists after ensureGsdSymlink");
63
+ assertTrue(lstatSync(join(base, ".gsd")).isSymbolicLink(), ".gsd is a symlink");
64
+
65
+ // The numbered variants must have been removed
66
+ assertTrue(!existsSync(join(base, ".gsd 2")), '".gsd 2" directory was cleaned up');
67
+ assertTrue(!existsSync(join(base, ".gsd 3")), '".gsd 3" directory was cleaned up');
68
+ assertTrue(!existsSync(join(base, ".gsd 4")), '".gsd 4" directory was cleaned up');
69
+ }
70
+
71
+ // ── Test: numbered variant symlinks are cleaned up ─────────────────
72
+ console.log("\n=== ensureGsdSymlink removes numbered symlink variants ===");
73
+ {
74
+ // Clean slate
75
+ rmSync(join(base, ".gsd"), { recursive: true, force: true });
76
+
77
+ // Simulate: ".gsd 2" is a symlink to the correct target (the real .gsd)
78
+ // and ".gsd" doesn't exist — this is the actual macOS scenario
79
+ const staleTarget = join(stateDir, "projects", "stale-target");
80
+ mkdirSync(staleTarget, { recursive: true });
81
+ symlinkSync(externalPath, join(base, ".gsd 2"), "junction");
82
+ symlinkSync(staleTarget, join(base, ".gsd 3"), "junction");
83
+
84
+ const result = ensureGsdSymlink(base);
85
+ assertEq(result, externalPath, "ensureGsdSymlink returns external path when variants exist");
86
+ assertTrue(existsSync(join(base, ".gsd")), ".gsd exists");
87
+ assertTrue(lstatSync(join(base, ".gsd")).isSymbolicLink(), ".gsd is a symlink");
88
+
89
+ assertTrue(!existsSync(join(base, ".gsd 2")), '".gsd 2" symlink variant was cleaned up');
90
+ assertTrue(!existsSync(join(base, ".gsd 3")), '".gsd 3" symlink variant was cleaned up');
91
+ }
92
+
93
+ // ── Test: real .gsd directory blocks symlink, but variants still cleaned ──
94
+ console.log("\n=== ensureGsdSymlink cleans variants even when .gsd is a real directory ===");
95
+ {
96
+ // Clean slate
97
+ rmSync(join(base, ".gsd"), { recursive: true, force: true });
98
+
99
+ // .gsd is a real directory (git-tracked) and numbered variants exist
100
+ mkdirSync(join(base, ".gsd", "milestones"), { recursive: true });
101
+ writeFileSync(join(base, ".gsd", "milestones", "M001.md"), "# M001\n", "utf-8");
102
+ mkdirSync(join(base, ".gsd 2"), { recursive: true });
103
+ mkdirSync(join(base, ".gsd 3"), { recursive: true });
104
+
105
+ const result = ensureGsdSymlink(base);
106
+ // When .gsd is a real directory, ensureGsdSymlink preserves it
107
+ assertEq(result, join(base, ".gsd"), "real .gsd directory preserved");
108
+ assertTrue(lstatSync(join(base, ".gsd")).isDirectory(), ".gsd remains a directory");
109
+
110
+ // But the numbered variants should still be cleaned up
111
+ assertTrue(!existsSync(join(base, ".gsd 2")), '".gsd 2" cleaned even when .gsd is a directory');
112
+ assertTrue(!existsSync(join(base, ".gsd 3")), '".gsd 3" cleaned even when .gsd is a directory');
113
+ }
114
+
115
+ // ── Test: only numeric-suffixed variants are removed ───────────────
116
+ console.log("\n=== ensureGsdSymlink only removes .gsd + space + digit variants ===");
117
+ {
118
+ rmSync(join(base, ".gsd"), { recursive: true, force: true });
119
+
120
+ // These should NOT be touched
121
+ mkdirSync(join(base, ".gsd-backup"), { recursive: true });
122
+ mkdirSync(join(base, ".gsd_old"), { recursive: true });
123
+
124
+ // These SHOULD be removed (macOS collision pattern)
125
+ mkdirSync(join(base, ".gsd 2"), { recursive: true });
126
+ mkdirSync(join(base, ".gsd 10"), { recursive: true });
127
+
128
+ ensureGsdSymlink(base);
129
+
130
+ assertTrue(existsSync(join(base, ".gsd-backup")), ".gsd-backup is NOT removed");
131
+ assertTrue(existsSync(join(base, ".gsd_old")), ".gsd_old is NOT removed");
132
+ assertTrue(!existsSync(join(base, ".gsd 2")), '".gsd 2" removed');
133
+ assertTrue(!existsSync(join(base, ".gsd 10")), '".gsd 10" removed');
134
+
135
+ // Cleanup non-variant dirs
136
+ rmSync(join(base, ".gsd-backup"), { recursive: true, force: true });
137
+ rmSync(join(base, ".gsd_old"), { recursive: true, force: true });
138
+ }
139
+
140
+ } finally {
141
+ delete process.env.GSD_STATE_DIR;
142
+ try { rmSync(base, { recursive: true, force: true }); } catch { /* ignore */ }
143
+ try { rmSync(stateDir, { recursive: true, force: true }); } catch { /* ignore */ }
144
+ report();
145
+ }
146
+ }
147
+
148
+ main().catch((error) => {
149
+ console.error(error);
150
+ process.exit(1);
151
+ });