@opengsd/gsd-pi 1.9.0-dev.97ef11e3 → 1.9.0-dev.e10b60d4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +4 -24
- package/dist/resources/extensions/gsd/auto/run-unit.js +1 -19
- package/dist/resources/extensions/gsd/auto-start.js +9 -29
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +3 -7
- package/dist/resources/extensions/gsd/auto.js +1 -12
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +2 -21
- package/dist/resources/extensions/gsd/flat-phase-migration.js +0 -12
- package/dist/resources/extensions/gsd/gsd-db.js +31 -75
- package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -20
- 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 +10 -10
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
- package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/README.md +3 -9
- package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cli.js +15 -121
- package/packages/gsd-cloud/dist/cli.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.js +1 -7
- package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts +3 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.js +7 -9
- package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
- package/packages/gsd-cloud/package.json +2 -2
- package/packages/mcp-server/dist/cli-runner.d.ts +0 -3
- package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
- package/packages/mcp-server/dist/cli-runner.js +33 -119
- package/packages/mcp-server/dist/cli-runner.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +0 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +0 -17
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop.ts +4 -48
- package/src/resources/extensions/gsd/auto/run-unit.ts +1 -20
- package/src/resources/extensions/gsd/auto-start.ts +8 -31
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +3 -10
- package/src/resources/extensions/gsd/auto.ts +1 -13
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -27
- package/src/resources/extensions/gsd/flat-phase-migration.ts +0 -14
- package/src/resources/extensions/gsd/gsd-db.ts +33 -85
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +0 -156
- package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +1 -21
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +1 -74
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +0 -83
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +9 -8
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +0 -32
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +0 -13
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +0 -60
- package/src/resources/extensions/gsd/worktree-state-projection.ts +24 -21
- package/packages/gsd-cloud/dist/background-cli.test.d.ts +0 -2
- package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +0 -1
- package/packages/gsd-cloud/dist/background-cli.test.js +0 -248
- package/packages/gsd-cloud/dist/background-cli.test.js.map +0 -1
- package/packages/gsd-cloud/dist/runtime-process.d.ts +0 -27
- package/packages/gsd-cloud/dist/runtime-process.d.ts.map +0 -1
- package/packages/gsd-cloud/dist/runtime-process.js +0 -272
- package/packages/gsd-cloud/dist/runtime-process.js.map +0 -1
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts +0 -2
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +0 -1
- package/packages/gsd-cloud/dist/runtime-process.test.js +0 -97
- package/packages/gsd-cloud/dist/runtime-process.test.js.map +0 -1
- /package/dist/web/standalone/.next/static/{DXD9fZPnqtjZSlT5xdC3v → V9MAVyYVPboQUB9KTTAMi}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{DXD9fZPnqtjZSlT5xdC3v → V9MAVyYVPboQUB9KTTAMi}/_ssgManifest.js +0 -0
|
@@ -41,7 +41,6 @@ import { claimMilestoneLease } from "../db/milestone-leases.js";
|
|
|
41
41
|
import { recordDispatchClaim, markCanceled } from "../db/unit-dispatches.js";
|
|
42
42
|
import { setRuntimeKv, getRuntimeKv } from "../db/runtime-kv.js";
|
|
43
43
|
import { SourceObservationStore } from "../source-observations.js";
|
|
44
|
-
import { autoCommitCurrentBranch } from "../worktree.js";
|
|
45
44
|
|
|
46
45
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
47
46
|
|
|
@@ -833,37 +832,6 @@ test("runUnit returns cancelled when session creation fails", async () => {
|
|
|
833
832
|
assert.equal(pi.calls.length, 0);
|
|
834
833
|
});
|
|
835
834
|
|
|
836
|
-
test("runUnit returns cancelled when command context lacks newSession", async () => {
|
|
837
|
-
_resetPendingResolve();
|
|
838
|
-
|
|
839
|
-
const ctx = {
|
|
840
|
-
...makeMockCtx(),
|
|
841
|
-
ui: {
|
|
842
|
-
notify: () => {},
|
|
843
|
-
setStatus: () => {},
|
|
844
|
-
setWorkingMessage: () => {},
|
|
845
|
-
},
|
|
846
|
-
sessionManager: {
|
|
847
|
-
getEntries: () => [],
|
|
848
|
-
},
|
|
849
|
-
modelRegistry: {
|
|
850
|
-
getProviderAuthMode: () => undefined,
|
|
851
|
-
isProviderRequestReady: () => true,
|
|
852
|
-
},
|
|
853
|
-
} as any;
|
|
854
|
-
const pi = makeMockPi();
|
|
855
|
-
const s = makeMockSession();
|
|
856
|
-
s.cmdCtx = {} as any;
|
|
857
|
-
|
|
858
|
-
const result = await runUnit(ctx, pi, s, "task", "T01", "prompt");
|
|
859
|
-
|
|
860
|
-
assert.equal(result.status, "cancelled");
|
|
861
|
-
assert.equal(result.errorContext?.category, "session-failed");
|
|
862
|
-
assert.equal(result.errorContext?.isTransient, false);
|
|
863
|
-
assert.match(result.errorContext?.message ?? "", /missing newSession/);
|
|
864
|
-
assert.equal(pi.calls.length, 0);
|
|
865
|
-
});
|
|
866
|
-
|
|
867
835
|
test("runUnit: TypeError from newSession is classified as structural (isTransient: false)", async () => {
|
|
868
836
|
// Regression for #572: a TypeError thrown from newSession (e.g. "something is
|
|
869
837
|
// not a function") indicates a programming error, not a transient provider
|
|
@@ -1614,130 +1582,6 @@ test("autoLoop exits when s.active is set to false", async (t) => {
|
|
|
1614
1582
|
);
|
|
1615
1583
|
});
|
|
1616
1584
|
|
|
1617
|
-
test("autoLoop stops before dispatch when command context lacks newSession", async () => {
|
|
1618
|
-
_resetPendingResolve();
|
|
1619
|
-
|
|
1620
|
-
const ctx = makeMockCtx();
|
|
1621
|
-
ctx.ui.setStatus = () => {};
|
|
1622
|
-
const pi = makeMockPi();
|
|
1623
|
-
const s = makeLoopSession({
|
|
1624
|
-
cmdCtx: {
|
|
1625
|
-
getContextUsage: () => ({ percent: 10, tokens: 1000, limit: 10000 }),
|
|
1626
|
-
},
|
|
1627
|
-
});
|
|
1628
|
-
let stopReason: string | undefined;
|
|
1629
|
-
let preserveWorktree: boolean | undefined;
|
|
1630
|
-
let deriveCalled = false;
|
|
1631
|
-
|
|
1632
|
-
try {
|
|
1633
|
-
const deps = makeMockDeps({
|
|
1634
|
-
stopAuto: async (_ctx, _pi, reason, options) => {
|
|
1635
|
-
stopReason = reason;
|
|
1636
|
-
preserveWorktree = options?.preserveWorktree;
|
|
1637
|
-
s.active = false;
|
|
1638
|
-
},
|
|
1639
|
-
deriveState: async () => {
|
|
1640
|
-
deriveCalled = true;
|
|
1641
|
-
throw new Error("deriveState should not run without command session support");
|
|
1642
|
-
},
|
|
1643
|
-
});
|
|
1644
|
-
|
|
1645
|
-
await autoLoop(ctx, pi, s, deps);
|
|
1646
|
-
|
|
1647
|
-
assert.equal(stopReason, "Auto-mode has no command context for dispatch.");
|
|
1648
|
-
assert.equal(preserveWorktree, true);
|
|
1649
|
-
assert.equal(deriveCalled, false);
|
|
1650
|
-
assert.equal(pi.calls.length, 0);
|
|
1651
|
-
} finally {
|
|
1652
|
-
rmSync(s.basePath, { recursive: true, force: true });
|
|
1653
|
-
}
|
|
1654
|
-
});
|
|
1655
|
-
|
|
1656
|
-
test("autoLoop commits open unit work when command context lacks newSession", async () => {
|
|
1657
|
-
_resetPendingResolve();
|
|
1658
|
-
|
|
1659
|
-
const ctx = makeMockCtx();
|
|
1660
|
-
ctx.ui.setStatus = () => {};
|
|
1661
|
-
const pi = makeMockPi();
|
|
1662
|
-
const s = makeLoopSession({
|
|
1663
|
-
cmdCtx: {
|
|
1664
|
-
getContextUsage: () => ({ percent: 10, tokens: 1000, limit: 10000 }),
|
|
1665
|
-
},
|
|
1666
|
-
currentUnit: { type: "execute-task", id: "T01", startedAt: Date.now() },
|
|
1667
|
-
});
|
|
1668
|
-
const outputPath = join(s.basePath, "executor-output.txt");
|
|
1669
|
-
let autoCommitArgs: { unitType: string; unitId: string } | undefined;
|
|
1670
|
-
let preserveWorktree: boolean | undefined;
|
|
1671
|
-
|
|
1672
|
-
try {
|
|
1673
|
-
const deps = makeMockDeps({
|
|
1674
|
-
autoCommitUnit: async (basePath, unitType, unitId) => {
|
|
1675
|
-
autoCommitArgs = { unitType, unitId };
|
|
1676
|
-
return autoCommitCurrentBranch(basePath, unitType, unitId);
|
|
1677
|
-
},
|
|
1678
|
-
stopAuto: async (_ctx, _pi, _reason, options) => {
|
|
1679
|
-
preserveWorktree = options?.preserveWorktree;
|
|
1680
|
-
s.active = false;
|
|
1681
|
-
},
|
|
1682
|
-
deriveState: async () => {
|
|
1683
|
-
throw new Error("deriveState should not run without command session support");
|
|
1684
|
-
},
|
|
1685
|
-
});
|
|
1686
|
-
|
|
1687
|
-
writeFileSync(outputPath, "open unit work before stop\n", "utf-8");
|
|
1688
|
-
|
|
1689
|
-
await autoLoop(ctx, pi, s, deps);
|
|
1690
|
-
|
|
1691
|
-
assert.deepEqual(autoCommitArgs, { unitType: "execute-task", unitId: "T01" });
|
|
1692
|
-
assert.equal(preserveWorktree, true);
|
|
1693
|
-
const committed = execSync("git show HEAD:executor-output.txt", {
|
|
1694
|
-
cwd: s.basePath,
|
|
1695
|
-
encoding: "utf-8",
|
|
1696
|
-
});
|
|
1697
|
-
assert.equal(committed, "open unit work before stop\n");
|
|
1698
|
-
assert.equal(pi.calls.length, 0);
|
|
1699
|
-
} finally {
|
|
1700
|
-
rmSync(s.basePath, { recursive: true, force: true });
|
|
1701
|
-
}
|
|
1702
|
-
});
|
|
1703
|
-
|
|
1704
|
-
test("autoLoop snapshots dirty work when unit dispatch crashes", async () => {
|
|
1705
|
-
_resetPendingResolve();
|
|
1706
|
-
|
|
1707
|
-
const ctx = makeMockCtx();
|
|
1708
|
-
ctx.ui.setStatus = () => {};
|
|
1709
|
-
const pi = makeMockPi();
|
|
1710
|
-
const s = makeLoopSession();
|
|
1711
|
-
const outputPath = join(s.basePath, "executor-output.txt");
|
|
1712
|
-
let autoCommitCalls = 0;
|
|
1713
|
-
|
|
1714
|
-
try {
|
|
1715
|
-
const deps = makeMockDeps({
|
|
1716
|
-
ensurePreconditions: () => {
|
|
1717
|
-
writeFileSync(outputPath, "saved before crash\n", "utf-8");
|
|
1718
|
-
throw new Error("dispatch crash after file write");
|
|
1719
|
-
},
|
|
1720
|
-
autoCommitUnit: async (basePath, unitType, unitId) => {
|
|
1721
|
-
autoCommitCalls++;
|
|
1722
|
-
const commitMsg = autoCommitCurrentBranch(basePath, unitType, unitId);
|
|
1723
|
-
s.active = false;
|
|
1724
|
-
return commitMsg;
|
|
1725
|
-
},
|
|
1726
|
-
});
|
|
1727
|
-
|
|
1728
|
-
await autoLoop(ctx, pi, s, deps);
|
|
1729
|
-
|
|
1730
|
-
assert.equal(autoCommitCalls, 1);
|
|
1731
|
-
const committed = execSync("git show HEAD:executor-output.txt", {
|
|
1732
|
-
cwd: s.basePath,
|
|
1733
|
-
encoding: "utf-8",
|
|
1734
|
-
});
|
|
1735
|
-
assert.equal(committed, "saved before crash\n");
|
|
1736
|
-
} finally {
|
|
1737
|
-
rmSync(s.basePath, { recursive: true, force: true });
|
|
1738
|
-
}
|
|
1739
|
-
});
|
|
1740
|
-
|
|
1741
1585
|
test("autoLoop pauses visibly when Auto Orchestration Module is not wired", async () => {
|
|
1742
1586
|
_resetPendingResolve();
|
|
1743
1587
|
|
|
@@ -4,7 +4,7 @@ import { existsSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
|
|
7
|
-
import { hasGitIndexLockForTest
|
|
7
|
+
import { hasGitIndexLockForTest } from "../auto-start.ts";
|
|
8
8
|
|
|
9
9
|
test("bootstrapAutoSession detects .git/index.lock without deleting it", (t) => {
|
|
10
10
|
const base = mkdtempSync(join(tmpdir(), "gsd-index-lock-"));
|
|
@@ -24,23 +24,3 @@ test("bootstrapAutoSession reports no index lock when the lock file is absent",
|
|
|
24
24
|
|
|
25
25
|
assert.equal(hasGitIndexLockForTest(base), false);
|
|
26
26
|
});
|
|
27
|
-
|
|
28
|
-
test("bootstrapAutoSession removes empty legacy milestones dir in flat-phase projects", (t) => {
|
|
29
|
-
const base = mkdtempSync(join(tmpdir(), "gsd-flat-bootstrap-clean-"));
|
|
30
|
-
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
31
|
-
mkdirSync(join(base, ".gsd", "phases"), { recursive: true });
|
|
32
|
-
mkdirSync(join(base, ".gsd", "milestones"), { recursive: true });
|
|
33
|
-
|
|
34
|
-
assert.equal(reconcileFlatPhaseBootstrapLayout(base), true);
|
|
35
|
-
assert.equal(existsSync(join(base, ".gsd", "phases")), true);
|
|
36
|
-
assert.equal(existsSync(join(base, ".gsd", "milestones")), false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("bootstrapAutoSession creates phases instead of milestones for empty GSD state", (t) => {
|
|
40
|
-
const base = mkdtempSync(join(tmpdir(), "gsd-flat-bootstrap-init-"));
|
|
41
|
-
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
42
|
-
|
|
43
|
-
assert.equal(reconcileFlatPhaseBootstrapLayout(base), true);
|
|
44
|
-
assert.equal(existsSync(join(base, ".gsd", "phases")), true);
|
|
45
|
-
assert.equal(existsSync(join(base, ".gsd", "milestones")), false);
|
|
46
|
-
});
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { describe, test } from "node:test";
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
|
-
import { mkdtempSync, mkdirSync, realpathSync, rmSync
|
|
7
|
-
import { execSync } from "node:child_process";
|
|
6
|
+
import { mkdtempSync, mkdirSync, realpathSync, rmSync } from "node:fs";
|
|
8
7
|
import { join } from "node:path";
|
|
9
8
|
import { tmpdir } from "node:os";
|
|
10
9
|
|
|
@@ -97,78 +96,6 @@ describe("hook dispatch session workspace root", () => {
|
|
|
97
96
|
assert.equal(dispatched, true);
|
|
98
97
|
assert.deepEqual(newSessionOptions, { workspaceRoot: basePath });
|
|
99
98
|
});
|
|
100
|
-
|
|
101
|
-
test("dispatchHookUnit commits in-flight work when command context lacks newSession", async (t) => {
|
|
102
|
-
const originalCwd = process.cwd();
|
|
103
|
-
const basePath = realpathSync(mkdtempSync(join(tmpdir(), "gsd-hook-nosnap-")));
|
|
104
|
-
mkdirSync(join(basePath, ".gsd"), { recursive: true });
|
|
105
|
-
execSync("git init --initial-branch=main", { cwd: basePath, stdio: "ignore" });
|
|
106
|
-
execSync("git config user.email test@test.com", { cwd: basePath, stdio: "ignore" });
|
|
107
|
-
execSync("git config user.name Test", { cwd: basePath, stdio: "ignore" });
|
|
108
|
-
execSync("git commit --allow-empty -m init", { cwd: basePath, stdio: "ignore" });
|
|
109
|
-
|
|
110
|
-
autoSession.reset();
|
|
111
|
-
autoSession.active = true;
|
|
112
|
-
autoSession.basePath = basePath;
|
|
113
|
-
autoSession.originalBasePath = basePath;
|
|
114
|
-
// Simulate an in-flight unit whose executor already wrote files, then a hook
|
|
115
|
-
// fires with a command context missing newSession.
|
|
116
|
-
autoSession.currentUnit = {
|
|
117
|
-
type: "execute-task",
|
|
118
|
-
id: "M001/S01/T01",
|
|
119
|
-
startedAt: Date.now(),
|
|
120
|
-
};
|
|
121
|
-
t.after(() => {
|
|
122
|
-
try {
|
|
123
|
-
process.chdir(originalCwd);
|
|
124
|
-
} catch {
|
|
125
|
-
// best effort cleanup after cwd-sensitive dispatch tests
|
|
126
|
-
}
|
|
127
|
-
autoSession.reset();
|
|
128
|
-
rmSync(basePath, { recursive: true, force: true });
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
const outputPath = join(basePath, "executor-output.txt");
|
|
132
|
-
writeFileSync(outputPath, "in-flight executor work\n", "utf-8");
|
|
133
|
-
|
|
134
|
-
// Command context intentionally lacks newSession — the guard must snapshot
|
|
135
|
-
// the dirty work before stopping instead of discarding it.
|
|
136
|
-
const ctx = {
|
|
137
|
-
ui: {
|
|
138
|
-
notify: () => {},
|
|
139
|
-
setStatus: () => {},
|
|
140
|
-
setWidget: () => {},
|
|
141
|
-
},
|
|
142
|
-
modelRegistry: {
|
|
143
|
-
getAvailable: () => [],
|
|
144
|
-
},
|
|
145
|
-
sessionManager: {
|
|
146
|
-
getSessionFile: () => join(basePath, "session.jsonl"),
|
|
147
|
-
},
|
|
148
|
-
};
|
|
149
|
-
const pi = {
|
|
150
|
-
sendMessage: () => {},
|
|
151
|
-
setModel: async () => true,
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const dispatched = await dispatchHookUnit(
|
|
155
|
-
ctx as any,
|
|
156
|
-
pi as any,
|
|
157
|
-
"review",
|
|
158
|
-
"execute-task",
|
|
159
|
-
"M001/S01/T01",
|
|
160
|
-
"review the completed unit",
|
|
161
|
-
undefined,
|
|
162
|
-
basePath,
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
assert.equal(dispatched, false);
|
|
166
|
-
const committed = execSync("git show HEAD:executor-output.txt", {
|
|
167
|
-
cwd: basePath,
|
|
168
|
-
encoding: "utf-8",
|
|
169
|
-
});
|
|
170
|
-
assert.equal(committed, "in-flight executor work\n");
|
|
171
|
-
});
|
|
172
99
|
});
|
|
173
100
|
|
|
174
101
|
describe("deep setup approval questions pause immediately", () => {
|
|
@@ -167,45 +167,6 @@ test("checkEngineHealth keeps PLAN checkbox divergence after stale projection fl
|
|
|
167
167
|
assert.match(readFileSync(plan.planPath, "utf-8"), /- \[ \] \*\*T01\*\*:/);
|
|
168
168
|
});
|
|
169
169
|
|
|
170
|
-
test("checkEngineHealth ignores stale suffixed flat-phase duplicate when bare milestone exists", async (t) => {
|
|
171
|
-
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-checkbox-flat-duplicate-"));
|
|
172
|
-
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
173
|
-
|
|
174
|
-
const gsdDir = join(base, ".gsd");
|
|
175
|
-
mkdirSync(gsdDir, { recursive: true });
|
|
176
|
-
|
|
177
|
-
openDatabase(join(gsdDir, "gsd.db"));
|
|
178
|
-
insertMilestone({ id: "M003", title: "M003-vaz73w: New milestone M003-vaz73w", status: "active" });
|
|
179
|
-
insertSlice({ id: "S01", milestoneId: "M003", title: "Slice", status: "pending", risk: "low", depends: [], sequence: 1 });
|
|
180
|
-
for (const taskId of ["T01", "T02", "T03"]) {
|
|
181
|
-
insertTask({ id: taskId, milestoneId: "M003", sliceId: "S01", title: taskId, status: "complete", sequence: Number(taskId.slice(1)) });
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
insertMilestone({ id: "M003-vaz73w", title: "New milestone M003-vaz73w", status: "active" });
|
|
185
|
-
insertSlice({ id: "S01", milestoneId: "M003-vaz73w", title: "Slice", status: "pending", risk: "low", depends: [], sequence: 1 });
|
|
186
|
-
for (const taskId of ["T01", "T02", "T03"]) {
|
|
187
|
-
insertTask({ id: taskId, milestoneId: "M003-vaz73w", sliceId: "S01", title: taskId, status: "complete", sequence: Number(taskId.slice(1)) });
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const canonicalPlan = await renderPlanFromDb(base, "M003", "S01");
|
|
191
|
-
const staleDir = join(gsdDir, "phases", "03-vaz73w-new-milestone-m003-vaz73w");
|
|
192
|
-
mkdirSync(staleDir, { recursive: true });
|
|
193
|
-
writeFileSync(
|
|
194
|
-
join(staleDir, "03-01-PLAN.md"),
|
|
195
|
-
canonicalPlan.content.replace("- [x] **T03**:", "- [ ] **T03**:"),
|
|
196
|
-
"utf-8",
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
const issues: any[] = [];
|
|
200
|
-
await checkEngineHealth(base, issues, []);
|
|
201
|
-
|
|
202
|
-
assert.deepEqual(
|
|
203
|
-
issues.filter((issue) => issue.code === "checkbox_db_status_divergence").map((issue) => issue.unitId),
|
|
204
|
-
[],
|
|
205
|
-
"stale suffixed duplicate phase projection must not compete with the bare milestone projection",
|
|
206
|
-
);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
170
|
test("checkEngineHealth reads task checkboxes from the <tasks> block, not a stray line above it", async (t) => {
|
|
210
171
|
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-task-section-"));
|
|
211
172
|
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
@@ -467,50 +428,6 @@ test("checkEngineHealth repair prunes stale phases artifact rows with present mi
|
|
|
467
428
|
assert.deepEqual(rows.map((row) => row.path), []);
|
|
468
429
|
});
|
|
469
430
|
|
|
470
|
-
test("checkEngineHealth repair prunes stale phases artifact rows with renamed flat-phase files", async (t) => {
|
|
471
|
-
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-prune-renamed-flat-phase-artifact-"));
|
|
472
|
-
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
473
|
-
|
|
474
|
-
const gsdDir = join(base, ".gsd");
|
|
475
|
-
const stalePath = "phases/01-new-milestone-m001/01-01-PLAN.md";
|
|
476
|
-
const replacementPath = "phases/01-lokably-brand-foundation-and-welcome-pag/01-01-PLAN.md";
|
|
477
|
-
mkdirSync(join(gsdDir, "phases", "01-lokably-brand-foundation-and-welcome-pag"), { recursive: true });
|
|
478
|
-
writeFileSync(join(gsdDir, replacementPath), "# Plan\n", "utf-8");
|
|
479
|
-
|
|
480
|
-
openDatabase(join(gsdDir, "gsd.db"));
|
|
481
|
-
insertArtifact({
|
|
482
|
-
path: stalePath,
|
|
483
|
-
artifact_type: "PLAN",
|
|
484
|
-
milestone_id: "M001",
|
|
485
|
-
slice_id: "S01",
|
|
486
|
-
task_id: null,
|
|
487
|
-
full_content: "# stale plan\n",
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
const beforeIssues: any[] = [];
|
|
491
|
-
await checkEngineHealth(base, beforeIssues, []);
|
|
492
|
-
|
|
493
|
-
const beforeIssue = beforeIssues.find((issue) => issue.code === "artifact_file_missing" && issue.file === stalePath);
|
|
494
|
-
assert.ok(beforeIssue, "stale renamed phases row should be reported when repair is off");
|
|
495
|
-
assert.equal(beforeIssue.fixable, true, "renamed phases rows with a flat replacement should be fixable");
|
|
496
|
-
|
|
497
|
-
const repairIssues: any[] = [];
|
|
498
|
-
const fixes: string[] = [];
|
|
499
|
-
await checkEngineHealth(base, repairIssues, fixes, { repair: true });
|
|
500
|
-
|
|
501
|
-
assert.equal(
|
|
502
|
-
repairIssues.some((issue) => issue.code === "artifact_file_missing" && issue.file === stalePath),
|
|
503
|
-
false,
|
|
504
|
-
"repair should not report stale rows it pruned",
|
|
505
|
-
);
|
|
506
|
-
assert.ok(fixes.includes(`pruned stale flat-phase artifact row ${stalePath}`));
|
|
507
|
-
|
|
508
|
-
const rows = _getAdapter()!
|
|
509
|
-
.prepare("SELECT path FROM artifacts ORDER BY path")
|
|
510
|
-
.all() as Array<{ path: string }>;
|
|
511
|
-
assert.deepEqual(rows.map((row) => row.path), []);
|
|
512
|
-
});
|
|
513
|
-
|
|
514
431
|
test("checkEngineHealth repair keeps phases rows whose own file is still present", async (t) => {
|
|
515
432
|
const base = mkdtempSync(join(tmpdir(), "gsd-doctor-keep-present-phase-artifact-"));
|
|
516
433
|
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
@@ -122,7 +122,7 @@ test("migrateToFlatPhase creates a backup", async () => {
|
|
|
122
122
|
assert.ok(backups.length >= 1, "at least one migrate-* backup dir should exist");
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
test("migrateToFlatPhase
|
|
125
|
+
test("migrateToFlatPhase backs up existing phases projection before clearing it", async () => {
|
|
126
126
|
const base = makeTmp();
|
|
127
127
|
const reviewPath = join(base, ".gsd", "phases", "01-foundation", "PLAN-REVIEW.md");
|
|
128
128
|
mkdirSync(join(base, ".gsd", "phases", "01-foundation"), { recursive: true });
|
|
@@ -133,10 +133,11 @@ test("migrateToFlatPhase removes disposable phases snapshot after successful mig
|
|
|
133
133
|
const backupRoot = join(base, ".gsd-backups");
|
|
134
134
|
const backups = readdirSync(backupRoot).filter(d => d.startsWith("migrate-"));
|
|
135
135
|
assert.equal(backups.length, 1, "one migrate backup should exist");
|
|
136
|
+
const backedUpReview = join(backupRoot, backups[0]!, "__phases", "01-foundation", "PLAN-REVIEW.md");
|
|
136
137
|
assert.equal(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
"
|
|
138
|
+
readFileSync(backedUpReview, "utf-8"),
|
|
139
|
+
"# Plan Review\n\nHand-authored review.",
|
|
140
|
+
"pre-existing phases/ files should have a recovery copy",
|
|
140
141
|
);
|
|
141
142
|
});
|
|
142
143
|
|
|
@@ -201,7 +202,7 @@ test("failed migration rolls back without leaking a .gsd-backups/migrate-* dir",
|
|
|
201
202
|
assert.equal(leaked.length, 0, "rollback must delete the migrate-* backup it created");
|
|
202
203
|
});
|
|
203
204
|
|
|
204
|
-
test("resumed migration
|
|
205
|
+
test("resumed migration stores phases backup in retained migrate snapshot", async () => {
|
|
205
206
|
const base = makeTmp();
|
|
206
207
|
const milestonesPath = join(base, ".gsd", "milestones");
|
|
207
208
|
const migratingPath = join(base, ".gsd", "milestones.migrating");
|
|
@@ -217,9 +218,9 @@ test("resumed migration removes disposable phases snapshot after success", async
|
|
|
217
218
|
const backups = readdirSync(backupRoot).filter((d) => d.startsWith("migrate-"));
|
|
218
219
|
assert.equal(backups.length, 1, "resumed migration should create one retained migrate backup");
|
|
219
220
|
assert.equal(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
"
|
|
221
|
+
readFileSync(join(backupRoot, backups[0]!, "__phases", "01-foundation", "PLAN-REVIEW.md"), "utf-8"),
|
|
222
|
+
"# Plan Review\n\nResume recovery copy.",
|
|
223
|
+
"resume should retain the phases snapshot under .gsd-backups",
|
|
223
224
|
);
|
|
224
225
|
assert.equal(
|
|
225
226
|
existsSync(join(migratingPath, "__phases")),
|
|
@@ -46,9 +46,6 @@ import {
|
|
|
46
46
|
tryCreateMemoriesFts,
|
|
47
47
|
_isLikelyWslDrvFsPathForTest,
|
|
48
48
|
_shouldAttemptVacuumRecoveryForTest,
|
|
49
|
-
insertArtifact,
|
|
50
|
-
getArtifact,
|
|
51
|
-
upsertMilestonePlanning,
|
|
52
49
|
} from '../gsd-db.ts';
|
|
53
50
|
import { MigrationBackupError } from '../db-migration-backup.ts';
|
|
54
51
|
import { _resetLogs, peekLogs, setStderrLoggingEnabled } from '../workflow-logger.ts';
|
|
@@ -360,35 +357,6 @@ describe('gsd-db', () => {
|
|
|
360
357
|
closeDatabase();
|
|
361
358
|
});
|
|
362
359
|
|
|
363
|
-
test("gsd-db: upsertMilestonePlanning rewrites placeholder phase artifact paths to final title slug", () => {
|
|
364
|
-
openDatabase(":memory:");
|
|
365
|
-
|
|
366
|
-
const stalePath = "phases/01-new-milestone-m001/01-ROADMAP.md";
|
|
367
|
-
const finalPath = "phases/01-lokably-brand-foundation-and-welcome-pag/01-ROADMAP.md";
|
|
368
|
-
insertMilestone({ id: "M001", title: "New milestone M001", status: "active" });
|
|
369
|
-
insertArtifact({
|
|
370
|
-
path: stalePath,
|
|
371
|
-
artifact_type: "ROADMAP",
|
|
372
|
-
milestone_id: "M001",
|
|
373
|
-
slice_id: null,
|
|
374
|
-
task_id: null,
|
|
375
|
-
full_content: "# Placeholder roadmap\n",
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
upsertMilestonePlanning("M001", {
|
|
379
|
-
title: "Lokably brand foundation and welcome page rebuild",
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
assert.equal(getArtifact(stalePath), null, "placeholder-derived artifact row should be removed");
|
|
383
|
-
assert.equal(
|
|
384
|
-
getArtifact(finalPath)?.full_content,
|
|
385
|
-
"# Placeholder roadmap\n",
|
|
386
|
-
"artifact row should move to the final title-derived phase path",
|
|
387
|
-
);
|
|
388
|
-
|
|
389
|
-
closeDatabase();
|
|
390
|
-
});
|
|
391
|
-
|
|
392
360
|
test('gsd-db: active_requirements view excludes superseded', () => {
|
|
393
361
|
openDatabase(':memory:');
|
|
394
362
|
|
|
@@ -1428,19 +1428,6 @@ process.exit(result.status ?? 0);
|
|
|
1428
1428
|
rmSync(repo, { recursive: true, force: true });
|
|
1429
1429
|
});
|
|
1430
1430
|
|
|
1431
|
-
test('Integration branch: write does not create legacy milestones scaffold', () => {
|
|
1432
|
-
const repo = initBranchTestRepo();
|
|
1433
|
-
mkdirSync(join(repo, ".gsd", "phases"), { recursive: true });
|
|
1434
|
-
|
|
1435
|
-
writeIntegrationBranch(repo, "M001", "f-123-new-thing");
|
|
1436
|
-
|
|
1437
|
-
assert.deepStrictEqual(readIntegrationBranch(repo, "M001"), "f-123-new-thing");
|
|
1438
|
-
assert.ok(existsSync(join(repo, ".gsd", "M001-META.json")), "metadata is written at the flat .gsd root");
|
|
1439
|
-
assert.ok(!existsSync(join(repo, ".gsd", "milestones")), "legacy milestones/ root is not created");
|
|
1440
|
-
|
|
1441
|
-
rmSync(repo, { recursive: true, force: true });
|
|
1442
|
-
});
|
|
1443
|
-
|
|
1444
1431
|
// ─── writeIntegrationBranch: updates when branch changes (#300) ──────
|
|
1445
1432
|
|
|
1446
1433
|
test('Integration branch: updates on branch change', () => {
|
|
@@ -117,8 +117,8 @@ test("projectRootToWorktree projects flat-phase discuss artifacts before dispatc
|
|
|
117
117
|
new WorktreeStateProjection().projectRootToWorktree(scope);
|
|
118
118
|
|
|
119
119
|
assert.ok(
|
|
120
|
-
|
|
121
|
-
"
|
|
120
|
+
existsSync(join(worktree, ".gsd", "milestones", "M001", "M001-META.json")),
|
|
121
|
+
"milestone metadata is projected into the worktree",
|
|
122
122
|
);
|
|
123
123
|
assert.ok(
|
|
124
124
|
existsSync(join(worktree, ".gsd", "phases", "01-foundation", "01-CONTEXT.md")),
|
|
@@ -246,66 +246,6 @@ describe('worktree-sync-milestones', async () => {
|
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
// ─── 7b. flat-phase sync skips empty legacy milestones root ───────────
|
|
250
|
-
console.log('\n=== 7b. flat-phase sync skips empty legacy milestones root ===');
|
|
251
|
-
{
|
|
252
|
-
const mainBase = mkdtempSync(join(tmpdir(), 'gsd-wt-sync-flat-main-'));
|
|
253
|
-
const wtBase = mkdtempSync(join(tmpdir(), 'gsd-wt-sync-flat-wt-'));
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
const phaseDir = join(mainBase, '.gsd', 'phases', '01-foundation');
|
|
257
|
-
mkdirSync(phaseDir, { recursive: true });
|
|
258
|
-
mkdirSync(join(mainBase, '.gsd', 'milestones'), { recursive: true });
|
|
259
|
-
mkdirSync(join(wtBase, '.gsd'), { recursive: true });
|
|
260
|
-
writeFileSync(join(phaseDir, '01-CONTEXT.md'), '# Foundation\n');
|
|
261
|
-
|
|
262
|
-
syncGsdStateToWorktree(mainBase, wtBase);
|
|
263
|
-
|
|
264
|
-
assert.ok(
|
|
265
|
-
existsSync(join(wtBase, '.gsd', 'phases', '01-foundation', '01-CONTEXT.md')),
|
|
266
|
-
'flat-phase artifact is synced to worktree',
|
|
267
|
-
);
|
|
268
|
-
assert.ok(
|
|
269
|
-
!existsSync(join(wtBase, '.gsd', 'milestones')),
|
|
270
|
-
'empty legacy milestones/ root is not recreated in worktree',
|
|
271
|
-
);
|
|
272
|
-
} finally {
|
|
273
|
-
rmSync(mainBase, { recursive: true, force: true });
|
|
274
|
-
rmSync(wtBase, { recursive: true, force: true });
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// ─── 7c. flat-phase sync skips metadata-only legacy milestone dirs ────
|
|
279
|
-
console.log('\n=== 7c. flat-phase sync skips metadata-only legacy milestone dirs ===');
|
|
280
|
-
{
|
|
281
|
-
const mainBase = mkdtempSync(join(tmpdir(), 'gsd-wt-sync-flat-meta-main-'));
|
|
282
|
-
const wtBase = mkdtempSync(join(tmpdir(), 'gsd-wt-sync-flat-meta-wt-'));
|
|
283
|
-
|
|
284
|
-
try {
|
|
285
|
-
const phaseDir = join(mainBase, '.gsd', 'phases', '01-foundation');
|
|
286
|
-
const metaDir = join(mainBase, '.gsd', 'milestones', 'M001');
|
|
287
|
-
mkdirSync(phaseDir, { recursive: true });
|
|
288
|
-
mkdirSync(metaDir, { recursive: true });
|
|
289
|
-
mkdirSync(join(wtBase, '.gsd'), { recursive: true });
|
|
290
|
-
writeFileSync(join(phaseDir, '01-CONTEXT.md'), '# Foundation\n');
|
|
291
|
-
writeFileSync(join(metaDir, 'M001-META.json'), '{"integrationBranch":"main"}\n');
|
|
292
|
-
|
|
293
|
-
syncGsdStateToWorktree(mainBase, wtBase);
|
|
294
|
-
|
|
295
|
-
assert.ok(
|
|
296
|
-
existsSync(join(wtBase, '.gsd', 'phases', '01-foundation', '01-CONTEXT.md')),
|
|
297
|
-
'flat-phase artifact is synced to worktree',
|
|
298
|
-
);
|
|
299
|
-
assert.ok(
|
|
300
|
-
!existsSync(join(wtBase, '.gsd', 'milestones')),
|
|
301
|
-
'metadata-only legacy milestones/ scaffold is not recreated in worktree',
|
|
302
|
-
);
|
|
303
|
-
} finally {
|
|
304
|
-
rmSync(mainBase, { recursive: true, force: true });
|
|
305
|
-
rmSync(wtBase, { recursive: true, force: true });
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
249
|
// ─── 8. syncWorktreeStateBack does not copy task projections ───────────
|
|
310
250
|
console.log('\n=== 8. syncWorktreeStateBack leaves task projections in worktree ===');
|
|
311
251
|
{
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
import { join } from "node:path";
|
|
33
33
|
|
|
34
34
|
import { reconcileWorktreeDb } from "./gsd-db.js";
|
|
35
|
-
import {
|
|
35
|
+
import { resolveGsdPathContract } from "./paths.js";
|
|
36
36
|
import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
37
37
|
import type { MilestoneScope } from "./workspace.js";
|
|
38
38
|
import { logError, logWarning } from "./workflow-logger.js";
|
|
@@ -161,7 +161,6 @@ function syncOtherMilestoneArtifacts(
|
|
|
161
161
|
// additive safeCopyRecursive; skip it here to avoid redundant work.
|
|
162
162
|
if (milestoneEntry.name === currentMilestoneId) continue;
|
|
163
163
|
const srcMilestoneDir = join(srcMilestonesDir, milestoneEntry.name);
|
|
164
|
-
if (!dirIsContentBearingLegacyMilestone(srcMilestoneDir)) continue;
|
|
165
164
|
const dstMilestoneDir = join(dstMilestonesDir, milestoneEntry.name);
|
|
166
165
|
|
|
167
166
|
// Additively project the entire milestone subtree (force:false), not just
|
|
@@ -272,32 +271,36 @@ export function _projectRootToWorktreeImpl(
|
|
|
272
271
|
// Without this, worktree-local files (e.g. VALIDATION.md written
|
|
273
272
|
// by validate-milestone) get clobbered by stale project root copies,
|
|
274
273
|
// causing an infinite re-validation loop (#1886).
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
// Force-sync ASSESSMENT files that have a verdict from project root (#2821).
|
|
281
|
-
// The additive-only copy above preserves worktree-local files, but
|
|
282
|
-
// ASSESSMENT files are special: after run-uat writes a verdict and post-unit
|
|
283
|
-
// syncs it to the project root, the worktree may retain a stale copy (e.g.
|
|
284
|
-
// verdict:fail while the project root has verdict:pass from a retry). On
|
|
285
|
-
// session resume the DB is rebuilt from disk, and if the stale ASSESSMENT
|
|
286
|
-
// persists, checkNeedsRunUat finds no passing verdict → re-dispatches
|
|
287
|
-
// run-uat indefinitely (stuck-loop ×9).
|
|
288
|
-
forceOverwriteValidationWithVerdict(prMilestoneDir, wtMilestoneDir, milestoneId);
|
|
289
|
-
forceOverwriteAssessmentsWithVerdict(prMilestoneDir, wtMilestoneDir);
|
|
290
|
-
}
|
|
274
|
+
safeCopyRecursive(
|
|
275
|
+
join(prGsd, "milestones", milestoneId),
|
|
276
|
+
join(wtGsd, "milestones", milestoneId),
|
|
277
|
+
{ force: false },
|
|
278
|
+
);
|
|
291
279
|
|
|
292
|
-
// Additively project the full subtree of every OTHER
|
|
293
|
-
//
|
|
294
|
-
//
|
|
280
|
+
// Additively project the full subtree of every OTHER milestone so
|
|
281
|
+
// worktree-bound units can read prior-milestone context artifacts and so
|
|
282
|
+
// completed milestones retain their per-slice/per-task SUMMARY.md and
|
|
283
|
+
// UAT.md on the worktree filesystem (otherwise the stale-render detector
|
|
284
|
+
// flags them as "complete in DB but missing on disk").
|
|
295
285
|
syncOtherMilestoneArtifacts(
|
|
296
286
|
join(prGsd, "milestones"),
|
|
297
287
|
join(wtGsd, "milestones"),
|
|
298
288
|
milestoneId,
|
|
299
289
|
);
|
|
300
290
|
|
|
291
|
+
// Force-sync ASSESSMENT files that have a verdict from project root (#2821).
|
|
292
|
+
// The additive-only copy above preserves worktree-local files, but
|
|
293
|
+
// ASSESSMENT files are special: after run-uat writes a verdict and post-unit
|
|
294
|
+
// syncs it to the project root, the worktree may retain a stale copy (e.g.
|
|
295
|
+
// verdict:fail while the project root has verdict:pass from a retry). On
|
|
296
|
+
// session resume the DB is rebuilt from disk, and if the stale ASSESSMENT
|
|
297
|
+
// persists, checkNeedsRunUat finds no passing verdict → re-dispatches
|
|
298
|
+
// run-uat indefinitely (stuck-loop ×9).
|
|
299
|
+
const prMilestoneDir = join(prGsd, "milestones", milestoneId);
|
|
300
|
+
const wtMilestoneDir = join(wtGsd, "milestones", milestoneId);
|
|
301
|
+
forceOverwriteValidationWithVerdict(prMilestoneDir, wtMilestoneDir, milestoneId);
|
|
302
|
+
forceOverwriteAssessmentsWithVerdict(prMilestoneDir, wtMilestoneDir);
|
|
303
|
+
|
|
301
304
|
// Forward-sync completed-units.json from project root to worktree.
|
|
302
305
|
// Project root is authoritative for completion state after crash recovery;
|
|
303
306
|
// without this, the worktree re-dispatches already-completed units (#1886).
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"background-cli.test.d.ts","sourceRoot":"","sources":["../src/background-cli.test.ts"],"names":[],"mappings":""}
|