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
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
4
|
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
5
|
-
|
|
6
|
-
import {
|
|
5
|
+
import { ensureDbOpen } from "./dynamic-tools.js";
|
|
6
|
+
import { executeMilestoneStatus } from "../tools/workflow-tool-executors.js";
|
|
7
7
|
|
|
8
8
|
export function registerQueryTools(pi: ExtensionAPI): void {
|
|
9
9
|
pi.registerTool({
|
|
@@ -21,79 +21,14 @@ export function registerQueryTools(pi: ExtensionAPI): void {
|
|
|
21
21
|
milestoneId: Type.String({ description: "Milestone ID to query (e.g. M001)" }),
|
|
22
22
|
}),
|
|
23
23
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// ensureDbOpen() only creates/migrates when .gsd/ has content (#3644).
|
|
27
|
-
const { ensureDbOpen } = await import("./dynamic-tools.js");
|
|
28
|
-
const dbAvailable = await ensureDbOpen();
|
|
29
|
-
const {
|
|
30
|
-
getMilestone,
|
|
31
|
-
getSliceStatusSummary,
|
|
32
|
-
getSliceTaskCounts,
|
|
33
|
-
_getAdapter,
|
|
34
|
-
} = await import("../gsd-db.js");
|
|
35
|
-
|
|
36
|
-
if (!dbAvailable) {
|
|
37
|
-
return {
|
|
38
|
-
content: [{ type: "text" as const, text: "Error: GSD database is not available." }],
|
|
39
|
-
details: { operation: "milestone_status", error: "db_unavailable" } as any,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Wrap all reads in a single transaction for snapshot consistency.
|
|
44
|
-
// SQLite WAL mode guarantees reads within a transaction see a single
|
|
45
|
-
// consistent snapshot, preventing torn reads from concurrent writes.
|
|
46
|
-
const adapter = _getAdapter()!;
|
|
47
|
-
adapter.exec("BEGIN"); // eslint-disable-line -- SQLite exec, not child_process
|
|
48
|
-
try {
|
|
49
|
-
const milestone = getMilestone(params.milestoneId);
|
|
50
|
-
if (!milestone) {
|
|
51
|
-
adapter.exec("COMMIT"); // eslint-disable-line
|
|
52
|
-
return {
|
|
53
|
-
content: [{ type: "text" as const, text: `Milestone ${params.milestoneId} not found in database.` }],
|
|
54
|
-
details: { operation: "milestone_status", milestoneId: params.milestoneId, found: false } as any,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const sliceStatuses = getSliceStatusSummary(params.milestoneId);
|
|
59
|
-
|
|
60
|
-
const slices = sliceStatuses.map((s) => {
|
|
61
|
-
const counts = getSliceTaskCounts(params.milestoneId, s.id);
|
|
62
|
-
return {
|
|
63
|
-
id: s.id,
|
|
64
|
-
status: s.status,
|
|
65
|
-
taskCounts: counts,
|
|
66
|
-
};
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
adapter.exec("COMMIT"); // eslint-disable-line
|
|
70
|
-
|
|
71
|
-
const result = {
|
|
72
|
-
milestoneId: milestone.id,
|
|
73
|
-
title: milestone.title,
|
|
74
|
-
status: milestone.status,
|
|
75
|
-
createdAt: milestone.created_at,
|
|
76
|
-
completedAt: milestone.completed_at,
|
|
77
|
-
sliceCount: slices.length,
|
|
78
|
-
slices,
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
|
|
83
|
-
details: { operation: "milestone_status", milestoneId: milestone.id, sliceCount: slices.length } as any,
|
|
84
|
-
};
|
|
85
|
-
} catch (txErr) {
|
|
86
|
-
try { adapter.exec("ROLLBACK"); } catch { /* swallow */ } // eslint-disable-line
|
|
87
|
-
throw txErr;
|
|
88
|
-
}
|
|
89
|
-
} catch (err) {
|
|
90
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
91
|
-
logWarning("tool", `gsd_milestone_status tool failed: ${msg}`);
|
|
24
|
+
const dbAvailable = await ensureDbOpen();
|
|
25
|
+
if (!dbAvailable) {
|
|
92
26
|
return {
|
|
93
|
-
content: [{ type: "text"
|
|
94
|
-
details: { operation: "milestone_status", error:
|
|
27
|
+
content: [{ type: "text", text: "Error: GSD database is not available. Cannot read milestone status." }],
|
|
28
|
+
details: { operation: "milestone_status", error: "db_unavailable" },
|
|
95
29
|
};
|
|
96
30
|
}
|
|
31
|
+
return executeMilestoneStatus(params);
|
|
97
32
|
},
|
|
98
33
|
});
|
|
99
34
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
1
4
|
const MILESTONE_CONTEXT_RE = /M\d+(?:-[a-z0-9]{6})?-CONTEXT\.md$/;
|
|
2
5
|
const CONTEXT_MILESTONE_RE = /(?:^|[/\\])(M\d+(?:-[a-z0-9]{6})?)-CONTEXT\.md$/i;
|
|
3
6
|
const DEPTH_VERIFICATION_MILESTONE_RE = /depth_verification[_-](M\d+(?:-[a-z0-9]{6})?)/i;
|
|
@@ -65,6 +68,69 @@ const GATE_SAFE_TOOLS = new Set([
|
|
|
65
68
|
"search_and_read",
|
|
66
69
|
]);
|
|
67
70
|
|
|
71
|
+
export interface WriteGateSnapshot {
|
|
72
|
+
verifiedDepthMilestones: string[];
|
|
73
|
+
activeQueuePhase: boolean;
|
|
74
|
+
pendingGateId: string | null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function shouldPersistWriteGateSnapshot(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
78
|
+
return env.GSD_PERSIST_WRITE_GATE_STATE === "1";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function writeGateSnapshotPath(basePath: string = process.cwd()): string {
|
|
82
|
+
return join(basePath, ".gsd", "runtime", "write-gate-state.json");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function currentWriteGateSnapshot(): WriteGateSnapshot {
|
|
86
|
+
return {
|
|
87
|
+
verifiedDepthMilestones: [...verifiedDepthMilestones].sort(),
|
|
88
|
+
activeQueuePhase,
|
|
89
|
+
pendingGateId,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function persistWriteGateSnapshot(basePath: string = process.cwd()): void {
|
|
94
|
+
if (!shouldPersistWriteGateSnapshot()) return;
|
|
95
|
+
const path = writeGateSnapshotPath(basePath);
|
|
96
|
+
mkdirSync(join(basePath, ".gsd", "runtime"), { recursive: true });
|
|
97
|
+
const tempPath = `${path}.tmp`;
|
|
98
|
+
writeFileSync(tempPath, JSON.stringify(currentWriteGateSnapshot(), null, 2), "utf-8");
|
|
99
|
+
renameSync(tempPath, path);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function clearPersistedWriteGateSnapshot(basePath: string = process.cwd()): void {
|
|
103
|
+
if (!shouldPersistWriteGateSnapshot()) return;
|
|
104
|
+
const path = writeGateSnapshotPath(basePath);
|
|
105
|
+
try {
|
|
106
|
+
unlinkSync(path);
|
|
107
|
+
} catch {
|
|
108
|
+
// swallow
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function normalizeWriteGateSnapshot(value: unknown): WriteGateSnapshot {
|
|
113
|
+
const record = value && typeof value === "object" ? value as Record<string, unknown> : {};
|
|
114
|
+
const verified = Array.isArray(record.verifiedDepthMilestones)
|
|
115
|
+
? record.verifiedDepthMilestones.filter((item): item is string => typeof item === "string")
|
|
116
|
+
: [];
|
|
117
|
+
return {
|
|
118
|
+
verifiedDepthMilestones: [...new Set(verified)].sort(),
|
|
119
|
+
activeQueuePhase: record.activeQueuePhase === true,
|
|
120
|
+
pendingGateId: typeof record.pendingGateId === "string" ? record.pendingGateId : null,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function loadWriteGateSnapshot(basePath: string = process.cwd()): WriteGateSnapshot {
|
|
125
|
+
const path = writeGateSnapshotPath(basePath);
|
|
126
|
+
if (!existsSync(path)) return currentWriteGateSnapshot();
|
|
127
|
+
try {
|
|
128
|
+
return normalizeWriteGateSnapshot(JSON.parse(readFileSync(path, "utf-8")));
|
|
129
|
+
} catch {
|
|
130
|
+
return currentWriteGateSnapshot();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
68
134
|
export function isDepthVerified(): boolean {
|
|
69
135
|
return verifiedDepthMilestones.size > 0;
|
|
70
136
|
}
|
|
@@ -77,28 +143,40 @@ export function isMilestoneDepthVerified(milestoneId: string | null | undefined)
|
|
|
77
143
|
return verifiedDepthMilestones.has(milestoneId);
|
|
78
144
|
}
|
|
79
145
|
|
|
146
|
+
export function isMilestoneDepthVerifiedInSnapshot(
|
|
147
|
+
snapshot: WriteGateSnapshot,
|
|
148
|
+
milestoneId: string | null | undefined,
|
|
149
|
+
): boolean {
|
|
150
|
+
if (!milestoneId) return false;
|
|
151
|
+
return snapshot.verifiedDepthMilestones.includes(milestoneId);
|
|
152
|
+
}
|
|
153
|
+
|
|
80
154
|
export function isQueuePhaseActive(): boolean {
|
|
81
155
|
return activeQueuePhase;
|
|
82
156
|
}
|
|
83
157
|
|
|
84
158
|
export function setQueuePhaseActive(active: boolean): void {
|
|
85
159
|
activeQueuePhase = active;
|
|
160
|
+
persistWriteGateSnapshot();
|
|
86
161
|
}
|
|
87
162
|
|
|
88
163
|
export function resetWriteGateState(): void {
|
|
89
164
|
verifiedDepthMilestones.clear();
|
|
90
165
|
pendingGateId = null;
|
|
166
|
+
persistWriteGateSnapshot();
|
|
91
167
|
}
|
|
92
168
|
|
|
93
169
|
export function clearDiscussionFlowState(): void {
|
|
94
170
|
verifiedDepthMilestones.clear();
|
|
95
171
|
activeQueuePhase = false;
|
|
96
172
|
pendingGateId = null;
|
|
173
|
+
clearPersistedWriteGateSnapshot();
|
|
97
174
|
}
|
|
98
175
|
|
|
99
|
-
export function markDepthVerified(milestoneId?: string | null): void {
|
|
176
|
+
export function markDepthVerified(milestoneId?: string | null, basePath: string = process.cwd()): void {
|
|
100
177
|
if (!milestoneId) return;
|
|
101
178
|
verifiedDepthMilestones.add(milestoneId);
|
|
179
|
+
persistWriteGateSnapshot(basePath);
|
|
102
180
|
}
|
|
103
181
|
|
|
104
182
|
/**
|
|
@@ -130,6 +208,7 @@ function extractContextMilestoneId(inputPath: string): string | null {
|
|
|
130
208
|
*/
|
|
131
209
|
export function setPendingGate(gateId: string): void {
|
|
132
210
|
pendingGateId = gateId;
|
|
211
|
+
persistWriteGateSnapshot();
|
|
133
212
|
}
|
|
134
213
|
|
|
135
214
|
/**
|
|
@@ -137,6 +216,7 @@ export function setPendingGate(gateId: string): void {
|
|
|
137
216
|
*/
|
|
138
217
|
export function clearPendingGate(): void {
|
|
139
218
|
pendingGateId = null;
|
|
219
|
+
persistWriteGateSnapshot();
|
|
140
220
|
}
|
|
141
221
|
|
|
142
222
|
/**
|
|
@@ -154,11 +234,20 @@ export function getPendingGate(): string | null {
|
|
|
154
234
|
* Read-only tools and ask_user_questions itself are always allowed.
|
|
155
235
|
*/
|
|
156
236
|
export function shouldBlockPendingGate(
|
|
237
|
+
toolName: string,
|
|
238
|
+
milestoneId: string | null,
|
|
239
|
+
queuePhaseActive?: boolean,
|
|
240
|
+
): { block: boolean; reason?: string } {
|
|
241
|
+
return shouldBlockPendingGateInSnapshot(currentWriteGateSnapshot(), toolName, milestoneId, queuePhaseActive);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function shouldBlockPendingGateInSnapshot(
|
|
245
|
+
snapshot: WriteGateSnapshot,
|
|
157
246
|
toolName: string,
|
|
158
247
|
_milestoneId: string | null,
|
|
159
248
|
_queuePhaseActive?: boolean,
|
|
160
249
|
): { block: boolean; reason?: string } {
|
|
161
|
-
if (!pendingGateId) return { block: false };
|
|
250
|
+
if (!snapshot.pendingGateId) return { block: false };
|
|
162
251
|
|
|
163
252
|
if (GATE_SAFE_TOOLS.has(toolName)) return { block: false };
|
|
164
253
|
|
|
@@ -168,7 +257,7 @@ export function shouldBlockPendingGate(
|
|
|
168
257
|
return {
|
|
169
258
|
block: true,
|
|
170
259
|
reason: [
|
|
171
|
-
`HARD BLOCK: Discussion gate "${pendingGateId}" has not been confirmed by the user.`,
|
|
260
|
+
`HARD BLOCK: Discussion gate "${snapshot.pendingGateId}" has not been confirmed by the user.`,
|
|
172
261
|
`You MUST re-call ask_user_questions with the gate question before making any other tool calls.`,
|
|
173
262
|
`If the previous ask_user_questions call failed, errored, was cancelled, or the user's response`,
|
|
174
263
|
`did not match a provided option, you MUST re-ask — never rationalize past the block.`,
|
|
@@ -182,11 +271,20 @@ export function shouldBlockPendingGate(
|
|
|
182
271
|
* Read-only bash commands are allowed; mutating commands are blocked.
|
|
183
272
|
*/
|
|
184
273
|
export function shouldBlockPendingGateBash(
|
|
274
|
+
command: string,
|
|
275
|
+
milestoneId: string | null,
|
|
276
|
+
queuePhaseActive?: boolean,
|
|
277
|
+
): { block: boolean; reason?: string } {
|
|
278
|
+
return shouldBlockPendingGateBashInSnapshot(currentWriteGateSnapshot(), command, milestoneId, queuePhaseActive);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function shouldBlockPendingGateBashInSnapshot(
|
|
282
|
+
snapshot: WriteGateSnapshot,
|
|
185
283
|
command: string,
|
|
186
284
|
_milestoneId: string | null,
|
|
187
285
|
_queuePhaseActive?: boolean,
|
|
188
286
|
): { block: boolean; reason?: string } {
|
|
189
|
-
if (!pendingGateId) return { block: false };
|
|
287
|
+
if (!snapshot.pendingGateId) return { block: false };
|
|
190
288
|
|
|
191
289
|
// Allow read-only bash commands
|
|
192
290
|
if (BASH_READ_ONLY_RE.test(command)) return { block: false };
|
|
@@ -194,7 +292,7 @@ export function shouldBlockPendingGateBash(
|
|
|
194
292
|
return {
|
|
195
293
|
block: true,
|
|
196
294
|
reason: [
|
|
197
|
-
`HARD BLOCK: Discussion gate "${pendingGateId}" has not been confirmed by the user.`,
|
|
295
|
+
`HARD BLOCK: Discussion gate "${snapshot.pendingGateId}" has not been confirmed by the user.`,
|
|
198
296
|
`You MUST re-call ask_user_questions with the gate question before running mutating commands.`,
|
|
199
297
|
`If the previous ask_user_questions call failed, errored, was cancelled, or the user's response`,
|
|
200
298
|
`did not match a provided option, you MUST re-ask — never rationalize past the block.`,
|
|
@@ -275,6 +373,15 @@ export function shouldBlockContextArtifactSave(
|
|
|
275
373
|
artifactType: string,
|
|
276
374
|
milestoneId: string | null,
|
|
277
375
|
sliceId?: string | null,
|
|
376
|
+
): { block: boolean; reason?: string } {
|
|
377
|
+
return shouldBlockContextArtifactSaveInSnapshot(currentWriteGateSnapshot(), artifactType, milestoneId, sliceId);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function shouldBlockContextArtifactSaveInSnapshot(
|
|
381
|
+
snapshot: WriteGateSnapshot,
|
|
382
|
+
artifactType: string,
|
|
383
|
+
milestoneId: string | null,
|
|
384
|
+
sliceId?: string | null,
|
|
278
385
|
): { block: boolean; reason?: string } {
|
|
279
386
|
if (artifactType !== "CONTEXT") return { block: false };
|
|
280
387
|
if (sliceId) return { block: false };
|
|
@@ -287,7 +394,7 @@ export function shouldBlockContextArtifactSave(
|
|
|
287
394
|
].join(" "),
|
|
288
395
|
};
|
|
289
396
|
}
|
|
290
|
-
if (
|
|
397
|
+
if (isMilestoneDepthVerifiedInSnapshot(snapshot, milestoneId)) return { block: false };
|
|
291
398
|
|
|
292
399
|
return {
|
|
293
400
|
block: true,
|
|
@@ -317,6 +424,15 @@ export function shouldBlockQueueExecution(
|
|
|
317
424
|
toolName: string,
|
|
318
425
|
input: string,
|
|
319
426
|
queuePhaseActive: boolean,
|
|
427
|
+
): { block: boolean; reason?: string } {
|
|
428
|
+
return shouldBlockQueueExecutionInSnapshot(currentWriteGateSnapshot(), toolName, input, queuePhaseActive);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export function shouldBlockQueueExecutionInSnapshot(
|
|
432
|
+
snapshot: WriteGateSnapshot,
|
|
433
|
+
toolName: string,
|
|
434
|
+
input: string,
|
|
435
|
+
queuePhaseActive: boolean = snapshot.activeQueuePhase,
|
|
320
436
|
): { block: boolean; reason?: string } {
|
|
321
437
|
if (!queuePhaseActive) return { block: false };
|
|
322
438
|
|
|
@@ -70,7 +70,7 @@ export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
|
|
|
70
70
|
{ cmd: "templates", desc: "List available workflow templates" },
|
|
71
71
|
{ cmd: "extensions", desc: "Manage extensions (list, enable, disable, info)" },
|
|
72
72
|
{ cmd: "fast", desc: "Toggle OpenAI service tier (on/off/flex/status)" },
|
|
73
|
-
{ cmd: "mcp", desc: "MCP server status and
|
|
73
|
+
{ cmd: "mcp", desc: "MCP server status, connectivity, and local config bootstrap (status, check, init)" },
|
|
74
74
|
{ cmd: "rethink", desc: "Conversational project reorganization — reorder, park, discard, add milestones" },
|
|
75
75
|
{ cmd: "workflow", desc: "Custom workflow lifecycle (new, run, list, validate, pause, resume)" },
|
|
76
76
|
{ cmd: "codebase", desc: "Generate, refresh, and inspect the codebase map cache (.gsd/CODEBASE.md)" },
|
|
@@ -201,6 +201,7 @@ const NESTED_COMPLETIONS: CompletionMap = {
|
|
|
201
201
|
mcp: [
|
|
202
202
|
{ cmd: "status", desc: "Show all MCP server statuses (default)" },
|
|
203
203
|
{ cmd: "check", desc: "Detailed status for a specific server" },
|
|
204
|
+
{ cmd: "init", desc: "Write .mcp.json for the local GSD workflow MCP server" },
|
|
204
205
|
],
|
|
205
206
|
doctor: [
|
|
206
207
|
{ cmd: "fix", desc: "Auto-fix detected issues" },
|
|
@@ -60,7 +60,7 @@ export function showHelp(ctx: ExtensionCommandContext): void {
|
|
|
60
60
|
" /gsd hooks Show post-unit hook configuration",
|
|
61
61
|
" /gsd extensions Manage extensions [list|enable|disable|info]",
|
|
62
62
|
" /gsd fast Toggle OpenAI service tier [on|off|flex|status]",
|
|
63
|
-
" /gsd mcp MCP server status and connectivity [status|check <server
|
|
63
|
+
" /gsd mcp MCP server status and connectivity [status|check <server>|init [dir]]",
|
|
64
64
|
"",
|
|
65
65
|
"MAINTENANCE",
|
|
66
66
|
" /gsd doctor Diagnose and repair .gsd/ state [audit|fix|heal] [scope]",
|
|
@@ -194,6 +194,56 @@ function sortModelsForSelection(models: Model<any>[], currentModel: Model<any> |
|
|
|
194
194
|
});
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
function buildProviderModelGroups(
|
|
198
|
+
models: Model<any>[],
|
|
199
|
+
currentModel: Model<any> | undefined,
|
|
200
|
+
): Map<string, Model<any>[]> {
|
|
201
|
+
const byProvider = new Map<string, Model<any>[]>();
|
|
202
|
+
|
|
203
|
+
for (const model of sortModelsForSelection(models, currentModel)) {
|
|
204
|
+
let group = byProvider.get(model.provider);
|
|
205
|
+
if (!group) {
|
|
206
|
+
group = [];
|
|
207
|
+
byProvider.set(model.provider, group);
|
|
208
|
+
}
|
|
209
|
+
group.push(model);
|
|
210
|
+
}
|
|
211
|
+
return byProvider;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function selectModelByProvider(
|
|
215
|
+
title: string,
|
|
216
|
+
models: Model<any>[],
|
|
217
|
+
ctx: ExtensionCommandContext,
|
|
218
|
+
currentModel: Model<any> | undefined,
|
|
219
|
+
): Promise<Model<any> | undefined> {
|
|
220
|
+
const byProvider = buildProviderModelGroups(models, currentModel);
|
|
221
|
+
const providerOptions = Array.from(byProvider.entries()).map(([provider, group]) =>
|
|
222
|
+
`${provider} (${group.length} model${group.length === 1 ? "" : "s"})`,
|
|
223
|
+
);
|
|
224
|
+
providerOptions.push("(cancel)");
|
|
225
|
+
|
|
226
|
+
const providerChoice = await ctx.ui.select(`${title} — choose provider:`, providerOptions);
|
|
227
|
+
if (!providerChoice || typeof providerChoice !== "string" || providerChoice === "(cancel)") return undefined;
|
|
228
|
+
|
|
229
|
+
const providerName = providerChoice.replace(/ \(\d+ models?\)$/, "");
|
|
230
|
+
const providerModels = byProvider.get(providerName);
|
|
231
|
+
if (!providerModels || providerModels.length === 0) return undefined;
|
|
232
|
+
|
|
233
|
+
const optionToModel = new Map<string, Model<any>>();
|
|
234
|
+
const modelOptions = providerModels.map((model) => {
|
|
235
|
+
const isCurrent = currentModel && model.provider === currentModel.provider && model.id === currentModel.id;
|
|
236
|
+
const label = `${isCurrent ? "* " : ""}${model.id}`;
|
|
237
|
+
optionToModel.set(label, model);
|
|
238
|
+
return label;
|
|
239
|
+
});
|
|
240
|
+
modelOptions.push("(cancel)");
|
|
241
|
+
|
|
242
|
+
const modelChoice = await ctx.ui.select(`${title} — ${providerName}:`, modelOptions);
|
|
243
|
+
if (!modelChoice || typeof modelChoice !== "string" || modelChoice === "(cancel)") return undefined;
|
|
244
|
+
return optionToModel.get(modelChoice);
|
|
245
|
+
}
|
|
246
|
+
|
|
197
247
|
async function resolveRequestedModel(
|
|
198
248
|
query: string,
|
|
199
249
|
ctx: ExtensionCommandContext,
|
|
@@ -211,19 +261,7 @@ async function resolveRequestedModel(
|
|
|
211
261
|
|
|
212
262
|
if (partialMatches.length === 1) return partialMatches[0];
|
|
213
263
|
if (partialMatches.length === 0 || !ctx.hasUI) return undefined;
|
|
214
|
-
|
|
215
|
-
const sorted = sortModelsForSelection(partialMatches, ctx.model);
|
|
216
|
-
const optionToModel = new Map<string, Model<any>>();
|
|
217
|
-
const options = sorted.map((model) => {
|
|
218
|
-
const label = `${model.provider}/${model.id}`;
|
|
219
|
-
optionToModel.set(label, model);
|
|
220
|
-
return label;
|
|
221
|
-
});
|
|
222
|
-
options.push("(cancel)");
|
|
223
|
-
|
|
224
|
-
const choice = await ctx.ui.select(`Multiple models match "${query}" — choose one:`, options);
|
|
225
|
-
if (!choice || typeof choice !== "string" || choice === "(cancel)") return undefined;
|
|
226
|
-
return optionToModel.get(choice);
|
|
264
|
+
return selectModelByProvider(`Multiple models match "${query}"`, partialMatches, ctx, ctx.model);
|
|
227
265
|
}
|
|
228
266
|
|
|
229
267
|
async function handleModel(trimmedArgs: string, ctx: ExtensionCommandContext, pi: ExtensionAPI | undefined): Promise<void> {
|
|
@@ -247,18 +285,7 @@ async function handleModel(trimmedArgs: string, ctx: ExtensionCommandContext, pi
|
|
|
247
285
|
return;
|
|
248
286
|
}
|
|
249
287
|
|
|
250
|
-
|
|
251
|
-
const options = sortModelsForSelection(availableModels, ctx.model).map((model) => {
|
|
252
|
-
const isCurrent = ctx.model && model.provider === ctx.model.provider && model.id === ctx.model.id;
|
|
253
|
-
const label = `${isCurrent ? "* " : ""}${model.provider}/${model.id}`;
|
|
254
|
-
optionToModel.set(label, model);
|
|
255
|
-
return label;
|
|
256
|
-
});
|
|
257
|
-
options.push("(cancel)");
|
|
258
|
-
|
|
259
|
-
const choice = await ctx.ui.select("Select session model:", options);
|
|
260
|
-
if (!choice || typeof choice !== "string" || choice === "(cancel)") return;
|
|
261
|
-
targetModel = optionToModel.get(choice);
|
|
288
|
+
targetModel = await selectModelByProvider("Select session model:", availableModels, ctx, ctx.model);
|
|
262
289
|
} else {
|
|
263
290
|
targetModel = await resolveRequestedModel(trimmed, ctx);
|
|
264
291
|
}
|
|
@@ -8,7 +8,13 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
8
8
|
getArgumentCompletions: getGsdArgumentCompletions,
|
|
9
9
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
10
10
|
const { handleGSDCommand } = await import("./dispatcher.js");
|
|
11
|
-
await
|
|
11
|
+
const { setStderrLoggingEnabled } = await import("../workflow-logger.js");
|
|
12
|
+
const previousStderrSetting = setStderrLoggingEnabled(false);
|
|
13
|
+
try {
|
|
14
|
+
await handleGSDCommand(args, ctx, pi);
|
|
15
|
+
} finally {
|
|
16
|
+
setStderrLoggingEnabled(previousStderrSetting);
|
|
17
|
+
}
|
|
12
18
|
},
|
|
13
19
|
});
|
|
14
20
|
}
|
|
@@ -7,12 +7,15 @@
|
|
|
7
7
|
* /gsd mcp — Overview of all servers (alias: /gsd mcp status)
|
|
8
8
|
* /gsd mcp status — Same as bare /gsd mcp
|
|
9
9
|
* /gsd mcp check <srv> — Detailed status for a specific server
|
|
10
|
+
* /gsd mcp init [dir] — Write project-local GSD workflow MCP config
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
13
14
|
|
|
14
15
|
import { existsSync, readFileSync } from "node:fs";
|
|
15
|
-
import { join } from "node:path";
|
|
16
|
+
import { join, resolve } from "node:path";
|
|
17
|
+
|
|
18
|
+
import { ensureProjectWorkflowMcpConfig } from "./mcp-project-config.js";
|
|
16
19
|
|
|
17
20
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
18
21
|
|
|
@@ -28,6 +31,28 @@ export interface McpServerDetail extends McpServerStatus {
|
|
|
28
31
|
tools: string[];
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
export function formatMcpInitResult(
|
|
35
|
+
status: "created" | "updated" | "unchanged",
|
|
36
|
+
configPath: string,
|
|
37
|
+
targetPath: string,
|
|
38
|
+
): string {
|
|
39
|
+
const summary =
|
|
40
|
+
status === "created"
|
|
41
|
+
? "Created project MCP config."
|
|
42
|
+
: status === "updated"
|
|
43
|
+
? "Updated project MCP config."
|
|
44
|
+
: "Project MCP config is already up to date.";
|
|
45
|
+
|
|
46
|
+
return [
|
|
47
|
+
summary,
|
|
48
|
+
"",
|
|
49
|
+
`Project: ${targetPath}`,
|
|
50
|
+
`Config: ${configPath}`,
|
|
51
|
+
"",
|
|
52
|
+
"Claude Code can now load the GSD workflow MCP server from this folder.",
|
|
53
|
+
].join("\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
31
56
|
// ─── Config reader (standalone — does not import mcp-client internals) ──────
|
|
32
57
|
|
|
33
58
|
interface McpServerRawConfig {
|
|
@@ -94,6 +119,7 @@ export function formatMcpStatusReport(servers: McpServerStatus[]): string {
|
|
|
94
119
|
"No MCP servers configured.",
|
|
95
120
|
"",
|
|
96
121
|
"Add servers to .mcp.json or .gsd/mcp.json to enable MCP integrations.",
|
|
122
|
+
"Tip: run /gsd mcp init . to write the local GSD workflow MCP config.",
|
|
97
123
|
"See: https://modelcontextprotocol.io/quickstart",
|
|
98
124
|
].join("\n");
|
|
99
125
|
}
|
|
@@ -153,12 +179,31 @@ export async function handleMcpStatus(
|
|
|
153
179
|
args: string,
|
|
154
180
|
ctx: ExtensionCommandContext,
|
|
155
181
|
): Promise<void> {
|
|
156
|
-
const trimmed = args.trim()
|
|
182
|
+
const trimmed = args.trim();
|
|
183
|
+
const lowered = trimmed.toLowerCase();
|
|
157
184
|
const configs = readMcpConfigs();
|
|
158
185
|
|
|
186
|
+
// /gsd mcp init [dir]
|
|
187
|
+
if (!lowered || lowered === "status") {
|
|
188
|
+
// handled below
|
|
189
|
+
} else if (lowered === "init" || lowered.startsWith("init ")) {
|
|
190
|
+
const rawPath = trimmed.slice("init".length).trim();
|
|
191
|
+
const targetPath = resolve(rawPath || ".");
|
|
192
|
+
try {
|
|
193
|
+
const result = ensureProjectWorkflowMcpConfig(targetPath);
|
|
194
|
+
ctx.ui.notify(formatMcpInitResult(result.status, result.configPath, targetPath), "info");
|
|
195
|
+
} catch (err) {
|
|
196
|
+
ctx.ui.notify(
|
|
197
|
+
`Failed to prepare MCP config for ${targetPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
198
|
+
"error",
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
159
204
|
// /gsd mcp check <server>
|
|
160
|
-
if (
|
|
161
|
-
const serverName =
|
|
205
|
+
if (lowered.startsWith("check ")) {
|
|
206
|
+
const serverName = trimmed.slice("check ".length).trim();
|
|
162
207
|
const config = configs.find((c) => c.name === serverName);
|
|
163
208
|
if (!config) {
|
|
164
209
|
const available = configs.map((c) => c.name).join(", ") || "(none)";
|
|
@@ -202,7 +247,7 @@ export async function handleMcpStatus(
|
|
|
202
247
|
}
|
|
203
248
|
|
|
204
249
|
// /gsd mcp or /gsd mcp status
|
|
205
|
-
if (!
|
|
250
|
+
if (!lowered || lowered === "status") {
|
|
206
251
|
// Build status for each server
|
|
207
252
|
const statuses: McpServerStatus[] = [];
|
|
208
253
|
|
|
@@ -239,9 +284,10 @@ export async function handleMcpStatus(
|
|
|
239
284
|
|
|
240
285
|
// Unknown subcommand
|
|
241
286
|
ctx.ui.notify(
|
|
242
|
-
"Usage: /gsd mcp [status|check <server
|
|
287
|
+
"Usage: /gsd mcp [status|check <server>|init [dir]]\n\n" +
|
|
243
288
|
" status Show all MCP server statuses (default)\n" +
|
|
244
|
-
" check <server> Detailed status for a specific server"
|
|
289
|
+
" check <server> Detailed status for a specific server\n" +
|
|
290
|
+
" init [dir] Write .mcp.json for the local GSD workflow MCP server",
|
|
245
291
|
"warning",
|
|
246
292
|
);
|
|
247
293
|
}
|
|
@@ -40,6 +40,10 @@ import { findMilestoneIds, nextMilestoneId, reserveMilestoneId, getReservedMiles
|
|
|
40
40
|
import { parkMilestone, discardMilestone } from "./milestone-actions.js";
|
|
41
41
|
import { selectAndApplyModel } from "./auto-model-selection.js";
|
|
42
42
|
import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
|
|
43
|
+
import {
|
|
44
|
+
getWorkflowTransportSupportError,
|
|
45
|
+
getRequiredWorkflowToolsForGuidedUnit,
|
|
46
|
+
} from "./workflow-mcp.js";
|
|
43
47
|
import {
|
|
44
48
|
runPreparation,
|
|
45
49
|
formatCodebaseBrief,
|
|
@@ -318,6 +322,26 @@ async function dispatchWorkflow(
|
|
|
318
322
|
routing: result.routing,
|
|
319
323
|
});
|
|
320
324
|
}
|
|
325
|
+
|
|
326
|
+
const compatibilityError = getWorkflowTransportSupportError(
|
|
327
|
+
result.appliedModel?.provider ?? ctx.model?.provider,
|
|
328
|
+
getRequiredWorkflowToolsForGuidedUnit(unitType),
|
|
329
|
+
{
|
|
330
|
+
projectRoot: process.cwd(),
|
|
331
|
+
surface: "guided flow",
|
|
332
|
+
unitType,
|
|
333
|
+
authMode: result.appliedModel?.provider
|
|
334
|
+
? ctx.modelRegistry.getProviderAuthMode(result.appliedModel.provider)
|
|
335
|
+
: ctx.model?.provider
|
|
336
|
+
? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
|
|
337
|
+
: undefined,
|
|
338
|
+
baseUrl: result.appliedModel?.baseUrl ?? ctx.model?.baseUrl,
|
|
339
|
+
},
|
|
340
|
+
);
|
|
341
|
+
if (compatibilityError) {
|
|
342
|
+
ctx.ui.notify(compatibilityError, "error");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
321
345
|
}
|
|
322
346
|
|
|
323
347
|
// Scope tools for discuss flows (#2949).
|
|
@@ -235,6 +235,16 @@ export async function showProjectInit(
|
|
|
235
235
|
// ── Step 9: Bootstrap .gsd/ ────────────────────────────────────────────────
|
|
236
236
|
bootstrapGsdDirectory(basePath, prefs, signals);
|
|
237
237
|
|
|
238
|
+
// Initialize SQLite database so GSD starts in full-capability mode (#3880).
|
|
239
|
+
// Without this, isDbAvailable() returns false and GSD enters degraded
|
|
240
|
+
// markdown-only mode until a tool handler happens to call ensureDbOpen().
|
|
241
|
+
try {
|
|
242
|
+
const { ensureDbOpen } = await import("./bootstrap/dynamic-tools.js");
|
|
243
|
+
await ensureDbOpen(basePath);
|
|
244
|
+
} catch {
|
|
245
|
+
// Non-fatal — DB creation failure should not block project init
|
|
246
|
+
}
|
|
247
|
+
|
|
238
248
|
// Ensure .gitignore
|
|
239
249
|
ensureGitignore(basePath);
|
|
240
250
|
untrackRuntimeFiles(basePath);
|
|
@@ -250,6 +260,35 @@ export async function showProjectInit(
|
|
|
250
260
|
// Non-fatal — codebase map generation failure should never block project init
|
|
251
261
|
}
|
|
252
262
|
|
|
263
|
+
// Write initial STATE.md so it exists before the first /gsd invocation.
|
|
264
|
+
// The explicit /gsd init path (ops.ts) returns without entering showSmartEntry(),
|
|
265
|
+
// which would otherwise generate STATE.md at guided-flow.ts:1358.
|
|
266
|
+
try {
|
|
267
|
+
const { deriveState } = await import("./state.js");
|
|
268
|
+
const { buildStateMarkdown } = await import("./doctor.js");
|
|
269
|
+
const { saveFile } = await import("./files.js");
|
|
270
|
+
const { resolveGsdRootFile } = await import("./paths.js");
|
|
271
|
+
const state = await deriveState(basePath);
|
|
272
|
+
await saveFile(resolveGsdRootFile(basePath, "STATE"), buildStateMarkdown(state));
|
|
273
|
+
} catch {
|
|
274
|
+
// Non-fatal — STATE.md will be regenerated on next /gsd invocation
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (ctx.model?.provider === "claude-code") {
|
|
278
|
+
try {
|
|
279
|
+
const { ensureProjectWorkflowMcpConfig } = await import("./mcp-project-config.js");
|
|
280
|
+
const result = ensureProjectWorkflowMcpConfig(basePath);
|
|
281
|
+
if (result.status !== "unchanged") {
|
|
282
|
+
ctx.ui.notify(`Claude Code MCP prepared at ${result.configPath}`, "info");
|
|
283
|
+
}
|
|
284
|
+
} catch (err) {
|
|
285
|
+
ctx.ui.notify(
|
|
286
|
+
`Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
287
|
+
"warning",
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
253
292
|
ctx.ui.notify("GSD initialized. Starting your first milestone...", "info");
|
|
254
293
|
|
|
255
294
|
return { completed: true, bootstrapped: true };
|
|
@@ -433,6 +472,7 @@ function bootstrapGsdDirectory(
|
|
|
433
472
|
|
|
434
473
|
const gsd = gsdRoot(basePath);
|
|
435
474
|
mkdirSync(join(gsd, "milestones"), { recursive: true });
|
|
475
|
+
mkdirSync(join(gsd, "runtime"), { recursive: true });
|
|
436
476
|
|
|
437
477
|
// Write PREFERENCES.md from wizard answers
|
|
438
478
|
const preferencesContent = buildPreferencesFile(prefs);
|