@opengsd/gsd-pi 1.1.1-dev.616a1a1 → 1.1.1-dev.74e8dd1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +167 -16
- package/dist/resources/extensions/gsd/auto/phases.js +4 -3
- package/dist/resources/extensions/gsd/auto-dashboard.js +15 -4
- package/dist/resources/extensions/gsd/auto-dispatch.js +39 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +113 -7
- package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +4 -4
- package/dist/resources/extensions/gsd/auto-start.js +94 -15
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +2 -1
- package/dist/resources/extensions/gsd/auto.js +22 -4
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +79 -0
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +43 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +30 -9
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -10
- package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +6 -2
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +7 -3
- package/dist/resources/extensions/gsd/commands-maintenance.js +172 -2
- package/dist/resources/extensions/gsd/commands-mcp-status.js +107 -59
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -1
- package/dist/resources/extensions/gsd/commands-verdict.js +1 -1
- package/dist/resources/extensions/gsd/config-overlay.js +2 -1
- package/dist/resources/extensions/gsd/error-classifier.js +2 -1
- package/dist/resources/extensions/gsd/exec-sandbox.js +2 -0
- package/dist/resources/extensions/gsd/gsd-db.js +37 -4
- package/dist/resources/extensions/gsd/guided-flow.js +1 -1
- package/dist/resources/extensions/gsd/mcp-filter.js +3 -0
- package/dist/resources/extensions/gsd/mcp-project-config.js +67 -8
- package/dist/resources/extensions/gsd/migration-auto-check.js +2 -2
- package/dist/resources/extensions/gsd/prompts/run-uat.md +10 -4
- package/dist/resources/extensions/gsd/prompts/system.md +3 -1
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +3 -0
- package/dist/resources/extensions/gsd/skill-activation.js +20 -3
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +4 -2
- package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +1 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +18 -1
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +6 -0
- package/dist/resources/extensions/gsd/state.js +15 -12
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +120 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +109 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -9
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +2 -2
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +366 -3
- package/dist/resources/extensions/gsd/unit-context-manifest.js +8 -3
- package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +3 -1
- package/dist/resources/extensions/gsd/workflow-mcp.js +5 -1
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -0
- package/dist/resources/extensions/mcp-client/manager.js +31 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +4 -4
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- 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 +4 -4
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-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/package.json +2 -2
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +14 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +16 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +72 -31
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +82 -0
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/image-models.generated.d.ts +15 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/image-models.generated.js +15 -0
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +338 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +412 -112
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/terminal.d.ts +1 -0
- package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal.js +8 -4
- package/packages/pi-tui/dist/terminal.js.map +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +196 -16
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +239 -63
- package/src/resources/extensions/gsd/auto/phases.ts +5 -3
- package/src/resources/extensions/gsd/auto-dashboard.ts +16 -4
- package/src/resources/extensions/gsd/auto-dispatch.ts +48 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +138 -7
- package/src/resources/extensions/gsd/auto-prompts.ts +9 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +4 -4
- package/src/resources/extensions/gsd/auto-start.ts +112 -17
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +2 -1
- package/src/resources/extensions/gsd/auto.ts +35 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +86 -0
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +51 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +51 -14
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +21 -10
- package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -2
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +7 -3
- package/src/resources/extensions/gsd/commands-maintenance.ts +197 -2
- package/src/resources/extensions/gsd/commands-mcp-status.ts +134 -57
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -1
- package/src/resources/extensions/gsd/commands-verdict.ts +1 -1
- package/src/resources/extensions/gsd/config-overlay.ts +3 -1
- package/src/resources/extensions/gsd/error-classifier.ts +2 -1
- package/src/resources/extensions/gsd/exec-sandbox.ts +4 -0
- package/src/resources/extensions/gsd/gsd-db.ts +41 -6
- package/src/resources/extensions/gsd/guided-flow.ts +1 -1
- package/src/resources/extensions/gsd/mcp-filter.ts +3 -0
- package/src/resources/extensions/gsd/mcp-project-config.ts +92 -10
- package/src/resources/extensions/gsd/migration-auto-check.ts +2 -2
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +10 -4
- package/src/resources/extensions/gsd/prompts/system.md +3 -1
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +3 -0
- package/src/resources/extensions/gsd/skill-activation.ts +20 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +4 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +1 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +20 -0
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +6 -0
- package/src/resources/extensions/gsd/state-reconciliation/types.ts +1 -0
- package/src/resources/extensions/gsd/state.ts +16 -12
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +86 -0
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +143 -2
- package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +24 -2
- package/src/resources/extensions/gsd/tests/commands-dispatcher-validation-block.test.ts +38 -3
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +50 -13
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/gsd-rebuild.test.ts +199 -0
- package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +13 -6
- package/src/resources/extensions/gsd/tests/mcp-filter.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +177 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +54 -7
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +39 -1
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +84 -10
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +29 -6
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +29 -6
- package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +17 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +25 -0
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +167 -0
- package/src/resources/extensions/gsd/tools/exec-tool.ts +130 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +14 -9
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +2 -2
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +440 -2
- package/src/resources/extensions/gsd/unit-context-manifest.ts +14 -5
- package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +5 -1
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +26 -0
- package/src/resources/extensions/mcp-client/manager.ts +33 -1
- package/src/resources/extensions/mcp-client/tests/manager.test.ts +35 -0
- /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{L9N5SPFi7f-Ne4u2uXzCe → eRWf-RI9bzbrwEurm_3uI}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import {
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
mkdtempSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
|
|
15
|
+
import { handleRebuild } from "../commands-maintenance.ts";
|
|
16
|
+
import {
|
|
17
|
+
closeDatabase,
|
|
18
|
+
getTask,
|
|
19
|
+
insertMilestone,
|
|
20
|
+
insertSlice,
|
|
21
|
+
insertTask,
|
|
22
|
+
openDatabase,
|
|
23
|
+
} from "../gsd-db.ts";
|
|
24
|
+
import { invalidateStateCache } from "../state.ts";
|
|
25
|
+
|
|
26
|
+
type Note = { message: string; kind: string };
|
|
27
|
+
|
|
28
|
+
function makeBase(): string {
|
|
29
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-rebuild-"));
|
|
30
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), {
|
|
31
|
+
recursive: true,
|
|
32
|
+
});
|
|
33
|
+
return base;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function cleanup(base: string): void {
|
|
37
|
+
closeDatabase();
|
|
38
|
+
invalidateStateCache();
|
|
39
|
+
rmSync(base, { recursive: true, force: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function makeCtx(): { ctx: any; notes: Note[] } {
|
|
43
|
+
const notes: Note[] = [];
|
|
44
|
+
return {
|
|
45
|
+
ctx: {
|
|
46
|
+
ui: {
|
|
47
|
+
notify: (message: string, kind: string) => notes.push({ message, kind }),
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
notes,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function seedOpenTask(): void {
|
|
55
|
+
insertMilestone({ id: "M001", title: "Milestone", status: "active" });
|
|
56
|
+
insertSlice({
|
|
57
|
+
id: "S01",
|
|
58
|
+
milestoneId: "M001",
|
|
59
|
+
title: "Slice",
|
|
60
|
+
status: "in_progress",
|
|
61
|
+
risk: "low",
|
|
62
|
+
depends: [],
|
|
63
|
+
});
|
|
64
|
+
insertTask({
|
|
65
|
+
id: "T01",
|
|
66
|
+
sliceId: "S01",
|
|
67
|
+
milestoneId: "M001",
|
|
68
|
+
title: "Task",
|
|
69
|
+
status: "pending",
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function listFiles(dir: string): string[] {
|
|
74
|
+
if (!existsSync(dir)) return [];
|
|
75
|
+
const out: string[] = [];
|
|
76
|
+
const stack = [dir];
|
|
77
|
+
while (stack.length > 0) {
|
|
78
|
+
const current = stack.pop()!;
|
|
79
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
80
|
+
const full = join(current, entry.name);
|
|
81
|
+
if (entry.isDirectory()) {
|
|
82
|
+
stack.push(full);
|
|
83
|
+
} else {
|
|
84
|
+
out.push(full);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return out.sort();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
test("handleRebuild quarantines stale completion projections without mutating DB state", async () => {
|
|
92
|
+
const base = makeBase();
|
|
93
|
+
try {
|
|
94
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
95
|
+
seedOpenTask();
|
|
96
|
+
|
|
97
|
+
const summaryPath = join(
|
|
98
|
+
base,
|
|
99
|
+
".gsd",
|
|
100
|
+
"milestones",
|
|
101
|
+
"M001",
|
|
102
|
+
"slices",
|
|
103
|
+
"S01",
|
|
104
|
+
"tasks",
|
|
105
|
+
"T01-SUMMARY.md",
|
|
106
|
+
);
|
|
107
|
+
writeFileSync(summaryPath, "# T01 Summary\n\nDisk-only completion.\n", "utf-8");
|
|
108
|
+
|
|
109
|
+
const { ctx, notes } = makeCtx();
|
|
110
|
+
await handleRebuild(ctx, base, "markdown");
|
|
111
|
+
|
|
112
|
+
assert.equal(existsSync(summaryPath), false, "stale SUMMARY projection should be moved aside");
|
|
113
|
+
const task = getTask("M001", "S01", "T01");
|
|
114
|
+
assert.equal(task?.status, "pending", "DB task status remains authoritative");
|
|
115
|
+
assert.equal(task?.full_summary_md, "", "disk summary must not be imported into DB");
|
|
116
|
+
|
|
117
|
+
const quarantined = listFiles(join(base, ".gsd", "quarantine", "projections"));
|
|
118
|
+
assert.equal(quarantined.length, 1);
|
|
119
|
+
assert.match(readFileSync(quarantined[0]!, "utf-8"), /Disk-only completion/);
|
|
120
|
+
assert.match(notes.at(-1)?.message ?? "", /Quarantined:\s+1/);
|
|
121
|
+
assert.equal(notes.at(-1)?.kind, "success");
|
|
122
|
+
} finally {
|
|
123
|
+
cleanup(base);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("handleRebuild re-renders missing task summary projections from DB", async () => {
|
|
128
|
+
const base = makeBase();
|
|
129
|
+
try {
|
|
130
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
131
|
+
seedOpenTask();
|
|
132
|
+
insertTask({
|
|
133
|
+
id: "T01",
|
|
134
|
+
sliceId: "S01",
|
|
135
|
+
milestoneId: "M001",
|
|
136
|
+
title: "Task",
|
|
137
|
+
status: "complete",
|
|
138
|
+
oneLiner: "Task complete",
|
|
139
|
+
narrative: "Finished through the DB.",
|
|
140
|
+
verificationResult: "passed",
|
|
141
|
+
fullSummaryMd: "# T01 Summary\n\nRendered from DB.\n",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const summaryPath = join(
|
|
145
|
+
base,
|
|
146
|
+
".gsd",
|
|
147
|
+
"milestones",
|
|
148
|
+
"M001",
|
|
149
|
+
"slices",
|
|
150
|
+
"S01",
|
|
151
|
+
"tasks",
|
|
152
|
+
"T01-SUMMARY.md",
|
|
153
|
+
);
|
|
154
|
+
rmSync(summaryPath, { force: true });
|
|
155
|
+
|
|
156
|
+
const { ctx, notes } = makeCtx();
|
|
157
|
+
await handleRebuild(ctx, base);
|
|
158
|
+
|
|
159
|
+
assert.equal(existsSync(summaryPath), true, "missing SUMMARY projection should be regenerated");
|
|
160
|
+
assert.equal(readFileSync(summaryPath, "utf-8"), "# T01 Summary\n\nRendered from DB.\n");
|
|
161
|
+
assert.match(notes.at(-1)?.message ?? "", /rebuilt markdown projections from the canonical DB/);
|
|
162
|
+
assert.match(notes.at(-1)?.message ?? "", /Quarantined:\s+0/);
|
|
163
|
+
} finally {
|
|
164
|
+
cleanup(base);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("handleRebuild database target is reserved and does not import markdown", async () => {
|
|
169
|
+
const base = makeBase();
|
|
170
|
+
try {
|
|
171
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
172
|
+
seedOpenTask();
|
|
173
|
+
|
|
174
|
+
const summaryPath = join(
|
|
175
|
+
base,
|
|
176
|
+
".gsd",
|
|
177
|
+
"milestones",
|
|
178
|
+
"M001",
|
|
179
|
+
"slices",
|
|
180
|
+
"S01",
|
|
181
|
+
"tasks",
|
|
182
|
+
"T01-SUMMARY.md",
|
|
183
|
+
);
|
|
184
|
+
writeFileSync(summaryPath, "# T01 Summary\n\nShould not import.\n", "utf-8");
|
|
185
|
+
|
|
186
|
+
const { ctx, notes } = makeCtx();
|
|
187
|
+
await handleRebuild(ctx, base, "database");
|
|
188
|
+
|
|
189
|
+
assert.equal(existsSync(summaryPath), true, "reserved DB rebuild must not move projection files");
|
|
190
|
+
const task = getTask("M001", "S01", "T01");
|
|
191
|
+
assert.equal(task?.status, "pending", "reserved DB rebuild must not mutate task status");
|
|
192
|
+
assert.equal(task?.full_summary_md, "", "reserved DB rebuild must not import markdown");
|
|
193
|
+
assert.match(notes.at(-1)?.message ?? "", /reserved/);
|
|
194
|
+
assert.match(notes.at(-1)?.message ?? "", /\/gsd recover --confirm/);
|
|
195
|
+
assert.equal(notes.at(-1)?.kind, "warning");
|
|
196
|
+
} finally {
|
|
197
|
+
cleanup(base);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from '../gsd-db.ts';
|
|
25
25
|
import { migrateHierarchyToDb } from '../md-importer.ts';
|
|
26
26
|
import { deriveStateFromDb, invalidateStateCache } from '../state.ts';
|
|
27
|
+
import { handleRecover } from '../commands-maintenance.ts';
|
|
27
28
|
// ─── Fixture Helpers ───────────────────────────────────────────────────────
|
|
28
29
|
|
|
29
30
|
function createFixtureBase(): string {
|
|
@@ -42,6 +43,22 @@ function cleanup(base: string): void {
|
|
|
42
43
|
rmSync(base, { recursive: true, force: true });
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
function makeCtx(confirm?: () => Promise<boolean>): {
|
|
47
|
+
ctx: any;
|
|
48
|
+
notes: Array<{ message: string; kind: string }>;
|
|
49
|
+
} {
|
|
50
|
+
const notes: Array<{ message: string; kind: string }> = [];
|
|
51
|
+
return {
|
|
52
|
+
ctx: {
|
|
53
|
+
ui: {
|
|
54
|
+
notify: (message: string, kind: string) => notes.push({ message, kind }),
|
|
55
|
+
...(confirm ? { confirm: async () => confirm() } : {}),
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
notes,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
45
62
|
// ─── Fixture Content ──────────────────────────────────────────────────────
|
|
46
63
|
|
|
47
64
|
const ROADMAP_M001 = `# M001: Recovery Test
|
|
@@ -437,4 +454,62 @@ describe('gsd-recover', async () => {
|
|
|
437
454
|
cleanup(base);
|
|
438
455
|
}
|
|
439
456
|
});
|
|
457
|
+
|
|
458
|
+
test('handleRecover warns and does not import markdown without confirmation', async () => {
|
|
459
|
+
const base = createFixtureBase();
|
|
460
|
+
try {
|
|
461
|
+
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_M001);
|
|
462
|
+
openDatabase(':memory:');
|
|
463
|
+
insertMilestone({ id: 'M999', title: 'Existing DB State', status: 'active' });
|
|
464
|
+
|
|
465
|
+
const { ctx, notes } = makeCtx();
|
|
466
|
+
await handleRecover(ctx, base);
|
|
467
|
+
|
|
468
|
+
assert.ok(getMilestone('M999'), 'existing DB row remains when recover is unconfirmed');
|
|
469
|
+
assert.equal(getMilestone('M001'), null, 'markdown milestone is not imported without confirmation');
|
|
470
|
+
assert.equal(notes.at(-1)?.kind, 'warning');
|
|
471
|
+
assert.match(notes.at(-1)?.message ?? '', /\/gsd recover --confirm/);
|
|
472
|
+
} finally {
|
|
473
|
+
closeDatabase();
|
|
474
|
+
cleanup(base);
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test('handleRecover interactive cancellation leaves DB unchanged', async () => {
|
|
479
|
+
const base = createFixtureBase();
|
|
480
|
+
try {
|
|
481
|
+
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_M001);
|
|
482
|
+
openDatabase(':memory:');
|
|
483
|
+
insertMilestone({ id: 'M999', title: 'Existing DB State', status: 'active' });
|
|
484
|
+
|
|
485
|
+
const { ctx, notes } = makeCtx(async () => false);
|
|
486
|
+
await handleRecover(ctx, base);
|
|
487
|
+
|
|
488
|
+
assert.ok(getMilestone('M999'), 'existing DB row remains when recover is cancelled');
|
|
489
|
+
assert.equal(getMilestone('M001'), null, 'markdown milestone is not imported after cancellation');
|
|
490
|
+
assert.match(notes.at(-1)?.message ?? '', /cancelled/);
|
|
491
|
+
} finally {
|
|
492
|
+
closeDatabase();
|
|
493
|
+
cleanup(base);
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
test('handleRecover imports markdown after explicit confirmation', async () => {
|
|
498
|
+
const base = createFixtureBase();
|
|
499
|
+
try {
|
|
500
|
+
writeFile(base, 'milestones/M001/M001-ROADMAP.md', ROADMAP_M001);
|
|
501
|
+
openDatabase(':memory:');
|
|
502
|
+
insertMilestone({ id: 'M999', title: 'Existing DB State', status: 'active' });
|
|
503
|
+
|
|
504
|
+
const { ctx, notes } = makeCtx();
|
|
505
|
+
await handleRecover(ctx, base, '--confirm');
|
|
506
|
+
|
|
507
|
+
assert.equal(getMilestone('M999'), null, 'confirmed recover clears old hierarchy rows');
|
|
508
|
+
assert.ok(getMilestone('M001'), 'confirmed recover imports markdown hierarchy');
|
|
509
|
+
assert.equal(notes.at(-1)?.kind, 'success');
|
|
510
|
+
} finally {
|
|
511
|
+
closeDatabase();
|
|
512
|
+
cleanup(base);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
440
515
|
});
|
package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts
CHANGED
|
@@ -50,6 +50,7 @@ import { handleCompleteSlice } from "../../tools/complete-slice.ts";
|
|
|
50
50
|
import { handleCompleteMilestone } from "../../tools/complete-milestone.ts";
|
|
51
51
|
import { handleReopenTask } from "../../tools/reopen-task.ts";
|
|
52
52
|
import { handleReopenSlice } from "../../tools/reopen-slice.ts";
|
|
53
|
+
import { handleReopenMilestone } from "../../tools/reopen-milestone.ts";
|
|
53
54
|
|
|
54
55
|
// ── State derivation ──────────────────────────────────────────────────────
|
|
55
56
|
import {
|
|
@@ -700,9 +701,7 @@ describe("state-machine-live-validation", () => {
|
|
|
700
701
|
assert.match((result as any).error, /closed milestone/);
|
|
701
702
|
});
|
|
702
703
|
|
|
703
|
-
test("
|
|
704
|
-
// This test documents the H5 finding: there is no handleReopenMilestone function.
|
|
705
|
-
// A completed milestone can only be undone via direct DB manipulation.
|
|
704
|
+
test("closed milestone cannot be reopened by generic DB update", async () => {
|
|
706
705
|
base = createFullFixture();
|
|
707
706
|
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
708
707
|
insertMilestone({ id: "M001", title: "Done", status: "complete" });
|
|
@@ -710,10 +709,18 @@ describe("state-machine-live-validation", () => {
|
|
|
710
709
|
const milestone = getMilestone("M001");
|
|
711
710
|
assert.ok(isClosedStatus(milestone!.status), "milestone is closed");
|
|
712
711
|
|
|
713
|
-
|
|
714
|
-
|
|
712
|
+
assert.throws(
|
|
713
|
+
() => updateMilestoneStatus("M001", "active", null),
|
|
714
|
+
/use gsd_milestone_reopen for an explicit reopen/,
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
const result = await handleReopenMilestone(
|
|
718
|
+
{ milestoneId: "M001", reason: "regression surfaced after closure" },
|
|
719
|
+
base,
|
|
720
|
+
);
|
|
721
|
+
assert.ok(!("error" in result), `unexpected reopen error: ${"error" in result ? result.error : ""}`);
|
|
715
722
|
const reopened = getMilestone("M001");
|
|
716
|
-
assert.equal(reopened!.status, "active", "
|
|
723
|
+
assert.equal(reopened!.status, "active", "explicit reopen handler reopens the milestone");
|
|
717
724
|
});
|
|
718
725
|
});
|
|
719
726
|
|
|
@@ -48,6 +48,21 @@ describe("discoverMcpServerNames", () => {
|
|
|
48
48
|
assert.deepEqual(result.sort(), ["server-a", "server-b", "shared"]);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
it("reads from .claude/settings.local.json for Claude Code project-local servers", () => {
|
|
52
|
+
const dir = mkdtempSync(join(tmpdir(), "mcp-filter-test-"));
|
|
53
|
+
mkdirSync(join(dir, ".claude"), { recursive: true });
|
|
54
|
+
writeFileSync(
|
|
55
|
+
join(dir, ".claude", "settings.local.json"),
|
|
56
|
+
JSON.stringify({ mcpServers: { "local-server": {}, "shared": {} } }),
|
|
57
|
+
);
|
|
58
|
+
writeFileSync(
|
|
59
|
+
join(dir, ".claude", "settings.json"),
|
|
60
|
+
JSON.stringify({ mcpServers: { "project-server": {}, "shared": {} } }),
|
|
61
|
+
);
|
|
62
|
+
const result = discoverMcpServerNames(dir);
|
|
63
|
+
assert.deepEqual(result.sort(), ["local-server", "project-server", "shared"]);
|
|
64
|
+
});
|
|
65
|
+
|
|
51
66
|
it("handles .claude/settings.json missing gracefully", () => {
|
|
52
67
|
const dir = mkdtempSync(join(tmpdir(), "mcp-filter-test-"));
|
|
53
68
|
writeFileSync(
|
|
@@ -5,6 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
+
ensureClaudeCodeMcpJsonServerEnabled,
|
|
8
9
|
ensureProjectWorkflowMcpConfig,
|
|
9
10
|
GSD_BROWSER_MCP_SERVER_NAME,
|
|
10
11
|
GSD_WORKFLOW_MCP_SERVER_NAME,
|
|
@@ -54,6 +55,14 @@ test("ensureProjectWorkflowMcpConfig creates .mcp.json with workflow and browser
|
|
|
54
55
|
]);
|
|
55
56
|
assert.equal(browserArgs[mcpArgIndex + 6], projectRoot);
|
|
56
57
|
assert.equal((browserServer as { cwd?: string })?.cwd, projectRoot);
|
|
58
|
+
|
|
59
|
+
const settings = JSON.parse(readFileSync(join(projectRoot, ".claude", "settings.local.json"), "utf-8")) as {
|
|
60
|
+
enabledMcpjsonServers?: string[];
|
|
61
|
+
};
|
|
62
|
+
assert.deepEqual(settings.enabledMcpjsonServers, [
|
|
63
|
+
GSD_WORKFLOW_MCP_SERVER_NAME,
|
|
64
|
+
GSD_BROWSER_MCP_SERVER_NAME,
|
|
65
|
+
]);
|
|
57
66
|
} finally {
|
|
58
67
|
rmSync(projectRoot, { recursive: true, force: true });
|
|
59
68
|
}
|
|
@@ -115,6 +124,11 @@ test("ensureProjectWorkflowMcpConfig uses custom workflow server name from env",
|
|
|
115
124
|
assert.ok(parsed.mcpServers?.["custom-workflow"]);
|
|
116
125
|
assert.ok(parsed.mcpServers?.[GSD_BROWSER_MCP_SERVER_NAME]);
|
|
117
126
|
assert.equal(parsed.mcpServers?.[GSD_WORKFLOW_MCP_SERVER_NAME], undefined);
|
|
127
|
+
|
|
128
|
+
const settings = JSON.parse(readFileSync(join(projectRoot, ".claude", "settings.local.json"), "utf-8")) as {
|
|
129
|
+
enabledMcpjsonServers?: string[];
|
|
130
|
+
};
|
|
131
|
+
assert.deepEqual(settings.enabledMcpjsonServers, ["custom-workflow", GSD_BROWSER_MCP_SERVER_NAME]);
|
|
118
132
|
} finally {
|
|
119
133
|
rmSync(projectRoot, { recursive: true, force: true });
|
|
120
134
|
}
|
|
@@ -136,6 +150,11 @@ test("ensureProjectWorkflowMcpConfig can disable the default browser MCP server"
|
|
|
136
150
|
};
|
|
137
151
|
assert.ok(parsed.mcpServers?.[GSD_WORKFLOW_MCP_SERVER_NAME]);
|
|
138
152
|
assert.equal(parsed.mcpServers?.[GSD_BROWSER_MCP_SERVER_NAME], undefined);
|
|
153
|
+
|
|
154
|
+
const settings = JSON.parse(readFileSync(join(projectRoot, ".claude", "settings.local.json"), "utf-8")) as {
|
|
155
|
+
enabledMcpjsonServers?: string[];
|
|
156
|
+
};
|
|
157
|
+
assert.deepEqual(settings.enabledMcpjsonServers, [GSD_WORKFLOW_MCP_SERVER_NAME]);
|
|
139
158
|
} finally {
|
|
140
159
|
rmSync(projectRoot, { recursive: true, force: true });
|
|
141
160
|
}
|
|
@@ -156,3 +175,52 @@ test("ensureProjectWorkflowMcpConfig is idempotent when config is already curren
|
|
|
156
175
|
rmSync(projectRoot, { recursive: true, force: true });
|
|
157
176
|
}
|
|
158
177
|
});
|
|
178
|
+
|
|
179
|
+
test("ensureProjectWorkflowMcpConfig updates stale Claude Code MCP approval state", () => {
|
|
180
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-mcp-init-"));
|
|
181
|
+
mkdirSync(join(projectRoot, ".gsd"), { recursive: true });
|
|
182
|
+
const settingsPath = join(projectRoot, ".claude", "settings.local.json");
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const first = ensureProjectWorkflowMcpConfig(projectRoot);
|
|
186
|
+
assert.equal(first.status, "created");
|
|
187
|
+
|
|
188
|
+
writeFileSync(
|
|
189
|
+
settingsPath,
|
|
190
|
+
`${JSON.stringify({
|
|
191
|
+
permissions: { allow: ["Bash(gh issue *)"] },
|
|
192
|
+
enabledMcpjsonServers: [],
|
|
193
|
+
disabledMcpjsonServers: [GSD_WORKFLOW_MCP_SERVER_NAME, GSD_BROWSER_MCP_SERVER_NAME],
|
|
194
|
+
}, null, 2)}\n`,
|
|
195
|
+
"utf-8",
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const second = ensureProjectWorkflowMcpConfig(projectRoot);
|
|
199
|
+
assert.equal(second.status, "updated");
|
|
200
|
+
|
|
201
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8")) as {
|
|
202
|
+
permissions?: { allow?: string[] };
|
|
203
|
+
enabledMcpjsonServers?: string[];
|
|
204
|
+
disabledMcpjsonServers?: string[];
|
|
205
|
+
};
|
|
206
|
+
assert.deepEqual(settings.permissions?.allow, ["Bash(gh issue *)"]);
|
|
207
|
+
assert.deepEqual(settings.enabledMcpjsonServers, [
|
|
208
|
+
GSD_WORKFLOW_MCP_SERVER_NAME,
|
|
209
|
+
GSD_BROWSER_MCP_SERVER_NAME,
|
|
210
|
+
]);
|
|
211
|
+
assert.deepEqual(settings.disabledMcpjsonServers, []);
|
|
212
|
+
} finally {
|
|
213
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("ensureClaudeCodeMcpJsonServerEnabled is idempotent", () => {
|
|
218
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-mcp-init-"));
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
assert.equal(ensureClaudeCodeMcpJsonServerEnabled(projectRoot, "gsd-workflow"), true);
|
|
222
|
+
assert.equal(ensureClaudeCodeMcpJsonServerEnabled(projectRoot, "gsd-workflow"), false);
|
|
223
|
+
} finally {
|
|
224
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
225
|
+
}
|
|
226
|
+
});
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import test, { describe } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
3
9
|
|
|
4
10
|
import {
|
|
11
|
+
formatMcpDiscoveryResult,
|
|
5
12
|
formatMcpInitResult,
|
|
6
13
|
formatMcpConnectionTestResult,
|
|
7
14
|
formatMcpStatusReport,
|
|
8
15
|
formatMcpServerDetail,
|
|
9
16
|
hasHostMcpTool,
|
|
17
|
+
handleMcpStatus,
|
|
10
18
|
type McpServerStatus,
|
|
11
19
|
} from "../commands-mcp-status.ts";
|
|
20
|
+
import { clearMcpConfigCache } from "../../mcp-client/manager.ts";
|
|
12
21
|
|
|
13
22
|
// ─── formatMcpStatusReport ──────────────────────────────────────────────────
|
|
14
23
|
|
|
@@ -49,6 +58,16 @@ describe("formatMcpStatusReport", () => {
|
|
|
49
58
|
assert.match(result, /disabled/i);
|
|
50
59
|
});
|
|
51
60
|
|
|
61
|
+
test("shows available state for servers that pass a status probe", () => {
|
|
62
|
+
const servers: McpServerStatus[] = [
|
|
63
|
+
{ name: "gsd-workflow", transport: "stdio", connected: false, available: true, toolCount: 62, error: undefined },
|
|
64
|
+
];
|
|
65
|
+
const result = formatMcpStatusReport(servers);
|
|
66
|
+
assert.match(result, /gsd-workflow/);
|
|
67
|
+
assert.match(result, /available — 62 tools/);
|
|
68
|
+
assert.doesNotMatch(result, /disconnected/);
|
|
69
|
+
});
|
|
70
|
+
|
|
52
71
|
test("includes server count in header", () => {
|
|
53
72
|
const servers: McpServerStatus[] = [
|
|
54
73
|
{ name: "a", transport: "stdio", connected: true, toolCount: 3, error: undefined },
|
|
@@ -113,6 +132,20 @@ describe("formatMcpServerDetail", () => {
|
|
|
113
132
|
assert.match(result, /disconnected/i);
|
|
114
133
|
});
|
|
115
134
|
|
|
135
|
+
test("shows available status with tool names", () => {
|
|
136
|
+
const result = formatMcpServerDetail({
|
|
137
|
+
name: "gsd-workflow",
|
|
138
|
+
transport: "stdio",
|
|
139
|
+
connected: false,
|
|
140
|
+
available: true,
|
|
141
|
+
toolCount: 1,
|
|
142
|
+
tools: ["gsd_milestone_status"],
|
|
143
|
+
error: undefined,
|
|
144
|
+
});
|
|
145
|
+
assert.match(result, /available/i);
|
|
146
|
+
assert.match(result, /gsd_milestone_status/);
|
|
147
|
+
});
|
|
148
|
+
|
|
116
149
|
test("shows env warnings for server detail", () => {
|
|
117
150
|
const result = formatMcpServerDetail({
|
|
118
151
|
name: "warned",
|
|
@@ -128,6 +161,150 @@ describe("formatMcpServerDetail", () => {
|
|
|
128
161
|
});
|
|
129
162
|
});
|
|
130
163
|
|
|
164
|
+
describe("handleMcpStatus", () => {
|
|
165
|
+
test("probes configured stdio servers before reporting disconnected", async () => {
|
|
166
|
+
const previousGsdHome = process.env.GSD_HOME;
|
|
167
|
+
const originalCwd = process.cwd();
|
|
168
|
+
const projectDir = mkdtempSync(join(tmpdir(), "gsd-mcp-status-project-"));
|
|
169
|
+
const gsdHomeDir = mkdtempSync(join(tmpdir(), "gsd-mcp-status-home-"));
|
|
170
|
+
try {
|
|
171
|
+
process.env.GSD_HOME = gsdHomeDir;
|
|
172
|
+
process.chdir(projectDir);
|
|
173
|
+
mkdirSync(join(projectDir, ".gsd"), { recursive: true });
|
|
174
|
+
|
|
175
|
+
const require = createRequire(import.meta.url);
|
|
176
|
+
const mcpModuleUrl = pathToFileURL(require.resolve("@modelcontextprotocol/sdk/server/mcp.js")).href;
|
|
177
|
+
const stdioModuleUrl = pathToFileURL(require.resolve("@modelcontextprotocol/sdk/server/stdio.js")).href;
|
|
178
|
+
const serverPath = join(projectDir, "fake-mcp-server.mjs");
|
|
179
|
+
writeFileSync(
|
|
180
|
+
serverPath,
|
|
181
|
+
[
|
|
182
|
+
`const { McpServer } = await import(${JSON.stringify(mcpModuleUrl)});`,
|
|
183
|
+
`const { StdioServerTransport } = await import(${JSON.stringify(stdioModuleUrl)});`,
|
|
184
|
+
'const server = new McpServer({ name: "fake", version: "1.0.0" }, { capabilities: { tools: {} } });',
|
|
185
|
+
'server.tool("fake_tool", "Probe-visible tool", {}, async () => ({ content: [{ type: "text", text: "ok" }] }));',
|
|
186
|
+
'await server.connect(new StdioServerTransport());',
|
|
187
|
+
].join("\n"),
|
|
188
|
+
"utf-8",
|
|
189
|
+
);
|
|
190
|
+
writeFileSync(
|
|
191
|
+
join(projectDir, ".mcp.json"),
|
|
192
|
+
JSON.stringify({ mcpServers: { "gsd-workflow": { command: process.execPath, args: [serverPath] } } }),
|
|
193
|
+
"utf-8",
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
let message = "";
|
|
197
|
+
const ctx = {
|
|
198
|
+
getSystemPrompt: () => "",
|
|
199
|
+
ui: {
|
|
200
|
+
notify: (text: string) => {
|
|
201
|
+
message = text;
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
await handleMcpStatus("status", ctx as unknown as ExtensionCommandContext);
|
|
207
|
+
|
|
208
|
+
assert.match(message, /gsd-workflow/);
|
|
209
|
+
assert.match(message, /available — 1 tools/);
|
|
210
|
+
assert.doesNotMatch(message, /disconnected/);
|
|
211
|
+
} finally {
|
|
212
|
+
process.chdir(originalCwd);
|
|
213
|
+
if (previousGsdHome === undefined) delete process.env.GSD_HOME;
|
|
214
|
+
else process.env.GSD_HOME = previousGsdHome;
|
|
215
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
216
|
+
rmSync(gsdHomeDir, { recursive: true, force: true });
|
|
217
|
+
clearMcpConfigCache();
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("discovers the only configured server when no server name is provided", async () => {
|
|
222
|
+
const previousGsdHome = process.env.GSD_HOME;
|
|
223
|
+
const originalCwd = process.cwd();
|
|
224
|
+
const projectDir = mkdtempSync(join(tmpdir(), "gsd-mcp-discover-project-"));
|
|
225
|
+
const gsdHomeDir = mkdtempSync(join(tmpdir(), "gsd-mcp-discover-home-"));
|
|
226
|
+
try {
|
|
227
|
+
process.env.GSD_HOME = gsdHomeDir;
|
|
228
|
+
process.chdir(projectDir);
|
|
229
|
+
|
|
230
|
+
const require = createRequire(import.meta.url);
|
|
231
|
+
const mcpModuleUrl = pathToFileURL(require.resolve("@modelcontextprotocol/sdk/server/mcp.js")).href;
|
|
232
|
+
const stdioModuleUrl = pathToFileURL(require.resolve("@modelcontextprotocol/sdk/server/stdio.js")).href;
|
|
233
|
+
const serverPath = join(projectDir, "discover-mcp-server.mjs");
|
|
234
|
+
writeFileSync(
|
|
235
|
+
serverPath,
|
|
236
|
+
[
|
|
237
|
+
`const { McpServer } = await import(${JSON.stringify(mcpModuleUrl)});`,
|
|
238
|
+
`const { StdioServerTransport } = await import(${JSON.stringify(stdioModuleUrl)});`,
|
|
239
|
+
'const server = new McpServer({ name: "fake", version: "1.0.0" }, { capabilities: { tools: {} } });',
|
|
240
|
+
'server.tool("discover_tool", "Discover-visible tool", {}, async () => ({ content: [{ type: "text", text: "ok" }] }));',
|
|
241
|
+
'await server.connect(new StdioServerTransport());',
|
|
242
|
+
].join("\n"),
|
|
243
|
+
"utf-8",
|
|
244
|
+
);
|
|
245
|
+
writeFileSync(
|
|
246
|
+
join(projectDir, ".mcp.json"),
|
|
247
|
+
JSON.stringify({ mcpServers: { "gsd-workflow": { command: process.execPath, args: [serverPath] } } }),
|
|
248
|
+
"utf-8",
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
let message = "";
|
|
252
|
+
const ctx = {
|
|
253
|
+
getSystemPrompt: () => "",
|
|
254
|
+
ui: {
|
|
255
|
+
notify: (text: string) => {
|
|
256
|
+
message = text;
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
await handleMcpStatus("discover", ctx as unknown as ExtensionCommandContext);
|
|
262
|
+
|
|
263
|
+
assert.match(message, /MCP discovery completed for gsd-workflow/);
|
|
264
|
+
assert.match(message, /discover_tool/);
|
|
265
|
+
assert.doesNotMatch(message, /Usage: \/gsd mcp/);
|
|
266
|
+
} finally {
|
|
267
|
+
process.chdir(originalCwd);
|
|
268
|
+
if (previousGsdHome === undefined) delete process.env.GSD_HOME;
|
|
269
|
+
else process.env.GSD_HOME = previousGsdHome;
|
|
270
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
271
|
+
rmSync(gsdHomeDir, { recursive: true, force: true });
|
|
272
|
+
clearMcpConfigCache();
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
describe("formatMcpDiscoveryResult", () => {
|
|
278
|
+
test("summarizes discovered tools", () => {
|
|
279
|
+
const result = formatMcpDiscoveryResult({
|
|
280
|
+
ok: true,
|
|
281
|
+
server: "demo",
|
|
282
|
+
transport: "stdio",
|
|
283
|
+
toolCount: 1,
|
|
284
|
+
tools: ["ping"],
|
|
285
|
+
warnings: [],
|
|
286
|
+
});
|
|
287
|
+
assert.match(result, /discovery completed/i);
|
|
288
|
+
assert.match(result, /ping/);
|
|
289
|
+
assert.match(result, /mcp_call/);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("summarizes discovery failures", () => {
|
|
293
|
+
const result = formatMcpDiscoveryResult({
|
|
294
|
+
ok: false,
|
|
295
|
+
server: "demo",
|
|
296
|
+
transport: "http",
|
|
297
|
+
toolCount: 0,
|
|
298
|
+
tools: [],
|
|
299
|
+
warnings: ["url references unset environment variable TOKEN."],
|
|
300
|
+
error: "bad config",
|
|
301
|
+
});
|
|
302
|
+
assert.match(result, /discovery failed/i);
|
|
303
|
+
assert.match(result, /bad config/);
|
|
304
|
+
assert.match(result, /TOKEN/);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
131
308
|
describe("formatMcpConnectionTestResult", () => {
|
|
132
309
|
test("summarizes successful tools/list", () => {
|
|
133
310
|
const result = formatMcpConnectionTestResult({
|
|
@@ -102,8 +102,8 @@ test("migration auto-check preserves empty DB and reports explicit recovery", as
|
|
|
102
102
|
assert.equal(result.action, "recovery-required");
|
|
103
103
|
assert.equal(result.reason, "db-empty");
|
|
104
104
|
assert.deepEqual(result.afterDb, { milestones: 0, slices: 0, tasks: 0 });
|
|
105
|
-
assert.equal(result.recoveryCommand, "/gsd recover");
|
|
106
|
-
assert.match(result.message ?? "", /run `\/gsd recover`/);
|
|
105
|
+
assert.equal(result.recoveryCommand, "/gsd recover --confirm");
|
|
106
|
+
assert.match(result.message ?? "", /run `\/gsd recover --confirm`/);
|
|
107
107
|
assert.equal(getAllMilestones().length, 0);
|
|
108
108
|
assert.equal(getSliceTasks("M001", "S01").length, 0);
|
|
109
109
|
} finally {
|
|
@@ -125,7 +125,7 @@ test("migration auto-check preserves DB on hierarchy count mismatch", async () =
|
|
|
125
125
|
assert.equal(result.reason, "count-mismatch");
|
|
126
126
|
assert.deepEqual(result.beforeDb, { milestones: 1, slices: 1, tasks: 0 });
|
|
127
127
|
assert.deepEqual(result.afterDb, { milestones: 1, slices: 1, tasks: 0 });
|
|
128
|
-
assert.equal(result.recoveryCommand, "/gsd recover");
|
|
128
|
+
assert.equal(result.recoveryCommand, "/gsd recover --confirm");
|
|
129
129
|
assert.equal(getSliceTasks("M001", "S01").length, 0);
|
|
130
130
|
} finally {
|
|
131
131
|
cleanup(base);
|