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
@@ -25,6 +25,7 @@ import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
25
25
  import { dirname } from "node:path";
26
26
  import type { Decision, Requirement, GateRow, GateId, GateScope, GateStatus, GateVerdict } from "./types.js";
27
27
  import { GSDError, GSD_STALE_STATE } from "./errors.js";
28
+ import type { GsdWorkspace, MilestoneScope } from "./workspace.js";
28
29
  import { getGateIdsForTurn, type OwnerTurn } from "./gate-registry.js";
29
30
  import { logError, logWarning } from "./workflow-logger.js";
30
31
  // Type-only import to avoid a circular runtime dep. The runtime side of
@@ -181,7 +182,7 @@ function openRawDb(path: string): unknown {
181
182
  return new Database(path);
182
183
  }
183
184
 
184
- export const SCHEMA_VERSION = 23;
185
+ export const SCHEMA_VERSION = 25;
185
186
 
186
187
  function indexExists(db: DbAdapter, name: string): boolean {
187
188
  return !!db.prepare(
@@ -189,6 +190,137 @@ function indexExists(db: DbAdapter, name: string): boolean {
189
190
  ).get(name);
190
191
  }
191
192
 
193
+ /**
194
+ * Create the v24 coordination tables (workers, milestone_leases,
195
+ * unit_dispatches, cancellation_requests, command_queue) and their indexes.
196
+ *
197
+ * Idempotent — uses IF NOT EXISTS throughout. Called from both the
198
+ * fresh-install path and the v24 migration block in migrateSchema().
199
+ *
200
+ * Single-host invariant: these tables coordinate concurrent auto-mode
201
+ * workers via shared SQLite WAL on local disk only. NFS / network
202
+ * filesystems break the coordination semantics — multi-host execution
203
+ * needs a real coordinator (etcd, Postgres) and is out of scope.
204
+ */
205
+ function createCoordinationTablesV24(db: DbAdapter): void {
206
+ const ddl = [
207
+ `CREATE TABLE IF NOT EXISTS workers (
208
+ worker_id TEXT PRIMARY KEY,
209
+ host TEXT NOT NULL,
210
+ pid INTEGER NOT NULL,
211
+ started_at TEXT NOT NULL,
212
+ version TEXT NOT NULL,
213
+ last_heartbeat_at TEXT NOT NULL,
214
+ status TEXT NOT NULL,
215
+ project_root_realpath TEXT NOT NULL
216
+ )`,
217
+ `CREATE TABLE IF NOT EXISTS milestone_leases (
218
+ milestone_id TEXT PRIMARY KEY,
219
+ worker_id TEXT NOT NULL,
220
+ fencing_token INTEGER NOT NULL,
221
+ acquired_at TEXT NOT NULL,
222
+ expires_at TEXT NOT NULL,
223
+ status TEXT NOT NULL,
224
+ FOREIGN KEY (worker_id) REFERENCES workers(worker_id),
225
+ FOREIGN KEY (milestone_id) REFERENCES milestones(id)
226
+ )`,
227
+ `CREATE TABLE IF NOT EXISTS unit_dispatches (
228
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
229
+ trace_id TEXT NOT NULL,
230
+ turn_id TEXT,
231
+ worker_id TEXT NOT NULL,
232
+ milestone_lease_token INTEGER NOT NULL,
233
+ milestone_id TEXT NOT NULL,
234
+ slice_id TEXT,
235
+ task_id TEXT,
236
+ unit_type TEXT NOT NULL,
237
+ unit_id TEXT NOT NULL,
238
+ status TEXT NOT NULL,
239
+ attempt_n INTEGER NOT NULL DEFAULT 1,
240
+ started_at TEXT NOT NULL,
241
+ ended_at TEXT,
242
+ exit_reason TEXT,
243
+ error_summary TEXT,
244
+ verification_evidence_id INTEGER,
245
+ next_run_at TEXT,
246
+ retry_after_ms INTEGER,
247
+ max_attempts INTEGER NOT NULL DEFAULT 3,
248
+ last_error_code TEXT,
249
+ last_error_at TEXT,
250
+ FOREIGN KEY (worker_id) REFERENCES workers(worker_id),
251
+ FOREIGN KEY (verification_evidence_id) REFERENCES verification_evidence(id)
252
+ )`,
253
+ `CREATE TABLE IF NOT EXISTS cancellation_requests (
254
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
255
+ requested_at TEXT NOT NULL,
256
+ requested_by TEXT NOT NULL,
257
+ scope TEXT NOT NULL,
258
+ scope_id TEXT NOT NULL,
259
+ dispatch_id INTEGER,
260
+ reason TEXT NOT NULL,
261
+ status TEXT NOT NULL,
262
+ acked_at TEXT,
263
+ acked_worker_id TEXT,
264
+ FOREIGN KEY (dispatch_id) REFERENCES unit_dispatches(id),
265
+ FOREIGN KEY (acked_worker_id) REFERENCES workers(worker_id)
266
+ )`,
267
+ `CREATE TABLE IF NOT EXISTS command_queue (
268
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
269
+ target_worker TEXT,
270
+ command TEXT NOT NULL,
271
+ args_json TEXT NOT NULL DEFAULT '{}',
272
+ enqueued_at TEXT NOT NULL,
273
+ claimed_at TEXT,
274
+ claimed_by TEXT,
275
+ completed_at TEXT,
276
+ result_json TEXT
277
+ )`,
278
+ ];
279
+ for (const stmt of ddl) db.exec(stmt);
280
+
281
+ // Indexes — created here so both fresh-install and v24-migration paths
282
+ // produce identical structure.
283
+ db.exec("CREATE INDEX IF NOT EXISTS idx_unit_dispatches_active ON unit_dispatches(milestone_id, status)");
284
+ db.exec("CREATE INDEX IF NOT EXISTS idx_unit_dispatches_trace ON unit_dispatches(trace_id, turn_id)");
285
+ // Partial unique index — prevents two workers from claiming the same
286
+ // unit concurrently. Codex review MEDIUM B2: enforces double-claim guard
287
+ // at the DB level.
288
+ db.exec(
289
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_unit_dispatches_active_per_unit "
290
+ + "ON unit_dispatches(unit_id) WHERE status IN ('claimed','running')",
291
+ );
292
+ // command_queue index — SQLite indexes NULLs in B-trees, so this single
293
+ // index serves both targeted (target_worker = ?) and broadcast
294
+ // (target_worker IS NULL) queries. Codex review LOW B4 documented.
295
+ db.exec("CREATE INDEX IF NOT EXISTS idx_command_queue_pending ON command_queue(target_worker, claimed_at)");
296
+ }
297
+
298
+ /**
299
+ * Create the v25 runtime_kv table. Idempotent — uses IF NOT EXISTS.
300
+ *
301
+ * STRICT INVARIANT: runtime_kv is NON-CORRECTNESS-CRITICAL. UI cursors,
302
+ * dashboard caches, last-seen-version markers, resume cursors, and other
303
+ * "soft" state are OK. Anything that drives auto-mode control flow gets
304
+ * typed columns in unit_dispatches / workers / milestone_leases — never
305
+ * a bag of JSON in runtime_kv.
306
+ *
307
+ * Scope partitioning: ('global', '', key) for project-wide values;
308
+ * ('worker', worker_id, key) for per-worker state (resume cursors);
309
+ * ('milestone', milestone_id, key) for per-milestone soft state.
310
+ */
311
+ function createRuntimeKvTableV25(db: DbAdapter): void {
312
+ db.exec(`
313
+ CREATE TABLE IF NOT EXISTS runtime_kv (
314
+ scope TEXT NOT NULL,
315
+ scope_id TEXT NOT NULL DEFAULT '',
316
+ key TEXT NOT NULL,
317
+ value_json TEXT NOT NULL,
318
+ updated_at TEXT NOT NULL,
319
+ PRIMARY KEY (scope, scope_id, key)
320
+ )
321
+ `);
322
+ }
323
+
192
324
  function dedupeVerificationEvidenceRows(db: DbAdapter): void {
193
325
  db.exec(`
194
326
  DELETE FROM verification_evidence
@@ -585,6 +717,9 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
585
717
 
586
718
  const existing = db.prepare("SELECT count(*) as cnt FROM schema_version").get();
587
719
  if (existing && (existing["cnt"] as number) === 0) {
720
+ createCoordinationTablesV24(db);
721
+ createRuntimeKvTableV25(db);
722
+
588
723
  // Fresh install — all tables are created above with the full current schema,
589
724
  // so it is safe to create all migration-specific indexes here. For existing
590
725
  // databases these indexes are created inside the individual migration guards
@@ -1245,6 +1380,28 @@ function migrateSchema(db: DbAdapter): void {
1245
1380
  });
1246
1381
  }
1247
1382
 
1383
+ if (currentVersion < 24) {
1384
+ // v24: auto-mode coordination tables. See createCoordinationTablesV24
1385
+ // for full schema + invariants. No-op for fresh installs (the same
1386
+ // helper runs in the fresh-install path); for upgraded DBs this is
1387
+ // the only place these tables get created.
1388
+ createCoordinationTablesV24(db);
1389
+ db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1390
+ ":version": 24,
1391
+ ":applied_at": new Date().toISOString(),
1392
+ });
1393
+ }
1394
+
1395
+ if (currentVersion < 25) {
1396
+ // v25: runtime_kv non-correctness-critical key-value storage. See
1397
+ // createRuntimeKvTableV25 for the full schema + invariants.
1398
+ createRuntimeKvTableV25(db);
1399
+ db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
1400
+ ":version": 25,
1401
+ ":applied_at": new Date().toISOString(),
1402
+ });
1403
+ }
1404
+
1248
1405
  db.exec("COMMIT");
1249
1406
  } catch (err) {
1250
1407
  db.exec("ROLLBACK");
@@ -1259,6 +1416,182 @@ let _exitHandlerRegistered = false;
1259
1416
  let _dbOpenAttempted = false;
1260
1417
  let _lastDbError: Error | null = null;
1261
1418
  let _lastDbPhase: "open" | "initSchema" | "vacuum-recovery" | null = null;
1419
+ /**
1420
+ * Identity key of the workspace whose connection is currently active
1421
+ * (currentDb). Set by openDatabaseByWorkspace(); null when the active
1422
+ * connection was opened via the legacy openDatabase(path) path.
1423
+ */
1424
+ let _currentIdentityKey: string | null = null;
1425
+
1426
+ /**
1427
+ * Workspace-scoped connection cache.
1428
+ * Key: GsdWorkspace.identityKey (realpath-normalized project root).
1429
+ * Value: the DB path and open adapter for that workspace.
1430
+ *
1431
+ * Sibling worktrees of the same project share the same identityKey (set by
1432
+ * createWorkspace) and therefore reuse the same cached connection, preserving
1433
+ * shared-WAL semantics. Different projects get distinct cache entries.
1434
+ *
1435
+ * NOTE: Only one connection is "active" at a time (currentDb/currentPath).
1436
+ * The cache allows fast re-activation of a previously opened connection when
1437
+ * callers switch between known workspaces via openDatabaseByWorkspace().
1438
+ */
1439
+ const _dbCache = new Map<string, { dbPath: string; db: DbAdapter }>();
1440
+
1441
+ /** Test helper: expose the internal cache for inspection. Not for production use. */
1442
+ export function _getDbCache(): ReadonlyMap<string, { dbPath: string; db: DbAdapter }> {
1443
+ return _dbCache;
1444
+ }
1445
+
1446
+ /**
1447
+ * Close and evict every entry in the workspace connection cache, then call
1448
+ * closeDatabase() to close the active connection.
1449
+ *
1450
+ * Use this for test teardown or process-shutdown paths where every open
1451
+ * connection must be flushed. Normal callers should use closeDatabase() or
1452
+ * closeDatabaseByWorkspace() instead.
1453
+ */
1454
+ export function closeAllDatabases(): void {
1455
+ // Close all non-active cached connections first.
1456
+ for (const [key, entry] of _dbCache) {
1457
+ if (entry.db === currentDb) continue; // handled by closeDatabase() below
1458
+ _dbCache.delete(key);
1459
+ try { entry.db.exec("PRAGMA wal_checkpoint(TRUNCATE)"); } catch { /* best-effort */ }
1460
+ try { entry.db.exec("PRAGMA incremental_vacuum(64)"); } catch { /* best-effort */ }
1461
+ try { entry.db.close(); } catch { /* best-effort */ }
1462
+ }
1463
+ closeDatabase();
1464
+ }
1465
+
1466
+ /**
1467
+ * Open (or reuse) the database connection scoped to the given workspace.
1468
+ *
1469
+ * Uses workspace.identityKey as the cache key, so sibling worktrees of the
1470
+ * same project resolve to the same connection. On a cache hit the existing
1471
+ * adapter is reactivated as the current connection without re-opening the
1472
+ * file. On a cache miss, delegates to openDatabase() for the full
1473
+ * open + schema-init + migration flow, then caches the result.
1474
+ *
1475
+ * When switching to a different workspace, the previously active connection
1476
+ * is preserved in the cache (not closed), so callers can switch back to it
1477
+ * cheaply via a subsequent openDatabaseByWorkspace() call.
1478
+ *
1479
+ * @param workspace A GsdWorkspace created by createWorkspace().
1480
+ * @returns true if the connection is open and ready, false otherwise.
1481
+ */
1482
+ export function openDatabaseByWorkspace(workspace: GsdWorkspace): boolean {
1483
+ const key = workspace.identityKey;
1484
+ const dbPath = workspace.contract.projectDb;
1485
+
1486
+ const cached = _dbCache.get(key);
1487
+ if (cached) {
1488
+ // Reactivate the cached connection as the current singleton.
1489
+ currentDb = cached.db;
1490
+ currentPath = cached.dbPath;
1491
+ currentPid = process.pid;
1492
+ _dbOpenAttempted = true;
1493
+ _currentIdentityKey = key;
1494
+ return true;
1495
+ }
1496
+
1497
+ // Cache miss — need to open a new connection.
1498
+ //
1499
+ // If there is a currently active workspace connection, stash it in the
1500
+ // cache under its identity key before calling openDatabase(), because
1501
+ // openDatabase() will call closeDatabase() when the path changes (which
1502
+ // would destroy the existing adapter). By nulling out currentDb first,
1503
+ // we prevent openDatabase() from closing the live adapter.
1504
+ let oldDb: typeof currentDb = null;
1505
+ let oldPath: typeof currentPath = null;
1506
+ let oldPid: typeof currentPid = 0;
1507
+ let oldKey: typeof _currentIdentityKey = null;
1508
+
1509
+ if (currentDb !== null && _currentIdentityKey !== null) {
1510
+ // Snapshot the old globals so we can restore them on failure.
1511
+ oldDb = currentDb;
1512
+ oldPath = currentPath;
1513
+ oldPid = currentPid;
1514
+ oldKey = _currentIdentityKey;
1515
+ // Save the current connection so it stays alive in the cache.
1516
+ _dbCache.set(_currentIdentityKey, {
1517
+ dbPath: currentPath!,
1518
+ db: currentDb,
1519
+ });
1520
+ // Detach from globals so openDatabase() opens fresh without closing it.
1521
+ currentDb = null;
1522
+ currentPath = null;
1523
+ currentPid = 0;
1524
+ _currentIdentityKey = null;
1525
+ }
1526
+
1527
+ // Run the full open/schema/migration flow for the new workspace.
1528
+ // openDatabase() can throw on corrupt DB or permission error — catch so we
1529
+ // can restore the previous connection rather than leaving globals null.
1530
+ let opened: boolean;
1531
+ try {
1532
+ opened = openDatabase(dbPath);
1533
+ } catch (err) {
1534
+ // Failed to open the new DB. Restore the previous workspace connection so
1535
+ // the caller's workspace remains active (it is still safe in _dbCache).
1536
+ if (oldDb !== null) {
1537
+ currentDb = oldDb;
1538
+ currentPath = oldPath;
1539
+ currentPid = oldPid;
1540
+ _currentIdentityKey = oldKey;
1541
+ }
1542
+ throw err;
1543
+ }
1544
+ if (opened && currentDb) {
1545
+ _dbCache.set(key, { dbPath, db: currentDb });
1546
+ _currentIdentityKey = key;
1547
+ } else if (!opened && oldDb !== null) {
1548
+ // Restore the previous connection so the caller's workspace remains active.
1549
+ // The failed attempt left no live adapter, so the globals stayed null.
1550
+ currentDb = oldDb;
1551
+ currentPath = oldPath;
1552
+ currentPid = oldPid;
1553
+ _currentIdentityKey = oldKey;
1554
+ }
1555
+ return opened;
1556
+ }
1557
+
1558
+ /**
1559
+ * Open (or reuse) the database connection scoped to the workspace in a
1560
+ * MilestoneScope. Thin delegation to openDatabaseByWorkspace().
1561
+ */
1562
+ export function openDatabaseByScope(scope: MilestoneScope): boolean {
1563
+ return openDatabaseByWorkspace(scope.workspace);
1564
+ }
1565
+
1566
+ /**
1567
+ * Close the database connection for the given workspace and remove it from
1568
+ * the cache. If the workspace's connection is currently active (currentDb),
1569
+ * performs a full closeDatabase() including WAL checkpoint. Otherwise only
1570
+ * removes the cache entry (the adapter was already replaced by a later open).
1571
+ */
1572
+ export function closeDatabaseByWorkspace(workspace: GsdWorkspace): void {
1573
+ const key = workspace.identityKey;
1574
+ const cached = _dbCache.get(key);
1575
+ if (!cached) return;
1576
+
1577
+ _dbCache.delete(key);
1578
+
1579
+ if (currentDb === cached.db) {
1580
+ // This workspace's connection is the active one — full close.
1581
+ closeDatabase();
1582
+ } else {
1583
+ // Connection was displaced by a later open; close the adapter directly.
1584
+ try {
1585
+ cached.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
1586
+ } catch (e) { logWarning("db", `WAL checkpoint (byWorkspace) failed: ${(e as Error).message}`); }
1587
+ try {
1588
+ cached.db.exec("PRAGMA incremental_vacuum(64)");
1589
+ } catch (e) { logWarning("db", `incremental vacuum (byWorkspace) failed: ${(e as Error).message}`); }
1590
+ try {
1591
+ cached.db.close();
1592
+ } catch (e) { logWarning("db", `database close (byWorkspace) failed: ${(e as Error).message}`); }
1593
+ }
1594
+ }
1262
1595
 
1263
1596
  export function getDbProvider(): ProviderName | null {
1264
1597
  loadProvider();
@@ -1389,6 +1722,13 @@ export function closeDatabase(): void {
1389
1722
  try {
1390
1723
  currentDb.close();
1391
1724
  } catch (e) { logWarning("db", `database close failed: ${(e as Error).message}`); }
1725
+ // If this connection was workspace-tracked, evict it from the cache so
1726
+ // subsequent openDatabaseByWorkspace() calls re-open rather than reactivate
1727
+ // a closed adapter.
1728
+ if (_currentIdentityKey !== null) {
1729
+ _dbCache.delete(_currentIdentityKey);
1730
+ _currentIdentityKey = null;
1731
+ }
1392
1732
  currentDb = null;
1393
1733
  currentPath = null;
1394
1734
  currentPid = 0;
@@ -3117,6 +3457,9 @@ export function deleteMilestone(milestoneId: string): void {
3117
3457
  currentDb!.prepare(
3118
3458
  `DELETE FROM artifacts WHERE milestone_id = :mid`,
3119
3459
  ).run({ ":mid": milestoneId });
3460
+ currentDb!.prepare(
3461
+ `DELETE FROM milestone_leases WHERE milestone_id = :mid`,
3462
+ ).run({ ":mid": milestoneId });
3120
3463
  currentDb!.prepare(
3121
3464
  `DELETE FROM milestones WHERE id = :mid`,
3122
3465
  ).run({ ":mid": milestoneId });
@@ -3539,14 +3882,20 @@ export function deleteArtifactByPath(path: string): void {
3539
3882
  }
3540
3883
 
3541
3884
  /**
3542
- * Drop all rows from tasks/slices/milestones in dependency order inside a
3543
- * transaction. Used by `gsd recover` to rebuild engine state from markdown.
3885
+ * Drop hierarchy rows in dependency order inside a transaction. Used by
3886
+ * `gsd recover` to rebuild engine state from markdown.
3544
3887
  */
3545
3888
  export function clearEngineHierarchy(): void {
3546
3889
  if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
3547
3890
  transaction(() => {
3891
+ currentDb!.exec("DELETE FROM verification_evidence");
3892
+ currentDb!.exec("DELETE FROM quality_gates");
3893
+ currentDb!.exec("DELETE FROM slice_dependencies");
3894
+ currentDb!.exec("DELETE FROM assessments");
3895
+ currentDb!.exec("DELETE FROM replan_history");
3548
3896
  currentDb!.exec("DELETE FROM tasks");
3549
3897
  currentDb!.exec("DELETE FROM slices");
3898
+ currentDb!.exec("DELETE FROM milestone_leases");
3550
3899
  currentDb!.exec("DELETE FROM milestones");
3551
3900
  });
3552
3901
  }
@@ -3660,6 +4009,7 @@ export function restoreManifest(manifest: StateManifest): void {
3660
4009
  db.exec("DELETE FROM verification_evidence");
3661
4010
  db.exec("DELETE FROM tasks");
3662
4011
  db.exec("DELETE FROM slices");
4012
+ db.exec("DELETE FROM milestone_leases");
3663
4013
  db.exec("DELETE FROM milestones");
3664
4014
  db.exec("DELETE FROM decisions WHERE 1=1");
3665
4015
 
@@ -3798,6 +4148,7 @@ export function bulkInsertLegacyHierarchy(payload: {
3798
4148
  transaction(() => {
3799
4149
  db.prepare(`DELETE FROM tasks WHERE milestone_id IN (${placeholders})`).run(...clearMilestoneIds);
3800
4150
  db.prepare(`DELETE FROM slices WHERE milestone_id IN (${placeholders})`).run(...clearMilestoneIds);
4151
+ db.prepare(`DELETE FROM milestone_leases WHERE milestone_id IN (${placeholders})`).run(...clearMilestoneIds);
3801
4152
  db.prepare(`DELETE FROM milestones WHERE id IN (${placeholders})`).run(...clearMilestoneIds);
3802
4153
 
3803
4154
  const insertMilestone = db.prepare(
@@ -194,7 +194,7 @@ export async function showQueueAdd(
194
194
 
195
195
  // ── Dispatch the queue prompt ───────────────────────────────────────
196
196
  // Activate the queue phase so the write-gate applies to CONTEXT.md writes
197
- setQueuePhaseActive(true);
197
+ setQueuePhaseActive(true, basePath);
198
198
 
199
199
  const queueInlinedTemplates = inlineTemplate("context", "Context");
200
200
  const prompt = loadPrompt("queue", {