gsd-pi 2.78.1-dev.e9d88a536 → 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 (212) 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 +14 -7
  8. package/dist/resources/extensions/gsd/auto/session.js +36 -0
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +49 -4
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +26 -12
  11. package/dist/resources/extensions/gsd/auto-worktree.js +185 -201
  12. package/dist/resources/extensions/gsd/auto.js +139 -49
  13. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
  14. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +26 -20
  15. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
  16. package/dist/resources/extensions/gsd/crash-recovery.js +160 -47
  17. package/dist/resources/extensions/gsd/db/auto-workers.js +227 -0
  18. package/dist/resources/extensions/gsd/db/command-queue.js +105 -0
  19. package/dist/resources/extensions/gsd/db/milestone-leases.js +210 -0
  20. package/dist/resources/extensions/gsd/db/runtime-kv.js +91 -0
  21. package/dist/resources/extensions/gsd/db/unit-dispatches.js +322 -0
  22. package/dist/resources/extensions/gsd/db-writer.js +96 -16
  23. package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
  24. package/dist/resources/extensions/gsd/docs/COORDINATION.md +42 -0
  25. package/dist/resources/extensions/gsd/doctor-proactive.js +4 -0
  26. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +22 -6
  27. package/dist/resources/extensions/gsd/doctor.js +12 -2
  28. package/dist/resources/extensions/gsd/gsd-db.js +355 -3
  29. package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
  30. package/dist/resources/extensions/gsd/guided-flow.js +116 -26
  31. package/dist/resources/extensions/gsd/interrupted-session.js +18 -15
  32. package/dist/resources/extensions/gsd/metrics.js +287 -1
  33. package/dist/resources/extensions/gsd/paths.js +79 -8
  34. package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  35. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
  36. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  37. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  38. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  39. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  40. package/dist/resources/extensions/gsd/state.js +21 -6
  41. package/dist/resources/extensions/gsd/templates/project.md +10 -0
  42. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
  43. package/dist/resources/extensions/gsd/workspace.js +59 -0
  44. package/dist/resources/extensions/gsd/worktree-resolver.js +79 -2
  45. package/dist/resources/extensions/gsd/write-intercept.js +3 -3
  46. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  47. package/dist/web/standalone/.next/BUILD_ID +1 -1
  48. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  49. package/dist/web/standalone/.next/build-manifest.json +2 -2
  50. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  51. package/dist/web/standalone/.next/required-server-files.json +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.html +1 -1
  69. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  76. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  78. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  79. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  80. package/dist/web/standalone/server.js +1 -1
  81. package/package.json +1 -1
  82. package/packages/mcp-server/README.md +2 -11
  83. package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
  84. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
  85. package/packages/mcp-server/dist/remote-questions.js +28 -0
  86. package/packages/mcp-server/dist/remote-questions.js.map +1 -1
  87. package/packages/mcp-server/dist/server.d.ts +28 -0
  88. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  89. package/packages/mcp-server/dist/server.js +94 -4
  90. package/packages/mcp-server/dist/server.js.map +1 -1
  91. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  92. package/packages/mcp-server/src/mcp-server.test.ts +226 -0
  93. package/packages/mcp-server/src/remote-questions.test.ts +103 -0
  94. package/packages/mcp-server/src/remote-questions.ts +35 -0
  95. package/packages/mcp-server/src/server.ts +129 -6
  96. package/packages/mcp-server/src/workflow-tools.ts +1 -1
  97. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  98. package/src/resources/extensions/gsd/auto/detect-stuck.ts +37 -5
  99. package/src/resources/extensions/gsd/auto/loop.ts +263 -41
  100. package/src/resources/extensions/gsd/auto/phases.ts +15 -7
  101. package/src/resources/extensions/gsd/auto/session.ts +40 -0
  102. package/src/resources/extensions/gsd/auto-dispatch.ts +63 -4
  103. package/src/resources/extensions/gsd/auto-post-unit.ts +27 -12
  104. package/src/resources/extensions/gsd/auto-worktree.ts +218 -225
  105. package/src/resources/extensions/gsd/auto.ts +166 -43
  106. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
  107. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +26 -21
  108. package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
  109. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
  110. package/src/resources/extensions/gsd/crash-recovery.ts +177 -43
  111. package/src/resources/extensions/gsd/db/auto-workers.ts +273 -0
  112. package/src/resources/extensions/gsd/db/command-queue.ts +149 -0
  113. package/src/resources/extensions/gsd/db/milestone-leases.ts +274 -0
  114. package/src/resources/extensions/gsd/db/runtime-kv.ts +127 -0
  115. package/src/resources/extensions/gsd/db/unit-dispatches.ts +446 -0
  116. package/src/resources/extensions/gsd/db-writer.ts +113 -17
  117. package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
  118. package/src/resources/extensions/gsd/docs/COORDINATION.md +42 -0
  119. package/src/resources/extensions/gsd/doctor-proactive.ts +4 -0
  120. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +24 -6
  121. package/src/resources/extensions/gsd/doctor.ts +10 -2
  122. package/src/resources/extensions/gsd/gsd-db.ts +354 -3
  123. package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
  124. package/src/resources/extensions/gsd/guided-flow.ts +152 -26
  125. package/src/resources/extensions/gsd/interrupted-session.ts +19 -12
  126. package/src/resources/extensions/gsd/metrics.ts +321 -1
  127. package/src/resources/extensions/gsd/paths.ts +67 -8
  128. package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  129. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
  130. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  131. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  132. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  133. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  134. package/src/resources/extensions/gsd/state.ts +44 -6
  135. package/src/resources/extensions/gsd/templates/project.md +10 -0
  136. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
  137. package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +72 -0
  138. package/src/resources/extensions/gsd/tests/auto-loop-symlink-worktree.test.ts +190 -0
  139. package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
  140. package/src/resources/extensions/gsd/tests/auto-workers.test.ts +105 -0
  141. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
  142. package/src/resources/extensions/gsd/tests/command-queue.test.ts +141 -0
  143. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +203 -0
  144. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +169 -59
  145. package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
  146. package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
  147. package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
  148. package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
  149. package/src/resources/extensions/gsd/tests/detect-stuck-respects-retry.test.ts +173 -0
  150. package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
  151. package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
  152. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
  153. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
  154. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
  155. package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
  156. package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
  157. package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
  158. package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
  159. package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
  160. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +22 -12
  161. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +24 -10
  162. package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +35 -23
  163. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +369 -0
  164. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +72 -25
  165. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +72 -25
  166. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +9 -6
  167. package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
  168. package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
  169. package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
  170. package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
  171. package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
  172. package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
  173. package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +152 -0
  174. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
  175. package/src/resources/extensions/gsd/tests/parallel-milestone-isolation.test.ts +106 -0
  176. package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
  177. package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
  178. package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
  179. package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +119 -0
  180. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
  181. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +58 -0
  182. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +3 -17
  183. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
  184. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +138 -16
  185. package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
  186. package/src/resources/extensions/gsd/tests/runtime-kv.test.ts +120 -0
  187. package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +133 -28
  188. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +17 -0
  189. package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +134 -0
  190. package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +434 -0
  191. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
  192. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +98 -0
  193. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
  194. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
  195. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +247 -0
  196. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +41 -1
  197. package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
  198. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
  199. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
  200. package/src/resources/extensions/gsd/tests/workspace.test.ts +196 -0
  201. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
  202. package/src/resources/extensions/gsd/tests/write-gate.test.ts +94 -71
  203. package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
  204. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
  205. package/src/resources/extensions/gsd/workspace.ts +95 -0
  206. package/src/resources/extensions/gsd/worktree-resolver.ts +78 -2
  207. package/src/resources/extensions/gsd/write-intercept.ts +3 -3
  208. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +0 -213
  209. package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +0 -87
  210. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +0 -159
  211. /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_buildManifest.js +0 -0
  212. /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_ssgManifest.js +0 -0
@@ -16,6 +16,7 @@
16
16
  * `let` or `var` declarations.
17
17
  */
18
18
  import { resolveWorktreeProjectRoot } from "../worktree-root.js";
19
+ import { normalizeRealPath } from "../paths.js";
19
20
  // ─── Constants ───────────────────────────────────────────────────────────────
20
21
  export const STUB_RECOVERY_THRESHOLD = 2;
21
22
  export const NEW_SESSION_TIMEOUT_MS = 120_000;
@@ -32,6 +33,22 @@ export class AutoSession {
32
33
  // ── Paths ────────────────────────────────────────────────────────────────
33
34
  basePath = "";
34
35
  originalBasePath = "";
36
+ // TODO(C8): remove basePath/originalBasePath once all readers use s.scope
37
+ scope = null;
38
+ // ── Coordination identity (Phase B — DB-backed coordination) ────────────
39
+ /**
40
+ * Worker registry ID set by registerAutoWorker() at session start. Used by
41
+ * heartbeatAutoWorker() each loop iteration and by recordDispatchClaim()
42
+ * to fence dispatch ledger writes against stale workers.
43
+ */
44
+ workerId = null;
45
+ /**
46
+ * Active milestone lease fencing token, set by claimMilestoneLease() inside
47
+ * worktree-resolver.enterMilestone(). Threaded into recordDispatchClaim()
48
+ * as milestone_lease_token so out-of-band dispatches by a stale worker
49
+ * are detectable.
50
+ */
51
+ milestoneLeaseToken = null;
35
52
  previousProjectRootEnv = null;
36
53
  hadProjectRootEnv = false;
37
54
  projectRootEnvCaptured = false;
@@ -160,6 +177,22 @@ export class AutoSession {
160
177
  get lockBasePath() {
161
178
  return resolveWorktreeProjectRoot(this.basePath, this.originalBasePath);
162
179
  }
180
+ /**
181
+ * Canonical project root for state-derivation reads AND writer paths.
182
+ *
183
+ * Prefers the realpath-normalized projectRoot from the MilestoneScope
184
+ * (introduced by PR #5236), falling back to resolveWorktreeProjectRoot
185
+ * during early lifecycle / engine-bypass paths where scope may be null.
186
+ *
187
+ * Always realpath-normalized so cache keys (e.g. deriveState's _stateCache)
188
+ * cannot drift across worktree↔project-root path-string variants for the
189
+ * same filesystem location.
190
+ */
191
+ get canonicalProjectRoot() {
192
+ const root = this.scope?.workspace.projectRoot
193
+ ?? resolveWorktreeProjectRoot(this.basePath, this.originalBasePath);
194
+ return normalizeRealPath(root);
195
+ }
163
196
  reset() {
164
197
  this.clearTimers();
165
198
  // Lifecycle
@@ -173,6 +206,9 @@ export class AutoSession {
173
206
  // Paths
174
207
  this.basePath = "";
175
208
  this.originalBasePath = "";
209
+ this.scope = null;
210
+ this.workerId = null;
211
+ this.milestoneLeaseToken = null;
176
212
  this.previousProjectRootEnv = null;
177
213
  this.hadProjectRootEnv = false;
178
214
  this.projectRootEnvCaptured = false;
@@ -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";
@@ -31,6 +31,9 @@ import { ensureWorkflowPreferencesCaptured } from "./planning-depth.js";
31
31
  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
+ import { annotateBackgroundable } from "./delegation-policy.js";
35
+ import { invalidateAllCaches } from "./cache.js";
36
+ import { insertMilestoneValidationGates } from "./milestone-validation-gates.js";
34
37
  let reassessmentChecker = checkNeedsReassessment;
35
38
  let researchProjectPromptBuilder = buildResearchProjectPrompt;
36
39
  function shouldBypassMilestoneDepthGateInAuto(prefs) {
@@ -995,9 +998,12 @@ export const DISPATCH_RULES = [
995
998
  const skipSource = trivialVariant
996
999
  ? "trivial-scope pipeline variant (#4781)"
997
1000
  : "`skip_milestone_validation` preference";
1001
+ const skipValidationReason = trivialVariant ? "trivial-scope" : "preference";
998
1002
  const content = [
999
1003
  "---",
1000
1004
  "verdict: pass",
1005
+ "skip_validation: true",
1006
+ `skip_validation_reason: ${skipValidationReason}`,
1001
1007
  "remediation_round: 0",
1002
1008
  "---",
1003
1009
  "",
@@ -1006,6 +1012,39 @@ export const DISPATCH_RULES = [
1006
1012
  `Milestone validation was skipped via ${skipSource}.`,
1007
1013
  ].join("\n");
1008
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();
1009
1048
  }
1010
1049
  return { action: "skip" };
1011
1050
  }
@@ -1086,13 +1125,19 @@ export const DISPATCH_RULES = [
1086
1125
  if (validationContent) {
1087
1126
  // Allow completion when validation was intentionally skipped by
1088
1127
  // preference/budget profile (#3399, #3344).
1128
+ const skippedByMarker = /^skip_validation:\s*true$/im.test(validationContent);
1089
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);
1090
1131
  // Accept either the structured template format (table with MET/N/A/SATISFIED)
1091
1132
  // or prose evidence patterns the validation agent may emit.
1092
1133
  const structuredMatch = validationContent.includes("Operational") &&
1093
1134
  (validationContent.includes("MET") || validationContent.includes("N/A") || validationContent.includes("SATISFIED"));
1094
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);
1095
- const hasOperationalCheck = skippedByPreference || structuredMatch || proseMatch;
1136
+ const hasOperationalCheck = skippedByMarker ||
1137
+ skippedByPreference ||
1138
+ skippedByTrivialVariant ||
1139
+ structuredMatch ||
1140
+ proseMatch;
1096
1141
  if (!hasOperationalCheck) {
1097
1142
  return {
1098
1143
  action: "stop",
@@ -1152,7 +1197,7 @@ export async function resolveDispatch(ctx) {
1152
1197
  // Delegate to registry when available
1153
1198
  try {
1154
1199
  const registry = getRegistry();
1155
- return await registry.evaluateDispatch(ctx);
1200
+ return annotateBackgroundable(await registry.evaluateDispatch(ctx));
1156
1201
  }
1157
1202
  catch (err) {
1158
1203
  // Registry not initialized — fall back to inline loop
@@ -1163,7 +1208,7 @@ export async function resolveDispatch(ctx) {
1163
1208
  if (result) {
1164
1209
  if (result.action !== "skip")
1165
1210
  result.matchedRule = rule.name;
1166
- return result;
1211
+ return annotateBackgroundable(result);
1167
1212
  }
1168
1213
  }
1169
1214
  // No rule matched — unhandled phase.
@@ -24,6 +24,7 @@ import { runTurnGitAction, } from "./git-service.js";
24
24
  import { verifyExpectedArtifact, resolveExpectedArtifactPath, writeBlockerPlaceholder, diagnoseExpectedArtifact, } from "./auto-recovery.js";
25
25
  import { regenerateIfMissing } from "./workflow-projections.js";
26
26
  import { syncStateToProjectRoot } from "./auto-worktree.js";
27
+ import { normalizeWorktreePathForCompare } from "./worktree-root.js";
27
28
  import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
28
29
  import { renderPlanCheckboxes } from "./markdown-renderer.js";
29
30
  import { consumeSignal } from "./session-status-io.js";
@@ -51,6 +52,11 @@ import { detectAbandonMilestone } from "./abandon-detect.js";
51
52
  import { isDeterministicPolicyError } from "./auto-tool-tracking.js";
52
53
  import { clearProjectResearchInflightMarker, finalizeProjectResearchTimeout, } from "./project-research-policy.js";
53
54
  import { validateArtifact } from "./schemas/validate.js";
55
+ // ─── Path Comparison Helper ───────────────────────────────────────────────
56
+ /** Compare two paths for physical identity, tolerating trailing slashes and symlinks. */
57
+ function isSamePathLocal(a, b) {
58
+ return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
59
+ }
54
60
  /** Maximum verification retry attempts before escalating to blocker placeholder (#2653). */
55
61
  const MAX_VERIFICATION_RETRIES = 3;
56
62
  /** Keep failure toasts short while still showing concrete examples. */
@@ -510,7 +516,7 @@ export async function postUnitPreVerification(pctx, opts) {
510
516
  await rebuildState(s.basePath);
511
517
  });
512
518
  // Sync worktree state back to project root (skipped for lightweight sidecars)
513
- if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath !== s.basePath) {
519
+ if (!opts?.skipWorktreeSync && s.originalBasePath && !isSamePathLocal(s.originalBasePath, s.basePath)) {
514
520
  await runSafely("postUnit", "worktree-sync", () => {
515
521
  syncStateToProjectRoot(s.basePath, s.originalBasePath, s.currentMilestoneId);
516
522
  });
@@ -650,13 +656,15 @@ export async function postUnitPreVerification(pctx, opts) {
650
656
  if (s.currentUnit.type === "triage-captures") {
651
657
  try {
652
658
  const { executeTriageResolutions } = await import("./triage-resolution.js");
653
- const state = await deriveState(s.basePath);
659
+ const state = await deriveState(s.canonicalProjectRoot);
654
660
  const mid = state.activeMilestone?.id ?? "";
655
661
  const sid = state.activeSlice?.id ?? "";
656
662
  // executeTriageResolutions handles defer milestone creation even
657
663
  // without an active milestone/slice (the "all milestones complete"
658
664
  // scenario from #1562). inject/replan/quick-task still require mid+sid.
659
- 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);
660
668
  if (triageResult.injected > 0) {
661
669
  ctx.ui.notify(`Triage: injected ${triageResult.injected} task${triageResult.injected === 1 ? "" : "s"} into ${sid} plan.`, "info");
662
670
  }
@@ -799,10 +807,14 @@ export async function postUnitPreVerification(pctx, opts) {
799
807
  try {
800
808
  const { milestone: mid, slice: sid } = parseUnitId(s.currentUnit.id);
801
809
  if (mid && sid) {
802
- 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");
803
815
  if (regenerated) {
804
816
  // Re-check after regeneration
805
- triggerArtifactVerified = verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
817
+ triggerArtifactVerified = verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.canonicalProjectRoot);
806
818
  if (triggerArtifactVerified) {
807
819
  invalidateAllCaches();
808
820
  }
@@ -993,7 +1005,7 @@ export async function postUnitPostVerification(pctx) {
993
1005
  if (mid && sid && tid) {
994
1006
  try {
995
1007
  updateTaskStatus(mid, sid, tid, "pending");
996
- await renderPlanCheckboxes(s.basePath, mid, sid);
1008
+ await renderPlanCheckboxes(s.canonicalProjectRoot, mid, sid);
997
1009
  }
998
1010
  catch (dbErr) {
999
1011
  // DB unavailable — fail explicitly rather than silently reverting to markdown mutation.
@@ -1003,7 +1015,8 @@ export async function postUnitPostVerification(pctx) {
1003
1015
  }
1004
1016
  // 2. Delete SUMMARY.md for the task
1005
1017
  if (mid && sid && tid) {
1006
- const tasksDir = resolveTasksDir(s.basePath, mid, sid);
1018
+ // Phase C: read+delete via canonical project root.
1019
+ const tasksDir = resolveTasksDir(s.canonicalProjectRoot, mid, sid);
1007
1020
  if (tasksDir) {
1008
1021
  const summaryFile = join(tasksDir, buildTaskFileName(tid, "SUMMARY"));
1009
1022
  if (existsSync(summaryFile)) {
@@ -1013,7 +1026,7 @@ export async function postUnitPostVerification(pctx) {
1013
1026
  }
1014
1027
  // 3. Delete the retry_on artifact (e.g. NEEDS-REWORK.md)
1015
1028
  if (trigger.retryArtifact) {
1016
- const retryArtifactPath = resolveHookArtifactPath(s.basePath, trigger.unitId, trigger.retryArtifact);
1029
+ const retryArtifactPath = resolveHookArtifactPath(s.canonicalProjectRoot, trigger.unitId, trigger.retryArtifact);
1017
1030
  if (existsSync(retryArtifactPath)) {
1018
1031
  unlinkSync(retryArtifactPath);
1019
1032
  }
@@ -1249,16 +1262,17 @@ export async function postUnitPostVerification(pctx) {
1249
1262
  if (hasPendingCaptures(s.basePath)) {
1250
1263
  const pending = loadPendingCaptures(s.basePath);
1251
1264
  if (pending.length > 0) {
1252
- const state = await deriveState(s.basePath);
1265
+ const readRoot = s.canonicalProjectRoot;
1266
+ const state = await deriveState(readRoot);
1253
1267
  const mid = state.activeMilestone?.id;
1254
1268
  const sid = state.activeSlice?.id;
1255
1269
  if (mid && sid) {
1256
1270
  let currentPlan = "";
1257
1271
  let roadmapContext = "";
1258
- const planFile = resolveSliceFile(s.basePath, mid, sid, "PLAN");
1272
+ const planFile = resolveSliceFile(readRoot, mid, sid, "PLAN");
1259
1273
  if (planFile)
1260
1274
  currentPlan = (await loadFile(planFile)) ?? "";
1261
- const roadmapFile = resolveMilestoneFile(s.basePath, mid, "ROADMAP");
1275
+ const roadmapFile = resolveMilestoneFile(readRoot, mid, "ROADMAP");
1262
1276
  if (roadmapFile)
1263
1277
  roadmapContext = (await loadFile(roadmapFile)) ?? "";
1264
1278
  const capturesList = pending.map(c => `- **${c.id}**: "${c.text}" (captured: ${c.timestamp})`).join("\n");
@@ -1306,7 +1320,7 @@ export async function postUnitPostVerification(pctx) {
1306
1320
  // exits the loop, leaving the user with no hint to /clear and /gsd again.
1307
1321
  if (s.stepMode) {
1308
1322
  try {
1309
- const nextState = await deriveState(s.basePath);
1323
+ const nextState = await deriveState(s.canonicalProjectRoot);
1310
1324
  ctx.ui.notify(buildStepCompleteMessage(nextState), "info");
1311
1325
  }
1312
1326
  catch (e) {