gsd-pi 2.78.0-dev.aeeb2ca00 → 2.78.1-dev.84a383f51

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 (383) hide show
  1. package/README.md +7 -7
  2. package/dist/claude-cli-check.js +64 -37
  3. package/dist/cli-policy.d.ts +13 -0
  4. package/dist/cli-policy.js +17 -0
  5. package/dist/cli.js +95 -55
  6. package/dist/headless-query.d.ts +22 -0
  7. package/dist/headless-query.js +24 -4
  8. package/dist/headless.d.ts +10 -0
  9. package/dist/headless.js +16 -1
  10. package/dist/loader.js +7 -10
  11. package/dist/onboarding.d.ts +10 -0
  12. package/dist/onboarding.js +2 -2
  13. package/dist/provider-migrations.d.ts +2 -2
  14. package/dist/provider-migrations.js +5 -2
  15. package/dist/resource-loader.d.ts +5 -2
  16. package/dist/resource-loader.js +28 -5
  17. package/dist/resources/.managed-resources-content-hash +1 -0
  18. package/dist/resources/extensions/claude-code-cli/readiness.js +77 -45
  19. package/dist/resources/extensions/gsd/auto/loop.js +23 -0
  20. package/dist/resources/extensions/gsd/auto/phases.js +2 -2
  21. package/dist/resources/extensions/gsd/auto/run-unit.js +3 -1
  22. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  23. package/dist/resources/extensions/gsd/auto-recovery.js +43 -4
  24. package/dist/resources/extensions/gsd/auto-runtime-state.js +31 -0
  25. package/dist/resources/extensions/gsd/auto-start.js +1 -1
  26. package/dist/resources/extensions/gsd/auto-tool-tracking.js +2 -2
  27. package/dist/resources/extensions/gsd/auto-worktree.js +30 -0
  28. package/dist/resources/extensions/gsd/auto.js +14 -5
  29. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +14 -2
  30. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +7 -5
  31. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +2 -2
  32. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +5 -4
  33. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +94 -31
  34. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +11 -6
  35. package/dist/resources/extensions/gsd/bootstrap/system-context.js +34 -8
  36. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +38 -2
  37. package/dist/resources/extensions/gsd/commands/catalog.js +69 -5
  38. package/dist/resources/extensions/gsd/commands/handlers/core.js +22 -1
  39. package/dist/resources/extensions/gsd/commands-mcp-status.js +3 -1
  40. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +10 -1
  41. package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -1
  42. package/dist/resources/extensions/gsd/docs/preferences-reference.md +4 -0
  43. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +39 -1
  44. package/dist/resources/extensions/gsd/error-classifier.js +1 -1
  45. package/dist/resources/extensions/gsd/forensics.js +2 -2
  46. package/dist/resources/extensions/gsd/git-service.js +12 -5
  47. package/dist/resources/extensions/gsd/gsd-db.js +11 -2
  48. package/dist/resources/extensions/gsd/guided-flow.js +23 -23
  49. package/dist/resources/extensions/gsd/memory-store.js +66 -31
  50. package/dist/resources/extensions/gsd/milestone-id-reservation.js +36 -0
  51. package/dist/resources/extensions/gsd/model-router.js +114 -9
  52. package/dist/resources/extensions/gsd/native-git-bridge.js +7 -1
  53. package/dist/resources/extensions/gsd/preferences-models.js +91 -15
  54. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  55. package/dist/resources/extensions/gsd/preferences-validation.js +32 -0
  56. package/dist/resources/extensions/gsd/preferences.js +5 -3
  57. package/dist/resources/extensions/gsd/prompt-loader.js +23 -12
  58. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +9 -3
  59. package/dist/resources/extensions/gsd/state.js +42 -0
  60. package/dist/resources/extensions/gsd/templates/PREFERENCES.md +1 -0
  61. package/dist/resources/extensions/gsd/tools/memory-tools.js +18 -1
  62. package/dist/resources/extensions/gsd/visualizer-overlay.js +1 -1
  63. package/dist/resources/extensions/gsd/watch/header-renderer.js +3 -1
  64. package/dist/resources/extensions/gsd/worktree-command.js +26 -46
  65. package/dist/resources/extensions/gsd/worktree-session-state.js +33 -0
  66. package/dist/resources/extensions/mcp-client/index.js +6 -3
  67. package/dist/resources/extensions/slash-commands/create-extension.js +36 -22
  68. package/dist/resources/skills/create-gsd-extension/SKILL.md +9 -5
  69. package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +1 -1
  70. package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +5 -5
  71. package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +4 -4
  72. package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +6 -6
  73. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +3 -3
  74. package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +1 -1
  75. package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +3 -3
  76. package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +32 -12
  77. package/dist/rtk-shared.d.ts +3 -0
  78. package/dist/rtk-shared.js +17 -0
  79. package/dist/rtk.d.ts +2 -5
  80. package/dist/rtk.js +3 -20
  81. package/dist/runtime-checks.d.ts +27 -0
  82. package/dist/runtime-checks.js +38 -0
  83. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  84. package/dist/web/standalone/.next/BUILD_ID +1 -1
  85. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  86. package/dist/web/standalone/.next/build-manifest.json +3 -3
  87. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  88. package/dist/web/standalone/.next/react-loadable-manifest.json +44 -4
  89. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/session/events/route.js +4 -2
  110. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/index.html +1 -1
  112. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  113. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  114. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  115. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  120. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  122. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  124. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  125. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  126. package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
  127. package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +1 -0
  128. package/dist/web/standalone/.next/static/chunks/2824.08296bc2f9654698.js +1 -0
  129. package/dist/web/standalone/.next/static/chunks/3026.3af53b279375f082.js +1 -0
  130. package/dist/web/standalone/.next/static/chunks/315.6f68ae79b67d25cf.js +1 -0
  131. package/dist/web/standalone/.next/static/chunks/3497.4bfc60a3b3dea717.js +1 -0
  132. package/dist/web/standalone/.next/static/chunks/5516.4a07c872b5c3a663.js +1 -0
  133. package/dist/web/standalone/.next/static/chunks/8336.31b019697882acfb.js +10 -0
  134. package/dist/web/standalone/.next/static/chunks/8845.c9702695e8c5a9c5.js +2 -0
  135. package/dist/web/standalone/.next/static/chunks/9058.01ef3a463bda88f1.js +20 -0
  136. package/dist/web/standalone/.next/static/chunks/9441.1081da1125d1764f.js +1 -0
  137. package/dist/web/standalone/.next/static/chunks/app/{page-5b113fd32bc2a1c3.js → page-9bf2e0c50fb2ca05.js} +1 -1
  138. package/dist/web/standalone/.next/static/chunks/webpack-f9f0dc45e4f3ac10.js +1 -0
  139. package/dist/web/standalone/package.json +2 -1
  140. package/dist/worktree-status-banner.d.ts +1 -0
  141. package/dist/worktree-status-banner.js +132 -0
  142. package/package.json +1 -1
  143. package/packages/daemon/package.json +2 -2
  144. package/packages/mcp-server/dist/alias-telemetry.d.ts +8 -0
  145. package/packages/mcp-server/dist/alias-telemetry.d.ts.map +1 -0
  146. package/packages/mcp-server/dist/alias-telemetry.js +30 -0
  147. package/packages/mcp-server/dist/alias-telemetry.js.map +1 -0
  148. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  149. package/packages/mcp-server/dist/workflow-tools.js +74 -46
  150. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  151. package/packages/mcp-server/package.json +2 -2
  152. package/packages/mcp-server/src/alias-telemetry.test.ts +78 -0
  153. package/packages/mcp-server/src/alias-telemetry.ts +30 -0
  154. package/packages/mcp-server/src/workflow-tools.test.ts +26 -0
  155. package/packages/mcp-server/src/workflow-tools.ts +93 -58
  156. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  157. package/packages/native/package.json +1 -1
  158. package/packages/pi-agent-core/package.json +1 -1
  159. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  160. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.d.ts +2 -0
  161. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.d.ts.map +1 -0
  162. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.js +231 -0
  163. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.js.map +1 -0
  164. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  165. package/packages/pi-ai/dist/providers/anthropic-shared.js +48 -19
  166. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  167. package/packages/pi-ai/dist/types.d.ts +13 -0
  168. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  169. package/packages/pi-ai/dist/types.js.map +1 -1
  170. package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
  171. package/packages/pi-ai/dist/utils/repair-tool-json.js +24 -3
  172. package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
  173. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +26 -0
  174. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
  175. package/packages/pi-ai/package.json +1 -1
  176. package/packages/pi-ai/src/providers/anthropic-shared.cache-breakpoint.test.ts +289 -0
  177. package/packages/pi-ai/src/providers/anthropic-shared.ts +52 -20
  178. package/packages/pi-ai/src/types.ts +13 -0
  179. package/packages/pi-ai/src/utils/repair-tool-json.ts +24 -3
  180. package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +32 -0
  181. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  182. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  183. package/packages/pi-coding-agent/dist/core/agent-session.js +6 -0
  184. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  185. package/packages/pi-coding-agent/dist/core/messages.d.ts.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/messages.js +4 -0
  187. package/packages/pi-coding-agent/dist/core/messages.js.map +1 -1
  188. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +19 -2
  189. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  190. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +10 -0
  191. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  192. package/packages/pi-coding-agent/dist/core/model-registry.js +18 -0
  193. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  194. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +13 -0
  195. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  196. package/packages/pi-coding-agent/dist/core/system-prompt.js +20 -16
  197. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  198. package/packages/pi-coding-agent/dist/core/token-telemetry.d.ts +37 -0
  199. package/packages/pi-coding-agent/dist/core/token-telemetry.d.ts.map +1 -0
  200. package/packages/pi-coding-agent/dist/core/token-telemetry.js +49 -0
  201. package/packages/pi-coding-agent/dist/core/token-telemetry.js.map +1 -0
  202. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.d.ts +2 -0
  203. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.d.ts.map +1 -0
  204. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +133 -0
  205. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -0
  206. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +1 -1
  207. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  208. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +14 -1
  209. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  210. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.d.ts +2 -0
  211. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.d.ts.map +1 -0
  212. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.js +78 -0
  213. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.js.map +1 -0
  214. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.d.ts +2 -0
  215. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.d.ts.map +1 -0
  216. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.js +181 -0
  217. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.js.map +1 -0
  218. package/packages/pi-coding-agent/package.json +1 -1
  219. package/packages/pi-coding-agent/src/core/agent-session.ts +7 -0
  220. package/packages/pi-coding-agent/src/core/messages.ts +4 -0
  221. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +32 -2
  222. package/packages/pi-coding-agent/src/core/model-registry.ts +21 -0
  223. package/packages/pi-coding-agent/src/core/system-prompt.ts +33 -15
  224. package/packages/pi-coding-agent/src/core/token-telemetry.ts +77 -0
  225. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +212 -0
  226. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +17 -1
  227. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +1 -1
  228. package/packages/pi-coding-agent/src/tests/system-prompt-cache-stability.test.ts +102 -0
  229. package/packages/pi-coding-agent/src/tests/token-telemetry.test.ts +200 -0
  230. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  231. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +17 -3
  232. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  233. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.d.ts +2 -0
  234. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.d.ts.map +1 -0
  235. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.js +161 -0
  236. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.js.map +1 -0
  237. package/packages/pi-tui/package.json +1 -1
  238. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +20 -3
  239. package/packages/pi-tui/src/components/__tests__/leak-fixes-runtime.test.ts +219 -0
  240. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  241. package/packages/rpc-client/package.json +1 -1
  242. package/pkg/package.json +1 -1
  243. package/src/resources/extensions/claude-code-cli/readiness.ts +78 -46
  244. package/src/resources/extensions/gsd/auto/loop.ts +24 -2
  245. package/src/resources/extensions/gsd/auto/phases.ts +3 -3
  246. package/src/resources/extensions/gsd/auto/run-unit.ts +3 -1
  247. package/src/resources/extensions/gsd/auto/session.ts +3 -0
  248. package/src/resources/extensions/gsd/auto/types.ts +1 -0
  249. package/src/resources/extensions/gsd/auto-recovery.ts +46 -8
  250. package/src/resources/extensions/gsd/auto-runtime-state.ts +51 -0
  251. package/src/resources/extensions/gsd/auto-start.ts +1 -1
  252. package/src/resources/extensions/gsd/auto-tool-tracking.ts +2 -4
  253. package/src/resources/extensions/gsd/auto-worktree.ts +38 -0
  254. package/src/resources/extensions/gsd/auto.ts +14 -4
  255. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +15 -13
  256. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +8 -7
  257. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +2 -2
  258. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +10 -9
  259. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +102 -31
  260. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +12 -6
  261. package/src/resources/extensions/gsd/bootstrap/system-context.ts +39 -8
  262. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +39 -11
  263. package/src/resources/extensions/gsd/commands/catalog.ts +75 -5
  264. package/src/resources/extensions/gsd/commands/handlers/core.ts +22 -1
  265. package/src/resources/extensions/gsd/commands-mcp-status.ts +3 -1
  266. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +15 -1
  267. package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -1
  268. package/src/resources/extensions/gsd/docs/preferences-reference.md +4 -0
  269. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +39 -1
  270. package/src/resources/extensions/gsd/doctor-types.ts +3 -1
  271. package/src/resources/extensions/gsd/error-classifier.ts +1 -1
  272. package/src/resources/extensions/gsd/forensics.ts +2 -2
  273. package/src/resources/extensions/gsd/git-service.ts +13 -5
  274. package/src/resources/extensions/gsd/gsd-db.ts +12 -2
  275. package/src/resources/extensions/gsd/guided-flow.ts +25 -25
  276. package/src/resources/extensions/gsd/memory-store.ts +81 -28
  277. package/src/resources/extensions/gsd/milestone-id-reservation.ts +47 -0
  278. package/src/resources/extensions/gsd/model-router.ts +172 -9
  279. package/src/resources/extensions/gsd/native-git-bridge.ts +7 -1
  280. package/src/resources/extensions/gsd/preferences-models.ts +101 -15
  281. package/src/resources/extensions/gsd/preferences-types.ts +6 -0
  282. package/src/resources/extensions/gsd/preferences-validation.ts +35 -0
  283. package/src/resources/extensions/gsd/preferences.ts +16 -2
  284. package/src/resources/extensions/gsd/prompt-loader.ts +26 -12
  285. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +9 -3
  286. package/src/resources/extensions/gsd/state.ts +42 -0
  287. package/src/resources/extensions/gsd/templates/PREFERENCES.md +1 -0
  288. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +178 -1
  289. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +58 -0
  290. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +9 -5
  291. package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +21 -4
  292. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +1 -1
  293. package/src/resources/extensions/gsd/tests/budget-prediction.test.ts +138 -211
  294. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +142 -59
  295. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +7 -4
  296. package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +89 -32
  297. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +41 -23
  298. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +3 -43
  299. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +5 -3
  300. package/src/resources/extensions/gsd/tests/deferred-milestone-dir-4996.test.ts +116 -0
  301. package/src/resources/extensions/gsd/tests/discuss-empty-db-fallback.test.ts +22 -87
  302. package/src/resources/extensions/gsd/tests/discuss-queued-milestones.test.ts +7 -118
  303. package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +18 -60
  304. package/src/resources/extensions/gsd/tests/doctor-orphan-milestone-4996.test.ts +100 -0
  305. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +14 -76
  306. package/src/resources/extensions/gsd/tests/ensure-preconditions-guard-4996.test.ts +93 -0
  307. package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +22 -83
  308. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +1 -63
  309. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed-runtime.test.ts +47 -0
  310. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +26 -1
  311. package/src/resources/extensions/gsd/tests/gitignore-bg-shell-runtime.test.ts +63 -0
  312. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +30 -0
  313. package/src/resources/extensions/gsd/tests/gsd-no-project-error-runtime.test.ts +81 -0
  314. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +14 -4
  315. package/src/resources/extensions/gsd/tests/health-widget.test.ts +22 -12
  316. package/src/resources/extensions/gsd/tests/help-menu-coverage.test.ts +57 -0
  317. package/src/resources/extensions/gsd/tests/import-done-milestones-runtime.test.ts +145 -0
  318. package/src/resources/extensions/gsd/tests/init-prefs-routing.test.ts +64 -1
  319. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +22 -0
  320. package/src/resources/extensions/gsd/tests/integration/token-savings.test.ts +0 -23
  321. package/src/resources/extensions/gsd/tests/memory-store.test.ts +128 -0
  322. package/src/resources/extensions/gsd/tests/memory-tools.test.ts +33 -1
  323. package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +124 -0
  324. package/src/resources/extensions/gsd/tests/milestone-id-gap-reuse-4996.test.ts +152 -0
  325. package/src/resources/extensions/gsd/tests/model-router.test.ts +169 -8
  326. package/src/resources/extensions/gsd/tests/native-git-infra-errors.test.ts +50 -0
  327. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +8 -0
  328. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +32 -43
  329. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +4 -10
  330. package/src/resources/extensions/gsd/tests/preferences.test.ts +127 -0
  331. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +16 -0
  332. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +7 -0
  333. package/src/resources/extensions/gsd/tests/quick-turn-end-cleanup.test.ts +6 -6
  334. package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +93 -0
  335. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +168 -19
  336. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +7 -1
  337. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +23 -1
  338. package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +101 -0
  339. package/src/resources/extensions/gsd/tests/token-profile.test.ts +51 -4
  340. package/src/resources/extensions/gsd/tests/turn-epoch.test.ts +7 -16
  341. package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +5 -7
  342. package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +15 -1
  343. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -6
  344. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -0
  345. package/src/resources/extensions/gsd/tools/memory-tools.ts +17 -1
  346. package/src/resources/extensions/gsd/unit-context-manifest.ts +8 -8
  347. package/src/resources/extensions/gsd/visualizer-overlay.ts +1 -1
  348. package/src/resources/extensions/gsd/watch/header-renderer.ts +3 -1
  349. package/src/resources/extensions/gsd/workflow-logger.ts +1 -0
  350. package/src/resources/extensions/gsd/worktree-command.ts +31 -44
  351. package/src/resources/extensions/gsd/worktree-session-state.ts +35 -0
  352. package/src/resources/extensions/mcp-client/index.ts +6 -3
  353. package/src/resources/extensions/mcp-client/tests/global-config.test.ts +91 -0
  354. package/src/resources/extensions/slash-commands/create-extension.ts +38 -24
  355. package/src/resources/skills/create-gsd-extension/SKILL.md +9 -5
  356. package/src/resources/skills/create-gsd-extension/references/custom-commands.md +1 -1
  357. package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +5 -5
  358. package/src/resources/skills/create-gsd-extension/references/custom-tools.md +4 -4
  359. package/src/resources/skills/create-gsd-extension/references/custom-ui.md +6 -6
  360. package/src/resources/skills/create-gsd-extension/references/events-reference.md +3 -3
  361. package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +1 -1
  362. package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +3 -3
  363. package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +2 -2
  364. package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +3 -3
  365. package/src/resources/skills/create-gsd-extension/templates/templates.test.ts +58 -0
  366. package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +32 -12
  367. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +0 -601
  368. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +0 -651
  369. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +0 -91
  370. package/dist/resources/extensions/gsd/tests/auto-supervisor.test.mjs +0 -53
  371. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +0 -112
  372. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +0 -23
  373. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +0 -5
  374. package/dist/resources/skills/github-workflows/references/gh/tests/__init__.py +0 -0
  375. package/dist/resources/skills/github-workflows/references/gh/tests/test_github_project_setup.py +0 -608
  376. package/dist/web/standalone/.next/static/chunks/2826.e9f5195e91f9cad2.js +0 -11
  377. package/dist/web/standalone/.next/static/chunks/3621.fc7480022c972438.js +0 -20
  378. package/dist/web/standalone/.next/static/chunks/webpack-2e68521d7c82f7c2.js +0 -1
  379. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +0 -22
  380. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +0 -47
  381. package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +0 -75
  382. /package/dist/web/standalone/.next/static/{cAJH99yNS1UPbeSEiNRrV → UF5VF4F1tB0miEtJS7LyX}/_buildManifest.js +0 -0
  383. /package/dist/web/standalone/.next/static/{cAJH99yNS1UPbeSEiNRrV → UF5VF4F1tB0miEtJS7LyX}/_ssgManifest.js +0 -0
@@ -1,104 +1,43 @@
1
1
  /**
2
- * false-degraded-mode-warning.test.ts Regression tests for #3922.
2
+ * Behavioural regression tests for #3922.
3
3
  *
4
- * Before this fix, deriveState() logged a "DB unavailable — degraded mode"
5
- * warning even when the DB simply hadn't been opened yet (e.g. during
6
- * before_agent_start context injection). The fix introduces wasDbOpenAttempted()
7
- * to distinguish "not yet initialized" from "genuinely unavailable."
4
+ * Before this fix, deriveState() logged "DB unavailable — degraded mode"
5
+ * even when the DB had not been opened yet (e.g. during
6
+ * before_agent_start context injection). The fix introduced
7
+ * wasDbOpenAttempted() so the warning fires only after a real open attempt.
8
8
  *
9
- * Two aspects:
10
- * 1. gsd-db: wasDbOpenAttempted() tracks whether openDatabase() was ever called.
11
- * 2. state: the degraded-mode warning is gated behind wasDbOpenAttempted().
9
+ * The earlier tests source-grepped state.ts for the warning string and the
10
+ * preceding line (POSITIONAL/SOURCE_GREP per #4826/#4829). They are
11
+ * replaced here with direct calls to the wasDbOpenAttempted flag.
12
12
  */
13
13
 
14
- import { describe, test } from "node:test";
14
+ import { describe, test, beforeEach, afterEach } from "node:test";
15
15
  import assert from "node:assert/strict";
16
- import { readFileSync } from "node:fs";
17
- import { dirname, join } from "node:path";
18
- import { fileURLToPath } from "node:url";
19
16
  import {
20
17
  openDatabase,
21
18
  closeDatabase,
22
- isDbAvailable,
23
19
  wasDbOpenAttempted,
24
20
  } from "../gsd-db.ts";
25
21
 
26
- const __dirname = dirname(fileURLToPath(import.meta.url));
27
- const stateSource = readFileSync(join(__dirname, "..", "state.ts"), "utf-8");
28
-
29
- // ═══════════════════════════════════════════════════════════════════════════
30
- // 1. gsd-db: wasDbOpenAttempted flag
31
- // ═══════════════════════════════════════════════════════════════════════════
32
-
33
22
  describe("wasDbOpenAttempted (#3922)", () => {
23
+ beforeEach(() => { closeDatabase(); });
24
+ afterEach(() => { closeDatabase(); });
34
25
 
35
- test("wasDbOpenAttempted returns true after openDatabase is called", () => {
36
- // By this point in the test suite, openDatabase may or may not have been
37
- // called by other tests. So we call it explicitly and verify it returns true.
26
+ test("returns true after a successful openDatabase call", () => {
38
27
  openDatabase(":memory:");
39
- assert.strictEqual(wasDbOpenAttempted(), true,
40
- "wasDbOpenAttempted should be true after openDatabase call");
41
- closeDatabase();
42
- });
43
-
44
- test("openDatabase sets the flag even if it fails on invalid path", () => {
45
- // openDatabase with an unreachable path may fail, but the flag should
46
- // still be set because the attempt was made.
47
- try { openDatabase("/nonexistent/path/that/will/fail.db"); } catch { /* expected */ }
48
- assert.strictEqual(wasDbOpenAttempted(), true,
49
- "wasDbOpenAttempted should be true even after a failed open attempt");
50
- });
51
- });
52
-
53
- // ═══════════════════════════════════════════════════════════════════════════
54
- // 2. state.ts: degraded-mode warning is gated behind wasDbOpenAttempted
55
- // ═══════════════════════════════════════════════════════════════════════════
56
-
57
- describe("degraded-mode warning guard (#3922)", () => {
58
-
59
- test("state.ts imports wasDbOpenAttempted from gsd-db", () => {
60
- assert.ok(
61
- stateSource.includes("wasDbOpenAttempted"),
62
- "state.ts must import wasDbOpenAttempted to gate the degraded-mode warning",
28
+ assert.strictEqual(
29
+ wasDbOpenAttempted(),
30
+ true,
31
+ "wasDbOpenAttempted should report true after openDatabase succeeds",
63
32
  );
64
33
  });
65
34
 
66
- test("degraded-mode warning is inside a wasDbOpenAttempted() guard", () => {
67
- // Find the degraded-mode warning string
68
- const warningStr = 'DB unavailable — using filesystem state derivation (degraded mode)';
69
- const warningIdx = stateSource.indexOf(warningStr);
70
- assert.ok(warningIdx > 0, "degraded-mode warning string must exist in state.ts");
71
-
72
- // The wasDbOpenAttempted() check must appear BEFORE the warning,
73
- // within the same else-branch (i.e. within a reasonable distance).
74
- // Look backwards from the warning for the guard.
75
- const searchWindow = stateSource.slice(Math.max(0, warningIdx - 300), warningIdx);
76
- assert.ok(
77
- searchWindow.includes("wasDbOpenAttempted()"),
78
- "wasDbOpenAttempted() guard must appear shortly before the degraded-mode warning " +
79
- "to prevent false warnings when DB has not been initialized yet",
35
+ test("remains true after a failed open attempt — the attempt is what matters", () => {
36
+ try { openDatabase("/nonexistent/path/that/will/fail.db"); } catch { /* expected */ }
37
+ assert.strictEqual(
38
+ wasDbOpenAttempted(),
39
+ true,
40
+ "wasDbOpenAttempted should be true even when openDatabase throws — the warning gate keys on the attempt, not the outcome",
80
41
  );
81
42
  });
82
-
83
- test("warning is NOT emitted unconditionally in the else branch", () => {
84
- // The old code had `logWarning(...)` directly in the else branch.
85
- // The fix wraps it in `if (wasDbOpenAttempted())`.
86
- // Verify the logWarning call is inside a conditional, not bare.
87
- const lines = stateSource.split("\n");
88
- for (let i = 0; i < lines.length; i++) {
89
- if (lines[i]!.includes("DB unavailable") && lines[i]!.includes("degraded mode")) {
90
- // This line has the warning. Check that the preceding non-empty line
91
- // contains an if-condition (wasDbOpenAttempted), not a bare else.
92
- let prev = i - 1;
93
- while (prev >= 0 && lines[prev]!.trim() === "") prev--;
94
- const prevLine = lines[prev]!.trim();
95
- assert.ok(
96
- prevLine.includes("wasDbOpenAttempted"),
97
- `Line ${i + 1} emits degraded-mode warning — preceding line ${prev + 1} must ` +
98
- `contain wasDbOpenAttempted guard, but found: "${prevLine}"`,
99
- );
100
- break;
101
- }
102
- }
103
- });
104
43
  });
@@ -17,12 +17,7 @@
17
17
  */
18
18
 
19
19
  import { createTestContext } from "./test-helpers.ts";
20
- import {
21
- withTimeout,
22
- FINALIZE_PRE_TIMEOUT_MS,
23
- FINALIZE_POST_TIMEOUT_MS,
24
- } from "../auto/finalize-timeout.ts";
25
- import { MAX_FINALIZE_TIMEOUTS } from "../auto/types.ts";
20
+ import { withTimeout } from "../auto/finalize-timeout.ts";
26
21
 
27
22
  const { assertTrue, assertEq, report } = createTestContext();
28
23
 
@@ -80,44 +75,6 @@ const { assertTrue, assertEq, report } = createTestContext();
80
75
  assertTrue(caught, "rejection should propagate");
81
76
  }
82
77
 
83
- // ═══ Test: FINALIZE_PRE_TIMEOUT_MS is defined and reasonable ═════════════════
84
-
85
- {
86
- console.log("\n=== #3757: pre-verification timeout constant is defined and reasonable ===");
87
-
88
- assertTrue(
89
- typeof FINALIZE_PRE_TIMEOUT_MS === "number",
90
- "FINALIZE_PRE_TIMEOUT_MS should be a number",
91
- );
92
- assertTrue(
93
- FINALIZE_PRE_TIMEOUT_MS >= 30_000,
94
- `pre timeout should be >= 30s (got ${FINALIZE_PRE_TIMEOUT_MS}ms)`,
95
- );
96
- assertTrue(
97
- FINALIZE_PRE_TIMEOUT_MS <= 120_000,
98
- `pre timeout should be <= 120s (got ${FINALIZE_PRE_TIMEOUT_MS}ms)`,
99
- );
100
- }
101
-
102
- // ═══ Test: FINALIZE_POST_TIMEOUT_MS is defined and reasonable ═════════════════
103
-
104
- {
105
- console.log("\n=== #2344: timeout constant is defined and reasonable ===");
106
-
107
- assertTrue(
108
- typeof FINALIZE_POST_TIMEOUT_MS === "number",
109
- "FINALIZE_POST_TIMEOUT_MS should be a number",
110
- );
111
- assertTrue(
112
- FINALIZE_POST_TIMEOUT_MS >= 30_000,
113
- `timeout should be >= 30s (got ${FINALIZE_POST_TIMEOUT_MS}ms)`,
114
- );
115
- assertTrue(
116
- FINALIZE_POST_TIMEOUT_MS <= 120_000,
117
- `timeout should be <= 120s (got ${FINALIZE_POST_TIMEOUT_MS}ms)`,
118
- );
119
- }
120
-
121
78
  // ═══ Test: withTimeout cleans up timer on success ════════════════════════════
122
79
 
123
80
  {
@@ -134,25 +91,6 @@ const { assertTrue, assertEq, report } = createTestContext();
134
91
  assertEq(result.timedOut, false, "should not time out");
135
92
  }
136
93
 
137
- // ═══ Test: MAX_FINALIZE_TIMEOUTS is defined and reasonable ═══════════════════
138
-
139
- {
140
- console.log("\n=== #3757: MAX_FINALIZE_TIMEOUTS is defined and reasonable ===");
141
-
142
- assertTrue(
143
- typeof MAX_FINALIZE_TIMEOUTS === "number",
144
- "MAX_FINALIZE_TIMEOUTS should be a number",
145
- );
146
- assertTrue(
147
- MAX_FINALIZE_TIMEOUTS >= 2,
148
- `threshold should be >= 2 (got ${MAX_FINALIZE_TIMEOUTS})`,
149
- );
150
- assertTrue(
151
- MAX_FINALIZE_TIMEOUTS <= 10,
152
- `threshold should be <= 10 (got ${MAX_FINALIZE_TIMEOUTS})`,
153
- );
154
- }
155
-
156
94
  // Note: the two previous source-grep blocks that scanned phases.ts for
157
95
  // `withTimeout(` / `failClosedOnFinalizeTimeout(` occurrences were removed
158
96
  // under #4825 — they encoded implementation shape (Goodhart) and broke on
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Runtime regression — closed-status omission for missing-summary detection
3
+ * (#4902).
4
+ *
5
+ * `findMissingSummaries` (auto-dispatch.ts) filters out slices in any closed
6
+ * status before checking for an on-disk SUMMARY. The deleted source-grep
7
+ * test asserted the literal `CLOSED_STATUSES` Set; this rewrite tests the
8
+ * exported predicate (`isClosedStatus`) that the inline Set replicates,
9
+ * so any drift between the predicate and the inline filter surfaces as a
10
+ * test failure here rather than a runtime miss.
11
+ */
12
+
13
+ import { describe, test } from 'node:test';
14
+ import assert from 'node:assert/strict';
15
+
16
+ import { isClosedStatus, isInactiveStatus } from '../status-guards.ts';
17
+
18
+ describe('isClosedStatus — closed-status omission contract (#4902)', () => {
19
+ test('returns true for every status findMissingSummaries skips', () => {
20
+ // Mirror the inline Set in auto-dispatch.ts:findMissingSummaries.
21
+ for (const s of ['complete', 'done', 'skipped']) {
22
+ assert.equal(
23
+ isClosedStatus(s),
24
+ true,
25
+ `${s} must count as a closed status (would-have-summary omission)`,
26
+ );
27
+ }
28
+ });
29
+
30
+ test('returns false for live in-flight statuses', () => {
31
+ for (const s of ['pending', 'active', 'in-progress', 'planning', 'executing']) {
32
+ assert.equal(
33
+ isClosedStatus(s),
34
+ false,
35
+ `${s} is in-flight and MUST be checked for a missing SUMMARY`,
36
+ );
37
+ }
38
+ });
39
+
40
+ test('isInactiveStatus also covers deferred so it is not summary-checked', () => {
41
+ // Deferred slices likewise never produce a SUMMARY; the active-slice
42
+ // selector uses isInactiveStatus to skip them. Pin the contract.
43
+ assert.equal(isInactiveStatus('deferred'), true);
44
+ assert.equal(isInactiveStatus('complete'), true);
45
+ assert.equal(isInactiveStatus('pending'), false);
46
+ });
47
+ });
@@ -9,7 +9,8 @@
9
9
  import test from "node:test";
10
10
  import assert from "node:assert/strict";
11
11
  import type { UnitMetrics } from "../metrics.js";
12
- import { detectStuckLoops, type ForensicAnomaly } from "../forensics.js";
12
+ import type { WorktreeTelemetrySummary } from "../worktree-telemetry.js";
13
+ import { detectStuckLoops, detectWorktreeOrphans, type ForensicAnomaly } from "../forensics.js";
13
14
 
14
15
  // ── Helpers ──────────────────────────────────────────────────────────────────
15
16
 
@@ -163,3 +164,27 @@ test("#3760 detectStuckLoops still flags repeated dispatches within one auto ses
163
164
  `details should explain the session-aware rule: ${anomalies[0].details}`,
164
165
  );
165
166
  });
167
+
168
+ test("#4711 detectWorktreeOrphans suggests doctor fix for completed unmerged branches", () => {
169
+ const anomalies: ForensicAnomaly[] = [];
170
+ const summary: WorktreeTelemetrySummary = {
171
+ worktreesCreated: 0,
172
+ worktreesMerged: 0,
173
+ orphansDetected: 1,
174
+ orphansByReason: { "complete-unmerged": 1 },
175
+ mergeDurationsMs: [],
176
+ mergeConflicts: 0,
177
+ exitsByReason: {},
178
+ exitsWithUnmergedWork: 0,
179
+ canonicalRedirects: 0,
180
+ slicesMerged: 0,
181
+ sliceMergeConflicts: 0,
182
+ milestoneResquashes: 0,
183
+ };
184
+
185
+ detectWorktreeOrphans(summary, anomalies);
186
+
187
+ assert.equal(anomalies.length, 1);
188
+ assert.match(anomalies[0]!.details, /\/gsd doctor fix/);
189
+ assert.doesNotMatch(anomalies[0]!.details, /\/gsd health --fix/);
190
+ });
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Runtime regression — `.bg-shell/` baseline pattern (#4902, prior #2655).
3
+ *
4
+ * The deleted `gitignore-bg-shell.test.ts` asserted `.bg-shell/` appeared in
5
+ * the BASELINE_PATTERNS array via source grep. This rewrite drives
6
+ * `ensureGitignore()` against a tmp directory and asserts the written
7
+ * `.gitignore` actually contains the `.bg-shell/` pattern — i.e. tests the
8
+ * behaviour the constant exists to guarantee, not the spelling of the
9
+ * constant.
10
+ */
11
+
12
+ import { describe, test } from 'node:test';
13
+ import assert from 'node:assert/strict';
14
+ import * as fs from 'node:fs';
15
+ import * as os from 'node:os';
16
+ import * as path from 'node:path';
17
+
18
+ import { ensureGitignore } from '../gitignore.ts';
19
+
20
+ function makeTmpRepo(): string {
21
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-gitignore-bg-'));
22
+ }
23
+
24
+ function cleanup(dir: string): void {
25
+ try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* swallow */ }
26
+ }
27
+
28
+ describe('ensureGitignore writes .bg-shell/ baseline (#4902)', () => {
29
+ test('appends .bg-shell/ to a fresh project .gitignore', () => {
30
+ const dir = makeTmpRepo();
31
+ try {
32
+ const wrote = ensureGitignore(dir);
33
+ assert.equal(wrote, true, 'ensureGitignore should report it wrote');
34
+
35
+ const ignore = fs.readFileSync(path.join(dir, '.gitignore'), 'utf-8');
36
+ const lines = new Set(
37
+ ignore.split('\n').map((l) => l.trim()).filter(Boolean),
38
+ );
39
+ assert.ok(
40
+ lines.has('.bg-shell/'),
41
+ `.gitignore should include .bg-shell/. Got:\n${ignore}`,
42
+ );
43
+ } finally {
44
+ cleanup(dir);
45
+ }
46
+ });
47
+
48
+ test('preserves .bg-shell/ when it is already present (idempotent)', () => {
49
+ const dir = makeTmpRepo();
50
+ try {
51
+ fs.writeFileSync(
52
+ path.join(dir, '.gitignore'),
53
+ '.bg-shell/\nnode_modules/\n',
54
+ );
55
+ ensureGitignore(dir); // run once to fill missing baseline
56
+ const ignore = fs.readFileSync(path.join(dir, '.gitignore'), 'utf-8');
57
+ const occurrences = ignore.split('\n').filter((l) => l.trim() === '.bg-shell/').length;
58
+ assert.equal(occurrences, 1, 'should not duplicate an existing .bg-shell/ entry');
59
+ } finally {
60
+ cleanup(dir);
61
+ }
62
+ });
63
+ });
@@ -18,6 +18,8 @@ import {
18
18
  getActiveDecisions,
19
19
  getActiveRequirements,
20
20
  transaction,
21
+ readTransaction,
22
+ isInTransaction,
21
23
  _getAdapter,
22
24
  _resetProvider,
23
25
  insertMilestone,
@@ -383,6 +385,34 @@ describe('gsd-db', () => {
383
385
  closeDatabase();
384
386
  });
385
387
 
388
+ test('gsd-db: failed BEGIN does not poison transaction depth', () => {
389
+ openDatabase(':memory:');
390
+ const adapter = _getAdapter()!;
391
+
392
+ const assertFailedBeginLeavesDepthClear = (label: string, fn: () => void) => {
393
+ adapter.exec('BEGIN');
394
+ try {
395
+ let threw = false;
396
+ try {
397
+ fn();
398
+ } catch {
399
+ threw = true;
400
+ }
401
+ assert.equal(threw, true, `${label} should surface the SQLite BEGIN failure`);
402
+ assert.equal(isInTransaction(), false, `${label} failed BEGIN must not leave depth active`);
403
+ } finally {
404
+ adapter.exec('ROLLBACK');
405
+ }
406
+ };
407
+
408
+ try {
409
+ assertFailedBeginLeavesDepthClear('transaction', () => transaction(() => undefined));
410
+ assertFailedBeginLeavesDepthClear('readTransaction', () => readTransaction(() => undefined));
411
+ } finally {
412
+ closeDatabase();
413
+ }
414
+ });
415
+
386
416
  test('gsd-db: recreates missing verification evidence dedup index after removing duplicate rows', () => {
387
417
  const dbPath = tempDbPath();
388
418
  openDatabase(dbPath);
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Runtime regression — `projectRoot()` throws `GSDNoProjectError` when
3
+ * invoked outside a project directory (#4902).
4
+ *
5
+ * The deleted `gsd-no-project-error.test.ts` was a source-grep check.
6
+ * This rewrite chdirs to $HOME, calls the real `projectRoot()`, and
7
+ * asserts a `GSDNoProjectError` is thrown with the project-required
8
+ * message.
9
+ */
10
+
11
+ import { describe, test, after } from 'node:test';
12
+ import assert from 'node:assert/strict';
13
+ import * as os from 'node:os';
14
+ import * as fs from 'node:fs';
15
+ import * as path from 'node:path';
16
+
17
+ import { projectRoot, GSDNoProjectError } from '../commands/context.ts';
18
+
19
+ const ORIGINAL_CWD = process.cwd();
20
+
21
+ after(() => {
22
+ try { process.chdir(ORIGINAL_CWD); } catch { /* swallow */ }
23
+ });
24
+
25
+ describe('projectRoot() throws GSDNoProjectError outside a project (#4902)', () => {
26
+ test('throws GSDNoProjectError when cwd is $HOME', () => {
27
+ const home = os.homedir();
28
+ process.chdir(home);
29
+ try {
30
+ assert.throws(
31
+ () => projectRoot(),
32
+ (err: unknown) => {
33
+ assert.ok(err instanceof GSDNoProjectError, 'should throw GSDNoProjectError');
34
+ assert.match(
35
+ (err as Error).message,
36
+ /home directory|project directory/i,
37
+ 'error message should mention home/project directory',
38
+ );
39
+ return true;
40
+ },
41
+ );
42
+ } finally {
43
+ process.chdir(ORIGINAL_CWD);
44
+ }
45
+ });
46
+
47
+ test('throws GSDNoProjectError when cwd is the system tmpdir root', () => {
48
+ // Use realpath to dodge symlinks blocking the cwd
49
+ const tmpRoot = fs.realpathSync(os.tmpdir());
50
+ // Some systems make tmpdir a subdirectory; only run when it normalizes
51
+ // to a known-blocked root. validateDirectory blocks /tmp + /var/folders
52
+ // tmp roots; build a small subdir under tmp and then assert that the
53
+ // raw tmpdir root itself blocks. We just use it directly.
54
+ process.chdir(tmpRoot);
55
+ try {
56
+ // Behaviour: either we get a GSDNoProjectError (blocked tmpdir root) or
57
+ // we don't — but in the case where we don't (tmpdir is somehow allowed
58
+ // as a project root on this machine), the test is vacuously satisfied
59
+ // by the prior $HOME case. We assert the type-narrowing path instead:
60
+ let threw: unknown = null;
61
+ try { projectRoot(); } catch (err) { threw = err; }
62
+ if (threw !== null) {
63
+ assert.ok(
64
+ threw instanceof GSDNoProjectError,
65
+ 'if projectRoot throws, it must be a GSDNoProjectError (typed)',
66
+ );
67
+ }
68
+ } finally {
69
+ process.chdir(ORIGINAL_CWD);
70
+ }
71
+ });
72
+ });
73
+
74
+ describe('GSDNoProjectError shape (#4902)', () => {
75
+ test('GSDNoProjectError extends Error and carries its name', () => {
76
+ const err = new GSDNoProjectError('test reason');
77
+ assert.ok(err instanceof Error);
78
+ assert.equal(err.name, 'GSDNoProjectError');
79
+ assert.equal(err.message, 'test reason');
80
+ });
81
+ });
@@ -6,7 +6,7 @@
6
6
  * secrets, stats, and unused warnings).
7
7
  */
8
8
 
9
- import test from 'node:test';
9
+ import test, { mock } from 'node:test';
10
10
  import assert from 'node:assert/strict';
11
11
  import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
12
12
  import { join } from 'node:path';
@@ -164,7 +164,15 @@ test('tryHandle matches by question ID — single select', (t) => {
164
164
  assert.strictEqual(injector.getStats().questionsAnswered, 1);
165
165
  });
166
166
 
167
- test('tryHandle unknown question deferred — first_option timeout', async (t) => {
167
+ test('tryHandle unknown question deferred — first_option timeout', (t) => {
168
+ // Use Node's MockTimers instead of a real-time setTimeout race. The
169
+ // production class schedules an internal setTimeout to default the
170
+ // answer when no metadata arrives — driving virtual time advances that
171
+ // timer deterministically, regardless of the literal ms value the
172
+ // production code chose.
173
+ mock.timers.enable({ apis: ['setTimeout'] });
174
+ t.after(() => { mock.timers.reset(); });
175
+
168
176
  const injector = new AnswerInjector({ defaults: { strategy: 'first_option' } });
169
177
 
170
178
  const captured: string[] = [];
@@ -177,8 +185,10 @@ test('tryHandle unknown question deferred — first_option timeout', async (t) =
177
185
  assert.strictEqual(handled, true);
178
186
  assert.strictEqual(captured.length, 0, 'nothing sent immediately');
179
187
 
180
- // Wait for the 500ms deferred timeout to fire
181
- await new Promise((resolve) => setTimeout(resolve, 600));
188
+ // Advance virtual time past any internal defer timeout (well above any
189
+ // reasonable defer cap). MockTimers fires synchronously, so by the time
190
+ // tick() returns the deferred handler has run.
191
+ mock.timers.tick(60_000);
182
192
 
183
193
  assert.strictEqual(captured.length, 1);
184
194
  const response = JSON.parse(captured[0].trim());
@@ -66,16 +66,22 @@ test("detectHealthWidgetProjectState: milestone without metrics returns active",
66
66
  assert.equal(detectHealthWidgetProjectState(dir), "active");
67
67
  });
68
68
 
69
- test("buildHealthLines: none state shows onboarding copy", (t) => {
70
- assert.deepEqual(buildHealthLines(activeData({ projectState: "none" })), [
71
- " GSD No project loaded run /gsd to start",
72
- ]);
69
+ test("buildHealthLines: none state shows single onboarding line pointing at /gsd", (t) => {
70
+ const lines = buildHealthLines(activeData({ projectState: "none" }));
71
+ assert.equal(lines.length, 1, "renders exactly one line");
72
+ // Should not show System OK / Budget / Last commit chrome when there's no project.
73
+ assert.ok(!/System OK|Budget|Last commit/.test(lines[0]!), "no active-project chrome");
74
+ // Should direct user to bootstrap via /gsd.
75
+ assert.match(lines[0]!, /\/gsd/);
73
76
  });
74
77
 
75
- test("buildHealthLines: initialized state shows continue setup copy", (t) => {
76
- assert.deepEqual(buildHealthLines(activeData({ projectState: "initialized" })), [
77
- " GSD Project initialized run /gsd to continue setup",
78
- ]);
78
+ test("buildHealthLines: initialized state shows single setup line pointing at /gsd", (t) => {
79
+ const lines = buildHealthLines(activeData({ projectState: "initialized" }));
80
+ assert.equal(lines.length, 1, "renders exactly one line");
81
+ assert.ok(!/System OK|Budget|Last commit/.test(lines[0]!), "no active-project chrome");
82
+ // Distinct from "none" — must mention initialized/setup language and /gsd.
83
+ assert.match(lines[0]!, /\/gsd/);
84
+ assert.match(lines[0]!, /initiali[sz]ed|setup/i);
79
85
  });
80
86
 
81
87
  test("buildHealthLines: active state with ledger-driven spend shows spent summary", (t) => {
@@ -115,16 +121,20 @@ test("buildHealthLines: shows last commit with relative time and message", (t) =
115
121
  assert.match(lines[0]!, /feat\(widget\): add health display/);
116
122
  });
117
123
 
118
- test("buildHealthLines: truncates long commit messages", (t) => {
124
+ test("buildHealthLines: truncates long commit messages with ellipsis", (t) => {
119
125
  const epoch = Math.floor(Date.now() / 1000) - 60;
120
- const longMsg = "a".repeat(80);
126
+ const longMsg = "a".repeat(200); // far longer than any reasonable widget cap
121
127
  const lines = buildHealthLines(activeData({
122
128
  lastCommitEpoch: epoch,
123
129
  lastCommitMessage: longMsg,
124
130
  }));
125
131
  assert.equal(lines.length, 1);
126
- assert.match(lines[0]!, /a{49}…/);
127
- assert.ok(!lines[0]!.includes("a".repeat(51)), "message is truncated");
132
+ // Behavioural contract: rendered output is shorter than the input message
133
+ // and ends the message portion with the ellipsis character.
134
+ const aRun = lines[0]!.match(/a+…/);
135
+ assert.ok(aRun, "rendered output contains a run of a-chars terminated by an ellipsis");
136
+ assert.ok(aRun![0].length - 1 < longMsg.length, "truncated message is shorter than input");
137
+ assert.ok(!lines[0]!.includes("a".repeat(longMsg.length)), "untruncated message must not appear in output");
128
138
  });
129
139
 
130
140
  test("buildHealthLines: no last commit section when epoch is null", (t) => {
@@ -0,0 +1,57 @@
1
+ // GSD-2 — Verify /gsd help menu covers all registered commands
2
+ // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
+
4
+ import { describe, test } from "node:test";
5
+ import assert from "node:assert/strict";
6
+
7
+ import { TOP_LEVEL_SUBCOMMANDS } from "../commands/catalog.ts";
8
+
9
+ /**
10
+ * Extracts command names from the showHelp("full") lines array.
11
+ * Each help line follows the pattern: " /gsd <cmd> ..."
12
+ */
13
+ function extractHelpCommands(lines: string[]): Set<string> {
14
+ const cmds = new Set<string>();
15
+ for (const line of lines) {
16
+ const m = line.match(/^\s+\/gsd\s+(\S+)/);
17
+ if (m) cmds.add(m[1]);
18
+ }
19
+ return cmds;
20
+ }
21
+
22
+ describe("help menu coverage", () => {
23
+ test("every TOP_LEVEL_SUBCOMMAND appears in showHelp(\"full\") output", async () => {
24
+ // Import showHelp and capture its output via a mock ctx
25
+ const lines: string[] = [];
26
+ const mockCtx = {
27
+ ui: {
28
+ notify(message: string) {
29
+ lines.push(...message.split("\n"));
30
+ },
31
+ custom: async () => {},
32
+ },
33
+ };
34
+
35
+ const { showHelp } = await import("../commands/handlers/core.ts");
36
+ showHelp(mockCtx as any, "full");
37
+
38
+ const helpCmds = extractHelpCommands(lines);
39
+
40
+ // "help" is the command that shows the menu — it doesn't list itself
41
+ const SELF_REFERENTIAL = new Set(["help"]);
42
+
43
+ const missing: string[] = [];
44
+ for (const entry of TOP_LEVEL_SUBCOMMANDS) {
45
+ if (SELF_REFERENTIAL.has(entry.cmd)) continue;
46
+ if (!helpCmds.has(entry.cmd)) {
47
+ missing.push(entry.cmd);
48
+ }
49
+ }
50
+
51
+ assert.deepStrictEqual(
52
+ missing,
53
+ [],
54
+ `Commands registered in TOP_LEVEL_SUBCOMMANDS but missing from /gsd help full:\n ${missing.join(", ")}`,
55
+ );
56
+ });
57
+ });