gsd-pi 2.78.1-dev.d8826a445 → 2.78.1-dev.eccf86e27

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 (121) hide show
  1. package/README.md +5 -7
  2. package/dist/help-text.js +1 -1
  3. package/dist/resource-loader.js +6 -1
  4. package/dist/resources/.managed-resources-content-hash +1 -1
  5. package/dist/resources/extensions/gsd/auto/detect-stuck.js +41 -5
  6. package/dist/resources/extensions/gsd/auto/loop.js +235 -36
  7. package/dist/resources/extensions/gsd/auto/phases.js +7 -5
  8. package/dist/resources/extensions/gsd/auto/session.js +33 -0
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +46 -2
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +19 -11
  11. package/dist/resources/extensions/gsd/auto-worktree.js +26 -187
  12. package/dist/resources/extensions/gsd/auto.js +79 -50
  13. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -4
  14. package/dist/resources/extensions/gsd/crash-recovery.js +160 -47
  15. package/dist/resources/extensions/gsd/db/auto-workers.js +227 -0
  16. package/dist/resources/extensions/gsd/db/command-queue.js +105 -0
  17. package/dist/resources/extensions/gsd/db/milestone-leases.js +210 -0
  18. package/dist/resources/extensions/gsd/db/runtime-kv.js +91 -0
  19. package/dist/resources/extensions/gsd/db/unit-dispatches.js +322 -0
  20. package/dist/resources/extensions/gsd/docs/COORDINATION.md +42 -0
  21. package/dist/resources/extensions/gsd/doctor-proactive.js +4 -0
  22. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +22 -6
  23. package/dist/resources/extensions/gsd/doctor.js +12 -2
  24. package/dist/resources/extensions/gsd/gsd-db.js +161 -3
  25. package/dist/resources/extensions/gsd/guided-flow.js +6 -2
  26. package/dist/resources/extensions/gsd/interrupted-session.js +18 -15
  27. package/dist/resources/extensions/gsd/state.js +21 -6
  28. package/dist/resources/extensions/gsd/worktree-resolver.js +64 -0
  29. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  30. package/dist/web/standalone/.next/BUILD_ID +1 -1
  31. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  32. package/dist/web/standalone/.next/build-manifest.json +2 -2
  33. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  34. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/index.html +1 -1
  51. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  58. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  59. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  60. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  61. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  62. package/package.json +1 -1
  63. package/src/resources/extensions/gsd/auto/detect-stuck.ts +37 -5
  64. package/src/resources/extensions/gsd/auto/loop.ts +263 -41
  65. package/src/resources/extensions/gsd/auto/phases.ts +7 -5
  66. package/src/resources/extensions/gsd/auto/session.ts +36 -0
  67. package/src/resources/extensions/gsd/auto-dispatch.ts +53 -2
  68. package/src/resources/extensions/gsd/auto-post-unit.ts +19 -11
  69. package/src/resources/extensions/gsd/auto-worktree.ts +26 -211
  70. package/src/resources/extensions/gsd/auto.ts +89 -44
  71. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -4
  72. package/src/resources/extensions/gsd/crash-recovery.ts +177 -43
  73. package/src/resources/extensions/gsd/db/auto-workers.ts +273 -0
  74. package/src/resources/extensions/gsd/db/command-queue.ts +149 -0
  75. package/src/resources/extensions/gsd/db/milestone-leases.ts +274 -0
  76. package/src/resources/extensions/gsd/db/runtime-kv.ts +127 -0
  77. package/src/resources/extensions/gsd/db/unit-dispatches.ts +446 -0
  78. package/src/resources/extensions/gsd/docs/COORDINATION.md +42 -0
  79. package/src/resources/extensions/gsd/doctor-proactive.ts +4 -0
  80. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +24 -6
  81. package/src/resources/extensions/gsd/doctor.ts +10 -2
  82. package/src/resources/extensions/gsd/gsd-db.ts +170 -3
  83. package/src/resources/extensions/gsd/guided-flow.ts +6 -2
  84. package/src/resources/extensions/gsd/interrupted-session.ts +19 -12
  85. package/src/resources/extensions/gsd/state.ts +44 -6
  86. package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +72 -0
  87. package/src/resources/extensions/gsd/tests/auto-loop-symlink-worktree.test.ts +190 -0
  88. package/src/resources/extensions/gsd/tests/auto-workers.test.ts +105 -0
  89. package/src/resources/extensions/gsd/tests/command-queue.test.ts +141 -0
  90. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +203 -0
  91. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +169 -59
  92. package/src/resources/extensions/gsd/tests/detect-stuck-respects-retry.test.ts +173 -0
  93. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +22 -12
  94. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +24 -10
  95. package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +35 -23
  96. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +3 -5
  97. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +72 -25
  98. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +72 -25
  99. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +9 -6
  100. package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +152 -0
  101. package/src/resources/extensions/gsd/tests/parallel-milestone-isolation.test.ts +106 -0
  102. package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +119 -0
  103. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +58 -0
  104. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +3 -17
  105. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +110 -0
  106. package/src/resources/extensions/gsd/tests/runtime-kv.test.ts +120 -0
  107. package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +133 -28
  108. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +17 -0
  109. package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +134 -0
  110. package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +7 -26
  111. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +4 -8
  112. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +247 -0
  113. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +41 -1
  114. package/src/resources/extensions/gsd/tests/workspace.test.ts +15 -9
  115. package/src/resources/extensions/gsd/tests/write-gate.test.ts +31 -23
  116. package/src/resources/extensions/gsd/worktree-resolver.ts +62 -0
  117. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +0 -213
  118. package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +0 -87
  119. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +0 -159
  120. /package/dist/web/standalone/.next/static/{AT5qi39nKXkdmQIOIoh0f → Y5UeGFkXTYM9WIQOWHkot}/_buildManifest.js +0 -0
  121. /package/dist/web/standalone/.next/static/{AT5qi39nKXkdmQIOIoh0f → Y5UeGFkXTYM9WIQOWHkot}/_ssgManifest.js +0 -0
@@ -9,7 +9,7 @@
9
9
  * without modifying orchestration code.
10
10
  */
11
11
  import { loadFile, extractUatType, loadActiveOverrides } from "./files.js";
12
- import { isDbAvailable, getMilestoneSlices, getPendingGates, markAllGatesOmitted, getMilestone } from "./gsd-db.js";
12
+ import { isDbAvailable, getMilestoneSlices, getPendingGates, markAllGatesOmitted, getMilestone, insertAssessment, transaction } from "./gsd-db.js";
13
13
  import { isClosedStatus } from "./status-guards.js";
14
14
  import { extractVerdict, isAcceptableUatVerdict } from "./verdict-parser.js";
15
15
  import { gsdRoot, resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveTaskFile, relSliceFile, buildMilestoneFileName, } from "./paths.js";
@@ -32,6 +32,8 @@ import { MILESTONE_ID_RE } from "./milestone-ids.js";
32
32
  import { PROJECT_RESEARCH_INFLIGHT_MARKER, } from "./project-research-policy.js";
33
33
  import { isWorkflowPrefsCaptured, resolveDeepProjectSetupState, } from "./deep-project-setup-policy.js";
34
34
  import { annotateBackgroundable } from "./delegation-policy.js";
35
+ import { invalidateAllCaches } from "./cache.js";
36
+ import { insertMilestoneValidationGates } from "./milestone-validation-gates.js";
35
37
  let reassessmentChecker = checkNeedsReassessment;
36
38
  let researchProjectPromptBuilder = buildResearchProjectPrompt;
37
39
  function shouldBypassMilestoneDepthGateInAuto(prefs) {
@@ -996,9 +998,12 @@ export const DISPATCH_RULES = [
996
998
  const skipSource = trivialVariant
997
999
  ? "trivial-scope pipeline variant (#4781)"
998
1000
  : "`skip_milestone_validation` preference";
1001
+ const skipValidationReason = trivialVariant ? "trivial-scope" : "preference";
999
1002
  const content = [
1000
1003
  "---",
1001
1004
  "verdict: pass",
1005
+ "skip_validation: true",
1006
+ `skip_validation_reason: ${skipValidationReason}`,
1002
1007
  "remediation_round: 0",
1003
1008
  "---",
1004
1009
  "",
@@ -1007,6 +1012,39 @@ export const DISPATCH_RULES = [
1007
1012
  `Milestone validation was skipped via ${skipSource}.`,
1008
1013
  ].join("\n");
1009
1014
  writeFileSync(validationPath, content, "utf-8");
1015
+ try {
1016
+ // DB-backed state derivation keys off assessments, not only the file
1017
+ // projection. Persist the skipped validation there too so the next
1018
+ // loop iteration advances to completing-milestone instead of
1019
+ // re-entering validating-milestone.
1020
+ if (isDbAvailable()) {
1021
+ transaction(() => {
1022
+ insertAssessment({
1023
+ path: validationPath,
1024
+ milestoneId: mid,
1025
+ sliceId: null,
1026
+ taskId: null,
1027
+ status: "pass",
1028
+ scope: "milestone-validation",
1029
+ fullContent: content,
1030
+ });
1031
+ const gateSliceId = getMilestoneSlices(mid)[0]?.id;
1032
+ if (gateSliceId) {
1033
+ insertMilestoneValidationGates(mid, gateSliceId, "pass", new Date().toISOString());
1034
+ }
1035
+ });
1036
+ }
1037
+ }
1038
+ catch (err) {
1039
+ try {
1040
+ unlinkSync(validationPath);
1041
+ }
1042
+ catch (unlinkErr) {
1043
+ logWarning("dispatch", `failed to remove skipped validation file after DB write failure for ${mid}: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}`);
1044
+ }
1045
+ throw err;
1046
+ }
1047
+ invalidateAllCaches();
1010
1048
  }
1011
1049
  return { action: "skip" };
1012
1050
  }
@@ -1087,13 +1125,19 @@ export const DISPATCH_RULES = [
1087
1125
  if (validationContent) {
1088
1126
  // Allow completion when validation was intentionally skipped by
1089
1127
  // preference/budget profile (#3399, #3344).
1128
+ const skippedByMarker = /^skip_validation:\s*true$/im.test(validationContent);
1090
1129
  const skippedByPreference = /skip(?:ped)?[\s\-]+(?:by|per|due to)\s+(?:preference|budget|profile)/i.test(validationContent);
1130
+ const skippedByTrivialVariant = /trivial-scope pipeline variant/i.test(validationContent);
1091
1131
  // Accept either the structured template format (table with MET/N/A/SATISFIED)
1092
1132
  // or prose evidence patterns the validation agent may emit.
1093
1133
  const structuredMatch = validationContent.includes("Operational") &&
1094
1134
  (validationContent.includes("MET") || validationContent.includes("N/A") || validationContent.includes("SATISFIED"));
1095
1135
  const proseMatch = /[Oo]perational[\s\S]{0,500}?(?:✅|pass|verified|confirmed|met|complete|true|yes|addressed|covered|satisfied|partially|n\/a|not[\s-]+applicable)/i.test(validationContent);
1096
- const hasOperationalCheck = skippedByPreference || structuredMatch || proseMatch;
1136
+ const hasOperationalCheck = skippedByMarker ||
1137
+ skippedByPreference ||
1138
+ skippedByTrivialVariant ||
1139
+ structuredMatch ||
1140
+ proseMatch;
1097
1141
  if (!hasOperationalCheck) {
1098
1142
  return {
1099
1143
  action: "stop",
@@ -656,13 +656,15 @@ export async function postUnitPreVerification(pctx, opts) {
656
656
  if (s.currentUnit.type === "triage-captures") {
657
657
  try {
658
658
  const { executeTriageResolutions } = await import("./triage-resolution.js");
659
- const state = await deriveState(s.basePath);
659
+ const state = await deriveState(s.canonicalProjectRoot);
660
660
  const mid = state.activeMilestone?.id ?? "";
661
661
  const sid = state.activeSlice?.id ?? "";
662
662
  // executeTriageResolutions handles defer milestone creation even
663
663
  // without an active milestone/slice (the "all milestones complete"
664
664
  // scenario from #1562). inject/replan/quick-task still require mid+sid.
665
- const triageResult = executeTriageResolutions(s.basePath, mid, sid);
665
+ // Phase C: write to canonical project root. copyPlanningArtifacts
666
+ // has been deleted, so triage writes land where readers consult.
667
+ const triageResult = executeTriageResolutions(s.canonicalProjectRoot, mid, sid);
666
668
  if (triageResult.injected > 0) {
667
669
  ctx.ui.notify(`Triage: injected ${triageResult.injected} task${triageResult.injected === 1 ? "" : "s"} into ${sid} plan.`, "info");
668
670
  }
@@ -805,10 +807,14 @@ export async function postUnitPreVerification(pctx, opts) {
805
807
  try {
806
808
  const { milestone: mid, slice: sid } = parseUnitId(s.currentUnit.id);
807
809
  if (mid && sid) {
808
- const regenerated = await regenerateIfMissing(s.basePath, mid, sid, "PLAN");
810
+ // Phase C: write to the canonical project root (#5236 scope)
811
+ // so non-symlinked worktrees no longer maintain a separate
812
+ // local .gsd/ projection. copyPlanningArtifacts has been
813
+ // deleted; reads + writes converge at projectRoot.
814
+ const regenerated = await regenerateIfMissing(s.canonicalProjectRoot, mid, sid, "PLAN");
809
815
  if (regenerated) {
810
816
  // Re-check after regeneration
811
- triggerArtifactVerified = verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
817
+ triggerArtifactVerified = verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.canonicalProjectRoot);
812
818
  if (triggerArtifactVerified) {
813
819
  invalidateAllCaches();
814
820
  }
@@ -999,7 +1005,7 @@ export async function postUnitPostVerification(pctx) {
999
1005
  if (mid && sid && tid) {
1000
1006
  try {
1001
1007
  updateTaskStatus(mid, sid, tid, "pending");
1002
- await renderPlanCheckboxes(s.basePath, mid, sid);
1008
+ await renderPlanCheckboxes(s.canonicalProjectRoot, mid, sid);
1003
1009
  }
1004
1010
  catch (dbErr) {
1005
1011
  // DB unavailable — fail explicitly rather than silently reverting to markdown mutation.
@@ -1009,7 +1015,8 @@ export async function postUnitPostVerification(pctx) {
1009
1015
  }
1010
1016
  // 2. Delete SUMMARY.md for the task
1011
1017
  if (mid && sid && tid) {
1012
- const tasksDir = resolveTasksDir(s.basePath, mid, sid);
1018
+ // Phase C: read+delete via canonical project root.
1019
+ const tasksDir = resolveTasksDir(s.canonicalProjectRoot, mid, sid);
1013
1020
  if (tasksDir) {
1014
1021
  const summaryFile = join(tasksDir, buildTaskFileName(tid, "SUMMARY"));
1015
1022
  if (existsSync(summaryFile)) {
@@ -1019,7 +1026,7 @@ export async function postUnitPostVerification(pctx) {
1019
1026
  }
1020
1027
  // 3. Delete the retry_on artifact (e.g. NEEDS-REWORK.md)
1021
1028
  if (trigger.retryArtifact) {
1022
- const retryArtifactPath = resolveHookArtifactPath(s.basePath, trigger.unitId, trigger.retryArtifact);
1029
+ const retryArtifactPath = resolveHookArtifactPath(s.canonicalProjectRoot, trigger.unitId, trigger.retryArtifact);
1023
1030
  if (existsSync(retryArtifactPath)) {
1024
1031
  unlinkSync(retryArtifactPath);
1025
1032
  }
@@ -1255,16 +1262,17 @@ export async function postUnitPostVerification(pctx) {
1255
1262
  if (hasPendingCaptures(s.basePath)) {
1256
1263
  const pending = loadPendingCaptures(s.basePath);
1257
1264
  if (pending.length > 0) {
1258
- const state = await deriveState(s.basePath);
1265
+ const readRoot = s.canonicalProjectRoot;
1266
+ const state = await deriveState(readRoot);
1259
1267
  const mid = state.activeMilestone?.id;
1260
1268
  const sid = state.activeSlice?.id;
1261
1269
  if (mid && sid) {
1262
1270
  let currentPlan = "";
1263
1271
  let roadmapContext = "";
1264
- const planFile = resolveSliceFile(s.basePath, mid, sid, "PLAN");
1272
+ const planFile = resolveSliceFile(readRoot, mid, sid, "PLAN");
1265
1273
  if (planFile)
1266
1274
  currentPlan = (await loadFile(planFile)) ?? "";
1267
- const roadmapFile = resolveMilestoneFile(s.basePath, mid, "ROADMAP");
1275
+ const roadmapFile = resolveMilestoneFile(readRoot, mid, "ROADMAP");
1268
1276
  if (roadmapFile)
1269
1277
  roadmapContext = (await loadFile(roadmapFile)) ?? "";
1270
1278
  const capturesList = pending.map(c => `- **${c.id}**: "${c.text}" (captured: ${c.timestamp})`).join("\n");
@@ -1312,7 +1320,7 @@ export async function postUnitPostVerification(pctx) {
1312
1320
  // exits the loop, leaving the user with no hint to /clear and /gsd again.
1313
1321
  if (s.stepMode) {
1314
1322
  try {
1315
- const nextState = await deriveState(s.basePath);
1323
+ const nextState = await deriveState(s.canonicalProjectRoot);
1316
1324
  ctx.ui.notify(buildStepCompleteMessage(nextState), "info");
1317
1325
  }
1318
1326
  catch (e) {
@@ -9,7 +9,6 @@ import { existsSync, cpSync, readFileSync, readdirSync, mkdirSync, realpathSync,
9
9
  import { isAbsolute, join, sep as pathSep } from "node:path";
10
10
  import { GSDError, GSD_IO_ERROR, GSD_GIT_ERROR } from "./errors.js";
11
11
  import { reconcileWorktreeDb, isDbAvailable, getMilestone, getMilestoneSlices, closeDatabase, openDatabase, getDbPath, } from "./gsd-db.js";
12
- import { atomicWriteSync } from "./atomic-write.js";
13
12
  import { execFileSync } from "node:child_process";
14
13
  import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
15
14
  import { gsdRoot, resolveGsdPathContract } from "./paths.js";
@@ -206,9 +205,12 @@ function getActiveWorkspace() {
206
205
  }
207
206
  function clearProjectRootStateFiles(basePath, milestoneId) {
208
207
  const gsdDir = gsdRoot(basePath);
208
+ // Phase C pt 2: auto.lock removed from this list — the file is gone
209
+ // (migrated to the workers + unit_dispatches + runtime_kv tables). The
210
+ // remaining transient files (STATE.md, {MID}-META.json) are still
211
+ // worth removing on teardown.
209
212
  const transientFiles = [
210
213
  join(gsdDir, "STATE.md"),
211
- join(gsdDir, "auto.lock"),
212
214
  join(gsdDir, "milestones", milestoneId, `${milestoneId}-META.json`),
213
215
  ];
214
216
  for (const file of transientFiles) {
@@ -968,120 +970,13 @@ export function enterBranchModeForMilestone(basePath, milestoneId) {
968
970
  * Forward-merge plan checkbox state from the project root into a freshly
969
971
  * re-attached worktree (#778).
970
972
  *
971
- * When auto-mode stops via crash (not graceful stop), the milestone branch
972
- * HEAD may be behind the filesystem state at the project root because
973
- * syncStateToProjectRoot() runs after every task completion but the final
974
- * git commit may not have happened before the crash. On restart the worktree
975
- * is re-attached to the branch HEAD, which has [ ] for the crashed task,
976
- * causing verifyExpectedArtifact() to fail and triggering an infinite
977
- * dispatch/skip loop.
978
- *
979
- * Fix: after re-attaching, read every *.md plan file in the milestone
980
- * directory at the project root and apply any [x] checkbox states that are
981
- * ahead of the worktree version (forward-only: never downgrade [x] → [ ]).
982
- *
983
- * This is forward-only compatibility for legacy projection copies. The DB
984
- * remains authoritative; this never downgrades checked boxes in a local
985
- * worktree projection.
973
+ * Phase C: deleted. Writers in workflow-projections.ts, triage-resolution.ts,
974
+ * rule-registry.ts, and auto-post-unit.ts now route through
975
+ * s.canonicalProjectRoot, so non-symlinked worktrees no longer need a local
976
+ * .gsd/ projection the project-root .gsd/ is the only authoritative source
977
+ * for both reads and writes. copyPlanningArtifacts and reconcilePlanCheckboxes
978
+ * (both formerly here) became dead.
986
979
  */
987
- /**
988
- * Scope-typed variant of reconcilePlanCheckboxes.
989
- *
990
- * Takes an explicit (rootScope, worktreeScope) pair. milestoneId is taken
991
- * from rootScope. Asserts both scopes belong to the same workspace identity
992
- * to prevent silent mismatch bugs.
993
- */
994
- export function reconcilePlanCheckboxesByScope(rootScope, worktreeScope) {
995
- if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
996
- throw new Error(`reconcilePlanCheckboxesByScope: scope identity mismatch — ` +
997
- `rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
998
- `worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`);
999
- }
1000
- if (rootScope.milestoneId !== worktreeScope.milestoneId) {
1001
- throw new Error(`reconcilePlanCheckboxesByScope: milestoneId mismatch — ` +
1002
- `rootScope.milestoneId="${rootScope.milestoneId}" worktreeScope.milestoneId="${worktreeScope.milestoneId}"`);
1003
- }
1004
- const projectRoot = rootScope.workspace.projectRoot;
1005
- const wtPath = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
1006
- const milestoneId = rootScope.milestoneId;
1007
- reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId);
1008
- }
1009
- /**
1010
- * @deprecated Use reconcilePlanCheckboxesByScope instead.
1011
- * TODO(C-future): remove once all callers migrated.
1012
- */
1013
- function reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId) {
1014
- const srcMilestone = join(projectRoot, ".gsd", "milestones", milestoneId);
1015
- const dstMilestone = join(wtPath, ".gsd", "milestones", milestoneId);
1016
- if (!existsSync(srcMilestone) || !existsSync(dstMilestone))
1017
- return;
1018
- // Walk all markdown files in the milestone directory (plans, summaries, etc.)
1019
- function walkMd(dir) {
1020
- const results = [];
1021
- try {
1022
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
1023
- const full = join(dir, entry.name);
1024
- if (entry.isDirectory()) {
1025
- results.push(...walkMd(full));
1026
- }
1027
- else if (entry.isFile() && entry.name.endsWith(".md")) {
1028
- results.push(full);
1029
- }
1030
- }
1031
- }
1032
- catch (err) {
1033
- /* non-fatal */
1034
- logWarning("worktree", `walkMd directory read failed: ${err instanceof Error ? err.message : String(err)}`);
1035
- }
1036
- return results;
1037
- }
1038
- for (const srcFile of walkMd(srcMilestone)) {
1039
- const rel = srcFile.slice(srcMilestone.length);
1040
- const dstFile = dstMilestone + rel;
1041
- if (!existsSync(dstFile))
1042
- continue; // only reconcile existing files
1043
- let srcContent;
1044
- let dstContent;
1045
- try {
1046
- srcContent = readFileSync(srcFile, "utf-8");
1047
- dstContent = readFileSync(dstFile, "utf-8");
1048
- }
1049
- catch (e) {
1050
- logWarning("worktree", `reconcilePlanCheckboxes read failed: ${e.message}`);
1051
- continue;
1052
- }
1053
- if (srcContent === dstContent)
1054
- continue;
1055
- // Extract all checked task IDs from the source (project root)
1056
- // Pattern: - [x] **T<id>: or - [x] **S<id>: (case-insensitive x)
1057
- const checkedRe = /^- \[[xX]\] \*\*([TS]\d+):/gm;
1058
- const srcChecked = new Set();
1059
- for (const m of srcContent.matchAll(checkedRe))
1060
- srcChecked.add(m[1]);
1061
- if (srcChecked.size === 0)
1062
- continue;
1063
- // Forward-apply: replace [ ] → [x] for any IDs that are checked in src
1064
- let updated = dstContent;
1065
- let changed = false;
1066
- for (const id of srcChecked) {
1067
- const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1068
- const uncheckedRe = new RegExp(`^(- )\\[ \\]( \\*\\*${escapedId}:)`, "gm");
1069
- if (uncheckedRe.test(updated)) {
1070
- updated = updated.replace(new RegExp(`^(- )\\[ \\]( \\*\\*${escapedId}:)`, "gm"), "$1[x]$2");
1071
- changed = true;
1072
- }
1073
- }
1074
- if (changed) {
1075
- try {
1076
- atomicWriteSync(dstFile, updated, "utf-8");
1077
- }
1078
- catch (err) {
1079
- /* non-fatal */
1080
- logWarning("worktree", `plan checkbox reconcile write failed: ${err instanceof Error ? err.message : String(err)}`);
1081
- }
1082
- }
1083
- }
1084
- }
1085
980
  export function createAutoWorktree(basePath, milestoneId) {
1086
981
  basePath = resolveWorktreeProjectRoot(basePath);
1087
982
  // Check if repo has commits — git worktree requires a valid HEAD
@@ -1127,33 +1022,15 @@ export function createAutoWorktree(basePath, milestoneId) {
1127
1022
  startPoint,
1128
1023
  });
1129
1024
  }
1130
- // Copy .gsd/ planning artifacts from the source repo into the new worktree.
1131
- // Worktrees are fresh git checkouts untracked files don't carry over.
1132
- // Planning artifacts may be untracked if the project's .gitignore had a
1133
- // blanket .gsd/ rule (pre-v2.14.0). Without this copy, auto-mode loops
1134
- // on plan-slice because the plan file doesn't exist in the worktree.
1135
- //
1136
- // IMPORTANT: Skip when re-attaching to an existing branch (#759).
1137
- // The branch checkout already has committed artifacts with correct state
1138
- // (e.g. [x] for completed slices). Copying from the project root would
1139
- // overwrite them with stale data ([ ] checkboxes) because the root is
1140
- // not always fully synced.
1141
- if (!branchExists) {
1142
- copyPlanningArtifacts(basePath, info.path);
1143
- }
1144
- else {
1145
- // Re-attaching to an existing branch: forward-merge any plan checkpoint
1146
- // state from the project root into the worktree (#778).
1147
- //
1148
- // If auto-mode stopped via crash, the milestone branch HEAD may lag behind
1149
- // the project root filesystem because syncStateToProjectRoot() ran after
1150
- // task completion but the auto-commit never fired. On restart the worktree
1151
- // is re-created from the branch HEAD (which has [ ] for the crashed task),
1152
- // causing verifyExpectedArtifact() to return false → stale-key eviction →
1153
- // infinite dispatch/skip loop. Reconciling here ensures the worktree sees
1154
- // the same [x] state that syncStateToProjectRoot() wrote to the root.
1155
- reconcilePlanCheckboxes(basePath, info.path, milestoneId);
1156
- }
1025
+ // Phase C: copyPlanningArtifacts and reconcilePlanCheckboxes were
1026
+ // deleted. Both addressed the same problem (worktree-local .gsd/
1027
+ // projection lagging behind project-root state) by maintaining a stale
1028
+ // copy. Now that auto-mode writers in workflow-projections.ts,
1029
+ // triage-resolution.ts, rule-registry.ts, and auto-post-unit.ts route
1030
+ // through s.canonicalProjectRoot, the worktree never needs a local
1031
+ // .gsd/ both reads and writes converge on the project-root .gsd/.
1032
+ // The original concerns (#759, #778) no longer apply because there is
1033
+ // no second copy to drift.
1157
1034
  // Run user-configured post-create hook (#597) — e.g. copy .env, symlink assets
1158
1035
  const hookError = runWorktreePostCreateHook(basePath, info.path);
1159
1036
  if (hookError) {
@@ -1173,51 +1050,13 @@ export function createAutoWorktree(basePath, milestoneId) {
1173
1050
  nudgeGitBranchCache(previousCwd);
1174
1051
  return info.path;
1175
1052
  }
1176
- /**
1177
- * Copy .gsd/ planning artifacts from source repo to a new worktree.
1178
- * Copies milestones/, DECISIONS.md, REQUIREMENTS.md, PROJECT.md, QUEUE.md,
1179
- * STATE.md, KNOWLEDGE.md, and OVERRIDES.md.
1180
- * Skips runtime files (auto.lock, metrics.json, etc.) and the worktrees/ dir.
1181
- * Best-effort failures are non-fatal since auto-mode can recreate artifacts.
1182
- */
1183
- function copyPlanningArtifacts(srcBase, wtPath) {
1184
- const srcGsd = join(srcBase, ".gsd");
1185
- const dstGsd = join(wtPath, ".gsd");
1186
- if (!existsSync(srcGsd))
1187
- return;
1188
- if (isSamePath(srcGsd, dstGsd))
1189
- return;
1190
- // Copy milestones/ directory (planning files, roadmaps, plans, research)
1191
- safeCopyRecursive(join(srcGsd, "milestones"), join(dstGsd, "milestones"), {
1192
- force: true,
1193
- filter: (src) => !src.endsWith("-META.json"),
1194
- });
1195
- // Copy top-level planning files
1196
- for (const file of [
1197
- "DECISIONS.md",
1198
- "REQUIREMENTS.md",
1199
- "PROJECT.md",
1200
- "QUEUE.md",
1201
- "STATE.md",
1202
- "KNOWLEDGE.md",
1203
- "OVERRIDES.md",
1204
- "mcp.json",
1205
- ]) {
1206
- safeCopy(join(srcGsd, file), join(dstGsd, file), { force: true });
1207
- }
1208
- // Seed canonical PREFERENCES.md when available; fall back to legacy lowercase.
1209
- if (existsSync(join(srcGsd, PROJECT_PREFERENCES_FILE))) {
1210
- safeCopy(join(srcGsd, PROJECT_PREFERENCES_FILE), join(dstGsd, PROJECT_PREFERENCES_FILE), { force: true });
1211
- }
1212
- else if (existsSync(join(srcGsd, LEGACY_PROJECT_PREFERENCES_FILE))) {
1213
- safeCopy(join(srcGsd, LEGACY_PROJECT_PREFERENCES_FILE), join(dstGsd, LEGACY_PROJECT_PREFERENCES_FILE), { force: true });
1214
- }
1215
- // Shared WAL (R012): worktrees use the project root's DB directly.
1216
- // No longer copy gsd.db into the worktree — the DB path resolver in
1217
- // ensureDbOpen() detects the worktree location and opens the root DB.
1218
- // Compat note: reconcileWorktreeDb() in mergeMilestoneToMain handles
1219
- // worktrees that already have a local gsd.db from before this change.
1220
- }
1053
+ // Phase C: copyPlanningArtifacts removed. Planning artifacts now live
1054
+ // only at the project root .gsd/; auto-mode writers (workflow-projections,
1055
+ // triage-resolution, rule-registry, regenerateIfMissing,
1056
+ // resolveHookArtifactPath) all route through s.canonicalProjectRoot.
1057
+ // Worktrees are pure git checkouts they no longer maintain a parallel
1058
+ // .gsd/ projection. The gsd.db has always lived at the project root via
1059
+ // the shared-WAL R012 contract; that is unchanged.
1221
1060
  /**
1222
1061
  * Teardown an auto-worktree: chdir back to original base, then remove
1223
1062
  * the worktree and its branch.