@opengsd/gsd-pi 1.9.0-dev.d2070876 → 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 +12 -12
- 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 +12 -12
- 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/dist/core/skills.d.ts +0 -5
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +0 -54
- package/packages/pi-coding-agent/dist/core/skills.js.map +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/{sggX-h41OjePo-FjdArP5 → V9MAVyYVPboQUB9KTTAMi}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{sggX-h41OjePo-FjdArP5 → V9MAVyYVPboQUB9KTTAMi}/_ssgManifest.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
b54acda1c11b5a17
|
|
@@ -261,22 +261,7 @@ async function enforceMinRequestInterval(s, prefs) {
|
|
|
261
261
|
await new Promise(r => setTimeout(r, decision.waitMs));
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
|
-
|
|
265
|
-
try {
|
|
266
|
-
const commitMsg = await deps.autoCommitUnit?.(s.basePath, iterData.unitType, iterData.unitId, ctx);
|
|
267
|
-
if (commitMsg) {
|
|
268
|
-
debugLog("autoLoop", {
|
|
269
|
-
phase: "crash-snapshot-commit",
|
|
270
|
-
unitType: iterData.unitType,
|
|
271
|
-
unitId: iterData.unitId,
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
catch (snapshotErr) {
|
|
276
|
-
logWarning("dispatch", `unit crash snapshot failed: ${snapshotErr instanceof Error ? snapshotErr.message : String(snapshotErr)}`);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
async function closeOutCrashedUnit(ctx, s, deps, iterData, err) {
|
|
264
|
+
function closeOutCrashedUnit(s, iterData, err) {
|
|
280
265
|
const summary = formatDispatchExceptionSummary({ error: err });
|
|
281
266
|
try {
|
|
282
267
|
emitOpenUnitEndForUnit(s.basePath, iterData.unitType, iterData.unitId, "cancelled", {
|
|
@@ -293,7 +278,6 @@ async function closeOutCrashedUnit(ctx, s, deps, iterData, err) {
|
|
|
293
278
|
catch (closeoutErr) {
|
|
294
279
|
logWarning("dispatch", `unit crash closeout failed: ${closeoutErr instanceof Error ? closeoutErr.message : String(closeoutErr)}`);
|
|
295
280
|
}
|
|
296
|
-
await snapshotCrashedUnitWork(ctx, s, deps, iterData);
|
|
297
281
|
}
|
|
298
282
|
/**
|
|
299
283
|
* Main auto-mode execution loop. Iterates: orchestration.advance → guards →
|
|
@@ -416,15 +400,11 @@ export async function autoLoop(ctx, pi, s, deps, options) {
|
|
|
416
400
|
active: s.active,
|
|
417
401
|
iteration,
|
|
418
402
|
maxIterations: MAX_LOOP_ITERATIONS,
|
|
419
|
-
hasCommandContext:
|
|
403
|
+
hasCommandContext: Boolean(s.cmdCtx),
|
|
420
404
|
sessionLockValid: true,
|
|
421
405
|
});
|
|
422
406
|
if (commandContextDecision.action === "stop" && commandContextDecision.reason === "missing-command-context") {
|
|
423
407
|
debugLog("autoLoop", { phase: "exit", reason: "no-cmdCtx" });
|
|
424
|
-
if (s.currentUnit) {
|
|
425
|
-
await deps.autoCommitUnit?.(s.basePath, s.currentUnit.type, s.currentUnit.id, ctx);
|
|
426
|
-
}
|
|
427
|
-
await deps.stopAuto(ctx, pi, commandContextDecision.message, { preserveWorktree: true });
|
|
428
408
|
finishTurn("stopped", "manual-attention", commandContextDecision.reason);
|
|
429
409
|
break;
|
|
430
410
|
}
|
|
@@ -606,7 +586,7 @@ export async function autoLoop(ctx, pi, s, deps, options) {
|
|
|
606
586
|
if (err instanceof ModelPolicyDispatchBlockedError) {
|
|
607
587
|
throw err;
|
|
608
588
|
}
|
|
609
|
-
|
|
589
|
+
closeOutCrashedUnit(s, iterData, err);
|
|
610
590
|
throw err;
|
|
611
591
|
}
|
|
612
592
|
if (unitPhaseResult.action === "next") {
|
|
@@ -1129,7 +1109,7 @@ export async function autoLoop(ctx, pi, s, deps, options) {
|
|
|
1129
1109
|
if (err instanceof ModelPolicyDispatchBlockedError) {
|
|
1130
1110
|
throw err;
|
|
1131
1111
|
}
|
|
1132
|
-
|
|
1112
|
+
closeOutCrashedUnit(s, iterData, err);
|
|
1133
1113
|
dispatchSettled = settleDispatchFailed(dispatchId, formatDispatchExceptionSummary({ error: err }), {
|
|
1134
1114
|
markFailed: markDispatchFailed,
|
|
1135
1115
|
logWriteFailure: logDispatchLedgerWriteFailure,
|
|
@@ -58,24 +58,6 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
58
58
|
debugLog("runUnit", { phase: "start", unitType, unitId });
|
|
59
59
|
// ── Session creation with timeout ──
|
|
60
60
|
debugLog("runUnit", { phase: "session-create", unitType, unitId });
|
|
61
|
-
const cmdCtx = s.cmdCtx;
|
|
62
|
-
if (typeof cmdCtx?.newSession !== "function") {
|
|
63
|
-
const msg = "command context is missing newSession";
|
|
64
|
-
debugLog("runUnit", {
|
|
65
|
-
phase: "session-error",
|
|
66
|
-
unitType,
|
|
67
|
-
unitId,
|
|
68
|
-
error: msg,
|
|
69
|
-
});
|
|
70
|
-
return {
|
|
71
|
-
status: "cancelled",
|
|
72
|
-
errorContext: {
|
|
73
|
-
message: `Session creation failed: ${msg}`,
|
|
74
|
-
category: "session-failed",
|
|
75
|
-
isTransient: false,
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
61
|
let sessionResult;
|
|
80
62
|
let sessionTimeoutHandle;
|
|
81
63
|
const mySessionSwitchGeneration = ++sessionSwitchGeneration;
|
|
@@ -85,7 +67,7 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
85
67
|
const sessionAbortController = new AbortController();
|
|
86
68
|
_setSessionSwitchInFlight(true);
|
|
87
69
|
try {
|
|
88
|
-
const sessionPromise = cmdCtx.newSession({
|
|
70
|
+
const sessionPromise = s.cmdCtx.newSession({
|
|
89
71
|
abortSignal: sessionAbortController.signal,
|
|
90
72
|
workspaceRoot: s.basePath,
|
|
91
73
|
}).finally(() => {
|
|
@@ -46,10 +46,9 @@ import { classifyMilestoneSummaryContent } from "./milestone-summary-classifier.
|
|
|
46
46
|
import { extractVerdict } from "./verdict-parser.js";
|
|
47
47
|
import { auditOrphanedPreflightStashes } from "./orphan-stash-audit.js";
|
|
48
48
|
import { parseProject } from "./schemas/parsers.js";
|
|
49
|
-
import { LAYOUT_SEGMENTS } from "./layout-policy.js";
|
|
50
49
|
import { debugLog, enableDebug, isDebugEnabled, getDebugLogPath, } from "./debug-logger.js";
|
|
51
50
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
52
|
-
import { existsSync, mkdirSync, readFileSync,
|
|
51
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, } from "node:fs";
|
|
53
52
|
import { join } from "node:path";
|
|
54
53
|
import { validateDirectory } from "./validate-directory.js";
|
|
55
54
|
import { isCustomProvider, resolveDefaultSessionModel, resolveDynamicRoutingConfig, } from "./preferences-models.js";
|
|
@@ -74,30 +73,6 @@ const MAX_CONSECUTIVE_COMPLETE_BOOTSTRAPS = 2;
|
|
|
74
73
|
export function hasGitIndexLockForTest(basePath) {
|
|
75
74
|
return existsSync(join(basePath, ".git", "index.lock"));
|
|
76
75
|
}
|
|
77
|
-
function isEmptyDirectory(dirPath) {
|
|
78
|
-
try {
|
|
79
|
-
return existsSync(dirPath) && readdirSync(dirPath).length === 0;
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
export function reconcileFlatPhaseBootstrapLayout(basePath) {
|
|
86
|
-
const gsdDir = join(basePath, ".gsd");
|
|
87
|
-
const phasesPath = join(gsdDir, LAYOUT_SEGMENTS.level1);
|
|
88
|
-
const milestonesPath = join(gsdDir, "milestones");
|
|
89
|
-
if (isEmptyDirectory(milestonesPath)) {
|
|
90
|
-
if (!existsSync(phasesPath))
|
|
91
|
-
mkdirSync(phasesPath, { recursive: true });
|
|
92
|
-
rmSync(milestonesPath, { recursive: true, force: true });
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
if (!existsSync(phasesPath) && !existsSync(milestonesPath)) {
|
|
96
|
-
mkdirSync(phasesPath, { recursive: true });
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
76
|
export function _shouldAbortBootstrapForUnavailableDbForTest(gsdDbPath, dbAvailable, pathExists = existsSync) {
|
|
102
77
|
return pathExists(gsdDbPath) && !dbAvailable;
|
|
103
78
|
}
|
|
@@ -893,15 +868,20 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
893
868
|
ensureGitignore(base, { manageGitignore });
|
|
894
869
|
if (manageGitignore !== false)
|
|
895
870
|
untrackRuntimeFiles(base);
|
|
896
|
-
// Bootstrap
|
|
897
|
-
|
|
871
|
+
// Bootstrap milestones/ if it doesn't exist.
|
|
872
|
+
// Check milestones/ directly — ensureGsdSymlink above already created .gsd/,
|
|
873
|
+
// so checking .gsd/ existence would be dead code (#2942).
|
|
874
|
+
const gsdDir = join(base, ".gsd");
|
|
875
|
+
const milestonesPath = join(gsdDir, "milestones");
|
|
876
|
+
if (!existsSync(milestonesPath)) {
|
|
877
|
+
mkdirSync(milestonesPath, { recursive: true });
|
|
898
878
|
try {
|
|
899
879
|
nativeAddAll(base);
|
|
900
880
|
nativeCommit(base, "chore: init gsd");
|
|
901
881
|
}
|
|
902
882
|
catch (err) {
|
|
903
883
|
/* nothing to commit */
|
|
904
|
-
logWarning("engine", `
|
|
884
|
+
logWarning("engine", `mkdir failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
905
885
|
}
|
|
906
886
|
}
|
|
907
887
|
{
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { cpSync, existsSync, lstatSync as lstatSyncFn, mkdirSync, readdirSync, } from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { _isSamePath as isSamePath, } from "./auto-worktree-cleanup.js";
|
|
8
|
-
import {
|
|
8
|
+
import { resolveGsdPathContract } from "./paths.js";
|
|
9
9
|
import { WorktreeStateProjection } from "./worktree-state-projection.js";
|
|
10
10
|
import { logWarning } from "./workflow-logger.js";
|
|
11
11
|
const PROJECT_PREFERENCES_FILE = "PREFERENCES.md";
|
|
@@ -143,14 +143,10 @@ function syncMilestoneLayout(mainGsd, wtGsd, layoutSegment, synced) {
|
|
|
143
143
|
if (!existsSync(mainMilestonesDir))
|
|
144
144
|
return;
|
|
145
145
|
try {
|
|
146
|
+
mkdirSync(wtMilestonesDir, { recursive: true });
|
|
146
147
|
const mainMilestones = readdirSync(mainMilestonesDir, { withFileTypes: true })
|
|
147
148
|
.filter((entry) => entry.isDirectory())
|
|
148
|
-
.map((entry) => entry.name)
|
|
149
|
-
.filter((name) => layoutSegment !== "milestones" ||
|
|
150
|
-
dirIsContentBearingLegacyMilestone(join(mainMilestonesDir, name)));
|
|
151
|
-
if (mainMilestones.length === 0)
|
|
152
|
-
return;
|
|
153
|
-
mkdirSync(wtMilestonesDir, { recursive: true });
|
|
149
|
+
.map((entry) => entry.name);
|
|
154
150
|
for (const milestoneId of mainMilestones) {
|
|
155
151
|
syncMilestoneDirectory(mainMilestonesDir, wtMilestonesDir, milestoneId, synced);
|
|
156
152
|
}
|
|
@@ -2440,18 +2440,7 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
|
|
|
2440
2440
|
startedAt: hookStartedAt,
|
|
2441
2441
|
workspaceRoot: s.basePath,
|
|
2442
2442
|
});
|
|
2443
|
-
const
|
|
2444
|
-
if (typeof cmdCtx?.newSession !== "function") {
|
|
2445
|
-
const message = "Auto-mode has no command context for dispatch.";
|
|
2446
|
-
ctx.ui.notify(message, "error");
|
|
2447
|
-
const unitToCommit = previousCurrentUnit ?? s.currentUnit;
|
|
2448
|
-
if (unitToCommit) {
|
|
2449
|
-
await autoCommitUnit(s.basePath, unitToCommit.type, unitToCommit.id, ctx);
|
|
2450
|
-
}
|
|
2451
|
-
await stopAuto(ctx, pi, message, { preserveWorktree: true });
|
|
2452
|
-
return false;
|
|
2453
|
-
}
|
|
2454
|
-
const result = await cmdCtx.newSession({ workspaceRoot: s.basePath });
|
|
2443
|
+
const result = await s.cmdCtx.newSession({ workspaceRoot: s.basePath });
|
|
2455
2444
|
if (result.cancelled) {
|
|
2456
2445
|
await stopAuto(ctx, pi);
|
|
2457
2446
|
return false;
|
|
@@ -58,23 +58,6 @@ function reportCheckboxDbStatusDivergence(issues, basePath, filePath, scope, uni
|
|
|
58
58
|
fixable: false,
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
|
-
function bareDuplicateMilestoneId(milestoneId) {
|
|
62
|
-
const match = milestoneId.match(/^(M\d{3})-[a-z0-9]{6}$/i);
|
|
63
|
-
return match?.[1]?.toUpperCase() ?? null;
|
|
64
|
-
}
|
|
65
|
-
function checkboxDbStatusMilestoneIds(basePath, milestoneIds) {
|
|
66
|
-
if (!hasFlatPhaseLayout(basePath))
|
|
67
|
-
return milestoneIds;
|
|
68
|
-
// Flat-phase directories are keyed by phase number. A restored
|
|
69
|
-
// M003-xxxxxx row alongside M003 is a stale duplicate for the same
|
|
70
|
-
// phases/03-* projection, so checking both can compare DB status against two
|
|
71
|
-
// competing PLAN files and report a persistent false divergence.
|
|
72
|
-
const allIds = new Set(milestoneIds.map((id) => id.toUpperCase()));
|
|
73
|
-
return milestoneIds.filter((milestoneId) => {
|
|
74
|
-
const bareId = bareDuplicateMilestoneId(milestoneId);
|
|
75
|
-
return !bareId || !allIds.has(bareId);
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
61
|
function checkProjectionCheckboxDbStatus(basePath, milestoneIds, issues) {
|
|
79
62
|
for (const milestoneId of milestoneIds) {
|
|
80
63
|
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
@@ -339,9 +322,7 @@ function staleMilestonesArtifactRowFixable(basePath, row, artifactRows) {
|
|
|
339
322
|
}
|
|
340
323
|
function stalePhasesArtifactRowFixable(basePath, row, artifactRows) {
|
|
341
324
|
const issuePath = artifactPathRelativeToGsd(row.path);
|
|
342
|
-
return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) &&
|
|
343
|
-
(hasPresentFlatPhaseReplacement(basePath, row, artifactRows) ||
|
|
344
|
-
hasPresentMilestonesReplacement(basePath, row, artifactRows));
|
|
325
|
+
return issuePath.startsWith(`${LAYOUT_SEGMENTS.level1}/`) && hasPresentMilestonesReplacement(basePath, row, artifactRows);
|
|
345
326
|
}
|
|
346
327
|
function staleArtifactRowFixable(basePath, row, artifactRows) {
|
|
347
328
|
return staleMilestonesArtifactRowFixable(basePath, row, artifactRows) ||
|
|
@@ -677,7 +658,7 @@ export async function checkEngineHealth(basePath, issues, fixesApplied, options)
|
|
|
677
658
|
// block doctor.
|
|
678
659
|
try {
|
|
679
660
|
if (isDbAvailable()) {
|
|
680
|
-
checkProjectionCheckboxDbStatus(basePath,
|
|
661
|
+
checkProjectionCheckboxDbStatus(basePath, getAllMilestones().map((milestone) => milestone.id), issues);
|
|
681
662
|
}
|
|
682
663
|
}
|
|
683
664
|
catch {
|
|
@@ -130,17 +130,6 @@ function isInsideFlatProjectionBackup(backupDir, src) {
|
|
|
130
130
|
const phaseBackupDir = join(backupDir, "__phases");
|
|
131
131
|
return src === phaseBackupDir || src.startsWith(`${phaseBackupDir}/`) || src.startsWith(`${phaseBackupDir}\\`);
|
|
132
132
|
}
|
|
133
|
-
function removeFlatProjectionBackup(backupDir) {
|
|
134
|
-
const phaseBackupDir = join(backupDir, "__phases");
|
|
135
|
-
if (!existsSync(phaseBackupDir))
|
|
136
|
-
return;
|
|
137
|
-
try {
|
|
138
|
-
removePathWithRetries(phaseBackupDir);
|
|
139
|
-
}
|
|
140
|
-
catch (err) {
|
|
141
|
-
logWarning("migration", `flat-phase migration succeeded but could not remove temporary ${LAYOUT_SEGMENTS.level1}/ backup: ${err.message}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
133
|
function restoreFlatProjectionFromBackup(basePath, backupDir) {
|
|
145
134
|
const phaseBackupDir = join(backupDir, "__phases");
|
|
146
135
|
if (!existsSync(phaseBackupDir))
|
|
@@ -444,5 +433,4 @@ export async function migrateToFlatPhase(basePath) {
|
|
|
444
433
|
catch (err) {
|
|
445
434
|
logWarning("migration", `flat-phase migration succeeded but could not remove ${LEGACY_MIGRATING_SEGMENT}: ${err.message}`);
|
|
446
435
|
}
|
|
447
|
-
removeFlatProjectionBackup(backupDir);
|
|
448
436
|
}
|
|
@@ -38,7 +38,6 @@ export * from "./db/queries.js";
|
|
|
38
38
|
export * from "./db/writers/cascades.js";
|
|
39
39
|
import { TERMINAL_STATUS_SQL } from "./db/sql-constants.js";
|
|
40
40
|
import { applyStatusTransition } from "./db/writers/status.js";
|
|
41
|
-
import { LAYOUT_SEGMENTS, derivePhaseSlug, milestoneIdToPhaseNum, milestoneIdUniqueSuffix, phaseDirName, } from "./layout-policy.js";
|
|
42
41
|
export function insertDecision(d) {
|
|
43
42
|
if (!getDbOrNull())
|
|
44
43
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
@@ -170,44 +169,6 @@ export function insertArtifact(a) {
|
|
|
170
169
|
":content_hash": contentHash,
|
|
171
170
|
});
|
|
172
171
|
}
|
|
173
|
-
function canonicalPhaseDirNameForDb(milestoneId, title) {
|
|
174
|
-
const phaseNum = milestoneIdToPhaseNum(milestoneId);
|
|
175
|
-
const slug = derivePhaseSlug(title || milestoneId);
|
|
176
|
-
const suffix = milestoneIdUniqueSuffix(milestoneId);
|
|
177
|
-
return phaseDirName(phaseNum, suffix ? `${suffix}-${slug}` : slug);
|
|
178
|
-
}
|
|
179
|
-
function reconcileMilestonePhaseArtifactPaths(milestoneId, title) {
|
|
180
|
-
const db = getDbOrNull();
|
|
181
|
-
const phaseNumPrefix = String(milestoneIdToPhaseNum(milestoneId)).padStart(2, "0");
|
|
182
|
-
const canonicalDir = canonicalPhaseDirNameForDb(milestoneId, title);
|
|
183
|
-
const staleRows = db.prepare(`SELECT path
|
|
184
|
-
FROM artifacts
|
|
185
|
-
WHERE milestone_id = :milestone_id
|
|
186
|
-
AND path LIKE :phase_prefix`).all({
|
|
187
|
-
":milestone_id": milestoneId,
|
|
188
|
-
":phase_prefix": `${LAYOUT_SEGMENTS.level1}/${phaseNumPrefix}-%/%`,
|
|
189
|
-
});
|
|
190
|
-
const updatePath = db.prepare("UPDATE artifacts SET path = :new_path WHERE path = :old_path");
|
|
191
|
-
const deletePath = db.prepare("DELETE FROM artifacts WHERE path = :path");
|
|
192
|
-
const existingPath = db.prepare("SELECT 1 AS present FROM artifacts WHERE path = :path");
|
|
193
|
-
for (const row of staleRows) {
|
|
194
|
-
const parts = row.path.split("/");
|
|
195
|
-
if (parts.length < 3)
|
|
196
|
-
continue;
|
|
197
|
-
if (parts[0] !== LAYOUT_SEGMENTS.level1)
|
|
198
|
-
continue;
|
|
199
|
-
if (parts[1] === canonicalDir)
|
|
200
|
-
continue;
|
|
201
|
-
if (!parts[1]?.startsWith(`${phaseNumPrefix}-`))
|
|
202
|
-
continue;
|
|
203
|
-
const newPath = [LAYOUT_SEGMENTS.level1, canonicalDir, ...parts.slice(2)].join("/");
|
|
204
|
-
if (existingPath.get({ ":path": newPath })) {
|
|
205
|
-
deletePath.run({ ":path": row.path });
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
updatePath.run({ ":new_path": newPath, ":old_path": row.path });
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
172
|
export function insertMilestone(m) {
|
|
212
173
|
if (!getDbOrNull())
|
|
213
174
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
@@ -246,42 +207,37 @@ export function insertMilestone(m) {
|
|
|
246
207
|
export function upsertMilestonePlanning(milestoneId, planning) {
|
|
247
208
|
if (!getDbOrNull())
|
|
248
209
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
":boundary_map_markdown": planning.boundaryMapMarkdown ?? null,
|
|
281
|
-
});
|
|
282
|
-
const finalTitle = planning.title?.trim();
|
|
283
|
-
if (finalTitle)
|
|
284
|
-
reconcileMilestonePhaseArtifactPaths(milestoneId, finalTitle);
|
|
210
|
+
getDbOrNull().prepare(`UPDATE milestones SET
|
|
211
|
+
title = COALESCE(NULLIF(:title, ''), title),
|
|
212
|
+
status = COALESCE(NULLIF(:status, ''), status),
|
|
213
|
+
depends_on = COALESCE(:depends_on, depends_on),
|
|
214
|
+
vision = COALESCE(:vision, vision),
|
|
215
|
+
success_criteria = COALESCE(:success_criteria, success_criteria),
|
|
216
|
+
key_risks = COALESCE(:key_risks, key_risks),
|
|
217
|
+
proof_strategy = COALESCE(:proof_strategy, proof_strategy),
|
|
218
|
+
verification_contract = COALESCE(:verification_contract, verification_contract),
|
|
219
|
+
verification_integration = COALESCE(:verification_integration, verification_integration),
|
|
220
|
+
verification_operational = COALESCE(:verification_operational, verification_operational),
|
|
221
|
+
verification_uat = COALESCE(:verification_uat, verification_uat),
|
|
222
|
+
definition_of_done = COALESCE(:definition_of_done, definition_of_done),
|
|
223
|
+
requirement_coverage = COALESCE(:requirement_coverage, requirement_coverage),
|
|
224
|
+
boundary_map_markdown = COALESCE(:boundary_map_markdown, boundary_map_markdown)
|
|
225
|
+
WHERE id = :id`).run({
|
|
226
|
+
":id": milestoneId,
|
|
227
|
+
":title": planning.title ?? "",
|
|
228
|
+
":status": planning.status ?? "",
|
|
229
|
+
":depends_on": planning.depends_on ? JSON.stringify(planning.depends_on) : null,
|
|
230
|
+
":vision": planning.vision ?? null,
|
|
231
|
+
":success_criteria": planning.successCriteria ? JSON.stringify(planning.successCriteria) : null,
|
|
232
|
+
":key_risks": planning.keyRisks ? JSON.stringify(planning.keyRisks) : null,
|
|
233
|
+
":proof_strategy": planning.proofStrategy ? JSON.stringify(planning.proofStrategy) : null,
|
|
234
|
+
":verification_contract": planning.verificationContract ?? null,
|
|
235
|
+
":verification_integration": planning.verificationIntegration ?? null,
|
|
236
|
+
":verification_operational": planning.verificationOperational ?? null,
|
|
237
|
+
":verification_uat": planning.verificationUat ?? null,
|
|
238
|
+
":definition_of_done": planning.definitionOfDone ? JSON.stringify(planning.definitionOfDone) : null,
|
|
239
|
+
":requirement_coverage": planning.requirementCoverage ?? null,
|
|
240
|
+
":boundary_map_markdown": planning.boundaryMapMarkdown ?? null,
|
|
285
241
|
});
|
|
286
242
|
}
|
|
287
243
|
export function insertSlice(s) {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, unlinkSync, } from "node:fs";
|
|
22
22
|
import { join } from "node:path";
|
|
23
23
|
import { reconcileWorktreeDb } from "./gsd-db.js";
|
|
24
|
-
import {
|
|
24
|
+
import { resolveGsdPathContract } from "./paths.js";
|
|
25
25
|
import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
26
26
|
import { logError, logWarning } from "./workflow-logger.js";
|
|
27
27
|
// ─── Private helpers ──────────────────────────────────────────────────────
|
|
@@ -129,8 +129,6 @@ function syncOtherMilestoneArtifacts(srcMilestonesDir, dstMilestonesDir, current
|
|
|
129
129
|
if (milestoneEntry.name === currentMilestoneId)
|
|
130
130
|
continue;
|
|
131
131
|
const srcMilestoneDir = join(srcMilestonesDir, milestoneEntry.name);
|
|
132
|
-
if (!dirIsContentBearingLegacyMilestone(srcMilestoneDir))
|
|
133
|
-
continue;
|
|
134
132
|
const dstMilestoneDir = join(dstMilestonesDir, milestoneEntry.name);
|
|
135
133
|
// Additively project the entire milestone subtree (force:false), not just
|
|
136
134
|
// top-level files. Prior completed milestones keep their per-slice and
|
|
@@ -221,25 +219,25 @@ export function _projectRootToWorktreeImpl(projectRoot, worktreePath_, milestone
|
|
|
221
219
|
// Without this, worktree-local files (e.g. VALIDATION.md written
|
|
222
220
|
// by validate-milestone) get clobbered by stale project root copies,
|
|
223
221
|
// causing an infinite re-validation loop (#1886).
|
|
222
|
+
safeCopyRecursive(join(prGsd, "milestones", milestoneId), join(wtGsd, "milestones", milestoneId), { force: false });
|
|
223
|
+
// Additively project the full subtree of every OTHER milestone so
|
|
224
|
+
// worktree-bound units can read prior-milestone context artifacts and so
|
|
225
|
+
// completed milestones retain their per-slice/per-task SUMMARY.md and
|
|
226
|
+
// UAT.md on the worktree filesystem (otherwise the stale-render detector
|
|
227
|
+
// flags them as "complete in DB but missing on disk").
|
|
228
|
+
syncOtherMilestoneArtifacts(join(prGsd, "milestones"), join(wtGsd, "milestones"), milestoneId);
|
|
229
|
+
// Force-sync ASSESSMENT files that have a verdict from project root (#2821).
|
|
230
|
+
// The additive-only copy above preserves worktree-local files, but
|
|
231
|
+
// ASSESSMENT files are special: after run-uat writes a verdict and post-unit
|
|
232
|
+
// syncs it to the project root, the worktree may retain a stale copy (e.g.
|
|
233
|
+
// verdict:fail while the project root has verdict:pass from a retry). On
|
|
234
|
+
// session resume the DB is rebuilt from disk, and if the stale ASSESSMENT
|
|
235
|
+
// persists, checkNeedsRunUat finds no passing verdict → re-dispatches
|
|
236
|
+
// run-uat indefinitely (stuck-loop ×9).
|
|
224
237
|
const prMilestoneDir = join(prGsd, "milestones", milestoneId);
|
|
225
238
|
const wtMilestoneDir = join(wtGsd, "milestones", milestoneId);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// Force-sync ASSESSMENT files that have a verdict from project root (#2821).
|
|
229
|
-
// The additive-only copy above preserves worktree-local files, but
|
|
230
|
-
// ASSESSMENT files are special: after run-uat writes a verdict and post-unit
|
|
231
|
-
// syncs it to the project root, the worktree may retain a stale copy (e.g.
|
|
232
|
-
// verdict:fail while the project root has verdict:pass from a retry). On
|
|
233
|
-
// session resume the DB is rebuilt from disk, and if the stale ASSESSMENT
|
|
234
|
-
// persists, checkNeedsRunUat finds no passing verdict → re-dispatches
|
|
235
|
-
// run-uat indefinitely (stuck-loop ×9).
|
|
236
|
-
forceOverwriteValidationWithVerdict(prMilestoneDir, wtMilestoneDir, milestoneId);
|
|
237
|
-
forceOverwriteAssessmentsWithVerdict(prMilestoneDir, wtMilestoneDir);
|
|
238
|
-
}
|
|
239
|
-
// Additively project the full subtree of every OTHER content-bearing legacy
|
|
240
|
-
// milestone so worktree-bound units can read prior-milestone context artifacts
|
|
241
|
-
// without recreating empty/meta-only milestones/ scaffolds in flat-phase projects.
|
|
242
|
-
syncOtherMilestoneArtifacts(join(prGsd, "milestones"), join(wtGsd, "milestones"), milestoneId);
|
|
239
|
+
forceOverwriteValidationWithVerdict(prMilestoneDir, wtMilestoneDir, milestoneId);
|
|
240
|
+
forceOverwriteAssessmentsWithVerdict(prMilestoneDir, wtMilestoneDir);
|
|
243
241
|
// Forward-sync completed-units.json from project root to worktree.
|
|
244
242
|
// Project root is authoritative for completion state after crash recovery;
|
|
245
243
|
// without this, the worktree re-dispatches already-completed units (#1886).
|