gsd-pi 2.67.0-dev.509bd95 → 2.67.0-dev.6fc2289
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +152 -70
- package/dist/resources/extensions/gsd/auto/session.js +10 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-start.js +16 -30
- package/dist/resources/extensions/gsd/auto-worktree.js +62 -15
- package/dist/resources/extensions/gsd/auto.js +121 -59
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +7 -2
- package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
- package/dist/resources/extensions/gsd/commands-mcp-status.js +43 -7
- package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -4
- package/dist/resources/extensions/gsd/doctor-proactive.js +3 -3
- package/dist/resources/extensions/gsd/doctor.js +8 -4
- package/dist/resources/extensions/gsd/guided-flow.js +40 -31
- package/dist/resources/extensions/gsd/init-wizard.js +37 -0
- package/dist/resources/extensions/gsd/interrupted-session.js +146 -0
- package/dist/resources/extensions/gsd/mcp-project-config.js +83 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +90 -19
- 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 +2 -2
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +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/page_client-reference-manifest.js +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 +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
- 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/page_client-reference-manifest.js +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/2826.821e01b07d92e948.js +9 -0
- package/dist/web/standalone/.next/static/chunks/app/{page-0c485498795110d6.js → page-f1e30ab6bb269149.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{webpack-b49b09f97429b5d0.js → webpack-6e4d7e9a4f57bed4.js} +1 -1
- package/package.json +4 -2
- 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/workflow-tools.ts +13 -2
- 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/core/contextual-tips.d.ts +43 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.js +208 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.js +227 -0
- package/packages/pi-coding-agent/dist/core/contextual-tips.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/index.js +1 -0
- package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +28 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +19 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +14 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
- 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 +15 -12
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/contextual-tips.test.ts +259 -0
- package/packages/pi-coding-agent/src/core/contextual-tips.ts +232 -0
- package/packages/pi-coding-agent/src/core/index.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +54 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -12
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +21 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +19 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +19 -15
- 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 +190 -93
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +89 -116
- package/src/resources/extensions/gsd/auto/session.ts +10 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-start.ts +23 -55
- package/src/resources/extensions/gsd/auto-worktree.ts +59 -15
- package/src/resources/extensions/gsd/auto.ts +133 -64
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -2
- package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
- package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +4 -4
- package/src/resources/extensions/gsd/doctor-proactive.ts +3 -3
- package/src/resources/extensions/gsd/doctor.ts +9 -5
- package/src/resources/extensions/gsd/guided-flow.ts +42 -36
- package/src/resources/extensions/gsd/init-wizard.ts +40 -0
- package/src/resources/extensions/gsd/interrupted-session.ts +224 -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/auto-recovery.test.ts +668 -2
- package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +14 -4
- package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +380 -2
- package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/integration/doctor-fixlevel.test.ts +52 -1
- package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +2 -9
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +0 -33
- package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +169 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +146 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +136 -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/verification-operational-gate.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +212 -13
- package/src/resources/extensions/gsd/workflow-mcp.ts +106 -19
- package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts +0 -13
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts.map +0 -1
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js +0 -27
- package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js.map +0 -1
- package/packages/pi-coding-agent/src/modes/interactive/provider-auth-setup.ts +0 -40
- /package/dist/web/standalone/.next/static/{mHJZ3Z8yGRzZ32BmQs-I7 → yh2vT27L1E6PChb_C1N_F}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{mHJZ3Z8yGRzZ32BmQs-I7 → yh2vT27L1E6PChb_C1N_F}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD-2 — Regression tests for merge cwd restore (#2929)
|
|
3
|
+
* merge-cwd-restore.test.ts — Regression tests for #2929.
|
|
4
|
+
*
|
|
5
|
+
* Verifies:
|
|
6
|
+
* 1. MergeConflictError restores process.cwd() to the pre-merge directory.
|
|
7
|
+
* 2. autoCommitDirtyState does not run on the integration branch when cwd
|
|
8
|
+
* leaked there from a prior failed merge (parallel mode).
|
|
9
|
+
*
|
|
10
|
+
* Bug: PR #2298 added a stash lifecycle around mergeMilestoneToMain but the
|
|
11
|
+
* MergeConflictError throw path omitted the process.chdir(previousCwd) that
|
|
12
|
+
* the dirty-working-tree and divergence handlers both include. In parallel
|
|
13
|
+
* merge sequences, this left cwd on the integration branch, causing the next
|
|
14
|
+
* merge's autoCommitDirtyState to commit dirty files from OTHER milestones
|
|
15
|
+
* onto main.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
19
|
+
import assert from "node:assert/strict";
|
|
20
|
+
import {
|
|
21
|
+
mkdtempSync,
|
|
22
|
+
mkdirSync,
|
|
23
|
+
writeFileSync,
|
|
24
|
+
rmSync,
|
|
25
|
+
realpathSync,
|
|
26
|
+
} from "node:fs";
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
import { tmpdir } from "node:os";
|
|
29
|
+
import { execSync } from "node:child_process";
|
|
30
|
+
|
|
31
|
+
import { mergeMilestoneToMain } from "../../auto-worktree.ts";
|
|
32
|
+
import { MergeConflictError } from "../../git-service.ts";
|
|
33
|
+
|
|
34
|
+
function run(cmd: string, cwd: string): string {
|
|
35
|
+
return execSync(cmd, {
|
|
36
|
+
cwd,
|
|
37
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
38
|
+
encoding: "utf-8",
|
|
39
|
+
}).trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createTempRepo(): string {
|
|
43
|
+
const dir = realpathSync(
|
|
44
|
+
mkdtempSync(join(tmpdir(), "merge-cwd-restore-test-")),
|
|
45
|
+
);
|
|
46
|
+
run("git init -b main", dir);
|
|
47
|
+
run("git config user.email test@test.com", dir);
|
|
48
|
+
run("git config user.name Test", dir);
|
|
49
|
+
writeFileSync(join(dir, "README.md"), "# test\n");
|
|
50
|
+
writeFileSync(join(dir, ".gitignore"), ".gsd/worktrees/\n");
|
|
51
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
52
|
+
writeFileSync(join(dir, ".gsd", "STATE.md"), "# State\n");
|
|
53
|
+
run("git add .", dir);
|
|
54
|
+
run("git commit -m init", dir);
|
|
55
|
+
return dir;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function makeRoadmap(mid: string, title: string): string {
|
|
59
|
+
return [
|
|
60
|
+
`# ${mid}: Test milestone`,
|
|
61
|
+
"",
|
|
62
|
+
"## Slices",
|
|
63
|
+
"- [x] **S01: Test slice**",
|
|
64
|
+
].join("\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe("merge cwd restore (#2929)", () => {
|
|
68
|
+
let repo: string;
|
|
69
|
+
let savedCwd: string;
|
|
70
|
+
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
savedCwd = process.cwd();
|
|
73
|
+
repo = createTempRepo();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
process.chdir(savedCwd);
|
|
78
|
+
try { run("git reset --hard HEAD", repo); } catch { /* */ }
|
|
79
|
+
rmSync(repo, { recursive: true, force: true });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
83
|
+
// Test 1: MergeConflictError restores cwd (#2929 bug 2)
|
|
84
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
test("MergeConflictError restores cwd to pre-merge directory", () => {
|
|
87
|
+
// Create milestone branch that modifies README.md
|
|
88
|
+
run("git checkout -b milestone/M010", repo);
|
|
89
|
+
writeFileSync(join(repo, "README.md"), "# M010 version\n");
|
|
90
|
+
run("git add .", repo);
|
|
91
|
+
run('git commit -m "M010 changes README"', repo);
|
|
92
|
+
run("git checkout main", repo);
|
|
93
|
+
|
|
94
|
+
// Modify README.md on main to create a conflict
|
|
95
|
+
writeFileSync(join(repo, "README.md"), "# main version (diverged)\n");
|
|
96
|
+
run("git add .", repo);
|
|
97
|
+
run('git commit -m "main diverges README"', repo);
|
|
98
|
+
|
|
99
|
+
// cwd must be repo root (simulates parallel-merge calling from project root)
|
|
100
|
+
process.chdir(repo);
|
|
101
|
+
const cwdBefore = process.cwd();
|
|
102
|
+
|
|
103
|
+
let caught: unknown = null;
|
|
104
|
+
try {
|
|
105
|
+
mergeMilestoneToMain(repo, "M010", makeRoadmap("M010", "Conflict test"));
|
|
106
|
+
} catch (err) {
|
|
107
|
+
caught = err;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Should have thrown a MergeConflictError
|
|
111
|
+
assert.ok(caught instanceof MergeConflictError, "expected MergeConflictError");
|
|
112
|
+
|
|
113
|
+
// Critical: cwd must be restored to where it was before the merge
|
|
114
|
+
const cwdAfter = process.cwd();
|
|
115
|
+
assert.equal(
|
|
116
|
+
cwdAfter,
|
|
117
|
+
cwdBefore,
|
|
118
|
+
"cwd should be restored after MergeConflictError — was left on integration branch before fix",
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
123
|
+
// Test 2: autoCommitDirtyState skipped when on integration branch (#2929 bug 1)
|
|
124
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
test("autoCommitDirtyState does not commit on integration branch in worktree mode", () => {
|
|
127
|
+
// Create milestone branch with real work
|
|
128
|
+
run("git checkout -b milestone/M010", repo);
|
|
129
|
+
writeFileSync(join(repo, "m010.ts"), "export const m010 = true;\n");
|
|
130
|
+
run("git add .", repo);
|
|
131
|
+
run('git commit -m "M010 work"', repo);
|
|
132
|
+
run("git checkout main", repo);
|
|
133
|
+
|
|
134
|
+
// Simulate the parallel-mode state: cwd is on main with dirty files
|
|
135
|
+
// from another milestone (as if a prior merge's MergeConflictError
|
|
136
|
+
// left cwd on main and syncStateToProjectRoot wrote these files).
|
|
137
|
+
writeFileSync(join(repo, "dirty-from-m020.txt"), "should not be committed\n");
|
|
138
|
+
|
|
139
|
+
// Set up roadmap so mergeMilestoneToMain can find milestone metadata
|
|
140
|
+
mkdirSync(join(repo, ".gsd", "milestones", "M010"), { recursive: true });
|
|
141
|
+
writeFileSync(
|
|
142
|
+
join(repo, ".gsd", "milestones", "M010", "M010-ROADMAP.md"),
|
|
143
|
+
makeRoadmap("M010", "First milestone"),
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
process.chdir(repo);
|
|
147
|
+
|
|
148
|
+
const result = mergeMilestoneToMain(
|
|
149
|
+
repo,
|
|
150
|
+
"M010",
|
|
151
|
+
makeRoadmap("M010", "First milestone"),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
assert.ok(result.commitMessage.includes("M010"), "commit should be for M010");
|
|
155
|
+
|
|
156
|
+
// Verify the squash merge brought M010's work file
|
|
157
|
+
const mergeLog = run("git log --oneline --diff-filter=A -- m010.ts", repo);
|
|
158
|
+
assert.ok(mergeLog.length > 0, "m010.ts should be in a commit on main");
|
|
159
|
+
|
|
160
|
+
// The dirty file should NOT appear in the squash merge commit.
|
|
161
|
+
const squashCommit = run("git log --format=%H --grep='GSD-Milestone: M010' -1", repo);
|
|
162
|
+
assert.ok(squashCommit.length > 0, "should find the squash merge commit");
|
|
163
|
+
const filesInSquash = run(`git diff-tree --no-commit-id --name-only -r ${squashCommit}`, repo);
|
|
164
|
+
assert.ok(
|
|
165
|
+
!filesInSquash.includes("dirty-from-m020.txt"),
|
|
166
|
+
"dirty-from-m020.txt should NOT be in the squash merge commit",
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
|
|
8
|
+
import { assessInterruptedSession } from "../interrupted-session.ts";
|
|
9
|
+
|
|
10
|
+
function makeTmpBase(): string {
|
|
11
|
+
const base = join(tmpdir(), `gsd-auto-interrupted-${randomUUID()}`);
|
|
12
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
13
|
+
return base;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function cleanup(base: string): void {
|
|
17
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writeRoadmap(base: string, checked = false): void {
|
|
21
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
22
|
+
mkdirSync(join(milestoneDir, "slices", "S01", "tasks"), { recursive: true });
|
|
23
|
+
writeFileSync(
|
|
24
|
+
join(milestoneDir, "M001-ROADMAP.md"),
|
|
25
|
+
[
|
|
26
|
+
"# M001: Test Milestone",
|
|
27
|
+
"",
|
|
28
|
+
"## Vision",
|
|
29
|
+
"",
|
|
30
|
+
"Test milestone.",
|
|
31
|
+
"",
|
|
32
|
+
"## Success Criteria",
|
|
33
|
+
"",
|
|
34
|
+
"- It works.",
|
|
35
|
+
"",
|
|
36
|
+
"## Slices",
|
|
37
|
+
"",
|
|
38
|
+
`- [${checked ? "x" : " "}] **S01: Test slice** \`risk:low\``,
|
|
39
|
+
" After this: Demo",
|
|
40
|
+
"",
|
|
41
|
+
"## Boundary Map",
|
|
42
|
+
"",
|
|
43
|
+
"- S01 → terminal",
|
|
44
|
+
" - Produces: done",
|
|
45
|
+
" - Consumes: nothing",
|
|
46
|
+
].join("\n"),
|
|
47
|
+
"utf-8",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writeCompleteArtifacts(base: string): void {
|
|
52
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
53
|
+
const sliceDir = join(milestoneDir, "slices", "S01");
|
|
54
|
+
mkdirSync(sliceDir, { recursive: true });
|
|
55
|
+
writeFileSync(join(sliceDir, "S01-SUMMARY.md"), "# Summary\nDone.\n", "utf-8");
|
|
56
|
+
writeFileSync(join(sliceDir, "S01-UAT.md"), "# UAT\nPassed.\n", "utf-8");
|
|
57
|
+
writeFileSync(join(milestoneDir, "M001-SUMMARY.md"), "# Milestone Summary\nDone.\n", "utf-8");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function writeLock(base: string, unitType: string, unitId: string): void {
|
|
61
|
+
writeFileSync(
|
|
62
|
+
join(base, ".gsd", "auto.lock"),
|
|
63
|
+
JSON.stringify({
|
|
64
|
+
pid: 999999999,
|
|
65
|
+
startedAt: new Date().toISOString(),
|
|
66
|
+
unitType,
|
|
67
|
+
unitId,
|
|
68
|
+
unitStartedAt: new Date().toISOString(),
|
|
69
|
+
}, null, 2),
|
|
70
|
+
"utf-8",
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function writePausedSession(base: string, milestoneId = "M001", stepMode = false): void {
|
|
75
|
+
const runtimeDir = join(base, ".gsd", "runtime");
|
|
76
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
77
|
+
writeFileSync(
|
|
78
|
+
join(runtimeDir, "paused-session.json"),
|
|
79
|
+
JSON.stringify({ milestoneId, originalBasePath: base, stepMode }, null, 2),
|
|
80
|
+
"utf-8",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
test("direct /gsd auto stale complete repo yields stale classification with no recovery payload", async () => {
|
|
85
|
+
const base = makeTmpBase();
|
|
86
|
+
try {
|
|
87
|
+
writeRoadmap(base, true);
|
|
88
|
+
writeCompleteArtifacts(base);
|
|
89
|
+
writeLock(base, "execute-task", "M001/S01/T01");
|
|
90
|
+
|
|
91
|
+
const assessment = await assessInterruptedSession(base);
|
|
92
|
+
assert.equal(assessment.classification, "stale");
|
|
93
|
+
assert.equal(assessment.recoveryPrompt, null);
|
|
94
|
+
assert.equal(assessment.hasResumableDiskState, false);
|
|
95
|
+
} finally {
|
|
96
|
+
cleanup(base);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("direct /gsd auto paused-session metadata remains recoverable when work is unfinished", async () => {
|
|
101
|
+
const base = makeTmpBase();
|
|
102
|
+
try {
|
|
103
|
+
writeRoadmap(base, false);
|
|
104
|
+
writePausedSession(base, "M001", false);
|
|
105
|
+
writeLock(base, "execute-task", "M001/S01/T01");
|
|
106
|
+
|
|
107
|
+
const assessment = await assessInterruptedSession(base);
|
|
108
|
+
assert.equal(assessment.classification, "recoverable");
|
|
109
|
+
assert.equal(assessment.pausedSession?.milestoneId, "M001");
|
|
110
|
+
} finally {
|
|
111
|
+
cleanup(base);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("direct /gsd auto stale paused-session metadata is treated as stale when no resumable work remains", async () => {
|
|
116
|
+
const base = makeTmpBase();
|
|
117
|
+
try {
|
|
118
|
+
writeRoadmap(base, true);
|
|
119
|
+
writeCompleteArtifacts(base);
|
|
120
|
+
writePausedSession(base, "M999", true);
|
|
121
|
+
|
|
122
|
+
const assessment = await assessInterruptedSession(base);
|
|
123
|
+
assert.equal(assessment.classification, "stale");
|
|
124
|
+
assert.equal(assessment.hasResumableDiskState, false);
|
|
125
|
+
} finally {
|
|
126
|
+
cleanup(base);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("direct /gsd auto source only resumes paused-session metadata for recoverable state with real recovery signals", async () => {
|
|
131
|
+
const source = await import(`node:fs/promises`).then((fs) =>
|
|
132
|
+
fs.readFile(new URL("../auto.ts", import.meta.url), "utf-8")
|
|
133
|
+
);
|
|
134
|
+
assert.ok(source.includes('const shouldResumePausedSession ='));
|
|
135
|
+
assert.ok(source.includes('freshStartAssessment.classification === "recoverable"'));
|
|
136
|
+
assert.ok(source.includes('&& ('));
|
|
137
|
+
assert.ok(source.includes('freshStartAssessment.hasResumableDiskState'));
|
|
138
|
+
assert.ok(source.includes('|| !!freshStartAssessment.recoveryPrompt'));
|
|
139
|
+
assert.ok(source.includes('|| !!freshStartAssessment.lock'));
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("auto module imports successfully after interrupted-session changes", async () => {
|
|
143
|
+
const mod = await import(`../auto.ts?ts=${Date.now()}-${Math.random()}`);
|
|
144
|
+
assert.equal(typeof mod.startAuto, "function");
|
|
145
|
+
assert.equal(typeof mod.pauseAuto, "function");
|
|
146
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
|
|
8
|
+
import { assessInterruptedSession } from "../interrupted-session.ts";
|
|
9
|
+
|
|
10
|
+
function makeTmpBase(): string {
|
|
11
|
+
const base = join(tmpdir(), `gsd-smart-entry-${randomUUID()}`);
|
|
12
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
13
|
+
return base;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function cleanup(base: string): void {
|
|
17
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writeRoadmap(base: string, checked = false): void {
|
|
21
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
22
|
+
mkdirSync(join(milestoneDir, "slices", "S01", "tasks"), { recursive: true });
|
|
23
|
+
writeFileSync(
|
|
24
|
+
join(milestoneDir, "M001-ROADMAP.md"),
|
|
25
|
+
[
|
|
26
|
+
"# M001: Test Milestone",
|
|
27
|
+
"",
|
|
28
|
+
"## Vision",
|
|
29
|
+
"",
|
|
30
|
+
"Test milestone.",
|
|
31
|
+
"",
|
|
32
|
+
"## Success Criteria",
|
|
33
|
+
"",
|
|
34
|
+
"- It works.",
|
|
35
|
+
"",
|
|
36
|
+
"## Slices",
|
|
37
|
+
"",
|
|
38
|
+
`- [${checked ? "x" : " "}] **S01: Test slice** \`risk:low\``,
|
|
39
|
+
" After this: Demo",
|
|
40
|
+
"",
|
|
41
|
+
"## Boundary Map",
|
|
42
|
+
"",
|
|
43
|
+
"- S01 → terminal",
|
|
44
|
+
" - Produces: done",
|
|
45
|
+
" - Consumes: nothing",
|
|
46
|
+
].join("\n"),
|
|
47
|
+
"utf-8",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writeCompleteArtifacts(base: string): void {
|
|
52
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
53
|
+
const sliceDir = join(milestoneDir, "slices", "S01");
|
|
54
|
+
mkdirSync(sliceDir, { recursive: true });
|
|
55
|
+
writeFileSync(join(sliceDir, "S01-SUMMARY.md"), "# Summary\nDone.\n", "utf-8");
|
|
56
|
+
writeFileSync(join(sliceDir, "S01-UAT.md"), "# UAT\nPassed.\n", "utf-8");
|
|
57
|
+
writeFileSync(join(milestoneDir, "M001-SUMMARY.md"), "# Milestone Summary\nDone.\n", "utf-8");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function writePausedSession(base: string, milestoneId = "M001", stepMode = false): void {
|
|
61
|
+
const runtimeDir = join(base, ".gsd", "runtime");
|
|
62
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
63
|
+
writeFileSync(
|
|
64
|
+
join(runtimeDir, "paused-session.json"),
|
|
65
|
+
JSON.stringify({ milestoneId, originalBasePath: base, stepMode }, null, 2),
|
|
66
|
+
"utf-8",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function writeLock(base: string, unitType: string, unitId: string): void {
|
|
71
|
+
writeFileSync(
|
|
72
|
+
join(base, ".gsd", "auto.lock"),
|
|
73
|
+
JSON.stringify({
|
|
74
|
+
pid: 999999999,
|
|
75
|
+
startedAt: new Date().toISOString(),
|
|
76
|
+
unitType,
|
|
77
|
+
unitId,
|
|
78
|
+
unitStartedAt: new Date().toISOString(),
|
|
79
|
+
}, null, 2),
|
|
80
|
+
"utf-8",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
test("guided-flow stale complete scenario classifies as stale so the resume prompt can be suppressed", async () => {
|
|
85
|
+
const base = makeTmpBase();
|
|
86
|
+
try {
|
|
87
|
+
writeRoadmap(base, true);
|
|
88
|
+
writeCompleteArtifacts(base);
|
|
89
|
+
writeLock(base, "execute-task", "M001/S01/T01");
|
|
90
|
+
|
|
91
|
+
const assessment = await assessInterruptedSession(base);
|
|
92
|
+
assert.equal(assessment.classification, "stale");
|
|
93
|
+
assert.equal(assessment.recoveryPrompt, null);
|
|
94
|
+
} finally {
|
|
95
|
+
cleanup(base);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("guided-flow paused-session scenario classifies as recoverable so resume remains available", async () => {
|
|
100
|
+
const base = makeTmpBase();
|
|
101
|
+
try {
|
|
102
|
+
writeRoadmap(base, false);
|
|
103
|
+
writePausedSession(base);
|
|
104
|
+
writeLock(base, "execute-task", "M001/S01/T01");
|
|
105
|
+
|
|
106
|
+
const assessment = await assessInterruptedSession(base);
|
|
107
|
+
assert.equal(assessment.classification, "recoverable");
|
|
108
|
+
assert.equal(assessment.pausedSession?.milestoneId, "M001");
|
|
109
|
+
} finally {
|
|
110
|
+
cleanup(base);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("guided-flow stale paused-session scenario is suppressed when no resumable work remains", async () => {
|
|
115
|
+
const base = makeTmpBase();
|
|
116
|
+
try {
|
|
117
|
+
writeRoadmap(base, true);
|
|
118
|
+
writeCompleteArtifacts(base);
|
|
119
|
+
writePausedSession(base, "M999", true);
|
|
120
|
+
|
|
121
|
+
const assessment = await assessInterruptedSession(base);
|
|
122
|
+
assert.equal(assessment.classification, "stale");
|
|
123
|
+
assert.equal(assessment.hasResumableDiskState, false);
|
|
124
|
+
} finally {
|
|
125
|
+
cleanup(base);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("guided-flow source uses step-aware resume and clears stale paused metadata without changing discuss handoff semantics", () => {
|
|
130
|
+
const source = readFileSync(join(import.meta.dirname, "..", "guided-flow.ts"), "utf-8");
|
|
131
|
+
assert.ok(source.includes('const interrupted = await assessInterruptedSession(basePath);'));
|
|
132
|
+
assert.ok(source.includes('resumeLabel = interrupted.pausedSession?.stepMode'));
|
|
133
|
+
assert.ok(source.includes('step: interrupted.pausedSession?.stepMode ?? false'));
|
|
134
|
+
assert.ok(source.includes('unlinkSync(join(gsdRoot(basePath), "runtime", "paused-session.json"))'));
|
|
135
|
+
assert.ok(source.includes('pendingAutoStartMap.set(basePath,'));
|
|
136
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
ensureProjectWorkflowMcpConfig,
|
|
9
|
+
GSD_WORKFLOW_MCP_SERVER_NAME,
|
|
10
|
+
} from "../mcp-project-config.ts";
|
|
11
|
+
|
|
12
|
+
test("ensureProjectWorkflowMcpConfig creates .mcp.json with the workflow server", () => {
|
|
13
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-mcp-init-"));
|
|
14
|
+
mkdirSync(join(projectRoot, ".gsd"), { recursive: true });
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const result = ensureProjectWorkflowMcpConfig(projectRoot);
|
|
18
|
+
assert.equal(result.status, "created");
|
|
19
|
+
assert.equal(existsSync(result.configPath), true);
|
|
20
|
+
|
|
21
|
+
const parsed = JSON.parse(readFileSync(result.configPath, "utf-8")) as {
|
|
22
|
+
mcpServers?: Record<string, { command?: string; args?: string[]; env?: Record<string, string> }>;
|
|
23
|
+
};
|
|
24
|
+
const server = parsed.mcpServers?.[GSD_WORKFLOW_MCP_SERVER_NAME];
|
|
25
|
+
assert.ok(server, "workflow server should be written to mcpServers");
|
|
26
|
+
assert.equal(typeof server?.command, "string");
|
|
27
|
+
assert.equal(Array.isArray(server?.args), true);
|
|
28
|
+
assert.equal(server?.env?.GSD_WORKFLOW_PROJECT_ROOT, projectRoot);
|
|
29
|
+
assert.match(server?.env?.GSD_WORKFLOW_EXECUTORS_MODULE ?? "", /workflow-tool-executors\.js$/);
|
|
30
|
+
assert.match(server?.env?.GSD_WORKFLOW_WRITE_GATE_MODULE ?? "", /write-gate\.js$/);
|
|
31
|
+
} finally {
|
|
32
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("ensureProjectWorkflowMcpConfig preserves existing mcp servers", () => {
|
|
37
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-mcp-init-"));
|
|
38
|
+
mkdirSync(join(projectRoot, ".gsd"), { recursive: true });
|
|
39
|
+
const configPath = join(projectRoot, ".mcp.json");
|
|
40
|
+
|
|
41
|
+
writeFileSync(
|
|
42
|
+
configPath,
|
|
43
|
+
`${JSON.stringify({
|
|
44
|
+
mcpServers: {
|
|
45
|
+
railway: {
|
|
46
|
+
command: "npx",
|
|
47
|
+
args: ["railway-mcp"],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
}, null, 2)}\n`,
|
|
51
|
+
"utf-8",
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const result = ensureProjectWorkflowMcpConfig(projectRoot);
|
|
56
|
+
assert.equal(result.status, "updated");
|
|
57
|
+
|
|
58
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf-8")) as {
|
|
59
|
+
mcpServers?: Record<string, { command?: string; args?: string[] }>;
|
|
60
|
+
};
|
|
61
|
+
assert.deepEqual(parsed.mcpServers?.railway, {
|
|
62
|
+
command: "npx",
|
|
63
|
+
args: ["railway-mcp"],
|
|
64
|
+
});
|
|
65
|
+
assert.ok(parsed.mcpServers?.[GSD_WORKFLOW_MCP_SERVER_NAME]);
|
|
66
|
+
} finally {
|
|
67
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("ensureProjectWorkflowMcpConfig is idempotent when config is already current", () => {
|
|
72
|
+
const projectRoot = mkdtempSync(join(tmpdir(), "gsd-mcp-init-"));
|
|
73
|
+
mkdirSync(join(projectRoot, ".gsd"), { recursive: true });
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const first = ensureProjectWorkflowMcpConfig(projectRoot);
|
|
77
|
+
const second = ensureProjectWorkflowMcpConfig(projectRoot);
|
|
78
|
+
|
|
79
|
+
assert.equal(first.status, "created");
|
|
80
|
+
assert.equal(second.status, "unchanged");
|
|
81
|
+
assert.equal(first.configPath, second.configPath);
|
|
82
|
+
} finally {
|
|
83
|
+
rmSync(projectRoot, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
@@ -2,6 +2,7 @@ import test, { describe } from "node:test";
|
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
+
formatMcpInitResult,
|
|
5
6
|
formatMcpStatusReport,
|
|
6
7
|
formatMcpServerDetail,
|
|
7
8
|
type McpServerStatus,
|
|
@@ -101,3 +102,17 @@ describe("formatMcpServerDetail", () => {
|
|
|
101
102
|
assert.match(result, /disconnected/i);
|
|
102
103
|
});
|
|
103
104
|
});
|
|
105
|
+
|
|
106
|
+
describe("formatMcpInitResult", () => {
|
|
107
|
+
test("shows created message with config path", () => {
|
|
108
|
+
const result = formatMcpInitResult("created", "/tmp/project/.mcp.json", "/tmp/project");
|
|
109
|
+
assert.match(result, /created project mcp config/i);
|
|
110
|
+
assert.match(result, /\/tmp\/project\/\.mcp\.json/);
|
|
111
|
+
assert.match(result, /claude code/i);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("shows unchanged message when config is current", () => {
|
|
115
|
+
const result = formatMcpInitResult("unchanged", "/tmp/project/.mcp.json", "/tmp/project");
|
|
116
|
+
assert.match(result, /already up to date/i);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -32,6 +32,17 @@ test("isVerificationNotApplicable: 'None planned' is not applicable", () => {
|
|
|
32
32
|
assert.equal(isVerificationNotApplicable("None planned"), true);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
test("isVerificationNotApplicable: 'None — <rationale>' is not applicable (#3897)", () => {
|
|
36
|
+
assert.equal(
|
|
37
|
+
isVerificationNotApplicable("None — no new background jobs, workers, or lifecycle changes introduced."),
|
|
38
|
+
true,
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("isVerificationNotApplicable: em dash without spaces is not applicable (#3897)", () => {
|
|
43
|
+
assert.equal(isVerificationNotApplicable("none—inline"), true);
|
|
44
|
+
});
|
|
45
|
+
|
|
35
46
|
test("isVerificationNotApplicable: 'N/A' is not applicable", () => {
|
|
36
47
|
assert.equal(isVerificationNotApplicable("N/A"), true);
|
|
37
48
|
});
|