gsd-pi 2.67.0-dev.a5b1d8f → 2.67.0-dev.fe39184
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/README.md +41 -31
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +121 -8
- package/dist/resources/extensions/gsd/auto/phases.js +17 -0
- package/dist/resources/extensions/gsd/auto/session.js +6 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +12 -0
- package/dist/resources/extensions/gsd/auto-start.js +12 -0
- package/dist/resources/extensions/gsd/auto.js +27 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +11 -435
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -4
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +7 -64
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +88 -8
- package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +39 -25
- package/dist/resources/extensions/gsd/commands/index.js +8 -1
- package/dist/resources/extensions/gsd/commands-mcp-status.js +43 -7
- package/dist/resources/extensions/gsd/guided-flow.js +16 -0
- package/dist/resources/extensions/gsd/init-wizard.js +37 -0
- package/dist/resources/extensions/gsd/mcp-project-config.js +83 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +508 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +18 -3
- package/dist/resources/extensions/gsd/workflow-mcp.js +261 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
- 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/react-loadable-manifest.json +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.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +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/6502.5dcdcf1e1432e20d.js +9 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-b49b09f97429b5d0.js → webpack-42a66876b763aa26.js} +1 -1
- package/package.json +4 -2
- package/packages/mcp-server/README.md +38 -0
- package/packages/mcp-server/dist/cli.d.ts +9 -0
- package/packages/mcp-server/dist/cli.d.ts.map +1 -0
- package/packages/mcp-server/dist/cli.js +58 -0
- package/packages/mcp-server/dist/cli.js.map +1 -0
- package/packages/mcp-server/dist/index.d.ts +20 -0
- package/packages/mcp-server/dist/index.d.ts.map +1 -0
- package/packages/mcp-server/dist/index.js +14 -0
- package/packages/mcp-server/dist/index.js.map +1 -0
- package/packages/mcp-server/dist/readers/captures.d.ts +25 -0
- package/packages/mcp-server/dist/readers/captures.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/captures.js +67 -0
- package/packages/mcp-server/dist/readers/captures.js.map +1 -0
- package/packages/mcp-server/dist/readers/doctor-lite.d.ts +20 -0
- package/packages/mcp-server/dist/readers/doctor-lite.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/doctor-lite.js +173 -0
- package/packages/mcp-server/dist/readers/doctor-lite.js.map +1 -0
- package/packages/mcp-server/dist/readers/index.d.ts +14 -0
- package/packages/mcp-server/dist/readers/index.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/index.js +10 -0
- package/packages/mcp-server/dist/readers/index.js.map +1 -0
- package/packages/mcp-server/dist/readers/knowledge.d.ts +18 -0
- package/packages/mcp-server/dist/readers/knowledge.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/knowledge.js +82 -0
- package/packages/mcp-server/dist/readers/knowledge.js.map +1 -0
- package/packages/mcp-server/dist/readers/metrics.d.ts +32 -0
- package/packages/mcp-server/dist/readers/metrics.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/metrics.js +74 -0
- package/packages/mcp-server/dist/readers/metrics.js.map +1 -0
- package/packages/mcp-server/dist/readers/paths.d.ts +42 -0
- package/packages/mcp-server/dist/readers/paths.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/paths.js +199 -0
- package/packages/mcp-server/dist/readers/paths.js.map +1 -0
- package/packages/mcp-server/dist/readers/roadmap.d.ts +26 -0
- package/packages/mcp-server/dist/readers/roadmap.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/roadmap.js +194 -0
- package/packages/mcp-server/dist/readers/roadmap.js.map +1 -0
- package/packages/mcp-server/dist/readers/state.d.ts +43 -0
- package/packages/mcp-server/dist/readers/state.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/state.js +184 -0
- package/packages/mcp-server/dist/readers/state.js.map +1 -0
- package/packages/mcp-server/dist/server.d.ts +28 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -0
- package/packages/mcp-server/dist/server.js +319 -0
- package/packages/mcp-server/dist/server.js.map +1 -0
- package/packages/mcp-server/dist/session-manager.d.ts +54 -0
- package/packages/mcp-server/dist/session-manager.d.ts.map +1 -0
- package/packages/mcp-server/dist/session-manager.js +284 -0
- package/packages/mcp-server/dist/session-manager.js.map +1 -0
- package/packages/mcp-server/dist/types.d.ts +61 -0
- package/packages/mcp-server/dist/types.d.ts.map +1 -0
- package/packages/mcp-server/dist/types.js +11 -0
- package/packages/mcp-server/dist/types.js.map +1 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts +9 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -0
- package/packages/mcp-server/dist/workflow-tools.js +532 -0
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -0
- package/packages/mcp-server/src/server.ts +6 -2
- package/packages/mcp-server/src/workflow-tools.test.ts +976 -0
- package/packages/mcp-server/src/workflow-tools.ts +997 -0
- package/packages/mcp-server/tsconfig.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +14 -6
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +53 -0
- package/packages/pi-agent-core/src/agent-loop.ts +20 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +28 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +19 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +54 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -12
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +21 -0
- package/packages/rpc-client/dist/index.d.ts +10 -0
- package/packages/rpc-client/dist/index.d.ts.map +1 -0
- package/packages/rpc-client/dist/index.js +9 -0
- package/packages/rpc-client/dist/index.js.map +1 -0
- package/packages/rpc-client/dist/jsonl.d.ts +17 -0
- package/packages/rpc-client/dist/jsonl.d.ts.map +1 -0
- package/packages/rpc-client/dist/jsonl.js +54 -0
- package/packages/rpc-client/dist/jsonl.js.map +1 -0
- package/packages/rpc-client/dist/rpc-client.d.ts +259 -0
- package/packages/rpc-client/dist/rpc-client.d.ts.map +1 -0
- package/packages/rpc-client/dist/rpc-client.js +541 -0
- package/packages/rpc-client/dist/rpc-client.js.map +1 -0
- package/packages/rpc-client/dist/rpc-client.test.d.ts +2 -0
- package/packages/rpc-client/dist/rpc-client.test.d.ts.map +1 -0
- package/packages/rpc-client/dist/rpc-client.test.js +477 -0
- package/packages/rpc-client/dist/rpc-client.test.js.map +1 -0
- package/packages/rpc-client/dist/rpc-types.d.ts +566 -0
- package/packages/rpc-client/dist/rpc-types.d.ts.map +1 -0
- package/packages/rpc-client/dist/rpc-types.js +12 -0
- package/packages/rpc-client/dist/rpc-types.js.map +1 -0
- package/scripts/ensure-workspace-builds.cjs +2 -0
- package/scripts/link-workspace-packages.cjs +21 -14
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +157 -8
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +182 -0
- package/src/resources/extensions/gsd/auto/phases.ts +25 -0
- package/src/resources/extensions/gsd/auto/session.ts +6 -0
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +20 -0
- package/src/resources/extensions/gsd/auto-start.ts +15 -1
- package/src/resources/extensions/gsd/auto.ts +29 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +22 -435
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -5
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +7 -72
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +122 -6
- package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +53 -26
- package/src/resources/extensions/gsd/commands/index.ts +7 -1
- package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
- package/src/resources/extensions/gsd/guided-flow.ts +24 -0
- package/src/resources/extensions/gsd/init-wizard.ts +40 -0
- package/src/resources/extensions/gsd/mcp-project-config.ts +128 -0
- package/src/resources/extensions/gsd/tests/auto-project-root-env.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +101 -0
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +500 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +625 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +629 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +19 -3
- package/src/resources/extensions/gsd/workflow-mcp.ts +320 -0
- package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
- /package/dist/web/standalone/.next/static/{NllX5BEOLdTXS9ypf1i3i → gbSATDX4Jt2ufxzUr5nYm}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{NllX5BEOLdTXS9ypf1i3i → gbSATDX4Jt2ufxzUr5nYm}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { assertSafeDirectory } from "./validate-directory.js";
|
|
6
|
+
import { detectWorkflowMcpLaunchConfig } from "./workflow-mcp.js";
|
|
7
|
+
|
|
8
|
+
export const GSD_WORKFLOW_MCP_SERVER_NAME = "gsd-workflow";
|
|
9
|
+
|
|
10
|
+
export interface ProjectMcpServerConfig {
|
|
11
|
+
command?: string;
|
|
12
|
+
args?: string[];
|
|
13
|
+
cwd?: string;
|
|
14
|
+
env?: Record<string, string>;
|
|
15
|
+
url?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface EnsureProjectWorkflowMcpConfigResult {
|
|
19
|
+
configPath: string;
|
|
20
|
+
serverName: string;
|
|
21
|
+
status: "created" | "updated" | "unchanged";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface McpConfigFile {
|
|
25
|
+
mcpServers?: Record<string, ProjectMcpServerConfig>;
|
|
26
|
+
servers?: Record<string, ProjectMcpServerConfig>;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function resolveBundledGsdCliPath(env: NodeJS.ProcessEnv = process.env): string | null {
|
|
31
|
+
const explicit = env.GSD_CLI_PATH?.trim() || env.GSD_BIN_PATH?.trim();
|
|
32
|
+
if (explicit) return explicit;
|
|
33
|
+
|
|
34
|
+
const candidates = [
|
|
35
|
+
resolve(fileURLToPath(new URL("../../../../scripts/dev-cli.js", import.meta.url))),
|
|
36
|
+
resolve(fileURLToPath(new URL("../../../../dist/loader.js", import.meta.url))),
|
|
37
|
+
resolve(fileURLToPath(new URL("../../../loader.js", import.meta.url))),
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
for (const candidate of candidates) {
|
|
41
|
+
if (existsSync(candidate)) return candidate;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function buildProjectWorkflowMcpServerConfig(
|
|
48
|
+
projectRoot: string,
|
|
49
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
50
|
+
): ProjectMcpServerConfig {
|
|
51
|
+
const resolvedProjectRoot = resolve(projectRoot);
|
|
52
|
+
const gsdCliPath = resolveBundledGsdCliPath(env);
|
|
53
|
+
const launch = detectWorkflowMcpLaunchConfig(resolvedProjectRoot, {
|
|
54
|
+
...env,
|
|
55
|
+
...(gsdCliPath ? { GSD_CLI_PATH: gsdCliPath, GSD_BIN_PATH: gsdCliPath } : {}),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (!launch) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
"Unable to resolve the GSD workflow MCP server. Build this checkout or install gsd-mcp-server on PATH.",
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
command: launch.command,
|
|
66
|
+
...(launch.args && launch.args.length > 0 ? { args: launch.args } : {}),
|
|
67
|
+
...(launch.cwd ? { cwd: launch.cwd } : {}),
|
|
68
|
+
...(launch.env ? { env: launch.env } : {}),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readExistingConfig(configPath: string): McpConfigFile {
|
|
73
|
+
if (!existsSync(configPath)) return {};
|
|
74
|
+
|
|
75
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(raw) as McpConfigFile;
|
|
78
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
79
|
+
} catch (err) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Failed to parse ${configPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function ensureProjectWorkflowMcpConfig(
|
|
87
|
+
projectRoot: string,
|
|
88
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
89
|
+
): EnsureProjectWorkflowMcpConfigResult {
|
|
90
|
+
const resolvedProjectRoot = resolve(projectRoot);
|
|
91
|
+
assertSafeDirectory(resolvedProjectRoot);
|
|
92
|
+
|
|
93
|
+
const configPath = resolve(resolvedProjectRoot, ".mcp.json");
|
|
94
|
+
const existing = readExistingConfig(configPath);
|
|
95
|
+
const desiredServer = buildProjectWorkflowMcpServerConfig(resolvedProjectRoot, env);
|
|
96
|
+
const previousServers = existing.mcpServers ?? {};
|
|
97
|
+
const nextServers = {
|
|
98
|
+
...previousServers,
|
|
99
|
+
[GSD_WORKFLOW_MCP_SERVER_NAME]: desiredServer,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const alreadyPresent = existsSync(configPath);
|
|
103
|
+
const unchanged =
|
|
104
|
+
JSON.stringify(previousServers[GSD_WORKFLOW_MCP_SERVER_NAME] ?? null)
|
|
105
|
+
=== JSON.stringify(desiredServer)
|
|
106
|
+
&& existing.mcpServers !== undefined;
|
|
107
|
+
|
|
108
|
+
if (unchanged) {
|
|
109
|
+
return {
|
|
110
|
+
configPath,
|
|
111
|
+
serverName: GSD_WORKFLOW_MCP_SERVER_NAME,
|
|
112
|
+
status: "unchanged",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const nextConfig: McpConfigFile = {
|
|
117
|
+
...existing,
|
|
118
|
+
mcpServers: nextServers,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf-8");
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
configPath,
|
|
125
|
+
serverName: GSD_WORKFLOW_MCP_SERVER_NAME,
|
|
126
|
+
status: alreadyPresent ? "updated" : "created",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
const sourcePath = join(import.meta.dirname, "..", "auto.ts");
|
|
7
|
+
const source = readFileSync(sourcePath, "utf-8");
|
|
8
|
+
|
|
9
|
+
test("auto-mode captures GSD_PROJECT_ROOT before entering the dispatch loop", () => {
|
|
10
|
+
const captureDeclIdx = source.indexOf("function captureProjectRootEnv(projectRoot: string): void {");
|
|
11
|
+
assert.ok(captureDeclIdx > -1, "auto.ts should define captureProjectRootEnv()");
|
|
12
|
+
|
|
13
|
+
const resumeCallIdx = source.indexOf("captureProjectRootEnv(s.originalBasePath || s.basePath);");
|
|
14
|
+
assert.ok(resumeCallIdx > -1, "auto.ts should capture GSD_PROJECT_ROOT before resume autoLoop");
|
|
15
|
+
|
|
16
|
+
const firstAutoLoopIdx = source.indexOf("await autoLoop(ctx, pi, s, buildLoopDeps());");
|
|
17
|
+
assert.ok(firstAutoLoopIdx > -1, "auto.ts should invoke autoLoop()");
|
|
18
|
+
assert.ok(
|
|
19
|
+
resumeCallIdx < firstAutoLoopIdx,
|
|
20
|
+
"auto.ts must set GSD_PROJECT_ROOT before the first autoLoop() call",
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("auto-mode restores GSD_PROJECT_ROOT when execution stops or pauses", () => {
|
|
25
|
+
assert.match(source, /function restoreProjectRootEnv\(\): void \{/);
|
|
26
|
+
assert.match(source, /cleanupAfterLoopExit\(ctx: ExtensionContext\): void \{[\s\S]*restoreProjectRootEnv\(\);/);
|
|
27
|
+
assert.match(source, /export async function pauseAuto\([\s\S]*restoreProjectRootEnv\(\);/);
|
|
28
|
+
assert.match(source, /\} finally \{[\s\S]*restoreProjectRootEnv\(\);[\s\S]*s\.reset\(\);/);
|
|
29
|
+
});
|
|
@@ -74,3 +74,104 @@ test("model command resolves and persists exact provider-qualified selection", a
|
|
|
74
74
|
assert.deepEqual(applied, selectedModel);
|
|
75
75
|
assert.match(notices[0]!.message, /openai\/gpt-5\.4/);
|
|
76
76
|
});
|
|
77
|
+
|
|
78
|
+
test("interactive model picker chooses provider first, then model", async () => {
|
|
79
|
+
const selectedModel = { provider: "openai", id: "gpt-5.4" };
|
|
80
|
+
let applied: typeof selectedModel | null = null;
|
|
81
|
+
const selects: Array<{ title: string; options: string[] }> = [];
|
|
82
|
+
const notices: Array<{ message: string; type?: string }> = [];
|
|
83
|
+
|
|
84
|
+
const ctx = {
|
|
85
|
+
hasUI: true,
|
|
86
|
+
model: { provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
87
|
+
modelRegistry: {
|
|
88
|
+
getAvailable: () => [
|
|
89
|
+
{ provider: "openai", id: "gpt-5.4" },
|
|
90
|
+
{ provider: "anthropic", id: "claude-opus-4-6" },
|
|
91
|
+
{ provider: "openai", id: "gpt-5.3-mini" },
|
|
92
|
+
{ provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
ui: {
|
|
96
|
+
select: async (title: string, options: string[]) => {
|
|
97
|
+
selects.push({ title, options });
|
|
98
|
+
return selects.length === 1 ? "openai (2 models)" : "gpt-5.4";
|
|
99
|
+
},
|
|
100
|
+
notify: (message: string, type?: string) => {
|
|
101
|
+
notices.push({ message, type });
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
} as any;
|
|
105
|
+
|
|
106
|
+
const pi = {
|
|
107
|
+
setModel: async (model: typeof selectedModel) => {
|
|
108
|
+
applied = model;
|
|
109
|
+
return true;
|
|
110
|
+
},
|
|
111
|
+
} as any;
|
|
112
|
+
|
|
113
|
+
const handled = await handleCoreCommand("model", ctx, pi);
|
|
114
|
+
assert.equal(handled, true);
|
|
115
|
+
assert.deepEqual(selects, [
|
|
116
|
+
{
|
|
117
|
+
title: "Select session model: — choose provider:",
|
|
118
|
+
options: ["anthropic (2 models)", "openai (2 models)", "(cancel)"],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
title: "Select session model: — openai:",
|
|
122
|
+
options: ["gpt-5.3-mini", "gpt-5.4", "(cancel)"],
|
|
123
|
+
},
|
|
124
|
+
]);
|
|
125
|
+
assert.deepEqual(applied, selectedModel);
|
|
126
|
+
assert.match(notices[0]!.message, /openai\/gpt-5\.4/);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("ambiguous typed model selection chooses provider first, then model", async () => {
|
|
130
|
+
const selectedModel = { provider: "github-copilot", id: "gpt-5" };
|
|
131
|
+
let applied: typeof selectedModel | null = null;
|
|
132
|
+
const selects: Array<{ title: string; options: string[] }> = [];
|
|
133
|
+
const notices: Array<{ message: string; type?: string }> = [];
|
|
134
|
+
|
|
135
|
+
const ctx = {
|
|
136
|
+
hasUI: true,
|
|
137
|
+
model: { provider: "anthropic", id: "claude-sonnet-4-6" },
|
|
138
|
+
modelRegistry: {
|
|
139
|
+
getAvailable: () => [
|
|
140
|
+
{ provider: "openai", id: "gpt-5" },
|
|
141
|
+
{ provider: "github-copilot", id: "gpt-5" },
|
|
142
|
+
{ provider: "openai", id: "gpt-5-mini" },
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
ui: {
|
|
146
|
+
select: async (title: string, options: string[]) => {
|
|
147
|
+
selects.push({ title, options });
|
|
148
|
+
return selects.length === 1 ? "github-copilot (1 model)" : "gpt-5";
|
|
149
|
+
},
|
|
150
|
+
notify: (message: string, type?: string) => {
|
|
151
|
+
notices.push({ message, type });
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
} as any;
|
|
155
|
+
|
|
156
|
+
const pi = {
|
|
157
|
+
setModel: async (model: typeof selectedModel) => {
|
|
158
|
+
applied = model;
|
|
159
|
+
return true;
|
|
160
|
+
},
|
|
161
|
+
} as any;
|
|
162
|
+
|
|
163
|
+
const handled = await handleCoreCommand("model gpt", ctx, pi);
|
|
164
|
+
assert.equal(handled, true);
|
|
165
|
+
assert.deepEqual(selects, [
|
|
166
|
+
{
|
|
167
|
+
title: "Multiple models match \"gpt\" — choose provider:",
|
|
168
|
+
options: ["github-copilot (1 model)", "openai (2 models)", "(cancel)"],
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
title: "Multiple models match \"gpt\" — github-copilot:",
|
|
172
|
+
options: ["gpt-5", "(cancel)"],
|
|
173
|
+
},
|
|
174
|
+
]);
|
|
175
|
+
assert.deepEqual(applied, selectedModel);
|
|
176
|
+
assert.match(notices[0]!.message, /github-copilot\/gpt-5/);
|
|
177
|
+
});
|
|
@@ -77,6 +77,36 @@ describe('ensure-db-open', () => {
|
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
test('ensureDbOpen: explicit basePath opens target project without cwd override', async () => {
|
|
81
|
+
const tmpDir = makeTmpDir();
|
|
82
|
+
const gsdDir = path.join(tmpDir, '.gsd');
|
|
83
|
+
fs.mkdirSync(gsdDir, { recursive: true });
|
|
84
|
+
fs.writeFileSync(path.join(gsdDir, 'DECISIONS.md'), `# Decisions
|
|
85
|
+
|
|
86
|
+
| # | When | Scope | Decision | Choice | Rationale | Revisable |
|
|
87
|
+
|---|------|-------|----------|--------|-----------|-----------|
|
|
88
|
+
| D777 | M001 | architecture | Use explicit basePath | BasePath | Avoid cwd coupling | Yes |
|
|
89
|
+
`);
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
closeDatabase();
|
|
93
|
+
} catch { /* ok */ }
|
|
94
|
+
|
|
95
|
+
const originalCwd = process.cwd();
|
|
96
|
+
try {
|
|
97
|
+
const { ensureDbOpen } = await import('../bootstrap/dynamic-tools.ts');
|
|
98
|
+
const result = await ensureDbOpen(tmpDir);
|
|
99
|
+
|
|
100
|
+
assert.ok(result === true, 'ensureDbOpen should honor explicit basePath');
|
|
101
|
+
assert.equal(process.cwd(), originalCwd, 'ensureDbOpen should not mutate process.cwd');
|
|
102
|
+
assert.ok(isDbAvailable(), 'DB should be available after explicit open');
|
|
103
|
+
assert.ok(getDecisionById('D777') !== null, 'explicit basePath DB should be opened');
|
|
104
|
+
} finally {
|
|
105
|
+
closeDatabase();
|
|
106
|
+
cleanupDir(tmpDir);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
80
110
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
81
111
|
// ensureDbOpen returns false when no .gsd/ exists
|
|
82
112
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -159,6 +189,42 @@ describe('ensure-db-open', () => {
|
|
|
159
189
|
}
|
|
160
190
|
});
|
|
161
191
|
|
|
192
|
+
test('ensureDbOpen: switches open database when basePath changes', async () => {
|
|
193
|
+
const firstDir = makeTmpDir();
|
|
194
|
+
const secondDir = makeTmpDir();
|
|
195
|
+
fs.mkdirSync(path.join(firstDir, '.gsd'), { recursive: true });
|
|
196
|
+
fs.mkdirSync(path.join(secondDir, '.gsd'), { recursive: true });
|
|
197
|
+
fs.writeFileSync(path.join(firstDir, '.gsd', 'DECISIONS.md'), `# Decisions
|
|
198
|
+
|
|
199
|
+
| # | When | Scope | Decision | Choice | Rationale | Revisable |
|
|
200
|
+
|---|------|-------|----------|--------|-----------|-----------|
|
|
201
|
+
| D101 | M001 | architecture | First DB | First | First rationale | Yes |
|
|
202
|
+
`);
|
|
203
|
+
fs.writeFileSync(path.join(secondDir, '.gsd', 'DECISIONS.md'), `# Decisions
|
|
204
|
+
|
|
205
|
+
| # | When | Scope | Decision | Choice | Rationale | Revisable |
|
|
206
|
+
|---|------|-------|----------|--------|-----------|-----------|
|
|
207
|
+
| D202 | M001 | architecture | Second DB | Second | Second rationale | Yes |
|
|
208
|
+
`);
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
closeDatabase();
|
|
212
|
+
} catch { /* ok */ }
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const { ensureDbOpen } = await import('../bootstrap/dynamic-tools.ts');
|
|
216
|
+
assert.equal(await ensureDbOpen(firstDir), true);
|
|
217
|
+
assert.ok(getDecisionById('D101') !== null, 'first DB should be active');
|
|
218
|
+
assert.equal(await ensureDbOpen(secondDir), true);
|
|
219
|
+
assert.ok(getDecisionById('D202') !== null, 'second DB should be active after switch');
|
|
220
|
+
assert.equal(getDecisionById('D101'), null, 'first DB should no longer be active after switch');
|
|
221
|
+
} finally {
|
|
222
|
+
closeDatabase();
|
|
223
|
+
cleanupDir(firstDir);
|
|
224
|
+
cleanupDir(secondDir);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
162
228
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
163
229
|
|
|
164
230
|
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
ensureProjectWorkflowMcpConfig,
|
|
9
|
+
GSD_WORKFLOW_MCP_SERVER_NAME,
|
|
10
|
+
} from "../mcp-project-config.ts";
|
|
11
|
+
|
|
12
|
+
test("ensureProjectWorkflowMcpConfig creates .mcp.json with the workflow server", () => {
|
|
13
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-mcp-init-"));
|
|
14
|
+
mkdirSync(join(projectRoot, ".gsd"), { recursive: true });
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const result = ensureProjectWorkflowMcpConfig(projectRoot);
|
|
18
|
+
assert.equal(result.status, "created");
|
|
19
|
+
assert.equal(existsSync(result.configPath), true);
|
|
20
|
+
|
|
21
|
+
const parsed = JSON.parse(readFileSync(result.configPath, "utf-8")) as {
|
|
22
|
+
mcpServers?: Record<string, { command?: string; args?: string[]; env?: Record<string, string> }>;
|
|
23
|
+
};
|
|
24
|
+
const server = parsed.mcpServers?.[GSD_WORKFLOW_MCP_SERVER_NAME];
|
|
25
|
+
assert.ok(server, "workflow server should be written to mcpServers");
|
|
26
|
+
assert.equal(typeof server?.command, "string");
|
|
27
|
+
assert.equal(Array.isArray(server?.args), true);
|
|
28
|
+
assert.equal(server?.env?.GSD_WORKFLOW_PROJECT_ROOT, projectRoot);
|
|
29
|
+
assert.match(server?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.js$/);
|
|
30
|
+
assert.match(server?.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.js$/);
|
|
31
|
+
} finally {
|
|
32
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("ensureProjectWorkflowMcpConfig preserves existing mcp servers", () => {
|
|
37
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-mcp-init-"));
|
|
38
|
+
mkdirSync(join(projectRoot, ".gsd"), { recursive: true });
|
|
39
|
+
const configPath = join(projectRoot, ".mcp.json");
|
|
40
|
+
|
|
41
|
+
writeFileSync(
|
|
42
|
+
configPath,
|
|
43
|
+
`${JSON.stringify({
|
|
44
|
+
mcpServers: {
|
|
45
|
+
railway: {
|
|
46
|
+
command: "npx",
|
|
47
|
+
args: ["railway-mcp"],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
}, null, 2)}\n`,
|
|
51
|
+
"utf-8",
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const result = ensureProjectWorkflowMcpConfig(projectRoot);
|
|
56
|
+
assert.equal(result.status, "updated");
|
|
57
|
+
|
|
58
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf-8")) as {
|
|
59
|
+
mcpServers?: Record<string, { command?: string; args?: string[] }>;
|
|
60
|
+
};
|
|
61
|
+
assert.deepEqual(parsed.mcpServers?.railway, {
|
|
62
|
+
command: "npx",
|
|
63
|
+
args: ["railway-mcp"],
|
|
64
|
+
});
|
|
65
|
+
assert.ok(parsed.mcpServers?.[GSD_WORKFLOW_MCP_SERVER_NAME]);
|
|
66
|
+
} finally {
|
|
67
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("ensureProjectWorkflowMcpConfig is idempotent when config is already current", () => {
|
|
72
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-mcp-init-"));
|
|
73
|
+
mkdirSync(join(projectRoot, ".gsd"), { recursive: true });
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const first = ensureProjectWorkflowMcpConfig(projectRoot);
|
|
77
|
+
const second = ensureProjectWorkflowMcpConfig(projectRoot);
|
|
78
|
+
|
|
79
|
+
assert.equal(first.status, "created");
|
|
80
|
+
assert.equal(second.status, "unchanged");
|
|
81
|
+
assert.equal(first.configPath, second.configPath);
|
|
82
|
+
} finally {
|
|
83
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
@@ -2,6 +2,7 @@ import test, { describe } from "node:test";
|
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
+
formatMcpInitResult,
|
|
5
6
|
formatMcpStatusReport,
|
|
6
7
|
formatMcpServerDetail,
|
|
7
8
|
type McpServerStatus,
|
|
@@ -101,3 +102,17 @@ describe("formatMcpServerDetail", () => {
|
|
|
101
102
|
assert.match(result, /disconnected/i);
|
|
102
103
|
});
|
|
103
104
|
});
|
|
105
|
+
|
|
106
|
+
describe("formatMcpInitResult", () => {
|
|
107
|
+
test("shows created message with config path", () => {
|
|
108
|
+
const result = formatMcpInitResult("created", "/tmp/project/.mcp.json", "/tmp/project");
|
|
109
|
+
assert.match(result, /created project mcp config/i);
|
|
110
|
+
assert.match(result, /\/tmp\/project\/\.mcp\.json/);
|
|
111
|
+
assert.match(result, /claude code/i);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("shows unchanged message when config is current", () => {
|
|
115
|
+
const result = formatMcpInitResult("unchanged", "/tmp/project/.mcp.json", "/tmp/project");
|
|
116
|
+
assert.match(result, /already up to date/i);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
summarizeLogs,
|
|
19
19
|
formatForNotification,
|
|
20
20
|
setLogBasePath,
|
|
21
|
+
setStderrLoggingEnabled,
|
|
21
22
|
_resetLogs,
|
|
22
23
|
} from "../workflow-logger.ts";
|
|
23
24
|
|
|
@@ -375,5 +376,20 @@ describe("workflow-logger", () => {
|
|
|
375
376
|
logError("tool", "failed", { cmd: "complete_task" });
|
|
376
377
|
assert.ok(written[0].includes('"cmd":"complete_task"'));
|
|
377
378
|
});
|
|
379
|
+
|
|
380
|
+
test("suppresses stderr when disabled", (t) => {
|
|
381
|
+
const written: string[] = [];
|
|
382
|
+
const orig = process.stderr.write.bind(process.stderr);
|
|
383
|
+
const previous = setStderrLoggingEnabled(false);
|
|
384
|
+
// @ts-ignore — patching for test
|
|
385
|
+
process.stderr.write = (chunk: string) => { written.push(chunk); return true; };
|
|
386
|
+
t.after(() => {
|
|
387
|
+
process.stderr.write = orig;
|
|
388
|
+
setStderrLoggingEnabled(previous);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
logWarning("engine", "hidden warning");
|
|
392
|
+
assert.deepEqual(written, []);
|
|
393
|
+
});
|
|
378
394
|
});
|
|
379
395
|
});
|