gsd-pi 2.72.0-dev.de4c4b3 → 2.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/README.md +12 -2
  2. package/dist/cli.js +59 -3
  3. package/dist/onboarding.js +10 -0
  4. package/dist/resources/extensions/async-jobs/await-tool.js +7 -4
  5. package/dist/resources/extensions/async-jobs/job-manager.js +28 -3
  6. package/dist/resources/extensions/claude-code-cli/partial-builder.js +40 -12
  7. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +48 -23
  8. package/dist/resources/extensions/gsd/auto/loop.js +84 -1
  9. package/dist/resources/extensions/gsd/auto-post-unit.js +6 -0
  10. package/dist/resources/extensions/gsd/auto-recovery.js +11 -0
  11. package/dist/resources/extensions/gsd/auto.js +25 -19
  12. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -11
  13. package/dist/resources/extensions/gsd/commands-handlers.js +4 -1
  14. package/dist/resources/extensions/gsd/context-injector.js +1 -1
  15. package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -7
  16. package/dist/resources/extensions/gsd/definition-io.js +15 -0
  17. package/dist/resources/extensions/gsd/dispatch-guard.js +4 -0
  18. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +6 -3
  19. package/dist/resources/extensions/gsd/git-service.js +11 -8
  20. package/dist/resources/extensions/gsd/gitignore.js +12 -6
  21. package/dist/resources/extensions/gsd/gsd-db.js +49 -6
  22. package/dist/resources/extensions/gsd/key-manager.js +2 -0
  23. package/dist/resources/extensions/gsd/preferences-skills.js +2 -34
  24. package/dist/resources/extensions/gsd/preferences-types.js +15 -0
  25. package/dist/resources/extensions/gsd/preferences.js +16 -3
  26. package/dist/resources/extensions/gsd/prompt-loader.js +4 -1
  27. package/dist/resources/extensions/gsd/prompts/discuss.md +122 -13
  28. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  29. package/dist/resources/extensions/gsd/state.js +21 -1
  30. package/dist/resources/extensions/gsd/workflow-projections.js +7 -0
  31. package/dist/resources/extensions/gsd/worktree-manager.js +30 -3
  32. package/dist/resources/extensions/gsd/write-intercept.js +10 -1
  33. package/dist/resources/extensions/ollama/index.js +4 -5
  34. package/dist/resources/extensions/ollama/ollama-client.js +35 -6
  35. package/dist/resources/extensions/ollama/ollama-discovery.js +32 -6
  36. package/dist/web/standalone/.next/BUILD_ID +1 -1
  37. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  38. package/dist/web/standalone/.next/build-manifest.json +3 -3
  39. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  40. package/dist/web/standalone/.next/required-server-files.json +3 -3
  41. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  42. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  52. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  55. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  56. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  68. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  80. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  100. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  110. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  116. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  130. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  132. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  134. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  136. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/index.html +1 -1
  146. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  147. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  148. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  149. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  150. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  151. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  152. package/dist/web/standalone/.next/server/app/page.js +2 -2
  153. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  154. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  155. package/dist/web/standalone/.next/server/chunks/2331.js +16 -16
  156. package/dist/web/standalone/.next/server/chunks/4741.js +12 -12
  157. package/dist/web/standalone/.next/server/chunks/5822.js +2 -2
  158. package/dist/web/standalone/.next/server/chunks/63.js +8 -8
  159. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  160. package/dist/web/standalone/.next/server/edge-runtime-webpack.js +2 -0
  161. package/dist/web/standalone/.next/server/functions-config-manifest.json +0 -9
  162. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/middleware-manifest.json +29 -2
  164. package/dist/web/standalone/.next/server/middleware.js +4 -12
  165. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  166. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  167. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  168. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  169. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  170. package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
  171. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-f2a7482d42a5614b.js → page-2f24283c162b6ab3.js} +1 -1
  172. package/dist/web/standalone/.next/static/chunks/app/{layout-a16c7a7ecdf0c2cf.js → layout-9ecfd95f343793f0.js} +1 -1
  173. package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +1 -0
  174. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +1 -0
  175. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +1 -0
  176. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  177. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  178. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  179. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  180. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  181. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  182. package/dist/web/standalone/server.js +1 -1
  183. package/package.json +1 -1
  184. package/packages/pi-ai/dist/env-api-keys.js +1 -0
  185. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  186. package/packages/pi-ai/dist/models.custom.d.ts +105 -0
  187. package/packages/pi-ai/dist/models.custom.d.ts.map +1 -1
  188. package/packages/pi-ai/dist/models.custom.js +97 -0
  189. package/packages/pi-ai/dist/models.custom.js.map +1 -1
  190. package/packages/pi-ai/dist/models.generated.d.ts +648 -140
  191. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  192. package/packages/pi-ai/dist/models.generated.js +867 -370
  193. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  194. package/packages/pi-ai/dist/models.generated.test.d.ts +2 -0
  195. package/packages/pi-ai/dist/models.generated.test.d.ts.map +1 -0
  196. package/packages/pi-ai/dist/models.generated.test.js +334 -0
  197. package/packages/pi-ai/dist/models.generated.test.js.map +1 -0
  198. package/packages/pi-ai/dist/models.test.js +105 -0
  199. package/packages/pi-ai/dist/models.test.js.map +1 -1
  200. package/packages/pi-ai/dist/types.d.ts +1 -1
  201. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  202. package/packages/pi-ai/dist/types.js.map +1 -1
  203. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  204. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +5 -1
  205. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  206. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts +2 -0
  207. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts.map +1 -0
  208. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js +57 -0
  209. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js.map +1 -0
  210. package/packages/pi-ai/src/env-api-keys.ts +1 -0
  211. package/packages/pi-ai/src/models.custom.ts +98 -0
  212. package/packages/pi-ai/src/models.generated.test.ts +373 -0
  213. package/packages/pi-ai/src/models.generated.ts +867 -370
  214. package/packages/pi-ai/src/models.test.ts +135 -0
  215. package/packages/pi-ai/src/types.ts +1 -0
  216. package/packages/pi-ai/src/utils/oauth/github-copilot.test.ts +71 -0
  217. package/packages/pi-ai/src/utils/oauth/github-copilot.ts +4 -1
  218. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  219. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  220. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  221. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  222. package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
  223. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  224. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -0
  225. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  226. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  227. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +87 -12
  228. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  229. package/packages/pi-coding-agent/package.json +1 -1
  230. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  231. package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
  232. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +72 -0
  233. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -12
  234. package/packages/pi-tui/dist/components/__tests__/editor.test.js +12 -0
  235. package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -1
  236. package/packages/pi-tui/dist/components/__tests__/input.test.js +12 -0
  237. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  238. package/packages/pi-tui/dist/keys.d.ts.map +1 -1
  239. package/packages/pi-tui/dist/keys.js +27 -0
  240. package/packages/pi-tui/dist/keys.js.map +1 -1
  241. package/packages/pi-tui/src/components/__tests__/editor.test.ts +18 -0
  242. package/packages/pi-tui/src/components/__tests__/input.test.ts +18 -0
  243. package/packages/pi-tui/src/keys.ts +32 -0
  244. package/pkg/package.json +1 -1
  245. package/src/resources/extensions/async-jobs/await-tool.test.ts +40 -7
  246. package/src/resources/extensions/async-jobs/await-tool.ts +7 -4
  247. package/src/resources/extensions/async-jobs/job-manager.ts +33 -3
  248. package/src/resources/extensions/claude-code-cli/partial-builder.ts +45 -12
  249. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +49 -24
  250. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +91 -2
  251. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +112 -0
  252. package/src/resources/extensions/gsd/auto/loop.ts +89 -1
  253. package/src/resources/extensions/gsd/auto-post-unit.ts +7 -0
  254. package/src/resources/extensions/gsd/auto-recovery.ts +10 -0
  255. package/src/resources/extensions/gsd/auto.ts +25 -20
  256. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -10
  257. package/src/resources/extensions/gsd/commands-handlers.ts +5 -1
  258. package/src/resources/extensions/gsd/context-injector.ts +1 -1
  259. package/src/resources/extensions/gsd/custom-workflow-engine.ts +4 -8
  260. package/src/resources/extensions/gsd/definition-io.ts +18 -0
  261. package/src/resources/extensions/gsd/dispatch-guard.ts +5 -0
  262. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +6 -3
  263. package/src/resources/extensions/gsd/git-service.ts +11 -8
  264. package/src/resources/extensions/gsd/gitignore.ts +12 -6
  265. package/src/resources/extensions/gsd/gsd-db.ts +54 -6
  266. package/src/resources/extensions/gsd/key-manager.ts +2 -0
  267. package/src/resources/extensions/gsd/preferences-skills.ts +2 -36
  268. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  269. package/src/resources/extensions/gsd/preferences.ts +19 -6
  270. package/src/resources/extensions/gsd/prompt-loader.ts +6 -1
  271. package/src/resources/extensions/gsd/prompts/discuss.md +122 -13
  272. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  273. package/src/resources/extensions/gsd/state.ts +20 -0
  274. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +27 -0
  275. package/src/resources/extensions/gsd/tests/block-db-writes.test.ts +63 -0
  276. package/src/resources/extensions/gsd/tests/definition-io.test.ts +57 -0
  277. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +26 -0
  278. package/src/resources/extensions/gsd/tests/doctor-heal-fixable-warnings.test.ts +14 -0
  279. package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +104 -0
  280. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +107 -5
  281. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +8 -6
  282. package/src/resources/extensions/gsd/tests/key-manager.test.ts +63 -0
  283. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +54 -0
  284. package/src/resources/extensions/gsd/tests/plan-milestone-artifact-verification.test.ts +62 -0
  285. package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +34 -0
  286. package/src/resources/extensions/gsd/tests/preferences-formatting.test.ts +87 -0
  287. package/src/resources/extensions/gsd/tests/preferences.test.ts +53 -0
  288. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +96 -1
  289. package/src/resources/extensions/gsd/tests/prompt-loader-working-directory.test.ts +19 -0
  290. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +97 -0
  291. package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +41 -0
  292. package/src/resources/extensions/gsd/workflow-projections.ts +8 -0
  293. package/src/resources/extensions/gsd/worktree-manager.ts +29 -3
  294. package/src/resources/extensions/gsd/write-intercept.ts +10 -1
  295. package/src/resources/extensions/ollama/index.ts +4 -5
  296. package/src/resources/extensions/ollama/ollama-client.ts +35 -6
  297. package/src/resources/extensions/ollama/ollama-discovery.ts +37 -6
  298. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +54 -0
  299. package/dist/resources/extensions/gsd/auto-observability.js +0 -54
  300. package/dist/resources/extensions/gsd/file-watcher.js +0 -80
  301. package/dist/resources/extensions/gsd/rtk-status.js +0 -43
  302. package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +0 -1
  303. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +0 -1
  304. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +0 -1
  305. package/src/resources/extensions/gsd/auto-observability.ts +0 -72
  306. package/src/resources/extensions/gsd/file-watcher.ts +0 -100
  307. package/src/resources/extensions/gsd/rtk-status.ts +0 -53
  308. /package/dist/web/standalone/.next/static/{f-Gremw0nLxxFUySaHRPw → KSZ2dcC3p4z6lOmUpPpzr}/_buildManifest.js +0 -0
  309. /package/dist/web/standalone/.next/static/{f-Gremw0nLxxFUySaHRPw → KSZ2dcC3p4z6lOmUpPpzr}/_ssgManifest.js +0 -0
@@ -15,6 +15,7 @@ import {
15
15
  getRequirementById,
16
16
  getActiveDecisions,
17
17
  getActiveRequirements,
18
+ getTask,
18
19
  transaction,
19
20
  _getAdapter,
20
21
  _resetProvider,
@@ -43,6 +44,16 @@ function cleanup(dbPath: string): void {
43
44
  }
44
45
  }
45
46
 
47
+ function withPlatform<T>(platform: NodeJS.Platform, fn: () => T): T {
48
+ const original = process.platform;
49
+ Object.defineProperty(process, 'platform', { value: platform });
50
+ try {
51
+ return fn();
52
+ } finally {
53
+ Object.defineProperty(process, 'platform', { value: original });
54
+ }
55
+ }
56
+
46
57
  // ═══════════════════════════════════════════════════════════════════════════
47
58
  // gsd-db tests
48
59
  // ═══════════════════════════════════════════════════════════════════════════
@@ -279,6 +290,26 @@ describe('gsd-db', () => {
279
290
  cleanup(dbPath);
280
291
  });
281
292
 
293
+ test('gsd-db: mmap stays disabled on darwin file-backed DBs', () => {
294
+ const darwinDbPath = tempDbPath();
295
+ withPlatform('darwin', () => {
296
+ openDatabase(darwinDbPath);
297
+ const adapter = _getAdapter()!;
298
+ const mmap = adapter.prepare('PRAGMA mmap_size').get();
299
+ assert.deepStrictEqual(mmap?.['mmap_size'], 0, 'darwin should leave mmap_size disabled');
300
+ cleanup(darwinDbPath);
301
+ });
302
+
303
+ const linuxDbPath = tempDbPath();
304
+ withPlatform('linux', () => {
305
+ openDatabase(linuxDbPath);
306
+ const adapter = _getAdapter()!;
307
+ const mmap = adapter.prepare('PRAGMA mmap_size').get();
308
+ assert.deepStrictEqual(mmap?.['mmap_size'], 67108864, 'non-darwin should still enable mmap_size');
309
+ cleanup(linuxDbPath);
310
+ });
311
+ });
312
+
282
313
  test('gsd-db: transaction rollback on error', () => {
283
314
  openDatabase(':memory:');
284
315
 
@@ -329,6 +360,79 @@ describe('gsd-db', () => {
329
360
  closeDatabase();
330
361
  });
331
362
 
363
+ test('gsd-db: recreates missing verification evidence dedup index after removing duplicate rows', () => {
364
+ const dbPath = tempDbPath();
365
+ openDatabase(dbPath);
366
+
367
+ let adapter = _getAdapter()!;
368
+ adapter.prepare("INSERT INTO milestones (id, created_at) VALUES (?, '')").run('M001');
369
+ adapter.prepare("INSERT INTO slices (milestone_id, id, created_at) VALUES (?, ?, '')").run('M001', 'S01');
370
+ adapter.prepare("INSERT INTO tasks (milestone_id, slice_id, id) VALUES (?, ?, ?)").run('M001', 'S01', 'T01');
371
+ adapter.exec('DROP INDEX IF EXISTS idx_verification_evidence_dedup');
372
+
373
+ const insertEvidence = adapter.prepare(
374
+ `INSERT INTO verification_evidence (
375
+ task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at
376
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
377
+ );
378
+ insertEvidence.run('T01', 'S01', 'M001', 'npm test', 1, 'fail', 125, '2026-04-12T00:00:00.000Z');
379
+ insertEvidence.run('T01', 'S01', 'M001', 'npm test', 1, 'fail', 125, '2026-04-12T00:00:01.000Z');
380
+ insertEvidence.run('T01', 'S01', 'M001', 'npm run lint', 0, 'pass', 90, '2026-04-12T00:00:02.000Z');
381
+
382
+ closeDatabase();
383
+
384
+ assert.equal(openDatabase(dbPath), true, 'openDatabase should repair legacy duplicate evidence rows');
385
+
386
+ adapter = _getAdapter()!;
387
+ const countRow = adapter.prepare(
388
+ `SELECT count(*) as cnt
389
+ FROM verification_evidence
390
+ WHERE task_id = ? AND slice_id = ? AND milestone_id = ? AND command = ? AND verdict = ?`,
391
+ ).get('T01', 'S01', 'M001', 'npm test', 'fail');
392
+ assert.equal(countRow?.['cnt'], 1, 'duplicate verification evidence rows should be deduplicated before index creation');
393
+
394
+ const indexRow = adapter.prepare(
395
+ "SELECT name FROM sqlite_master WHERE type = 'index' AND name = 'idx_verification_evidence_dedup'",
396
+ ).get();
397
+ assert.equal(indexRow?.['name'], 'idx_verification_evidence_dedup', 'dedup index should be recreated on reopen');
398
+
399
+ cleanup(dbPath);
400
+ });
401
+
402
+ test('gsd-db: rowToTask tolerates legacy comma-separated task arrays', () => {
403
+ openDatabase(':memory:');
404
+
405
+ const adapter = _getAdapter()!;
406
+ adapter.prepare("INSERT INTO milestones (id, created_at) VALUES (?, '')").run('M001');
407
+ adapter.prepare("INSERT INTO slices (milestone_id, id, created_at) VALUES (?, ?, '')").run('M001', 'S01');
408
+ adapter.prepare(
409
+ `INSERT INTO tasks (
410
+ milestone_id, slice_id, id, key_files, key_decisions, files, inputs, expected_output
411
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
412
+ ).run(
413
+ 'M001',
414
+ 'S01',
415
+ 'T01',
416
+ '[]',
417
+ '[]',
418
+ 'tests/test_verify.py, config.yaml, configs/roster_2026-05-11.yaml',
419
+ 'tests/test_verify.py',
420
+ 'reports/summary.md, artifacts/output.json',
421
+ );
422
+
423
+ const task = getTask('M001', 'S01', 'T01');
424
+ assert.ok(task, 'task should load successfully from DB');
425
+ assert.deepEqual(task?.files, [
426
+ 'tests/test_verify.py',
427
+ 'config.yaml',
428
+ 'configs/roster_2026-05-11.yaml',
429
+ ]);
430
+ assert.deepEqual(task?.inputs, ['tests/test_verify.py']);
431
+ assert.deepEqual(task?.expected_output, ['reports/summary.md', 'artifacts/output.json']);
432
+
433
+ closeDatabase();
434
+ });
435
+
332
436
  test('gsd-db: query wrappers return null/empty when DB unavailable', () => {
333
437
  // Ensure DB is closed
334
438
  closeDatabase();
@@ -347,15 +451,13 @@ describe('gsd-db', () => {
347
451
  assert.deepStrictEqual(ar, [], 'getActiveRequirements returns [] when DB closed');
348
452
  });
349
453
 
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)
454
+ test('gsd-db: closeDatabase resets wasDbOpenAttempted after an intentional close', () => {
455
+ openDatabase(':memory:');
353
456
  assert.ok(wasDbOpenAttempted(), 'wasDbOpenAttempted should be true after openDatabase was called');
354
457
 
355
- // Verify the flag persists even after closeDatabase
356
458
  closeDatabase();
357
459
  assert.ok(!isDbAvailable(), 'DB should not be available after close');
358
- assert.ok(wasDbOpenAttempted(), 'wasDbOpenAttempted should remain true after closeDatabase');
460
+ assert.ok(!wasDbOpenAttempted(), 'wasDbOpenAttempted should reset after closeDatabase');
359
461
  });
360
462
 
361
463
  // ─── Final Report ──────────────────────────────────────────────────────────
@@ -248,23 +248,25 @@ describe('git-service', async () => {
248
248
 
249
249
  assert.deepStrictEqual(
250
250
  RUNTIME_EXCLUSION_PATHS.length,
251
- 13,
252
- "exactly 13 runtime exclusion paths"
251
+ 15,
252
+ "exactly 15 runtime exclusion paths"
253
253
  );
254
254
 
255
255
  const expectedPaths = [
256
256
  ".gsd/activity/",
257
+ ".gsd/forensics/",
257
258
  ".gsd/runtime/",
258
259
  ".gsd/worktrees/",
260
+ ".gsd/parallel/",
259
261
  ".gsd/auto.lock",
260
262
  ".gsd/metrics.json",
261
- ".gsd/completed-units.json",
263
+ ".gsd/completed-units*.json",
264
+ ".gsd/state-manifest.json",
262
265
  ".gsd/STATE.md",
263
- ".gsd/gsd.db",
264
- ".gsd/gsd.db-shm",
265
- ".gsd/gsd.db-wal",
266
+ ".gsd/gsd.db*",
266
267
  ".gsd/journal/",
267
268
  ".gsd/doctor-history.jsonl",
269
+ ".gsd/event-log.jsonl",
268
270
  ".gsd/DISCUSSION-MANIFEST.json",
269
271
  ];
270
272
 
@@ -427,3 +427,66 @@ test("formatDoctorFindings shows findings with appropriate icons", () => {
427
427
  assert.ok(output.includes("1 warning"));
428
428
  assert.ok(output.includes("1 fixed"));
429
429
  });
430
+
431
+ // ─── Regression #3891 — alibaba-coding-plan missing from PROVIDER_REGISTRY ───────
432
+ //
433
+ // Before this fix, `alibaba-coding-plan` was not in PROVIDER_REGISTRY, causing
434
+ // `/gsd keys add alibaba-coding-plan` to silently fail (provider not found).
435
+ // alibaba-dashscope is the new standalone provider added in the same PR.
436
+
437
+ test("regression #3891 — alibaba-coding-plan is in PROVIDER_REGISTRY", () => {
438
+ const provider = findProvider("alibaba-coding-plan");
439
+ assert.ok(provider, "alibaba-coding-plan must be in PROVIDER_REGISTRY for /gsd keys add to work");
440
+ assert.equal(provider.id, "alibaba-coding-plan");
441
+ assert.equal(provider.category, "llm");
442
+ assert.equal(provider.envVar, "ALIBABA_API_KEY");
443
+ });
444
+
445
+ test("alibaba-dashscope is in PROVIDER_REGISTRY", () => {
446
+ const provider = findProvider("alibaba-dashscope");
447
+ assert.ok(provider, "alibaba-dashscope must be in PROVIDER_REGISTRY for /gsd keys add to work");
448
+ assert.equal(provider.id, "alibaba-dashscope");
449
+ assert.equal(provider.category, "llm");
450
+ assert.equal(provider.envVar, "DASHSCOPE_API_KEY");
451
+ });
452
+
453
+ test("alibaba-coding-plan and alibaba-dashscope are separate providers (different env vars)", () => {
454
+ const codingPlan = findProvider("alibaba-coding-plan");
455
+ const dashscope = findProvider("alibaba-dashscope");
456
+ assert.ok(codingPlan, "alibaba-coding-plan must exist");
457
+ assert.ok(dashscope, "alibaba-dashscope must exist");
458
+ assert.notEqual(
459
+ codingPlan.envVar,
460
+ dashscope.envVar,
461
+ "alibaba-coding-plan and alibaba-dashscope must use different env vars",
462
+ );
463
+ });
464
+
465
+ test("getAllKeyStatuses includes alibaba-coding-plan", () => {
466
+ const auth = makeAuth();
467
+ const statuses = getAllKeyStatuses(auth);
468
+ const found = statuses.find((s) => s.provider.id === "alibaba-coding-plan");
469
+ assert.ok(found, "getAllKeyStatuses must include alibaba-coding-plan");
470
+ });
471
+
472
+ test("getAllKeyStatuses includes alibaba-dashscope", () => {
473
+ const auth = makeAuth();
474
+ const statuses = getAllKeyStatuses(auth);
475
+ const found = statuses.find((s) => s.provider.id === "alibaba-dashscope");
476
+ assert.ok(found, "getAllKeyStatuses must include alibaba-dashscope");
477
+ });
478
+
479
+ test("getAllKeyStatuses detects DASHSCOPE_API_KEY for alibaba-dashscope (failure path: missing key shows not configured)", () => {
480
+ const saved = process.env.DASHSCOPE_API_KEY;
481
+ delete process.env.DASHSCOPE_API_KEY;
482
+ try {
483
+ const auth = makeAuth();
484
+ const statuses = getAllKeyStatuses(auth);
485
+ const found = statuses.find((s) => s.provider.id === "alibaba-dashscope");
486
+ assert.ok(found);
487
+ assert.equal(found.configured, false);
488
+ assert.equal(found.source, "none");
489
+ } finally {
490
+ if (saved !== undefined) process.env.DASHSCOPE_API_KEY = saved;
491
+ }
492
+ });
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Regression tests for memory pressure monitoring (#3331) and
3
+ * stuck detection persistence (#3704) in auto/loop.ts.
4
+ */
5
+
6
+ import { describe, test } from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { readFileSync } from "node:fs";
9
+ import { join, dirname } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const loopSource = readFileSync(join(__dirname, "..", "auto", "loop.ts"), "utf-8");
14
+
15
+ describe("memory pressure monitoring (#3331)", () => {
16
+ test("checkMemoryPressure function exists", () => {
17
+ assert.match(loopSource, /function checkMemoryPressure/);
18
+ });
19
+
20
+ test("MEMORY_PRESSURE_THRESHOLD constant is defined", () => {
21
+ assert.match(loopSource, /MEMORY_PRESSURE_THRESHOLD\s*=\s*0\.\d+/);
22
+ });
23
+
24
+ test("memory check runs every MEMORY_CHECK_INTERVAL iterations", () => {
25
+ assert.match(loopSource, /iteration\s*%\s*MEMORY_CHECK_INTERVAL\s*===\s*0/);
26
+ });
27
+
28
+ test("memory pressure triggers graceful stopAuto", () => {
29
+ assert.match(loopSource, /mem\.pressured/);
30
+ assert.match(loopSource, /Stopping gracefully to prevent OOM/);
31
+ });
32
+ });
33
+
34
+ describe("stuck detection persistence (#3704)", () => {
35
+ test("loadStuckState function exists", () => {
36
+ assert.match(loopSource, /function loadStuckState/);
37
+ });
38
+
39
+ test("saveStuckState function exists", () => {
40
+ assert.match(loopSource, /function saveStuckState/);
41
+ });
42
+
43
+ test("loopState initialized from persisted state", () => {
44
+ assert.match(loopSource, /loadStuckState\(s\.basePath\)/);
45
+ });
46
+
47
+ test("stuck state saved after each iteration", () => {
48
+ assert.match(loopSource, /saveStuckState\(s\.basePath,\s*loopState\)/);
49
+ });
50
+
51
+ test("stuck state file path uses runtime directory", () => {
52
+ assert.match(loopSource, /stuck-state\.json/);
53
+ });
54
+ });
@@ -0,0 +1,62 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+
7
+ import { verifyExpectedArtifact } from "../auto-recovery.ts";
8
+
9
+ function createFixtureBase(): string {
10
+ const base = mkdtempSync(join(tmpdir(), "gsd-plan-milestone-artifact-"));
11
+ mkdirSync(join(base, ".gsd", "milestones"), { recursive: true });
12
+ return base;
13
+ }
14
+
15
+ function writeRoadmap(base: string, milestoneId: string, content: string): void {
16
+ const milestoneDir = join(base, ".gsd", "milestones", milestoneId);
17
+ mkdirSync(milestoneDir, { recursive: true });
18
+ writeFileSync(join(milestoneDir, `${milestoneId}-ROADMAP.md`), content, "utf-8");
19
+ }
20
+
21
+ test("#3405: plan-milestone roadmap stub does not count as a verified artifact", () => {
22
+ const base = createFixtureBase();
23
+ try {
24
+ writeRoadmap(base, "M001", [
25
+ "# M001: Placeholder",
26
+ "",
27
+ "**Vision:** Stub only.",
28
+ "",
29
+ "## Slices",
30
+ "",
31
+ "_TBD_",
32
+ "",
33
+ ].join("\n"));
34
+
35
+ const result = verifyExpectedArtifact("plan-milestone", "M001", base);
36
+ assert.equal(result, false, "zero-slice roadmap stubs must fail verification");
37
+ } finally {
38
+ rmSync(base, { recursive: true, force: true });
39
+ }
40
+ });
41
+
42
+ test("#3405: plan-milestone roadmap with real slices still passes artifact verification", () => {
43
+ const base = createFixtureBase();
44
+ try {
45
+ writeRoadmap(base, "M001", [
46
+ "# M001: Real roadmap",
47
+ "",
48
+ "**Vision:** Real work.",
49
+ "",
50
+ "## Slices",
51
+ "",
52
+ "- [ ] **S01: First slice** `risk:low` `depends:[]`",
53
+ " > After this: a real slice exists.",
54
+ "",
55
+ ].join("\n"));
56
+
57
+ const result = verifyExpectedArtifact("plan-milestone", "M001", base);
58
+ assert.equal(result, true, "real roadmap slices should keep passing verification");
59
+ } finally {
60
+ rmSync(base, { recursive: true, force: true });
61
+ }
62
+ });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Regression test for #3869: normal post-unit flow should rebuild STATE.md
3
+ * before syncing worktree state back to the project root.
4
+ */
5
+
6
+ import test from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+
11
+ const source = readFileSync(join(import.meta.dirname, "..", "auto-post-unit.ts"), "utf-8");
12
+
13
+ test("auto-post-unit imports rebuildState", () => {
14
+ assert.ok(
15
+ source.includes('import { rebuildState } from "./doctor.js";'),
16
+ "auto-post-unit.ts should import rebuildState from doctor.ts",
17
+ );
18
+ });
19
+
20
+ test("postUnitPreVerification rebuilds STATE.md before worktree sync", () => {
21
+ const fnStart = source.indexOf("export async function postUnitPreVerification");
22
+ assert.ok(fnStart > 0, "postUnitPreVerification should exist");
23
+
24
+ const section = source.slice(fnStart, fnStart + 8000);
25
+ const rebuildIdx = section.indexOf('await runSafely("postUnit", "state-rebuild"');
26
+ const syncIdx = section.indexOf('await runSafely("postUnit", "worktree-sync"');
27
+
28
+ assert.ok(rebuildIdx > 0, "postUnitPreVerification should rebuild STATE.md after unit completion");
29
+ assert.ok(syncIdx > 0, "postUnitPreVerification should sync worktree state back to the project root");
30
+ assert.ok(
31
+ rebuildIdx < syncIdx,
32
+ "STATE.md rebuild should happen before worktree sync so synced state is fresh",
33
+ );
34
+ });
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Tests for formatSkillRef — pure formatting logic for skill references
3
+ * in the system prompt. Moved from preferences-skills.ts to preferences-types.ts
4
+ * to break the preferences ↔ preferences-skills circular dependency.
5
+ */
6
+
7
+ import { describe, test } from "node:test";
8
+ import assert from "node:assert/strict";
9
+
10
+ import { formatSkillRef } from "../preferences-types.ts";
11
+ import type { SkillResolution } from "../preferences-types.ts";
12
+
13
+ function makeResolutions(entries: [string, Partial<SkillResolution>][]): Map<string, SkillResolution> {
14
+ const map = new Map<string, SkillResolution>();
15
+ for (const [key, partial] of entries) {
16
+ map.set(key, {
17
+ original: partial.original ?? key,
18
+ resolvedPath: partial.resolvedPath ?? null,
19
+ method: partial.method ?? "unresolved",
20
+ });
21
+ }
22
+ return map;
23
+ }
24
+
25
+ describe("formatSkillRef", () => {
26
+ test("marks unresolved references with a warning", () => {
27
+ const resolutions = makeResolutions([
28
+ ["my-skill", { method: "unresolved" }],
29
+ ]);
30
+ const result = formatSkillRef("my-skill", resolutions);
31
+ assert.match(result, /my-skill/);
32
+ assert.match(result, /not found/);
33
+ });
34
+
35
+ test("marks unknown references (not in map) with a warning", () => {
36
+ const resolutions = new Map<string, SkillResolution>();
37
+ const result = formatSkillRef("unknown-skill", resolutions);
38
+ assert.match(result, /unknown-skill/);
39
+ assert.match(result, /not found/);
40
+ });
41
+
42
+ test("returns bare ref for absolute-path resolution", () => {
43
+ const resolutions = makeResolutions([
44
+ ["/home/user/skills/SKILL.md", {
45
+ method: "absolute-path",
46
+ resolvedPath: "/home/user/skills/SKILL.md",
47
+ }],
48
+ ]);
49
+ const result = formatSkillRef("/home/user/skills/SKILL.md", resolutions);
50
+ assert.equal(result, "/home/user/skills/SKILL.md");
51
+ });
52
+
53
+ test("returns bare ref for absolute-dir resolution", () => {
54
+ const resolutions = makeResolutions([
55
+ ["/home/user/skills/my-skill", {
56
+ method: "absolute-dir",
57
+ resolvedPath: "/home/user/skills/my-skill/SKILL.md",
58
+ }],
59
+ ]);
60
+ const result = formatSkillRef("/home/user/skills/my-skill", resolutions);
61
+ assert.equal(result, "/home/user/skills/my-skill");
62
+ });
63
+
64
+ test("shows resolved path for user-skill resolution", () => {
65
+ const resolutions = makeResolutions([
66
+ ["code-review", {
67
+ method: "user-skill",
68
+ resolvedPath: "/home/user/.claude/skills/code-review/SKILL.md",
69
+ }],
70
+ ]);
71
+ const result = formatSkillRef("code-review", resolutions);
72
+ assert.match(result, /code-review/);
73
+ assert.match(result, /\.claude\/skills\/code-review\/SKILL\.md/);
74
+ });
75
+
76
+ test("shows resolved path for project-skill resolution", () => {
77
+ const resolutions = makeResolutions([
78
+ ["lint-fix", {
79
+ method: "project-skill",
80
+ resolvedPath: "/repo/.gsd/skills/lint-fix/SKILL.md",
81
+ }],
82
+ ]);
83
+ const result = formatSkillRef("lint-fix", resolutions);
84
+ assert.match(result, /lint-fix/);
85
+ assert.match(result, /\.gsd\/skills\/lint-fix\/SKILL\.md/);
86
+ });
87
+ });
@@ -10,10 +10,14 @@
10
10
 
11
11
  import test from "node:test";
12
12
  import assert from "node:assert/strict";
13
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
14
+ import { tmpdir } from "node:os";
15
+ import { join } from "node:path";
13
16
  import {
14
17
  validatePreferences,
15
18
  applyModeDefaults,
16
19
  getIsolationMode,
20
+ loadEffectiveGSDPreferences,
17
21
  parsePreferencesMarkdown,
18
22
  _resetParseWarningFlag,
19
23
  } from "../preferences.ts";
@@ -501,6 +505,55 @@ test("experimental.rtk parses correctly from preferences markdown", () => {
501
505
  assert.equal(prefs!.experimental?.rtk, true);
502
506
  });
503
507
 
508
+ test("loadEffectiveGSDPreferences preserves experimental prefs across global+project merge", () => {
509
+ const originalCwd = process.cwd();
510
+ const originalGsdHome = process.env.GSD_HOME;
511
+ const tempProject = mkdtempSync(join(tmpdir(), "gsd-prefs-project-"));
512
+ const tempGsdHome = mkdtempSync(join(tmpdir(), "gsd-prefs-home-"));
513
+
514
+ try {
515
+ mkdirSync(join(tempProject, ".gsd"), { recursive: true });
516
+
517
+ writeFileSync(
518
+ join(tempGsdHome, "preferences.md"),
519
+ [
520
+ "---",
521
+ "version: 1",
522
+ "experimental:",
523
+ " rtk: true",
524
+ "---",
525
+ ].join("\n"),
526
+ "utf-8",
527
+ );
528
+
529
+ writeFileSync(
530
+ join(tempProject, ".gsd", "PREFERENCES.md"),
531
+ [
532
+ "---",
533
+ "version: 1",
534
+ "git:",
535
+ " isolation: none",
536
+ "---",
537
+ ].join("\n"),
538
+ "utf-8",
539
+ );
540
+
541
+ process.env.GSD_HOME = tempGsdHome;
542
+ process.chdir(tempProject);
543
+
544
+ const loaded = loadEffectiveGSDPreferences();
545
+ assert.notEqual(loaded, null);
546
+ assert.equal(loaded!.preferences.experimental?.rtk, true);
547
+ assert.equal(loaded!.preferences.git?.isolation, "none");
548
+ } finally {
549
+ process.chdir(originalCwd);
550
+ if (originalGsdHome === undefined) delete process.env.GSD_HOME;
551
+ else process.env.GSD_HOME = originalGsdHome;
552
+ rmSync(tempProject, { recursive: true, force: true });
553
+ rmSync(tempGsdHome, { recursive: true, force: true });
554
+ }
555
+ });
556
+
504
557
  test("experimental.rtk defaults to off in new project preferences", () => {
505
558
  // No experimental key → feature is disabled
506
559
  const content = "---\nversion: 1\n---\n";
@@ -5,7 +5,7 @@
5
5
  import test from 'node:test';
6
6
  import assert from 'node:assert/strict';
7
7
 
8
- import { renderPlanContent, renderRoadmapContent } from '../workflow-projections.ts';
8
+ import { renderPlanContent, renderRoadmapContent, renderSummaryContent } from '../workflow-projections.ts';
9
9
  import type { SliceRow, TaskRow } from '../gsd-db.ts';
10
10
 
11
11
  // ─── Helpers ─────────────────────────────────────────────────────────────
@@ -172,3 +172,98 @@ test('renderRoadmapContent: slice with status "pending" shows ⬜', () => {
172
172
 
173
173
  assert.ok(content.includes('⬜'), 'pending slice should show ⬜');
174
174
  });
175
+
176
+ // ─── renderSummaryContent: double-frontmatter regression ─────────────────
177
+
178
+ test('renderSummaryContent: uses full_summary_md as-is when it contains frontmatter', () => {
179
+ const existingSummary = [
180
+ '---',
181
+ 'id: T01',
182
+ 'parent: S01',
183
+ 'milestone: M001',
184
+ 'key_files:',
185
+ ' - src/thing.ts',
186
+ 'verification_result: passed',
187
+ 'completed_at: 2026-01-01T00:00:00Z',
188
+ 'blocker_discovered: false',
189
+ '---',
190
+ '',
191
+ '# T01: Did the thing',
192
+ '',
193
+ '**One-liner summary**',
194
+ '',
195
+ '## What Happened',
196
+ '',
197
+ 'Narrative content here.',
198
+ '',
199
+ '## Deviations',
200
+ '',
201
+ 'None.',
202
+ '',
203
+ ].join('\n');
204
+
205
+ const task = makeTaskRow({
206
+ id: 'T01',
207
+ status: 'complete',
208
+ title: 'Did the thing',
209
+ one_liner: 'One-liner summary',
210
+ narrative: 'Narrative content here.',
211
+ full_summary_md: existingSummary,
212
+ });
213
+
214
+ const result = renderSummaryContent(task, 'S01', 'M001');
215
+
216
+ // Must NOT produce double frontmatter
217
+ const frontmatterCount = (result.match(/^---$/gm) || []).length;
218
+ assert.equal(frontmatterCount, 2, `Expected exactly 2 frontmatter delimiters (one block), got ${frontmatterCount}`);
219
+
220
+ // Must NOT produce double H1 heading
221
+ const h1Count = (result.match(/^# T01:/gm) || []).length;
222
+ assert.equal(h1Count, 1, `Expected exactly 1 H1 heading, got ${h1Count}`);
223
+
224
+ // Content should match the full_summary_md exactly
225
+ assert.equal(result, existingSummary);
226
+ });
227
+
228
+ test('renderSummaryContent: synthesizes from DB columns when full_summary_md is empty', () => {
229
+ const task = makeTaskRow({
230
+ id: 'T01',
231
+ status: 'complete',
232
+ title: 'Did the thing',
233
+ one_liner: 'One-liner summary',
234
+ narrative: 'Built the feature.',
235
+ full_summary_md: '',
236
+ deviations: 'Deviated slightly.',
237
+ known_issues: 'None.',
238
+ });
239
+
240
+ const result = renderSummaryContent(task, 'S01', 'M001');
241
+
242
+ // Should have exactly one frontmatter block
243
+ const frontmatterCount = (result.match(/^---$/gm) || []).length;
244
+ assert.equal(frontmatterCount, 2, 'Should have one frontmatter block (2 delimiters)');
245
+
246
+ // Should contain synthesized sections
247
+ assert.ok(result.includes('## What Happened'), 'Should have What Happened section');
248
+ assert.ok(result.includes('Built the feature.'), 'Should use narrative for content');
249
+ assert.ok(result.includes('## Deviations'), 'Should have Deviations section');
250
+ assert.ok(result.includes('Deviated slightly.'), 'Should include deviation text');
251
+ });
252
+
253
+ test('renderSummaryContent: synthesizes when full_summary_md has no frontmatter', () => {
254
+ const task = makeTaskRow({
255
+ id: 'T02',
256
+ status: 'complete',
257
+ title: 'Partial summary',
258
+ narrative: 'Did some work.',
259
+ full_summary_md: 'Just a plain text summary with no frontmatter.',
260
+ });
261
+
262
+ const result = renderSummaryContent(task, 'S01', 'M001');
263
+
264
+ // Should synthesize with proper frontmatter since the stored md lacks it
265
+ assert.ok(result.startsWith('---'), 'Should start with frontmatter');
266
+ assert.ok(result.includes('id: T02'), 'Should have task ID in frontmatter');
267
+ assert.ok(result.includes('## What Happened'), 'Should have What Happened section');
268
+ assert.ok(result.includes('Did some work.'), 'Should use narrative');
269
+ });