gsd-pi 2.76.0 → 2.77.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (536) hide show
  1. package/README.md +45 -25
  2. package/dist/claude-cli-check.js +32 -3
  3. package/dist/mcp-server.d.ts +7 -0
  4. package/dist/mcp-server.js +35 -1
  5. package/dist/onboarding.js +45 -0
  6. package/dist/resource-loader.d.ts +1 -1
  7. package/dist/resource-loader.js +2 -8
  8. package/dist/resources/agents/researcher.md +1 -1
  9. package/dist/resources/extensions/claude-code-cli/readiness.js +31 -8
  10. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +77 -17
  11. package/dist/resources/extensions/gsd/auto/loop.js +9 -0
  12. package/dist/resources/extensions/gsd/auto/phases.js +104 -11
  13. package/dist/resources/extensions/gsd/auto/run-unit.js +38 -2
  14. package/dist/resources/extensions/gsd/auto/session.js +22 -1
  15. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -3
  16. package/dist/resources/extensions/gsd/auto-model-selection.js +53 -16
  17. package/dist/resources/extensions/gsd/auto-post-unit.js +25 -2
  18. package/dist/resources/extensions/gsd/auto-prompts.js +14 -0
  19. package/dist/resources/extensions/gsd/auto-recovery.js +32 -1
  20. package/dist/resources/extensions/gsd/auto-start.js +58 -57
  21. package/dist/resources/extensions/gsd/auto-verification.js +33 -0
  22. package/dist/resources/extensions/gsd/auto-worktree.js +51 -53
  23. package/dist/resources/extensions/gsd/auto.js +70 -28
  24. package/dist/resources/extensions/gsd/blocked-models.js +68 -0
  25. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +93 -1
  26. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
  27. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
  28. package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +3 -0
  29. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +12 -0
  30. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +52 -6
  31. package/dist/resources/extensions/gsd/bootstrap/system-context.js +84 -23
  32. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +34 -2
  33. package/dist/resources/extensions/gsd/clean-root-preflight.js +93 -0
  34. package/dist/resources/extensions/gsd/commands-extract-learnings.js +54 -89
  35. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +968 -23
  36. package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
  37. package/dist/resources/extensions/gsd/complexity-classifier.js +5 -3
  38. package/dist/resources/extensions/gsd/db-writer.js +88 -16
  39. package/dist/resources/extensions/gsd/doctor-git-checks.js +23 -29
  40. package/dist/resources/extensions/gsd/doctor-providers.js +51 -5
  41. package/dist/resources/extensions/gsd/ecosystem/gsd-extension-api.js +1 -0
  42. package/dist/resources/extensions/gsd/error-classifier.js +31 -3
  43. package/dist/resources/extensions/gsd/exec-history.js +120 -0
  44. package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
  45. package/dist/resources/extensions/gsd/gitignore.js +1 -0
  46. package/dist/resources/extensions/gsd/gsd-db.js +168 -23
  47. package/dist/resources/extensions/gsd/guided-flow.js +190 -1
  48. package/dist/resources/extensions/gsd/health-widget.js +4 -1
  49. package/dist/resources/extensions/gsd/hook-emitter.js +108 -0
  50. package/dist/resources/extensions/gsd/init-wizard.js +15 -1
  51. package/dist/resources/extensions/gsd/key-manager.js +28 -0
  52. package/dist/resources/extensions/gsd/memory-backfill.js +126 -0
  53. package/dist/resources/extensions/gsd/memory-store.js +19 -0
  54. package/dist/resources/extensions/gsd/model-router.js +36 -3
  55. package/dist/resources/extensions/gsd/pre-execution-checks.js +44 -9
  56. package/dist/resources/extensions/gsd/preferences-types.js +9 -0
  57. package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
  58. package/dist/resources/extensions/gsd/preferences.js +17 -17
  59. package/dist/resources/extensions/gsd/prompt-loader.js +22 -7
  60. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  61. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  62. package/dist/resources/extensions/gsd/prompts/debug-diagnose.md +2 -0
  63. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  64. package/dist/resources/extensions/gsd/prompts/discuss.md +29 -2
  65. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -2
  66. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  67. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -0
  68. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -0
  69. package/dist/resources/extensions/gsd/safety/evidence-collector.js +96 -0
  70. package/dist/resources/extensions/gsd/safety/file-change-validator.js +13 -5
  71. package/dist/resources/extensions/gsd/safety/safety-harness.js +5 -1
  72. package/dist/resources/extensions/gsd/state.js +43 -4
  73. package/dist/resources/extensions/gsd/token-counter.js +22 -5
  74. package/dist/resources/extensions/gsd/tools/complete-milestone.js +16 -10
  75. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
  76. package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
  77. package/dist/resources/extensions/gsd/tools/memory-tools.js +26 -1
  78. package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
  79. package/dist/resources/extensions/gsd/uok/plan-v2.js +20 -3
  80. package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
  81. package/dist/resources/extensions/gsd/workflow-templates/spike.md +6 -0
  82. package/dist/resources/extensions/gsd/worktree-resolver.js +50 -10
  83. package/dist/resources/extensions/search-the-web/command-search-provider.js +5 -4
  84. package/dist/resources/extensions/search-the-web/native-search.js +45 -13
  85. package/dist/resources/skills/api-design/SKILL.md +190 -0
  86. package/dist/resources/skills/create-mcp-server/SKILL.md +121 -0
  87. package/dist/resources/skills/decompose-into-slices/SKILL.md +139 -0
  88. package/dist/resources/skills/dependency-upgrade/SKILL.md +158 -0
  89. package/dist/resources/skills/design-an-interface/SKILL.md +102 -0
  90. package/dist/resources/skills/forensics/SKILL.md +153 -0
  91. package/dist/resources/skills/grill-me/SKILL.md +93 -0
  92. package/dist/resources/skills/handoff/SKILL.md +121 -0
  93. package/dist/resources/skills/observability/SKILL.md +174 -0
  94. package/dist/resources/skills/security-review/SKILL.md +181 -0
  95. package/dist/resources/skills/spike-wrap-up/SKILL.md +138 -0
  96. package/dist/resources/skills/tdd/SKILL.md +112 -0
  97. package/dist/resources/skills/verify-before-complete/SKILL.md +98 -0
  98. package/dist/resources/skills/write-docs/SKILL.md +82 -0
  99. package/dist/resources/skills/write-milestone-brief/SKILL.md +135 -0
  100. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  101. package/dist/web/standalone/.next/BUILD_ID +1 -1
  102. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  103. package/dist/web/standalone/.next/build-manifest.json +2 -2
  104. package/dist/web/standalone/.next/required-server-files.json +1 -1
  105. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  106. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  114. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  119. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  120. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app/index.html +1 -1
  122. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  123. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  126. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  128. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  129. package/dist/web/standalone/.next/server/chunks/6897.js +2 -2
  130. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/middleware-manifest.json +1 -1
  132. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  133. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  134. package/dist/web/standalone/server.js +1 -1
  135. package/dist/welcome-screen.js +6 -1
  136. package/dist/wizard.js +2 -0
  137. package/package.json +1 -1
  138. package/packages/daemon/package.json +2 -2
  139. package/packages/mcp-server/dist/remote-questions.d.ts +45 -0
  140. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -0
  141. package/packages/mcp-server/dist/remote-questions.js +732 -0
  142. package/packages/mcp-server/dist/remote-questions.js.map +1 -0
  143. package/packages/mcp-server/dist/server.d.ts +7 -0
  144. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  145. package/packages/mcp-server/dist/server.js +70 -8
  146. package/packages/mcp-server/dist/server.js.map +1 -1
  147. package/packages/mcp-server/dist/session-manager.d.ts +14 -0
  148. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -1
  149. package/packages/mcp-server/dist/session-manager.js +49 -1
  150. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  151. package/packages/mcp-server/dist/workflow-tools.d.ts +1 -1
  152. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  153. package/packages/mcp-server/dist/workflow-tools.js +163 -25
  154. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  155. package/packages/mcp-server/package.json +4 -3
  156. package/packages/mcp-server/src/mcp-server.test.ts +67 -0
  157. package/packages/mcp-server/src/remote-questions.test.ts +294 -0
  158. package/packages/mcp-server/src/remote-questions.ts +916 -0
  159. package/packages/mcp-server/src/server.ts +89 -14
  160. package/packages/mcp-server/src/session-manager.ts +43 -1
  161. package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
  162. package/packages/mcp-server/src/workflow-tools.ts +215 -43
  163. package/packages/mcp-server/tsconfig.test.json +19 -0
  164. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  165. package/packages/native/package.json +1 -1
  166. package/packages/pi-agent-core/dist/agent-loop.js +12 -0
  167. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  168. package/packages/pi-agent-core/dist/types.d.ts +30 -0
  169. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  170. package/packages/pi-agent-core/dist/types.js.map +1 -1
  171. package/packages/pi-agent-core/package.json +1 -1
  172. package/packages/pi-agent-core/src/agent-loop.ts +14 -0
  173. package/packages/pi-agent-core/src/types.ts +34 -0
  174. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  175. package/packages/pi-ai/dist/models/custom.d.ts +38 -0
  176. package/packages/pi-ai/dist/models/custom.d.ts.map +1 -1
  177. package/packages/pi-ai/dist/models/custom.js +41 -0
  178. package/packages/pi-ai/dist/models/custom.js.map +1 -1
  179. package/packages/pi-ai/dist/providers/anthropic-auth.test.js +1 -1
  180. package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -1
  181. package/packages/pi-ai/dist/providers/anthropic-bearer-auth.test.d.ts +2 -0
  182. package/packages/pi-ai/dist/providers/anthropic-bearer-auth.test.d.ts.map +1 -0
  183. package/packages/pi-ai/dist/providers/anthropic-bearer-auth.test.js +13 -0
  184. package/packages/pi-ai/dist/providers/anthropic-bearer-auth.test.js.map +1 -0
  185. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  186. package/packages/pi-ai/dist/providers/anthropic-shared.js +27 -4
  187. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  188. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  189. package/packages/pi-ai/dist/providers/anthropic.js +13 -4
  190. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  191. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts +2 -0
  192. package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts.map +1 -0
  193. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js +80 -0
  194. package/packages/pi-ai/dist/providers/minimax-tool-name.test.js.map +1 -0
  195. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  196. package/packages/pi-ai/dist/providers/openai-completions.js +60 -15
  197. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  198. package/packages/pi-ai/dist/providers/simple-options.d.ts +10 -0
  199. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  200. package/packages/pi-ai/dist/providers/simple-options.js +16 -1
  201. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  202. package/packages/pi-ai/dist/providers/think-tag-parser.d.ts +17 -0
  203. package/packages/pi-ai/dist/providers/think-tag-parser.d.ts.map +1 -0
  204. package/packages/pi-ai/dist/providers/think-tag-parser.js +75 -0
  205. package/packages/pi-ai/dist/providers/think-tag-parser.js.map +1 -0
  206. package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts +2 -0
  207. package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts.map +1 -0
  208. package/packages/pi-ai/dist/providers/think-tag-parser.test.js +41 -0
  209. package/packages/pi-ai/dist/providers/think-tag-parser.test.js.map +1 -0
  210. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  211. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +12 -2
  212. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  213. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js +164 -14
  214. package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js.map +1 -1
  215. package/packages/pi-ai/dist/utils/oauth/google-antigravity.d.ts.map +1 -1
  216. package/packages/pi-ai/dist/utils/oauth/google-antigravity.js +15 -3
  217. package/packages/pi-ai/dist/utils/oauth/google-antigravity.js.map +1 -1
  218. package/packages/pi-ai/dist/utils/oauth/google-antigravity.test.d.ts +2 -0
  219. package/packages/pi-ai/dist/utils/oauth/google-antigravity.test.d.ts.map +1 -0
  220. package/packages/pi-ai/dist/utils/oauth/google-antigravity.test.js +67 -0
  221. package/packages/pi-ai/dist/utils/oauth/google-antigravity.test.js.map +1 -0
  222. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -1
  223. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js +16 -3
  224. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.js.map +1 -1
  225. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.test.d.ts +2 -0
  226. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.test.d.ts.map +1 -0
  227. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.test.js +67 -0
  228. package/packages/pi-ai/dist/utils/oauth/google-gemini-cli.test.js.map +1 -0
  229. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +2 -0
  230. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +1 -0
  231. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +289 -0
  232. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +1 -0
  233. package/packages/pi-ai/package.json +1 -1
  234. package/packages/pi-ai/src/models/custom.ts +42 -0
  235. package/packages/pi-ai/src/providers/anthropic-auth.test.ts +1 -1
  236. package/packages/pi-ai/src/providers/anthropic-bearer-auth.test.ts +26 -0
  237. package/packages/pi-ai/src/providers/anthropic-shared.ts +26 -5
  238. package/packages/pi-ai/src/providers/anthropic.ts +15 -4
  239. package/packages/pi-ai/src/providers/minimax-tool-name.test.ts +98 -0
  240. package/packages/pi-ai/src/providers/openai-completions.ts +57 -16
  241. package/packages/pi-ai/src/providers/simple-options.ts +17 -1
  242. package/packages/pi-ai/src/providers/think-tag-parser.test.ts +44 -0
  243. package/packages/pi-ai/src/providers/think-tag-parser.ts +94 -0
  244. package/packages/pi-ai/src/utils/oauth/github-copilot.test.ts +200 -23
  245. package/packages/pi-ai/src/utils/oauth/github-copilot.ts +12 -2
  246. package/packages/pi-ai/src/utils/oauth/google-antigravity.test.ts +84 -0
  247. package/packages/pi-ai/src/utils/oauth/google-antigravity.ts +15 -5
  248. package/packages/pi-ai/src/utils/oauth/google-gemini-cli.test.ts +84 -0
  249. package/packages/pi-ai/src/utils/oauth/google-gemini-cli.ts +16 -5
  250. package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +363 -0
  251. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  252. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +3 -2
  253. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  254. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +2 -0
  255. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  256. package/packages/pi-coding-agent/dist/core/agent-session.js +32 -2
  257. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  258. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +1 -1
  259. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
  260. package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
  261. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  262. package/packages/pi-coding-agent/dist/core/extensions/loader.js +4 -0
  263. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  264. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +35 -2
  265. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  266. package/packages/pi-coding-agent/dist/core/extensions/runner.js +233 -0
  267. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  268. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +205 -2
  269. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  270. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  271. package/packages/pi-coding-agent/dist/core/hooks-runner.d.ts +53 -0
  272. package/packages/pi-coding-agent/dist/core/hooks-runner.d.ts.map +1 -0
  273. package/packages/pi-coding-agent/dist/core/hooks-runner.js +337 -0
  274. package/packages/pi-coding-agent/dist/core/hooks-runner.js.map +1 -0
  275. package/packages/pi-coding-agent/dist/core/hooks-runner.test.d.ts +2 -0
  276. package/packages/pi-coding-agent/dist/core/hooks-runner.test.d.ts.map +1 -0
  277. package/packages/pi-coding-agent/dist/core/hooks-runner.test.js +234 -0
  278. package/packages/pi-coding-agent/dist/core/hooks-runner.test.js.map +1 -0
  279. package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
  280. package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
  281. package/packages/pi-coding-agent/dist/core/index.js +1 -0
  282. package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
  283. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +3 -1
  284. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -1
  285. package/packages/pi-coding-agent/dist/core/model-discovery.js +92 -12
  286. package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -1
  287. package/packages/pi-coding-agent/dist/core/model-discovery.test.js +16 -1
  288. package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -1
  289. package/packages/pi-coding-agent/dist/core/model-registry-auth-header.test.d.ts +2 -0
  290. package/packages/pi-coding-agent/dist/core/model-registry-auth-header.test.d.ts.map +1 -0
  291. package/packages/pi-coding-agent/dist/core/model-registry-auth-header.test.js +40 -0
  292. package/packages/pi-coding-agent/dist/core/model-registry-auth-header.test.js.map +1 -0
  293. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts +2 -0
  294. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts.map +1 -0
  295. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js +203 -0
  296. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js.map +1 -0
  297. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +61 -1
  298. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -1
  299. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +5 -0
  300. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  301. package/packages/pi-coding-agent/dist/core/model-registry.js +90 -10
  302. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  303. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
  304. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
  305. package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
  306. package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
  307. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
  308. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
  309. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
  310. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
  311. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  312. package/packages/pi-coding-agent/dist/core/session-manager.js +10 -6
  313. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  314. package/packages/pi-coding-agent/dist/core/session-manager.test.js +45 -1
  315. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  316. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +55 -0
  317. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  318. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  319. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  320. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  321. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  322. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
  323. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  324. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
  325. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  326. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  327. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +13 -7
  328. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  329. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
  330. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  331. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
  332. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  333. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  334. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +13 -1
  335. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  336. package/packages/pi-coding-agent/package.json +1 -1
  337. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +3 -2
  338. package/packages/pi-coding-agent/src/core/agent-session.ts +38 -2
  339. package/packages/pi-coding-agent/src/core/extensions/index.ts +16 -0
  340. package/packages/pi-coding-agent/src/core/extensions/loader.ts +5 -0
  341. package/packages/pi-coding-agent/src/core/extensions/runner.ts +351 -0
  342. package/packages/pi-coding-agent/src/core/extensions/types.ts +258 -0
  343. package/packages/pi-coding-agent/src/core/hooks-runner.test.ts +269 -0
  344. package/packages/pi-coding-agent/src/core/hooks-runner.ts +460 -0
  345. package/packages/pi-coding-agent/src/core/index.ts +10 -0
  346. package/packages/pi-coding-agent/src/core/model-discovery.test.ts +19 -0
  347. package/packages/pi-coding-agent/src/core/model-discovery.ts +99 -12
  348. package/packages/pi-coding-agent/src/core/model-registry-auth-header.test.ts +44 -0
  349. package/packages/pi-coding-agent/src/core/model-registry-custom-caps.test.ts +245 -0
  350. package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +75 -0
  351. package/packages/pi-coding-agent/src/core/model-registry.ts +102 -10
  352. package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
  353. package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
  354. package/packages/pi-coding-agent/src/core/session-manager.test.ts +65 -1
  355. package/packages/pi-coding-agent/src/core/session-manager.ts +10 -6
  356. package/packages/pi-coding-agent/src/core/settings-manager.ts +57 -0
  357. package/packages/pi-coding-agent/src/index.ts +16 -0
  358. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
  359. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -7
  360. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
  361. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +13 -1
  362. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  363. package/packages/pi-tui/package.json +1 -1
  364. package/packages/rpc-client/package.json +1 -1
  365. package/pkg/package.json +1 -1
  366. package/scripts/link-workspace-packages.cjs +1 -0
  367. package/src/resources/agents/researcher.md +1 -1
  368. package/src/resources/extensions/claude-code-cli/readiness.ts +32 -8
  369. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +78 -17
  370. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +149 -5
  371. package/src/resources/extensions/gsd/auto/loop-deps.ts +14 -0
  372. package/src/resources/extensions/gsd/auto/loop.ts +9 -0
  373. package/src/resources/extensions/gsd/auto/phases.ts +131 -10
  374. package/src/resources/extensions/gsd/auto/run-unit.ts +40 -2
  375. package/src/resources/extensions/gsd/auto/session.ts +35 -2
  376. package/src/resources/extensions/gsd/auto-dispatch.ts +16 -3
  377. package/src/resources/extensions/gsd/auto-model-selection.ts +71 -15
  378. package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
  379. package/src/resources/extensions/gsd/auto-prompts.ts +28 -1
  380. package/src/resources/extensions/gsd/auto-recovery.ts +26 -1
  381. package/src/resources/extensions/gsd/auto-start.ts +60 -68
  382. package/src/resources/extensions/gsd/auto-verification.ts +33 -0
  383. package/src/resources/extensions/gsd/auto-worktree.ts +62 -63
  384. package/src/resources/extensions/gsd/auto.ts +73 -28
  385. package/src/resources/extensions/gsd/blocked-models.ts +98 -0
  386. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +120 -1
  387. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
  388. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
  389. package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +5 -0
  390. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +15 -0
  391. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +54 -6
  392. package/src/resources/extensions/gsd/bootstrap/system-context.ts +89 -26
  393. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +35 -2
  394. package/src/resources/extensions/gsd/clean-root-preflight.ts +111 -0
  395. package/src/resources/extensions/gsd/commands-extract-learnings.ts +55 -90
  396. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +898 -32
  397. package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
  398. package/src/resources/extensions/gsd/complexity-classifier.ts +5 -3
  399. package/src/resources/extensions/gsd/db-writer.ts +88 -17
  400. package/src/resources/extensions/gsd/doctor-git-checks.ts +23 -27
  401. package/src/resources/extensions/gsd/doctor-providers.ts +59 -6
  402. package/src/resources/extensions/gsd/ecosystem/gsd-extension-api.ts +2 -0
  403. package/src/resources/extensions/gsd/error-classifier.ts +36 -3
  404. package/src/resources/extensions/gsd/exec-history.ts +153 -0
  405. package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
  406. package/src/resources/extensions/gsd/gitignore.ts +1 -1
  407. package/src/resources/extensions/gsd/gsd-db.ts +186 -23
  408. package/src/resources/extensions/gsd/guided-flow.ts +222 -1
  409. package/src/resources/extensions/gsd/health-widget.ts +3 -1
  410. package/src/resources/extensions/gsd/hook-emitter.ts +188 -0
  411. package/src/resources/extensions/gsd/init-wizard.ts +15 -1
  412. package/src/resources/extensions/gsd/journal.ts +2 -1
  413. package/src/resources/extensions/gsd/key-manager.ts +28 -0
  414. package/src/resources/extensions/gsd/memory-backfill.ts +140 -0
  415. package/src/resources/extensions/gsd/memory-store.ts +26 -0
  416. package/src/resources/extensions/gsd/model-router.ts +42 -1
  417. package/src/resources/extensions/gsd/pre-execution-checks.ts +46 -10
  418. package/src/resources/extensions/gsd/preferences-types.ts +46 -0
  419. package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
  420. package/src/resources/extensions/gsd/preferences.ts +17 -17
  421. package/src/resources/extensions/gsd/prompt-loader.ts +30 -7
  422. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  423. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -2
  424. package/src/resources/extensions/gsd/prompts/debug-diagnose.md +2 -0
  425. package/src/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  426. package/src/resources/extensions/gsd/prompts/discuss.md +29 -2
  427. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -2
  428. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  429. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -0
  430. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -0
  431. package/src/resources/extensions/gsd/safety/evidence-collector.ts +119 -0
  432. package/src/resources/extensions/gsd/safety/file-change-validator.ts +17 -4
  433. package/src/resources/extensions/gsd/safety/safety-harness.ts +9 -0
  434. package/src/resources/extensions/gsd/state.ts +45 -4
  435. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +188 -2
  436. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +95 -1
  437. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +12 -0
  438. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +49 -0
  439. package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +141 -0
  440. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +33 -3
  441. package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +38 -0
  442. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +23 -0
  443. package/src/resources/extensions/gsd/tests/blocked-models.test.ts +98 -0
  444. package/src/resources/extensions/gsd/tests/bundled-skill-triggers.test.ts +54 -0
  445. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +186 -0
  446. package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +68 -66
  447. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
  448. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +61 -1
  449. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  450. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  451. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +3 -3
  452. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
  453. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +42 -0
  454. package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +8 -4
  455. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +148 -3
  456. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +1 -1
  457. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +306 -1
  458. package/src/resources/extensions/gsd/tests/escalation.test.ts +1 -1
  459. package/src/resources/extensions/gsd/tests/exec-history.test.ts +237 -0
  460. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
  461. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +58 -0
  462. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +40 -9
  463. package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +62 -0
  464. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +447 -1
  465. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
  466. package/src/resources/extensions/gsd/tests/integration/doctor-git-symlink-cwd.test.ts +11 -0
  467. package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +78 -0
  468. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +1 -0
  469. package/src/resources/extensions/gsd/tests/integration/gitignore-tracked-gsd.test.ts +1 -0
  470. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +30 -0
  471. package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +1 -1
  472. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
  473. package/src/resources/extensions/gsd/tests/issue-4540-regressions.test.ts +288 -0
  474. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +37 -0
  475. package/src/resources/extensions/gsd/tests/key-manager.test.ts +9 -0
  476. package/src/resources/extensions/gsd/tests/load-memory-block.test.ts +36 -0
  477. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  478. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +12 -0
  479. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  480. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +19 -0
  481. package/src/resources/extensions/gsd/tests/plan-gate-failed-doctor-heal-hint.test.ts +37 -0
  482. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
  483. package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +272 -0
  484. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +356 -0
  485. package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
  486. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +44 -0
  487. package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +49 -0
  488. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +103 -4
  489. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +388 -0
  490. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +9 -3
  491. package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +230 -0
  492. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +205 -0
  493. package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
  494. package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +413 -0
  495. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +32 -40
  496. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +56 -0
  497. package/src/resources/extensions/gsd/tests/token-counter.test.ts +105 -1
  498. package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -0
  499. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +23 -0
  500. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +9 -3
  501. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +65 -2
  502. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +35 -0
  503. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +6 -1
  504. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +78 -5
  505. package/src/resources/extensions/gsd/tests/write-gate.test.ts +64 -0
  506. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
  507. package/src/resources/extensions/gsd/token-counter.ts +22 -5
  508. package/src/resources/extensions/gsd/tools/complete-milestone.ts +15 -9
  509. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
  510. package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
  511. package/src/resources/extensions/gsd/tools/memory-tools.ts +31 -1
  512. package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
  513. package/src/resources/extensions/gsd/uok/plan-v2.ts +26 -3
  514. package/src/resources/extensions/gsd/workflow-logger.ts +4 -1
  515. package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
  516. package/src/resources/extensions/gsd/workflow-templates/spike.md +6 -0
  517. package/src/resources/extensions/gsd/worktree-resolver.ts +54 -9
  518. package/src/resources/extensions/search-the-web/command-search-provider.ts +5 -4
  519. package/src/resources/extensions/search-the-web/native-search.ts +48 -12
  520. package/src/resources/skills/api-design/SKILL.md +190 -0
  521. package/src/resources/skills/create-mcp-server/SKILL.md +121 -0
  522. package/src/resources/skills/decompose-into-slices/SKILL.md +139 -0
  523. package/src/resources/skills/dependency-upgrade/SKILL.md +158 -0
  524. package/src/resources/skills/design-an-interface/SKILL.md +102 -0
  525. package/src/resources/skills/forensics/SKILL.md +153 -0
  526. package/src/resources/skills/grill-me/SKILL.md +93 -0
  527. package/src/resources/skills/handoff/SKILL.md +121 -0
  528. package/src/resources/skills/observability/SKILL.md +174 -0
  529. package/src/resources/skills/security-review/SKILL.md +181 -0
  530. package/src/resources/skills/spike-wrap-up/SKILL.md +138 -0
  531. package/src/resources/skills/tdd/SKILL.md +112 -0
  532. package/src/resources/skills/verify-before-complete/SKILL.md +98 -0
  533. package/src/resources/skills/write-docs/SKILL.md +82 -0
  534. package/src/resources/skills/write-milestone-brief/SKILL.md +135 -0
  535. /package/dist/web/standalone/.next/static/{ssX7BLv3Dw9Fb4CtrCGeR → pV-mPo7rYGb5JBC09C8GG}/_buildManifest.js +0 -0
  536. /package/dist/web/standalone/.next/static/{ssX7BLv3Dw9Fb4CtrCGeR → pV-mPo7rYGb5JBC09C8GG}/_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 = 20;
183
+ const SCHEMA_VERSION = 22;
184
184
 
185
185
  function indexExists(db: DbAdapter, name: string): boolean {
186
186
  return !!db.prepare(
@@ -283,7 +283,8 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
283
283
  superseded_by TEXT DEFAULT NULL,
284
284
  hit_count INTEGER NOT NULL DEFAULT 0,
285
285
  scope TEXT NOT NULL DEFAULT 'project',
286
- tags TEXT NOT NULL DEFAULT '[]'
286
+ tags TEXT NOT NULL DEFAULT '[]',
287
+ structured_fields TEXT DEFAULT NULL
287
288
  )
288
289
  `);
289
290
 
@@ -557,11 +558,7 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
557
558
  `);
558
559
 
559
560
  db.exec("CREATE INDEX IF NOT EXISTS idx_memories_active ON memories(superseded_by)");
560
- db.exec("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope)");
561
- db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_kind ON memory_sources(kind)");
562
- db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_scope ON memory_sources(scope)");
563
- db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_from ON memory_relations(from_id)");
564
- db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_to ON memory_relations(to_id)");
561
+
565
562
  db.exec("CREATE INDEX IF NOT EXISTS idx_replan_history_milestone ON replan_history(milestone_id, created_at)");
566
563
 
567
564
  // v13 indexes — hot-path dispatch queries
@@ -579,9 +576,6 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
579
576
  db.exec("CREATE INDEX IF NOT EXISTS idx_turn_git_tx_turn ON turn_git_transactions(trace_id, turn_id)");
580
577
  db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_trace ON audit_events(trace_id, ts)");
581
578
  db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_turn ON audit_events(trace_id, turn_id, ts)");
582
- // ADR-011 Phase 2 — also created by the v17 migration; fresh installs
583
- // skip migrations so the index must be created here too.
584
- db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_escalation_pending ON tasks(milestone_id, slice_id, escalation_pending)");
585
579
 
586
580
  db.exec(`CREATE VIEW IF NOT EXISTS active_decisions AS SELECT * FROM decisions WHERE superseded_by IS NULL`);
587
581
  db.exec(`CREATE VIEW IF NOT EXISTS active_requirements AS SELECT * FROM requirements WHERE superseded_by IS NULL`);
@@ -589,6 +583,17 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
589
583
 
590
584
  const existing = db.prepare("SELECT count(*) as cnt FROM schema_version").get();
591
585
  if (existing && (existing["cnt"] as number) === 0) {
586
+ // Fresh install — all tables are created above with the full current schema,
587
+ // so it is safe to create all migration-specific indexes here. For existing
588
+ // databases these indexes are created inside the individual migration guards
589
+ // in migrateSchema() after the corresponding columns have been added.
590
+ db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_escalation_pending ON tasks(milestone_id, slice_id, escalation_pending)");
591
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope)");
592
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_kind ON memory_sources(kind)");
593
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_scope ON memory_sources(scope)");
594
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_from ON memory_relations(from_id)");
595
+ db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_to ON memory_relations(to_id)");
596
+
592
597
  db.prepare(
593
598
  "INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)",
594
599
  ).run({
@@ -932,19 +937,24 @@ function migrateSchema(db: DbAdapter): void {
932
937
  }
933
938
 
934
939
  if (currentVersion < 12) {
940
+ // NOTE: The original DDL used COALESCE(task_id, '') in the PRIMARY KEY
941
+ // expression, which is invalid SQLite syntax and causes startup errors on
942
+ // DBs that migrate through v12. The corrected DDL uses
943
+ // task_id TEXT NOT NULL DEFAULT '' with a plain column list PK. DBs that
944
+ // were created with the broken DDL are repaired by the v22 migration below.
935
945
  db.exec(`
936
946
  CREATE TABLE IF NOT EXISTS quality_gates (
937
947
  milestone_id TEXT NOT NULL,
938
948
  slice_id TEXT NOT NULL,
939
949
  gate_id TEXT NOT NULL,
940
950
  scope TEXT NOT NULL DEFAULT 'slice',
941
- task_id TEXT DEFAULT NULL,
951
+ task_id TEXT NOT NULL DEFAULT '',
942
952
  status TEXT NOT NULL DEFAULT 'pending',
943
953
  verdict TEXT NOT NULL DEFAULT '',
944
954
  rationale TEXT NOT NULL DEFAULT '',
945
955
  findings TEXT NOT NULL DEFAULT '',
946
956
  evaluated_at TEXT DEFAULT NULL,
947
- PRIMARY KEY (milestone_id, slice_id, gate_id, COALESCE(task_id, '')),
957
+ PRIMARY KEY (milestone_id, slice_id, gate_id, task_id),
948
958
  FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
949
959
  )
950
960
  `);
@@ -1102,6 +1112,10 @@ function migrateSchema(db: DbAdapter): void {
1102
1112
  tags TEXT NOT NULL DEFAULT '[]'
1103
1113
  )
1104
1114
  `);
1115
+ // If memory_sources already existed before v18 (created by an earlier
1116
+ // version of initSchema that lacked scope/tags), add the missing columns.
1117
+ ensureColumn(db, "memory_sources", "scope", `ALTER TABLE memory_sources ADD COLUMN scope TEXT NOT NULL DEFAULT 'project'`);
1118
+ ensureColumn(db, "memory_sources", "tags", `ALTER TABLE memory_sources ADD COLUMN tags TEXT NOT NULL DEFAULT '[]'`);
1105
1119
  db.exec("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope)");
1106
1120
  db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_kind ON memory_sources(kind)");
1107
1121
  db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_scope ON memory_sources(scope)");
@@ -1157,6 +1171,67 @@ function migrateSchema(db: DbAdapter): void {
1157
1171
  });
1158
1172
  }
1159
1173
 
1174
+ if (currentVersion < 21) {
1175
+ // ADR-013 Step 2: preserve structured fields (gsd_save_decision's
1176
+ // scope/decision/choice/rationale/made_by/revisable) on memories rows so
1177
+ // the eventual decisions->memories cutover does not lose schema fidelity.
1178
+ // Nullable JSON column — existing rows stay NULL until backfilled in Step 5.
1179
+ // Use ensureColumn for race-safety (matches v15-v18 pattern; bare ALTER
1180
+ // throws "duplicate column" on the loser of a concurrent open race even
1181
+ // though the transaction wrapper protects the schema_version row).
1182
+ ensureColumn(db, "memories", "structured_fields", "ALTER TABLE memories ADD COLUMN structured_fields TEXT DEFAULT NULL");
1183
+ db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1184
+ ":version": 21,
1185
+ ":applied_at": new Date().toISOString(),
1186
+ });
1187
+ }
1188
+
1189
+ if (currentVersion < 22) {
1190
+ // v22: Repair quality_gates tables that were created by the broken v12
1191
+ // migration (which used COALESCE(task_id, '') as a PK expression — invalid
1192
+ // SQLite DDL). Those DBs have task_id nullable (dflt_value NULL, notnull 0).
1193
+ // Rebuild the table with the correct schema, migrating existing rows via
1194
+ // COALESCE so no data is lost.
1195
+ const qgInfo = db.prepare("PRAGMA table_info(quality_gates)").all() as Array<Record<string, unknown>>;
1196
+ const taskIdCol = qgInfo.find((r) => r["name"] === "task_id");
1197
+ const needsRepair = taskIdCol && (taskIdCol["notnull"] === 0 || taskIdCol["notnull"] === "0");
1198
+ if (needsRepair) {
1199
+ db.exec(`
1200
+ CREATE TABLE quality_gates_new (
1201
+ milestone_id TEXT NOT NULL,
1202
+ slice_id TEXT NOT NULL,
1203
+ gate_id TEXT NOT NULL,
1204
+ scope TEXT NOT NULL DEFAULT 'slice',
1205
+ task_id TEXT NOT NULL DEFAULT '',
1206
+ status TEXT NOT NULL DEFAULT 'pending',
1207
+ verdict TEXT NOT NULL DEFAULT '',
1208
+ rationale TEXT NOT NULL DEFAULT '',
1209
+ findings TEXT NOT NULL DEFAULT '',
1210
+ evaluated_at TEXT DEFAULT NULL,
1211
+ PRIMARY KEY (milestone_id, slice_id, gate_id, task_id),
1212
+ FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
1213
+ )
1214
+ `);
1215
+ db.exec(`
1216
+ INSERT OR IGNORE INTO quality_gates_new
1217
+ (milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
1218
+ SELECT milestone_id, slice_id, gate_id, scope, COALESCE(task_id, ''), status, verdict, rationale, findings, evaluated_at
1219
+ FROM quality_gates
1220
+ `);
1221
+ db.exec("DROP TABLE quality_gates");
1222
+ db.exec("ALTER TABLE quality_gates_new RENAME TO quality_gates");
1223
+ db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
1224
+ }
1225
+ // Ensure scope column exists on quality_gates and assessments (guard
1226
+ // against DBs that somehow lack it after a partial migration).
1227
+ ensureColumn(db, "quality_gates", "scope", "ALTER TABLE quality_gates ADD COLUMN scope TEXT NOT NULL DEFAULT 'slice'");
1228
+ ensureColumn(db, "assessments", "scope", "ALTER TABLE assessments ADD COLUMN scope TEXT NOT NULL DEFAULT ''");
1229
+ db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1230
+ ":version": 22,
1231
+ ":applied_at": new Date().toISOString(),
1232
+ });
1233
+ }
1234
+
1160
1235
  db.exec("COMMIT");
1161
1236
  } catch (err) {
1162
1237
  db.exec("ROLLBACK");
@@ -1169,6 +1244,8 @@ let currentPath: string | null = null;
1169
1244
  let currentPid: number = 0;
1170
1245
  let _exitHandlerRegistered = false;
1171
1246
  let _dbOpenAttempted = false;
1247
+ let _lastDbError: Error | null = null;
1248
+ let _lastDbPhase: "open" | "initSchema" | "vacuum-recovery" | null = null;
1172
1249
 
1173
1250
  export function getDbProvider(): ProviderName | null {
1174
1251
  loadProvider();
@@ -1189,12 +1266,58 @@ export function wasDbOpenAttempted(): boolean {
1189
1266
  return _dbOpenAttempted;
1190
1267
  }
1191
1268
 
1269
+ export function getDbStatus(): {
1270
+ available: boolean;
1271
+ provider: ProviderName | null;
1272
+ attempted: boolean;
1273
+ lastError: Error | null;
1274
+ lastPhase: "open" | "initSchema" | "vacuum-recovery" | null;
1275
+ } {
1276
+ loadProvider();
1277
+ return {
1278
+ available: currentDb !== null,
1279
+ provider: providerName,
1280
+ attempted: _dbOpenAttempted,
1281
+ lastError: _lastDbError,
1282
+ lastPhase: _lastDbPhase,
1283
+ };
1284
+ }
1285
+
1192
1286
  export function openDatabase(path: string): boolean {
1193
1287
  _dbOpenAttempted = true;
1194
1288
  if (currentDb && currentPath !== path) closeDatabase();
1195
1289
  if (currentDb && currentPath === path) return true;
1196
1290
 
1197
- const rawDb = openRawDb(path);
1291
+ // Reset error state only when a new open attempt is actually going to run.
1292
+ _lastDbError = null;
1293
+ _lastDbPhase = null;
1294
+
1295
+ let rawDb: unknown;
1296
+ let fallbackProvider: ProviderName | null = null;
1297
+ let fallbackModule: unknown = null;
1298
+ try {
1299
+ rawDb = openRawDb(path);
1300
+ } catch (primaryErr) {
1301
+ _lastDbPhase = "open";
1302
+ _lastDbError = primaryErr instanceof Error ? primaryErr : new Error(String(primaryErr));
1303
+ // node:sqlite loaded but failed to open this file — try better-sqlite3 as fallback.
1304
+ if (providerName === "node:sqlite") {
1305
+ try {
1306
+ const mod = _require("better-sqlite3");
1307
+ const Db = (mod && mod.default) ? mod.default : mod;
1308
+ if (typeof Db === "function") {
1309
+ rawDb = new Db(path);
1310
+ fallbackProvider = "better-sqlite3";
1311
+ fallbackModule = Db;
1312
+ _lastDbError = null;
1313
+ _lastDbPhase = null;
1314
+ }
1315
+ } catch {
1316
+ // fallback unavailable; surface original error
1317
+ }
1318
+ }
1319
+ if (!rawDb) throw primaryErr;
1320
+ }
1198
1321
  if (!rawDb) return false;
1199
1322
 
1200
1323
  const adapter = createAdapter(rawDb);
@@ -1210,15 +1333,25 @@ export function openDatabase(path: string): boolean {
1210
1333
  initSchema(adapter, fileBacked);
1211
1334
  process.stderr.write("gsd-db: recovered corrupt database via VACUUM\n");
1212
1335
  } catch (retryErr) {
1336
+ _lastDbPhase = "vacuum-recovery";
1337
+ _lastDbError = retryErr instanceof Error ? retryErr : new Error(String(retryErr));
1213
1338
  try { adapter.close(); } catch (e) { logWarning("db", `close after VACUUM failed: ${(e as Error).message}`); }
1214
1339
  throw retryErr;
1215
1340
  }
1216
1341
  } else {
1217
- try { adapter.close(); } catch (e) { logWarning("db", `close after VACUUM failed: ${(e as Error).message}`); }
1342
+ _lastDbPhase = "initSchema";
1343
+ _lastDbError = err instanceof Error ? err : new Error(String(err));
1344
+ try { adapter.close(); } catch (e) { logWarning("db", `close after initSchema failed: ${(e as Error).message}`); }
1218
1345
  throw err;
1219
1346
  }
1220
1347
  }
1221
1348
 
1349
+ // Commit fallback provider switch only after open + schema both succeeded.
1350
+ if (fallbackProvider) {
1351
+ providerName = fallbackProvider;
1352
+ providerModule = fallbackModule;
1353
+ }
1354
+
1222
1355
  currentDb = adapter;
1223
1356
  currentPath = path;
1224
1357
  currentPid = process.pid;
@@ -1246,8 +1379,12 @@ export function closeDatabase(): void {
1246
1379
  currentDb = null;
1247
1380
  currentPath = null;
1248
1381
  currentPid = 0;
1249
- _dbOpenAttempted = false;
1250
1382
  }
1383
+ // Reset session-scoped state unconditionally so stale error info from a
1384
+ // failed open doesn't persist into the next open attempt or status check.
1385
+ _dbOpenAttempted = false;
1386
+ _lastDbError = null;
1387
+ _lastDbPhase = null;
1251
1388
  }
1252
1389
 
1253
1390
  /** Run a full VACUUM — call sparingly (e.g. after milestone completion). */
@@ -2639,7 +2776,10 @@ export function reconcileWorktreeDb(
2639
2776
  FROM wt.artifacts
2640
2777
  `).run());
2641
2778
 
2642
- // Merge milestones — worktree may have updated status/planning fields
2779
+ // Merge milestones — worktree may have updated status/planning fields.
2780
+ // Never downgrade status: complete > active > pre-planning (#4372).
2781
+ // A stale worktree may carry an older 'active' status for a milestone
2782
+ // that the main DB has already marked 'complete'; preserve the higher status.
2643
2783
  merged.milestones = countChanges(adapter.prepare(`
2644
2784
  INSERT OR REPLACE INTO milestones (
2645
2785
  id, title, status, depends_on, created_at, completed_at,
@@ -2647,11 +2787,25 @@ export function reconcileWorktreeDb(
2647
2787
  verification_contract, verification_integration, verification_operational, verification_uat,
2648
2788
  definition_of_done, requirement_coverage, boundary_map_markdown
2649
2789
  )
2650
- SELECT id, title, status, depends_on, created_at, completed_at,
2651
- vision, success_criteria, key_risks, proof_strategy,
2652
- verification_contract, verification_integration, verification_operational, verification_uat,
2653
- definition_of_done, requirement_coverage, boundary_map_markdown
2654
- FROM wt.milestones
2790
+ SELECT w.id, w.title,
2791
+ CASE
2792
+ WHEN m.status IN ('complete', 'done') AND w.status NOT IN ('complete', 'done')
2793
+ THEN m.status ELSE w.status
2794
+ END,
2795
+ w.depends_on,
2796
+ CASE
2797
+ WHEN m.status IN ('complete', 'done') AND w.status NOT IN ('complete', 'done')
2798
+ THEN m.created_at ELSE w.created_at
2799
+ END,
2800
+ CASE
2801
+ WHEN m.status IN ('complete', 'done') AND w.status NOT IN ('complete', 'done')
2802
+ THEN m.completed_at ELSE w.completed_at
2803
+ END,
2804
+ w.vision, w.success_criteria, w.key_risks, w.proof_strategy,
2805
+ w.verification_contract, w.verification_integration, w.verification_operational, w.verification_uat,
2806
+ w.definition_of_done, w.requirement_coverage, w.boundary_map_markdown
2807
+ FROM wt.milestones w
2808
+ LEFT JOIN milestones m ON m.id = w.id
2655
2809
  `).run());
2656
2810
 
2657
2811
  // Merge slices — preserve worktree progress but never downgrade completed status (#2558).
@@ -3594,11 +3748,19 @@ export function insertMemoryRow(args: {
3594
3748
  updatedAt: string;
3595
3749
  scope?: string;
3596
3750
  tags?: string[];
3751
+ /**
3752
+ * ADR-013 Step 2: optional structured payload preserved alongside the flat
3753
+ * `content` field. Used to retain gsd_save_decision-style fields (scope,
3754
+ * decision, choice, rationale, made_by, revisable) on architecture-category
3755
+ * memories so the cutover in Step 6 is lossless. Schema is intentionally
3756
+ * open inside the JSON; documented per category in ADR-013.
3757
+ */
3758
+ structuredFields?: Record<string, unknown> | null;
3597
3759
  }): void {
3598
3760
  if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
3599
3761
  currentDb.prepare(
3600
- `INSERT INTO memories (id, category, content, confidence, source_unit_type, source_unit_id, created_at, updated_at, scope, tags)
3601
- VALUES (:id, :category, :content, :confidence, :source_unit_type, :source_unit_id, :created_at, :updated_at, :scope, :tags)`,
3762
+ `INSERT INTO memories (id, category, content, confidence, source_unit_type, source_unit_id, created_at, updated_at, scope, tags, structured_fields)
3763
+ VALUES (:id, :category, :content, :confidence, :source_unit_type, :source_unit_id, :created_at, :updated_at, :scope, :tags, :structured_fields)`,
3602
3764
  ).run({
3603
3765
  ":id": args.id,
3604
3766
  ":category": args.category,
@@ -3610,6 +3772,7 @@ export function insertMemoryRow(args: {
3610
3772
  ":updated_at": args.updatedAt,
3611
3773
  ":scope": args.scope ?? "project",
3612
3774
  ":tags": JSON.stringify(args.tags ?? []),
3775
+ ":structured_fields": args.structuredFields == null ? null : JSON.stringify(args.structuredFields),
3613
3776
  });
3614
3777
  }
3615
3778
 
@@ -105,7 +105,7 @@ function runPlanV2Gate(
105
105
  if (!compiled.ok) {
106
106
  const reason = compiled.reason ?? "plan-v2 compilation failed";
107
107
  ctx.ui.notify(
108
- `Plan gate failed-closed: ${reason}. Complete plan/discuss artifacts before execution.`,
108
+ `Plan gate failed-closed: ${reason}. Complete plan/discuss artifacts before execution.\n\nIf this keeps happening, try: /gsd doctor heal`,
109
109
  "error",
110
110
  );
111
111
  return false;
@@ -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
  };