gsd-pi 2.82.0-dev.3709f22a5 → 2.82.0-dev.3a3c6509d
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/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +53 -29
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +1 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +12 -18
- package/dist/resources/extensions/gsd/auto-post-unit.js +1 -1
- package/dist/resources/extensions/gsd/auto-recovery.js +40 -13
- package/dist/resources/extensions/gsd/auto-start.js +3 -3
- package/dist/resources/extensions/gsd/auto-verification.js +17 -4
- package/dist/resources/extensions/gsd/auto-worktree.js +65 -9
- package/dist/resources/extensions/gsd/auto.js +0 -1
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +6 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +7 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +16 -4
- package/dist/resources/extensions/gsd/db/milestone-leases.js +24 -0
- package/dist/resources/extensions/gsd/forensics.js +3 -3
- package/dist/resources/extensions/gsd/git-service.js +6 -2
- package/dist/resources/extensions/gsd/gsd-db.js +20 -6
- package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -3
- package/dist/resources/extensions/gsd/guided-flow.js +8 -5
- package/dist/resources/extensions/gsd/queue-reorder-ui.js +30 -13
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/status-guards.js +7 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +17 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +1 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
- 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/api/browse-directories/route.js +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 +9 -9
- 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 +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.js +5 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.js +41 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.js.map +1 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.test.ts +49 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.ts +7 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +44 -3
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.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 +71 -97
- 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-ordering.test.js +12 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.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 +19 -8
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +53 -3
- package/packages/pi-coding-agent/src/core/sdk.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +75 -102
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-ordering.test.ts +14 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +23 -8
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/terminal.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.js +103 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.js.map +1 -0
- package/packages/pi-tui/dist/terminal.d.ts +2 -0
- package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal.js +12 -0
- package/packages/pi-tui/dist/terminal.js.map +1 -1
- package/packages/pi-tui/src/__tests__/terminal.test.ts +121 -0
- package/packages/pi-tui/src/terminal.ts +11 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +1 -1
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +9 -0
- package/src/resources/extensions/gsd/auto/phases.ts +60 -36
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +12 -18
- package/src/resources/extensions/gsd/auto-post-unit.ts +1 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +45 -11
- package/src/resources/extensions/gsd/auto-start.ts +2 -3
- package/src/resources/extensions/gsd/auto-verification.ts +22 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +74 -9
- package/src/resources/extensions/gsd/auto.ts +0 -1
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +8 -3
- package/src/resources/extensions/gsd/crash-recovery.ts +16 -2
- package/src/resources/extensions/gsd/db/milestone-leases.ts +26 -0
- package/src/resources/extensions/gsd/forensics.ts +3 -3
- package/src/resources/extensions/gsd/git-service.ts +6 -3
- package/src/resources/extensions/gsd/gsd-db.ts +18 -6
- package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -3
- package/src/resources/extensions/gsd/guided-flow.ts +8 -5
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +31 -13
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/status-guards.ts +8 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +29 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +76 -5
- package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +179 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +13 -1
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +29 -2
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +18 -0
- package/src/resources/extensions/gsd/workflow-mcp.ts +18 -1
- package/src/resources/extensions/gsd/worktree-manager.ts +1 -1
- /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → O6femb9LLl3nlgsDaYwS-}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → O6femb9LLl3nlgsDaYwS-}/_ssgManifest.js +0 -0
|
@@ -6,7 +6,7 @@ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "nod
|
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
|
|
9
|
-
import { cleanupAfterLoopExit, rerootCommandSession, stopAuto } from "../auto.ts";
|
|
9
|
+
import { cleanupAfterLoopExit, pauseAuto, rerootCommandSession, stopAuto } from "../auto.ts";
|
|
10
10
|
import { autoSession } from "../auto-runtime-state.ts";
|
|
11
11
|
import { closeDatabase, insertMilestone, insertSlice, openDatabase } from "../gsd-db.ts";
|
|
12
12
|
import { WorktreeLifecycle } from "../worktree-lifecycle.ts";
|
|
@@ -117,6 +117,34 @@ test("cleanupAfterLoopExit clears progress widget after stopAuto reset", async (
|
|
|
117
117
|
}
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
+
test("pauseAuto preserves artifact retry counts across pause/resume", async () => {
|
|
121
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-pause-retry-count-"));
|
|
122
|
+
const previousCwd = process.cwd();
|
|
123
|
+
const retryKey = "execute-task:M001/S01/T01";
|
|
124
|
+
|
|
125
|
+
autoSession.reset();
|
|
126
|
+
autoSession.active = true;
|
|
127
|
+
autoSession.verificationRetryCount.set(retryKey, 2);
|
|
128
|
+
autoSession.pendingVerificationRetry = {
|
|
129
|
+
unitId: "M001/S01/T01",
|
|
130
|
+
failureContext: "Missing expected artifact (attempt 2/3).",
|
|
131
|
+
attempt: 2,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
process.chdir(base);
|
|
136
|
+
await pauseAuto();
|
|
137
|
+
|
|
138
|
+
assert.equal(autoSession.paused, true);
|
|
139
|
+
assert.equal(autoSession.pendingVerificationRetry, null);
|
|
140
|
+
assert.equal(autoSession.verificationRetryCount.get(retryKey), 2);
|
|
141
|
+
} finally {
|
|
142
|
+
autoSession.reset();
|
|
143
|
+
process.chdir(previousCwd);
|
|
144
|
+
rmSync(base, { recursive: true, force: true });
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
120
148
|
test("cleanupAfterLoopExit restores project root through lifecycle and preserves chdir", async (t) => {
|
|
121
149
|
const base = mkdtempSync(join(tmpdir(), "gsd-cleanup-lifecycle-"));
|
|
122
150
|
const worktree = join(base, ".gsd", "worktrees", "M001");
|
|
@@ -807,6 +807,51 @@ test("hasImplementationArtifacts finds integration implementation-only commits w
|
|
|
807
807
|
}
|
|
808
808
|
});
|
|
809
809
|
|
|
810
|
+
test("hasImplementationArtifacts ignores corrupted milestone/* integration metadata", () => {
|
|
811
|
+
const base = makeGitBase();
|
|
812
|
+
try {
|
|
813
|
+
mkdirSync(join(base, "src"), { recursive: true });
|
|
814
|
+
writeFileSync(join(base, "src", "feature.ts"), "export function feature() {}\n");
|
|
815
|
+
execFileSync("git", ["add", "src/feature.ts"], { cwd: base, stdio: "ignore" });
|
|
816
|
+
execFileSync("git", ["commit", "-m", "feat: add milestone feature\n\nGSD-Task: S01/T01"], { cwd: base, stdio: "ignore" });
|
|
817
|
+
|
|
818
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
819
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
820
|
+
insertMilestone({ id: "M001", title: "Milestone One", status: "active" });
|
|
821
|
+
insertSlice({
|
|
822
|
+
id: "S01",
|
|
823
|
+
milestoneId: "M001",
|
|
824
|
+
title: "Slice One",
|
|
825
|
+
status: "complete",
|
|
826
|
+
risk: "low",
|
|
827
|
+
depends: [],
|
|
828
|
+
});
|
|
829
|
+
insertTask({
|
|
830
|
+
id: "T01",
|
|
831
|
+
sliceId: "S01",
|
|
832
|
+
milestoneId: "M001",
|
|
833
|
+
title: "Task One",
|
|
834
|
+
status: "complete",
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
execFileSync("git", ["checkout", "-b", "milestone/M001"], { cwd: base, stdio: "ignore" });
|
|
838
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
839
|
+
writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\nDone.");
|
|
840
|
+
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
841
|
+
execFileSync("git", ["commit", "-m", "chore: auto-commit after complete-milestone\n\nGSD-Unit: M001"], { cwd: base, stdio: "ignore" });
|
|
842
|
+
|
|
843
|
+
writeFileSync(
|
|
844
|
+
join(base, ".gsd", "milestones", "M001", "M001-META.json"),
|
|
845
|
+
JSON.stringify({ integrationBranch: "milestone/M001" }, null, 2) + "\n",
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
const result = hasImplementationArtifacts(base, "M001");
|
|
849
|
+
assert.equal(result, "present", "corrupted milestone integration metadata should fall back to main branch for artifact detection");
|
|
850
|
+
} finally {
|
|
851
|
+
cleanup(base);
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
|
|
810
855
|
test("hasImplementationArtifacts backfills untagged main implementation commits from completed task file hints", () => {
|
|
811
856
|
const base = makeGitBase();
|
|
812
857
|
try {
|
|
@@ -1043,24 +1088,50 @@ test("hasImplementationArtifacts binds GSD-Task trailer to milestone via DB stat
|
|
|
1043
1088
|
}
|
|
1044
1089
|
});
|
|
1045
1090
|
|
|
1046
|
-
test("hasImplementationArtifacts does not
|
|
1091
|
+
test("hasImplementationArtifacts does not claim Sxx/Tyy commit trailers across milestones when ownership points elsewhere", () => {
|
|
1047
1092
|
const base = makeGitBase();
|
|
1048
1093
|
try {
|
|
1049
1094
|
writeFileSync(join(base, ".git", "info", "exclude"), ".gsd/\n");
|
|
1095
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
1096
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
1097
|
+
insertMilestone({ id: "M001", title: "Milestone One", status: "active" });
|
|
1098
|
+
insertMilestone({ id: "M002", title: "Milestone Two", status: "active" });
|
|
1099
|
+
insertSlice({
|
|
1100
|
+
id: "S01",
|
|
1101
|
+
milestoneId: "M002",
|
|
1102
|
+
title: "Slice One",
|
|
1103
|
+
status: "complete",
|
|
1104
|
+
risk: "low",
|
|
1105
|
+
depends: [],
|
|
1106
|
+
});
|
|
1107
|
+
insertTask({
|
|
1108
|
+
id: "T01",
|
|
1109
|
+
sliceId: "S01",
|
|
1110
|
+
milestoneId: "M002",
|
|
1111
|
+
title: "Task One",
|
|
1112
|
+
status: "complete",
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1050
1115
|
mkdirSync(join(base, "src"), { recursive: true });
|
|
1051
1116
|
writeFileSync(join(base, "src", "feature.ts"), "export function feature() {}\n");
|
|
1052
1117
|
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
1053
1118
|
execFileSync(
|
|
1054
1119
|
"git",
|
|
1055
|
-
["commit", "-m", "feat: add feature\n\nGSD-Task: S01/T01"],
|
|
1120
|
+
["commit", "-m", "feat: add sibling feature\n\nGSD-Task: S01/T01"],
|
|
1056
1121
|
{ cwd: base, stdio: "ignore" },
|
|
1057
1122
|
);
|
|
1058
1123
|
|
|
1059
|
-
const
|
|
1124
|
+
const m001Result = hasImplementationArtifacts(base, "M001");
|
|
1125
|
+
const m002Result = hasImplementationArtifacts(base, "M002");
|
|
1060
1126
|
assert.equal(
|
|
1061
|
-
|
|
1127
|
+
m001Result,
|
|
1062
1128
|
"absent",
|
|
1063
|
-
"
|
|
1129
|
+
"Sxx/Tyy commit trailers owned by M002 must not be attributed to M001",
|
|
1130
|
+
);
|
|
1131
|
+
assert.equal(
|
|
1132
|
+
m002Result,
|
|
1133
|
+
"present",
|
|
1134
|
+
"the owning milestone should still claim the implementation-bearing commit",
|
|
1064
1135
|
);
|
|
1065
1136
|
} finally {
|
|
1066
1137
|
cleanup(base);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
|
|
8
|
+
import { checkoutBranchWithStashGuard } from "../auto-worktree.ts";
|
|
9
|
+
|
|
10
|
+
function git(args: string[], cwd: string): string {
|
|
11
|
+
return execFileSync("git", args, {
|
|
12
|
+
cwd,
|
|
13
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
14
|
+
encoding: "utf-8",
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function createRepo(t: { after: (fn: () => void) => void }): string {
|
|
19
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "checkout-stash-guard-")));
|
|
20
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
21
|
+
git(["init"], dir);
|
|
22
|
+
git(["config", "user.email", "test@example.com"], dir);
|
|
23
|
+
git(["config", "user.name", "Test User"], dir);
|
|
24
|
+
writeFileSync(join(dir, "note.txt"), "base\n");
|
|
25
|
+
git(["add", "note.txt"], dir);
|
|
26
|
+
git(["commit", "-m", "init"], dir);
|
|
27
|
+
git(["branch", "-M", "main"], dir);
|
|
28
|
+
return dir;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("checkoutBranchWithStashGuard", () => {
|
|
32
|
+
test("restores dirty working tree after successful checkout", (t) => {
|
|
33
|
+
const repo = createRepo(t);
|
|
34
|
+
git(["checkout", "-b", "milestone/M001"], repo);
|
|
35
|
+
git(["checkout", "main"], repo);
|
|
36
|
+
|
|
37
|
+
writeFileSync(join(repo, "note.txt"), "dirty\n");
|
|
38
|
+
|
|
39
|
+
checkoutBranchWithStashGuard(repo, "milestone/M001", "test-success");
|
|
40
|
+
|
|
41
|
+
const branch = git(["branch", "--show-current"], repo).trim();
|
|
42
|
+
assert.equal(branch, "milestone/M001");
|
|
43
|
+
const content = git(["show", "HEAD:note.txt"], repo).trim();
|
|
44
|
+
assert.equal(content, "base");
|
|
45
|
+
const wtContent = readFileSync(join(repo, "note.txt"), "utf8");
|
|
46
|
+
assert.equal(wtContent, "dirty\n");
|
|
47
|
+
const status = git(["status", "--porcelain"], repo);
|
|
48
|
+
assert.match(status, /note\.txt/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("restores dirty working tree when checkout throws", (t) => {
|
|
52
|
+
const repo = createRepo(t);
|
|
53
|
+
writeFileSync(join(repo, "note.txt"), "dirty\n");
|
|
54
|
+
|
|
55
|
+
assert.throws(
|
|
56
|
+
() => checkoutBranchWithStashGuard(repo, "milestone/DOES-NOT-EXIST", "test-failure"),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const status = git(["status", "--porcelain"], repo);
|
|
60
|
+
assert.match(status, /note\.txt/);
|
|
61
|
+
const stashList = git(["stash", "list"], repo).trim();
|
|
62
|
+
assert.equal(stashList, "");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("surfaces distinct error when checkout succeeds but stash pop conflicts", (t) => {
|
|
66
|
+
const repo = createRepo(t);
|
|
67
|
+
// Branch B has a divergent version of note.txt so popping a stash made
|
|
68
|
+
// against main will conflict after the checkout to B.
|
|
69
|
+
git(["checkout", "-b", "milestone/B"], repo);
|
|
70
|
+
writeFileSync(join(repo, "note.txt"), "B-version\n");
|
|
71
|
+
git(["add", "note.txt"], repo);
|
|
72
|
+
git(["commit", "-m", "B"], repo);
|
|
73
|
+
git(["checkout", "main"], repo);
|
|
74
|
+
|
|
75
|
+
writeFileSync(join(repo, "note.txt"), "local\n");
|
|
76
|
+
|
|
77
|
+
assert.throws(
|
|
78
|
+
() => checkoutBranchWithStashGuard(repo, "milestone/B", "test-pop-failure"),
|
|
79
|
+
/checkout to 'milestone\/B' succeeded but stash restore failed/,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const branch = git(["branch", "--show-current"], repo).trim();
|
|
83
|
+
assert.equal(branch, "milestone/B");
|
|
84
|
+
const stashList = git(["stash", "list"], repo).trim();
|
|
85
|
+
assert.match(stashList, /gsd: checkout stash/);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -225,6 +225,25 @@ test("clearLock removes the session_file row for the active worker", (t) => {
|
|
|
225
225
|
"session_file row deleted by clearLock");
|
|
226
226
|
});
|
|
227
227
|
|
|
228
|
+
test("clearLock marks stale worker crashed when no current-process worker matches", (t) => {
|
|
229
|
+
const base = makeBase();
|
|
230
|
+
t.after(() => cleanup(base));
|
|
231
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
232
|
+
const projectRoot = normalizeRealPath(base);
|
|
233
|
+
const workerId = registerAutoWorker({ projectRootRealpath: projectRoot });
|
|
234
|
+
|
|
235
|
+
setRuntimeKv("worker", workerId, "session_file", "/tmp/stale-session.jsonl");
|
|
236
|
+
setWorkerPid(workerId, 99999);
|
|
237
|
+
expireWorker(workerId);
|
|
238
|
+
assert.ok(readCrashLock(base), "stale worker is detected before clearLock");
|
|
239
|
+
|
|
240
|
+
clearLock(base);
|
|
241
|
+
|
|
242
|
+
assert.equal(getAutoWorker(workerId)?.status, "crashed");
|
|
243
|
+
assert.equal(getRuntimeKv("worker", workerId, "session_file"), null);
|
|
244
|
+
assert.equal(readCrashLock(base), null);
|
|
245
|
+
});
|
|
246
|
+
|
|
228
247
|
test("clearStaleWorkerLock crashes stale worker and cancels latest active dispatch", (t) => {
|
|
229
248
|
const base = makeBase();
|
|
230
249
|
t.after(() => cleanup(base));
|
|
@@ -264,3 +283,27 @@ test("clearStaleWorkerLock crashes stale worker and cancels latest active dispat
|
|
|
264
283
|
assert.equal(getRuntimeKv("worker", workerId, "session_file"), null);
|
|
265
284
|
assert.equal(readCrashLock(base), null);
|
|
266
285
|
});
|
|
286
|
+
|
|
287
|
+
test("clearLock marks stale worker crashed and releases held milestone lease", (t) => {
|
|
288
|
+
const base = makeBase();
|
|
289
|
+
t.after(() => cleanup(base));
|
|
290
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
291
|
+
insertMilestone({ id: "M001", title: "T", status: "active" });
|
|
292
|
+
const projectRoot = normalizeRealPath(base);
|
|
293
|
+
const workerId = registerAutoWorker({ projectRootRealpath: projectRoot });
|
|
294
|
+
const lease = claimMilestoneLease(workerId, "M001");
|
|
295
|
+
assert.equal(lease.ok, true);
|
|
296
|
+
if (!lease.ok) return;
|
|
297
|
+
|
|
298
|
+
setWorkerPid(workerId, 99999);
|
|
299
|
+
expireWorker(workerId);
|
|
300
|
+
assert.ok(readCrashLock(base), "stale worker is detected before clearLock");
|
|
301
|
+
|
|
302
|
+
clearLock(base);
|
|
303
|
+
|
|
304
|
+
assert.equal(getAutoWorker(workerId)?.status, "crashed");
|
|
305
|
+
const leaseRow = _getAdapter()!.prepare(
|
|
306
|
+
`SELECT status FROM milestone_leases WHERE milestone_id = :m`,
|
|
307
|
+
).get({ ":m": "M001" }) as { status: string } | undefined;
|
|
308
|
+
assert.equal(leaseRow?.status, "released");
|
|
309
|
+
});
|
|
@@ -10,6 +10,7 @@ import assert from "node:assert/strict";
|
|
|
10
10
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { tmpdir } from "node:os";
|
|
13
|
+
import { execFileSync } from "node:child_process";
|
|
13
14
|
|
|
14
15
|
import { DISPATCH_RULES, type DispatchContext } from "../auto-dispatch.ts";
|
|
15
16
|
import { closeDatabase, insertMilestone, openDatabase } from "../gsd-db.ts";
|
|
@@ -23,6 +24,14 @@ function makeBase(): string {
|
|
|
23
24
|
return base;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
function initGitRepo(base: string): void {
|
|
28
|
+
execFileSync("git", ["init"], { cwd: base, stdio: "ignore" });
|
|
29
|
+
execFileSync("git", ["config", "user.email", "test@test.com"], { cwd: base, stdio: "ignore" });
|
|
30
|
+
execFileSync("git", ["config", "user.name", "Test"], { cwd: base, stdio: "ignore" });
|
|
31
|
+
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
32
|
+
execFileSync("git", ["commit", "-m", "initial"], { cwd: base, stdio: "ignore" });
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
function buildDispatchCtx(basePath: string): DispatchContext {
|
|
27
36
|
return {
|
|
28
37
|
basePath,
|
|
@@ -76,6 +85,24 @@ describe("completing-milestone dispatch guard (#4324)", () => {
|
|
|
76
85
|
assert.equal(result?.unitType, "complete-milestone");
|
|
77
86
|
assert.equal(result?.unitId, "M001");
|
|
78
87
|
});
|
|
88
|
+
|
|
89
|
+
test("dispatches complete-milestone when only .gsd/ files exist in git history (#5097)", async () => {
|
|
90
|
+
base = makeBase();
|
|
91
|
+
rmSync(join(base, "implementation.txt"), { force: true });
|
|
92
|
+
initGitRepo(base);
|
|
93
|
+
writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\n");
|
|
94
|
+
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
95
|
+
execFileSync("git", ["commit", "-m", "chore: planning artifacts only"], { cwd: base, stdio: "ignore" });
|
|
96
|
+
|
|
97
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
98
|
+
insertMilestone({ id: "M001", title: "Milestone One", status: "active" });
|
|
99
|
+
|
|
100
|
+
const result = await rule.match(buildDispatchCtx(base));
|
|
101
|
+
|
|
102
|
+
assert.equal(result?.action, "dispatch");
|
|
103
|
+
assert.equal(result?.unitType, "complete-milestone");
|
|
104
|
+
assert.equal(result?.unitId, "M001");
|
|
105
|
+
});
|
|
79
106
|
});
|
|
80
107
|
|
|
81
108
|
describe("complete phase dispatch guard (#5683)", () => {
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
checkpointDatabase,
|
|
38
38
|
refreshOpenDatabaseFromDisk,
|
|
39
39
|
tryCreateMemoriesFts,
|
|
40
|
+
_isLikelyWslDrvFsPathForTest,
|
|
40
41
|
} from '../gsd-db.ts';
|
|
41
42
|
import { _resetLogs, peekLogs, setStderrLoggingEnabled } from '../workflow-logger.ts';
|
|
42
43
|
|
|
@@ -345,6 +346,16 @@ describe('gsd-db', () => {
|
|
|
345
346
|
});
|
|
346
347
|
});
|
|
347
348
|
|
|
349
|
+
test('gsd-db: detects WSL DrvFs mount paths for conservative pragmas', () => {
|
|
350
|
+
withPlatform('linux', () => {
|
|
351
|
+
assert.equal(_isLikelyWslDrvFsPathForTest('/mnt/d/code/project/.gsd/gsd.db'), true);
|
|
352
|
+
assert.equal(_isLikelyWslDrvFsPathForTest('/tmp/gsd.db'), false);
|
|
353
|
+
});
|
|
354
|
+
withPlatform('darwin', () => {
|
|
355
|
+
assert.equal(_isLikelyWslDrvFsPathForTest('/mnt/d/code/project/.gsd/gsd.db'), false);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
348
359
|
test('gsd-db: transaction rollback on error', () => {
|
|
349
360
|
openDatabase(':memory:');
|
|
350
361
|
|
|
@@ -1168,6 +1168,15 @@ describe('git-service', async () => {
|
|
|
1168
1168
|
rmSync(repo, { recursive: true, force: true });
|
|
1169
1169
|
});
|
|
1170
1170
|
|
|
1171
|
+
test('Integration branch: rejects milestone branches', () => {
|
|
1172
|
+
const repo = initBranchTestRepo();
|
|
1173
|
+
|
|
1174
|
+
writeIntegrationBranch(repo, "M001", "milestone/M001");
|
|
1175
|
+
assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), null, "milestone branches are not recorded as integration branch");
|
|
1176
|
+
|
|
1177
|
+
rmSync(repo, { recursive: true, force: true });
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1171
1180
|
// ─── writeIntegrationBranch: still records legitimate branches ────────
|
|
1172
1181
|
|
|
1173
1182
|
test('Integration branch: records non-ephemeral gsd branches', () => {
|
|
@@ -22,6 +22,7 @@ import type { IterationContext, LoopState, PreDispatchData, IterationData } from
|
|
|
22
22
|
import type { SessionLockStatus } from "../session-lock.js";
|
|
23
23
|
import { runDispatch, runUnitPhase, runPreDispatch, runFinalize } from "../auto/phases.js";
|
|
24
24
|
import { readUnitRuntimeRecord } from "../unit-runtime.js";
|
|
25
|
+
import { ModelPolicyDispatchBlockedError } from "../auto-model-selection.js";
|
|
25
26
|
import {
|
|
26
27
|
closeDatabase,
|
|
27
28
|
insertMilestone,
|
|
@@ -160,6 +161,8 @@ function makeIC(
|
|
|
160
161
|
pi: {
|
|
161
162
|
sendMessage: () => {},
|
|
162
163
|
setModel: async () => true,
|
|
164
|
+
getThinkingLevel: () => "off",
|
|
165
|
+
setThinkingLevel: () => {},
|
|
163
166
|
} as any,
|
|
164
167
|
s: makeSession(),
|
|
165
168
|
deps,
|
|
@@ -868,6 +871,49 @@ test("runUnitPhase increments unitDispatchCount for repeated artifact-missing re
|
|
|
868
871
|
assert.equal(ic.s.unitDispatchCount.get("execute-task/M001/S01/T01"), 2);
|
|
869
872
|
});
|
|
870
873
|
|
|
874
|
+
test("runUnitPhase pre-dispatch model validation failures do not emit unit-start or dispatch runtime state", async (t) => {
|
|
875
|
+
const capture = createEventCapture();
|
|
876
|
+
const base = mkdtempSync(join(tmpdir(), `gsd-pre-dispatch-block-${randomUUID()}`));
|
|
877
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
878
|
+
|
|
879
|
+
const deps = makeMockDeps(capture, {
|
|
880
|
+
selectAndApplyModel: async () => {
|
|
881
|
+
throw new ModelPolicyDispatchBlockedError("execute-task", "M001/S01/T01", []);
|
|
882
|
+
},
|
|
883
|
+
});
|
|
884
|
+
const ic = makeIC(deps, {
|
|
885
|
+
s: {
|
|
886
|
+
...makeSession(),
|
|
887
|
+
basePath: base,
|
|
888
|
+
} as any,
|
|
889
|
+
});
|
|
890
|
+
const iterData: IterationData = {
|
|
891
|
+
unitType: "execute-task",
|
|
892
|
+
unitId: "M001/S01/T01",
|
|
893
|
+
prompt: "do stuff",
|
|
894
|
+
finalPrompt: "do stuff",
|
|
895
|
+
pauseAfterUatDispatch: false,
|
|
896
|
+
state: { phase: "executing", activeMilestone: { id: "M001" }, activeSlice: { id: "S01" }, registry: [], blockers: [] } as any,
|
|
897
|
+
mid: "M001",
|
|
898
|
+
midTitle: "Test",
|
|
899
|
+
isRetry: false,
|
|
900
|
+
previousTier: undefined,
|
|
901
|
+
};
|
|
902
|
+
const loopState: LoopState = { recentUnits: [{ key: "execute-task/M001/S01/T01" }], stuckRecoveryAttempts: 0, consecutiveFinalizeTimeouts: 0 };
|
|
903
|
+
|
|
904
|
+
await assert.rejects(() => runUnitPhase(ic, iterData, loopState), ModelPolicyDispatchBlockedError);
|
|
905
|
+
await assert.rejects(() => runUnitPhase(ic, iterData, loopState), ModelPolicyDispatchBlockedError);
|
|
906
|
+
|
|
907
|
+
const startEvents = capture.events.filter(e => e.eventType === "unit-start");
|
|
908
|
+
assert.equal(startEvents.length, 0, "pre-dispatch validation failures must not emit unit-start");
|
|
909
|
+
assert.equal(ic.s.unitDispatchCount.get("execute-task/M001/S01/T01") ?? 0, 0, "dispatch count must not increment on pre-dispatch validation failure");
|
|
910
|
+
assert.equal(
|
|
911
|
+
readUnitRuntimeRecord(base, "execute-task", "M001/S01/T01"),
|
|
912
|
+
null,
|
|
913
|
+
"pre-dispatch validation failures must not persist a dispatched runtime record",
|
|
914
|
+
);
|
|
915
|
+
});
|
|
916
|
+
|
|
871
917
|
test("all events from a mock iteration have monotonically increasing seq and same flowId", async () => {
|
|
872
918
|
const capture = createEventCapture();
|
|
873
919
|
const { resolveAgentEnd, _resetPendingResolve } = await import("../auto/resolve.js");
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import {
|
|
4
|
+
chmodSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdtempSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
realpathSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
import { delimiter, join } from "node:path";
|
|
15
|
+
import { execFileSync } from "node:child_process";
|
|
16
|
+
|
|
17
|
+
import { mergeMilestoneToMain } from "../auto-worktree.ts";
|
|
18
|
+
import { closeDatabase, openDatabase } from "../gsd-db.ts";
|
|
19
|
+
import { GIT_NO_PROMPT_ENV } from "../git-constants.js";
|
|
20
|
+
import { _clearGsdRootCache } from "../paths.ts";
|
|
21
|
+
import { _resetServiceCache } from "../worktree.ts";
|
|
22
|
+
import { worktreePath } from "../worktree-manager.ts";
|
|
23
|
+
|
|
24
|
+
function git(args: string[], cwd: string): string {
|
|
25
|
+
return execFileSync("git", args, {
|
|
26
|
+
cwd,
|
|
27
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
28
|
+
encoding: "utf-8",
|
|
29
|
+
}).trim();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function withPlatform<T>(platform: NodeJS.Platform, fn: () => T): T {
|
|
33
|
+
const descriptor = Object.getOwnPropertyDescriptor(process, "platform");
|
|
34
|
+
Object.defineProperty(process, "platform", { value: platform });
|
|
35
|
+
try {
|
|
36
|
+
return fn();
|
|
37
|
+
} finally {
|
|
38
|
+
if (descriptor) {
|
|
39
|
+
Object.defineProperty(process, "platform", descriptor);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function realGitPath(): string {
|
|
45
|
+
const gitExecPath = execFileSync("git", ["--exec-path"], {
|
|
46
|
+
encoding: "utf-8",
|
|
47
|
+
}).trim();
|
|
48
|
+
return join(gitExecPath, process.platform === "win32" ? "git.exe" : "git");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function installGitShim(bin: string, probePath: string): void {
|
|
52
|
+
const shim = join(bin, "git-proxy.cjs");
|
|
53
|
+
writeFileSync(
|
|
54
|
+
shim,
|
|
55
|
+
`
|
|
56
|
+
const { appendFileSync, existsSync } = require("node:fs");
|
|
57
|
+
const { join } = require("node:path");
|
|
58
|
+
const { spawnSync } = require("node:child_process");
|
|
59
|
+
|
|
60
|
+
const realGit = ${JSON.stringify(realGitPath())};
|
|
61
|
+
const probePath = ${JSON.stringify(probePath)};
|
|
62
|
+
const args = process.argv.slice(2);
|
|
63
|
+
|
|
64
|
+
if (args[0] === "merge" && args[1] === "--squash") {
|
|
65
|
+
const sidecars = [
|
|
66
|
+
join(process.cwd(), ".gsd", "gsd.db-wal"),
|
|
67
|
+
join(process.cwd(), ".gsd", "gsd.db-shm"),
|
|
68
|
+
];
|
|
69
|
+
const locked = sidecars.find((path) => existsSync(path));
|
|
70
|
+
if (locked) {
|
|
71
|
+
appendFileSync(probePath, "blocked:" + locked + "\\n");
|
|
72
|
+
console.error("error: local changes would be overwritten by merge");
|
|
73
|
+
console.error("\\t" + locked);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
appendFileSync(probePath, "clean\\n");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const result = spawnSync(realGit, args, { stdio: "inherit", env: process.env });
|
|
80
|
+
process.exit(result.status ?? 1);
|
|
81
|
+
`,
|
|
82
|
+
"utf-8",
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (process.platform === "win32") {
|
|
86
|
+
writeFileSync(join(bin, "git.cmd"), `@echo off\r\nnode "%~dp0git-proxy.cjs" %*\r\n`, "utf-8");
|
|
87
|
+
} else {
|
|
88
|
+
const executable = join(bin, "git");
|
|
89
|
+
writeFileSync(executable, `#!/bin/sh\nexec node "${shim}" "$@"\n`, "utf-8");
|
|
90
|
+
chmodSync(executable, 0o755);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function createRepo(root: string): { repo: string; worktree: string } {
|
|
95
|
+
const repo = join(root, "repo");
|
|
96
|
+
mkdirSync(repo, { recursive: true });
|
|
97
|
+
git(["init"], repo);
|
|
98
|
+
git(["config", "user.email", "test@test.com"], repo);
|
|
99
|
+
git(["config", "user.name", "Test"], repo);
|
|
100
|
+
writeFileSync(join(repo, ".gitignore"), ".gsd/\n", "utf-8");
|
|
101
|
+
writeFileSync(join(repo, "README.md"), "# test\n", "utf-8");
|
|
102
|
+
git(["add", "."], repo);
|
|
103
|
+
git(["commit", "-m", "init"], repo);
|
|
104
|
+
git(["branch", "-M", "main"], repo);
|
|
105
|
+
|
|
106
|
+
git(["checkout", "-b", "milestone/M001"], repo);
|
|
107
|
+
writeFileSync(join(repo, "feature.txt"), "milestone change\n", "utf-8");
|
|
108
|
+
mkdirSync(join(repo, ".gsd"), { recursive: true });
|
|
109
|
+
writeFileSync(join(repo, ".gsd", "gsd.db-shm"), "milestone placeholder\n", "utf-8");
|
|
110
|
+
git(["add", "feature.txt"], repo);
|
|
111
|
+
git(["add", "-f", ".gsd/gsd.db-shm"], repo);
|
|
112
|
+
git(["commit", "-m", "feat: milestone change"], repo);
|
|
113
|
+
git(["checkout", "main"], repo);
|
|
114
|
+
|
|
115
|
+
const wt = worktreePath(repo, "M001");
|
|
116
|
+
mkdirSync(join(repo, ".gsd", "worktrees"), { recursive: true });
|
|
117
|
+
git(["worktree", "add", wt, "milestone/M001"], repo);
|
|
118
|
+
return { repo, worktree: wt };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
test("mergeMilestoneToMain keeps the Windows DB cycle closed through squash merge", () => {
|
|
122
|
+
const savedCwd = process.cwd();
|
|
123
|
+
const originalPath = process.env.PATH ?? "";
|
|
124
|
+
const gitEnv = GIT_NO_PROMPT_ENV as NodeJS.ProcessEnv;
|
|
125
|
+
const originalGitEnvPath = gitEnv.PATH;
|
|
126
|
+
const originalHome = process.env.HOME;
|
|
127
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
128
|
+
|
|
129
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-db-cycle-")));
|
|
130
|
+
const fakeHome = join(root, "home");
|
|
131
|
+
const bin = join(root, "bin");
|
|
132
|
+
const probePath = join(root, "merge-probe.txt");
|
|
133
|
+
mkdirSync(fakeHome, { recursive: true });
|
|
134
|
+
mkdirSync(bin, { recursive: true });
|
|
135
|
+
installGitShim(bin, probePath);
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
process.env.HOME = fakeHome;
|
|
139
|
+
process.env.GSD_HOME = join(fakeHome, ".gsd");
|
|
140
|
+
_clearGsdRootCache();
|
|
141
|
+
_resetServiceCache();
|
|
142
|
+
|
|
143
|
+
const { repo, worktree } = createRepo(root);
|
|
144
|
+
mkdirSync(join(repo, ".gsd"), { recursive: true });
|
|
145
|
+
|
|
146
|
+
withPlatform("win32", () => {
|
|
147
|
+
assert.equal(openDatabase(join(repo, ".gsd", "gsd.db")), true);
|
|
148
|
+
assert.equal(existsSync(join(repo, ".gsd", "gsd.db-shm")), true);
|
|
149
|
+
|
|
150
|
+
process.env.PATH = `${bin}${delimiter}${originalPath}`;
|
|
151
|
+
gitEnv.PATH = process.env.PATH;
|
|
152
|
+
process.chdir(worktree);
|
|
153
|
+
|
|
154
|
+
const result = mergeMilestoneToMain(repo, "M001", "# M001: Windows DB cycle\n");
|
|
155
|
+
assert.equal(result.codeFilesChanged, true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
assert.equal(git(["show", "HEAD:feature.txt"], repo), "milestone change");
|
|
159
|
+
assert.equal(readFileSync(probePath, "utf-8"), "clean\n");
|
|
160
|
+
} finally {
|
|
161
|
+
closeDatabase();
|
|
162
|
+
process.chdir(savedCwd);
|
|
163
|
+
process.env.PATH = originalPath;
|
|
164
|
+
gitEnv.PATH = originalGitEnvPath;
|
|
165
|
+
if (originalHome === undefined) {
|
|
166
|
+
delete process.env.HOME;
|
|
167
|
+
} else {
|
|
168
|
+
process.env.HOME = originalHome;
|
|
169
|
+
}
|
|
170
|
+
if (originalGsdHome === undefined) {
|
|
171
|
+
delete process.env.GSD_HOME;
|
|
172
|
+
} else {
|
|
173
|
+
process.env.GSD_HOME = originalGsdHome;
|
|
174
|
+
}
|
|
175
|
+
_clearGsdRootCache();
|
|
176
|
+
_resetServiceCache();
|
|
177
|
+
if (existsSync(root)) rmSync(root, { recursive: true, force: true });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
@@ -243,7 +243,8 @@ test("#4781 phase 2: validate-milestone rule writes pass-through VALIDATION for
|
|
|
243
243
|
const content = readFileSync(validationPath, "utf-8");
|
|
244
244
|
assert.match(content, /verdict: pass/);
|
|
245
245
|
assert.match(content, /skip_validation: true/);
|
|
246
|
-
assert.match(content, /trivial-scope pipeline variant
|
|
246
|
+
assert.match(content, /trivial-scope pipeline variant/);
|
|
247
|
+
assert.doesNotMatch(content, /#[0-9]{3,}/, "validation output must not include tracker-style refs");
|
|
247
248
|
});
|
|
248
249
|
|
|
249
250
|
test("#4781 phase 2: validate-milestone skip path does not persist gates without a real slice", async (t) => {
|