gsd-pi 2.67.0-dev.5399650 → 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 +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +152 -70
- package/dist/resources/extensions/gsd/auto-start.js +12 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
- package/dist/resources/extensions/gsd/commands-mcp-status.js +43 -7
- package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -4
- package/dist/resources/extensions/gsd/doctor-proactive.js +3 -3
- package/dist/resources/extensions/gsd/init-wizard.js +15 -12
- package/dist/resources/extensions/gsd/mcp-project-config.js +83 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +64 -24
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
- 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 +13 -13
- 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 +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +10 -4
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.ts +13 -2
- 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/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +2 -12
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.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/pi-coding-agent/src/modes/interactive/interactive-mode.ts +2 -15
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +190 -93
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +89 -116
- package/src/resources/extensions/gsd/auto-start.ts +15 -1
- package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
- package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +4 -4
- package/src/resources/extensions/gsd/doctor-proactive.ts +3 -3
- package/src/resources/extensions/gsd/init-wizard.ts +17 -11
- package/src/resources/extensions/gsd/mcp-project-config.ts +128 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +2 -9
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +0 -33
- 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-mcp.test.ts +178 -17
- package/src/resources/extensions/gsd/workflow-mcp.ts +76 -23
- package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts +0 -13
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts.map +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js +0 -27
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js.map +0 -1
- package/packages/pi-coding-agent/src/modes/interactive/provider-auth-setup.ts +0 -40
- package/src/resources/extensions/gsd/tests/init-bootstrap-completeness.test.ts +0 -121
- /package/dist/web/standalone/.next/static/{6_QPFhgX0DQnDhhquheRc → gbSATDX4Jt2ufxzUr5nYm}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{6_QPFhgX0DQnDhhquheRc → gbSATDX4Jt2ufxzUr5nYm}/_ssgManifest.js +0 -0
|
@@ -661,10 +661,9 @@ describe('doctor-git', async () => {
|
|
|
661
661
|
env: { ...process.env, GIT_COMMITTER_DATE: pastDate },
|
|
662
662
|
});
|
|
663
663
|
|
|
664
|
-
// Modify
|
|
665
|
-
//
|
|
664
|
+
// Modify an already-tracked file (nativeAddTracked uses git add -u,
|
|
665
|
+
// which only stages tracked files — new untracked files are not staged)
|
|
666
666
|
writeFileSync(join(dir, "README.md"), "# test\nmodified content\n");
|
|
667
|
-
writeFileSync(join(dir, "new-untracked.ts"), "export const preserved = true;\n");
|
|
668
667
|
|
|
669
668
|
const detect = await runGSDDoctor(dir);
|
|
670
669
|
const staleIssues = detect.issues.filter(i => i.code === "stale_uncommitted_changes");
|
|
@@ -682,12 +681,6 @@ describe('doctor-git', async () => {
|
|
|
682
681
|
// Verify the snapshot commit was created with the gsd snapshot tag
|
|
683
682
|
const log = run("git log -1 --oneline", dir);
|
|
684
683
|
assert.ok(log.includes("gsd snapshot"), "commit is tagged with gsd snapshot");
|
|
685
|
-
|
|
686
|
-
const files = run("git show --name-only --format= HEAD", dir);
|
|
687
|
-
assert.ok(files.includes("README.md"), "snapshot keeps tracked modifications");
|
|
688
|
-
assert.ok(files.includes("new-untracked.ts"), "snapshot also includes new untracked files");
|
|
689
|
-
const status = run("git status --short", dir);
|
|
690
|
-
assert.ok(!status.includes("new-untracked.ts"), "snapshot does not leave the new source file untracked");
|
|
691
684
|
});
|
|
692
685
|
|
|
693
686
|
// ─── Test: stale_uncommitted_changes NOT flagged when recent commit ──
|
|
@@ -219,39 +219,6 @@ describe('doctor-proactive', async () => {
|
|
|
219
219
|
assert.ok(result.fixesApplied.some((f: string) => f.includes("STATE.md")), "reports STATE.md status as info");
|
|
220
220
|
});
|
|
221
221
|
|
|
222
|
-
test('health gate: pre-dispatch snapshot includes new untracked files', async () => {
|
|
223
|
-
const dir = createRepoWithActiveMilestone();
|
|
224
|
-
cleanups.push(dir);
|
|
225
|
-
|
|
226
|
-
const pastDate = new Date(Date.now() - 45 * 60 * 1000).toISOString();
|
|
227
|
-
run(`git commit --amend --no-edit --date="${pastDate}"`, dir);
|
|
228
|
-
execSync(`git commit --amend --no-edit`, {
|
|
229
|
-
cwd: dir,
|
|
230
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
231
|
-
encoding: "utf-8",
|
|
232
|
-
env: { ...process.env, GIT_COMMITTER_DATE: pastDate },
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
writeFileSync(join(dir, "README.md"), "# test\nmodified content\n");
|
|
236
|
-
writeFileSync(join(dir, "new-untracked.ts"), "export const preserved = true;\n");
|
|
237
|
-
|
|
238
|
-
const result = await preDispatchHealthGate(dir);
|
|
239
|
-
assert.ok(result.proceed, "dispatch still proceeds after snapshotting");
|
|
240
|
-
assert.ok(
|
|
241
|
-
result.fixesApplied.some((f: string) => f.includes("gsd snapshot")),
|
|
242
|
-
"pre-dispatch gate creates a snapshot commit",
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
const log = run("git log -1 --oneline", dir);
|
|
246
|
-
assert.ok(log.includes("gsd snapshot"), "snapshot commit is created");
|
|
247
|
-
|
|
248
|
-
const files = run("git show --name-only --format= HEAD", dir);
|
|
249
|
-
assert.ok(files.includes("README.md"), "snapshot keeps tracked modifications");
|
|
250
|
-
assert.ok(files.includes("new-untracked.ts"), "snapshot also includes new untracked files");
|
|
251
|
-
const status = run("git status --short", dir);
|
|
252
|
-
assert.ok(!status.includes("new-untracked.ts"), "snapshot does not leave the new source file untracked");
|
|
253
|
-
});
|
|
254
|
-
|
|
255
222
|
test('health gate: stale crash lock auto-cleared', async () => {
|
|
256
223
|
const dir = realpathSync(mkdtempSync(join(tmpdir(), "doc-proactive-")));
|
|
257
224
|
cleanups.push(dir);
|
|
@@ -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
|
+
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
7
9
|
|
|
8
10
|
import {
|
|
9
11
|
buildWorkflowMcpServers,
|
|
@@ -44,13 +46,14 @@ test("detectWorkflowMcpLaunchConfig prefers explicit env override", () => {
|
|
|
44
46
|
command: "node",
|
|
45
47
|
args: ["dist/cli.js"],
|
|
46
48
|
cwd: "/tmp/project",
|
|
47
|
-
env:
|
|
48
|
-
FOO: "bar",
|
|
49
|
-
GSD_CLI_PATH: "/tmp/gsd",
|
|
50
|
-
GSD_PERSIST_WRITE_GATE_STATE: "1",
|
|
51
|
-
GSD_WORKFLOW_PROJECT_ROOT: "/tmp/project",
|
|
52
|
-
},
|
|
49
|
+
env: launch?.env,
|
|
53
50
|
});
|
|
51
|
+
assert.equal(launch?.env?.FOO, "bar");
|
|
52
|
+
assert.equal(launch?.env?.GSD_CLI_PATH, "/tmp/gsd");
|
|
53
|
+
assert.equal(launch?.env?.GSD_PERSIST_WRITE_GATE_STATE, "1");
|
|
54
|
+
assert.equal(launch?.env?.GSD_WORKFLOW_PROJECT_ROOT, "/tmp/project");
|
|
55
|
+
assert.match(launch?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.(js|ts)$/);
|
|
56
|
+
assert.match(launch?.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.(js|ts)$/);
|
|
54
57
|
});
|
|
55
58
|
|
|
56
59
|
test("buildWorkflowMcpServers mirrors explicit launch config", () => {
|
|
@@ -63,12 +66,13 @@ test("buildWorkflowMcpServers mirrors explicit launch config", () => {
|
|
|
63
66
|
"gsd-workflow": {
|
|
64
67
|
command: "node",
|
|
65
68
|
args: ["dist/cli.js"],
|
|
66
|
-
env:
|
|
67
|
-
GSD_PERSIST_WRITE_GATE_STATE: "1",
|
|
68
|
-
GSD_WORKFLOW_PROJECT_ROOT: "/tmp/project",
|
|
69
|
-
},
|
|
69
|
+
env: servers?.["gsd-workflow"]?.env,
|
|
70
70
|
},
|
|
71
71
|
});
|
|
72
|
+
assert.equal((servers?.["gsd-workflow"]?.env as Record<string, string> | undefined)?.GSD_PERSIST_WRITE_GATE_STATE, "1");
|
|
73
|
+
assert.equal((servers?.["gsd-workflow"]?.env as Record<string, string> | undefined)?.GSD_WORKFLOW_PROJECT_ROOT, "/tmp/project");
|
|
74
|
+
assert.match((servers?.["gsd-workflow"]?.env as Record<string, string> | undefined)?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.(js|ts)$/);
|
|
75
|
+
assert.match((servers?.["gsd-workflow"]?.env as Record<string, string> | undefined)?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.(js|ts)$/);
|
|
72
76
|
});
|
|
73
77
|
|
|
74
78
|
test("detectWorkflowMcpLaunchConfig resolves the bundled server from GSD_PROJECT_ROOT", () => {
|
|
@@ -88,11 +92,41 @@ test("detectWorkflowMcpLaunchConfig resolves the bundled server from GSD_PROJECT
|
|
|
88
92
|
command: process.execPath,
|
|
89
93
|
args: [cliPath],
|
|
90
94
|
cwd: repoRoot,
|
|
91
|
-
env:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
env: launch?.env,
|
|
96
|
+
});
|
|
97
|
+
assert.equal(launch?.env?.GSD_PERSIST_WRITE_GATE_STATE, "1");
|
|
98
|
+
assert.equal(launch?.env?.GSD_WORKFLOW_PROJECT_ROOT, repoRoot);
|
|
99
|
+
assert.match(launch?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.(js|ts)$/);
|
|
100
|
+
assert.match(launch?.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.(js|ts)$/);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("detectWorkflowMcpLaunchConfig resolves the bundled server from GSD_BIN_PATH ancestry", () => {
|
|
104
|
+
const repoRoot = mkdtempSync(join(tmpdir(), "gsd-workflow-root-"));
|
|
105
|
+
const worktreeRoot = mkdtempSync(join(tmpdir(), "gsd-workflow-worktree-"));
|
|
106
|
+
const cliPath = join(repoRoot, "packages", "mcp-server", "dist", "cli.js");
|
|
107
|
+
const devCliPath = join(repoRoot, "scripts", "dev-cli.js");
|
|
108
|
+
|
|
109
|
+
mkdirSync(join(repoRoot, "packages", "mcp-server", "dist"), { recursive: true });
|
|
110
|
+
mkdirSync(join(repoRoot, "scripts"), { recursive: true });
|
|
111
|
+
writeFileSync(cliPath, "#!/usr/bin/env node\n", "utf-8");
|
|
112
|
+
writeFileSync(devCliPath, "#!/usr/bin/env node\n", "utf-8");
|
|
113
|
+
|
|
114
|
+
const launch = detectWorkflowMcpLaunchConfig(worktreeRoot, {
|
|
115
|
+
GSD_BIN_PATH: devCliPath,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
assert.deepEqual(launch, {
|
|
119
|
+
name: "gsd-workflow",
|
|
120
|
+
command: process.execPath,
|
|
121
|
+
args: [cliPath],
|
|
122
|
+
cwd: worktreeRoot,
|
|
123
|
+
env: launch?.env,
|
|
95
124
|
});
|
|
125
|
+
assert.equal(launch?.env?.GSD_CLI_PATH, devCliPath);
|
|
126
|
+
assert.equal(launch?.env?.GSD_PERSIST_WRITE_GATE_STATE, "1");
|
|
127
|
+
assert.equal(launch?.env?.GSD_WORKFLOW_PROJECT_ROOT, worktreeRoot);
|
|
128
|
+
assert.match(launch?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.(js|ts)$/);
|
|
129
|
+
assert.match(launch?.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.(js|ts)$/);
|
|
96
130
|
});
|
|
97
131
|
|
|
98
132
|
test("detectWorkflowMcpLaunchConfig resolves the bundled server relative to the installed GSD package", () => {
|
|
@@ -104,10 +138,137 @@ test("detectWorkflowMcpLaunchConfig resolves the bundled server relative to the
|
|
|
104
138
|
assert.equal(launch?.cwd, "/tmp/project");
|
|
105
139
|
assert.equal(launch?.env?.GSD_CLI_PATH, "/tmp/gsd-loader.js");
|
|
106
140
|
assert.equal(launch?.env?.GSD_WORKFLOW_PROJECT_ROOT, "/tmp/project");
|
|
141
|
+
assert.match(launch?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.(js|ts)$/);
|
|
142
|
+
assert.match(launch?.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.(js|ts)$/);
|
|
143
|
+
assert.equal(typeof launch?.args?.[0], "string");
|
|
144
|
+
assert.match(launch?.args?.[0] ?? "", /packages[\/\\]mcp-server[\/\\]dist[\/\\]cli\.js$/);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("detectWorkflowMcpLaunchConfig resolves the bundled server relative to the package without env hints", () => {
|
|
148
|
+
const launch = detectWorkflowMcpLaunchConfig("/tmp/project", {});
|
|
149
|
+
|
|
150
|
+
assert.equal(launch?.command, process.execPath);
|
|
151
|
+
assert.equal(launch?.cwd, "/tmp/project");
|
|
152
|
+
assert.equal(launch?.env?.GSD_CLI_PATH, undefined);
|
|
153
|
+
assert.equal(launch?.env?.GSD_WORKFLOW_PROJECT_ROOT, "/tmp/project");
|
|
154
|
+
assert.match(launch?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.(js|ts)$/);
|
|
155
|
+
assert.match(launch?.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.(js|ts)$/);
|
|
107
156
|
assert.equal(typeof launch?.args?.[0], "string");
|
|
108
157
|
assert.match(launch?.args?.[0] ?? "", /packages[\/\\]mcp-server[\/\\]dist[\/\\]cli\.js$/);
|
|
109
158
|
});
|
|
110
159
|
|
|
160
|
+
test("workflow MCP launch config reaches mutation tools over stdio", async () => {
|
|
161
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-workflow-transport-"));
|
|
162
|
+
mkdirSync(join(projectRoot, ".gsd"), { recursive: true });
|
|
163
|
+
|
|
164
|
+
const launch = detectWorkflowMcpLaunchConfig(projectRoot, {});
|
|
165
|
+
assert.ok(launch, "expected a workflow MCP launch config");
|
|
166
|
+
assert.match(
|
|
167
|
+
launch.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "",
|
|
168
|
+
/dist[\/\\]resources[\/\\]extensions[\/\\]gsd[\/\\]tools[\/\\]workflow-tool-executors\.js$/,
|
|
169
|
+
);
|
|
170
|
+
assert.match(
|
|
171
|
+
launch.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "",
|
|
172
|
+
/dist[\/\\]resources[\/\\]extensions[\/\\]gsd[\/\\]bootstrap[\/\\]write-gate\.js$/,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const client = new Client({ name: "workflow-mcp-transport-test", version: "1.0.0" });
|
|
176
|
+
const transport = new StdioClientTransport({
|
|
177
|
+
command: launch.command,
|
|
178
|
+
args: launch.args,
|
|
179
|
+
env: { ...process.env, ...launch.env } as Record<string, string>,
|
|
180
|
+
cwd: launch.cwd,
|
|
181
|
+
stderr: "pipe",
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
await client.connect(transport, { timeout: 30_000 });
|
|
186
|
+
|
|
187
|
+
const tools = await client.listTools(undefined, { timeout: 30_000 });
|
|
188
|
+
assert.ok(
|
|
189
|
+
(tools.tools ?? []).some((tool) => tool.name === "gsd_plan_slice"),
|
|
190
|
+
"expected workflow MCP surface to expose gsd_plan_slice",
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const milestoneResult = await client.callTool(
|
|
194
|
+
{
|
|
195
|
+
name: "gsd_plan_milestone",
|
|
196
|
+
arguments: {
|
|
197
|
+
projectDir: projectRoot,
|
|
198
|
+
milestoneId: "M001",
|
|
199
|
+
title: "Transport planning",
|
|
200
|
+
vision: "Verify stdio workflow MCP uses the executor bridge.",
|
|
201
|
+
slices: [
|
|
202
|
+
{
|
|
203
|
+
sliceId: "S01",
|
|
204
|
+
title: "Bridge path",
|
|
205
|
+
risk: "low",
|
|
206
|
+
depends: [],
|
|
207
|
+
demo: "Milestone planning succeeds over stdio MCP.",
|
|
208
|
+
goal: "Prove the executor bridge works in the spawned server.",
|
|
209
|
+
successCriteria: "gsd_plan_slice can write plan artifacts.",
|
|
210
|
+
proofLevel: "integration",
|
|
211
|
+
integrationClosure: "Stdio MCP client reaches the workflow executor bridge.",
|
|
212
|
+
observabilityImpact: "Regression test covers the spawned-server path.",
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
undefined,
|
|
218
|
+
{ timeout: 30_000 },
|
|
219
|
+
);
|
|
220
|
+
assert.equal(milestoneResult.isError, undefined);
|
|
221
|
+
assert.match(
|
|
222
|
+
((milestoneResult.content as Array<{ text?: string }>)?.[0])?.text ?? "",
|
|
223
|
+
/Planned milestone M001/,
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const sliceResult = await client.callTool(
|
|
227
|
+
{
|
|
228
|
+
name: "gsd_plan_slice",
|
|
229
|
+
arguments: {
|
|
230
|
+
projectDir: projectRoot,
|
|
231
|
+
milestoneId: "M001",
|
|
232
|
+
sliceId: "S01",
|
|
233
|
+
goal: "Persist slice planning over the spawned MCP transport.",
|
|
234
|
+
tasks: [
|
|
235
|
+
{
|
|
236
|
+
taskId: "T01",
|
|
237
|
+
title: "Connect the bridge",
|
|
238
|
+
description: "Ensure the workflow executor bridge resolves in the child process.",
|
|
239
|
+
estimate: "10m",
|
|
240
|
+
files: ["src/resources/extensions/gsd/workflow-mcp.ts"],
|
|
241
|
+
verify: "node --test",
|
|
242
|
+
inputs: ["M001-ROADMAP.md"],
|
|
243
|
+
expectedOutput: ["S01-PLAN.md", "T01-PLAN.md"],
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
undefined,
|
|
249
|
+
{ timeout: 30_000 },
|
|
250
|
+
);
|
|
251
|
+
assert.equal(sliceResult.isError, undefined);
|
|
252
|
+
assert.match(
|
|
253
|
+
((sliceResult.content as Array<{ text?: string }>)?.[0])?.text ?? "",
|
|
254
|
+
/Planned slice S01/,
|
|
255
|
+
);
|
|
256
|
+
assert.ok(
|
|
257
|
+
existsSync(join(projectRoot, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md")),
|
|
258
|
+
"expected slice plan artifact to be written through stdio MCP",
|
|
259
|
+
);
|
|
260
|
+
assert.ok(
|
|
261
|
+
existsSync(
|
|
262
|
+
join(projectRoot, ".gsd", "milestones", "M001", "slices", "S01", "tasks", "T01-PLAN.md"),
|
|
263
|
+
),
|
|
264
|
+
"expected task plan artifact to be written through stdio MCP",
|
|
265
|
+
);
|
|
266
|
+
} finally {
|
|
267
|
+
await client.close().catch(() => {});
|
|
268
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
111
272
|
test("usesWorkflowMcpTransport matches local externalCli providers", () => {
|
|
112
273
|
assert.equal(usesWorkflowMcpTransport("externalCli", "local://claude-code"), true);
|
|
113
274
|
assert.equal(usesWorkflowMcpTransport("externalCli", "https://api.example.com"), false);
|
|
@@ -131,7 +292,7 @@ test("transport compatibility passes when required tools fit current MCP surface
|
|
|
131
292
|
assert.equal(error, null);
|
|
132
293
|
});
|
|
133
294
|
|
|
134
|
-
test("transport compatibility
|
|
295
|
+
test("transport compatibility discovers the bundled MCP server without env overrides", () => {
|
|
135
296
|
const error = getWorkflowTransportSupportError(
|
|
136
297
|
"claude-code",
|
|
137
298
|
["gsd_task_complete"],
|
|
@@ -145,7 +306,7 @@ test("transport compatibility fails cleanly when MCP server is unavailable", ()
|
|
|
145
306
|
},
|
|
146
307
|
);
|
|
147
308
|
|
|
148
|
-
assert.
|
|
309
|
+
assert.equal(error, null);
|
|
149
310
|
});
|
|
150
311
|
|
|
151
312
|
test("transport compatibility now allows auto execute-task over workflow MCP surface", () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
-
import { resolve } from "node:path";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
6
|
export interface WorkflowMcpLaunchConfig {
|
|
@@ -67,8 +67,32 @@ function lookupCommand(command: string, platform: NodeJS.Platform = process.plat
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
function findWorkflowCliFromAncestorPath(startPath: string): string | null {
|
|
71
|
+
let current = resolve(startPath);
|
|
72
|
+
|
|
73
|
+
while (true) {
|
|
74
|
+
const candidate = resolve(current, "packages", "mcp-server", "dist", "cli.js");
|
|
75
|
+
if (existsSync(candidate)) return candidate;
|
|
76
|
+
|
|
77
|
+
const parent = dirname(current);
|
|
78
|
+
if (parent === current) break;
|
|
79
|
+
current = parent;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
70
85
|
function getBundledWorkflowMcpCliPath(env: NodeJS.ProcessEnv): string | null {
|
|
71
|
-
|
|
86
|
+
const envAnchors = [
|
|
87
|
+
env.GSD_BIN_PATH?.trim(),
|
|
88
|
+
env.GSD_CLI_PATH?.trim(),
|
|
89
|
+
env.GSD_WORKFLOW_PATH?.trim(),
|
|
90
|
+
].filter((value): value is string => typeof value === "string" && value.length > 0);
|
|
91
|
+
|
|
92
|
+
for (const anchor of envAnchors) {
|
|
93
|
+
const candidate = findWorkflowCliFromAncestorPath(anchor);
|
|
94
|
+
if (candidate) return candidate;
|
|
95
|
+
}
|
|
72
96
|
|
|
73
97
|
const candidates = [
|
|
74
98
|
resolve(fileURLToPath(new URL("../../../../packages/mcp-server/dist/cli.js", import.meta.url))),
|
|
@@ -82,6 +106,52 @@ function getBundledWorkflowMcpCliPath(env: NodeJS.ProcessEnv): string | null {
|
|
|
82
106
|
return null;
|
|
83
107
|
}
|
|
84
108
|
|
|
109
|
+
function getBundledWorkflowExecutorModulePath(): string | null {
|
|
110
|
+
const candidates = [
|
|
111
|
+
resolve(fileURLToPath(new URL("../../../../dist/resources/extensions/gsd/tools/workflow-tool-executors.js", import.meta.url))),
|
|
112
|
+
resolve(fileURLToPath(new URL("./tools/workflow-tool-executors.js", import.meta.url))),
|
|
113
|
+
resolve(fileURLToPath(new URL("./tools/workflow-tool-executors.ts", import.meta.url))),
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
for (const candidate of candidates) {
|
|
117
|
+
if (existsSync(candidate)) return candidate;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getBundledWorkflowWriteGateModulePath(): string | null {
|
|
124
|
+
const candidates = [
|
|
125
|
+
resolve(fileURLToPath(new URL("../../../../dist/resources/extensions/gsd/bootstrap/write-gate.js", import.meta.url))),
|
|
126
|
+
resolve(fileURLToPath(new URL("./bootstrap/write-gate.js", import.meta.url))),
|
|
127
|
+
resolve(fileURLToPath(new URL("./bootstrap/write-gate.ts", import.meta.url))),
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
for (const candidate of candidates) {
|
|
131
|
+
if (existsSync(candidate)) return candidate;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function buildWorkflowLaunchEnv(
|
|
138
|
+
projectRoot: string,
|
|
139
|
+
gsdCliPath: string | undefined,
|
|
140
|
+
explicitEnv?: Record<string, string>,
|
|
141
|
+
): Record<string, string> {
|
|
142
|
+
const executorModulePath = getBundledWorkflowExecutorModulePath();
|
|
143
|
+
const writeGateModulePath = getBundledWorkflowWriteGateModulePath();
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
...(explicitEnv ?? {}),
|
|
147
|
+
...(gsdCliPath ? { GSD_CLI_PATH: gsdCliPath } : {}),
|
|
148
|
+
...(executorModulePath ? { GSD_WORKFLOW_EXECUTORS_MODULE: executorModulePath } : {}),
|
|
149
|
+
...(writeGateModulePath ? { GSD_WORKFLOW_WRITE_GATE_MODULE: writeGateModulePath } : {}),
|
|
150
|
+
GSD_PERSIST_WRITE_GATE_STATE: "1",
|
|
151
|
+
GSD_WORKFLOW_PROJECT_ROOT: projectRoot,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
85
155
|
export function detectWorkflowMcpLaunchConfig(
|
|
86
156
|
projectRoot = process.cwd(),
|
|
87
157
|
env: NodeJS.ProcessEnv = process.env,
|
|
@@ -101,12 +171,7 @@ export function detectWorkflowMcpLaunchConfig(
|
|
|
101
171
|
const resolvedWorkflowProjectRoot = resolve(workflowProjectRoot);
|
|
102
172
|
|
|
103
173
|
if (explicitCommand) {
|
|
104
|
-
const launchEnv =
|
|
105
|
-
...(explicitEnv ?? {}),
|
|
106
|
-
...(gsdCliPath ? { GSD_CLI_PATH: gsdCliPath } : {}),
|
|
107
|
-
GSD_PERSIST_WRITE_GATE_STATE: "1",
|
|
108
|
-
GSD_WORKFLOW_PROJECT_ROOT: resolve(workflowProjectRoot),
|
|
109
|
-
};
|
|
174
|
+
const launchEnv = buildWorkflowLaunchEnv(resolve(workflowProjectRoot), gsdCliPath, explicitEnv);
|
|
110
175
|
return {
|
|
111
176
|
name,
|
|
112
177
|
command: explicitCommand,
|
|
@@ -123,11 +188,7 @@ export function detectWorkflowMcpLaunchConfig(
|
|
|
123
188
|
command: process.execPath,
|
|
124
189
|
args: [distCli],
|
|
125
190
|
cwd: resolvedWorkflowProjectRoot,
|
|
126
|
-
env:
|
|
127
|
-
...(gsdCliPath ? { GSD_CLI_PATH: gsdCliPath } : {}),
|
|
128
|
-
GSD_PERSIST_WRITE_GATE_STATE: "1",
|
|
129
|
-
GSD_WORKFLOW_PROJECT_ROOT: resolvedWorkflowProjectRoot,
|
|
130
|
-
},
|
|
191
|
+
env: buildWorkflowLaunchEnv(resolvedWorkflowProjectRoot, gsdCliPath),
|
|
131
192
|
};
|
|
132
193
|
}
|
|
133
194
|
|
|
@@ -138,11 +199,7 @@ export function detectWorkflowMcpLaunchConfig(
|
|
|
138
199
|
command: process.execPath,
|
|
139
200
|
args: [bundledCli],
|
|
140
201
|
cwd: resolvedWorkflowProjectRoot,
|
|
141
|
-
env:
|
|
142
|
-
...(gsdCliPath ? { GSD_CLI_PATH: gsdCliPath } : {}),
|
|
143
|
-
GSD_PERSIST_WRITE_GATE_STATE: "1",
|
|
144
|
-
GSD_WORKFLOW_PROJECT_ROOT: resolvedWorkflowProjectRoot,
|
|
145
|
-
},
|
|
202
|
+
env: buildWorkflowLaunchEnv(resolvedWorkflowProjectRoot, gsdCliPath),
|
|
146
203
|
};
|
|
147
204
|
}
|
|
148
205
|
|
|
@@ -151,11 +208,7 @@ export function detectWorkflowMcpLaunchConfig(
|
|
|
151
208
|
return {
|
|
152
209
|
name,
|
|
153
210
|
command: binPath,
|
|
154
|
-
env:
|
|
155
|
-
...(gsdCliPath ? { GSD_CLI_PATH: gsdCliPath } : {}),
|
|
156
|
-
GSD_PERSIST_WRITE_GATE_STATE: "1",
|
|
157
|
-
GSD_WORKFLOW_PROJECT_ROOT: resolvedWorkflowProjectRoot,
|
|
158
|
-
},
|
|
211
|
+
env: buildWorkflowLaunchEnv(resolvedWorkflowProjectRoot, gsdCliPath),
|
|
159
212
|
};
|
|
160
213
|
}
|
|
161
214
|
|