gsd-pi 2.76.0-dev.82e249f7b → 2.76.0-dev.97f5583d9

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 (271) hide show
  1. package/dist/claude-cli-check.js +32 -3
  2. package/dist/mcp-server.d.ts +7 -0
  3. package/dist/mcp-server.js +35 -1
  4. package/dist/resource-loader.d.ts +1 -1
  5. package/dist/resource-loader.js +2 -8
  6. package/dist/resources/extensions/claude-code-cli/readiness.js +4 -3
  7. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +77 -17
  8. package/dist/resources/extensions/gsd/auto/phases.js +42 -1
  9. package/dist/resources/extensions/gsd/auto/run-unit.js +27 -0
  10. package/dist/resources/extensions/gsd/auto/session.js +12 -0
  11. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -3
  12. package/dist/resources/extensions/gsd/auto-model-selection.js +1 -1
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +25 -2
  14. package/dist/resources/extensions/gsd/auto-prompts.js +14 -0
  15. package/dist/resources/extensions/gsd/auto-recovery.js +13 -0
  16. package/dist/resources/extensions/gsd/auto-start.js +27 -18
  17. package/dist/resources/extensions/gsd/auto-worktree.js +51 -53
  18. package/dist/resources/extensions/gsd/auto.js +55 -27
  19. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -1
  20. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
  21. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
  22. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  23. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +51 -5
  24. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +34 -2
  25. package/dist/resources/extensions/gsd/clean-root-preflight.js +93 -0
  26. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +968 -23
  27. package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
  28. package/dist/resources/extensions/gsd/error-classifier.js +10 -3
  29. package/dist/resources/extensions/gsd/exec-history.js +120 -0
  30. package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
  31. package/dist/resources/extensions/gsd/gsd-db.js +115 -7
  32. package/dist/resources/extensions/gsd/guided-flow.js +189 -0
  33. package/dist/resources/extensions/gsd/health-widget.js +4 -1
  34. package/dist/resources/extensions/gsd/init-wizard.js +15 -1
  35. package/dist/resources/extensions/gsd/key-manager.js +6 -0
  36. package/dist/resources/extensions/gsd/model-router.js +36 -3
  37. package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -9
  38. package/dist/resources/extensions/gsd/preferences-types.js +9 -0
  39. package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
  40. package/dist/resources/extensions/gsd/preferences.js +17 -17
  41. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  42. package/dist/resources/extensions/gsd/prompts/discuss.md +29 -2
  43. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  44. package/dist/resources/extensions/gsd/safety/evidence-collector.js +96 -0
  45. package/dist/resources/extensions/gsd/safety/file-change-validator.js +13 -5
  46. package/dist/resources/extensions/gsd/safety/safety-harness.js +5 -1
  47. package/dist/resources/extensions/gsd/token-counter.js +22 -5
  48. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
  49. package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
  50. package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
  51. package/dist/resources/extensions/gsd/uok/plan-v2.js +20 -3
  52. package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
  53. package/dist/resources/skills/verify-before-complete/SKILL.md +2 -1
  54. package/dist/resources/skills/write-docs/SKILL.md +2 -1
  55. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  56. package/dist/web/standalone/.next/BUILD_ID +1 -1
  57. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  58. package/dist/web/standalone/.next/build-manifest.json +2 -2
  59. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  60. package/dist/web/standalone/.next/required-server-files.json +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.html +1 -1
  78. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  85. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  87. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  88. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  89. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  90. package/dist/web/standalone/server.js +1 -1
  91. package/package.json +1 -1
  92. package/packages/mcp-server/dist/remote-questions.d.ts +45 -0
  93. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -0
  94. package/packages/mcp-server/dist/remote-questions.js +732 -0
  95. package/packages/mcp-server/dist/remote-questions.js.map +1 -0
  96. package/packages/mcp-server/dist/server.d.ts +7 -0
  97. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  98. package/packages/mcp-server/dist/server.js +41 -4
  99. package/packages/mcp-server/dist/server.js.map +1 -1
  100. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  101. package/packages/mcp-server/dist/workflow-tools.js +64 -25
  102. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  103. package/packages/mcp-server/package.json +2 -1
  104. package/packages/mcp-server/src/mcp-server.test.ts +30 -0
  105. package/packages/mcp-server/src/remote-questions.test.ts +294 -0
  106. package/packages/mcp-server/src/remote-questions.ts +916 -0
  107. package/packages/mcp-server/src/server.ts +62 -10
  108. package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
  109. package/packages/mcp-server/src/workflow-tools.ts +84 -43
  110. package/packages/mcp-server/tsconfig.test.json +19 -0
  111. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  112. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +1 -1
  113. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -1
  114. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  115. package/packages/pi-ai/dist/providers/anthropic-shared.js +27 -4
  116. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  117. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  118. package/packages/pi-ai/dist/providers/anthropic.js +8 -3
  119. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  120. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts +2 -0
  121. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts.map +1 -0
  122. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js +80 -0
  123. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js.map +1 -0
  124. package/packages/pi-ai/dist/providers/simple-options.d.ts +10 -0
  125. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  126. package/packages/pi-ai/dist/providers/simple-options.js +16 -1
  127. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  128. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +1 -1
  129. package/packages/pi-ai/src/providers/anthropic-shared.ts +26 -5
  130. package/packages/pi-ai/src/providers/anthropic.ts +9 -3
  131. package/packages/pi-ai/src/providers/minimax-tool-name.test.ts +98 -0
  132. package/packages/pi-ai/src/providers/simple-options.ts +17 -1
  133. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  134. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts +2 -0
  135. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts.map +1 -0
  136. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js +203 -0
  137. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js.map +1 -0
  138. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/model-registry.js +14 -0
  140. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
  142. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
  143. package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
  144. package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
  145. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
  146. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
  147. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
  148. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
  149. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  150. package/packages/pi-coding-agent/dist/core/session-manager.js +9 -5
  151. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/session-manager.test.js +25 -1
  153. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
  155. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
  157. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
  159. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
  161. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  162. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  163. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +13 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  165. package/packages/pi-coding-agent/src/core/model-registry-custom-caps.test.ts +245 -0
  166. package/packages/pi-coding-agent/src/core/model-registry.ts +16 -0
  167. package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
  168. package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
  169. package/packages/pi-coding-agent/src/core/session-manager.test.ts +36 -1
  170. package/packages/pi-coding-agent/src/core/session-manager.ts +9 -5
  171. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
  172. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
  173. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +13 -1
  174. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  175. package/src/resources/extensions/claude-code-cli/readiness.ts +4 -3
  176. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +78 -17
  177. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +149 -5
  178. package/src/resources/extensions/gsd/auto/loop-deps.ts +13 -0
  179. package/src/resources/extensions/gsd/auto/phases.ts +66 -1
  180. package/src/resources/extensions/gsd/auto/run-unit.ts +29 -0
  181. package/src/resources/extensions/gsd/auto/session.ts +22 -0
  182. package/src/resources/extensions/gsd/auto-dispatch.ts +16 -3
  183. package/src/resources/extensions/gsd/auto-model-selection.ts +1 -1
  184. package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
  185. package/src/resources/extensions/gsd/auto-prompts.ts +28 -1
  186. package/src/resources/extensions/gsd/auto-recovery.ts +15 -0
  187. package/src/resources/extensions/gsd/auto-start.ts +29 -19
  188. package/src/resources/extensions/gsd/auto-worktree.ts +62 -63
  189. package/src/resources/extensions/gsd/auto.ts +58 -27
  190. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +23 -1
  191. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
  192. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
  193. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  194. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +53 -5
  195. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +35 -2
  196. package/src/resources/extensions/gsd/clean-root-preflight.ts +111 -0
  197. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +898 -32
  198. package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
  199. package/src/resources/extensions/gsd/error-classifier.ts +10 -3
  200. package/src/resources/extensions/gsd/exec-history.ts +153 -0
  201. package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
  202. package/src/resources/extensions/gsd/gsd-db.ts +122 -7
  203. package/src/resources/extensions/gsd/guided-flow.ts +221 -0
  204. package/src/resources/extensions/gsd/health-widget.ts +3 -1
  205. package/src/resources/extensions/gsd/init-wizard.ts +15 -1
  206. package/src/resources/extensions/gsd/journal.ts +2 -1
  207. package/src/resources/extensions/gsd/key-manager.ts +6 -0
  208. package/src/resources/extensions/gsd/model-router.ts +42 -1
  209. package/src/resources/extensions/gsd/pre-execution-checks.ts +36 -10
  210. package/src/resources/extensions/gsd/preferences-types.ts +46 -0
  211. package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
  212. package/src/resources/extensions/gsd/preferences.ts +17 -17
  213. package/src/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  214. package/src/resources/extensions/gsd/prompts/discuss.md +29 -2
  215. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  216. package/src/resources/extensions/gsd/safety/evidence-collector.ts +119 -0
  217. package/src/resources/extensions/gsd/safety/file-change-validator.ts +17 -4
  218. package/src/resources/extensions/gsd/safety/safety-harness.ts +9 -0
  219. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +119 -1
  220. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +12 -0
  221. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +49 -0
  222. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +186 -0
  223. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
  224. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  225. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  226. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
  227. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +31 -0
  228. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +1 -1
  229. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +1 -1
  230. package/src/resources/extensions/gsd/tests/escalation.test.ts +1 -1
  231. package/src/resources/extensions/gsd/tests/exec-history.test.ts +124 -0
  232. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
  233. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +58 -0
  234. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +152 -1
  235. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
  236. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
  237. package/src/resources/extensions/gsd/tests/issue-4540-regressions.test.ts +288 -0
  238. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +2 -0
  239. package/src/resources/extensions/gsd/tests/key-manager.test.ts +7 -0
  240. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  241. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  242. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +19 -0
  243. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
  244. package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +272 -0
  245. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +234 -0
  246. package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
  247. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +44 -0
  248. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +48 -0
  249. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +388 -0
  250. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +9 -3
  251. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +205 -0
  252. package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
  253. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +32 -40
  254. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +56 -0
  255. package/src/resources/extensions/gsd/tests/token-counter.test.ts +105 -1
  256. package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -0
  257. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +23 -0
  258. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +65 -2
  259. package/src/resources/extensions/gsd/tests/write-gate.test.ts +64 -0
  260. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
  261. package/src/resources/extensions/gsd/token-counter.ts +22 -5
  262. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
  263. package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
  264. package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
  265. package/src/resources/extensions/gsd/uok/plan-v2.ts +26 -3
  266. package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
  267. package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
  268. package/src/resources/skills/verify-before-complete/SKILL.md +2 -1
  269. package/src/resources/skills/write-docs/SKILL.md +2 -1
  270. /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → lLdDRDspgYzfz0bJAmUSz}/_buildManifest.js +0 -0
  271. /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → lLdDRDspgYzfz0bJAmUSz}/_ssgManifest.js +0 -0
@@ -180,7 +180,7 @@ function openRawDb(path: string): unknown {
180
180
  return new Database(path);
181
181
  }
182
182
 
183
- const SCHEMA_VERSION = 21;
183
+ const SCHEMA_VERSION = 22;
184
184
 
185
185
  function indexExists(db: DbAdapter, name: string): boolean {
186
186
  return !!db.prepare(
@@ -564,7 +564,9 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
564
564
  // indexes so old DBs can open far enough for the normal migration chain.
565
565
  ensureBootstrapIndexColumns(db);
566
566
 
567
- db.exec("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope)");
567
+ if (columnExists(db, "memories", "scope")) {
568
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope)");
569
+ }
568
570
  db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_kind ON memory_sources(kind)");
569
571
  db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_scope ON memory_sources(scope)");
570
572
  db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_from ON memory_relations(from_id)");
@@ -947,19 +949,24 @@ function migrateSchema(db: DbAdapter): void {
947
949
  }
948
950
 
949
951
  if (currentVersion < 12) {
952
+ // NOTE: The original DDL used COALESCE(task_id, '') in the PRIMARY KEY
953
+ // expression, which is invalid SQLite syntax and causes startup errors on
954
+ // DBs that migrate through v12. The corrected DDL uses
955
+ // task_id TEXT NOT NULL DEFAULT '' with a plain column list PK. DBs that
956
+ // were created with the broken DDL are repaired by the v22 migration below.
950
957
  db.exec(`
951
958
  CREATE TABLE IF NOT EXISTS quality_gates (
952
959
  milestone_id TEXT NOT NULL,
953
960
  slice_id TEXT NOT NULL,
954
961
  gate_id TEXT NOT NULL,
955
962
  scope TEXT NOT NULL DEFAULT 'slice',
956
- task_id TEXT DEFAULT NULL,
963
+ task_id TEXT NOT NULL DEFAULT '',
957
964
  status TEXT NOT NULL DEFAULT 'pending',
958
965
  verdict TEXT NOT NULL DEFAULT '',
959
966
  rationale TEXT NOT NULL DEFAULT '',
960
967
  findings TEXT NOT NULL DEFAULT '',
961
968
  evaluated_at TEXT DEFAULT NULL,
962
- PRIMARY KEY (milestone_id, slice_id, gate_id, COALESCE(task_id, '')),
969
+ PRIMARY KEY (milestone_id, slice_id, gate_id, task_id),
963
970
  FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
964
971
  )
965
972
  `);
@@ -1187,6 +1194,52 @@ function migrateSchema(db: DbAdapter): void {
1187
1194
  });
1188
1195
  }
1189
1196
 
1197
+ if (currentVersion < 22) {
1198
+ // v22: Repair quality_gates tables that were created by the broken v12
1199
+ // migration (which used COALESCE(task_id, '') as a PK expression — invalid
1200
+ // SQLite DDL). Those DBs have task_id nullable (dflt_value NULL, notnull 0).
1201
+ // Rebuild the table with the correct schema, migrating existing rows via
1202
+ // COALESCE so no data is lost.
1203
+ const qgInfo = db.prepare("PRAGMA table_info(quality_gates)").all() as Array<Record<string, unknown>>;
1204
+ const taskIdCol = qgInfo.find((r) => r["name"] === "task_id");
1205
+ const needsRepair = taskIdCol && (taskIdCol["notnull"] === 0 || taskIdCol["notnull"] === "0");
1206
+ if (needsRepair) {
1207
+ db.exec(`
1208
+ CREATE TABLE quality_gates_new (
1209
+ milestone_id TEXT NOT NULL,
1210
+ slice_id TEXT NOT NULL,
1211
+ gate_id TEXT NOT NULL,
1212
+ scope TEXT NOT NULL DEFAULT 'slice',
1213
+ task_id TEXT NOT NULL DEFAULT '',
1214
+ status TEXT NOT NULL DEFAULT 'pending',
1215
+ verdict TEXT NOT NULL DEFAULT '',
1216
+ rationale TEXT NOT NULL DEFAULT '',
1217
+ findings TEXT NOT NULL DEFAULT '',
1218
+ evaluated_at TEXT DEFAULT NULL,
1219
+ PRIMARY KEY (milestone_id, slice_id, gate_id, task_id),
1220
+ FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
1221
+ )
1222
+ `);
1223
+ db.exec(`
1224
+ INSERT OR IGNORE INTO quality_gates_new
1225
+ (milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
1226
+ SELECT milestone_id, slice_id, gate_id, scope, COALESCE(task_id, ''), status, verdict, rationale, findings, evaluated_at
1227
+ FROM quality_gates
1228
+ `);
1229
+ db.exec("DROP TABLE quality_gates");
1230
+ db.exec("ALTER TABLE quality_gates_new RENAME TO quality_gates");
1231
+ db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
1232
+ }
1233
+ // Ensure scope column exists on quality_gates and assessments (guard
1234
+ // against DBs that somehow lack it after a partial migration).
1235
+ ensureColumn(db, "quality_gates", "scope", "ALTER TABLE quality_gates ADD COLUMN scope TEXT NOT NULL DEFAULT 'slice'");
1236
+ ensureColumn(db, "assessments", "scope", "ALTER TABLE assessments ADD COLUMN scope TEXT NOT NULL DEFAULT ''");
1237
+ db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1238
+ ":version": 22,
1239
+ ":applied_at": new Date().toISOString(),
1240
+ });
1241
+ }
1242
+
1190
1243
  db.exec("COMMIT");
1191
1244
  } catch (err) {
1192
1245
  db.exec("ROLLBACK");
@@ -1199,6 +1252,8 @@ let currentPath: string | null = null;
1199
1252
  let currentPid: number = 0;
1200
1253
  let _exitHandlerRegistered = false;
1201
1254
  let _dbOpenAttempted = false;
1255
+ let _lastDbError: Error | null = null;
1256
+ let _lastDbPhase: "open" | "initSchema" | "vacuum-recovery" | null = null;
1202
1257
 
1203
1258
  export function getDbProvider(): ProviderName | null {
1204
1259
  loadProvider();
@@ -1219,12 +1274,58 @@ export function wasDbOpenAttempted(): boolean {
1219
1274
  return _dbOpenAttempted;
1220
1275
  }
1221
1276
 
1277
+ export function getDbStatus(): {
1278
+ available: boolean;
1279
+ provider: ProviderName | null;
1280
+ attempted: boolean;
1281
+ lastError: Error | null;
1282
+ lastPhase: "open" | "initSchema" | "vacuum-recovery" | null;
1283
+ } {
1284
+ loadProvider();
1285
+ return {
1286
+ available: currentDb !== null,
1287
+ provider: providerName,
1288
+ attempted: _dbOpenAttempted,
1289
+ lastError: _lastDbError,
1290
+ lastPhase: _lastDbPhase,
1291
+ };
1292
+ }
1293
+
1222
1294
  export function openDatabase(path: string): boolean {
1223
1295
  _dbOpenAttempted = true;
1224
1296
  if (currentDb && currentPath !== path) closeDatabase();
1225
1297
  if (currentDb && currentPath === path) return true;
1226
1298
 
1227
- const rawDb = openRawDb(path);
1299
+ // Reset error state only when a new open attempt is actually going to run.
1300
+ _lastDbError = null;
1301
+ _lastDbPhase = null;
1302
+
1303
+ let rawDb: unknown;
1304
+ let fallbackProvider: ProviderName | null = null;
1305
+ let fallbackModule: unknown = null;
1306
+ try {
1307
+ rawDb = openRawDb(path);
1308
+ } catch (primaryErr) {
1309
+ _lastDbPhase = "open";
1310
+ _lastDbError = primaryErr instanceof Error ? primaryErr : new Error(String(primaryErr));
1311
+ // node:sqlite loaded but failed to open this file — try better-sqlite3 as fallback.
1312
+ if (providerName === "node:sqlite") {
1313
+ try {
1314
+ const mod = _require("better-sqlite3");
1315
+ const Db = (mod && mod.default) ? mod.default : mod;
1316
+ if (typeof Db === "function") {
1317
+ rawDb = new Db(path);
1318
+ fallbackProvider = "better-sqlite3";
1319
+ fallbackModule = Db;
1320
+ _lastDbError = null;
1321
+ _lastDbPhase = null;
1322
+ }
1323
+ } catch {
1324
+ // fallback unavailable; surface original error
1325
+ }
1326
+ }
1327
+ if (!rawDb) throw primaryErr;
1328
+ }
1228
1329
  if (!rawDb) return false;
1229
1330
 
1230
1331
  const adapter = createAdapter(rawDb);
@@ -1240,15 +1341,25 @@ export function openDatabase(path: string): boolean {
1240
1341
  initSchema(adapter, fileBacked);
1241
1342
  process.stderr.write("gsd-db: recovered corrupt database via VACUUM\n");
1242
1343
  } catch (retryErr) {
1344
+ _lastDbPhase = "vacuum-recovery";
1345
+ _lastDbError = retryErr instanceof Error ? retryErr : new Error(String(retryErr));
1243
1346
  try { adapter.close(); } catch (e) { logWarning("db", `close after VACUUM failed: ${(e as Error).message}`); }
1244
1347
  throw retryErr;
1245
1348
  }
1246
1349
  } else {
1247
- try { adapter.close(); } catch (e) { logWarning("db", `close after VACUUM failed: ${(e as Error).message}`); }
1350
+ _lastDbPhase = "initSchema";
1351
+ _lastDbError = err instanceof Error ? err : new Error(String(err));
1352
+ try { adapter.close(); } catch (e) { logWarning("db", `close after initSchema failed: ${(e as Error).message}`); }
1248
1353
  throw err;
1249
1354
  }
1250
1355
  }
1251
1356
 
1357
+ // Commit fallback provider switch only after open + schema both succeeded.
1358
+ if (fallbackProvider) {
1359
+ providerName = fallbackProvider;
1360
+ providerModule = fallbackModule;
1361
+ }
1362
+
1252
1363
  currentDb = adapter;
1253
1364
  currentPath = path;
1254
1365
  currentPid = process.pid;
@@ -1276,8 +1387,12 @@ export function closeDatabase(): void {
1276
1387
  currentDb = null;
1277
1388
  currentPath = null;
1278
1389
  currentPid = 0;
1279
- _dbOpenAttempted = false;
1280
1390
  }
1391
+ // Reset session-scoped state unconditionally so stale error info from a
1392
+ // failed open doesn't persist into the next open attempt or status check.
1393
+ _dbOpenAttempted = false;
1394
+ _lastDbError = null;
1395
+ _lastDbPhase = null;
1281
1396
  }
1282
1397
 
1283
1398
  /** Run a full VACUUM — call sparingly (e.g. after milestone completion). */
@@ -130,8 +130,20 @@ interface PendingAutoStartEntry {
130
130
  milestoneId: string; // the milestone being discussed
131
131
  step?: boolean; // preserve step mode through discuss → auto transition
132
132
  createdAt: number; // timestamp for staleness detection (#3274)
133
+ // #4573: counter for how many times the LLM emitted the ready phrase
134
+ // without writing the required artifacts. Cleared on entry delete/recreate.
135
+ readyRejectCount?: number;
133
136
  }
134
137
 
138
+ // #4573: cap for how many times we nudge the LLM after a premature ready
139
+ // phrase before giving up and asking the user to re-run /gsd.
140
+ const MAX_READY_REJECTS = 2;
141
+
142
+ // #4573: matches the canonical ready phrase the discuss prompt asks the LLM
143
+ // to emit. Accepts any M-prefixed milestone ID (three digits + optional
144
+ // suffix) with optional trailing punctuation.
145
+ const READY_PHRASE_RE = /\bMilestone\s+M\d{3}[A-Z0-9-]*\s+ready\.?/i;
146
+
135
147
  const pendingAutoStartMap = new Map<string, PendingAutoStartEntry>();
136
148
 
137
149
  /**
@@ -279,6 +291,215 @@ export function checkAutoStartAfterDiscuss(): boolean {
279
291
  return true;
280
292
  }
281
293
 
294
+ /**
295
+ * Extract the concatenated text content from an assistant message, whether it
296
+ * stores content as a string or as an array of text blocks.
297
+ */
298
+ function extractAssistantText(msg: any): string {
299
+ if (!msg) return "";
300
+ const content = msg.content;
301
+ if (typeof content === "string") return content;
302
+ if (!Array.isArray(content)) return "";
303
+ const parts: string[] = [];
304
+ for (const block of content) {
305
+ if (!block || typeof block !== "object") continue;
306
+ if (block.type === "text" && typeof block.text === "string") parts.push(block.text);
307
+ }
308
+ return parts.join("\n");
309
+ }
310
+
311
+ /**
312
+ * Return true if the assistant message contains any tool-use block.
313
+ */
314
+ function hasToolUse(msg: any): boolean {
315
+ if (!msg) return false;
316
+ const content = msg.content;
317
+ if (!Array.isArray(content)) return false;
318
+ return content.some((b: any) => b && typeof b === "object" && (b.type === "tool_use" || b.type === "tool-use"));
319
+ }
320
+
321
+ /**
322
+ * #4573 — Detect and recover from the "ready phrase without files" failure mode.
323
+ *
324
+ * When the LLM emits "Milestone {{id}} ready." but has not written CONTEXT.md
325
+ * or ROADMAP.md, `checkAutoStartAfterDiscuss()` silently returns false and the
326
+ * next /gsd invocation loops into the "All milestones complete" warning.
327
+ *
328
+ * This function, called from `handleAgentEnd` after `checkAutoStartAfterDiscuss`
329
+ * returns false, pattern-matches the ready phrase on the last assistant message.
330
+ * If it fired AND neither CONTEXT.md nor ROADMAP.md exists, it:
331
+ * 1. Notifies the user that the signal was rejected.
332
+ * 2. Injects a system message via `pi.sendMessage(..., {triggerTurn:true})`
333
+ * telling the LLM the signal was premature and to emit the writes now.
334
+ * 3. Caps at `MAX_READY_REJECTS` per-entry; beyond that, gives up and asks
335
+ * the user to re-run /gsd.
336
+ *
337
+ * Returns true when a nudge (or give-up) was emitted, signaling the caller to
338
+ * skip `resolveAgentEnd`.
339
+ */
340
+ export function maybeHandleReadyPhraseWithoutFiles(event: { messages: any[] }): boolean {
341
+ const entry = _getPendingAutoStart();
342
+ if (!entry) return false;
343
+ const { ctx, pi, basePath, milestoneId } = entry;
344
+
345
+ // Gate: last assistant message must contain the ready phrase
346
+ const lastMsg = event.messages[event.messages.length - 1];
347
+ const text = extractAssistantText(lastMsg);
348
+ if (!READY_PHRASE_RE.test(text)) return false;
349
+
350
+ // Gate: artifacts must still be missing — if they exist, the happy path
351
+ // already fired and we have nothing to do.
352
+ const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
353
+ const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
354
+ if (contextFile || roadmapFile) return false;
355
+
356
+ entry.readyRejectCount = (entry.readyRejectCount ?? 0) + 1;
357
+
358
+ if (entry.readyRejectCount > MAX_READY_REJECTS) {
359
+ // Give up: clear state and tell the user to re-run /gsd. Avoids an
360
+ // infinite nudge loop when the LLM never produces the writes.
361
+ pendingAutoStartMap.delete(basePath);
362
+ ctx.ui.notify(
363
+ `Milestone ${milestoneId}: LLM signaled "ready" ${entry.readyRejectCount} times without writing files. ` +
364
+ `Stopping auto-nudge. Run /gsd to try again.`,
365
+ "error",
366
+ );
367
+ return true;
368
+ }
369
+
370
+ ctx.ui.notify(
371
+ `Milestone ${milestoneId}: "ready" signal rejected — CONTEXT.md and ROADMAP.md are missing. Asking the LLM to complete the writes.`,
372
+ "warning",
373
+ );
374
+
375
+ const nudge =
376
+ `You emitted "Milestone ${milestoneId} ready." but neither ` +
377
+ `.gsd/milestones/${milestoneId}/${milestoneId}-CONTEXT.md nor ` +
378
+ `.gsd/milestones/${milestoneId}/${milestoneId}-ROADMAP.md exists on disk. ` +
379
+ `The ready phrase is a POST-WRITE signal and has been rejected. ` +
380
+ `In this turn: (1) write PROJECT.md, REQUIREMENTS.md, and the milestone ` +
381
+ `CONTEXT.md, (2) call gsd_plan_milestone, then (3) emit the ready phrase. ` +
382
+ `Do not describe these steps — execute them as tool calls. ` +
383
+ `This is retry ${entry.readyRejectCount}/${MAX_READY_REJECTS}; further ` +
384
+ `premature signals will clear the session.`;
385
+
386
+ try {
387
+ pi.sendMessage(
388
+ { customType: "gsd-ready-no-files", content: nudge, display: false },
389
+ { triggerTurn: true },
390
+ );
391
+ } catch (e) {
392
+ logWarning("guided", `ready-phrase nudge sendMessage failed: ${(e as Error).message}`);
393
+ return false;
394
+ }
395
+ return true;
396
+ }
397
+
398
+ /**
399
+ * #4573 — Detect and recover from the "announces tool, never calls it" stall.
400
+ *
401
+ * The LLM emits text like "I'll now write the CONTEXT.md file" but the turn
402
+ * ends with zero tool-use blocks. The harness has no post-turn tool-call
403
+ * validation, so the unit promise resolves and the user sees a stalled state.
404
+ *
405
+ * This function, called from `handleAgentEnd`, inspects the last assistant
406
+ * message. If ALL of the following are true, it injects a recovery message:
407
+ * - Text-only (no tool-use blocks)
408
+ * - Contains a commit-intent phrase ("I'll write", "I'll call", etc.)
409
+ * - Auto-mode is active OR a discussion autostart is pending
410
+ * - `emptyTurnRetryCount` is under the cap
411
+ *
412
+ * Per-handler state is held on the `PendingAutoStartEntry` when present, and
413
+ * on a module-level map otherwise. The counter resets on any successful
414
+ * tool-use turn via `resetEmptyTurnCounter`.
415
+ */
416
+ const emptyTurnCounterByBase = new Map<string, number>();
417
+ const MAX_EMPTY_TURN_RETRIES = 2;
418
+
419
+ // Phrases that indicate the LLM is about to do something but has not yet.
420
+ // Kept tight to avoid flagging legitimate narration like "I'll wait for your answer."
421
+ const COMMIT_INTENT_RE =
422
+ /\b(?:I['’]ll|I will|Next,? I['’]ll|Now I['’]ll|Let me|I['’]m going to|I am going to)\s+(?:now\s+)?(?:write|create|call|invoke|update|add|make|run|execute|generate|produce|emit|compose|implement|save|apply|commit)\b/i;
423
+
424
+ /**
425
+ * Reset the empty-turn counter for a basePath after a successful tool-use turn.
426
+ * Called from handleAgentEnd when the last message contains tool_use blocks.
427
+ */
428
+ export function resetEmptyTurnCounter(basePath?: string): void {
429
+ if (basePath) emptyTurnCounterByBase.delete(basePath);
430
+ else emptyTurnCounterByBase.clear();
431
+ }
432
+
433
+ export function maybeHandleEmptyIntentTurn(
434
+ event: { messages: any[] },
435
+ isAuto: boolean,
436
+ ): boolean {
437
+ // Gate: only fire when there is system-driven work in flight. Interactive
438
+ // /gsd discuss (user-driven) produces legitimate text-only turns.
439
+ if (!isAuto && pendingAutoStartMap.size === 0) return false;
440
+
441
+ const lastMsg = event.messages[event.messages.length - 1];
442
+ if (!lastMsg) return false;
443
+ if (hasToolUse(lastMsg)) return false;
444
+
445
+ const text = extractAssistantText(lastMsg).trim();
446
+ if (!text) return false;
447
+
448
+ // Skip if the LLM is emitting the ready phrase — that is the ready-no-files
449
+ // path, handled by maybeHandleReadyPhraseWithoutFiles.
450
+ if (READY_PHRASE_RE.test(text)) return false;
451
+
452
+ // Skip if the LLM is clearly handing back to the user. Keep the heuristic
453
+ // tight: a trailing question mark on the last non-empty line is the common
454
+ // signal for "I asked the user a question and stopped."
455
+ const lines = text.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
456
+ const lastLine = lines[lines.length - 1] ?? "";
457
+ if (lastLine.endsWith("?")) return false;
458
+
459
+ // Must contain a commit-intent phrase — this is the stall we care about.
460
+ if (!COMMIT_INTENT_RE.test(text)) return false;
461
+
462
+ // Resolve the target basePath + pi for injection. Prefer the pending
463
+ // autostart entry (discuss flow); otherwise we cannot inject.
464
+ const entry = _getPendingAutoStart();
465
+ if (!entry) return false;
466
+ const { ctx, pi, basePath } = entry;
467
+
468
+ const count = (emptyTurnCounterByBase.get(basePath) ?? 0) + 1;
469
+ emptyTurnCounterByBase.set(basePath, count);
470
+
471
+ if (count > MAX_EMPTY_TURN_RETRIES) {
472
+ ctx.ui.notify(
473
+ `Empty-turn recovery: LLM announced intent ${count} times without calling any tool. ` +
474
+ `Stopping auto-nudge.`,
475
+ "error",
476
+ );
477
+ return false; // let the normal flow resolve/pause the unit
478
+ }
479
+
480
+ ctx.ui.notify(
481
+ `Empty-turn detected: LLM announced intent but called no tool. Prompting it to execute.`,
482
+ "info",
483
+ );
484
+
485
+ const nudge =
486
+ `Your last turn announced an action (e.g. "I'll write…" or "Let me call…") ` +
487
+ `but contained no tool call. The system records zero tool-use blocks for ` +
488
+ `that turn. Execute the announced action NOW as a tool call in this turn. ` +
489
+ `Do not describe it again. Retry ${count}/${MAX_EMPTY_TURN_RETRIES}.`;
490
+
491
+ try {
492
+ pi.sendMessage(
493
+ { customType: "gsd-empty-turn-recovery", content: nudge, display: false },
494
+ { triggerTurn: true },
495
+ );
496
+ } catch (e) {
497
+ logWarning("guided", `empty-turn nudge sendMessage failed: ${(e as Error).message}`);
498
+ return false;
499
+ }
500
+ return true;
501
+ }
502
+
282
503
  /**
283
504
  * Extract milestone IDs from PROJECT.md milestone sequence table.
284
505
  * Looks for rows like "| M001 | Name | Status |" and extracts the ID column.
@@ -108,6 +108,7 @@ export function initHealthWidget(ctx: ExtensionContext): void {
108
108
  let data = initialData;
109
109
  let cachedLines: string[] | undefined;
110
110
  let refreshInFlight = false;
111
+ let isDisposed = false;
111
112
 
112
113
  const refresh = async () => {
113
114
  if (refreshInFlight) return;
@@ -115,7 +116,7 @@ export function initHealthWidget(ctx: ExtensionContext): void {
115
116
  try {
116
117
  data = loadHealthWidgetData(basePath);
117
118
  cachedLines = undefined;
118
- _tui.requestRender();
119
+ if (!isDisposed) _tui.requestRender();
119
120
  } catch { /* non-fatal */ } finally {
120
121
  refreshInFlight = false;
121
122
  }
@@ -140,6 +141,7 @@ export function initHealthWidget(ctx: ExtensionContext): void {
140
141
  },
141
142
  invalidate(): void { cachedLines = undefined; cachedWidth = undefined; },
142
143
  dispose(): void {
144
+ isDisposed = true;
143
145
  clearInterval(refreshTimer);
144
146
  },
145
147
  };
@@ -10,7 +10,7 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent
10
10
  import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import { showNextAction } from "../shared/tui.js";
13
- import { nativeIsRepo, nativeInit } from "./native-git-bridge.js";
13
+ import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit } from "./native-git-bridge.js";
14
14
  import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
15
15
  import { gsdRoot } from "./paths.js";
16
16
  import { assertSafeDirectory } from "./validate-directory.js";
@@ -74,6 +74,7 @@ export async function showProjectInit(
74
74
  }
75
75
 
76
76
  // ── Step 2: Git setup ──────────────────────────────────────────────────────
77
+ let didInitGit = false;
77
78
  if (!signals.isGitRepo) {
78
79
  const gitChoice = await showNextAction(ctx, {
79
80
  title: "GSD — Project Setup",
@@ -89,6 +90,7 @@ export async function showProjectInit(
89
90
 
90
91
  if (gitChoice === "init_git") {
91
92
  nativeInit(basePath, prefs.mainBranch);
93
+ didInitGit = true;
92
94
  }
93
95
  } else {
94
96
  // Auto-detect main branch from existing repo
@@ -295,6 +297,18 @@ export async function showProjectInit(
295
297
  ensureGitignore(basePath);
296
298
  untrackRuntimeFiles(basePath);
297
299
 
300
+ // Create initial commit so git log and git worktree work immediately (#4530).
301
+ // Without this, the branch is "unborn" (zero commits) and downstream operations
302
+ // like `git log` and `git worktree add` fail.
303
+ if (didInitGit) {
304
+ try {
305
+ nativeAddAll(basePath);
306
+ nativeCommit(basePath, "chore: init project");
307
+ } catch {
308
+ // Non-fatal — user can commit manually; don't block project init
309
+ }
310
+ }
311
+
298
312
  // Auto-generate codebase map for instant agent orientation
299
313
  try {
300
314
  const result = generateCodebaseMap(basePath);
@@ -39,7 +39,8 @@ export type JournalEventType =
39
39
  | "worktree-create-failed"
40
40
  | "worktree-skip"
41
41
  | "worktree-merge-start"
42
- | "worktree-merge-failed";
42
+ | "worktree-merge-failed"
43
+ | "artifact-verification-retry";
43
44
 
44
45
  /** A single structured event in the journal. */
45
46
  export interface JournalEntry {
@@ -35,6 +35,12 @@ export interface ProviderInfo {
35
35
  export const PROVIDER_REGISTRY: ProviderInfo[] = [
36
36
  // LLM Providers
37
37
  { id: "anthropic", label: "Anthropic (Claude)", category: "llm", envVar: "ANTHROPIC_API_KEY", prefixes: ["sk-ant-"], hasOAuth: true, dashboardUrl: "console.anthropic.com" },
38
+ // Claude Code CLI: routes through the local `claude` binary — no API key,
39
+ // authentication is handled by the CLI's own OAuth flow.
40
+ // Referenced by doctor-providers.ts, auto-model-selection.ts, and others;
41
+ // must be in the canonical registry so all consumers see the same catalog.
42
+ // See: https://github.com/gsd-build/gsd-2/issues/4541
43
+ { id: "claude-code", label: "Claude Code CLI", category: "llm", hasOAuth: true },
38
44
  { id: "openai", label: "OpenAI", category: "llm", envVar: "OPENAI_API_KEY", prefixes: ["sk-"], dashboardUrl: "platform.openai.com/api-keys" },
39
45
  { id: "github-copilot", label: "GitHub Copilot", category: "llm", envVar: "GITHUB_TOKEN", hasOAuth: true },
40
46
  { id: "openai-codex", label: "ChatGPT Plus/Pro (Codex)",category: "llm", hasOAuth: true },
@@ -561,6 +561,23 @@ function bareModelId(modelId: string): string {
561
561
  return modelId.includes("/") ? modelId.split("/").pop()! : modelId;
562
562
  }
563
563
 
564
+ // ─── Provider-specific Tool Limits ─────────────────────────────────────────
565
+
566
+ /**
567
+ * Groq enforces a hard limit of 128 tools per request.
568
+ * Requests exceeding this limit receive a 400 error:
569
+ * "maximum number of items is 128"
570
+ * @see https://console.groq.com/docs/tool-use
571
+ */
572
+ export const GROQ_MAX_TOOLS = 128;
573
+
574
+ /**
575
+ * Provider IDs that map to the Groq API backend.
576
+ * Used to detect Groq at the GSD routing layer where only the provider string
577
+ * is available (the pi-ai openai-completions adapter is shared across providers).
578
+ */
579
+ const GROQ_PROVIDER_IDS = new Set(["groq"]);
580
+
564
581
  // ─── Tool Compatibility Filter (ADR-005 Phase 3) ───────────────────────────
565
582
 
566
583
  /**
@@ -588,10 +605,17 @@ export function isToolCompatibleWithProvider(
588
605
  /**
589
606
  * Filter a list of tool names to only those compatible with a provider.
590
607
  * Used by the routing pipeline to adjust tool sets when switching providers.
608
+ *
609
+ * @param toolNames - The full list of active tool names to filter.
610
+ * @param providerApi - The pi-ai API string (e.g. "openai-completions").
611
+ * @param provider - Optional provider ID (e.g. "groq"). Used to apply
612
+ * provider-specific limits that can't be expressed as API-level capabilities
613
+ * (e.g. Groq's 128-tool hard limit on the shared openai-completions adapter).
591
614
  */
592
615
  export function filterToolsForProvider(
593
616
  toolNames: string[],
594
617
  providerApi: string,
618
+ provider?: string,
595
619
  ): { compatible: string[]; filtered: string[] } {
596
620
  const providerCaps = getProviderCapabilities(providerApi);
597
621
 
@@ -611,6 +635,17 @@ export function filterToolsForProvider(
611
635
  }
612
636
  }
613
637
 
638
+ // Groq enforces a hard limit of 128 tools per request (#4376).
639
+ // Trim the compatible list to GROQ_MAX_TOOLS and move the excess to filtered.
640
+ if (provider && GROQ_PROVIDER_IDS.has(provider) && compatible.length > GROQ_MAX_TOOLS) {
641
+ const trimmed = compatible.splice(GROQ_MAX_TOOLS);
642
+ filtered.push(...trimmed);
643
+ console.warn(
644
+ `[gsd] Groq tool limit: ${compatible.length + trimmed.length} tools active but Groq allows at most ${GROQ_MAX_TOOLS}. ` +
645
+ `Trimming to the first ${GROQ_MAX_TOOLS} tools. Removed: ${trimmed.join(", ")}`,
646
+ );
647
+ }
648
+
614
649
  return { compatible, filtered };
615
650
  }
616
651
 
@@ -620,11 +655,17 @@ export function filterToolsForProvider(
620
655
  *
621
656
  * This is a hard filter only — it removes tools that would fail at the
622
657
  * provider level. It does NOT remove tools based on soft heuristics.
658
+ *
659
+ * @param activeToolNames - The full list of currently active tool names.
660
+ * @param selectedModelApi - The pi-ai API string for the selected model.
661
+ * @param provider - Optional provider ID (e.g. "groq") for provider-specific
662
+ * limits beyond what the API-level capability profile expresses.
623
663
  */
624
664
  export function adjustToolSet(
625
665
  activeToolNames: string[],
626
666
  selectedModelApi: string,
667
+ provider?: string,
627
668
  ): { toolNames: string[]; removedTools: string[] } {
628
- const { compatible, filtered } = filterToolsForProvider(activeToolNames, selectedModelApi);
669
+ const { compatible, filtered } = filterToolsForProvider(activeToolNames, selectedModelApi, provider);
629
670
  return { toolNames: compatible, removedTools: filtered };
630
671
  }