gsd-pi 2.70.0 → 2.70.1-dev.3591dcf
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/loader.js +4 -0
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +150 -2
- package/dist/resources/extensions/gsd/auto-model-selection.js +33 -19
- package/dist/resources/extensions/gsd/auto-prompts.js +7 -3
- package/dist/resources/extensions/gsd/auto-start.js +25 -1
- package/dist/resources/extensions/gsd/auto.js +12 -8
- package/dist/resources/extensions/gsd/commands-handlers.js +22 -8
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +12 -0
- package/dist/resources/extensions/gsd/doctor-format.js +2 -0
- package/dist/resources/extensions/gsd/guided-flow.js +21 -10
- package/dist/resources/extensions/gsd/pre-execution-checks.js +5 -3
- package/dist/resources/extensions/gsd/validate-directory.js +30 -12
- package/dist/resources/extensions/gsd/workflow-mcp.js +11 -0
- package/dist/resources/extensions/slash-commands/audit.js +2 -1
- package/dist/resources/extensions/subagent/isolation.js +4 -2
- package/dist/update-check.d.ts +1 -0
- package/dist/update-check.js +30 -27
- package/dist/update-cmd.js +3 -11
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
- 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 +4 -4
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +2 -2
- 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 +11 -11
- package/dist/web/standalone/.next/server/chunks/63.js +3 -3
- package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
- 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 +1 -1
- 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-f1e30ab6bb269149.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/dist/web-mode.js +4 -0
- package/package.json +11 -11
- package/packages/mcp-server/dist/workflow-tools.d.ts +2 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +35 -3
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/import-candidates.test.ts +48 -0
- package/packages/mcp-server/src/workflow-tools.ts +34 -1
- package/packages/pi-agent-core/dist/agent.d.ts +8 -0
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +3 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/src/agent.test.ts +82 -0
- package/packages/pi-agent-core/src/agent.ts +12 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/config.js +38 -15
- package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +10 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +3 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/lsp/config.ts +43 -17
- package/packages/pi-coding-agent/src/core/sdk.ts +8 -0
- package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +7 -5
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +227 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +172 -0
- package/src/resources/extensions/gsd/auto-model-selection.ts +39 -25
- package/src/resources/extensions/gsd/auto-prompts.ts +7 -3
- package/src/resources/extensions/gsd/auto-start.ts +34 -1
- package/src/resources/extensions/gsd/auto.ts +12 -8
- package/src/resources/extensions/gsd/commands-handlers.ts +22 -7
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +14 -0
- package/src/resources/extensions/gsd/doctor-format.ts +1 -0
- package/src/resources/extensions/gsd/doctor-types.ts +1 -0
- package/src/resources/extensions/gsd/guided-flow.ts +24 -8
- package/src/resources/extensions/gsd/pre-execution-checks.ts +6 -3
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +207 -0
- package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +48 -1
- package/src/resources/extensions/gsd/tests/resource-loader-import-path.test.ts +8 -7
- package/src/resources/extensions/gsd/tests/validate-directory.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +87 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +25 -0
- package/src/resources/extensions/gsd/validate-directory.ts +33 -11
- package/src/resources/extensions/gsd/workflow-mcp.ts +15 -0
- package/src/resources/extensions/slash-commands/audit.ts +2 -1
- package/src/resources/extensions/subagent/isolation.ts +4 -3
- package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.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/{Nl6lg7zP5dNgNBV1107v1 → KdlODhIktLmeRKpLpHdKb}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{Nl6lg7zP5dNgNBV1107v1 → KdlODhIktLmeRKpLpHdKb}/_ssgManifest.js +0 -0
|
@@ -25,6 +25,26 @@ import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
|
25
25
|
import { projectRoot } from "./commands/context.js";
|
|
26
26
|
import { loadPrompt } from "./prompt-loader.js";
|
|
27
27
|
|
|
28
|
+
const UPDATE_REGISTRY_URL = "https://registry.npmjs.org/gsd-pi/latest";
|
|
29
|
+
const UPDATE_FETCH_TIMEOUT_MS = 5000;
|
|
30
|
+
|
|
31
|
+
async function fetchLatestVersionForCommand(): Promise<string | null> {
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const timeout = setTimeout(() => controller.abort(), UPDATE_FETCH_TIMEOUT_MS);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const res = await fetch(UPDATE_REGISTRY_URL, { signal: controller.signal });
|
|
37
|
+
if (!res.ok) return null;
|
|
38
|
+
const data = (await res.json()) as { version?: string };
|
|
39
|
+
const latest = typeof data.version === "string" ? data.version.trim().replace(/^v/, "") : "";
|
|
40
|
+
return latest.length > 0 ? latest : null;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
} finally {
|
|
44
|
+
clearTimeout(timeout);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
28
48
|
export function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportText: string, structuredIssues: string): void {
|
|
29
49
|
const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".gsd", "agent", "GSD-WORKFLOW.md");
|
|
30
50
|
const workflow = readFileSync(workflowPath, "utf-8");
|
|
@@ -394,13 +414,8 @@ export async function handleUpdate(ctx: ExtensionCommandContext): Promise<void>
|
|
|
394
414
|
|
|
395
415
|
ctx.ui.notify(`Current version: v${current}\nChecking npm registry...`, "info");
|
|
396
416
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
latest = execSync(`npm view ${NPM_PACKAGE} version`, {
|
|
400
|
-
encoding: "utf-8",
|
|
401
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
402
|
-
}).trim();
|
|
403
|
-
} catch {
|
|
417
|
+
const latest = await fetchLatestVersionForCommand();
|
|
418
|
+
if (!latest) {
|
|
404
419
|
ctx.ui.notify("Failed to reach npm registry. Check your network connection.", "error");
|
|
405
420
|
return;
|
|
406
421
|
}
|
|
@@ -13,6 +13,20 @@ export async function checkEngineHealth(
|
|
|
13
13
|
issues: DoctorIssue[],
|
|
14
14
|
fixesApplied: string[],
|
|
15
15
|
): Promise<void> {
|
|
16
|
+
const dbPath = join(basePath, ".gsd", "gsd.db");
|
|
17
|
+
|
|
18
|
+
if (!isDbAvailable() && existsSync(dbPath)) {
|
|
19
|
+
issues.push({
|
|
20
|
+
severity: "warning",
|
|
21
|
+
code: "db_unavailable",
|
|
22
|
+
scope: "project",
|
|
23
|
+
unitId: "project",
|
|
24
|
+
message: "Database unavailable — using filesystem state derivation (degraded mode). State queries may be slower and less reliable.",
|
|
25
|
+
file: ".gsd/gsd.db",
|
|
26
|
+
fixable: false,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
// ── DB constraint violation detection (full doctor only, not pre-dispatch per D-10) ──
|
|
17
31
|
try {
|
|
18
32
|
if (isDbAvailable()) {
|
|
@@ -2,6 +2,7 @@ import type { DoctorIssue, DoctorIssueCode, DoctorReport, DoctorSummary } from "
|
|
|
2
2
|
|
|
3
3
|
function matchesScope(unitId: string, scope?: string): boolean {
|
|
4
4
|
if (!scope) return true;
|
|
5
|
+
if (unitId === "project" || unitId === "environment") return true;
|
|
5
6
|
return unitId === scope || unitId.startsWith(`${scope}/`) || unitId.startsWith(`${scope}`);
|
|
6
7
|
}
|
|
7
8
|
|
|
@@ -48,6 +48,7 @@ import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
|
|
|
48
48
|
import {
|
|
49
49
|
getWorkflowTransportSupportError,
|
|
50
50
|
getRequiredWorkflowToolsForGuidedUnit,
|
|
51
|
+
supportsStructuredQuestions,
|
|
51
52
|
} from "./workflow-mcp.js";
|
|
52
53
|
import {
|
|
53
54
|
runPreparation,
|
|
@@ -294,6 +295,7 @@ async function dispatchWorkflow(
|
|
|
294
295
|
const result = await selectAndApplyModel(
|
|
295
296
|
ctx, pi, unitType, /* unitId */ "", /* basePath */ process.cwd(),
|
|
296
297
|
prefs, /* verbose */ false, /* autoModeStartModel */ null,
|
|
298
|
+
/* retryContext */ undefined, /* isAutoMode */ false,
|
|
297
299
|
);
|
|
298
300
|
if (result.appliedModel) {
|
|
299
301
|
debugLog("guided-flow-model-applied", {
|
|
@@ -367,6 +369,20 @@ async function dispatchWorkflow(
|
|
|
367
369
|
}
|
|
368
370
|
}
|
|
369
371
|
|
|
372
|
+
function getStructuredQuestionsAvailability(
|
|
373
|
+
pi: ExtensionAPI,
|
|
374
|
+
ctx: ExtensionContext | undefined,
|
|
375
|
+
): "true" | "false" {
|
|
376
|
+
if (!ctx) return "false";
|
|
377
|
+
|
|
378
|
+
const provider = ctx.model?.provider;
|
|
379
|
+
const authMode = provider ? ctx.modelRegistry.getProviderAuthMode(provider) : undefined;
|
|
380
|
+
return supportsStructuredQuestions(pi.getActiveTools(), {
|
|
381
|
+
authMode,
|
|
382
|
+
baseUrl: ctx.model?.baseUrl,
|
|
383
|
+
}) ? "true" : "false";
|
|
384
|
+
}
|
|
385
|
+
|
|
370
386
|
/**
|
|
371
387
|
* Resolve a model ID string to a model object from available models.
|
|
372
388
|
* Handles "provider/model" and bare ID formats.
|
|
@@ -739,7 +755,7 @@ export async function showDiscuss(
|
|
|
739
755
|
|
|
740
756
|
if (choice === "discuss_draft") {
|
|
741
757
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
742
|
-
const structuredQuestionsAvailable = pi
|
|
758
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
743
759
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
744
760
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
745
761
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
|
|
@@ -752,7 +768,7 @@ export async function showDiscuss(
|
|
|
752
768
|
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
|
|
753
769
|
} else if (choice === "discuss_fresh") {
|
|
754
770
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
755
|
-
const structuredQuestionsAvailable = pi
|
|
771
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
756
772
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
|
|
757
773
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
758
774
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
@@ -910,7 +926,7 @@ export async function showDiscuss(
|
|
|
910
926
|
if (confirm !== "rediscuss") continue;
|
|
911
927
|
}
|
|
912
928
|
|
|
913
|
-
const sqAvail = pi
|
|
929
|
+
const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
|
|
914
930
|
const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss, structuredQuestionsAvailable: sqAvail });
|
|
915
931
|
await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice");
|
|
916
932
|
|
|
@@ -1020,7 +1036,7 @@ async function dispatchDiscussForMilestone(
|
|
|
1020
1036
|
].join("\n")
|
|
1021
1037
|
: "";
|
|
1022
1038
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1023
|
-
const structuredQuestionsAvailable = pi
|
|
1039
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1024
1040
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
1025
1041
|
milestoneId: mid,
|
|
1026
1042
|
milestoneTitle,
|
|
@@ -1461,7 +1477,7 @@ export async function showSmartEntry(
|
|
|
1461
1477
|
|
|
1462
1478
|
if (choice === "discuss_draft") {
|
|
1463
1479
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1464
|
-
const structuredQuestionsAvailable = pi
|
|
1480
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1465
1481
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
1466
1482
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1467
1483
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
@@ -1474,7 +1490,7 @@ export async function showSmartEntry(
|
|
|
1474
1490
|
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
|
|
1475
1491
|
} else if (choice === "discuss_fresh") {
|
|
1476
1492
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1477
|
-
const structuredQuestionsAvailable = pi
|
|
1493
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1478
1494
|
pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
|
|
1479
1495
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1480
1496
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
@@ -1572,7 +1588,7 @@ export async function showSmartEntry(
|
|
|
1572
1588
|
}), "gsd-run", ctx, "plan-milestone");
|
|
1573
1589
|
} else if (choice === "discuss") {
|
|
1574
1590
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1575
|
-
const structuredQuestionsAvailable = pi
|
|
1591
|
+
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1576
1592
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1577
1593
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1578
1594
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
@@ -1712,7 +1728,7 @@ export async function showSmartEntry(
|
|
|
1712
1728
|
}),
|
|
1713
1729
|
}), "gsd-run", ctx, "plan-slice");
|
|
1714
1730
|
} else if (choice === "discuss") {
|
|
1715
|
-
const sqAvail = pi
|
|
1731
|
+
const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
|
|
1716
1732
|
await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext, structuredQuestionsAvailable: sqAvail }), "gsd-run", ctx, "discuss-slice");
|
|
1717
1733
|
} else if (choice === "research") {
|
|
1718
1734
|
const researchTemplates = inlineTemplate("research", "Research");
|
|
@@ -20,6 +20,8 @@ import { resolve } from "node:path";
|
|
|
20
20
|
import type { TaskRow } from "./gsd-db.ts";
|
|
21
21
|
import type { PreExecutionCheckJSON } from "./verification-evidence.ts";
|
|
22
22
|
|
|
23
|
+
const NPM_COMMAND = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
24
|
+
|
|
23
25
|
// ─── Result Types ────────────────────────────────────────────────────────────
|
|
24
26
|
|
|
25
27
|
export interface PreExecutionResult {
|
|
@@ -126,9 +128,10 @@ async function checkPackageOnNpm(
|
|
|
126
128
|
timeoutMs = 5000
|
|
127
129
|
): Promise<{ exists: boolean; error?: string }> {
|
|
128
130
|
return new Promise((resolve) => {
|
|
129
|
-
const child = spawn(
|
|
131
|
+
const child = spawn(NPM_COMMAND, ["view", packageName, "name"], {
|
|
130
132
|
stdio: ["ignore", "pipe", "pipe"],
|
|
131
133
|
timeout: timeoutMs,
|
|
134
|
+
shell: process.platform === "win32",
|
|
132
135
|
});
|
|
133
136
|
|
|
134
137
|
let stdout = "";
|
|
@@ -263,9 +266,9 @@ function extractPathFromAnnotation(raw: string): string {
|
|
|
263
266
|
const trimmed = raw.trim();
|
|
264
267
|
if (!trimmed) return trimmed;
|
|
265
268
|
|
|
266
|
-
const backtickMatch = trimmed.match(
|
|
269
|
+
const backtickMatch = trimmed.match(/^(`+)([^`]+)\1(?:(?:\s+[—–-]\s+.+)|(?:\s+\([^()]+\)))?$/);
|
|
267
270
|
if (backtickMatch) {
|
|
268
|
-
return backtickMatch[
|
|
271
|
+
return backtickMatch[2].trim();
|
|
269
272
|
}
|
|
270
273
|
|
|
271
274
|
const annotatedMatch = trimmed.match(/^(.+?)\s+[—–-]\s+.+$/);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { afterEach, test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { closeDatabase } from "../gsd-db.ts";
|
|
4
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { filterDoctorIssues } from "../doctor-format.ts";
|
|
8
|
+
import { checkEngineHealth } from "../doctor-engine-checks.ts";
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
closeDatabase();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("filterDoctorIssues keeps project and environment issues in scoped reports", () => {
|
|
15
|
+
const issues = [
|
|
16
|
+
{ severity: "error", code: "env_dependencies", scope: "project", unitId: "environment", message: "node_modules missing", fixable: false },
|
|
17
|
+
{ severity: "warning", code: "db_unavailable", scope: "project", unitId: "project", message: "DB unavailable", fixable: false },
|
|
18
|
+
{ severity: "warning", code: "state_file_missing", scope: "slice", unitId: "M016/S01", message: "slice warning", fixable: false },
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
const filtered = filterDoctorIssues([...issues], { scope: "M016", includeWarnings: true });
|
|
22
|
+
assert.deepEqual(
|
|
23
|
+
filtered.map((issue) => issue.unitId),
|
|
24
|
+
["environment", "project", "M016/S01"],
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("checkEngineHealth reports db_unavailable when gsd.db exists but the DB is closed", async (t) => {
|
|
29
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-db-unavailable-"));
|
|
30
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
31
|
+
|
|
32
|
+
const gsdDir = join(base, ".gsd");
|
|
33
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
34
|
+
writeFileSync(join(gsdDir, "gsd.db"), "");
|
|
35
|
+
|
|
36
|
+
const issues: any[] = [];
|
|
37
|
+
await checkEngineHealth(base, issues, []);
|
|
38
|
+
|
|
39
|
+
const dbIssue = issues.find((issue) => issue.code === "db_unavailable");
|
|
40
|
+
assert.ok(dbIssue, "doctor should surface degraded DB mode when a DB file exists");
|
|
41
|
+
assert.equal(dbIssue.unitId, "project");
|
|
42
|
+
assert.equal(dbIssue.file, ".gsd/gsd.db");
|
|
43
|
+
});
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// GSD Extension — Interactive Routing Bypass Tests
|
|
2
|
+
// Verifies that dynamic routing is skipped for interactive (guided-flow) dispatches
|
|
3
|
+
// and that model downgrade notifications always fire (#3962).
|
|
4
|
+
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
5
|
+
|
|
6
|
+
import test, { describe } from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { join, dirname } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
// ─── Source-level structural tests ──────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
const modelSelectionSrc = readFileSync(
|
|
17
|
+
join(__dirname, "..", "auto-model-selection.ts"),
|
|
18
|
+
"utf-8",
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const guidedFlowSrc = readFileSync(
|
|
22
|
+
join(__dirname, "..", "guided-flow.ts"),
|
|
23
|
+
"utf-8",
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const autoStartSrc = readFileSync(
|
|
27
|
+
join(__dirname, "..", "auto-start.ts"),
|
|
28
|
+
"utf-8",
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
describe("interactive routing bypass (#3962)", () => {
|
|
32
|
+
test("selectAndApplyModel accepts isAutoMode parameter", () => {
|
|
33
|
+
// The function signature should include isAutoMode with a default of true
|
|
34
|
+
assert.ok(
|
|
35
|
+
modelSelectionSrc.includes("isAutoMode"),
|
|
36
|
+
"selectAndApplyModel should have isAutoMode parameter",
|
|
37
|
+
);
|
|
38
|
+
assert.ok(
|
|
39
|
+
modelSelectionSrc.includes("isAutoMode = true"),
|
|
40
|
+
"isAutoMode should default to true (auto-mode behavior preserved)",
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("routing is disabled when isAutoMode is false", () => {
|
|
45
|
+
// The code should disable routing when not in auto-mode
|
|
46
|
+
assert.ok(
|
|
47
|
+
modelSelectionSrc.includes("if (!isAutoMode)"),
|
|
48
|
+
"should check isAutoMode flag to disable routing",
|
|
49
|
+
);
|
|
50
|
+
assert.ok(
|
|
51
|
+
modelSelectionSrc.includes("routingConfig.enabled = false"),
|
|
52
|
+
"should set routingConfig.enabled = false for interactive mode",
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("resolvePreferredModelConfig skips routing synthesis when isAutoMode is false", () => {
|
|
57
|
+
// resolvePreferredModelConfig should accept isAutoMode and bail early
|
|
58
|
+
// before synthesizing a routing ceiling from tier_models (#3962 codex review)
|
|
59
|
+
assert.ok(
|
|
60
|
+
modelSelectionSrc.includes("function resolvePreferredModelConfig"),
|
|
61
|
+
"resolvePreferredModelConfig should exist",
|
|
62
|
+
);
|
|
63
|
+
// The function should check isAutoMode before routing synthesis
|
|
64
|
+
const fnIdx = modelSelectionSrc.indexOf("function resolvePreferredModelConfig");
|
|
65
|
+
const fnBody = modelSelectionSrc.slice(fnIdx, fnIdx + 600);
|
|
66
|
+
assert.ok(
|
|
67
|
+
fnBody.includes("isAutoMode"),
|
|
68
|
+
"resolvePreferredModelConfig should accept isAutoMode parameter",
|
|
69
|
+
);
|
|
70
|
+
assert.ok(
|
|
71
|
+
fnBody.includes("if (!isAutoMode) return undefined"),
|
|
72
|
+
"should return undefined (skip routing synthesis) when not in auto-mode",
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("selectAndApplyModel threads isAutoMode to resolvePreferredModelConfig", () => {
|
|
77
|
+
// The call to resolvePreferredModelConfig inside selectAndApplyModel
|
|
78
|
+
// should pass isAutoMode as the third argument
|
|
79
|
+
const callSite = "resolvePreferredModelConfig(unitType, autoModeStartModel, isAutoMode)";
|
|
80
|
+
assert.ok(
|
|
81
|
+
modelSelectionSrc.includes(callSite),
|
|
82
|
+
"selectAndApplyModel should pass isAutoMode to resolvePreferredModelConfig",
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("guided-flow passes isAutoMode=false", () => {
|
|
87
|
+
// guided-flow.ts should explicitly pass isAutoMode as false
|
|
88
|
+
assert.ok(
|
|
89
|
+
guidedFlowSrc.includes("/* isAutoMode */ false"),
|
|
90
|
+
"guided-flow should pass isAutoMode=false to selectAndApplyModel",
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("auto/phases.ts does NOT pass isAutoMode=false", () => {
|
|
95
|
+
// auto/phases.ts should use the default (true) — it's auto-mode
|
|
96
|
+
const phasesSrc = readFileSync(
|
|
97
|
+
join(__dirname, "..", "auto", "phases.ts"),
|
|
98
|
+
"utf-8",
|
|
99
|
+
);
|
|
100
|
+
assert.ok(
|
|
101
|
+
!phasesSrc.includes("isAutoMode"),
|
|
102
|
+
"auto/phases.ts should use default isAutoMode=true (not pass it explicitly)",
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("model downgrade notifications always visible (#3962)", () => {
|
|
108
|
+
test("downgrade notification is not gated by verbose flag", () => {
|
|
109
|
+
// The downgrade notification block should NOT be wrapped in `if (verbose)`
|
|
110
|
+
// Find the downgrade block and verify it's not behind a verbose check
|
|
111
|
+
const downgradeBlock = "if (routingResult.wasDowngraded)";
|
|
112
|
+
const downgradeIdx = modelSelectionSrc.indexOf(downgradeBlock);
|
|
113
|
+
assert.ok(downgradeIdx > 0, "downgrade block should exist");
|
|
114
|
+
|
|
115
|
+
// Extract the code between wasDowngraded check and the next routing label assignment
|
|
116
|
+
const afterDowngrade = modelSelectionSrc.slice(
|
|
117
|
+
downgradeIdx,
|
|
118
|
+
modelSelectionSrc.indexOf("routingTierLabel =", downgradeIdx),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// The notification calls should NOT be wrapped in `if (verbose)`
|
|
122
|
+
assert.ok(
|
|
123
|
+
!afterDowngrade.includes("if (verbose)"),
|
|
124
|
+
"downgrade notifications should not be gated by verbose flag",
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// But the notification calls should exist
|
|
128
|
+
assert.ok(
|
|
129
|
+
afterDowngrade.includes('ctx.ui.notify('),
|
|
130
|
+
"downgrade notifications should still fire",
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("tier escalation notification is not gated by verbose flag", () => {
|
|
135
|
+
// Extract the escalation block: from "if (escalated)" to its closing
|
|
136
|
+
// and verify the notification is present but `if (verbose)` is not.
|
|
137
|
+
const escalatedIdx = modelSelectionSrc.indexOf("if (escalated)");
|
|
138
|
+
assert.ok(escalatedIdx > 0, "escalation block should exist");
|
|
139
|
+
|
|
140
|
+
// Get the block from "if (escalated)" to the next closing brace pattern
|
|
141
|
+
const block = modelSelectionSrc.slice(escalatedIdx, escalatedIdx + 400);
|
|
142
|
+
assert.ok(
|
|
143
|
+
block.includes("Tier escalation:"),
|
|
144
|
+
"escalation block should contain the notification",
|
|
145
|
+
);
|
|
146
|
+
assert.ok(
|
|
147
|
+
!block.includes("if (verbose)"),
|
|
148
|
+
"escalation block should not gate notification behind verbose flag",
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("auto-mode start routing banner (#3962)", () => {
|
|
154
|
+
test("auto-start shows dynamic routing status on startup", () => {
|
|
155
|
+
assert.ok(
|
|
156
|
+
autoStartSrc.includes("Dynamic routing:"),
|
|
157
|
+
"auto-start should display routing status banner",
|
|
158
|
+
);
|
|
159
|
+
assert.ok(
|
|
160
|
+
autoStartSrc.includes("resolveDynamicRoutingConfig"),
|
|
161
|
+
"auto-start should import resolveDynamicRoutingConfig",
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("banner shows different messages for enabled vs disabled routing", () => {
|
|
166
|
+
assert.ok(
|
|
167
|
+
autoStartSrc.includes("Dynamic routing: enabled"),
|
|
168
|
+
"should show message when routing is enabled",
|
|
169
|
+
);
|
|
170
|
+
assert.ok(
|
|
171
|
+
autoStartSrc.includes("Dynamic routing: disabled"),
|
|
172
|
+
"should show message when routing is disabled",
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("banner shows the ceiling model", () => {
|
|
177
|
+
assert.ok(
|
|
178
|
+
autoStartSrc.includes("startModelLabel"),
|
|
179
|
+
"banner should reference the start/ceiling model",
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("banner accounts for flat-rate provider suppression", () => {
|
|
184
|
+
// The banner should check isFlatRateProvider to accurately reflect
|
|
185
|
+
// whether routing will actually be active at dispatch time (#3962 codex review)
|
|
186
|
+
assert.ok(
|
|
187
|
+
autoStartSrc.includes("isFlatRateProvider"),
|
|
188
|
+
"banner should check flat-rate provider status",
|
|
189
|
+
);
|
|
190
|
+
assert.ok(
|
|
191
|
+
autoStartSrc.includes("effectivelyEnabled"),
|
|
192
|
+
"banner should compute effective routing state, not just raw config",
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("banner uses effective ceiling from tier_models.heavy when configured", () => {
|
|
197
|
+
// The actual ceiling may come from tier_models.heavy, not the start model
|
|
198
|
+
assert.ok(
|
|
199
|
+
autoStartSrc.includes("tier_models?.heavy"),
|
|
200
|
+
"banner should check tier_models.heavy for the effective ceiling",
|
|
201
|
+
);
|
|
202
|
+
assert.ok(
|
|
203
|
+
autoStartSrc.includes("effectiveCeiling"),
|
|
204
|
+
"banner should compute the effective ceiling model",
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { describe, it } from 'node:test'
|
|
13
13
|
import assert from 'node:assert/strict'
|
|
14
14
|
import { normalizeFilePath, checkFilePathConsistency } from '../pre-execution-checks.ts'
|
|
15
|
-
import { readFileSync } from 'node:fs'
|
|
15
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
16
16
|
import { resolve } from 'node:path'
|
|
17
17
|
|
|
18
18
|
const src = readFileSync(
|
|
@@ -25,6 +25,11 @@ describe('normalizeFilePath backtick stripping (#3649)', () => {
|
|
|
25
25
|
assert.equal(normalizeFilePath('`src/foo.ts`'), 'src/foo.ts')
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
+
it('strips doubled backticks and trailing notes from file paths', () => {
|
|
29
|
+
assert.equal(normalizeFilePath('``src/foo.ts`` - current state'), 'src/foo.ts')
|
|
30
|
+
assert.equal(normalizeFilePath('``src/foo.ts`` (current state)'), 'src/foo.ts')
|
|
31
|
+
})
|
|
32
|
+
|
|
28
33
|
it('strips backticks even when mixed with other normalization', () => {
|
|
29
34
|
assert.equal(normalizeFilePath('`./src//bar.ts`'), 'src/bar.ts')
|
|
30
35
|
})
|
|
@@ -66,3 +71,45 @@ describe('checkFilePathConsistency checks task.inputs not task.files (#3626)', (
|
|
|
66
71
|
)
|
|
67
72
|
})
|
|
68
73
|
})
|
|
74
|
+
|
|
75
|
+
describe('checkFilePathConsistency handles doubled-backtick annotations (#3892)', () => {
|
|
76
|
+
it('accepts existing files when task.inputs include doubled-backtick notes', () => {
|
|
77
|
+
const task = {
|
|
78
|
+
milestone_id: 'M001',
|
|
79
|
+
slice_id: 'S01',
|
|
80
|
+
id: 'T01',
|
|
81
|
+
title: 'Test Task',
|
|
82
|
+
status: 'pending',
|
|
83
|
+
one_liner: '',
|
|
84
|
+
narrative: '',
|
|
85
|
+
verification_result: '',
|
|
86
|
+
duration: '',
|
|
87
|
+
completed_at: null,
|
|
88
|
+
blocker_discovered: false,
|
|
89
|
+
deviations: '',
|
|
90
|
+
known_issues: '',
|
|
91
|
+
key_files: [],
|
|
92
|
+
key_decisions: [],
|
|
93
|
+
full_summary_md: '',
|
|
94
|
+
description: '',
|
|
95
|
+
estimate: '',
|
|
96
|
+
files: [],
|
|
97
|
+
verify: '',
|
|
98
|
+
inputs: ['``src/foo.ts`` (current state)'],
|
|
99
|
+
expected_output: [],
|
|
100
|
+
observability_impact: '',
|
|
101
|
+
full_plan_md: '',
|
|
102
|
+
sequence: 0,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const tmp = resolve(process.cwd(), '.tmp-pre-exec-3892')
|
|
106
|
+
try {
|
|
107
|
+
mkdirSync(resolve(tmp, 'src'), { recursive: true })
|
|
108
|
+
writeFileSync(resolve(tmp, 'src', 'foo.ts'), '// ok')
|
|
109
|
+
const results = checkFilePathConsistency([task as any], tmp)
|
|
110
|
+
assert.deepEqual(results, [])
|
|
111
|
+
} finally {
|
|
112
|
+
rmSync(tmp, { recursive: true, force: true })
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
})
|
|
@@ -22,16 +22,17 @@ describe("resource-loader import path", () => {
|
|
|
22
22
|
);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
test("uses
|
|
26
|
-
// The fix uses
|
|
27
|
-
// dist/resource-loader.js
|
|
25
|
+
test("uses GSD_PKG_ROOT to resolve resource-loader from package root", () => {
|
|
26
|
+
// The fix uses GSD_PKG_ROOT (set by loader.ts) to construct an absolute
|
|
27
|
+
// file URL to dist/resource-loader.js — works in both source and deployed,
|
|
28
|
+
// and on Windows where raw paths fail with ERR_UNSUPPORTED_ESM_URL_SCHEME.
|
|
28
29
|
assert.ok(
|
|
29
|
-
autoSrc.includes('
|
|
30
|
-
"auto.ts should use
|
|
30
|
+
autoSrc.includes('process.env.GSD_PKG_ROOT'),
|
|
31
|
+
"auto.ts should use GSD_PKG_ROOT to resolve resource-loader",
|
|
31
32
|
);
|
|
32
33
|
assert.ok(
|
|
33
|
-
autoSrc.includes('
|
|
34
|
-
"auto.ts should
|
|
34
|
+
autoSrc.includes('pathToFileURL'),
|
|
35
|
+
"auto.ts should convert path to file URL for cross-platform import()",
|
|
35
36
|
);
|
|
36
37
|
});
|
|
37
38
|
});
|
|
@@ -74,6 +74,27 @@ test("validateDirectory: C:\\Windows is blocked", { skip: !isWindows ? "Windows-
|
|
|
74
74
|
assert.equal(result.severity, "blocked");
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
+
test("validateDirectory: D:\\Windows is blocked", { skip: !isWindows ? "Windows-only test" : undefined }, () => {
|
|
78
|
+
const result = validateDirectory("D:\\Windows");
|
|
79
|
+
assert.equal(result.safe, false);
|
|
80
|
+
assert.equal(result.severity, "blocked");
|
|
81
|
+
assert.ok(result.reason?.includes("system directory"));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("validateDirectory: E:\\Program Files is blocked", { skip: !isWindows ? "Windows-only test" : undefined }, () => {
|
|
85
|
+
const result = validateDirectory("E:\\Program Files");
|
|
86
|
+
assert.equal(result.safe, false);
|
|
87
|
+
assert.equal(result.severity, "blocked");
|
|
88
|
+
assert.ok(result.reason?.includes("system directory"));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("validateDirectory: any Windows drive root is blocked", { skip: !isWindows ? "Windows-only test" : undefined }, () => {
|
|
92
|
+
const result = validateDirectory("D:\\");
|
|
93
|
+
assert.equal(result.safe, false);
|
|
94
|
+
assert.equal(result.severity, "blocked");
|
|
95
|
+
assert.ok(result.reason?.includes("system directory"));
|
|
96
|
+
});
|
|
97
|
+
|
|
77
98
|
// ─── Home directory (cross-platform) ─────────────────────────────────────────────
|
|
78
99
|
|
|
79
100
|
test("validateDirectory: home directory itself is blocked", () => {
|
|
@@ -104,7 +125,13 @@ test("validateDirectory: subdirectory of home is NOT blocked", () => {
|
|
|
104
125
|
// Regression test for #1317: GSD worktree inside $HOME must not be blocked even
|
|
105
126
|
// when the resolved project root equals $HOME (e.g. home dir is a git repo).
|
|
106
127
|
test("validateDirectory: GSD worktree path nested under home is NOT blocked (#1317)", () => {
|
|
128
|
+
const originalHome = process.env.HOME;
|
|
129
|
+
const originalUserProfile = process.env.USERPROFILE;
|
|
130
|
+
const fakeHome = makeTempDir("fake-home");
|
|
131
|
+
process.env.HOME = fakeHome;
|
|
132
|
+
process.env.USERPROFILE = fakeHome;
|
|
107
133
|
const worktreePath = join(homedir(), ".gsd", "worktrees", "M001");
|
|
134
|
+
const worktreeRoot = join(fakeHome, ".gsd", "worktrees", "M001");
|
|
108
135
|
mkdirSync(worktreePath, { recursive: true });
|
|
109
136
|
try {
|
|
110
137
|
// The worktree CWD itself is a valid location — it must pass.
|
|
@@ -112,7 +139,12 @@ test("validateDirectory: GSD worktree path nested under home is NOT blocked (#13
|
|
|
112
139
|
assert.equal(result.safe, true, "GSD worktree path should be safe to run in");
|
|
113
140
|
assert.equal(result.severity, "ok");
|
|
114
141
|
} finally {
|
|
115
|
-
|
|
142
|
+
if (originalHome === undefined) delete process.env.HOME;
|
|
143
|
+
else process.env.HOME = originalHome;
|
|
144
|
+
if (originalUserProfile === undefined) delete process.env.USERPROFILE;
|
|
145
|
+
else process.env.USERPROFILE = originalUserProfile;
|
|
146
|
+
rmSync(worktreeRoot, { recursive: true, force: true });
|
|
147
|
+
rmSync(fakeHome, { recursive: true, force: true });
|
|
116
148
|
}
|
|
117
149
|
});
|
|
118
150
|
|