gsd-pi 2.44.0-dev.0b97ffd → 2.44.0-dev.73f2fd5
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/extensions/gsd/auto/infra-errors.js +3 -0
- package/dist/resources/extensions/gsd/auto/phases.js +36 -36
- package/dist/resources/extensions/gsd/auto-prompts.js +24 -1
- package/dist/resources/extensions/gsd/auto-timers.js +57 -3
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +4 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +9 -6
- package/dist/resources/extensions/gsd/auto.js +30 -3
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +156 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -12
- package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
- package/dist/resources/extensions/gsd/commands-mcp-status.js +187 -0
- package/dist/resources/extensions/gsd/db-writer.js +34 -16
- package/dist/resources/extensions/gsd/doctor.js +8 -0
- package/dist/resources/extensions/gsd/git-service.js +8 -3
- package/dist/resources/extensions/gsd/gsd-db.js +12 -1
- package/dist/resources/extensions/gsd/markdown-renderer.js +1 -1
- package/dist/resources/extensions/gsd/preferences.js +9 -1
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -4
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +3 -14
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
- package/dist/resources/extensions/gsd/provider-error-pause.js +7 -0
- package/dist/resources/extensions/gsd/state.js +19 -2
- package/dist/resources/extensions/gsd/tools/plan-slice.js +1 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +1 -0
- package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -0
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +88 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +6 -0
- package/dist/resources/extensions/mcp-client/index.js +14 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
- 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 +2 -2
- 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 +16 -16
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +15 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/local-model-check.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/local-model-check.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/local-model-check.js +41 -0
- package/packages/pi-coding-agent/dist/core/local-model-check.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +20 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +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 +6 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +17 -0
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js +32 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +8 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +12 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts +15 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js +40 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js.map +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 +4 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +5 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +13 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.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 +17 -8
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -3
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.ts +15 -1
- package/packages/pi-coding-agent/src/core/local-model-check.ts +45 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +21 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +9 -0
- package/packages/pi-coding-agent/src/main.ts +19 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/timestamp.test.ts +38 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +10 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/timestamp.ts +48 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +3 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +18 -3
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +16 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +8 -1
- package/src/resources/extensions/gsd/auto/infra-errors.ts +3 -0
- package/src/resources/extensions/gsd/auto/phases.ts +45 -48
- package/src/resources/extensions/gsd/auto-prompts.ts +24 -1
- package/src/resources/extensions/gsd/auto-timers.ts +64 -3
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +5 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +9 -6
- package/src/resources/extensions/gsd/auto.ts +37 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +148 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +48 -11
- package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
- package/src/resources/extensions/gsd/commands-mcp-status.ts +247 -0
- package/src/resources/extensions/gsd/db-writer.ts +39 -17
- package/src/resources/extensions/gsd/doctor.ts +7 -1
- package/src/resources/extensions/gsd/git-service.ts +6 -2
- package/src/resources/extensions/gsd/gsd-db.ts +16 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +1 -1
- package/src/resources/extensions/gsd/preferences.ts +11 -1
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -4
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
- package/src/resources/extensions/gsd/prompts/replan-slice.md +3 -14
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
- package/src/resources/extensions/gsd/provider-error-pause.ts +9 -0
- package/src/resources/extensions/gsd/state.ts +19 -1
- package/src/resources/extensions/gsd/tests/auto-pr-bugs.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/est-annotation-timeout.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/infra-error.test.ts +20 -2
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +11 -7
- package/src/resources/extensions/gsd/tests/stop-auto-merge-back.test.ts +67 -0
- package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +2 -1
- package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +2 -0
- package/src/resources/extensions/gsd/tools/replan-slice.ts +3 -0
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +127 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +7 -0
- package/src/resources/extensions/mcp-client/index.ts +20 -0
- /package/dist/web/standalone/.next/static/{alS4hoANx0TK4UVZY27da → kxxAA66bah_yhPYqLBHE2}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{alS4hoANx0TK4UVZY27da → kxxAA66bah_yhPYqLBHE2}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import test, { describe } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
formatMcpStatusReport,
|
|
6
|
+
formatMcpServerDetail,
|
|
7
|
+
type McpServerStatus,
|
|
8
|
+
} from "../commands-mcp-status.ts";
|
|
9
|
+
|
|
10
|
+
// ─── formatMcpStatusReport ──────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
describe("formatMcpStatusReport", () => {
|
|
13
|
+
test("returns no-servers message when list is empty", () => {
|
|
14
|
+
const result = formatMcpStatusReport([]);
|
|
15
|
+
assert.match(result, /no mcp servers configured/i);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("lists all servers with connection status", () => {
|
|
19
|
+
const servers: McpServerStatus[] = [
|
|
20
|
+
{ name: "railway", transport: "stdio", connected: true, toolCount: 5, error: undefined },
|
|
21
|
+
{ name: "linear", transport: "http", connected: false, toolCount: 0, error: undefined },
|
|
22
|
+
];
|
|
23
|
+
const result = formatMcpStatusReport(servers);
|
|
24
|
+
assert.match(result, /railway/);
|
|
25
|
+
assert.match(result, /linear/);
|
|
26
|
+
assert.match(result, /connected/i);
|
|
27
|
+
assert.match(result, /disconnected/i);
|
|
28
|
+
assert.match(result, /5 tools/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("shows error state for servers with errors", () => {
|
|
32
|
+
const servers: McpServerStatus[] = [
|
|
33
|
+
{ name: "broken", transport: "stdio", connected: false, toolCount: 0, error: "Connection refused" },
|
|
34
|
+
];
|
|
35
|
+
const result = formatMcpStatusReport(servers);
|
|
36
|
+
assert.match(result, /error/i);
|
|
37
|
+
assert.match(result, /Connection refused/);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("includes server count in header", () => {
|
|
41
|
+
const servers: McpServerStatus[] = [
|
|
42
|
+
{ name: "a", transport: "stdio", connected: true, toolCount: 3, error: undefined },
|
|
43
|
+
{ name: "b", transport: "http", connected: true, toolCount: 2, error: undefined },
|
|
44
|
+
];
|
|
45
|
+
const result = formatMcpStatusReport(servers);
|
|
46
|
+
assert.match(result, /2/);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// ─── formatMcpServerDetail ──────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
describe("formatMcpServerDetail", () => {
|
|
53
|
+
test("shows server name and transport", () => {
|
|
54
|
+
const result = formatMcpServerDetail({
|
|
55
|
+
name: "railway",
|
|
56
|
+
transport: "stdio",
|
|
57
|
+
connected: true,
|
|
58
|
+
toolCount: 3,
|
|
59
|
+
tools: ["railway_list_projects", "railway_deploy", "railway_logs"],
|
|
60
|
+
error: undefined,
|
|
61
|
+
});
|
|
62
|
+
assert.match(result, /railway/);
|
|
63
|
+
assert.match(result, /stdio/);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("lists individual tools when available", () => {
|
|
67
|
+
const result = formatMcpServerDetail({
|
|
68
|
+
name: "railway",
|
|
69
|
+
transport: "stdio",
|
|
70
|
+
connected: true,
|
|
71
|
+
toolCount: 2,
|
|
72
|
+
tools: ["railway_list_projects", "railway_deploy"],
|
|
73
|
+
error: undefined,
|
|
74
|
+
});
|
|
75
|
+
assert.match(result, /railway_list_projects/);
|
|
76
|
+
assert.match(result, /railway_deploy/);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("shows error message for failed servers", () => {
|
|
80
|
+
const result = formatMcpServerDetail({
|
|
81
|
+
name: "broken",
|
|
82
|
+
transport: "stdio",
|
|
83
|
+
connected: false,
|
|
84
|
+
toolCount: 0,
|
|
85
|
+
tools: [],
|
|
86
|
+
error: "spawn ENOENT",
|
|
87
|
+
});
|
|
88
|
+
assert.match(result, /error/i);
|
|
89
|
+
assert.match(result, /spawn ENOENT/);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("shows disconnected status with no tools", () => {
|
|
93
|
+
const result = formatMcpServerDetail({
|
|
94
|
+
name: "offline",
|
|
95
|
+
transport: "http",
|
|
96
|
+
connected: false,
|
|
97
|
+
toolCount: 0,
|
|
98
|
+
tools: [],
|
|
99
|
+
error: undefined,
|
|
100
|
+
});
|
|
101
|
+
assert.match(result, /disconnected/i);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* merge-conflict-stops-loop.test.ts — #2330
|
|
3
|
+
*
|
|
4
|
+
* When a squash merge has real code conflicts (not just .gsd/ files),
|
|
5
|
+
* the merge retries forever because MergeConflictError is caught
|
|
6
|
+
* silently in mergeAndExit. This test verifies that:
|
|
7
|
+
* 1. worktree-resolver re-throws MergeConflictError for code conflicts
|
|
8
|
+
* 2. auto/phases.ts wraps mergeAndExit calls to stop the loop on conflict
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { createTestContext } from "./test-helpers.ts";
|
|
14
|
+
|
|
15
|
+
const { assertTrue, report } = createTestContext();
|
|
16
|
+
|
|
17
|
+
const resolverPath = join(import.meta.dirname, "..", "worktree-resolver.ts");
|
|
18
|
+
const resolverSrc = readFileSync(resolverPath, "utf-8");
|
|
19
|
+
|
|
20
|
+
const phasesPath = join(import.meta.dirname, "..", "auto", "phases.ts");
|
|
21
|
+
const phasesSrc = readFileSync(phasesPath, "utf-8");
|
|
22
|
+
|
|
23
|
+
console.log("\n=== #2330: Merge conflict stops auto loop ===");
|
|
24
|
+
|
|
25
|
+
// ── Test 1: worktree-resolver re-throws MergeConflictError ──────────────
|
|
26
|
+
|
|
27
|
+
const methodStart = resolverSrc.indexOf("Worktree-mode merge:");
|
|
28
|
+
assertTrue(methodStart > 0, "worktree-resolver has _mergeWorktreeMode method");
|
|
29
|
+
|
|
30
|
+
const methodBody = resolverSrc.slice(methodStart, methodStart + 5000);
|
|
31
|
+
const rethrowsConflict =
|
|
32
|
+
methodBody.includes("MergeConflictError") &&
|
|
33
|
+
methodBody.includes("throw err");
|
|
34
|
+
|
|
35
|
+
assertTrue(
|
|
36
|
+
rethrowsConflict,
|
|
37
|
+
"worktree-resolver._mergeWorktreeMode re-throws MergeConflictError (#2330)",
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// ── Test 2: auto/phases.ts imports and uses MergeConflictError ──────────
|
|
41
|
+
|
|
42
|
+
assertTrue(
|
|
43
|
+
phasesSrc.includes("MergeConflictError") && phasesSrc.includes("mergeAndExit"),
|
|
44
|
+
"auto/phases.ts handles MergeConflictError from mergeAndExit (#2330)",
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// ── Test 3: The handler stops the loop (doesn't just warn) ──────────────
|
|
48
|
+
|
|
49
|
+
// Find the instanceof MergeConflictError check (not the import line)
|
|
50
|
+
const instanceofIdx = phasesSrc.indexOf("instanceof MergeConflictError");
|
|
51
|
+
assertTrue(instanceofIdx > 0, "auto/phases.ts has instanceof MergeConflictError check");
|
|
52
|
+
|
|
53
|
+
if (instanceofIdx > 0) {
|
|
54
|
+
const afterHandler = phasesSrc.slice(instanceofIdx, instanceofIdx + 500);
|
|
55
|
+
const stopsLoop =
|
|
56
|
+
afterHandler.includes("stopAuto") ||
|
|
57
|
+
afterHandler.includes('action: "break"') ||
|
|
58
|
+
afterHandler.includes("reason: \"merge-conflict\"");
|
|
59
|
+
|
|
60
|
+
assertTrue(
|
|
61
|
+
stopsLoop,
|
|
62
|
+
"auto/phases.ts stops the loop when merge conflict is detected (#2330)",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
report();
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
applyModeDefaults,
|
|
16
16
|
getIsolationMode,
|
|
17
17
|
parsePreferencesMarkdown,
|
|
18
|
+
_resetParseWarningFlag,
|
|
18
19
|
} from "../preferences.ts";
|
|
19
20
|
import type { GSDPreferences, GSDModelConfigV2, GSDPhaseModelConfig } from "../preferences.ts";
|
|
20
21
|
|
|
@@ -352,3 +353,29 @@ test("handles empty models config", () => {
|
|
|
352
353
|
assert.notEqual(prefs, null);
|
|
353
354
|
assert.equal(prefs!.models, undefined);
|
|
354
355
|
});
|
|
356
|
+
|
|
357
|
+
// ── Warn-once for unrecognized format (#2373) ────────────────────────────────
|
|
358
|
+
|
|
359
|
+
test("unrecognized format warning is emitted at most once (#2373)", () => {
|
|
360
|
+
const warnings: string[] = [];
|
|
361
|
+
const origWarn = console.warn;
|
|
362
|
+
console.warn = (...args: unknown[]) => warnings.push(args.join(" "));
|
|
363
|
+
try {
|
|
364
|
+
// Reset internal warned flag so the test starts clean
|
|
365
|
+
_resetParseWarningFlag();
|
|
366
|
+
|
|
367
|
+
const unrecognized = "This is just plain text with no frontmatter or headings.";
|
|
368
|
+
|
|
369
|
+
// Call multiple times — simulates repeated preference loads
|
|
370
|
+
parsePreferencesMarkdown(unrecognized);
|
|
371
|
+
parsePreferencesMarkdown(unrecognized);
|
|
372
|
+
parsePreferencesMarkdown(unrecognized);
|
|
373
|
+
|
|
374
|
+
const relevant = warnings.filter(w => w.includes("unrecognized format"));
|
|
375
|
+
assert.equal(relevant.length, 1, `expected exactly 1 warning, got ${relevant.length}: ${JSON.stringify(relevant)}`);
|
|
376
|
+
} finally {
|
|
377
|
+
console.warn = origWarn;
|
|
378
|
+
// Reset so other tests aren't affected by the flag state
|
|
379
|
+
_resetParseWarningFlag();
|
|
380
|
+
}
|
|
381
|
+
});
|
|
@@ -147,12 +147,12 @@ test("plan-slice prompt no longer frames direct PLAN writes as the source of tru
|
|
|
147
147
|
assert.match(prompt, /Do \*\*not\*\* rely on direct `PLAN\.md` writes as the source of truth/i);
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
test("plan-slice prompt explicitly names gsd_plan_slice
|
|
150
|
+
test("plan-slice prompt explicitly names gsd_plan_slice as DB-backed planning tool", () => {
|
|
151
151
|
const prompt = readPrompt("plan-slice");
|
|
152
152
|
assert.match(prompt, /gsd_plan_slice/);
|
|
153
153
|
assert.match(prompt, /gsd_plan_task/);
|
|
154
|
-
// The prompt should describe
|
|
155
|
-
assert.match(prompt, /DB-backed
|
|
154
|
+
// The prompt should describe the DB-backed tool as the canonical write path
|
|
155
|
+
assert.match(prompt, /DB-backed tool is the canonical write path/i);
|
|
156
156
|
});
|
|
157
157
|
|
|
158
158
|
test("plan-slice prompt does not instruct direct file writes as a primary step", () => {
|
|
@@ -161,14 +161,18 @@ test("plan-slice prompt does not instruct direct file writes as a primary step",
|
|
|
161
161
|
assert.doesNotMatch(prompt, /^\d+\.\s+Write `?\{\{outputPath\}\}`?\s*$/m);
|
|
162
162
|
});
|
|
163
163
|
|
|
164
|
-
test("plan-slice prompt
|
|
164
|
+
test("plan-slice prompt clarifies gsd_plan_slice handles task persistence", () => {
|
|
165
165
|
const prompt = readPrompt("plan-slice");
|
|
166
|
-
|
|
166
|
+
// gsd_plan_slice persists tasks in its transaction — no separate gsd_plan_task calls needed
|
|
167
|
+
assert.match(prompt, /gsd_plan_task/);
|
|
168
|
+
assert.match(prompt, /gsd_plan_slice` handles task persistence/i);
|
|
167
169
|
});
|
|
168
170
|
|
|
169
|
-
test("replan-slice prompt
|
|
171
|
+
test("replan-slice prompt uses gsd_replan_slice as canonical DB-backed tool", () => {
|
|
170
172
|
const prompt = readPrompt("replan-slice");
|
|
171
|
-
assert.match(prompt, /
|
|
173
|
+
assert.match(prompt, /gsd_replan_slice/);
|
|
174
|
+
// Degraded fallback (direct file writes) was removed — DB tools are always available
|
|
175
|
+
assert.doesNotMatch(prompt, /Degraded fallback/i);
|
|
172
176
|
});
|
|
173
177
|
|
|
174
178
|
test("reassess-roadmap prompt references gsd_reassess_roadmap tool", () => {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stop-auto-merge-back.test.ts — Regression test for #2317.
|
|
3
|
+
*
|
|
4
|
+
* When auto-mode stops after a milestone is complete, stopAuto should trigger
|
|
5
|
+
* merge-back (mergeAndExit) instead of just exiting the worktree with
|
|
6
|
+
* preserveBranch: true. Otherwise milestone code stays stranded on the
|
|
7
|
+
* worktree branch and never reaches main.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
// ─── Source analysis: stopAuto calls mergeAndExit for complete milestones ────
|
|
16
|
+
|
|
17
|
+
const autoSrcPath = join(import.meta.dirname, "..", "auto.ts");
|
|
18
|
+
const autoSrc = readFileSync(autoSrcPath, "utf-8");
|
|
19
|
+
|
|
20
|
+
test("#2317: stopAuto should check milestone completion status before choosing exit strategy", () => {
|
|
21
|
+
// stopAuto Step 4 should NOT unconditionally call exitMilestone(preserveBranch: true).
|
|
22
|
+
// It should check if the milestone is complete and call mergeAndExit instead.
|
|
23
|
+
|
|
24
|
+
// Find the Step 4 section
|
|
25
|
+
const step4Idx = autoSrc.indexOf("Step 4: Auto-worktree exit");
|
|
26
|
+
assert.ok(step4Idx !== -1, "Step 4 comment exists in stopAuto");
|
|
27
|
+
|
|
28
|
+
// Extract a reasonable window around Step 4 (up to Step 5)
|
|
29
|
+
const step5Idx = autoSrc.indexOf("Step 5:", step4Idx);
|
|
30
|
+
const step4Block = autoSrc.slice(step4Idx, step5Idx);
|
|
31
|
+
|
|
32
|
+
// The fix: Step 4 should call mergeAndExit when milestone is complete
|
|
33
|
+
assert.ok(
|
|
34
|
+
step4Block.includes("mergeAndExit"),
|
|
35
|
+
"Step 4 should call mergeAndExit for completed milestones",
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("#2317: stopAuto should detect milestone completion via SUMMARY file or DB", () => {
|
|
40
|
+
const step4Idx = autoSrc.indexOf("Step 4: Auto-worktree exit");
|
|
41
|
+
const step5Idx = autoSrc.indexOf("Step 5:", step4Idx);
|
|
42
|
+
const step4Block = autoSrc.slice(step4Idx, step5Idx);
|
|
43
|
+
|
|
44
|
+
// Should check completion status — either via SUMMARY file, DB getMilestone, or phase
|
|
45
|
+
const checksCompletion =
|
|
46
|
+
step4Block.includes("SUMMARY") ||
|
|
47
|
+
step4Block.includes("getMilestone") ||
|
|
48
|
+
step4Block.includes("complete") ||
|
|
49
|
+
step4Block.includes("isMilestoneComplete");
|
|
50
|
+
|
|
51
|
+
assert.ok(
|
|
52
|
+
checksCompletion,
|
|
53
|
+
"Step 4 should check if milestone is complete before deciding exit strategy",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("#2317: stopAuto still preserves branch for incomplete milestones", () => {
|
|
58
|
+
const step4Idx = autoSrc.indexOf("Step 4: Auto-worktree exit");
|
|
59
|
+
const step5Idx = autoSrc.indexOf("Step 5:", step4Idx);
|
|
60
|
+
const step4Block = autoSrc.slice(step4Idx, step5Idx);
|
|
61
|
+
|
|
62
|
+
// preserveBranch should still be used as fallback for non-complete milestones
|
|
63
|
+
assert.ok(
|
|
64
|
+
step4Block.includes("preserveBranch"),
|
|
65
|
+
"Step 4 should still preserve branch for incomplete milestones (fallback path)",
|
|
66
|
+
);
|
|
67
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* terminated-transient.test.ts — Regression test for #2309.
|
|
3
|
+
*
|
|
4
|
+
* classifyProviderError should treat 'terminated' errors (process killed,
|
|
5
|
+
* connection reset) as transient with auto-resume, not permanent.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import test from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { classifyProviderError } from "../provider-error-pause.ts";
|
|
11
|
+
|
|
12
|
+
test("#2309: 'terminated' errors should be classified as transient", () => {
|
|
13
|
+
const result = classifyProviderError("terminated");
|
|
14
|
+
assert.equal(result.isTransient, true, "'terminated' should be transient");
|
|
15
|
+
assert.equal(result.isRateLimit, false, "'terminated' is not a rate limit");
|
|
16
|
+
assert.ok(result.suggestedDelayMs > 0, "'terminated' should have a retry delay");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("#2309: 'connection reset' errors should be classified as transient", () => {
|
|
20
|
+
const result = classifyProviderError("connection reset by peer");
|
|
21
|
+
assert.equal(result.isTransient, true, "'connection reset' should be transient");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("#2309: 'other side closed' errors should be classified as transient", () => {
|
|
25
|
+
const result = classifyProviderError("other side closed the connection");
|
|
26
|
+
assert.equal(result.isTransient, true, "'other side closed' should be transient");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("#2309: 'fetch failed' errors should be classified as transient", () => {
|
|
30
|
+
const result = classifyProviderError("fetch failed: network error");
|
|
31
|
+
assert.equal(result.isTransient, true, "'fetch failed' should be transient");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("#2309: 'connection refused' errors should be classified as transient", () => {
|
|
35
|
+
const result = classifyProviderError("ECONNREFUSED: connection refused");
|
|
36
|
+
assert.equal(result.isTransient, true, "'connection refused' should be transient");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("#2309: permanent errors are still permanent", () => {
|
|
40
|
+
const authResult = classifyProviderError("unauthorized: invalid API key");
|
|
41
|
+
assert.equal(authResult.isTransient, false, "auth errors should stay permanent");
|
|
42
|
+
assert.equal(authResult.suggestedDelayMs, 0, "permanent errors have no delay");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("#2309: rate limits are still transient", () => {
|
|
46
|
+
const rlResult = classifyProviderError("rate limit exceeded (429)");
|
|
47
|
+
assert.equal(rlResult.isTransient, true, "rate limits are still transient");
|
|
48
|
+
assert.equal(rlResult.isRateLimit, true, "rate limits are flagged as rate limits");
|
|
49
|
+
});
|
|
@@ -34,6 +34,7 @@ const RENAME_MAP: Array<{ canonical: string; alias: string }> = [
|
|
|
34
34
|
{ canonical: "gsd_replan_slice", alias: "gsd_slice_replan" },
|
|
35
35
|
{ canonical: "gsd_reassess_roadmap", alias: "gsd_roadmap_reassess" },
|
|
36
36
|
{ canonical: "gsd_complete_milestone", alias: "gsd_milestone_complete" },
|
|
37
|
+
{ canonical: "gsd_validate_milestone", alias: "gsd_milestone_validate" },
|
|
37
38
|
];
|
|
38
39
|
|
|
39
40
|
// ─── Registration count ──────────────────────────────────────────────────────
|
|
@@ -43,7 +44,7 @@ console.log('\n── Tool naming: registration count ──');
|
|
|
43
44
|
const pi = makeMockPi();
|
|
44
45
|
registerDbTools(pi);
|
|
45
46
|
|
|
46
|
-
assert.deepStrictEqual(pi.tools.length,
|
|
47
|
+
assert.deepStrictEqual(pi.tools.length, 26, 'Should register exactly 26 tools (13 canonical + 13 aliases)');
|
|
47
48
|
|
|
48
49
|
// ─── Both names exist for each pair ──────────────────────────────────────────
|
|
49
50
|
|
|
@@ -20,6 +20,7 @@ export interface PlanSliceTaskInput {
|
|
|
20
20
|
inputs: string[];
|
|
21
21
|
expectedOutput: string[];
|
|
22
22
|
observabilityImpact?: string;
|
|
23
|
+
fullPlanMd?: string;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export interface PlanSliceParams {
|
|
@@ -167,6 +168,7 @@ export async function handlePlanSlice(
|
|
|
167
168
|
inputs: task.inputs,
|
|
168
169
|
expectedOutput: task.expectedOutput,
|
|
169
170
|
observabilityImpact: task.observabilityImpact ?? "",
|
|
171
|
+
fullPlanMd: task.fullPlanMd,
|
|
170
172
|
});
|
|
171
173
|
}
|
|
172
174
|
});
|
|
@@ -15,6 +15,7 @@ export interface PlanTaskParams {
|
|
|
15
15
|
inputs: string[];
|
|
16
16
|
expectedOutput: string[];
|
|
17
17
|
observabilityImpact?: string;
|
|
18
|
+
fullPlanMd?: string;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export interface PlanTaskResult {
|
|
@@ -94,6 +95,7 @@ export async function handlePlanTask(
|
|
|
94
95
|
inputs: params.inputs,
|
|
95
96
|
expectedOutput: params.expectedOutput,
|
|
96
97
|
observabilityImpact: params.observabilityImpact ?? "",
|
|
98
|
+
fullPlanMd: params.fullPlanMd,
|
|
97
99
|
});
|
|
98
100
|
});
|
|
99
101
|
} catch (err) {
|
|
@@ -21,6 +21,7 @@ export interface ReplanSliceTaskInput {
|
|
|
21
21
|
verify: string;
|
|
22
22
|
inputs: string[];
|
|
23
23
|
expectedOutput: string[];
|
|
24
|
+
fullPlanMd?: string;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export interface ReplanSliceParams {
|
|
@@ -136,6 +137,7 @@ export async function handleReplanSlice(
|
|
|
136
137
|
verify: updatedTask.verify || "",
|
|
137
138
|
inputs: updatedTask.inputs || [],
|
|
138
139
|
expectedOutput: updatedTask.expectedOutput || [],
|
|
140
|
+
fullPlanMd: updatedTask.fullPlanMd,
|
|
139
141
|
});
|
|
140
142
|
} else {
|
|
141
143
|
// Insert new task then set planning fields
|
|
@@ -154,6 +156,7 @@ export async function handleReplanSlice(
|
|
|
154
156
|
verify: updatedTask.verify || "",
|
|
155
157
|
inputs: updatedTask.inputs || [],
|
|
156
158
|
expectedOutput: updatedTask.expectedOutput || [],
|
|
159
|
+
fullPlanMd: updatedTask.fullPlanMd,
|
|
157
160
|
});
|
|
158
161
|
}
|
|
159
162
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* validate-milestone handler — the core operation behind gsd_validate_milestone.
|
|
3
|
+
*
|
|
4
|
+
* Persists milestone validation results to the assessments table,
|
|
5
|
+
* renders VALIDATION.md to disk, and invalidates caches.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
transaction,
|
|
12
|
+
_getAdapter,
|
|
13
|
+
} from "../gsd-db.js";
|
|
14
|
+
import { resolveMilestonePath, clearPathCache } from "../paths.js";
|
|
15
|
+
import { saveFile, clearParseCache } from "../files.js";
|
|
16
|
+
import { invalidateStateCache } from "../state.js";
|
|
17
|
+
|
|
18
|
+
export interface ValidateMilestoneParams {
|
|
19
|
+
milestoneId: string;
|
|
20
|
+
verdict: "pass" | "needs-attention" | "needs-remediation";
|
|
21
|
+
remediationRound: number;
|
|
22
|
+
successCriteriaChecklist: string;
|
|
23
|
+
sliceDeliveryAudit: string;
|
|
24
|
+
crossSliceIntegration: string;
|
|
25
|
+
requirementCoverage: string;
|
|
26
|
+
verdictRationale: string;
|
|
27
|
+
remediationPlan?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ValidateMilestoneResult {
|
|
31
|
+
milestoneId: string;
|
|
32
|
+
verdict: string;
|
|
33
|
+
validationPath: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function renderValidationMarkdown(params: ValidateMilestoneParams): string {
|
|
37
|
+
let md = `---
|
|
38
|
+
verdict: ${params.verdict}
|
|
39
|
+
remediation_round: ${params.remediationRound}
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
# Milestone Validation: ${params.milestoneId}
|
|
43
|
+
|
|
44
|
+
## Success Criteria Checklist
|
|
45
|
+
${params.successCriteriaChecklist}
|
|
46
|
+
|
|
47
|
+
## Slice Delivery Audit
|
|
48
|
+
${params.sliceDeliveryAudit}
|
|
49
|
+
|
|
50
|
+
## Cross-Slice Integration
|
|
51
|
+
${params.crossSliceIntegration}
|
|
52
|
+
|
|
53
|
+
## Requirement Coverage
|
|
54
|
+
${params.requirementCoverage}
|
|
55
|
+
|
|
56
|
+
## Verdict Rationale
|
|
57
|
+
${params.verdictRationale}
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
if (params.verdict === "needs-remediation" && params.remediationPlan) {
|
|
61
|
+
md += `\n## Remediation Plan\n${params.remediationPlan}\n`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return md;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function handleValidateMilestone(
|
|
68
|
+
params: ValidateMilestoneParams,
|
|
69
|
+
basePath: string,
|
|
70
|
+
): Promise<ValidateMilestoneResult | { error: string }> {
|
|
71
|
+
if (!params.milestoneId || typeof params.milestoneId !== "string" || params.milestoneId.trim() === "") {
|
|
72
|
+
return { error: "milestoneId is required and must be a non-empty string" };
|
|
73
|
+
}
|
|
74
|
+
const validVerdicts = ["pass", "needs-attention", "needs-remediation"];
|
|
75
|
+
if (!validVerdicts.includes(params.verdict)) {
|
|
76
|
+
return { error: `verdict must be one of: ${validVerdicts.join(", ")}` };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── Filesystem render ──────────────────────────────────────────────────
|
|
80
|
+
const validationMd = renderValidationMarkdown(params);
|
|
81
|
+
|
|
82
|
+
let validationPath: string;
|
|
83
|
+
const milestoneDir = resolveMilestonePath(basePath, params.milestoneId);
|
|
84
|
+
if (milestoneDir) {
|
|
85
|
+
validationPath = join(milestoneDir, `${params.milestoneId}-VALIDATION.md`);
|
|
86
|
+
} else {
|
|
87
|
+
const gsdDir = join(basePath, ".gsd");
|
|
88
|
+
const manualDir = join(gsdDir, "milestones", params.milestoneId);
|
|
89
|
+
validationPath = join(manualDir, `${params.milestoneId}-VALIDATION.md`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
await saveFile(validationPath, validationMd);
|
|
94
|
+
} catch (renderErr) {
|
|
95
|
+
process.stderr.write(
|
|
96
|
+
`gsd-db: validate_milestone — disk render failed: ${(renderErr as Error).message}\n`,
|
|
97
|
+
);
|
|
98
|
+
return { error: `disk render failed: ${(renderErr as Error).message}` };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── DB write — store in assessments table ──────────────────────────────
|
|
102
|
+
const validatedAt = new Date().toISOString();
|
|
103
|
+
|
|
104
|
+
transaction(() => {
|
|
105
|
+
const adapter = _getAdapter()!;
|
|
106
|
+
adapter.prepare(
|
|
107
|
+
`INSERT OR REPLACE INTO assessments (path, milestone_id, slice_id, task_id, status, scope, full_content, created_at)
|
|
108
|
+
VALUES (:path, :mid, NULL, NULL, :verdict, 'milestone-validation', :content, :created_at)`,
|
|
109
|
+
).run({
|
|
110
|
+
":path": validationPath,
|
|
111
|
+
":mid": params.milestoneId,
|
|
112
|
+
":verdict": params.verdict,
|
|
113
|
+
":content": validationMd,
|
|
114
|
+
":created_at": validatedAt,
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
invalidateStateCache();
|
|
119
|
+
clearPathCache();
|
|
120
|
+
clearParseCache();
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
milestoneId: params.milestoneId,
|
|
124
|
+
verdict: params.verdict,
|
|
125
|
+
validationPath,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -17,6 +17,7 @@ import { existsSync, unlinkSync } from "node:fs";
|
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import type { AutoSession } from "./auto/session.js";
|
|
19
19
|
import { debugLog } from "./debug-logger.js";
|
|
20
|
+
import { MergeConflictError } from "./git-service.js";
|
|
20
21
|
|
|
21
22
|
// ─── Dependency Interface ──────────────────────────────────────────────────
|
|
22
23
|
|
|
@@ -433,6 +434,12 @@ export class WorktreeResolver {
|
|
|
433
434
|
/* best-effort */
|
|
434
435
|
}
|
|
435
436
|
}
|
|
437
|
+
|
|
438
|
+
// Re-throw MergeConflictError so the auto loop can detect real code
|
|
439
|
+
// conflicts and stop instead of retrying forever (#2330).
|
|
440
|
+
if (err instanceof MergeConflictError) {
|
|
441
|
+
throw err;
|
|
442
|
+
}
|
|
436
443
|
}
|
|
437
444
|
|
|
438
445
|
// Always restore basePath and rebuild — whether merge succeeded or failed
|
|
@@ -213,6 +213,26 @@ function formatToolList(serverName: string, tools: McpToolSchema[]): string {
|
|
|
213
213
|
return lines.join("\n");
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
// ─── Status helper (consumed by /gsd mcp) ─────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Return the live connection status for a named MCP server.
|
|
220
|
+
* Safe to call even when the server has never been connected.
|
|
221
|
+
*/
|
|
222
|
+
export function getConnectionStatus(name: string): {
|
|
223
|
+
connected: boolean;
|
|
224
|
+
tools: string[];
|
|
225
|
+
error?: string;
|
|
226
|
+
} {
|
|
227
|
+
const conn = connections.get(name);
|
|
228
|
+
const cached = toolCache.get(name);
|
|
229
|
+
return {
|
|
230
|
+
connected: !!conn,
|
|
231
|
+
tools: cached ? cached.map((t) => t.name) : [],
|
|
232
|
+
error: undefined,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
216
236
|
// ─── Extension ────────────────────────────────────────────────────────────────
|
|
217
237
|
|
|
218
238
|
export default function (pi: ExtensionAPI) {
|
|
File without changes
|
|
File without changes
|