gsd-pi 2.66.0 → 2.66.1-dev.9a14d3d
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.
- package/dist/claude-cli-check.d.ts +8 -0
- package/dist/claude-cli-check.js +36 -0
- package/dist/cli.js +40 -0
- package/dist/onboarding.js +19 -2
- package/dist/resources/extensions/claude-code-cli/readiness.js +63 -12
- package/dist/resources/extensions/gsd/auto/phases.js +15 -2
- package/dist/resources/extensions/gsd/auto-model-selection.js +1 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +167 -19
- package/dist/resources/extensions/gsd/auto.js +13 -1
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +32 -1
- package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +5 -0
- package/dist/resources/extensions/gsd/context-store.js +134 -2
- package/dist/resources/extensions/gsd/preferences.js +6 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +1 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +57 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js +39 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +4 -0
- package/packages/pi-coding-agent/src/core/retry-handler.test.ts +47 -0
- package/packages/pi-coding-agent/src/core/retry-handler.ts +65 -1
- package/packages/pi-coding-agent/src/core/sdk.ts +5 -0
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +1 -3
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +1 -3
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/readiness.ts +67 -12
- package/src/resources/extensions/gsd/auto/phases.ts +20 -2
- package/src/resources/extensions/gsd/auto-model-selection.ts +1 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +190 -19
- package/src/resources/extensions/gsd/auto.ts +12 -1
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +34 -1
- package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +6 -0
- package/src/resources/extensions/gsd/context-store.ts +167 -2
- package/src/resources/extensions/gsd/preferences.ts +6 -1
- package/src/resources/extensions/gsd/tests/context-store.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/decision-scope-cascade.test.ts +370 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/measurement.test.ts +531 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +60 -0
- package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- /package/dist/web/standalone/.next/static/{Bdk1mnQugYZh7ZxuXUYvc → UR2XjRqocvGBpbjt8dHCS}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{Bdk1mnQugYZh7ZxuXUYvc → UR2XjRqocvGBpbjt8dHCS}/_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.
|
package/dist/onboarding.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
5
|
-
*
|
|
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 {
|
|
9
|
-
let
|
|
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
|
-
|
|
16
|
+
function refreshCache() {
|
|
13
17
|
const now = Date.now();
|
|
14
|
-
if (
|
|
15
|
-
return
|
|
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
|
-
|
|
19
|
-
|
|
25
|
+
execFileSync("claude", ["--version"], { timeout: 5_000, stdio: "pipe" });
|
|
26
|
+
cachedBinaryPresent = true;
|
|
20
27
|
}
|
|
21
28
|
catch {
|
|
22
|
-
|
|
29
|
+
cachedBinaryPresent = false;
|
|
30
|
+
cachedAuthed = false;
|
|
31
|
+
return;
|
|
23
32
|
}
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
951
|
-
|
|
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
|
}
|
|
@@ -274,7 +274,7 @@ export function resolveModelId(modelId, availableModels, currentProvider) {
|
|
|
274
274
|
* Uses case-insensitive matching with alias support to prevent fail-open on
|
|
275
275
|
* provider naming variations (e.g. "copilot" vs "github-copilot").
|
|
276
276
|
*/
|
|
277
|
-
const FLAT_RATE_PROVIDERS = new Set(["github-copilot", "copilot"]);
|
|
277
|
+
const FLAT_RATE_PROVIDERS = new Set(["github-copilot", "copilot", "claude-code"]);
|
|
278
278
|
export function isFlatRateProvider(provider) {
|
|
279
279
|
return FLAT_RATE_PROVIDERS.has(provider.toLowerCase());
|
|
280
280
|
}
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|