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.
- package/README.md +5 -7
- package/dist/help-text.js +1 -1
- package/dist/resource-loader.js +6 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +41 -5
- package/dist/resources/extensions/gsd/auto/loop.js +235 -36
- package/dist/resources/extensions/gsd/auto/phases.js +14 -7
- package/dist/resources/extensions/gsd/auto/session.js +36 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +49 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +26 -12
- package/dist/resources/extensions/gsd/auto-worktree.js +185 -201
- package/dist/resources/extensions/gsd/auto.js +139 -49
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +26 -20
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
- package/dist/resources/extensions/gsd/crash-recovery.js +160 -47
- package/dist/resources/extensions/gsd/db/auto-workers.js +227 -0
- package/dist/resources/extensions/gsd/db/command-queue.js +105 -0
- package/dist/resources/extensions/gsd/db/milestone-leases.js +210 -0
- package/dist/resources/extensions/gsd/db/runtime-kv.js +91 -0
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +322 -0
- package/dist/resources/extensions/gsd/db-writer.js +96 -16
- package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
- package/dist/resources/extensions/gsd/docs/COORDINATION.md +42 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +4 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +22 -6
- package/dist/resources/extensions/gsd/doctor.js +12 -2
- package/dist/resources/extensions/gsd/gsd-db.js +355 -3
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +116 -26
- package/dist/resources/extensions/gsd/interrupted-session.js +18 -15
- package/dist/resources/extensions/gsd/metrics.js +287 -1
- package/dist/resources/extensions/gsd/paths.js +79 -8
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/dist/resources/extensions/gsd/state.js +21 -6
- package/dist/resources/extensions/gsd/templates/project.md +10 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
- package/dist/resources/extensions/gsd/workspace.js +59 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +79 -2
- package/dist/resources/extensions/gsd/write-intercept.js +3 -3
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/README.md +2 -11
- package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
- package/packages/mcp-server/dist/remote-questions.js +28 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +28 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +94 -4
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +226 -0
- package/packages/mcp-server/src/remote-questions.test.ts +103 -0
- package/packages/mcp-server/src/remote-questions.ts +35 -0
- package/packages/mcp-server/src/server.ts +129 -6
- package/packages/mcp-server/src/workflow-tools.ts +1 -1
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +37 -5
- package/src/resources/extensions/gsd/auto/loop.ts +263 -41
- package/src/resources/extensions/gsd/auto/phases.ts +15 -7
- package/src/resources/extensions/gsd/auto/session.ts +40 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +63 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +27 -12
- package/src/resources/extensions/gsd/auto-worktree.ts +218 -225
- package/src/resources/extensions/gsd/auto.ts +166 -43
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +26 -21
- package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
- package/src/resources/extensions/gsd/crash-recovery.ts +177 -43
- package/src/resources/extensions/gsd/db/auto-workers.ts +273 -0
- package/src/resources/extensions/gsd/db/command-queue.ts +149 -0
- package/src/resources/extensions/gsd/db/milestone-leases.ts +274 -0
- package/src/resources/extensions/gsd/db/runtime-kv.ts +127 -0
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +446 -0
- package/src/resources/extensions/gsd/db-writer.ts +113 -17
- package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
- package/src/resources/extensions/gsd/docs/COORDINATION.md +42 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +4 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +24 -6
- package/src/resources/extensions/gsd/doctor.ts +10 -2
- package/src/resources/extensions/gsd/gsd-db.ts +354 -3
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +152 -26
- package/src/resources/extensions/gsd/interrupted-session.ts +19 -12
- package/src/resources/extensions/gsd/metrics.ts +321 -1
- package/src/resources/extensions/gsd/paths.ts +67 -8
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/src/resources/extensions/gsd/state.ts +44 -6
- package/src/resources/extensions/gsd/templates/project.md +10 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
- package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +72 -0
- package/src/resources/extensions/gsd/tests/auto-loop-symlink-worktree.test.ts +190 -0
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
- package/src/resources/extensions/gsd/tests/auto-workers.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/command-queue.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +169 -59
- package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
- package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/detect-stuck-respects-retry.test.ts +173 -0
- package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
- package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
- package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +22 -12
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +24 -10
- package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +35 -23
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +369 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +72 -25
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +72 -25
- package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +9 -6
- package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
- package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
- package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
- package/src/resources/extensions/gsd/tests/parallel-milestone-isolation.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +3 -17
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +138 -16
- package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/runtime-kv.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +133 -28
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +434 -0
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +247 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +41 -1
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/workspace.test.ts +196 -0
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +94 -71
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
- package/src/resources/extensions/gsd/workspace.ts +95 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +78 -2
- package/src/resources/extensions/gsd/write-intercept.ts +3 -3
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +0 -213
- package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +0 -87
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +0 -159
- /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_buildManifest.js +0 -0
- /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 =
|
|
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
|
|
3543
|
-
*
|
|
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", {
|