gsd-pi 2.76.0-dev.82e249f7b → 2.76.0-dev.97807402

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 (236) hide show
  1. package/dist/claude-cli-check.js +32 -3
  2. package/dist/mcp-server.d.ts +7 -0
  3. package/dist/mcp-server.js +35 -1
  4. package/dist/resource-loader.d.ts +1 -1
  5. package/dist/resource-loader.js +2 -8
  6. package/dist/resources/extensions/claude-code-cli/readiness.js +4 -3
  7. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +77 -17
  8. package/dist/resources/extensions/gsd/auto/phases.js +14 -0
  9. package/dist/resources/extensions/gsd/auto/run-unit.js +27 -0
  10. package/dist/resources/extensions/gsd/auto-model-selection.js +1 -1
  11. package/dist/resources/extensions/gsd/auto-post-unit.js +1 -1
  12. package/dist/resources/extensions/gsd/auto-recovery.js +13 -0
  13. package/dist/resources/extensions/gsd/auto-start.js +27 -18
  14. package/dist/resources/extensions/gsd/auto-worktree.js +30 -48
  15. package/dist/resources/extensions/gsd/auto.js +13 -17
  16. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -1
  17. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
  18. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
  19. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  20. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +40 -4
  21. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +12 -1
  22. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +968 -23
  23. package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
  24. package/dist/resources/extensions/gsd/error-classifier.js +10 -3
  25. package/dist/resources/extensions/gsd/exec-history.js +120 -0
  26. package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
  27. package/dist/resources/extensions/gsd/gsd-db.js +115 -7
  28. package/dist/resources/extensions/gsd/guided-flow.js +189 -0
  29. package/dist/resources/extensions/gsd/health-widget.js +4 -1
  30. package/dist/resources/extensions/gsd/init-wizard.js +15 -1
  31. package/dist/resources/extensions/gsd/key-manager.js +6 -0
  32. package/dist/resources/extensions/gsd/model-router.js +36 -3
  33. package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -9
  34. package/dist/resources/extensions/gsd/preferences-types.js +9 -0
  35. package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
  36. package/dist/resources/extensions/gsd/preferences.js +17 -17
  37. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  38. package/dist/resources/extensions/gsd/prompts/discuss.md +29 -2
  39. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  40. package/dist/resources/extensions/gsd/safety/file-change-validator.js +10 -4
  41. package/dist/resources/extensions/gsd/safety/safety-harness.js +4 -0
  42. package/dist/resources/extensions/gsd/token-counter.js +22 -5
  43. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
  44. package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
  45. package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
  46. package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
  47. package/dist/resources/skills/verify-before-complete/SKILL.md +2 -1
  48. package/dist/resources/skills/write-docs/SKILL.md +2 -1
  49. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  50. package/dist/web/standalone/.next/BUILD_ID +1 -1
  51. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  52. package/dist/web/standalone/.next/build-manifest.json +2 -2
  53. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  54. package/dist/web/standalone/.next/required-server-files.json +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.html +1 -1
  72. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  79. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  81. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  82. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  83. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  84. package/dist/web/standalone/server.js +1 -1
  85. package/package.json +1 -1
  86. package/packages/mcp-server/dist/remote-questions.d.ts +45 -0
  87. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -0
  88. package/packages/mcp-server/dist/remote-questions.js +732 -0
  89. package/packages/mcp-server/dist/remote-questions.js.map +1 -0
  90. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  91. package/packages/mcp-server/dist/server.js +18 -1
  92. package/packages/mcp-server/dist/server.js.map +1 -1
  93. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  94. package/packages/mcp-server/dist/workflow-tools.js +64 -25
  95. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  96. package/packages/mcp-server/package.json +2 -1
  97. package/packages/mcp-server/src/remote-questions.test.ts +294 -0
  98. package/packages/mcp-server/src/remote-questions.ts +916 -0
  99. package/packages/mcp-server/src/server.ts +19 -1
  100. package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
  101. package/packages/mcp-server/src/workflow-tools.ts +84 -43
  102. package/packages/mcp-server/tsconfig.test.json +19 -0
  103. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  104. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/anthropic-shared.js +2 -0
  106. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/simple-options.d.ts +10 -0
  108. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  109. package/packages/pi-ai/dist/providers/simple-options.js +16 -1
  110. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  111. package/packages/pi-ai/src/providers/anthropic-shared.ts +3 -1
  112. package/packages/pi-ai/src/providers/simple-options.ts +17 -1
  113. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  114. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts +2 -0
  115. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts.map +1 -0
  116. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js +203 -0
  117. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js.map +1 -0
  118. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/model-registry.js +14 -0
  120. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
  122. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
  123. package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
  124. package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
  125. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
  126. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
  127. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
  128. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
  129. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/session-manager.js +9 -5
  131. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/session-manager.test.js +25 -1
  133. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
  141. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  142. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +13 -1
  144. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  145. package/packages/pi-coding-agent/src/core/model-registry-custom-caps.test.ts +245 -0
  146. package/packages/pi-coding-agent/src/core/model-registry.ts +16 -0
  147. package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
  148. package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
  149. package/packages/pi-coding-agent/src/core/session-manager.test.ts +36 -1
  150. package/packages/pi-coding-agent/src/core/session-manager.ts +9 -5
  151. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
  152. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
  153. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +13 -1
  154. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  155. package/src/resources/extensions/claude-code-cli/readiness.ts +4 -3
  156. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +78 -17
  157. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +149 -5
  158. package/src/resources/extensions/gsd/auto/phases.ts +14 -0
  159. package/src/resources/extensions/gsd/auto/run-unit.ts +29 -0
  160. package/src/resources/extensions/gsd/auto-model-selection.ts +1 -1
  161. package/src/resources/extensions/gsd/auto-post-unit.ts +1 -2
  162. package/src/resources/extensions/gsd/auto-recovery.ts +15 -0
  163. package/src/resources/extensions/gsd/auto-start.ts +29 -19
  164. package/src/resources/extensions/gsd/auto-worktree.ts +34 -52
  165. package/src/resources/extensions/gsd/auto.ts +12 -17
  166. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +23 -1
  167. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
  168. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
  169. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  170. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +42 -4
  171. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +13 -1
  172. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +898 -32
  173. package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
  174. package/src/resources/extensions/gsd/error-classifier.ts +10 -3
  175. package/src/resources/extensions/gsd/exec-history.ts +153 -0
  176. package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
  177. package/src/resources/extensions/gsd/gsd-db.ts +122 -7
  178. package/src/resources/extensions/gsd/guided-flow.ts +221 -0
  179. package/src/resources/extensions/gsd/health-widget.ts +3 -1
  180. package/src/resources/extensions/gsd/init-wizard.ts +15 -1
  181. package/src/resources/extensions/gsd/journal.ts +2 -1
  182. package/src/resources/extensions/gsd/key-manager.ts +6 -0
  183. package/src/resources/extensions/gsd/model-router.ts +42 -1
  184. package/src/resources/extensions/gsd/pre-execution-checks.ts +36 -10
  185. package/src/resources/extensions/gsd/preferences-types.ts +46 -0
  186. package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
  187. package/src/resources/extensions/gsd/preferences.ts +17 -17
  188. package/src/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  189. package/src/resources/extensions/gsd/prompts/discuss.md +29 -2
  190. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  191. package/src/resources/extensions/gsd/safety/file-change-validator.ts +14 -3
  192. package/src/resources/extensions/gsd/safety/safety-harness.ts +6 -0
  193. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +116 -0
  194. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +49 -0
  195. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
  196. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  197. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  198. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +31 -0
  199. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +1 -1
  200. package/src/resources/extensions/gsd/tests/escalation.test.ts +1 -1
  201. package/src/resources/extensions/gsd/tests/exec-history.test.ts +124 -0
  202. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
  203. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +58 -0
  204. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +152 -1
  205. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
  206. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
  207. package/src/resources/extensions/gsd/tests/issue-4540-regressions.test.ts +288 -0
  208. package/src/resources/extensions/gsd/tests/key-manager.test.ts +7 -0
  209. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  210. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  211. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +19 -0
  212. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
  213. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +234 -0
  214. package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
  215. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +44 -0
  216. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +48 -0
  217. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +388 -0
  218. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +9 -3
  219. package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
  220. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +32 -40
  221. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +56 -0
  222. package/src/resources/extensions/gsd/tests/token-counter.test.ts +105 -1
  223. package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -0
  224. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +65 -2
  225. package/src/resources/extensions/gsd/tests/write-gate.test.ts +64 -0
  226. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
  227. package/src/resources/extensions/gsd/token-counter.ts +22 -5
  228. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
  229. package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
  230. package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
  231. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  232. package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
  233. package/src/resources/skills/verify-before-complete/SKILL.md +2 -1
  234. package/src/resources/skills/write-docs/SKILL.md +2 -1
  235. /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → pI48IF3dgfs0CBrYi2bh_}/_buildManifest.js +0 -0
  236. /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → pI48IF3dgfs0CBrYi2bh_}/_ssgManifest.js +0 -0
@@ -12,12 +12,41 @@ import { execFileSync } from 'node:child_process';
12
12
  * `src/resources/extensions/gsd/pre-execution-checks.ts`.
13
13
  */
14
14
  export const CLAUDE_COMMAND = process.platform === 'win32' ? 'claude.cmd' : 'claude';
15
+ /**
16
+ * Ordered list of binary names to probe for the Claude Code CLI.
17
+ *
18
+ * Windows installs vary: npm-global installs produce a `claude.cmd` shim,
19
+ * direct binary installs produce `claude.exe`, and Git Bash wrappers may
20
+ * expose a bare `claude` shim. Try all three so no valid install is missed.
21
+ */
22
+ const CLAUDE_COMMAND_CANDIDATES = process.platform === 'win32' ? [CLAUDE_COMMAND, 'claude.exe', 'claude'] : [CLAUDE_COMMAND];
23
+ /**
24
+ * Try to run `args` against each candidate binary.
25
+ * Returns the output buffer on first success, throws the last error if all fail.
26
+ */
27
+ function execClaudeCheck(args) {
28
+ let lastError;
29
+ for (const command of CLAUDE_COMMAND_CANDIDATES) {
30
+ try {
31
+ return execFileSync(command, args, { timeout: 5_000, stdio: 'pipe' });
32
+ }
33
+ catch (error) {
34
+ lastError = error;
35
+ const code = error?.code;
36
+ // EINVAL can surface on Windows Git Bash for .cmd spawn failures.
37
+ if (code === 'ENOENT' || code === 'EINVAL')
38
+ continue;
39
+ throw error;
40
+ }
41
+ }
42
+ throw lastError ?? new Error(`Claude CLI not found (tried: ${CLAUDE_COMMAND_CANDIDATES.join(', ')})`);
43
+ }
15
44
  /**
16
45
  * Check if the `claude` binary is installed (regardless of auth state).
17
46
  */
18
47
  export function isClaudeBinaryInstalled() {
19
48
  try {
20
- execFileSync(CLAUDE_COMMAND, ['--version'], { timeout: 5_000, stdio: 'pipe' });
49
+ execClaudeCheck(['--version']);
21
50
  return true;
22
51
  }
23
52
  catch {
@@ -29,13 +58,13 @@ export function isClaudeBinaryInstalled() {
29
58
  */
30
59
  export function isClaudeCliReady() {
31
60
  try {
32
- execFileSync(CLAUDE_COMMAND, ['--version'], { timeout: 5_000, stdio: 'pipe' });
61
+ execClaudeCheck(['--version']);
33
62
  }
34
63
  catch {
35
64
  return false;
36
65
  }
37
66
  try {
38
- const output = execFileSync(CLAUDE_COMMAND, ['auth', 'status'], { timeout: 5_000, stdio: 'pipe' })
67
+ const output = execClaudeCheck(['auth', 'status'])
39
68
  .toString()
40
69
  .toLowerCase();
41
70
  return !(/not logged in|no credentials|unauthenticated|not authenticated/i.test(output));
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * Minimal tool interface matching GSD's AgentTool shape.
3
3
  * Avoids a direct dependency on @gsd/pi-agent-core from this compiled module.
4
+ *
5
+ * `details` and `isError` are optional fields that runtime tool implementations
6
+ * may populate. The MCP transport drops non-standard fields, so the wrapper at
7
+ * the call site mirrors `details` into `structuredContent` and forwards
8
+ * `isError` directly. See #4472.
4
9
  */
5
10
  export interface McpToolDef {
6
11
  name: string;
@@ -13,6 +18,8 @@ export interface McpToolDef {
13
18
  data?: string;
14
19
  mimeType?: string;
15
20
  }>;
21
+ details?: Record<string, unknown>;
22
+ isError?: boolean;
16
23
  }>;
17
24
  }
18
25
  /**
@@ -1,3 +1,21 @@
1
+ /**
2
+ * Strict plain-object guard. True only for object literals and
3
+ * `Object.create(null)` — not for `Date`, `URL`, `Map`, `Set`, class instances,
4
+ * or arrays. Used to gate `structuredContent` forwarding so the MCP transport
5
+ * receives only true JSON objects (the protocol contract). See #4477 review.
6
+ *
7
+ * Mirrored in `packages/mcp-server/src/workflow-tools.ts` for the
8
+ * `adaptExecutorResult` adapter on the workflow path. Keep both copies in
9
+ * sync if the contract definition needs to evolve.
10
+ */
11
+ function isPlainObject(value) {
12
+ if (value === null || typeof value !== 'object')
13
+ return false;
14
+ if (Array.isArray(value))
15
+ return false;
16
+ const proto = Object.getPrototypeOf(value);
17
+ return proto === null || proto === Object.prototype;
18
+ }
1
19
  // MCP SDK subpath imports use wildcard exports (./*) in @modelcontextprotocol/sdk's
2
20
  // package.json export map. The wildcard maps "./foo" → "./dist/cjs/foo" (no .js
3
21
  // suffix), so bare subpath specifiers like `${MCP_PKG}/server/stdio` resolve to
@@ -83,7 +101,23 @@ export async function startMcpServer(options) {
83
101
  // by stringifying into a text block so clients see the payload.
84
102
  return { type: 'text', text: JSON.stringify(block) };
85
103
  });
86
- return { content };
104
+ // Forward a tool's runtime `details` field to MCP's `structuredContent`
105
+ // channel. The protocol drops non-standard fields on the wire, so tools
106
+ // that populate `details` for client-side renderers (e.g. save_gate_result)
107
+ // would otherwise arrive empty on the other side. See #4472.
108
+ //
109
+ // Use a strict plain-object guard (prototype-chain check) rather than just
110
+ // `typeof === 'object' && !Array.isArray()` — Date, URL, Map, Set, and
111
+ // class instances would otherwise pass through and end up as
112
+ // `structuredContent`, violating the protocol's JSON-object contract.
113
+ // The mirror discipline applies in `workflow-tools.ts adaptExecutorResult`.
114
+ const base = { content };
115
+ if (isPlainObject(result.details)) {
116
+ base.structuredContent = result.details;
117
+ }
118
+ if (result.isError === true)
119
+ base.isError = true;
120
+ return base;
87
121
  }
88
122
  catch (err) {
89
123
  // AbortError from a cancelled tool surfaces as a normal error — MCP
@@ -21,6 +21,6 @@ export declare function getNewerManagedResourceVersion(agentDir: string, current
21
21
  *
22
22
  * Inspectable: `ls ~/.gsd/agent/extensions/`
23
23
  */
24
- export declare function initResources(agentDir: string): void;
24
+ export declare function initResources(agentDir: string, skillsDir?: string): void;
25
25
  export declare function hasStaleCompiledExtensionSiblings(extensionsDir: string, sourceDir?: string): boolean;
26
26
  export declare function buildResourceLoader(agentDir: string): DefaultResourceLoader;
@@ -506,7 +506,7 @@ function pruneRemovedBundledExtensions(manifest, agentDir) {
506
506
  *
507
507
  * Inspectable: `ls ~/.gsd/agent/extensions/`
508
508
  */
509
- export function initResources(agentDir) {
509
+ export function initResources(agentDir, skillsDir = join(homedir(), '.agents', 'skills')) {
510
510
  mkdirSync(agentDir, { recursive: true });
511
511
  const currentVersion = getBundledGsdVersion();
512
512
  const manifest = readManagedResourceManifest(agentDir);
@@ -538,13 +538,7 @@ export function initResources(agentDir) {
538
538
  // Sync bundled resources — overwrite so updates land on next launch.
539
539
  syncResourceDir(bundledExtensionsDir, join(agentDir, 'extensions'));
540
540
  syncResourceDir(join(resourcesDir, 'agents'), join(agentDir, 'agents'));
541
- // Skills are no longer force-synced here. Users install skills via the
542
- // skills.sh CLI (`npx skills add <repo>`) into ~/.agents/skills/ which
543
- // is the industry-standard Agent Skills ecosystem directory.
544
- //
545
- // Migration from the legacy ~/.gsd/agent/skills/ directory is handled
546
- // above the manifest check so it runs on every launch (including retries
547
- // after partial copy failures).
541
+ syncResourceDir(join(resourcesDir, 'skills'), skillsDir);
548
542
  // Sync GSD-WORKFLOW.md to agentDir as a fallback for when GSD_WORKFLOW_PATH
549
543
  // env var is not set (e.g. fork/dev builds, alternative entry points).
550
544
  const workflowSrc = join(resourcesDir, 'GSD-WORKFLOW.md');
@@ -18,10 +18,11 @@ import { execFileSync } from "node:child_process";
18
18
  const CLAUDE_COMMAND = process.platform === "win32" ? "claude.cmd" : "claude";
19
19
  /**
20
20
  * Windows installs vary: some environments expose `claude.cmd` (npm shim),
21
- * others expose a `claude` shim on PATH (for example Git Bash wrappers).
22
- * Try both to avoid false "not installed" results in readiness checks.
21
+ * `claude.exe` (direct binary install), or a bare `claude` shim on PATH
22
+ * (for example Git Bash wrappers). Try all three to avoid false "not
23
+ * installed" results in readiness checks.
23
24
  */
24
- const CLAUDE_COMMAND_CANDIDATES = process.platform === "win32" ? [CLAUDE_COMMAND, "claude"] : [CLAUDE_COMMAND];
25
+ const CLAUDE_COMMAND_CANDIDATES = process.platform === "win32" ? [CLAUDE_COMMAND, "claude.exe", "claude"] : [CLAUDE_COMMAND];
25
26
  function execClaude(args) {
26
27
  let lastError;
27
28
  for (const command of CLAUDE_COMMAND_CANDIDATES) {
@@ -484,25 +484,23 @@ export function makeAbortedMessage(model, lastTextContent) {
484
484
  /**
485
485
  * Resolve the Claude Code permission mode for the current run.
486
486
  *
487
- * GSD subagents run underneath a host Claude Code session the user has
488
- * already consented to, and their work (edits, shell inspection, MCP calls)
489
- * spans the full workflow toolset. Defaulting the inner SDK to
490
- * `bypassPermissions` avoids per-tool approval prompts that offer no
491
- * meaningful safety beyond what the host session and the subagent prompts
492
- * already enforce. `GSD_CLAUDE_CODE_PERMISSION_MODE` lets security-conscious
493
- * users opt into a stricter mode (`acceptEdits`, `default`, `plan`).
487
+ * Defaults to `acceptEdits`, which auto-approves file reads/edits but
488
+ * surfaces a permission dialog for dangerous operations (e.g. general Bash,
489
+ * Agent, WebFetch). This prevents tools outside the allowlist from being
490
+ * silently denied the SDK emits an `extension_ui_request` event so the
491
+ * user sees a prompt instead of a silent refusal that Claude Code mistakes
492
+ * for user rejection (#4383).
494
493
  *
495
- * Tradeoff: bypass means a prompt-injection payload read from an untrusted
496
- * file could trigger tool calls without a second gate. Accepted for GSD
497
- * because the workflow is explicit user intent and the alternative
498
- * (#4099) is continuous approval fatigue that blocks real work.
494
+ * Set `GSD_CLAUDE_CODE_PERMISSION_MODE` to `bypassPermissions` to restore
495
+ * the old always-approve behaviour, or to `default` / `plan` for stricter
496
+ * modes.
499
497
  */
500
498
  export async function resolveClaudePermissionMode(env = process.env) {
501
499
  const override = env.GSD_CLAUDE_CODE_PERMISSION_MODE?.trim();
502
500
  if (override === "bypassPermissions" || override === "acceptEdits" || override === "default" || override === "plan") {
503
501
  return override;
504
502
  }
505
- return "bypassPermissions";
503
+ return "acceptEdits";
506
504
  }
507
505
  // NOTE: These helpers intentionally mirror @gsd/pi-ai anthropic-shared
508
506
  // behavior so this extension remains typecheck-stable even when the published
@@ -554,7 +552,7 @@ function mapThinkingLevelToAnthropicEffort(level, modelId) {
554
552
  export function buildSdkOptions(modelId, prompt, overrides, extraOptions = {}) {
555
553
  const { reasoning, ...sdkExtraOptions } = extraOptions;
556
554
  const mcpServers = buildWorkflowMcpServers();
557
- const permissionMode = overrides?.permissionMode ?? "bypassPermissions";
555
+ const permissionMode = overrides?.permissionMode ?? "acceptEdits";
558
556
  const disallowedTools = ["AskUserQuestion"];
559
557
  // Pre-authorize the safe built-ins and every registered workflow MCP
560
558
  // server's tools. `acceptEdits` mode (the interactive default) only
@@ -637,6 +635,68 @@ function normalizeToolResultContent(content) {
637
635
  }
638
636
  return blocks.length > 0 ? blocks : [{ type: "text", text: "" }];
639
637
  }
638
+ /**
639
+ * Extract a `details` payload from an MCP tool-result block.
640
+ *
641
+ * MCP's `CallToolResult` carries structured data in `structuredContent` — the
642
+ * protocol's supported channel for non-text payloads. Claude Code's synthetic
643
+ * user message may surface that field in one of two shapes depending on SDK
644
+ * version: as a sibling on the `mcp_tool_result` block itself, or as a
645
+ * dedicated content sub-block with `type: "structuredContent"`. Snake-case
646
+ * (`structured_content`) is accepted defensively in case a transport hop
647
+ * rewrites casing. All other shapes fall back to an empty object so callers
648
+ * can rely on `details` being present.
649
+ */
650
+ function extractStructuredDetailsFromBlock(block) {
651
+ const sibling = block.structuredContent ?? block.structured_content;
652
+ if (sibling && typeof sibling === "object" && !Array.isArray(sibling)) {
653
+ return sibling;
654
+ }
655
+ if (Array.isArray(block.content)) {
656
+ for (const item of block.content) {
657
+ if (!item || typeof item !== "object")
658
+ continue;
659
+ const sub = item;
660
+ if (sub.type !== "structuredContent" && sub.type !== "structured_content")
661
+ continue;
662
+ const payload = sub.structuredContent ?? sub.structured_content ?? sub.data ?? sub.value;
663
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
664
+ return payload;
665
+ }
666
+ }
667
+ }
668
+ // Return undefined (not {}) when no structured payload is present, matching
669
+ // the pre-#4477 contract where `details` was nullable. An empty-object
670
+ // sentinel is truthy and breaks downstream consumers that gate on
671
+ // `if (details)`. `undefined` matches the type of the field these results
672
+ // flow into (`Record<string, unknown> | undefined`).
673
+ return undefined;
674
+ }
675
+ /**
676
+ * True for items that are MCP `structuredContent` pseudo-blocks living inside
677
+ * a tool-result `content[]` array. These blocks carry the structured payload
678
+ * (extracted separately by `extractStructuredDetailsFromBlock`) and must NOT
679
+ * leak into the visible content rendered to the user — otherwise the renderer
680
+ * stringifies the JSON pseudo-block and shows it next to the actual tool
681
+ * output. See PR #4477 review (CodeRabbit, post-fix-round).
682
+ */
683
+ function isStructuredContentPseudoBlock(item) {
684
+ if (!item || typeof item !== "object")
685
+ return false;
686
+ const type = item.type;
687
+ return type === "structuredContent" || type === "structured_content";
688
+ }
689
+ /**
690
+ * Strip `structuredContent` pseudo-blocks from a tool-result content array
691
+ * before normalization. The structured payload is extracted via the sibling
692
+ * `structuredContent` field (or a dedicated extractor pass on the raw block);
693
+ * the visible content path must not include the pseudo-block itself.
694
+ */
695
+ function stripStructuredContentPseudoBlocks(content) {
696
+ if (!Array.isArray(content))
697
+ return content;
698
+ return content.filter((item) => !isStructuredContentPseudoBlock(item));
699
+ }
640
700
  /** Extract tool result payloads from an SDK synthetic user message, keyed by tool-use ID. */
641
701
  export function extractToolResultsFromSdkUserMessage(message) {
642
702
  const extracted = [];
@@ -657,8 +717,8 @@ export function extractToolResultsFromSdkUserMessage(message) {
657
717
  extracted.push({
658
718
  toolUseId,
659
719
  result: {
660
- content: normalizeToolResultContent(block.content),
661
- details: {},
720
+ content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(block.content)),
721
+ details: extractStructuredDetailsFromBlock(block),
662
722
  isError: block.is_error === true,
663
723
  },
664
724
  });
@@ -672,8 +732,8 @@ export function extractToolResultsFromSdkUserMessage(message) {
672
732
  extracted.push({
673
733
  toolUseId,
674
734
  result: {
675
- content: normalizeToolResultContent(toolResult.content),
676
- details: {},
735
+ content: normalizeToolResultContent(stripStructuredContentPseudoBlocks(toolResult.content)),
736
+ details: extractStructuredDetailsFromBlock(toolResult),
677
737
  isError: toolResult.is_error === true,
678
738
  },
679
739
  });
@@ -1430,6 +1430,20 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
1430
1430
  }
1431
1431
  else {
1432
1432
  // s.pendingVerificationRetry was set by postUnitPreVerification.
1433
+ // Emit a dedicated journal event so forensics can distinguish bounded
1434
+ // verification retries from genuine stuck-loop dispatch repetitions (#4540).
1435
+ const retryInfo = s.pendingVerificationRetry;
1436
+ deps.emitJournalEvent({
1437
+ ts: new Date().toISOString(),
1438
+ flowId: ic.flowId,
1439
+ seq: ic.nextSeq(),
1440
+ eventType: "artifact-verification-retry",
1441
+ data: {
1442
+ unitType: preUnitSnapshot?.type,
1443
+ unitId: retryInfo?.unitId,
1444
+ attempt: retryInfo?.attempt,
1445
+ },
1446
+ });
1433
1447
  // Continue the loop — next iteration will inject the retry context into the prompt.
1434
1448
  debugLog("autoLoop", { phase: "artifact-verification-retry", iteration: ic.iteration });
1435
1449
  return { action: "continue" };
@@ -92,6 +92,33 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
92
92
  catch (e) {
93
93
  logWarning("engine", "Failed to chdir to basePath before dispatch", { basePath: s.basePath, error: String(e) });
94
94
  }
95
+ // ── Provider request-readiness pre-check (#4555) ──
96
+ // Verify the provider can accept requests before dispatching. If the token
97
+ // has expired since bootstrap, return cancelled immediately so the unit is
98
+ // not wasted on a guaranteed 401.
99
+ {
100
+ const provider = s.currentUnitModel?.provider ?? ctx.model?.provider;
101
+ const registry = ctx.modelRegistry;
102
+ if (provider && registry != null && typeof registry.isProviderRequestReady === "function") {
103
+ let ready = false;
104
+ try {
105
+ ready = registry.isProviderRequestReady(provider);
106
+ }
107
+ catch {
108
+ ready = false;
109
+ }
110
+ if (!ready) {
111
+ return {
112
+ status: "cancelled",
113
+ errorContext: {
114
+ message: `Provider ${provider} is not request-ready (login/token expired)`,
115
+ category: "provider",
116
+ isTransient: false,
117
+ },
118
+ };
119
+ }
120
+ }
121
+ }
95
122
  // ── Send the prompt ──
96
123
  debugLog("runUnit", { phase: "send-message", unitType, unitId });
97
124
  pi.sendMessage({ customType: "gsd-auto", content: prompt, display: s.verbose }, { triggerTurn: true });
@@ -291,7 +291,7 @@ autoModeStartThinkingLevel) {
291
291
  // ADR-005: Adjust active tool set for the selected model's provider capabilities.
292
292
  // Hard-filter incompatible tools, then let extensions override via adjust_tool_set hook.
293
293
  const activeToolNames = pi.getActiveTools();
294
- const { toolNames: compatibleTools, removedTools } = adjustToolSet(activeToolNames, model.api);
294
+ const { toolNames: compatibleTools, removedTools } = adjustToolSet(activeToolNames, model.api, model.provider);
295
295
  let finalToolNames = compatibleTools;
296
296
  // Fire adjust_tool_set hook — extensions can override the filtered tool set
297
297
  if (routingConfig.hooks !== false) {
@@ -535,7 +535,7 @@ export async function postUnitPreVerification(pctx, opts) {
535
535
  if (taskRow) {
536
536
  const expectedOutput = taskRow.expected_output ?? [];
537
537
  const plannedFiles = taskRow.files ?? [];
538
- const audit = validateFileChanges(s.basePath, expectedOutput, plannedFiles);
538
+ const audit = validateFileChanges(s.basePath, expectedOutput, plannedFiles, safetyConfig.file_change_allowlist);
539
539
  if (audit && audit.violations.length > 0) {
540
540
  const warnings = audit.violations.filter(v => v.severity === "warning");
541
541
  for (const v of warnings) {
@@ -220,12 +220,25 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
220
220
  // RESEARCH file. Without this, resolveExpectedArtifactPath returns null and
221
221
  // the retry/escalation machinery silently re-dispatches forever.
222
222
  //
223
+ // #4068: Also treat a PARALLEL-BLOCKER placeholder as a terminal completion
224
+ // so that timeout-recovery can write the blocker, have verifyExpectedArtifact
225
+ // return true, and let the dispatch loop advance past this unit. Without
226
+ // this, the blocker is written but verification still returns false, the unit
227
+ // is never cleared from unitDispatchCount, and on the next iteration the
228
+ // dispatch rule (which correctly skips parallel-research when PARALLEL-BLOCKER
229
+ // exists) returns null — leaving the loop stuck re-deriving indefinitely.
230
+ //
223
231
  // NOTE: this predicate mirrors the dispatch rule at
224
232
  // auto-dispatch.ts parallel-research-slices — keep the two in sync.
225
233
  if (unitType === "research-slice" && unitId.endsWith("/parallel-research")) {
226
234
  const { milestone: mid } = parseUnitId(unitId);
227
235
  if (!mid)
228
236
  return false;
237
+ // #4068: PARALLEL-BLOCKER written by timeout-recovery is a terminal state.
238
+ const blockerPath = resolveExpectedArtifactPath(unitType, unitId, base);
239
+ if (blockerPath && existsSync(blockerPath)) {
240
+ return true;
241
+ }
229
242
  const roadmapFile = resolveMilestoneFile(base, mid, "ROADMAP");
230
243
  if (!roadmapFile || !existsSync(roadmapFile)) {
231
244
  logWarning("recovery", `verify-fail ${unitType} ${unitId}: roadmap missing`);
@@ -30,8 +30,7 @@ import { initRoutingHistory } from "./routing-history.js";
30
30
  import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
31
31
  import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
32
32
  import { snapshotSkills } from "./skill-discovery.js";
33
- import { isDbAvailable, getMilestone, openDatabase } from "./gsd-db.js";
34
- import { hideFooter } from "./auto-dashboard.js";
33
+ import { isDbAvailable, getMilestone, openDatabase, getDbStatus } from "./gsd-db.js";
35
34
  import { debugLog, enableDebug, isDebugEnabled, getDebugLogPath, } from "./debug-logger.js";
36
35
  import { logWarning, logError } from "./workflow-logger.js";
37
36
  import { existsSync, mkdirSync, readdirSync, rmSync, statSync, unlinkSync, } from "node:fs";
@@ -247,7 +246,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
247
246
  // the parent git root). See #2393 and related issue.
248
247
  const hasLocalGit = existsSync(join(base, ".git"));
249
248
  if (!hasLocalGit || isInheritedRepo(base)) {
250
- const mainBranch = loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
249
+ const mainBranch = loadEffectiveGSDPreferences(base)?.preferences?.git?.main_branch || "main";
251
250
  nativeInit(base, mainBranch);
252
251
  }
253
252
  // Migrate legacy in-project .gsd/ to external state directory.
@@ -263,7 +262,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
263
262
  // Ensure .gitignore has baseline patterns.
264
263
  // ensureGitignore checks for git-tracked .gsd/ files and skips the
265
264
  // ".gsd" pattern if the project intentionally tracks .gsd/ in git.
266
- const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
265
+ const gitPrefs = loadEffectiveGSDPreferences(base)?.preferences?.git;
267
266
  const manageGitignore = gitPrefs?.manage_gitignore;
268
267
  ensureGitignore(base, { manageGitignore });
269
268
  if (manageGitignore !== false)
@@ -289,7 +288,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
289
288
  prepareWorkflowMcpForProject(ctx, base);
290
289
  }
291
290
  // Initialize GitServiceImpl
292
- s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
291
+ s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences(base)?.preferences?.git ?? {});
293
292
  // ── Debug mode ──
294
293
  if (!isDebugEnabled() && process.env.GSD_DEBUG === "1") {
295
294
  enableDebug(base);
@@ -322,7 +321,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
322
321
  // was lost due to session ending between completion and teardown.
323
322
  // Must run after DB open and before worktree entry.
324
323
  try {
325
- const auditResult = auditOrphanedMilestoneBranches(base, getIsolationMode());
324
+ const auditResult = auditOrphanedMilestoneBranches(base, getIsolationMode(base));
326
325
  for (const msg of auditResult.recovered) {
327
326
  ctx.ui.notify(`Orphan audit: ${msg}`, "info");
328
327
  }
@@ -340,7 +339,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
340
339
  let state = await deriveState(base);
341
340
  // Stale worktree state recovery (#654)
342
341
  if (state.activeMilestone &&
343
- shouldUseWorktreeIsolation() &&
342
+ shouldUseWorktreeIsolation(base) &&
344
343
  !detectWorktreeName(base)) {
345
344
  const wtPath = getAutoWorktreePath(base, state.activeMilestone.id);
346
345
  if (wtPath) {
@@ -355,7 +354,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
355
354
  let hasSurvivorBranch = false;
356
355
  if (state.activeMilestone &&
357
356
  (state.phase === "pre-planning" || state.phase === "complete") &&
358
- getIsolationMode() !== "none" &&
357
+ getIsolationMode(base) !== "none" &&
359
358
  !detectWorktreeName(base) &&
360
359
  !base.includes(`${pathSep}.gsd${pathSep}worktrees${pathSep}`)) {
361
360
  const milestoneBranch = `milestone/${state.activeMilestone.id}`;
@@ -516,7 +515,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
516
515
  registerSigtermHandler(base);
517
516
  // Capture integration branch
518
517
  if (s.currentMilestoneId) {
519
- if (getIsolationMode() !== "none") {
518
+ if (getIsolationMode(base) !== "none") {
520
519
  captureIntegrationBranch(base, s.currentMilestoneId);
521
520
  }
522
521
  setActiveMilestoneId(base, s.currentMilestoneId);
@@ -524,7 +523,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
524
523
  // Guard against stale milestone branch when isolation:none (#3613).
525
524
  // A prior session with isolation:branch/worktree may have left HEAD on
526
525
  // milestone/<MID>. Auto-checkout back to the integration branch.
527
- if (getIsolationMode() === "none" && nativeIsRepo(base)) {
526
+ if (getIsolationMode(base) === "none" && nativeIsRepo(base)) {
528
527
  try {
529
528
  const currentBranch = nativeGetCurrentBranch(base);
530
529
  if (currentBranch.startsWith("milestone/")) {
@@ -552,7 +551,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
552
551
  return symlinkRe.test(p);
553
552
  };
554
553
  if (s.currentMilestoneId &&
555
- getIsolationMode() !== "none" &&
554
+ getIsolationMode(base) !== "none" &&
556
555
  !detectWorktreeName(base) &&
557
556
  !isUnderGsdWorktrees(base)) {
558
557
  buildResolver().enterMilestone(s.currentMilestoneId, {
@@ -597,8 +596,21 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
597
596
  // call returns "db_unavailable", triggering artifact-retry which
598
597
  // re-dispatches the same task — producing an infinite loop (#2419).
599
598
  if (existsSync(gsdDbPath) && !isDbAvailable()) {
600
- ctx.ui.notify("SQLite database exists but failed to open. Auto-mode cannot proceed without a working database provider. " +
601
- "Check for corrupt gsd.db or missing native SQLite bindings.", "error");
599
+ const dbStatus = getDbStatus();
600
+ const phaseHint = dbStatus.lastPhase === "open"
601
+ ? "The database file could not be opened"
602
+ : dbStatus.lastPhase === "initSchema"
603
+ ? "The database schema could not be initialized"
604
+ : dbStatus.lastPhase === "vacuum-recovery"
605
+ ? "Corruption recovery (VACUUM) failed"
606
+ : dbStatus.attempted
607
+ ? "The database could not be opened (phase unknown)"
608
+ : "The database provider could not be loaded";
609
+ const errorDetail = dbStatus.lastError ? ` (${dbStatus.lastError.message})` : "";
610
+ const providerHint = dbStatus.provider
611
+ ? ` Provider: ${dbStatus.provider}.`
612
+ : " No SQLite provider available — check Node >= 22 or install better-sqlite3.";
613
+ ctx.ui.notify(`SQLite database exists but failed to open: ${gsdDbPath}. ${phaseHint}${errorDetail}.${providerHint}`, "error");
602
614
  return releaseLockAndReturn();
603
615
  }
604
616
  // Initialize metrics
@@ -633,13 +645,10 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
633
645
  }
634
646
  }
635
647
  // Snapshot installed skills
636
- if (resolveSkillDiscoveryMode() !== "off") {
648
+ if (resolveSkillDiscoveryMode(base) !== "off") {
637
649
  snapshotSkills();
638
650
  }
639
651
  ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
640
- ctx.ui.setFooter(hideFooter);
641
- // Hide gsd-health during AUTO — gsd-progress is the single source of truth
642
- // for last-commit / cost / health signal while auto is running.
643
652
  ctx.ui.setWidget("gsd-health", undefined);
644
653
  const modeLabel = s.stepMode ? "Step-mode" : "Auto-mode";
645
654
  const pendingCount = (state.registry ?? []).filter((m) => m.status !== "complete" && m.status !== "parked").length;
@@ -661,7 +670,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
661
670
  // FlatRateContext used by selectAndApplyModel so user-declared
662
671
  // flat-rate providers and externalCli auto-detection are respected.
663
672
  const { isFlatRateProvider, buildFlatRateContext } = await import("./auto-model-selection.js");
664
- const bannerPrefs = loadEffectiveGSDPreferences()?.preferences;
673
+ const bannerPrefs = loadEffectiveGSDPreferences(base)?.preferences;
665
674
  const effectiveProvider = s.autoModeStartModel?.provider ?? ctx.model?.provider;
666
675
  const effectivelyEnabled = routingConfig.enabled
667
676
  && (routingConfig.allow_flat_rate_providers