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
@@ -0,0 +1,121 @@
1
+ // GSD Compaction Snapshot — writes a ≤2 KB markdown digest of durable
2
+ // project state before the session context is compacted. On resume, an
3
+ // agent can `gsd_resume` (or Read .gsd/last-snapshot.md) to re-orient
4
+ // without re-deriving the same memories.
5
+ //
6
+ // Inspired by mksglu/context-mode. Independent implementation.
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8
+ import { resolve } from "node:path";
9
+ import { getActiveMemoriesRanked } from "./memory-store.js";
10
+ import { listExecHistory } from "./exec-history.js";
11
+ export const DEFAULT_SNAPSHOT_BYTES = 2048;
12
+ export const SNAPSHOT_FILENAME = "last-snapshot.md";
13
+ /**
14
+ * Build a priority-tiered markdown snapshot. Pure — no I/O. Tiers:
15
+ * 1. Active context (if any)
16
+ * 2. Top memories by rank
17
+ * 3. Recent exec runs (failures highlighted)
18
+ * The result is guaranteed to be <= opts.maxBytes (truncated with an
19
+ * ellipsis marker if necessary).
20
+ */
21
+ export function buildSnapshot(sources, opts = {}) {
22
+ const maxBytes = opts.maxBytes ?? DEFAULT_SNAPSHOT_BYTES;
23
+ const maxMemories = opts.maxMemories ?? 6;
24
+ const maxExec = opts.maxExec ?? 5;
25
+ const lines = [];
26
+ lines.push(`# GSD context snapshot (${sources.generatedAt.toISOString()})`);
27
+ lines.push("");
28
+ if (sources.activeContext && sources.activeContext.trim().length > 0) {
29
+ lines.push("## Active context");
30
+ lines.push(sources.activeContext.trim());
31
+ lines.push("");
32
+ }
33
+ const memories = sources.memories.slice(0, maxMemories);
34
+ if (memories.length > 0) {
35
+ lines.push("## Top project memories");
36
+ for (const memory of memories) {
37
+ lines.push(`- [${memory.id}] (${memory.category}) ${memory.content.trim()}`);
38
+ }
39
+ lines.push("");
40
+ }
41
+ const exec = sources.execHistory.slice(0, maxExec);
42
+ if (exec.length > 0) {
43
+ lines.push("## Recent gsd_exec runs");
44
+ for (const entry of exec) {
45
+ const status = entry.timed_out
46
+ ? "timeout"
47
+ : entry.exit_code === null
48
+ ? "exit:null"
49
+ : `exit:${entry.exit_code}`;
50
+ const purpose = entry.purpose ? ` — ${entry.purpose}` : "";
51
+ lines.push(`- [${entry.id}] ${entry.runtime} ${status}${purpose}`);
52
+ }
53
+ lines.push("");
54
+ }
55
+ if (memories.length === 0 && exec.length === 0 && !sources.activeContext) {
56
+ lines.push("_No durable memories, active context, or exec history to surface._");
57
+ }
58
+ return enforceByteCap(lines.join("\n").trimEnd(), maxBytes);
59
+ }
60
+ function enforceByteCap(input, maxBytes) {
61
+ if (Buffer.byteLength(input, "utf-8") <= maxBytes)
62
+ return input;
63
+ const marker = "\n…[truncated]";
64
+ const markerBytes = Buffer.byteLength(marker, "utf-8");
65
+ const budget = Math.max(0, maxBytes - markerBytes);
66
+ // Walk backwards until the trimmed string fits. utf-8 is variable-width;
67
+ // naive char slicing is safe for ASCII but may split a multi-byte char.
68
+ // Guard by decoding the trimmed Buffer and relying on the replacement-char
69
+ // fallback in TextDecoder (implicit via toString).
70
+ const buf = Buffer.from(input, "utf-8").subarray(0, budget);
71
+ return `${buf.toString("utf-8")}${marker}`;
72
+ }
73
+ export function writeCompactionSnapshot(baseDir, opts = {}) {
74
+ const memories = safeGetMemories();
75
+ const execHistory = safeListExec(baseDir);
76
+ const content = buildSnapshot({
77
+ memories,
78
+ execHistory,
79
+ generatedAt: (opts.now ?? (() => new Date()))(),
80
+ activeContext: opts.activeContext ?? null,
81
+ }, opts);
82
+ const gsdDir = resolve(baseDir, ".gsd");
83
+ if (!existsSync(gsdDir))
84
+ mkdirSync(gsdDir, { recursive: true });
85
+ const path = resolve(gsdDir, SNAPSHOT_FILENAME);
86
+ const finalContent = `${content}\n`;
87
+ writeFileSync(path, finalContent, "utf-8");
88
+ return {
89
+ path,
90
+ bytes: Buffer.byteLength(finalContent, "utf-8"),
91
+ memories: memories.length,
92
+ execRuns: execHistory.length,
93
+ };
94
+ }
95
+ export function readCompactionSnapshot(baseDir) {
96
+ const path = resolve(baseDir, ".gsd", SNAPSHOT_FILENAME);
97
+ if (!existsSync(path))
98
+ return null;
99
+ try {
100
+ return readFileSync(path, "utf-8");
101
+ }
102
+ catch {
103
+ return null;
104
+ }
105
+ }
106
+ function safeGetMemories() {
107
+ try {
108
+ return getActiveMemoriesRanked(12);
109
+ }
110
+ catch {
111
+ return [];
112
+ }
113
+ }
114
+ function safeListExec(baseDir) {
115
+ try {
116
+ return listExecHistory(baseDir);
117
+ }
118
+ catch {
119
+ return [];
120
+ }
121
+ }
@@ -8,10 +8,12 @@ import { getAdaptiveTierAdjustment } from "./routing-history.js";
8
8
  import { parseUnitId } from "./unit-id.js";
9
9
  // ─── Unit Type → Default Tier Mapping ────────────────────────────────────────
10
10
  const UNIT_TYPE_TIERS = {
11
- // Tier 1 — Light: structured summaries, completion, UAT
12
- "complete-slice": "light",
11
+ // Tier 1 — Light: compact verification turns
13
12
  "run-uat": "light",
14
- // Tier 2 — Standard: research, routine discussion
13
+ // Tier 2 — Standard: research, routine discussion, slice completion
14
+ // complete-slice can carry large inlined context; avoid routing it to the
15
+ // cheapest "light" model by default (#4520).
16
+ "complete-slice": "standard",
15
17
  "discuss-milestone": "standard",
16
18
  "discuss-slice": "standard",
17
19
  "research-milestone": "standard",
@@ -205,6 +205,19 @@ export async function nextDecisionId() {
205
205
  return 'D001';
206
206
  }
207
207
  }
208
+ /** Synchronous variant for use inside db.transaction(). */
209
+ function nextDecisionIdSync(adapter) {
210
+ if (!adapter)
211
+ return 'D001';
212
+ const row = adapter
213
+ .prepare('SELECT MAX(CAST(SUBSTR(id, 2) AS INTEGER)) as max_num FROM decisions')
214
+ .get();
215
+ const maxNum = row ? row['max_num'] : null;
216
+ if (maxNum == null || isNaN(maxNum))
217
+ return 'D001';
218
+ const next = maxNum + 1;
219
+ return `D${String(next).padStart(3, '0')}`;
220
+ }
208
221
  // ─── Next Requirement ID ─────────────────────────────────────────────────
209
222
  /**
210
223
  * Compute the next requirement ID from the current DB state.
@@ -318,32 +331,44 @@ export async function saveRequirementToDb(fields, basePath) {
318
331
  throw err;
319
332
  }
320
333
  }
334
+ // ─── Async Mutex for Decision Saves ───────────────────────────────────────
335
+ //
336
+ // Serializes the entire saveDecisionToDb operation (ID generation + DB upsert
337
+ // + file read + markdown regeneration + file write) so that parallel callers
338
+ // cannot interleave and produce a last-writer-wins race on DECISIONS.md.
339
+ let _decisionSaveLock = Promise.resolve();
340
+ /** Reset the mutex — only for tests. */
341
+ export function _resetDecisionSaveLock() {
342
+ _decisionSaveLock = Promise.resolve();
343
+ }
321
344
  /**
322
345
  * Save a new decision to DB and regenerate DECISIONS.md.
323
346
  * Auto-assigns the next ID via nextDecisionId().
324
347
  *
325
- * The ID computation (SELECT MAX) and insert are wrapped in a single
326
- * transaction to prevent parallel tool calls from computing the same ID
327
- * and silently overwriting each other (#3326, #3339, #3459).
348
+ * Concurrency: uses an async mutex (promise chain) to serialize the entire
349
+ * operation ID generation, DB upsert, file read, markdown regeneration,
350
+ * and file write — preventing parallel callers from overwriting each other's
351
+ * output (last-writer-wins race condition).
328
352
  *
329
353
  * Returns the assigned ID.
330
354
  */
331
355
  export async function saveDecisionToDb(fields, basePath) {
356
+ // Serialize via async mutex: each call waits for the previous one to
357
+ // complete before starting, preventing interleaved DB + file writes.
358
+ let release;
359
+ const prev = _decisionSaveLock;
360
+ _decisionSaveLock = new Promise(r => { release = r; });
361
+ try {
362
+ await prev;
363
+ }
364
+ catch {
365
+ // Previous call failed — proceed regardless; the lock chain must continue.
366
+ }
332
367
  try {
333
368
  const db = await import('./gsd-db.js');
334
- // Atomic ID assignment + insert inside a transaction to prevent
335
- // parallel calls from racing on the same MAX(id) value.
369
+ const adapter = db._getAdapter();
336
370
  const id = db.transaction(() => {
337
- const adapter = db._getAdapter();
338
- if (!adapter)
339
- throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
340
- const row = adapter
341
- .prepare('SELECT MAX(CAST(SUBSTR(id, 2) AS INTEGER)) as max_num FROM decisions')
342
- .get();
343
- const maxNum = row ? row['max_num'] : null;
344
- const nextId = (maxNum == null || isNaN(maxNum))
345
- ? 'D001'
346
- : `D${String(maxNum + 1).padStart(3, '0')}`;
371
+ const nextId = nextDecisionIdSync(adapter);
347
372
  db.upsertDecision({
348
373
  id: nextId,
349
374
  when_context: fields.when_context ?? '',
@@ -359,7 +384,6 @@ export async function saveDecisionToDb(fields, basePath) {
359
384
  return nextId;
360
385
  });
361
386
  // Fetch all decisions (including superseded for the full register)
362
- const adapter = db._getAdapter();
363
387
  let allDecisions = [];
364
388
  if (adapter) {
365
389
  const rows = adapter.prepare('SELECT * FROM decisions ORDER BY seq').all();
@@ -434,12 +458,60 @@ export async function saveDecisionToDb(fields, basePath) {
434
458
  invalidateStateCache();
435
459
  clearPathCache();
436
460
  clearParseCache();
461
+ // ADR-013 dual-write: keep the memory store in sync with every decision
462
+ // persisted via the legacy gsd_save_decision path. Without this, prompts
463
+ // that still call gsd_save_decision (discuss.md, plan-milestone.md,
464
+ // guided-plan-slice.md, et al. during the deprecation window) would
465
+ // create decisions rows invisible to memory_query and loadMemoryBlock.
466
+ // Best-effort — never throw, never roll back the decision on failure.
467
+ try {
468
+ const { createMemory } = await import('./memory-store.js');
469
+ const decisionText = (fields.decision ?? '').trim();
470
+ const choiceText = (fields.choice ?? '').trim();
471
+ const rationaleText = (fields.rationale ?? '').trim();
472
+ const contentParts = [];
473
+ if (decisionText)
474
+ contentParts.push(decisionText);
475
+ if (choiceText)
476
+ contentParts.push(`Chose: ${choiceText}.`);
477
+ if (rationaleText)
478
+ contentParts.push(`Rationale: ${rationaleText}.`);
479
+ const content = contentParts.join(' ').slice(0, 600);
480
+ if (content) {
481
+ createMemory({
482
+ category: 'architecture',
483
+ content,
484
+ scope: fields.scope || 'project',
485
+ confidence: 0.85,
486
+ structuredFields: {
487
+ sourceDecisionId: id,
488
+ when_context: fields.when_context ?? '',
489
+ scope: fields.scope,
490
+ decision: fields.decision,
491
+ choice: fields.choice,
492
+ rationale: fields.rationale,
493
+ made_by: fields.made_by ?? 'agent',
494
+ revisable: fields.revisable ?? '',
495
+ },
496
+ });
497
+ }
498
+ }
499
+ catch (mirrorErr) {
500
+ logError('manifest', 'memory-store mirror write failed (non-fatal)', {
501
+ fn: 'saveDecisionToDb',
502
+ decisionId: id,
503
+ error: String(mirrorErr.message),
504
+ });
505
+ }
437
506
  return { id };
438
507
  }
439
508
  catch (err) {
440
509
  logError('manifest', 'saveDecisionToDb failed', { fn: 'saveDecisionToDb', error: String(err.message) });
441
510
  throw err;
442
511
  }
512
+ finally {
513
+ release();
514
+ }
443
515
  }
444
516
  /**
445
517
  * Extract a milestone/slice reference from a deferral decision.
@@ -2,7 +2,7 @@ import { existsSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs
2
2
  import { join, sep } from "node:path";
3
3
  import { loadFile } from "./files.js";
4
4
  import { parseRoadmap as parseLegacyRoadmap } from "./parsers-legacy.js";
5
- import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js";
5
+ import { isDbAvailable, getMilestone } from "./gsd-db.js";
6
6
  import { resolveMilestoneFile } from "./paths.js";
7
7
  import { deriveState, isMilestoneComplete } from "./state.js";
8
8
  import { listWorktrees, resolveGitDir, worktreesDir } from "./worktree-manager.js";
@@ -48,6 +48,21 @@ function isSameOrNestedPath(candidate, container) {
48
48
  return normalizedCandidate === normalizedContainer ||
49
49
  normalizedCandidate.startsWith(`${normalizedContainer}/`);
50
50
  }
51
+ async function isCompletedMilestoneTerminal(basePath, milestoneId) {
52
+ const summaryPath = resolveMilestoneFile(basePath, milestoneId, "SUMMARY");
53
+ if (!summaryPath)
54
+ return false;
55
+ if (isDbAvailable()) {
56
+ const milestone = getMilestone(milestoneId);
57
+ return !!milestone && milestone.status === "complete";
58
+ }
59
+ const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
60
+ const roadmapContent = roadmapPath ? await loadFile(roadmapPath) : null;
61
+ if (!roadmapContent)
62
+ return false;
63
+ const roadmap = parseLegacyRoadmap(roadmapContent);
64
+ return isMilestoneComplete(roadmap);
65
+ }
51
66
  export async function checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode = "none") {
52
67
  // Degrade gracefully if not a git repo
53
68
  if (!nativeIsRepo(basePath)) {
@@ -67,23 +82,9 @@ export async function checkGitHealth(basePath, issues, fixesApplied, shouldFix,
67
82
  // Extract milestone ID from branch name "milestone/M001" → "M001"
68
83
  const milestoneId = wt.branch.replace(/^milestone\//, "");
69
84
  const milestoneEntry = state.registry.find(m => m.id === milestoneId);
70
- // Check if milestone is complete via roadmap
71
- let isComplete = false;
72
- if (milestoneEntry) {
73
- if (isDbAvailable()) {
74
- const dbSlices = getMilestoneSlices(milestoneId);
75
- isComplete = dbSlices.length > 0 && dbSlices.every(s => s.status === "complete");
76
- }
77
- else {
78
- const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
79
- const roadmapContent = roadmapPath ? await loadFile(roadmapPath) : null;
80
- if (roadmapContent) {
81
- const roadmap = parseLegacyRoadmap(roadmapContent);
82
- isComplete = isMilestoneComplete(roadmap);
83
- }
84
- }
85
- // When DB unavailable and no roadmap, isComplete stays false
86
- }
85
+ const isComplete = milestoneEntry
86
+ ? await isCompletedMilestoneTerminal(basePath, milestoneId)
87
+ : false;
87
88
  if (isComplete) {
88
89
  issues.push({
89
90
  severity: "warning",
@@ -136,17 +137,10 @@ export async function checkGitHealth(basePath, issues, fixesApplied, shouldFix,
136
137
  const milestoneId = branch.replace(/^milestone\//, "");
137
138
  const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
138
139
  let branchMilestoneComplete = false;
139
- if (isDbAvailable()) {
140
- const dbSlices = getMilestoneSlices(milestoneId);
141
- branchMilestoneComplete = dbSlices.length > 0 && dbSlices.every(s => s.status === "complete");
142
- }
143
- else {
144
- const roadmapContent = roadmapPath ? await loadFile(roadmapPath) : null;
145
- if (!roadmapContent)
146
- continue;
147
- const roadmap = parseLegacyRoadmap(roadmapContent);
148
- branchMilestoneComplete = isMilestoneComplete(roadmap);
149
- }
140
+ const roadmapContent = roadmapPath ? await loadFile(roadmapPath) : null;
141
+ if (!roadmapContent)
142
+ continue;
143
+ branchMilestoneComplete = await isCompletedMilestoneTerminal(basePath, milestoneId);
150
144
  if (branchMilestoneComplete) {
151
145
  issues.push({
152
146
  severity: "info",
@@ -10,8 +10,8 @@
10
10
  * - Remote questions channel if configured (Slack/Discord/Telegram token)
11
11
  * - Optional search/tool integrations (Brave, Tavily, Jina, Context7)
12
12
  */
13
- import { existsSync } from "node:fs";
14
- import { join } from "node:path";
13
+ import { existsSync, readFileSync } from "node:fs";
14
+ import { delimiter, join } from "node:path";
15
15
  import { AuthStorage } from "@gsd/pi-coding-agent";
16
16
  import { getEnvApiKey } from "@gsd/pi-ai";
17
17
  import { loadEffectiveGSDPreferences } from "./preferences.js";
@@ -47,7 +47,8 @@ function modelToProviderId(model) {
47
47
  return null;
48
48
  // Explicit provider prefix (e.g. "openrouter/deepseek-r1")
49
49
  if (model.includes("/")) {
50
- const prefix = model.split("/")[0].toLowerCase();
50
+ const rawPrefix = model.split("/")[0];
51
+ const prefix = rawPrefix.toLowerCase();
51
52
  // Map known prefixes to registry IDs
52
53
  const prefixMap = {
53
54
  "anthropic-vertex": "anthropic-vertex",
@@ -62,6 +63,7 @@ function modelToProviderId(model) {
62
63
  };
63
64
  if (prefixMap[prefix])
64
65
  return prefixMap[prefix];
66
+ return rawPrefix;
65
67
  }
66
68
  const lower = model.toLowerCase();
67
69
  if (lower.startsWith("claude"))
@@ -136,8 +138,49 @@ function isCliBinaryInPath(providerId) {
136
138
  const binary = CLI_BINARY_MAP[providerId];
137
139
  if (!binary)
138
140
  return false;
139
- const pathDirs = (process.env.PATH ?? "").split(":");
140
- return pathDirs.some(dir => dir && existsSync(join(dir, binary)));
141
+ const pathDirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
142
+ // On Windows, command shims are commonly installed as .cmd/.exe/.bat/.com.
143
+ // Scan PATHEXT candidates in addition to the bare binary name.
144
+ const executableNames = [binary];
145
+ if (process.platform === "win32") {
146
+ const rawPathExt = process.env.PATHEXT
147
+ ?.split(";")
148
+ .map(ext => ext.trim())
149
+ .filter(Boolean) ?? [];
150
+ const normalizedPathExt = rawPathExt.map(ext => ext.startsWith(".") ? ext.toLowerCase() : `.${ext.toLowerCase()}`);
151
+ const defaultExt = [".exe", ".cmd", ".bat", ".com"];
152
+ for (const ext of [...normalizedPathExt, ...defaultExt]) {
153
+ const candidate = `${binary}${ext}`;
154
+ if (!executableNames.includes(candidate))
155
+ executableNames.push(candidate);
156
+ }
157
+ }
158
+ return pathDirs.some(dir => executableNames.some(name => existsSync(join(dir, name))));
159
+ }
160
+ function modelsJsonPaths() {
161
+ const home = process.env.HOME ?? "~";
162
+ return [
163
+ join(home, ".gsd", "agent", "models.json"),
164
+ // Keep parity with custom-provider discovery during auto bootstrap.
165
+ join(home, ".pi", "agent", "models.json"),
166
+ ];
167
+ }
168
+ function hasModelsJsonApiKey(providerId) {
169
+ for (const path of modelsJsonPaths()) {
170
+ if (!existsSync(path))
171
+ continue;
172
+ try {
173
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
174
+ const apiKey = parsed.providers?.[providerId]?.apiKey;
175
+ if (typeof apiKey === "string" && apiKey.trim().length > 0) {
176
+ return true;
177
+ }
178
+ }
179
+ catch {
180
+ // Malformed models.json should not break the dashboard health check.
181
+ }
182
+ }
183
+ return false;
141
184
  }
142
185
  function resolveKey(providerId) {
143
186
  const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
@@ -182,6 +225,9 @@ function resolveKey(providerId) {
182
225
  if (info?.envVar && process.env[info.envVar]) {
183
226
  return { found: true, source: "env", backedOff: false };
184
227
  }
228
+ if (hasModelsJsonApiKey(providerId)) {
229
+ return { found: true, source: "models.json", backedOff: false };
230
+ }
185
231
  return { found: false, source: "none", backedOff: false };
186
232
  }
187
233
  // ── Individual check groups ────────────────────────────────────────────────────
@@ -101,6 +101,7 @@ export function createGSDExtensionAPI(pi, sharedHandlers) {
101
101
  // ── Event emission ─────────────────────────────────────────────────
102
102
  emitBeforeModelSelect: (...args) => pi.emitBeforeModelSelect(...args),
103
103
  emitAdjustToolSet: (...args) => pi.emitAdjustToolSet(...args),
104
+ emitExtensionEvent: (...args) => pi.emitExtensionEvent(...args),
104
105
  // ── Tool / command / shortcut / flag registration ──────────────────
105
106
  registerTool: ((tool) => pi.registerTool(tool)),
106
107
  registerCommand: (...args) => pi.registerCommand(...args),
@@ -22,18 +22,35 @@ const PERMANENT_RE = /auth|unauthorized|forbidden|invalid.*key|invalid.*api|bill
22
22
  // Include provider-specific quota-window phrasing like:
23
23
  // - "You've hit your limit"
24
24
  // - "usage limit" / "quota reached"
25
- const RATE_LIMIT_RE = /rate.?limit|too many requests|429|hit your limit|usage limit|quota (?:reached|hit)|limit.*resets?/i;
25
+ // - "out of extra usage"
26
+ const RATE_LIMIT_RE = /rate.?limit|too many requests|429|hit your limit|usage limit|out of extra usage|quota (?:reached|hit)|limit.*resets?/i;
26
27
  // OpenRouter affordability-style quota errors should be treated as transient
27
28
  // so core retry logic can lower maxTokens and continue in-session.
28
29
  const AFFORDABILITY_RE = /requires more credits|can only afford|insufficient credits|not enough credits|fewer max_tokens/i;
29
- const NETWORK_RE = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|fetch failed|connection.*reset|dns|unexpected eof/i;
30
- const SERVER_RE = /internal server error|500|502|503|overloaded|server_error|api_error|service.?unavailable/i;
30
+ // "Stream idle timeout" and "partial response received" are emitted by the SDK/harness
31
+ // for mid-stream disconnects. Both indicate a transient network-level interruption.
32
+ // See: https://github.com/gsd-build/gsd-2/issues/4558
33
+ const NETWORK_RE = /network|ECONNRESET|ETIMEDOUT|ECONNREFUSED|socket hang up|fetch failed|connection.*reset|dns|unexpected eof|stream idle timeout|partial response received/i;
34
+ // Context overflow errors (context window/length exceeded) should be treated as server-class
35
+ // transient errors so auto-mode can retry with reduced budget or fall back to a larger-context model.
36
+ // See: https://github.com/gsd-build/gsd-2/issues/4528
37
+ const SERVER_RE = /internal server error|500|502|503|overloaded|server_error|api_error|service.?unavailable|context (?:window|length) exceed|context window exceed/i;
31
38
  // ECONNRESET/ECONNREFUSED are in NETWORK_RE (same-model retry first).
32
39
  const CONNECTION_RE = /terminated|connection.?(?:refused|error)|other side closed|EPIPE|network.?(?:is\s+)?unavailable|stream_exhausted(?:_without_result)?/i;
33
40
  // Catch-all for V8 JSON.parse errors: all modern variants end with "in JSON at position \d+".
34
41
  // This eliminates the need to enumerate every error message variant individually.
35
42
  const STREAM_RE = /in JSON at position \d+|Unexpected end of JSON|SyntaxError.*JSON/i;
36
43
  const RESET_DELAY_RE = /reset in (\d+)s/i;
44
+ // Provider-side model entitlement rejection: the SDK accepted the model switch,
45
+ // but the provider refused at request time because the current account/plan/tier
46
+ // cannot use that model. Must match all three of: a model/deployment token,
47
+ // a negative-entitlement indicator, and an account/plan/tier/subscription token.
48
+ // Requiring all three keeps generic "account suspended" errors in `permanent`
49
+ // (no model token) while catching the phrasings providers actually use.
50
+ // See issue #4513.
51
+ const UNSUPPORTED_MODEL_MODEL_RE = /\b(?:model|deployment)\b/i;
52
+ const UNSUPPORTED_MODEL_INDICATOR_RE = /\bnot support(?:ed|s)?\b|\bunsupported\b|\bnot available\b|\bunavailable\b|\bno access\b|\bdoes(?:n['’]t| not) (?:have access|support)\b|\bnot authori[sz]ed\b/i;
53
+ const UNSUPPORTED_MODEL_SCOPE_RE = /\b(?:account|plan|tier|subscription)\b/i;
37
54
  /**
38
55
  * Classify an error message into one of the ErrorClass kinds.
39
56
  *
@@ -49,6 +66,17 @@ const RESET_DELAY_RE = /reset in (\d+)s/i;
49
66
  export function classifyError(errorMsg, retryAfterMs) {
50
67
  const isPermanent = PERMANENT_RE.test(errorMsg);
51
68
  const isRateLimit = RATE_LIMIT_RE.test(errorMsg) || AFFORDABILITY_RE.test(errorMsg);
69
+ const isUnsupportedModel = UNSUPPORTED_MODEL_MODEL_RE.test(errorMsg) &&
70
+ UNSUPPORTED_MODEL_INDICATOR_RE.test(errorMsg) &&
71
+ UNSUPPORTED_MODEL_SCOPE_RE.test(errorMsg);
72
+ // 0. Unsupported model (account/plan entitlement rejection) — checked before
73
+ // `permanent` because PERMANENT_RE also matches /account/i and would
74
+ // otherwise swallow these errors, blocking the blocklist-driven fallback.
75
+ // Rate limit still wins when both patterns appear (a throttled account is
76
+ // not an entitlement failure).
77
+ if (isUnsupportedModel && !isRateLimit) {
78
+ return { kind: "unsupported-model" };
79
+ }
52
80
  // 1. Permanent — but rate limit takes precedence
53
81
  if (isPermanent && !isRateLimit) {
54
82
  return { kind: "permanent" };
@@ -0,0 +1,120 @@
1
+ // GSD Exec History — read-side helpers for the exec sandbox.
2
+ //
3
+ // Pure I/O: scans `.gsd/exec/*.meta.json` under a base directory and
4
+ // returns lightweight records. Used by the gsd_exec_search tool and
5
+ // any future compaction-snapshot enrichment.
6
+ import { closeSync, openSync, readdirSync, readFileSync, readSync, statSync } from "node:fs";
7
+ import { join, resolve } from "node:path";
8
+ function listMetaFiles(baseDir) {
9
+ const dir = resolve(baseDir, ".gsd", "exec");
10
+ try {
11
+ return readdirSync(dir)
12
+ .filter((name) => name.endsWith(".meta.json"))
13
+ .map((name) => join(dir, name));
14
+ }
15
+ catch {
16
+ return [];
17
+ }
18
+ }
19
+ function safeReadMeta(path) {
20
+ try {
21
+ const raw = readFileSync(path, "utf-8");
22
+ const parsed = JSON.parse(raw);
23
+ if (typeof parsed.id !== "string" || typeof parsed.runtime !== "string")
24
+ return null;
25
+ return {
26
+ id: parsed.id,
27
+ runtime: parsed.runtime,
28
+ purpose: typeof parsed.purpose === "string" ? parsed.purpose : null,
29
+ started_at: typeof parsed.started_at === "string" ? parsed.started_at : "",
30
+ finished_at: typeof parsed.finished_at === "string" ? parsed.finished_at : "",
31
+ duration_ms: typeof parsed.duration_ms === "number" ? parsed.duration_ms : 0,
32
+ exit_code: typeof parsed.exit_code === "number" ? parsed.exit_code : null,
33
+ signal: typeof parsed.signal === "string" ? parsed.signal : null,
34
+ timed_out: parsed.timed_out === true,
35
+ stdout_bytes: typeof parsed.stdout_bytes === "number" ? parsed.stdout_bytes : 0,
36
+ stderr_bytes: typeof parsed.stderr_bytes === "number" ? parsed.stderr_bytes : 0,
37
+ stdout_truncated: parsed.stdout_truncated === true,
38
+ stderr_truncated: parsed.stderr_truncated === true,
39
+ stdout_path: path.replace(/\.meta\.json$/, ".stdout"),
40
+ stderr_path: path.replace(/\.meta\.json$/, ".stderr"),
41
+ meta_path: path,
42
+ };
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ export function listExecHistory(baseDir) {
49
+ const metas = listMetaFiles(baseDir)
50
+ .map((path) => {
51
+ let mtime = 0;
52
+ try {
53
+ mtime = statSync(path).mtimeMs;
54
+ }
55
+ catch {
56
+ /* ignore */
57
+ }
58
+ const entry = safeReadMeta(path);
59
+ return entry ? { entry, mtime } : null;
60
+ })
61
+ .filter((value) => value !== null);
62
+ metas.sort((a, b) => b.mtime - a.mtime);
63
+ return metas.map((m) => m.entry);
64
+ }
65
+ function matchesFilters(entry, opts) {
66
+ if (opts.runtime && entry.runtime !== opts.runtime)
67
+ return false;
68
+ if (opts.failing_only) {
69
+ const failed = entry.timed_out || (entry.exit_code !== 0 && entry.exit_code !== null);
70
+ if (!failed)
71
+ return false;
72
+ }
73
+ const query = (opts.query ?? "").trim().toLowerCase();
74
+ if (!query)
75
+ return true;
76
+ const haystack = `${entry.id} ${entry.purpose ?? ""}`.toLowerCase();
77
+ return haystack.includes(query);
78
+ }
79
+ function readDigestPreview(entry, maxChars) {
80
+ if (!entry.stdout_path || maxChars <= 0)
81
+ return undefined;
82
+ try {
83
+ const size = statSync(entry.stdout_path).size;
84
+ if (size === 0)
85
+ return undefined;
86
+ const readBytes = Math.min(size, maxChars * 4); // 4 bytes/char upper bound for UTF-8
87
+ const buf = Buffer.allocUnsafe(readBytes);
88
+ const fd = openSync(entry.stdout_path, "r");
89
+ try {
90
+ const bytesRead = readSync(fd, buf, 0, readBytes, Math.max(0, size - readBytes));
91
+ const text = buf.subarray(0, bytesRead).toString("utf-8");
92
+ const trimmed = text.trimEnd();
93
+ return trimmed.length <= maxChars ? trimmed : trimmed.slice(trimmed.length - maxChars);
94
+ }
95
+ finally {
96
+ closeSync(fd);
97
+ }
98
+ }
99
+ catch {
100
+ return undefined;
101
+ }
102
+ }
103
+ export function searchExecHistory(baseDir, opts = {}) {
104
+ const limit = clampLimit(opts.limit, 20, 200);
105
+ const entries = listExecHistory(baseDir);
106
+ const filtered = entries.filter((entry) => matchesFilters(entry, opts));
107
+ return filtered.slice(0, limit).map((entry) => ({
108
+ entry,
109
+ digest_preview: readDigestPreview(entry, 300),
110
+ }));
111
+ }
112
+ function clampLimit(value, fallback, max) {
113
+ if (typeof value !== "number" || !Number.isFinite(value))
114
+ return fallback;
115
+ if (value < 1)
116
+ return 1;
117
+ if (value > max)
118
+ return max;
119
+ return Math.floor(value);
120
+ }