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
@@ -11,7 +11,8 @@
11
11
  */
12
12
  import { deriveState } from "./state.js";
13
13
  import { parseUnitId } from "./unit-id.js";
14
- import { assessInterruptedSession, readPausedSessionMetadata, } from "./interrupted-session.js";
14
+ import { assessInterruptedSession, readPausedSessionMetadata, PAUSED_SESSION_KV_KEY, } from "./interrupted-session.js";
15
+ import { setRuntimeKv, deleteRuntimeKv, } from "./db/runtime-kv.js";
15
16
  import { getManifestStatus } from "./files.js";
16
17
  export { inlinePriorMilestoneSummary } from "./files.js";
17
18
  import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
@@ -40,7 +41,7 @@ import { setLogBasePath, logWarning } from "./workflow-logger.js";
40
41
  import { preflightCleanRoot, postflightPopStash } from "./clean-root-preflight.js";
41
42
  import { isAbsolute, join } from "node:path";
42
43
  import { pathToFileURL } from "node:url";
43
- import { readFileSync, existsSync, mkdirSync, unlinkSync } from "node:fs";
44
+ import { readFileSync, existsSync, mkdirSync } from "node:fs";
44
45
  import { atomicWriteSync } from "./atomic-write.js";
45
46
  import { autoCommitCurrentBranch, captureIntegrationBranch, detectWorktreeName, getCurrentBranch, getMainBranch, setActiveMilestoneId, } from "./worktree.js";
46
47
  import { GitServiceImpl } from "./git-service.js";
@@ -86,6 +87,10 @@ import { reorderForCaching } from "./prompt-ordering.js";
86
87
  export { STUB_RECOVERY_THRESHOLD, NEW_SESSION_TIMEOUT_MS, } from "./auto/session.js";
87
88
  import { autoSession as s } from "./auto-runtime-state.js";
88
89
  import { gsdHome } from "./gsd-home.js";
90
+ import { createWorkspace, scopeMilestone } from "./workspace.js";
91
+ import { registerAutoWorker, markWorkerStopping } from "./db/auto-workers.js";
92
+ import { releaseMilestoneLease } from "./db/milestone-leases.js";
93
+ import { normalizeRealPath } from "./paths.js";
89
94
  // ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
90
95
  // ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
91
96
  // This file must NOT declare module-level `let` or `var` variables for state.
@@ -100,6 +105,27 @@ import { gsdHome } from "./gsd-home.js";
100
105
  // ─────────────────────────────────────────────────────────────────────────────
101
106
  /** Throttle STATE.md rebuilds — at most once per 30 seconds */
102
107
  const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
108
+ /**
109
+ * Phase B — register this auto-mode process in the workers table so other
110
+ * workers and janitors can detect liveness via heartbeat. Best-effort: if
111
+ * the DB is unavailable (e.g. fresh project before init) we skip registration
112
+ * silently rather than blocking session start.
113
+ */
114
+ function registerAutoWorkerForSession(session) {
115
+ if (session.workerId)
116
+ return; // already registered (e.g. resume re-runs)
117
+ try {
118
+ const projectRootRealpath = normalizeRealPath(session.scope?.workspace.projectRoot
119
+ ?? (session.originalBasePath || session.basePath));
120
+ session.workerId = registerAutoWorker({ projectRootRealpath });
121
+ }
122
+ catch (err) {
123
+ debugLog("autoLoop", {
124
+ phase: "register-worker-failed",
125
+ error: err instanceof Error ? err.message : String(err),
126
+ });
127
+ }
128
+ }
103
129
  function captureProjectRootEnv(projectRoot) {
104
130
  if (!s.projectRootEnvCaptured) {
105
131
  s.hadProjectRootEnv = Object.prototype.hasOwnProperty.call(process.env, "GSD_PROJECT_ROOT");
@@ -147,6 +173,32 @@ function restoreMilestoneLockEnv() {
147
173
  s.hadMilestoneLockEnv = false;
148
174
  s.milestoneLockEnvCaptured = false;
149
175
  }
176
+ /**
177
+ * Rebuild s.scope from the current s.basePath / s.originalBasePath / s.currentMilestoneId.
178
+ *
179
+ * Pass the worktree path as rawPath when entering a worktree so createWorkspace
180
+ * can detect the worktree layout and set mode="worktree". When no worktree is
181
+ * active, rawPath should equal the project root.
182
+ *
183
+ * Clears s.scope when milestoneId is absent — scope is only meaningful when a
184
+ * milestone is active.
185
+ *
186
+ * TODO(C8): remove basePath/originalBasePath once all readers use s.scope.
187
+ */
188
+ function rebuildScope(rawPath, milestoneId) {
189
+ if (!milestoneId) {
190
+ s.scope = null;
191
+ return;
192
+ }
193
+ try {
194
+ const workspace = createWorkspace(rawPath);
195
+ s.scope = scopeMilestone(workspace, milestoneId);
196
+ }
197
+ catch {
198
+ // Non-fatal — scope is additive. Existing readers still use basePath.
199
+ s.scope = null;
200
+ }
201
+ }
150
202
  function normalizeSessionFilePath(raw) {
151
203
  if (typeof raw !== "string")
152
204
  return null;
@@ -284,6 +336,18 @@ export function isAutoActive() {
284
336
  export function _setAutoActiveForTest(active) {
285
337
  s.active = active;
286
338
  }
339
+ /**
340
+ * Test-only seam: emit the missing-worktree warning exactly as the resume path
341
+ * does. Allows unit tests to verify the warning is produced without
342
+ * bootstrapping the full auto-mode entry point. Do not use in production code.
343
+ */
344
+ export function _warnIfWorktreeMissingForTest(worktreePath, milestoneId) {
345
+ if (worktreePath && !existsSync(worktreePath)) {
346
+ logWarning("session", `Worktree was expected at ${worktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`, { file: "auto.ts", milestoneId });
347
+ return true;
348
+ }
349
+ return false;
350
+ }
287
351
  export function isAutoPaused() {
288
352
  return s.paused;
289
353
  }
@@ -582,6 +646,21 @@ export async function stopAuto(ctx, pi, reason) {
582
646
  catch (e) {
583
647
  debugLog("stop-cleanup-locks", { error: e instanceof Error ? e.message : String(e) });
584
648
  }
649
+ // ── Step 1b: Coordination cleanup (Phase B) ──
650
+ // Release any active milestone lease so other workers don't have to
651
+ // wait for TTL expiry, then mark this worker as stopping. Best-effort:
652
+ // DB unavailability or stale state must not block shutdown.
653
+ try {
654
+ if (s.workerId && s.currentMilestoneId && s.milestoneLeaseToken) {
655
+ releaseMilestoneLease(s.workerId, s.currentMilestoneId, s.milestoneLeaseToken);
656
+ }
657
+ if (s.workerId) {
658
+ markWorkerStopping(s.workerId);
659
+ }
660
+ }
661
+ catch (e) {
662
+ debugLog("stop-cleanup-coordination", { error: e instanceof Error ? e.message : String(e) });
663
+ }
585
664
  // ── Step 1b: Flush queued follow-up messages (#3512) ──
586
665
  // Late async notifications (async_job_result, gsd-auto-wrapup) can trigger
587
666
  // extra LLM turns after stop. Flush them the same way run-unit.ts does.
@@ -760,13 +839,12 @@ export async function stopAuto(ctx, pi, reason) {
760
839
  debugLog("stop-cleanup-metrics", { error: e instanceof Error ? e.message : String(e) });
761
840
  }
762
841
  // ── Step 12: Remove paused-session metadata (#1383) ──
842
+ // Phase C pt 2: deleteRuntimeKv replaces unlinkSync(paused-session.json).
763
843
  try {
764
- const pausedPath = join(gsdRoot(s.originalBasePath || s.basePath), "runtime", "paused-session.json");
765
- if (existsSync(pausedPath))
766
- unlinkSync(pausedPath);
844
+ deleteRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
767
845
  }
768
846
  catch (err) { /* non-fatal */
769
- logWarning("engine", `file unlink failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
847
+ logWarning("engine", `paused-session DB delete failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
770
848
  }
771
849
  // ── Step 13: Restore original model + thinking (before reset clears IDs) ──
772
850
  try {
@@ -858,10 +936,12 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
858
936
  resolveAgentEndCancelled(_errorContext);
859
937
  s.pausedSessionFile = normalizeSessionFilePath(ctx?.sessionManager?.getSessionFile() ?? null);
860
938
  // Persist paused-session metadata so resume survives /exit (#1383).
861
- // The fresh-start bootstrap checks for this file and restores worktree context.
939
+ // Phase C pt 2: persisted to runtime_kv (global scope, key
940
+ // PAUSED_SESSION_KV_KEY) instead of runtime/paused-session.json. The
941
+ // fresh-start bootstrap below reads from the same key.
862
942
  try {
863
943
  const pausedMeta = {
864
- milestoneId: s.currentMilestoneId,
944
+ milestoneId: s.currentMilestoneId ?? undefined,
865
945
  worktreePath: isInAutoWorktree(s.basePath) ? s.basePath : null,
866
946
  originalBasePath: s.originalBasePath,
867
947
  stepMode: s.stepMode,
@@ -869,17 +949,16 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
869
949
  sessionFile: s.pausedSessionFile,
870
950
  unitType: s.currentUnit?.type ?? undefined,
871
951
  unitId: s.currentUnit?.id ?? undefined,
872
- activeEngineId: s.activeEngineId,
952
+ activeEngineId: s.activeEngineId ?? undefined,
873
953
  activeRunDir: s.activeRunDir,
874
954
  autoStartTime: s.autoStartTime,
875
955
  milestoneLock: s.sessionMilestoneLock ?? undefined,
876
956
  };
877
- const runtimeDir = join(gsdRoot(s.originalBasePath || s.basePath), "runtime");
878
- atomicWriteSync(join(runtimeDir, "paused-session.json"), JSON.stringify(pausedMeta, null, 2), "utf-8");
957
+ setRuntimeKv("global", "", PAUSED_SESSION_KV_KEY, pausedMeta);
879
958
  }
880
959
  catch (err) {
881
960
  // Non-fatal — resume will still work via full bootstrap, just without worktree context
882
- logWarning("engine", `paused-session file write failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
961
+ logWarning("engine", `paused-session DB write failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
883
962
  }
884
963
  // Close out the current unit so its runtime record doesn't stay at "dispatched"
885
964
  if (s.currentUnit && ctx) {
@@ -1115,8 +1194,10 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1115
1194
  if (recoverFailedMigration(base)) {
1116
1195
  ctx.ui.notify("Recovered unfinished migration (.gsd.migrating → .gsd).", "info");
1117
1196
  }
1118
- const freshStartAssessment = interruptedAssessment
1119
- ?? await assessInterruptedSession(base);
1197
+ const freshStartAssessment = await (interruptedAssessment
1198
+ ?? (() => {
1199
+ return ensureDbOpen(base).then(() => assessInterruptedSession(base));
1200
+ })());
1120
1201
  if (freshStartAssessment.classification === "running") {
1121
1202
  const pid = freshStartAssessment.lock?.pid;
1122
1203
  ctx.ui.notify(pid
@@ -1126,10 +1207,20 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1126
1207
  }
1127
1208
  // If resuming from paused state, just re-activate and dispatch next unit.
1128
1209
  // Check persisted paused-session first (#1383) — survives /exit.
1210
+ // Phase C pt 2: persisted in runtime_kv (global scope) instead of
1211
+ // runtime/paused-session.json. The `clearPausedSession` helper
1212
+ // replaces every prior unlinkSync(pausedPath) call.
1213
+ const clearPausedSession = (logTag) => {
1214
+ try {
1215
+ deleteRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
1216
+ }
1217
+ catch (err) {
1218
+ logWarning("session", `${logTag}: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
1219
+ }
1220
+ };
1129
1221
  if (!s.paused) {
1130
1222
  try {
1131
1223
  const meta = freshStartAssessment.pausedSession ?? readPausedSessionMetadata(base);
1132
- const pausedPath = join(gsdRoot(base), "runtime", "paused-session.json");
1133
1224
  if (meta?.activeEngineId && meta.activeEngineId !== "dev") {
1134
1225
  // Custom workflow resume — restore engine state
1135
1226
  s.activeEngineId = meta.activeEngineId;
@@ -1139,14 +1230,6 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1139
1230
  s.autoStartTime = meta.autoStartTime || Date.now();
1140
1231
  s.sessionMilestoneLock = meta.milestoneLock ?? null;
1141
1232
  s.paused = true;
1142
- try {
1143
- unlinkSync(pausedPath);
1144
- }
1145
- catch (e) {
1146
- if (e.code !== "ENOENT") {
1147
- logWarning("session", `pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" });
1148
- }
1149
- }
1150
1233
  ctx.ui.notify(`Resuming paused custom workflow${meta.activeRunDir ? ` (${meta.activeRunDir})` : ""}.`, "info");
1151
1234
  }
1152
1235
  else if (meta?.milestoneId) {
@@ -1184,14 +1267,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1184
1267
  }
1185
1268
  }
1186
1269
  if (!mDir || summaryIsTerminal) {
1187
- try {
1188
- unlinkSync(pausedPath);
1189
- }
1190
- catch (err) {
1191
- if (err.code !== "ENOENT") {
1192
- logWarning("session", `pause file cleanup failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
1193
- }
1194
- }
1270
+ clearPausedSession("paused-session DB cleanup failed (milestone gone/complete)");
1195
1271
  ctx.ui.notify(`Paused milestone ${meta.milestoneId} is ${!mDir ? "missing" : "already complete"}. Starting fresh.`, "info");
1196
1272
  }
1197
1273
  else {
@@ -1204,26 +1280,25 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1204
1280
  s.autoStartTime = meta.autoStartTime || Date.now();
1205
1281
  s.sessionMilestoneLock = meta.milestoneLock ?? null;
1206
1282
  s.paused = true;
1207
- try {
1208
- unlinkSync(pausedPath);
1209
- }
1210
- catch (e) {
1211
- if (e.code !== "ENOENT") {
1212
- logWarning("session", `pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" });
1283
+ // Build scope from persisted state. Use worktreePath when present and
1284
+ // still on disk so mode is detected correctly; fall back to project root.
1285
+ {
1286
+ const persistedWorktreePath = meta.worktreePath ?? null;
1287
+ if (persistedWorktreePath && !existsSync(persistedWorktreePath)) {
1288
+ logWarning("session", `Worktree was expected at ${persistedWorktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`, { file: "auto.ts", milestoneId: meta.milestoneId ?? "" });
1213
1289
  }
1290
+ const rawForScope = (persistedWorktreePath && existsSync(persistedWorktreePath))
1291
+ ? persistedWorktreePath
1292
+ : (s.originalBasePath || base);
1293
+ rebuildScope(rawForScope, s.currentMilestoneId);
1214
1294
  }
1215
1295
  ctx.ui.notify(`Resuming paused session for ${meta.milestoneId}${meta.worktreePath && existsSync(meta.worktreePath) ? ` (worktree)` : ""}.`, "info");
1216
1296
  }
1217
1297
  }
1218
- else if (existsSync(pausedPath)) {
1219
- try {
1220
- unlinkSync(pausedPath);
1221
- }
1222
- catch (e) {
1223
- if (e.code !== "ENOENT") {
1224
- logWarning("session", `stale pause file cleanup failed: ${e instanceof Error ? e.message : String(e)}`, { file: "auto.ts" });
1225
- }
1226
- }
1298
+ else if (meta) {
1299
+ // Stale paused-session metadata that the assessment chose not to
1300
+ // resume — clean it up so the next bootstrap starts fresh.
1301
+ clearPausedSession("stale paused-session DB cleanup failed");
1227
1302
  }
1228
1303
  }
1229
1304
  }
@@ -1285,10 +1360,15 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1285
1360
  // session (e.g. isolation mode changed, detectWorktreeName differs across
1286
1361
  // process restarts). We guard with existsSync so a stale or deleted
1287
1362
  // worktree directory safely falls back to the project root.
1288
- const resumeWorktreePath = freshStartAssessment.pausedSession?.worktreePath;
1363
+ const resumeWorktreePath = freshStartAssessment.pausedSession?.worktreePath ?? null;
1364
+ if (resumeWorktreePath && !existsSync(resumeWorktreePath)) {
1365
+ logWarning("session", `Worktree was expected at ${resumeWorktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`, { file: "auto.ts", milestoneId: s.currentMilestoneId ?? "" });
1366
+ }
1289
1367
  if (resumeWorktreePath && existsSync(resumeWorktreePath)) {
1290
1368
  s.basePath = resumeWorktreePath;
1291
1369
  }
1370
+ // Rebuild scope now that s.basePath reflects the actual worktree (or project root).
1371
+ rebuildScope(s.basePath, s.currentMilestoneId);
1292
1372
  // Ensure the workflow-logger audit log is pinned to the project root
1293
1373
  // even when auto-mode is entered via a path that bypasses the
1294
1374
  // bootstrap/dynamic-tools ensureDbOpen() → setLogBasePath() chain
@@ -1315,6 +1395,8 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1315
1395
  buildResolver().enterMilestone(s.currentMilestoneId, {
1316
1396
  notify: ctx.ui.notify.bind(ctx.ui),
1317
1397
  });
1398
+ // s.basePath may have been updated to a worktree path by enterMilestone.
1399
+ rebuildScope(s.basePath, s.currentMilestoneId);
1318
1400
  }
1319
1401
  registerSigtermHandler(lockBase());
1320
1402
  ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
@@ -1367,10 +1449,14 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1367
1449
  }
1368
1450
  s.pausedSessionFile = null;
1369
1451
  }
1452
+ captureProjectRootEnv(s.originalBasePath || s.basePath);
1453
+ registerAutoWorkerForSession(s);
1370
1454
  updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown");
1371
- writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown");
1455
+ if (s.workerId) {
1456
+ writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown");
1457
+ clearPausedSession("paused-session DB cleanup failed (resume activation)");
1458
+ }
1372
1459
  pi.events.emit(CMUX_CHANNELS.LOG, { preferences: loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, message: s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", level: "progress" });
1373
- captureProjectRootEnv(s.originalBasePath || s.basePath);
1374
1460
  startAutoCommandPolling(s.basePath);
1375
1461
  await runAutoLoopWithUok({
1376
1462
  ctx,
@@ -1393,7 +1479,11 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1393
1479
  const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps, freshStartAssessment);
1394
1480
  if (!ready)
1395
1481
  return;
1482
+ // Build scope after bootstrap has populated s.basePath / s.originalBasePath /
1483
+ // s.currentMilestoneId (including worktree setup inside bootstrapAutoSession).
1484
+ rebuildScope(s.basePath, s.currentMilestoneId);
1396
1485
  captureProjectRootEnv(s.originalBasePath || s.basePath);
1486
+ registerAutoWorkerForSession(s);
1397
1487
  try {
1398
1488
  pi.events.emit(CMUX_CHANNELS.SIDEBAR, { action: "sync", preferences: loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, state: await deriveState(s.basePath) });
1399
1489
  }
@@ -86,7 +86,7 @@ export async function handleAgentEnd(pi, event, ctx) {
86
86
  logWarning("bootstrap", `checkDeepProjectSetupAfterTurn failed: ${message}`);
87
87
  }
88
88
  if (checkAutoStartAfterDiscuss()) {
89
- clearDiscussionFlowState();
89
+ clearDiscussionFlowState(resolveAgentEndBasePath() ?? process.cwd());
90
90
  return;
91
91
  }
92
92
  // #4573 — When the LLM emits "Milestone X ready." but the required files
@@ -60,7 +60,7 @@ export function registerHooks(pi, ecosystemHandlers) {
60
60
  const { initHealthWidget } = await import("../health-widget.js");
61
61
  initHealthWidget(ctx);
62
62
  }
63
- resetWriteGateState();
63
+ resetWriteGateState(process.cwd());
64
64
  resetToolCallLoopGuard();
65
65
  approvalQuestionAbortInFlight = false;
66
66
  await resetAskUserQuestionsTurnCache();
@@ -109,10 +109,10 @@ export function registerHooks(pi, ecosystemHandlers) {
109
109
  pi.on("session_switch", async (_event, ctx) => {
110
110
  initNotificationStore(process.cwd());
111
111
  installNotifyInterceptor(ctx);
112
- resetWriteGateState();
112
+ resetWriteGateState(process.cwd());
113
113
  resetToolCallLoopGuard();
114
114
  await resetAskUserQuestionsTurnCache();
115
- clearDiscussionFlowState();
115
+ clearDiscussionFlowState(process.cwd());
116
116
  await syncServiceTierStatus(ctx);
117
117
  await applyDisabledModelProviderPolicy(ctx);
118
118
  // Skip MCP auto-prep when running inside an auto-worktree. The worktree
@@ -137,13 +137,14 @@ export function registerHooks(pi, ecosystemHandlers) {
137
137
  // Wait for ecosystem loader to finish (no-op after first turn).
138
138
  const { getEcosystemReadyPromise } = await import("../ecosystem/loader.js");
139
139
  await getEcosystemReadyPromise();
140
+ const beforeAgentBasePath = process.cwd();
140
141
  const pendingApprovalGate = getPendingGate();
141
142
  if (pendingApprovalGate && isExplicitApprovalResponse(event.prompt, pendingApprovalGate)) {
142
- markApprovalGateVerified(pendingApprovalGate);
143
+ markApprovalGateVerified(pendingApprovalGate, beforeAgentBasePath);
143
144
  const milestoneId = extractDepthVerificationMilestoneId(pendingApprovalGate);
144
145
  if (milestoneId)
145
- markDepthVerified(milestoneId);
146
- clearPendingGate();
146
+ markDepthVerified(milestoneId, beforeAgentBasePath);
147
+ clearPendingGate(beforeAgentBasePath);
147
148
  }
148
149
  // GSD's own context injection (existing behavior — unchanged).
149
150
  const { buildBeforeAgentStartResult } = await import("./system-context.js");
@@ -318,7 +319,7 @@ export function registerHooks(pi, ecosystemHandlers) {
318
319
  return;
319
320
  const gateId = approvalGateIdForUnit(unitType, unitId);
320
321
  if (gateId)
321
- setPendingGate(gateId);
322
+ setPendingGate(gateId, process.cwd());
322
323
  approvalQuestionAbortInFlight = true;
323
324
  ctx.ui.notify(`${unitType}${unitId ? ` ${unitId}` : ""} is waiting for your approval - pausing before more tool calls run.`, "info");
324
325
  // The pending gate set above blocks subsequent non-read-only tool calls
@@ -360,7 +361,7 @@ export function registerHooks(pi, ecosystemHandlers) {
360
361
  const questions = event.input?.questions ?? [];
361
362
  const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
362
363
  if (typeof questionId === "string") {
363
- setPendingGate(questionId);
364
+ setPendingGate(questionId, discussionBasePath);
364
365
  }
365
366
  }
366
367
  // ── Discussion gate enforcement: block tool calls while gate is pending ──
@@ -501,7 +502,8 @@ export function registerHooks(pi, ecosystemHandlers) {
501
502
  const toolName = canonicalToolName(event.toolName);
502
503
  if (toolName !== "ask_user_questions")
503
504
  return;
504
- const milestoneId = await getDiscussionMilestoneIdFor(process.cwd());
505
+ const basePath = process.cwd();
506
+ const milestoneId = await getDiscussionMilestoneIdFor(basePath);
505
507
  const queueActive = isQueuePhaseActive();
506
508
  const details = event.details;
507
509
  // ── Discussion gate enforcement: handle gate question responses ──
@@ -513,8 +515,13 @@ export function registerHooks(pi, ecosystemHandlers) {
513
515
  const currentPendingGate = getPendingGate();
514
516
  if (currentPendingGate) {
515
517
  if (details?.cancelled || !details?.response) {
516
- // Gate stays pending. Return a hard instruction as the tool result so
517
- // the model cannot reinterpret a cancelled prompt as prior approval.
518
+ // Gate stays pending. Direct the agent to the most reliable recovery
519
+ // path re-calling ask_user_questions with the same gate id — without
520
+ // misrepresenting the plain-text path. The plain-text path also works
521
+ // (isExplicitApprovalResponse on the next before_agent_start clears
522
+ // the gate when the user replies with an approval keyword), but the
523
+ // structured re-ask is more deterministic and gives the user a clear UI.
524
+ resetToolCallLoopGuard();
518
525
  return {
519
526
  content: [{
520
527
  type: "text",
@@ -522,8 +529,8 @@ export function registerHooks(pi, ecosystemHandlers) {
522
529
  `HARD BLOCK: approval gate "${currentPendingGate}" is still pending.`,
523
530
  "No user response was received for the confirmation question.",
524
531
  "Do not infer approval from earlier or prior messages.",
525
- "Do not proceed, write files, save artifacts, or call more tools.",
526
- "Ask the user to confirm in plain chat, then stop and wait for their next message.",
532
+ "Do not proceed, write files, save artifacts, or call other tools.",
533
+ `Re-call ask_user_questions with the same gate question id ("${currentPendingGate}") and wait for the user's response.`,
527
534
  ].join(" "),
528
535
  }],
529
536
  };
@@ -533,11 +540,11 @@ export function registerHooks(pi, ecosystemHandlers) {
533
540
  if (pendingQuestion) {
534
541
  const answer = details.response?.answers?.[currentPendingGate];
535
542
  if (isDepthConfirmationAnswer(answer?.selected, pendingQuestion.options)) {
536
- markApprovalGateVerified(currentPendingGate);
543
+ markApprovalGateVerified(currentPendingGate, basePath);
537
544
  const milestoneIdFromGate = extractDepthVerificationMilestoneId(currentPendingGate);
538
545
  if (milestoneIdFromGate)
539
- markDepthVerified(milestoneIdFromGate);
540
- clearPendingGate();
546
+ markDepthVerified(milestoneIdFromGate, basePath);
547
+ clearPendingGate(basePath);
541
548
  }
542
549
  }
543
550
  }
@@ -553,9 +560,9 @@ export function registerHooks(pi, ecosystemHandlers) {
553
560
  if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
554
561
  if (currentPendingGate && question.id !== currentPendingGate)
555
562
  break;
556
- markApprovalGateVerified(question.id);
557
- markDepthVerified(inferredMilestoneId);
558
- clearPendingGate();
563
+ markApprovalGateVerified(question.id, basePath);
564
+ markDepthVerified(inferredMilestoneId, basePath);
565
+ clearPendingGate(basePath);
559
566
  }
560
567
  break;
561
568
  }
@@ -564,7 +571,6 @@ export function registerHooks(pi, ecosystemHandlers) {
564
571
  return;
565
572
  if (!milestoneId)
566
573
  return;
567
- const basePath = process.cwd();
568
574
  const milestoneDir = resolveMilestonePath(basePath, milestoneId);
569
575
  if (!milestoneDir)
570
576
  return;