gsd-pi 2.66.0 → 2.66.1-dev.3c26b49

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 (199) hide show
  1. package/dist/claude-cli-check.d.ts +8 -0
  2. package/dist/claude-cli-check.js +36 -0
  3. package/dist/cli.js +40 -0
  4. package/dist/onboarding.js +19 -2
  5. package/dist/resources/extensions/claude-code-cli/readiness.js +63 -12
  6. package/dist/resources/extensions/gsd/auto/phases.js +15 -2
  7. package/dist/resources/extensions/gsd/auto-model-selection.js +12 -3
  8. package/dist/resources/extensions/gsd/auto-prompts.js +167 -19
  9. package/dist/resources/extensions/gsd/auto.js +13 -1
  10. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +32 -1
  11. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +5 -0
  12. package/dist/resources/extensions/gsd/context-store.js +134 -2
  13. package/dist/resources/extensions/gsd/preferences.js +6 -1
  14. package/dist/web/standalone/.next/BUILD_ID +1 -1
  15. package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
  16. package/dist/web/standalone/.next/build-manifest.json +3 -3
  17. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  18. package/dist/web/standalone/.next/required-server-files.json +3 -3
  19. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  20. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  21. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  22. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  30. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  33. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  40. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  41. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  42. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  43. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  44. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  45. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  46. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  47. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  49. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  58. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  78. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  88. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  94. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  108. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  110. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  112. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  114. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/index.html +1 -1
  124. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  125. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  126. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  127. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  128. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  129. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  130. package/dist/web/standalone/.next/server/app/page.js +2 -2
  131. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
  133. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  134. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  135. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/middleware.js +2 -2
  137. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  138. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  139. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  140. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  141. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  142. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  143. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  144. package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
  145. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  146. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  147. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  148. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  149. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  150. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  151. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  152. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  153. package/dist/web/standalone/server.js +1 -1
  154. package/package.json +1 -1
  155. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +3 -0
  156. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/agent-session.js +1 -0
  158. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +16 -0
  160. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  161. package/packages/pi-coding-agent/dist/core/retry-handler.js +58 -1
  162. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  163. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +58 -0
  164. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  165. package/packages/pi-coding-agent/dist/core/sdk.d.ts +3 -0
  166. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  167. package/packages/pi-coding-agent/dist/core/sdk.js +1 -0
  168. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  169. package/packages/pi-coding-agent/package.json +1 -1
  170. package/packages/pi-coding-agent/src/core/agent-session.ts +4 -0
  171. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +69 -0
  172. package/packages/pi-coding-agent/src/core/retry-handler.ts +66 -1
  173. package/packages/pi-coding-agent/src/core/sdk.ts +5 -0
  174. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  175. package/packages/pi-tui/dist/tui.js +1 -3
  176. package/packages/pi-tui/dist/tui.js.map +1 -1
  177. package/packages/pi-tui/src/tui.ts +1 -3
  178. package/pkg/package.json +1 -1
  179. package/src/resources/extensions/claude-code-cli/readiness.ts +67 -12
  180. package/src/resources/extensions/gsd/auto/phases.ts +20 -2
  181. package/src/resources/extensions/gsd/auto-model-selection.ts +12 -3
  182. package/src/resources/extensions/gsd/auto-prompts.ts +190 -19
  183. package/src/resources/extensions/gsd/auto.ts +12 -1
  184. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -1
  185. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +6 -0
  186. package/src/resources/extensions/gsd/context-store.ts +167 -2
  187. package/src/resources/extensions/gsd/preferences.ts +6 -1
  188. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +21 -7
  189. package/src/resources/extensions/gsd/tests/context-store.test.ts +176 -0
  190. package/src/resources/extensions/gsd/tests/decision-scope-cascade.test.ts +370 -0
  191. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +2 -2
  192. package/src/resources/extensions/gsd/tests/measurement.test.ts +531 -0
  193. package/src/resources/extensions/gsd/tests/preferences.test.ts +20 -0
  194. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +60 -0
  195. package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
  196. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  197. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  198. /package/dist/web/standalone/.next/static/{Bdk1mnQugYZh7ZxuXUYvc → ZzNRjwBFLOhqEu4BYCQi9}/_buildManifest.js +0 -0
  199. /package/dist/web/standalone/.next/static/{Bdk1mnQugYZh7ZxuXUYvc → ZzNRjwBFLOhqEu4BYCQi9}/_ssgManifest.js +0 -0
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Check if the `claude` binary is installed (regardless of auth state).
3
+ */
4
+ export declare function isClaudeBinaryInstalled(): boolean;
5
+ /**
6
+ * Check if the `claude` CLI is installed AND authenticated.
7
+ */
8
+ export declare function isClaudeCliReady(): boolean;
@@ -0,0 +1,36 @@
1
+ // GSD2 — Claude CLI binary detection for onboarding
2
+ // Lightweight check used at onboarding time (before extensions load).
3
+ // The full readiness check with caching lives in the claude-code-cli extension.
4
+ import { execFileSync } from 'node:child_process';
5
+ /**
6
+ * Check if the `claude` binary is installed (regardless of auth state).
7
+ */
8
+ export function isClaudeBinaryInstalled() {
9
+ try {
10
+ execFileSync('claude', ['--version'], { timeout: 5_000, stdio: 'pipe' });
11
+ return true;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ /**
18
+ * Check if the `claude` CLI is installed AND authenticated.
19
+ */
20
+ export function isClaudeCliReady() {
21
+ try {
22
+ execFileSync('claude', ['--version'], { timeout: 5_000, stdio: 'pipe' });
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ try {
28
+ const output = execFileSync('claude', ['auth', 'status'], { timeout: 5_000, stdio: 'pipe' })
29
+ .toString()
30
+ .toLowerCase();
31
+ return !(/not logged in|no credentials|unauthenticated|not authenticated/i.test(output));
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ }
package/dist/cli.js CHANGED
@@ -395,8 +395,28 @@ if (isPrintMode) {
395
395
  settingsManager,
396
396
  sessionManager,
397
397
  resourceLoader,
398
+ isClaudeCodeReady: () => modelRegistry.isProviderRequestReady('claude-code'),
398
399
  });
399
400
  markStartup('createAgentSession');
401
+ // Migrate anthropic OAuth users to claude-code provider when CLI is available (#3772).
402
+ // Anthropic blocks third-party apps from using subscription quotas — routing through
403
+ // the local claude CLI binary is TOS-compliant.
404
+ if (modelRegistry.isProviderRequestReady('claude-code') && settingsManager.getDefaultProvider() === 'anthropic') {
405
+ const currentModelId = settingsManager.getDefaultModel();
406
+ if (currentModelId) {
407
+ const ccModel = modelRegistry.find('claude-code', currentModelId);
408
+ if (ccModel) {
409
+ try {
410
+ await session.setModel(ccModel);
411
+ // Only persist after successful session switch to avoid desync
412
+ settingsManager.setDefaultModelAndProvider('claude-code', currentModelId);
413
+ }
414
+ catch {
415
+ // claude-code provider not ready — leave both session and settings unchanged
416
+ }
417
+ }
418
+ }
419
+ }
400
420
  // Validate configured model AFTER extensions have registered their models (#2626).
401
421
  // Before this, extension-provided models (e.g. claude-code/*) were not yet in the
402
422
  // registry, causing the user's valid choice to be silently overwritten.
@@ -550,8 +570,28 @@ const { session, extensionsResult, modelFallbackMessage: interactiveFallbackMsg
550
570
  settingsManager,
551
571
  sessionManager,
552
572
  resourceLoader,
573
+ isClaudeCodeReady: () => modelRegistry.isProviderRequestReady('claude-code'),
553
574
  });
554
575
  markStartup('createAgentSession');
576
+ // Migrate anthropic OAuth users to claude-code provider when CLI is available (#3772).
577
+ // Anthropic blocks third-party apps from using subscription quotas — routing through
578
+ // the local claude CLI binary is TOS-compliant.
579
+ if (modelRegistry.isProviderRequestReady('claude-code') && settingsManager.getDefaultProvider() === 'anthropic') {
580
+ const currentModelId = settingsManager.getDefaultModel();
581
+ if (currentModelId) {
582
+ const ccModel = modelRegistry.find('claude-code', currentModelId);
583
+ if (ccModel) {
584
+ try {
585
+ await session.setModel(ccModel);
586
+ // Only persist after successful session switch to avoid desync
587
+ settingsManager.setDefaultModelAndProvider('claude-code', currentModelId);
588
+ }
589
+ catch {
590
+ // claude-code provider not ready — leave both session and settings unchanged
591
+ }
592
+ }
593
+ }
594
+ }
555
595
  // Validate configured model AFTER extensions have registered their models (#2626).
556
596
  // Before this, extension-provided models (e.g. claude-code/*) were not yet in the
557
597
  // registry, causing the user's valid choice to be silently overwritten.
@@ -14,6 +14,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
14
14
  import { dirname, join } from 'node:path';
15
15
  import { renderLogo } from './logo.js';
16
16
  import { agentDir } from './app-paths.js';
17
+ import { isClaudeCliReady } from './claude-cli-check.js';
17
18
  // ─── Constants ────────────────────────────────────────────────────────────────
18
19
  const TOOL_KEYS = [
19
20
  {
@@ -39,6 +40,7 @@ const TOOL_KEYS = [
39
40
  const LLM_PROVIDER_IDS = [
40
41
  'anthropic',
41
42
  'anthropic-vertex',
43
+ 'claude-code',
42
44
  'openai',
43
45
  'github-copilot',
44
46
  'openai-codex',
@@ -255,7 +257,12 @@ async function runLlmStep(p, pc, authStorage) {
255
257
  if (existingAuth) {
256
258
  authOptions.push({ value: 'keep', label: `Keep current (${existingAuth})`, hint: 'already configured' });
257
259
  }
258
- authOptions.push({ value: 'browser', label: 'Sign in with your browser', hint: 'recommended same login as claude.ai / ChatGPT' }, { value: 'api-key', label: 'Paste an API key', hint: 'from your provider dashboard' }, { value: 'skip', label: 'Skip for now', hint: 'use /login inside GSD later' });
260
+ // Show Claude Code CLI option at the top when the CLI is installed and authenticated (#3772).
261
+ // This is the only TOS-compliant path for Anthropic subscription users.
262
+ if (isClaudeCliReady()) {
263
+ authOptions.push({ value: 'claude-cli', label: 'Use Claude Code CLI', hint: 'recommended — uses your existing Claude subscription' });
264
+ }
265
+ authOptions.push({ value: 'browser', label: 'Sign in with your browser', hint: 'GitHub Copilot, ChatGPT, Google, etc.' }, { value: 'api-key', label: 'Paste an API key', hint: 'from your provider dashboard' }, { value: 'skip', label: 'Skip for now', hint: 'use /login inside GSD later' });
259
266
  const method = await p.select({
260
267
  message: existingAuth ? `LLM provider: ${existingAuth} — change it?` : 'How do you want to sign in?',
261
268
  options: authOptions,
@@ -264,12 +271,22 @@ async function runLlmStep(p, pc, authStorage) {
264
271
  return false;
265
272
  if (method === 'keep')
266
273
  return true;
274
+ // ── Claude Code CLI path (#3772) ────────────────────────────────────────
275
+ if (method === 'claude-cli') {
276
+ p.log.success('Claude Code CLI detected — routing through local CLI (TOS-compliant)');
277
+ p.log.info('Your Claude subscription will be used for inference. No API key needed.');
278
+ // Store sentinel so hasAuth('claude-code') returns true on future boots
279
+ authStorage.set('claude-code', { type: 'api_key', key: 'cli' });
280
+ return true;
281
+ }
267
282
  // ── Step 2: Which provider? ──────────────────────────────────────────────
268
283
  if (method === 'browser') {
284
+ // Anthropic OAuth is removed from browser auth — it violates Anthropic TOS for
285
+ // third-party apps (#3772). Anthropic subscription users should use the Claude
286
+ // Code CLI path (shown above when CLI is installed) or paste an API key.
269
287
  const provider = await p.select({
270
288
  message: 'Choose provider',
271
289
  options: [
272
- { value: 'anthropic', label: 'Anthropic (Claude)', hint: 'recommended' },
273
290
  { value: 'github-copilot', label: 'GitHub Copilot' },
274
291
  { value: 'openai-codex', label: 'ChatGPT Plus/Pro (Codex)' },
275
292
  { value: 'google-gemini-cli', label: 'Google Gemini CLI' },
@@ -1,26 +1,77 @@
1
1
  /**
2
2
  * Readiness check for the Claude Code CLI provider.
3
3
  *
4
- * Verifies the `claude` binary is installed and responsive.
5
- * Result is cached for 30 seconds to avoid shelling out on every
4
+ * Verifies the `claude` binary is installed, responsive, AND authenticated.
5
+ * Results are cached for 30 seconds to avoid shelling out on every
6
6
  * model-availability check.
7
+ *
8
+ * Auth verification follows the T3 Code pattern: run `claude auth status`
9
+ * and check the exit code + output for an authenticated session.
7
10
  */
8
- import { execSync } from "node:child_process";
9
- let cachedReady = null;
11
+ import { execFileSync } from "node:child_process";
12
+ let cachedBinaryPresent = null;
13
+ let cachedAuthed = null;
10
14
  let lastCheckMs = 0;
11
15
  const CHECK_INTERVAL_MS = 30_000;
12
- export function isClaudeCodeReady() {
16
+ function refreshCache() {
13
17
  const now = Date.now();
14
- if (cachedReady !== null && now - lastCheckMs < CHECK_INTERVAL_MS) {
15
- return cachedReady;
18
+ if (cachedBinaryPresent !== null && now - lastCheckMs < CHECK_INTERVAL_MS) {
19
+ return;
16
20
  }
21
+ // Set timestamp first to prevent re-entrant checks during the same window
22
+ lastCheckMs = now;
23
+ // Check binary presence
17
24
  try {
18
- execSync("claude --version", { timeout: 5_000, stdio: "pipe" });
19
- cachedReady = true;
25
+ execFileSync("claude", ["--version"], { timeout: 5_000, stdio: "pipe" });
26
+ cachedBinaryPresent = true;
20
27
  }
21
28
  catch {
22
- cachedReady = false;
29
+ cachedBinaryPresent = false;
30
+ cachedAuthed = false;
31
+ return;
23
32
  }
24
- lastCheckMs = now;
25
- return cachedReady;
33
+ // Check auth status — exit code 0 with non-error output means authenticated
34
+ try {
35
+ const output = execFileSync("claude", ["auth", "status"], { timeout: 5_000, stdio: "pipe" })
36
+ .toString()
37
+ .toLowerCase();
38
+ // The CLI outputs "not logged in", "no credentials", or similar when unauthenticated
39
+ cachedAuthed = !(/not logged in|no credentials|unauthenticated|not authenticated/i.test(output));
40
+ }
41
+ catch {
42
+ // Non-zero exit code means not authenticated
43
+ cachedAuthed = false;
44
+ }
45
+ }
46
+ /**
47
+ * Whether the `claude` binary is installed (regardless of auth state).
48
+ */
49
+ export function isClaudeBinaryPresent() {
50
+ refreshCache();
51
+ return cachedBinaryPresent ?? false;
52
+ }
53
+ /**
54
+ * Whether the `claude` CLI is authenticated with a valid session.
55
+ * Returns false if the binary is not installed.
56
+ */
57
+ export function isClaudeCodeAuthed() {
58
+ refreshCache();
59
+ return (cachedBinaryPresent ?? false) && (cachedAuthed ?? false);
60
+ }
61
+ /**
62
+ * Full readiness check: binary installed AND authenticated.
63
+ * This is the gating function used by the provider registration.
64
+ */
65
+ export function isClaudeCodeReady() {
66
+ refreshCache();
67
+ return (cachedBinaryPresent ?? false) && (cachedAuthed ?? false);
68
+ }
69
+ /**
70
+ * Force-clear the cached readiness state.
71
+ * Useful after the user completes auth setup so the next check is fresh.
72
+ */
73
+ export function clearReadinessCache() {
74
+ cachedBinaryPresent = null;
75
+ cachedAuthed = null;
76
+ lastCheckMs = 0;
26
77
  }
@@ -947,8 +947,21 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
947
947
  debugLog("autoLoop", { phase: "exit", reason: "provider-pause", isTransient: unitResult.errorContext.isTransient });
948
948
  return { action: "break", reason: "provider-pause" };
949
949
  }
950
- ctx.ui.notify(`Session creation timed out or was cancelled for ${unitType} ${unitId}. Will retry.`, "warning");
951
- await deps.stopAuto(ctx, pi, "Session creation failed");
950
+ // Session creation timeout (not a structural error): pause auto-mode
951
+ // and let the provider-error-resume timer handle recovery. This matches
952
+ // the provider-pause path — break out cleanly, don't hard-stop.
953
+ // Structural errors (TypeError, is not a function) are NOT transient
954
+ // and must hard-stop to avoid infinite retry loops.
955
+ if (unitResult.errorContext?.isTransient &&
956
+ unitResult.errorContext?.category === "timeout") {
957
+ ctx.ui.notify(`Session creation timed out for ${unitType} ${unitId}. Will retry.`, "warning");
958
+ debugLog("autoLoop", { phase: "session-timeout-pause", unitType, unitId });
959
+ await deps.pauseAuto(ctx, pi);
960
+ return { action: "break", reason: "session-timeout" };
961
+ }
962
+ // All other cancelled states (structural errors, non-transient failures): hard stop
963
+ ctx.ui.notify(`Session creation failed for ${unitType} ${unitId}: ${unitResult.errorContext?.message ?? "unknown"}. Stopping auto-mode.`, "warning");
964
+ await deps.stopAuto(ctx, pi, `Session creation failed: ${unitResult.errorContext?.message ?? "unknown"}`);
952
965
  debugLog("autoLoop", { phase: "exit", reason: "session-failed" });
953
966
  return { action: "break", reason: "session-failed" };
954
967
  }
@@ -252,8 +252,17 @@ export function resolveModelId(modelId, availableModels, currentProvider) {
252
252
  return undefined;
253
253
  if (candidates.length === 1)
254
254
  return candidates[0];
255
- // Extension / CLI-wrapper providers that should never win bare-ID resolution
256
- // when a first-class API provider also offers the same model.
255
+ // When the user's current provider is claude-code (set by startup migration
256
+ // or explicit selection), honour it for bare IDs. Routing back to anthropic
257
+ // would undo the migration and hit the third-party subscription block (#3772).
258
+ if (currentProvider === "claude-code") {
259
+ const ccMatch = candidates.find(m => m.provider === "claude-code");
260
+ if (ccMatch)
261
+ return ccMatch;
262
+ }
263
+ // Extension / CLI-wrapper providers that should not win bare-ID resolution
264
+ // when a first-class API provider also offers the same model AND the user
265
+ // has not explicitly chosen the extension provider.
257
266
  const EXTENSION_PROVIDERS = new Set(["claude-code"]);
258
267
  // Prefer currentProvider only when it is a first-class API provider
259
268
  if (currentProvider && !EXTENSION_PROVIDERS.has(currentProvider)) {
@@ -274,7 +283,7 @@ export function resolveModelId(modelId, availableModels, currentProvider) {
274
283
  * Uses case-insensitive matching with alias support to prevent fail-open on
275
284
  * provider naming variations (e.g. "copilot" vs "github-copilot").
276
285
  */
277
- const FLAT_RATE_PROVIDERS = new Set(["github-copilot", "copilot"]);
286
+ const FLAT_RATE_PROVIDERS = new Set(["github-copilot", "copilot", "claude-code"]);
278
287
  export function isFlatRateProvider(provider) {
279
288
  return FLAT_RATE_PROVIDERS.has(provider.toLowerCase());
280
289
  }
@@ -219,7 +219,12 @@ export async function inlineGsdRootFile(base, filename, label) {
219
219
  // ─── DB-Aware Inline Helpers ──────────────────────────────────────────────
220
220
  /**
221
221
  * Inline decisions with optional milestone scoping from the DB.
222
- * Falls back to filesystem via inlineGsdRootFile when DB unavailable or empty.
222
+ * Falls back to filesystem via inlineGsdRootFile only when DB is unavailable.
223
+ *
224
+ * Cascade logic (R005):
225
+ * 1. Query with { milestoneId, scope } if scope provided
226
+ * 2. If empty AND scope was provided, retry with { milestoneId } only (drop scope)
227
+ * 3. If still empty, return null (intentional per D020)
223
228
  */
224
229
  export async function inlineDecisionsFromDb(base, milestoneId, scope, level) {
225
230
  const inlineLevel = level ?? resolveInlineLevel();
@@ -227,7 +232,12 @@ export async function inlineDecisionsFromDb(base, milestoneId, scope, level) {
227
232
  const { isDbAvailable } = await import("./gsd-db.js");
228
233
  if (isDbAvailable()) {
229
234
  const { queryDecisions, formatDecisionsForPrompt } = await import("./context-store.js");
230
- const decisions = queryDecisions({ milestoneId, scope });
235
+ // First query: try with both milestoneId and scope (if scope provided)
236
+ let decisions = queryDecisions({ milestoneId, scope });
237
+ // Cascade: if empty AND scope was provided, retry without scope
238
+ if (decisions.length === 0 && scope) {
239
+ decisions = queryDecisions({ milestoneId });
240
+ }
231
241
  if (decisions.length > 0) {
232
242
  // Use compact format for non-full levels to save ~35% tokens
233
243
  const formatted = inlineLevel !== "full"
@@ -235,24 +245,27 @@ export async function inlineDecisionsFromDb(base, milestoneId, scope, level) {
235
245
  : formatDecisionsForPrompt(decisions);
236
246
  return `### Decisions\nSource: \`.gsd/DECISIONS.md\`\n\n${formatted}`;
237
247
  }
248
+ // DB available but cascade returned empty — intentional per D020, don't fall back to file
249
+ return null;
238
250
  }
239
251
  }
240
252
  catch (err) {
241
253
  logWarning("prompt", `inlineDecisionsFromDb failed: ${err instanceof Error ? err.message : String(err)}`);
242
254
  }
255
+ // DB unavailable — fall back to filesystem
243
256
  return inlineGsdRootFile(base, "decisions.md", "Decisions");
244
257
  }
245
258
  /**
246
- * Inline requirements with optional slice scoping from the DB.
259
+ * Inline requirements with optional milestone and slice scoping from the DB.
247
260
  * Falls back to filesystem via inlineGsdRootFile when DB unavailable or empty.
248
261
  */
249
- export async function inlineRequirementsFromDb(base, sliceId, level) {
262
+ export async function inlineRequirementsFromDb(base, milestoneId, sliceId, level) {
250
263
  const inlineLevel = level ?? resolveInlineLevel();
251
264
  try {
252
265
  const { isDbAvailable } = await import("./gsd-db.js");
253
266
  if (isDbAvailable()) {
254
267
  const { queryRequirements, formatRequirementsForPrompt } = await import("./context-store.js");
255
- const requirements = queryRequirements({ sliceId });
268
+ const requirements = queryRequirements({ milestoneId, sliceId });
256
269
  if (requirements.length > 0) {
257
270
  // Use compact format for non-full levels to save ~40% tokens
258
271
  const formatted = inlineLevel !== "full"
@@ -287,6 +300,117 @@ export async function inlineProjectFromDb(base) {
287
300
  }
288
301
  return inlineGsdRootFile(base, "project.md", "Project");
289
302
  }
303
+ // ─── Stopwords for keyword extraction ─────────────────────────────────────
304
+ const STOPWORDS = new Set(['of', 'the', 'and', 'a', 'for', '+', '-', 'to', 'in', 'on', 'with', 'is', 'as', 'by']);
305
+ // Generic words that don't provide meaningful scope differentiation
306
+ const GENERIC_WORDS = new Set([
307
+ 'setup', 'integration', 'implementation', 'testing', 'test', 'tests',
308
+ 'config', 'configuration', 'init', 'initial', 'basic', 'core',
309
+ 'main', 'primary', 'final', 'complete', 'finish', 'end',
310
+ 'start', 'begin', 'first', 'last', 'update', 'updates',
311
+ 'fix', 'fixes', 'add', 'adds', 'remove', 'removes',
312
+ 'create', 'creates', 'build', 'builds', 'deploy', 'deployment',
313
+ 'refactor', 'refactoring', 'cleanup', 'polish', 'review',
314
+ // Process/activity words that describe what you're doing, not what domain
315
+ 'hardening', 'validation', 'verification', 'optimization',
316
+ 'improvement', 'enhancement', 'infrastructure',
317
+ ]);
318
+ // Pattern to match slice/milestone/task IDs (e.g., S01, M001, T03)
319
+ const UNIT_ID_PATTERN = /^[smt]\d+$/i;
320
+ /**
321
+ * Derive a scope keyword from slice title and optional description.
322
+ * Returns the most specific noun (first non-generic keyword) for decision scoping.
323
+ *
324
+ * Examples:
325
+ * - "Auth Middleware & Protected Route" → "auth"
326
+ * - "Database & User Model Setup" → "database"
327
+ * - "Integration Testing" → undefined (too generic)
328
+ * - "API Rate Limiting" → "api"
329
+ *
330
+ * @param sliceTitle - The slice title
331
+ * @param sliceDescription - Optional roadmap description (demo text)
332
+ * @returns A single lowercase keyword or undefined if no meaningful scope
333
+ */
334
+ export function deriveSliceScope(sliceTitle, sliceDescription) {
335
+ // Combine title and description for keyword extraction
336
+ const combinedText = sliceDescription
337
+ ? `${sliceTitle} ${sliceDescription}`
338
+ : sliceTitle;
339
+ // Extract all words, lowercase, remove punctuation
340
+ const words = combinedText
341
+ .split(/[\s&+,;:|/\\()-]+/)
342
+ .map(w => w.toLowerCase().replace(/[^a-z0-9]/g, ''))
343
+ .filter(w => w.length >= 2);
344
+ // Find the first word that is:
345
+ // 1. Not a stopword
346
+ // 2. Not a generic word
347
+ // 3. Not a unit ID (S01, M001, T03)
348
+ // 4. At least 3 characters (meaningful scope)
349
+ for (const word of words) {
350
+ if (STOPWORDS.has(word))
351
+ continue;
352
+ if (GENERIC_WORDS.has(word))
353
+ continue;
354
+ if (UNIT_ID_PATTERN.test(word))
355
+ continue;
356
+ if (word.length < 3)
357
+ continue;
358
+ return word;
359
+ }
360
+ return undefined;
361
+ }
362
+ /**
363
+ * Extract keywords from a slice title for scoped knowledge queries.
364
+ * Splits on whitespace, filters stopwords, lowercases.
365
+ * Example: 'KNOWLEDGE scoping + roadmap excerpt' → ['knowledge', 'scoping', 'roadmap', 'excerpt']
366
+ */
367
+ function extractKeywords(title) {
368
+ return title
369
+ .split(/\s+/)
370
+ .map(w => w.toLowerCase().replace(/[^a-z0-9]/g, ''))
371
+ .filter(w => w.length > 0 && !STOPWORDS.has(w));
372
+ }
373
+ /**
374
+ * Inline scoped KNOWLEDGE.md content based on keywords from slice title.
375
+ * Reads KNOWLEDGE.md, filters to sections matching keywords, formats with header.
376
+ * Returns null if no KNOWLEDGE.md exists or no sections match.
377
+ */
378
+ export async function inlineKnowledgeScoped(base, keywords) {
379
+ const knowledgePath = resolveGsdRootFile(base, "KNOWLEDGE");
380
+ if (!existsSync(knowledgePath))
381
+ return null;
382
+ const content = await loadFile(knowledgePath);
383
+ if (!content)
384
+ return null;
385
+ // Import queryKnowledge from context-store
386
+ const { queryKnowledge } = await import("./context-store.js");
387
+ const scoped = await queryKnowledge(content, keywords);
388
+ // Return null if no sections matched (empty string from queryKnowledge)
389
+ if (!scoped)
390
+ return null;
391
+ return `### Project Knowledge (scoped)\nSource: \`${relGsdRootFile("KNOWLEDGE")}\`\n\n${scoped.trim()}`;
392
+ }
393
+ /**
394
+ * Inline a roadmap excerpt for a specific slice.
395
+ * Reads full roadmap, extracts minimal excerpt with header + predecessor + target row.
396
+ * Returns null if roadmap doesn't exist or slice not found.
397
+ */
398
+ export async function inlineRoadmapExcerpt(base, mid, sid) {
399
+ const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
400
+ if (!roadmapPath || !existsSync(roadmapPath))
401
+ return null;
402
+ const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
403
+ const content = await loadFile(roadmapPath);
404
+ if (!content)
405
+ return null;
406
+ // Import formatRoadmapExcerpt from context-store
407
+ const { formatRoadmapExcerpt } = await import("./context-store.js");
408
+ const excerpt = formatRoadmapExcerpt(content, sid, roadmapRel);
409
+ // Return null if slice not found in roadmap
410
+ if (!excerpt)
411
+ return null;
412
+ return `### Milestone Roadmap (excerpt)\nSource: \`${roadmapRel}\`\n\n${excerpt}`;
413
+ }
290
414
  // ─── Skill Activation & Discovery ─────────────────────────────────────────
291
415
  function normalizeSkillReference(ref) {
292
416
  const normalized = ref.replace(/\\/g, "/").trim();
@@ -772,7 +896,7 @@ export async function buildResearchMilestonePrompt(mid, midTitle, base) {
772
896
  const projectInline = await inlineProjectFromDb(base);
773
897
  if (projectInline)
774
898
  inlined.push(projectInline);
775
- const requirementsInline = await inlineRequirementsFromDb(base);
899
+ const requirementsInline = await inlineRequirementsFromDb(base, mid);
776
900
  if (requirementsInline)
777
901
  inlined.push(requirementsInline);
778
902
  const decisionsInline = await inlineDecisionsFromDb(base, mid);
@@ -823,7 +947,7 @@ export async function buildPlanMilestonePrompt(mid, midTitle, base, level) {
823
947
  const projectInline = await inlineProjectFromDb(base);
824
948
  if (projectInline)
825
949
  inlined.push(projectInline);
826
- const requirementsInline = await inlineRequirementsFromDb(base, undefined, inlineLevel);
950
+ const requirementsInline = await inlineRequirementsFromDb(base, mid, undefined, inlineLevel);
827
951
  if (requirementsInline)
828
952
  inlined.push(requirementsInline);
829
953
  const decisionsInline = await inlineDecisionsFromDb(base, mid, undefined, inlineLevel);
@@ -884,7 +1008,15 @@ export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base
884
1008
  const sliceContextPath = resolveSliceFile(base, mid, sid, "CONTEXT");
885
1009
  const sliceContextRel = relSliceFile(base, mid, sid, "CONTEXT");
886
1010
  const inlined = [];
887
- inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
1011
+ // Use roadmap excerpt instead of full roadmap for context reduction
1012
+ const roadmapExcerptRS = await inlineRoadmapExcerpt(base, mid, sid);
1013
+ if (roadmapExcerptRS) {
1014
+ inlined.push(roadmapExcerptRS);
1015
+ }
1016
+ else {
1017
+ // Fall back to full roadmap if excerpt fails
1018
+ inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
1019
+ }
888
1020
  const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
889
1021
  if (contextInline)
890
1022
  inlined.push(contextInline);
@@ -894,13 +1026,17 @@ export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base
894
1026
  const researchInline = await inlineFileOptional(milestoneResearchPath, milestoneResearchRel, "Milestone Research");
895
1027
  if (researchInline)
896
1028
  inlined.push(researchInline);
897
- const decisionsInline = await inlineDecisionsFromDb(base, mid);
1029
+ // Derive scope from slice title for decision filtering (R005)
1030
+ const derivedScope = deriveSliceScope(sTitle);
1031
+ const decisionsInline = await inlineDecisionsFromDb(base, mid, derivedScope);
898
1032
  if (decisionsInline)
899
1033
  inlined.push(decisionsInline);
900
- const requirementsInline = await inlineRequirementsFromDb(base, sid);
1034
+ const requirementsInline = await inlineRequirementsFromDb(base, mid, sid);
901
1035
  if (requirementsInline)
902
1036
  inlined.push(requirementsInline);
903
- const knowledgeInlineRS = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
1037
+ // Use scoped knowledge based on slice title keywords
1038
+ const keywords = extractKeywords(sTitle);
1039
+ const knowledgeInlineRS = await inlineKnowledgeScoped(base, keywords);
904
1040
  if (knowledgeInlineRS)
905
1041
  inlined.push(knowledgeInlineRS);
906
1042
  inlined.push(inlineTemplate("research", "Research"));
@@ -944,7 +1080,15 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
944
1080
  const researchSliceAnchor = readPhaseAnchor(base, mid, "research-slice");
945
1081
  if (researchSliceAnchor)
946
1082
  inlined.push(formatAnchorForPrompt(researchSliceAnchor));
947
- inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
1083
+ // Use roadmap excerpt instead of full roadmap for context reduction
1084
+ const roadmapExcerptPS = await inlineRoadmapExcerpt(base, mid, sid);
1085
+ if (roadmapExcerptPS) {
1086
+ inlined.push(roadmapExcerptPS);
1087
+ }
1088
+ else {
1089
+ // Fall back to full roadmap if excerpt fails
1090
+ inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
1091
+ }
948
1092
  const sliceCtxInline = await inlineFileOptional(sliceContextPath, sliceContextRel, "Slice Context (from discussion)");
949
1093
  if (sliceCtxInline)
950
1094
  inlined.push(sliceCtxInline);
@@ -952,14 +1096,18 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
952
1096
  if (researchInline)
953
1097
  inlined.push(researchInline);
954
1098
  if (inlineLevel !== "minimal") {
955
- const decisionsInline = await inlineDecisionsFromDb(base, mid, undefined, inlineLevel);
1099
+ // Derive scope from slice title for decision filtering (R005)
1100
+ const derivedScopePS = deriveSliceScope(sTitle);
1101
+ const decisionsInline = await inlineDecisionsFromDb(base, mid, derivedScopePS, inlineLevel);
956
1102
  if (decisionsInline)
957
1103
  inlined.push(decisionsInline);
958
- const requirementsInline = await inlineRequirementsFromDb(base, sid, inlineLevel);
1104
+ const requirementsInline = await inlineRequirementsFromDb(base, mid, sid, inlineLevel);
959
1105
  if (requirementsInline)
960
1106
  inlined.push(requirementsInline);
961
1107
  }
962
- const knowledgeInlinePS = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
1108
+ // Use scoped knowledge based on slice title keywords
1109
+ const keywordsPS = extractKeywords(sTitle);
1110
+ const knowledgeInlinePS = await inlineKnowledgeScoped(base, keywordsPS);
963
1111
  if (knowledgeInlinePS)
964
1112
  inlined.push(knowledgeInlinePS);
965
1113
  inlined.push(inlineTemplate("plan", "Slice Plan"));
@@ -1117,7 +1265,7 @@ export async function buildCompleteSlicePrompt(mid, _midTitle, sid, sTitle, base
1117
1265
  inlined.push(sliceCtxInline);
1118
1266
  inlined.push(await inlineFile(slicePlanPath, slicePlanRel, "Slice Plan"));
1119
1267
  if (inlineLevel !== "minimal") {
1120
- const requirementsInline = await inlineRequirementsFromDb(base, sid, inlineLevel);
1268
+ const requirementsInline = await inlineRequirementsFromDb(base, mid, sid, inlineLevel);
1121
1269
  if (requirementsInline)
1122
1270
  inlined.push(requirementsInline);
1123
1271
  }
@@ -1195,7 +1343,7 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
1195
1343
  }
1196
1344
  // Inline root GSD files (skip for minimal — completion can read these if needed)
1197
1345
  if (inlineLevel !== "minimal") {
1198
- const requirementsInline = await inlineRequirementsFromDb(base, undefined, inlineLevel);
1346
+ const requirementsInline = await inlineRequirementsFromDb(base, mid, undefined, inlineLevel);
1199
1347
  if (requirementsInline)
1200
1348
  inlined.push(requirementsInline);
1201
1349
  const decisionsInline = await inlineDecisionsFromDb(base, mid, undefined, inlineLevel);
@@ -1324,7 +1472,7 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) {
1324
1472
  }
1325
1473
  // Inline root GSD files
1326
1474
  if (inlineLevel !== "minimal") {
1327
- const requirementsInline = await inlineRequirementsFromDb(base, undefined, inlineLevel);
1475
+ const requirementsInline = await inlineRequirementsFromDb(base, mid, undefined, inlineLevel);
1328
1476
  if (requirementsInline)
1329
1477
  inlined.push(requirementsInline);
1330
1478
  const decisionsInline = await inlineDecisionsFromDb(base, mid, undefined, inlineLevel);
@@ -1487,7 +1635,7 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
1487
1635
  const projectInline = await inlineProjectFromDb(base);
1488
1636
  if (projectInline)
1489
1637
  inlined.push(projectInline);
1490
- const requirementsInline = await inlineRequirementsFromDb(base, undefined, inlineLevel);
1638
+ const requirementsInline = await inlineRequirementsFromDb(base, mid, undefined, inlineLevel);
1491
1639
  if (requirementsInline)
1492
1640
  inlined.push(requirementsInline);
1493
1641
  const decisionsInline = await inlineDecisionsFromDb(base, mid, undefined, inlineLevel);
@@ -906,7 +906,19 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
906
906
  s.active = true;
907
907
  s.verbose = verboseMode;
908
908
  s.stepMode = requestedStepMode;
909
- s.cmdCtx = ctx;
909
+ // Preserve the original cmdCtx (ExtensionCommandContext with newSession)
910
+ // when resuming from a provider-error pause. The resume callback receives
911
+ // an ExtensionContext (from the agent_end hook) which lacks newSession —
912
+ // using it would crash runUnit with "newSession is not a function".
913
+ // Only override if the new ctx actually has newSession (user-initiated resume).
914
+ if ("newSession" in ctx && typeof ctx.newSession === "function") {
915
+ s.cmdCtx = ctx;
916
+ }
917
+ else if (!s.cmdCtx) {
918
+ // No saved cmdCtx — this shouldn't happen, but handle gracefully
919
+ s.cmdCtx = ctx;
920
+ }
921
+ // else: keep existing s.cmdCtx which has the real newSession
910
922
  s.basePath = base;
911
923
  setLogBasePath(base);
912
924
  if (!s.autoStartTime || s.autoStartTime <= 0)