@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.
Files changed (117) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/gsd/auto/loop.js +4 -24
  3. package/dist/resources/extensions/gsd/auto/run-unit.js +1 -19
  4. package/dist/resources/extensions/gsd/auto-start.js +9 -29
  5. package/dist/resources/extensions/gsd/auto-worktree-sync.js +3 -7
  6. package/dist/resources/extensions/gsd/auto.js +1 -12
  7. package/dist/resources/extensions/gsd/doctor-engine-checks.js +2 -21
  8. package/dist/resources/extensions/gsd/flat-phase-migration.js +0 -12
  9. package/dist/resources/extensions/gsd/gsd-db.js +31 -75
  10. package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -20
  11. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  12. package/dist/web/standalone/.next/BUILD_ID +1 -1
  13. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  14. package/dist/web/standalone/.next/build-manifest.json +2 -2
  15. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  16. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  17. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  25. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/index.html +1 -1
  33. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  40. package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
  41. package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
  42. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  43. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  44. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  45. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  46. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  47. package/package.json +1 -1
  48. package/packages/cloud-mcp-gateway/package.json +2 -2
  49. package/packages/contracts/package.json +1 -1
  50. package/packages/daemon/package.json +4 -4
  51. package/packages/gsd-agent-core/package.json +5 -5
  52. package/packages/gsd-agent-modes/package.json +7 -7
  53. package/packages/gsd-cloud/README.md +3 -9
  54. package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
  55. package/packages/gsd-cloud/dist/cli.js +15 -121
  56. package/packages/gsd-cloud/dist/cli.js.map +1 -1
  57. package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
  58. package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
  59. package/packages/gsd-cloud/dist/cloud-config.js +1 -7
  60. package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
  61. package/packages/gsd-cloud/dist/cloud-runtime.d.ts +3 -1
  62. package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
  63. package/packages/gsd-cloud/dist/cloud-runtime.js +7 -9
  64. package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
  65. package/packages/gsd-cloud/package.json +2 -2
  66. package/packages/mcp-server/dist/cli-runner.d.ts +0 -3
  67. package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
  68. package/packages/mcp-server/dist/cli-runner.js +33 -119
  69. package/packages/mcp-server/dist/cli-runner.js.map +1 -1
  70. package/packages/mcp-server/package.json +4 -4
  71. package/packages/native/package.json +1 -1
  72. package/packages/pi-agent-core/package.json +1 -1
  73. package/packages/pi-ai/dist/models.generated.d.ts +0 -17
  74. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  75. package/packages/pi-ai/dist/models.generated.js +0 -17
  76. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  77. package/packages/pi-ai/package.json +1 -1
  78. package/packages/pi-coding-agent/dist/core/skills.d.ts +0 -5
  79. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  80. package/packages/pi-coding-agent/dist/core/skills.js +0 -54
  81. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  82. package/packages/pi-coding-agent/package.json +7 -7
  83. package/packages/pi-tui/package.json +2 -2
  84. package/packages/rpc-client/package.json +2 -2
  85. package/pkg/package.json +1 -1
  86. package/src/resources/extensions/gsd/auto/loop.ts +4 -48
  87. package/src/resources/extensions/gsd/auto/run-unit.ts +1 -20
  88. package/src/resources/extensions/gsd/auto-start.ts +8 -31
  89. package/src/resources/extensions/gsd/auto-worktree-sync.ts +3 -10
  90. package/src/resources/extensions/gsd/auto.ts +1 -13
  91. package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -27
  92. package/src/resources/extensions/gsd/flat-phase-migration.ts +0 -14
  93. package/src/resources/extensions/gsd/gsd-db.ts +33 -85
  94. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +0 -156
  95. package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +1 -21
  96. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +1 -74
  97. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +0 -83
  98. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +9 -8
  99. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +0 -32
  100. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +0 -13
  101. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
  102. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +0 -60
  103. package/src/resources/extensions/gsd/worktree-state-projection.ts +24 -21
  104. package/packages/gsd-cloud/dist/background-cli.test.d.ts +0 -2
  105. package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +0 -1
  106. package/packages/gsd-cloud/dist/background-cli.test.js +0 -248
  107. package/packages/gsd-cloud/dist/background-cli.test.js.map +0 -1
  108. package/packages/gsd-cloud/dist/runtime-process.d.ts +0 -27
  109. package/packages/gsd-cloud/dist/runtime-process.d.ts.map +0 -1
  110. package/packages/gsd-cloud/dist/runtime-process.js +0 -272
  111. package/packages/gsd-cloud/dist/runtime-process.js.map +0 -1
  112. package/packages/gsd-cloud/dist/runtime-process.test.d.ts +0 -2
  113. package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +0 -1
  114. package/packages/gsd-cloud/dist/runtime-process.test.js +0 -97
  115. package/packages/gsd-cloud/dist/runtime-process.test.js.map +0 -1
  116. /package/dist/web/standalone/.next/static/{sggX-h41OjePo-FjdArP5 → V9MAVyYVPboQUB9KTTAMi}/_buildManifest.js +0 -0
  117. /package/dist/web/standalone/.next/static/{sggX-h41OjePo-FjdArP5 → V9MAVyYVPboQUB9KTTAMi}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- 5f0f1c788a126951
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
- async function snapshotCrashedUnitWork(ctx, s, deps, iterData) {
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: typeof s.cmdCtx?.newSession === "function",
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
- await closeOutCrashedUnit(ctx, s, deps, iterData, err);
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
- await closeOutCrashedUnit(ctx, s, deps, iterData, err);
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, readdirSync, rmSync, } from "node:fs";
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 the flat-phase projection root and clean empty legacy scaffolds.
897
- if (reconcileFlatPhaseBootstrapLayout(base)) {
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", `layout bootstrap commit failed: ${err instanceof Error ? err.message : String(err)}`);
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 { dirIsContentBearingLegacyMilestone, resolveGsdPathContract } from "./paths.js";
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 cmdCtx = s.cmdCtx;
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, checkboxDbStatusMilestoneIds(basePath, getAllMilestones().map((milestone) => milestone.id)), issues);
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
- transaction(() => {
250
- getDbOrNull().prepare(`UPDATE milestones SET
251
- title = COALESCE(NULLIF(:title, ''), title),
252
- status = COALESCE(NULLIF(:status, ''), status),
253
- depends_on = COALESCE(:depends_on, depends_on),
254
- vision = COALESCE(:vision, vision),
255
- success_criteria = COALESCE(:success_criteria, success_criteria),
256
- key_risks = COALESCE(:key_risks, key_risks),
257
- proof_strategy = COALESCE(:proof_strategy, proof_strategy),
258
- verification_contract = COALESCE(:verification_contract, verification_contract),
259
- verification_integration = COALESCE(:verification_integration, verification_integration),
260
- verification_operational = COALESCE(:verification_operational, verification_operational),
261
- verification_uat = COALESCE(:verification_uat, verification_uat),
262
- definition_of_done = COALESCE(:definition_of_done, definition_of_done),
263
- requirement_coverage = COALESCE(:requirement_coverage, requirement_coverage),
264
- boundary_map_markdown = COALESCE(:boundary_map_markdown, boundary_map_markdown)
265
- WHERE id = :id`).run({
266
- ":id": milestoneId,
267
- ":title": planning.title ?? "",
268
- ":status": planning.status ?? "",
269
- ":depends_on": planning.depends_on ? JSON.stringify(planning.depends_on) : null,
270
- ":vision": planning.vision ?? null,
271
- ":success_criteria": planning.successCriteria ? JSON.stringify(planning.successCriteria) : null,
272
- ":key_risks": planning.keyRisks ? JSON.stringify(planning.keyRisks) : null,
273
- ":proof_strategy": planning.proofStrategy ? JSON.stringify(planning.proofStrategy) : null,
274
- ":verification_contract": planning.verificationContract ?? null,
275
- ":verification_integration": planning.verificationIntegration ?? null,
276
- ":verification_operational": planning.verificationOperational ?? null,
277
- ":verification_uat": planning.verificationUat ?? null,
278
- ":definition_of_done": planning.definitionOfDone ? JSON.stringify(planning.definitionOfDone) : null,
279
- ":requirement_coverage": planning.requirementCoverage ?? null,
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 { dirIsContentBearingLegacyMilestone, resolveGsdPathContract } from "./paths.js";
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
- if (dirIsContentBearingLegacyMilestone(prMilestoneDir)) {
227
- safeCopyRecursive(prMilestoneDir, wtMilestoneDir, { force: false });
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).