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
@@ -0,0 +1,42 @@
1
+ # Auto-mode coordination is single-host
2
+
3
+ The DB-backed coordination tables introduced by Phase B (`workers`,
4
+ `milestone_leases`, `unit_dispatches`, `cancellation_requests`,
5
+ `command_queue`) and the supporting `runtime_kv` table from Phase C all
6
+ rely on **shared SQLite WAL on local disk**. They do not work across
7
+ machines.
8
+
9
+ ## Why single-host only
10
+
11
+ - SQLite WAL coordination — the locking primitives that make
12
+ `claimMilestoneLease`, `recordDispatchClaim`, and `claimNextCommand`
13
+ atomic — is local-disk only. Network filesystems (NFS, SMB, S3FS) and
14
+ fuse mounts break the lock semantics that the WAL relies on.
15
+ - Heartbeat TTL (`workers.last_heartbeat_at`) compares timestamps written
16
+ with SQLite wall-clock time (`datetime('now')`). Across machines without
17
+ wall-clock synchronization (for example NTP/chrony), TTL filtering can
18
+ produce phantom-active or premature-crashed verdicts. Monotonic clocks
19
+ are not used for these comparisons.
20
+ - Fencing tokens (`milestone_leases.fencing_token`) are monotonically
21
+ ordered by SQL within a single transaction. Cross-host races could
22
+ produce duplicate tokens if two SQLite processes opened the same DB
23
+ on a network mount.
24
+
25
+ ## What does work
26
+
27
+ - Multiple `gsd auto` worker processes on the **same machine**, sharing
28
+ the project's SQLite DB via WAL. The lease check refuses concurrent
29
+ claims on the same milestone; the dispatch ledger's partial unique
30
+ index refuses double-claims of the same unit.
31
+ - A single `gsd auto` worker plus arbitrary read-only consumers
32
+ (dashboards, doctors) on the same machine.
33
+ - Worktree-based parallelism on the same machine, where each worker
34
+ holds a different milestone lease.
35
+
36
+ ## Multi-host alternatives
37
+
38
+ If you need to coordinate `gsd auto` workers across machines, you need
39
+ a real coordinator: Postgres for the ledger + a leader-election service
40
+ (etcd, Consul) for the leases. That's out of scope for these phases.
41
+ The schema and module shapes here would need a non-trivial backend
42
+ swap before they could ride on top of either.
@@ -24,6 +24,7 @@ import { resolveMilestoneIntegrationBranch } from "./git-service.js";
24
24
  import { nativeIsRepo, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch, nativeAddTracked, nativeCommit } from "./native-git-bridge.js";
25
25
  import { loadEffectiveGSDPreferences } from "./preferences.js";
26
26
  import { runEnvironmentChecks } from "./doctor-environment.js";
27
+ import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
27
28
  /** In-memory health history for the current auto-mode session. */
28
29
  let healthHistory = [];
29
30
  /** Count of consecutive units with unresolved errors. */
@@ -159,6 +160,9 @@ export async function preDispatchHealthGate(basePath) {
159
160
  // If a stale lock exists, the crash recovery path should handle it,
160
161
  // not a new dispatch. This prevents double-dispatch after crashes.
161
162
  try {
163
+ if (existsSync(join(gsdRoot(basePath), "gsd.db"))) {
164
+ await ensureDbOpen(basePath);
165
+ }
162
166
  const lock = readCrashLock(basePath);
163
167
  if (lock && !isLockProcessAlive(lock)) {
164
168
  // Auto-clear it since we're about to dispatch anyway
@@ -6,6 +6,8 @@ import { deriveState, isGhostMilestone, isReusableGhostMilestone } from "./state
6
6
  import { saveFile } from "./files.js";
7
7
  import { nativeIsRepo, nativeForEachRef, nativeUpdateRef } from "./native-git-bridge.js";
8
8
  import { readCrashLock, isLockProcessAlive, clearLock } from "./crash-recovery.js";
9
+ import { getActiveAutoWorkers } from "./db/auto-workers.js";
10
+ import { normalizeRealPath } from "./paths.js";
9
11
  import { ensureGitignore, isGsdGitignored } from "./gitignore.js";
10
12
  import { readAllSessionStatuses, isSessionStale, removeSessionStatus } from "./session-status-io.js";
11
13
  import { recoverFailedMigration } from "./migrate-external.js";
@@ -26,6 +28,9 @@ function hasAssessmentVerdict(basePath, mid, sid) {
26
28
  export async function checkRuntimeHealth(basePath, issues, fixesApplied, shouldFix) {
27
29
  const root = gsdRoot(basePath);
28
30
  // ── Stale crash lock ──────────────────────────────────────────────────
31
+ // Phase C pt 2: the lock state lives in the workers + unit_dispatches
32
+ // tables now, not auto.lock. readCrashLock synthesizes a LockData from
33
+ // the DB; isLockProcessAlive is a pure OS PID check.
29
34
  try {
30
35
  const lock = readCrashLock(basePath);
31
36
  if (lock) {
@@ -36,13 +41,13 @@ export async function checkRuntimeHealth(basePath, issues, fixesApplied, shouldF
36
41
  code: "stale_crash_lock",
37
42
  scope: "project",
38
43
  unitId: "project",
39
- message: `Stale auto.lock from PID ${lock.pid} (started ${lock.startedAt}, was executing ${lock.unitType} ${lock.unitId}) — process is no longer running`,
40
- file: ".gsd/auto.lock",
44
+ message: `Stale auto-mode worker (PID ${lock.pid}, started ${lock.startedAt}, was executing ${lock.unitType} ${lock.unitId}) — process is no longer running`,
45
+ file: "<workers table>",
41
46
  fixable: true,
42
47
  });
43
48
  if (shouldFix("stale_crash_lock")) {
44
49
  clearLock(basePath);
45
- fixesApplied.push("cleared stale auto.lock");
50
+ fixesApplied.push("cleared stale auto-mode worker state");
46
51
  }
47
52
  }
48
53
  }
@@ -60,9 +65,20 @@ export async function checkRuntimeHealth(basePath, issues, fixesApplied, shouldF
60
65
  if (existsSync(lockDir)) {
61
66
  const statRes = statSync(lockDir);
62
67
  if (statRes.isDirectory()) {
63
- // Check if any live process actually holds this lock
64
- const lock = readCrashLock(basePath);
65
- const lockHolderAlive = lock ? isLockProcessAlive(lock) : false;
68
+ // Phase C pt 2: "any live process holds the lock?" check now means
69
+ // "is any worker registered with status='active' AND a fresh
70
+ // heartbeat for this project?" readCrashLock returns null for
71
+ // healthy live workers (it surfaces stale ones only), so we must
72
+ // consult getActiveAutoWorkers directly.
73
+ const projectRoot = normalizeRealPath(basePath);
74
+ const activeWorkers = getActiveAutoWorkers().filter((w) => w.project_root_realpath === projectRoot && isLockProcessAlive({
75
+ pid: w.pid,
76
+ startedAt: w.started_at,
77
+ unitType: "starting",
78
+ unitId: "bootstrap",
79
+ unitStartedAt: w.started_at,
80
+ }));
81
+ const lockHolderAlive = activeWorkers.length > 0;
66
82
  if (!lockHolderAlive) {
67
83
  issues.push({
68
84
  severity: "error",
@@ -2,8 +2,8 @@ import { existsSync, mkdirSync, lstatSync, readdirSync, readFileSync } from "nod
2
2
  import { join } from "node:path";
3
3
  import { loadFile, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
4
4
  import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
5
- import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
6
- import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile, relMilestonePath } from "./paths.js";
5
+ import { isDbAvailable, openDatabase, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
6
+ import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile, relMilestonePath, resolveGsdPathContract } from "./paths.js";
7
7
  import { deriveState, isMilestoneComplete } from "./state.js";
8
8
  import { invalidateAllCaches } from "./cache.js";
9
9
  import { loadEffectiveGSDPreferences } from "./preferences.js";
@@ -309,6 +309,16 @@ export async function runGSDDoctor(basePath, options) {
309
309
  const fix = options?.fix === true;
310
310
  const dryRun = options?.dryRun === true;
311
311
  const fixLevel = options?.fixLevel ?? "all";
312
+ // CLI doctor can run before any tool handler has opened the DB. Runtime
313
+ // health checks need the existing project DB to surface DB-backed crash
314
+ // locks, paused sessions, and coordination rows.
315
+ const dbPath = resolveGsdPathContract(basePath).projectDb;
316
+ if (existsSync(dbPath)) {
317
+ try {
318
+ openDatabase(dbPath);
319
+ }
320
+ catch { /* surfaced later as db_unavailable */ }
321
+ }
312
322
  // Issue codes that represent completion state transitions — creating summary
313
323
  // stubs, marking slices/milestones done in the roadmap. These belong to the
314
324
  // dispatch lifecycle (complete-slice, complete-milestone units), not to
@@ -136,10 +136,137 @@ function openRawDb(path) {
136
136
  const Database = providerModule;
137
137
  return new Database(path);
138
138
  }
139
- export const SCHEMA_VERSION = 23;
139
+ export const SCHEMA_VERSION = 25;
140
140
  function indexExists(db, name) {
141
141
  return !!db.prepare("SELECT 1 as present FROM sqlite_master WHERE type = 'index' AND name = ?").get(name);
142
142
  }
143
+ /**
144
+ * Create the v24 coordination tables (workers, milestone_leases,
145
+ * unit_dispatches, cancellation_requests, command_queue) and their indexes.
146
+ *
147
+ * Idempotent — uses IF NOT EXISTS throughout. Called from both the
148
+ * fresh-install path and the v24 migration block in migrateSchema().
149
+ *
150
+ * Single-host invariant: these tables coordinate concurrent auto-mode
151
+ * workers via shared SQLite WAL on local disk only. NFS / network
152
+ * filesystems break the coordination semantics — multi-host execution
153
+ * needs a real coordinator (etcd, Postgres) and is out of scope.
154
+ */
155
+ function createCoordinationTablesV24(db) {
156
+ const ddl = [
157
+ `CREATE TABLE IF NOT EXISTS workers (
158
+ worker_id TEXT PRIMARY KEY,
159
+ host TEXT NOT NULL,
160
+ pid INTEGER NOT NULL,
161
+ started_at TEXT NOT NULL,
162
+ version TEXT NOT NULL,
163
+ last_heartbeat_at TEXT NOT NULL,
164
+ status TEXT NOT NULL,
165
+ project_root_realpath TEXT NOT NULL
166
+ )`,
167
+ `CREATE TABLE IF NOT EXISTS milestone_leases (
168
+ milestone_id TEXT PRIMARY KEY,
169
+ worker_id TEXT NOT NULL,
170
+ fencing_token INTEGER NOT NULL,
171
+ acquired_at TEXT NOT NULL,
172
+ expires_at TEXT NOT NULL,
173
+ status TEXT NOT NULL,
174
+ FOREIGN KEY (worker_id) REFERENCES workers(worker_id),
175
+ FOREIGN KEY (milestone_id) REFERENCES milestones(id)
176
+ )`,
177
+ `CREATE TABLE IF NOT EXISTS unit_dispatches (
178
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
179
+ trace_id TEXT NOT NULL,
180
+ turn_id TEXT,
181
+ worker_id TEXT NOT NULL,
182
+ milestone_lease_token INTEGER NOT NULL,
183
+ milestone_id TEXT NOT NULL,
184
+ slice_id TEXT,
185
+ task_id TEXT,
186
+ unit_type TEXT NOT NULL,
187
+ unit_id TEXT NOT NULL,
188
+ status TEXT NOT NULL,
189
+ attempt_n INTEGER NOT NULL DEFAULT 1,
190
+ started_at TEXT NOT NULL,
191
+ ended_at TEXT,
192
+ exit_reason TEXT,
193
+ error_summary TEXT,
194
+ verification_evidence_id INTEGER,
195
+ next_run_at TEXT,
196
+ retry_after_ms INTEGER,
197
+ max_attempts INTEGER NOT NULL DEFAULT 3,
198
+ last_error_code TEXT,
199
+ last_error_at TEXT,
200
+ FOREIGN KEY (worker_id) REFERENCES workers(worker_id),
201
+ FOREIGN KEY (verification_evidence_id) REFERENCES verification_evidence(id)
202
+ )`,
203
+ `CREATE TABLE IF NOT EXISTS cancellation_requests (
204
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
205
+ requested_at TEXT NOT NULL,
206
+ requested_by TEXT NOT NULL,
207
+ scope TEXT NOT NULL,
208
+ scope_id TEXT NOT NULL,
209
+ dispatch_id INTEGER,
210
+ reason TEXT NOT NULL,
211
+ status TEXT NOT NULL,
212
+ acked_at TEXT,
213
+ acked_worker_id TEXT,
214
+ FOREIGN KEY (dispatch_id) REFERENCES unit_dispatches(id),
215
+ FOREIGN KEY (acked_worker_id) REFERENCES workers(worker_id)
216
+ )`,
217
+ `CREATE TABLE IF NOT EXISTS command_queue (
218
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
219
+ target_worker TEXT,
220
+ command TEXT NOT NULL,
221
+ args_json TEXT NOT NULL DEFAULT '{}',
222
+ enqueued_at TEXT NOT NULL,
223
+ claimed_at TEXT,
224
+ claimed_by TEXT,
225
+ completed_at TEXT,
226
+ result_json TEXT
227
+ )`,
228
+ ];
229
+ for (const stmt of ddl)
230
+ db.exec(stmt);
231
+ // Indexes — created here so both fresh-install and v24-migration paths
232
+ // produce identical structure.
233
+ db.exec("CREATE INDEX IF NOT EXISTS idx_unit_dispatches_active ON unit_dispatches(milestone_id, status)");
234
+ db.exec("CREATE INDEX IF NOT EXISTS idx_unit_dispatches_trace ON unit_dispatches(trace_id, turn_id)");
235
+ // Partial unique index — prevents two workers from claiming the same
236
+ // unit concurrently. Codex review MEDIUM B2: enforces double-claim guard
237
+ // at the DB level.
238
+ db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_unit_dispatches_active_per_unit "
239
+ + "ON unit_dispatches(unit_id) WHERE status IN ('claimed','running')");
240
+ // command_queue index — SQLite indexes NULLs in B-trees, so this single
241
+ // index serves both targeted (target_worker = ?) and broadcast
242
+ // (target_worker IS NULL) queries. Codex review LOW B4 documented.
243
+ db.exec("CREATE INDEX IF NOT EXISTS idx_command_queue_pending ON command_queue(target_worker, claimed_at)");
244
+ }
245
+ /**
246
+ * Create the v25 runtime_kv table. Idempotent — uses IF NOT EXISTS.
247
+ *
248
+ * STRICT INVARIANT: runtime_kv is NON-CORRECTNESS-CRITICAL. UI cursors,
249
+ * dashboard caches, last-seen-version markers, resume cursors, and other
250
+ * "soft" state are OK. Anything that drives auto-mode control flow gets
251
+ * typed columns in unit_dispatches / workers / milestone_leases — never
252
+ * a bag of JSON in runtime_kv.
253
+ *
254
+ * Scope partitioning: ('global', '', key) for project-wide values;
255
+ * ('worker', worker_id, key) for per-worker state (resume cursors);
256
+ * ('milestone', milestone_id, key) for per-milestone soft state.
257
+ */
258
+ function createRuntimeKvTableV25(db) {
259
+ db.exec(`
260
+ CREATE TABLE IF NOT EXISTS runtime_kv (
261
+ scope TEXT NOT NULL,
262
+ scope_id TEXT NOT NULL DEFAULT '',
263
+ key TEXT NOT NULL,
264
+ value_json TEXT NOT NULL,
265
+ updated_at TEXT NOT NULL,
266
+ PRIMARY KEY (scope, scope_id, key)
267
+ )
268
+ `);
269
+ }
143
270
  function dedupeVerificationEvidenceRows(db) {
144
271
  db.exec(`
145
272
  DELETE FROM verification_evidence
@@ -513,6 +640,8 @@ function initSchema(db, fileBacked) {
513
640
  db.exec(`CREATE VIEW IF NOT EXISTS active_memories AS SELECT * FROM memories WHERE superseded_by IS NULL`);
514
641
  const existing = db.prepare("SELECT count(*) as cnt FROM schema_version").get();
515
642
  if (existing && existing["cnt"] === 0) {
643
+ createCoordinationTablesV24(db);
644
+ createRuntimeKvTableV25(db);
516
645
  // Fresh install — all tables are created above with the full current schema,
517
646
  // so it is safe to create all migration-specific indexes here. For existing
518
647
  // databases these indexes are created inside the individual migration guards
@@ -1142,6 +1271,26 @@ function migrateSchema(db) {
1142
1271
  ":applied_at": new Date().toISOString(),
1143
1272
  });
1144
1273
  }
1274
+ if (currentVersion < 24) {
1275
+ // v24: auto-mode coordination tables. See createCoordinationTablesV24
1276
+ // for full schema + invariants. No-op for fresh installs (the same
1277
+ // helper runs in the fresh-install path); for upgraded DBs this is
1278
+ // the only place these tables get created.
1279
+ createCoordinationTablesV24(db);
1280
+ db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1281
+ ":version": 24,
1282
+ ":applied_at": new Date().toISOString(),
1283
+ });
1284
+ }
1285
+ if (currentVersion < 25) {
1286
+ // v25: runtime_kv non-correctness-critical key-value storage. See
1287
+ // createRuntimeKvTableV25 for the full schema + invariants.
1288
+ createRuntimeKvTableV25(db);
1289
+ db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1290
+ ":version": 25,
1291
+ ":applied_at": new Date().toISOString(),
1292
+ });
1293
+ }
1145
1294
  db.exec("COMMIT");
1146
1295
  }
1147
1296
  catch (err) {
@@ -1156,6 +1305,193 @@ let _exitHandlerRegistered = false;
1156
1305
  let _dbOpenAttempted = false;
1157
1306
  let _lastDbError = null;
1158
1307
  let _lastDbPhase = null;
1308
+ /**
1309
+ * Identity key of the workspace whose connection is currently active
1310
+ * (currentDb). Set by openDatabaseByWorkspace(); null when the active
1311
+ * connection was opened via the legacy openDatabase(path) path.
1312
+ */
1313
+ let _currentIdentityKey = null;
1314
+ /**
1315
+ * Workspace-scoped connection cache.
1316
+ * Key: GsdWorkspace.identityKey (realpath-normalized project root).
1317
+ * Value: the DB path and open adapter for that workspace.
1318
+ *
1319
+ * Sibling worktrees of the same project share the same identityKey (set by
1320
+ * createWorkspace) and therefore reuse the same cached connection, preserving
1321
+ * shared-WAL semantics. Different projects get distinct cache entries.
1322
+ *
1323
+ * NOTE: Only one connection is "active" at a time (currentDb/currentPath).
1324
+ * The cache allows fast re-activation of a previously opened connection when
1325
+ * callers switch between known workspaces via openDatabaseByWorkspace().
1326
+ */
1327
+ const _dbCache = new Map();
1328
+ /** Test helper: expose the internal cache for inspection. Not for production use. */
1329
+ export function _getDbCache() {
1330
+ return _dbCache;
1331
+ }
1332
+ /**
1333
+ * Close and evict every entry in the workspace connection cache, then call
1334
+ * closeDatabase() to close the active connection.
1335
+ *
1336
+ * Use this for test teardown or process-shutdown paths where every open
1337
+ * connection must be flushed. Normal callers should use closeDatabase() or
1338
+ * closeDatabaseByWorkspace() instead.
1339
+ */
1340
+ export function closeAllDatabases() {
1341
+ // Close all non-active cached connections first.
1342
+ for (const [key, entry] of _dbCache) {
1343
+ if (entry.db === currentDb)
1344
+ continue; // handled by closeDatabase() below
1345
+ _dbCache.delete(key);
1346
+ try {
1347
+ entry.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
1348
+ }
1349
+ catch { /* best-effort */ }
1350
+ try {
1351
+ entry.db.exec("PRAGMA incremental_vacuum(64)");
1352
+ }
1353
+ catch { /* best-effort */ }
1354
+ try {
1355
+ entry.db.close();
1356
+ }
1357
+ catch { /* best-effort */ }
1358
+ }
1359
+ closeDatabase();
1360
+ }
1361
+ /**
1362
+ * Open (or reuse) the database connection scoped to the given workspace.
1363
+ *
1364
+ * Uses workspace.identityKey as the cache key, so sibling worktrees of the
1365
+ * same project resolve to the same connection. On a cache hit the existing
1366
+ * adapter is reactivated as the current connection without re-opening the
1367
+ * file. On a cache miss, delegates to openDatabase() for the full
1368
+ * open + schema-init + migration flow, then caches the result.
1369
+ *
1370
+ * When switching to a different workspace, the previously active connection
1371
+ * is preserved in the cache (not closed), so callers can switch back to it
1372
+ * cheaply via a subsequent openDatabaseByWorkspace() call.
1373
+ *
1374
+ * @param workspace A GsdWorkspace created by createWorkspace().
1375
+ * @returns true if the connection is open and ready, false otherwise.
1376
+ */
1377
+ export function openDatabaseByWorkspace(workspace) {
1378
+ const key = workspace.identityKey;
1379
+ const dbPath = workspace.contract.projectDb;
1380
+ const cached = _dbCache.get(key);
1381
+ if (cached) {
1382
+ // Reactivate the cached connection as the current singleton.
1383
+ currentDb = cached.db;
1384
+ currentPath = cached.dbPath;
1385
+ currentPid = process.pid;
1386
+ _dbOpenAttempted = true;
1387
+ _currentIdentityKey = key;
1388
+ return true;
1389
+ }
1390
+ // Cache miss — need to open a new connection.
1391
+ //
1392
+ // If there is a currently active workspace connection, stash it in the
1393
+ // cache under its identity key before calling openDatabase(), because
1394
+ // openDatabase() will call closeDatabase() when the path changes (which
1395
+ // would destroy the existing adapter). By nulling out currentDb first,
1396
+ // we prevent openDatabase() from closing the live adapter.
1397
+ let oldDb = null;
1398
+ let oldPath = null;
1399
+ let oldPid = 0;
1400
+ let oldKey = null;
1401
+ if (currentDb !== null && _currentIdentityKey !== null) {
1402
+ // Snapshot the old globals so we can restore them on failure.
1403
+ oldDb = currentDb;
1404
+ oldPath = currentPath;
1405
+ oldPid = currentPid;
1406
+ oldKey = _currentIdentityKey;
1407
+ // Save the current connection so it stays alive in the cache.
1408
+ _dbCache.set(_currentIdentityKey, {
1409
+ dbPath: currentPath,
1410
+ db: currentDb,
1411
+ });
1412
+ // Detach from globals so openDatabase() opens fresh without closing it.
1413
+ currentDb = null;
1414
+ currentPath = null;
1415
+ currentPid = 0;
1416
+ _currentIdentityKey = null;
1417
+ }
1418
+ // Run the full open/schema/migration flow for the new workspace.
1419
+ // openDatabase() can throw on corrupt DB or permission error — catch so we
1420
+ // can restore the previous connection rather than leaving globals null.
1421
+ let opened;
1422
+ try {
1423
+ opened = openDatabase(dbPath);
1424
+ }
1425
+ catch (err) {
1426
+ // Failed to open the new DB. Restore the previous workspace connection so
1427
+ // the caller's workspace remains active (it is still safe in _dbCache).
1428
+ if (oldDb !== null) {
1429
+ currentDb = oldDb;
1430
+ currentPath = oldPath;
1431
+ currentPid = oldPid;
1432
+ _currentIdentityKey = oldKey;
1433
+ }
1434
+ throw err;
1435
+ }
1436
+ if (opened && currentDb) {
1437
+ _dbCache.set(key, { dbPath, db: currentDb });
1438
+ _currentIdentityKey = key;
1439
+ }
1440
+ else if (!opened && oldDb !== null) {
1441
+ // Restore the previous connection so the caller's workspace remains active.
1442
+ // The failed attempt left no live adapter, so the globals stayed null.
1443
+ currentDb = oldDb;
1444
+ currentPath = oldPath;
1445
+ currentPid = oldPid;
1446
+ _currentIdentityKey = oldKey;
1447
+ }
1448
+ return opened;
1449
+ }
1450
+ /**
1451
+ * Open (or reuse) the database connection scoped to the workspace in a
1452
+ * MilestoneScope. Thin delegation to openDatabaseByWorkspace().
1453
+ */
1454
+ export function openDatabaseByScope(scope) {
1455
+ return openDatabaseByWorkspace(scope.workspace);
1456
+ }
1457
+ /**
1458
+ * Close the database connection for the given workspace and remove it from
1459
+ * the cache. If the workspace's connection is currently active (currentDb),
1460
+ * performs a full closeDatabase() including WAL checkpoint. Otherwise only
1461
+ * removes the cache entry (the adapter was already replaced by a later open).
1462
+ */
1463
+ export function closeDatabaseByWorkspace(workspace) {
1464
+ const key = workspace.identityKey;
1465
+ const cached = _dbCache.get(key);
1466
+ if (!cached)
1467
+ return;
1468
+ _dbCache.delete(key);
1469
+ if (currentDb === cached.db) {
1470
+ // This workspace's connection is the active one — full close.
1471
+ closeDatabase();
1472
+ }
1473
+ else {
1474
+ // Connection was displaced by a later open; close the adapter directly.
1475
+ try {
1476
+ cached.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
1477
+ }
1478
+ catch (e) {
1479
+ logWarning("db", `WAL checkpoint (byWorkspace) failed: ${e.message}`);
1480
+ }
1481
+ try {
1482
+ cached.db.exec("PRAGMA incremental_vacuum(64)");
1483
+ }
1484
+ catch (e) {
1485
+ logWarning("db", `incremental vacuum (byWorkspace) failed: ${e.message}`);
1486
+ }
1487
+ try {
1488
+ cached.db.close();
1489
+ }
1490
+ catch (e) {
1491
+ logWarning("db", `database close (byWorkspace) failed: ${e.message}`);
1492
+ }
1493
+ }
1494
+ }
1159
1495
  export function getDbProvider() {
1160
1496
  loadProvider();
1161
1497
  return providerName;
@@ -1300,6 +1636,13 @@ export function closeDatabase() {
1300
1636
  catch (e) {
1301
1637
  logWarning("db", `database close failed: ${e.message}`);
1302
1638
  }
1639
+ // If this connection was workspace-tracked, evict it from the cache so
1640
+ // subsequent openDatabaseByWorkspace() calls re-open rather than reactivate
1641
+ // a closed adapter.
1642
+ if (_currentIdentityKey !== null) {
1643
+ _dbCache.delete(_currentIdentityKey);
1644
+ _currentIdentityKey = null;
1645
+ }
1303
1646
  currentDb = null;
1304
1647
  currentPath = null;
1305
1648
  currentPid = 0;
@@ -2682,6 +3025,7 @@ export function deleteMilestone(milestoneId) {
2682
3025
  currentDb.prepare(`DELETE FROM replan_history WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
2683
3026
  currentDb.prepare(`DELETE FROM assessments WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
2684
3027
  currentDb.prepare(`DELETE FROM artifacts WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
3028
+ currentDb.prepare(`DELETE FROM milestone_leases WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
2685
3029
  currentDb.prepare(`DELETE FROM milestones WHERE id = :mid`).run({ ":mid": milestoneId });
2686
3030
  });
2687
3031
  }
@@ -2996,15 +3340,21 @@ export function deleteArtifactByPath(path) {
2996
3340
  currentDb.prepare("DELETE FROM artifacts WHERE path = :path").run({ ":path": path });
2997
3341
  }
2998
3342
  /**
2999
- * Drop all rows from tasks/slices/milestones in dependency order inside a
3000
- * transaction. Used by `gsd recover` to rebuild engine state from markdown.
3343
+ * Drop hierarchy rows in dependency order inside a transaction. Used by
3344
+ * `gsd recover` to rebuild engine state from markdown.
3001
3345
  */
3002
3346
  export function clearEngineHierarchy() {
3003
3347
  if (!currentDb)
3004
3348
  throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
3005
3349
  transaction(() => {
3350
+ currentDb.exec("DELETE FROM verification_evidence");
3351
+ currentDb.exec("DELETE FROM quality_gates");
3352
+ currentDb.exec("DELETE FROM slice_dependencies");
3353
+ currentDb.exec("DELETE FROM assessments");
3354
+ currentDb.exec("DELETE FROM replan_history");
3006
3355
  currentDb.exec("DELETE FROM tasks");
3007
3356
  currentDb.exec("DELETE FROM slices");
3357
+ currentDb.exec("DELETE FROM milestone_leases");
3008
3358
  currentDb.exec("DELETE FROM milestones");
3009
3359
  });
3010
3360
  }
@@ -3087,6 +3437,7 @@ export function restoreManifest(manifest) {
3087
3437
  db.exec("DELETE FROM verification_evidence");
3088
3438
  db.exec("DELETE FROM tasks");
3089
3439
  db.exec("DELETE FROM slices");
3440
+ db.exec("DELETE FROM milestone_leases");
3090
3441
  db.exec("DELETE FROM milestones");
3091
3442
  db.exec("DELETE FROM decisions WHERE 1=1");
3092
3443
  // Restore milestones
@@ -3149,6 +3500,7 @@ export function bulkInsertLegacyHierarchy(payload) {
3149
3500
  transaction(() => {
3150
3501
  db.prepare(`DELETE FROM tasks WHERE milestone_id IN (${placeholders})`).run(...clearMilestoneIds);
3151
3502
  db.prepare(`DELETE FROM slices WHERE milestone_id IN (${placeholders})`).run(...clearMilestoneIds);
3503
+ db.prepare(`DELETE FROM milestone_leases WHERE milestone_id IN (${placeholders})`).run(...clearMilestoneIds);
3152
3504
  db.prepare(`DELETE FROM milestones WHERE id IN (${placeholders})`).run(...clearMilestoneIds);
3153
3505
  const insertMilestone = db.prepare("INSERT INTO milestones (id, title, status, created_at) VALUES (?, ?, ?, ?)");
3154
3506
  for (const m of milestones) {
@@ -150,7 +150,7 @@ export async function showQueueAdd(ctx, pi, basePath, state) {
150
150
  ].join(" ");
151
151
  // ── Dispatch the queue prompt ───────────────────────────────────────
152
152
  // Activate the queue phase so the write-gate applies to CONTEXT.md writes
153
- setQueuePhaseActive(true);
153
+ setQueuePhaseActive(true, basePath);
154
154
  const queueInlinedTemplates = inlineTemplate("context", "Context");
155
155
  const prompt = loadPrompt("queue", {
156
156
  preamble,