gsd-pi 2.33.1-dev.ee47f1b → 2.34.0-dev.bbb5216
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/dist/bundled-resource-path.d.ts +8 -0
- package/dist/bundled-resource-path.js +14 -0
- package/dist/headless-query.js +6 -6
- package/dist/resources/extensions/gsd/auto/session.js +27 -32
- package/dist/resources/extensions/gsd/auto-dashboard.js +29 -109
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +6 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +52 -81
- package/dist/resources/extensions/gsd/auto-loop.js +956 -0
- package/dist/resources/extensions/gsd/auto-observability.js +4 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +75 -185
- package/dist/resources/extensions/gsd/auto-prompts.js +133 -101
- package/dist/resources/extensions/gsd/auto-recovery.js +59 -97
- package/dist/resources/extensions/gsd/auto-start.js +330 -309
- package/dist/resources/extensions/gsd/auto-supervisor.js +5 -11
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +7 -7
- package/dist/resources/extensions/gsd/auto-timers.js +3 -4
- package/dist/resources/extensions/gsd/auto-verification.js +35 -73
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +167 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +291 -126
- package/dist/resources/extensions/gsd/auto.js +283 -1013
- package/dist/resources/extensions/gsd/captures.js +10 -4
- package/dist/resources/extensions/gsd/dispatch-guard.js +7 -8
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -18
- package/dist/resources/extensions/gsd/doctor-checks.js +3 -4
- package/dist/resources/extensions/gsd/git-service.js +1 -1
- package/dist/resources/extensions/gsd/gsd-db.js +296 -151
- package/dist/resources/extensions/gsd/index.js +92 -228
- package/dist/resources/extensions/gsd/post-unit-hooks.js +13 -13
- package/dist/resources/extensions/gsd/progress-score.js +61 -156
- package/dist/resources/extensions/gsd/quick.js +98 -122
- package/dist/resources/extensions/gsd/session-lock.js +13 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
- package/dist/resources/extensions/gsd/undo.js +43 -48
- package/dist/resources/extensions/gsd/unit-runtime.js +16 -15
- package/dist/resources/extensions/gsd/verification-evidence.js +0 -1
- package/dist/resources/extensions/gsd/verification-gate.js +6 -35
- package/dist/resources/extensions/gsd/worktree-command.js +30 -24
- package/dist/resources/extensions/gsd/worktree-manager.js +2 -3
- package/dist/resources/extensions/gsd/worktree-resolver.js +344 -0
- package/dist/resources/extensions/gsd/worktree.js +7 -44
- package/dist/tool-bootstrap.js +59 -11
- package/dist/worktree-cli.js +7 -7
- package/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +3630 -5483
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +735 -2588
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/src/models.generated.ts +1039 -2892
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/session.ts +47 -30
- package/src/resources/extensions/gsd/auto-dashboard.ts +28 -131
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +6 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +135 -91
- package/src/resources/extensions/gsd/auto-loop.ts +1665 -0
- package/src/resources/extensions/gsd/auto-observability.ts +4 -2
- package/src/resources/extensions/gsd/auto-post-unit.ts +85 -228
- package/src/resources/extensions/gsd/auto-prompts.ts +138 -109
- package/src/resources/extensions/gsd/auto-recovery.ts +124 -118
- package/src/resources/extensions/gsd/auto-start.ts +440 -354
- package/src/resources/extensions/gsd/auto-supervisor.ts +5 -12
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +8 -8
- package/src/resources/extensions/gsd/auto-timers.ts +3 -4
- package/src/resources/extensions/gsd/auto-verification.ts +76 -90
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +204 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +389 -141
- package/src/resources/extensions/gsd/auto.ts +515 -1199
- package/src/resources/extensions/gsd/captures.ts +10 -4
- package/src/resources/extensions/gsd/dispatch-guard.ts +13 -9
- package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -18
- package/src/resources/extensions/gsd/doctor-checks.ts +3 -4
- package/src/resources/extensions/gsd/git-service.ts +8 -1
- package/src/resources/extensions/gsd/gitignore.ts +4 -2
- package/src/resources/extensions/gsd/gsd-db.ts +375 -180
- package/src/resources/extensions/gsd/index.ts +104 -263
- package/src/resources/extensions/gsd/post-unit-hooks.ts +13 -13
- package/src/resources/extensions/gsd/progress-score.ts +65 -200
- package/src/resources/extensions/gsd/quick.ts +121 -125
- package/src/resources/extensions/gsd/session-lock.ts +11 -0
- package/src/resources/extensions/gsd/templates/preferences.md +1 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +32 -59
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +75 -27
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1458 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +8 -162
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -108
- package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +1 -3
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -55
- package/src/resources/extensions/gsd/tests/headless-query.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +8 -11
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +4 -6
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/undo.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +7 -201
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +705 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +57 -106
- package/src/resources/extensions/gsd/tests/worktree.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +43 -132
- package/src/resources/extensions/gsd/types.ts +90 -81
- package/src/resources/extensions/gsd/undo.ts +42 -46
- package/src/resources/extensions/gsd/unit-runtime.ts +14 -18
- package/src/resources/extensions/gsd/verification-evidence.ts +1 -3
- package/src/resources/extensions/gsd/verification-gate.ts +6 -39
- package/src/resources/extensions/gsd/worktree-command.ts +36 -24
- package/src/resources/extensions/gsd/worktree-manager.ts +2 -3
- package/src/resources/extensions/gsd/worktree-resolver.ts +485 -0
- package/src/resources/extensions/gsd/worktree.ts +7 -44
- package/dist/resources/extensions/gsd/auto-constants.js +0 -5
- package/dist/resources/extensions/gsd/auto-idempotency.js +0 -106
- package/dist/resources/extensions/gsd/auto-stuck-detection.js +0 -165
- package/dist/resources/extensions/gsd/mechanical-completion.js +0 -351
- package/src/resources/extensions/gsd/auto-constants.ts +0 -6
- package/src/resources/extensions/gsd/auto-idempotency.ts +0 -151
- package/src/resources/extensions/gsd/auto-stuck-detection.ts +0 -221
- package/src/resources/extensions/gsd/mechanical-completion.ts +0 -430
- package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +0 -691
- package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +0 -127
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +0 -123
- package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +0 -126
- package/src/resources/extensions/gsd/tests/loop-regression.test.ts +0 -874
- package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +0 -356
- package/src/resources/extensions/gsd/tests/progress-score.test.ts +0 -206
- package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -434
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Exposes a unified sync API for decisions and requirements storage.
|
|
6
6
|
// Schema is initialized on first open with WAL mode for file-backed DBs.
|
|
7
|
-
import { createRequire } from
|
|
8
|
-
import {
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import { existsSync, copyFileSync, mkdirSync } from "node:fs";
|
|
9
|
+
import { dirname } from "node:path";
|
|
10
|
+
import { GSDError, GSD_STALE_STATE } from "./errors.js";
|
|
9
11
|
// Create a require function for loading native modules in ESM context
|
|
10
12
|
const _require = createRequire(import.meta.url);
|
|
11
13
|
let providerName = null;
|
|
@@ -19,14 +21,14 @@ function suppressSqliteWarning() {
|
|
|
19
21
|
const origEmit = process.emit;
|
|
20
22
|
// @ts-expect-error — overriding process.emit with filtered version
|
|
21
23
|
process.emit = function (event, ...args) {
|
|
22
|
-
if (event ===
|
|
24
|
+
if (event === "warning" &&
|
|
23
25
|
args[0] &&
|
|
24
|
-
typeof args[0] ===
|
|
25
|
-
|
|
26
|
-
args[0].name ===
|
|
27
|
-
|
|
28
|
-
typeof args[0].message ===
|
|
29
|
-
args[0].message.includes(
|
|
26
|
+
typeof args[0] === "object" &&
|
|
27
|
+
"name" in args[0] &&
|
|
28
|
+
args[0].name === "ExperimentalWarning" &&
|
|
29
|
+
"message" in args[0] &&
|
|
30
|
+
typeof args[0].message === "string" &&
|
|
31
|
+
args[0].message.includes("SQLite")) {
|
|
30
32
|
return false;
|
|
31
33
|
}
|
|
32
34
|
return origEmit.apply(process, [event, ...args]);
|
|
@@ -39,10 +41,10 @@ function loadProvider() {
|
|
|
39
41
|
// Try node:sqlite first
|
|
40
42
|
try {
|
|
41
43
|
suppressSqliteWarning();
|
|
42
|
-
const mod = _require(
|
|
44
|
+
const mod = _require("node:sqlite");
|
|
43
45
|
if (mod.DatabaseSync) {
|
|
44
46
|
providerModule = mod;
|
|
45
|
-
providerName =
|
|
47
|
+
providerName = "node:sqlite";
|
|
46
48
|
return;
|
|
47
49
|
}
|
|
48
50
|
}
|
|
@@ -51,17 +53,17 @@ function loadProvider() {
|
|
|
51
53
|
}
|
|
52
54
|
// Try better-sqlite3
|
|
53
55
|
try {
|
|
54
|
-
const mod = _require(
|
|
55
|
-
if (typeof mod ===
|
|
56
|
+
const mod = _require("better-sqlite3");
|
|
57
|
+
if (typeof mod === "function" || (mod && mod.default)) {
|
|
56
58
|
providerModule = mod.default || mod;
|
|
57
|
-
providerName =
|
|
59
|
+
providerName = "better-sqlite3";
|
|
58
60
|
return;
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
catch {
|
|
62
64
|
// better-sqlite3 not available
|
|
63
65
|
}
|
|
64
|
-
process.stderr.write(
|
|
66
|
+
process.stderr.write("gsd-db: No SQLite provider available (tried node:sqlite, better-sqlite3)\n");
|
|
65
67
|
}
|
|
66
68
|
// ─── Database Adapter ──────────────────────────────────────────────────────
|
|
67
69
|
/**
|
|
@@ -76,7 +78,7 @@ function normalizeRow(row) {
|
|
|
76
78
|
return row;
|
|
77
79
|
}
|
|
78
80
|
function normalizeRows(rows) {
|
|
79
|
-
return rows.map(r => normalizeRow(r));
|
|
81
|
+
return rows.map((r) => normalizeRow(r));
|
|
80
82
|
}
|
|
81
83
|
function createAdapter(rawDb) {
|
|
82
84
|
const db = rawDb;
|
|
@@ -88,7 +90,7 @@ function createAdapter(rawDb) {
|
|
|
88
90
|
const stmt = db.prepare(sql);
|
|
89
91
|
return {
|
|
90
92
|
run(...params) {
|
|
91
|
-
stmt.run(...params);
|
|
93
|
+
return stmt.run(...params);
|
|
92
94
|
},
|
|
93
95
|
get(...params) {
|
|
94
96
|
return normalizeRow(stmt.get(...params));
|
|
@@ -107,7 +109,7 @@ function openRawDb(path) {
|
|
|
107
109
|
loadProvider();
|
|
108
110
|
if (!providerModule || !providerName)
|
|
109
111
|
return null;
|
|
110
|
-
if (providerName ===
|
|
112
|
+
if (providerName === "node:sqlite") {
|
|
111
113
|
const { DatabaseSync } = providerModule;
|
|
112
114
|
return new DatabaseSync(path);
|
|
113
115
|
}
|
|
@@ -120,9 +122,9 @@ const SCHEMA_VERSION = 3;
|
|
|
120
122
|
function initSchema(db, fileBacked) {
|
|
121
123
|
// WAL mode for file-backed databases (must be outside transaction)
|
|
122
124
|
if (fileBacked) {
|
|
123
|
-
db.exec(
|
|
125
|
+
db.exec("PRAGMA journal_mode=WAL");
|
|
124
126
|
}
|
|
125
|
-
db.exec(
|
|
127
|
+
db.exec("BEGIN");
|
|
126
128
|
try {
|
|
127
129
|
db.exec(`
|
|
128
130
|
CREATE TABLE IF NOT EXISTS schema_version (
|
|
@@ -192,20 +194,25 @@ function initSchema(db, fileBacked) {
|
|
|
192
194
|
processed_at TEXT NOT NULL
|
|
193
195
|
)
|
|
194
196
|
`);
|
|
195
|
-
db.exec(
|
|
197
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memories_active ON memories(superseded_by)");
|
|
196
198
|
// Views — DROP + CREATE since CREATE VIEW IF NOT EXISTS doesn't update definitions
|
|
197
199
|
db.exec(`CREATE VIEW IF NOT EXISTS active_decisions AS SELECT * FROM decisions WHERE superseded_by IS NULL`);
|
|
198
200
|
db.exec(`CREATE VIEW IF NOT EXISTS active_requirements AS SELECT * FROM requirements WHERE superseded_by IS NULL`);
|
|
199
201
|
db.exec(`CREATE VIEW IF NOT EXISTS active_memories AS SELECT * FROM memories WHERE superseded_by IS NULL`);
|
|
200
202
|
// Insert schema version if not already present
|
|
201
|
-
const existing = db
|
|
202
|
-
|
|
203
|
-
|
|
203
|
+
const existing = db
|
|
204
|
+
.prepare("SELECT count(*) as cnt FROM schema_version")
|
|
205
|
+
.get();
|
|
206
|
+
if (existing && existing["cnt"] === 0) {
|
|
207
|
+
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
208
|
+
":version": SCHEMA_VERSION,
|
|
209
|
+
":applied_at": new Date().toISOString(),
|
|
210
|
+
});
|
|
204
211
|
}
|
|
205
|
-
db.exec(
|
|
212
|
+
db.exec("COMMIT");
|
|
206
213
|
}
|
|
207
214
|
catch (err) {
|
|
208
|
-
db.exec(
|
|
215
|
+
db.exec("ROLLBACK");
|
|
209
216
|
throw err;
|
|
210
217
|
}
|
|
211
218
|
// Run incremental migrations for existing databases
|
|
@@ -216,11 +223,11 @@ function initSchema(db, fileBacked) {
|
|
|
216
223
|
* and applies DDL for each version step up to SCHEMA_VERSION.
|
|
217
224
|
*/
|
|
218
225
|
function migrateSchema(db) {
|
|
219
|
-
const row = db.prepare(
|
|
220
|
-
const currentVersion = row ? row[
|
|
226
|
+
const row = db.prepare("SELECT MAX(version) as v FROM schema_version").get();
|
|
227
|
+
const currentVersion = row ? row["v"] : 0;
|
|
221
228
|
if (currentVersion >= SCHEMA_VERSION)
|
|
222
229
|
return;
|
|
223
|
-
db.exec(
|
|
230
|
+
db.exec("BEGIN");
|
|
224
231
|
try {
|
|
225
232
|
// v1 → v2: add artifacts table
|
|
226
233
|
if (currentVersion < 2) {
|
|
@@ -235,7 +242,7 @@ function migrateSchema(db) {
|
|
|
235
242
|
imported_at TEXT NOT NULL DEFAULT ''
|
|
236
243
|
)
|
|
237
244
|
`);
|
|
238
|
-
db.prepare(
|
|
245
|
+
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({ ":version": 2, ":applied_at": new Date().toISOString() });
|
|
239
246
|
}
|
|
240
247
|
// v2 → v3: add memories + memory_processed_units tables
|
|
241
248
|
if (currentVersion < 3) {
|
|
@@ -261,15 +268,15 @@ function migrateSchema(db) {
|
|
|
261
268
|
processed_at TEXT NOT NULL
|
|
262
269
|
)
|
|
263
270
|
`);
|
|
264
|
-
db.exec(
|
|
265
|
-
db.exec(
|
|
266
|
-
db.exec(
|
|
267
|
-
db.prepare(
|
|
271
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memories_active ON memories(superseded_by)");
|
|
272
|
+
db.exec("DROP VIEW IF EXISTS active_memories");
|
|
273
|
+
db.exec("CREATE VIEW active_memories AS SELECT * FROM memories WHERE superseded_by IS NULL");
|
|
274
|
+
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({ ":version": 3, ":applied_at": new Date().toISOString() });
|
|
268
275
|
}
|
|
269
|
-
db.exec(
|
|
276
|
+
db.exec("COMMIT");
|
|
270
277
|
}
|
|
271
278
|
catch (err) {
|
|
272
|
-
db.exec(
|
|
279
|
+
db.exec("ROLLBACK");
|
|
273
280
|
throw err;
|
|
274
281
|
}
|
|
275
282
|
}
|
|
@@ -309,7 +316,7 @@ export function openDatabase(path) {
|
|
|
309
316
|
if (!rawDb)
|
|
310
317
|
return false;
|
|
311
318
|
const adapter = createAdapter(rawDb);
|
|
312
|
-
const fileBacked = path !==
|
|
319
|
+
const fileBacked = path !== ":memory:";
|
|
313
320
|
try {
|
|
314
321
|
initSchema(adapter, fileBacked);
|
|
315
322
|
}
|
|
@@ -317,7 +324,9 @@ export function openDatabase(path) {
|
|
|
317
324
|
try {
|
|
318
325
|
adapter.close();
|
|
319
326
|
}
|
|
320
|
-
catch {
|
|
327
|
+
catch {
|
|
328
|
+
/* swallow */
|
|
329
|
+
}
|
|
321
330
|
throw err;
|
|
322
331
|
}
|
|
323
332
|
currentDb = adapter;
|
|
@@ -346,15 +355,15 @@ export function closeDatabase() {
|
|
|
346
355
|
*/
|
|
347
356
|
export function transaction(fn) {
|
|
348
357
|
if (!currentDb)
|
|
349
|
-
throw new GSDError(GSD_STALE_STATE,
|
|
350
|
-
currentDb.exec(
|
|
358
|
+
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
359
|
+
currentDb.exec("BEGIN");
|
|
351
360
|
try {
|
|
352
361
|
const result = fn();
|
|
353
|
-
currentDb.exec(
|
|
362
|
+
currentDb.exec("COMMIT");
|
|
354
363
|
return result;
|
|
355
364
|
}
|
|
356
365
|
catch (err) {
|
|
357
|
-
currentDb.exec(
|
|
366
|
+
currentDb.exec("ROLLBACK");
|
|
358
367
|
throw err;
|
|
359
368
|
}
|
|
360
369
|
}
|
|
@@ -364,17 +373,19 @@ export function transaction(fn) {
|
|
|
364
373
|
*/
|
|
365
374
|
export function insertDecision(d) {
|
|
366
375
|
if (!currentDb)
|
|
367
|
-
throw new GSDError(GSD_STALE_STATE,
|
|
368
|
-
currentDb
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
376
|
+
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
377
|
+
currentDb
|
|
378
|
+
.prepare(`INSERT INTO decisions (id, when_context, scope, decision, choice, rationale, revisable, superseded_by)
|
|
379
|
+
VALUES (:id, :when_context, :scope, :decision, :choice, :rationale, :revisable, :superseded_by)`)
|
|
380
|
+
.run({
|
|
381
|
+
":id": d.id,
|
|
382
|
+
":when_context": d.when_context,
|
|
383
|
+
":scope": d.scope,
|
|
384
|
+
":decision": d.decision,
|
|
385
|
+
":choice": d.choice,
|
|
386
|
+
":rationale": d.rationale,
|
|
387
|
+
":revisable": d.revisable,
|
|
388
|
+
":superseded_by": d.superseded_by,
|
|
378
389
|
});
|
|
379
390
|
}
|
|
380
391
|
/**
|
|
@@ -383,19 +394,19 @@ export function insertDecision(d) {
|
|
|
383
394
|
export function getDecisionById(id) {
|
|
384
395
|
if (!currentDb)
|
|
385
396
|
return null;
|
|
386
|
-
const row = currentDb.prepare(
|
|
397
|
+
const row = currentDb.prepare("SELECT * FROM decisions WHERE id = ?").get(id);
|
|
387
398
|
if (!row)
|
|
388
399
|
return null;
|
|
389
400
|
return {
|
|
390
|
-
seq: row[
|
|
391
|
-
id: row[
|
|
392
|
-
when_context: row[
|
|
393
|
-
scope: row[
|
|
394
|
-
decision: row[
|
|
395
|
-
choice: row[
|
|
396
|
-
rationale: row[
|
|
397
|
-
revisable: row[
|
|
398
|
-
superseded_by: row[
|
|
401
|
+
seq: row["seq"],
|
|
402
|
+
id: row["id"],
|
|
403
|
+
when_context: row["when_context"],
|
|
404
|
+
scope: row["scope"],
|
|
405
|
+
decision: row["decision"],
|
|
406
|
+
choice: row["choice"],
|
|
407
|
+
rationale: row["rationale"],
|
|
408
|
+
revisable: row["revisable"],
|
|
409
|
+
superseded_by: row["superseded_by"] ?? null,
|
|
399
410
|
};
|
|
400
411
|
}
|
|
401
412
|
/**
|
|
@@ -404,16 +415,16 @@ export function getDecisionById(id) {
|
|
|
404
415
|
export function getActiveDecisions() {
|
|
405
416
|
if (!currentDb)
|
|
406
417
|
return [];
|
|
407
|
-
const rows = currentDb.prepare(
|
|
408
|
-
return rows.map(row => ({
|
|
409
|
-
seq: row[
|
|
410
|
-
id: row[
|
|
411
|
-
when_context: row[
|
|
412
|
-
scope: row[
|
|
413
|
-
decision: row[
|
|
414
|
-
choice: row[
|
|
415
|
-
rationale: row[
|
|
416
|
-
revisable: row[
|
|
418
|
+
const rows = currentDb.prepare("SELECT * FROM active_decisions").all();
|
|
419
|
+
return rows.map((row) => ({
|
|
420
|
+
seq: row["seq"],
|
|
421
|
+
id: row["id"],
|
|
422
|
+
when_context: row["when_context"],
|
|
423
|
+
scope: row["scope"],
|
|
424
|
+
decision: row["decision"],
|
|
425
|
+
choice: row["choice"],
|
|
426
|
+
rationale: row["rationale"],
|
|
427
|
+
revisable: row["revisable"],
|
|
417
428
|
superseded_by: null,
|
|
418
429
|
}));
|
|
419
430
|
}
|
|
@@ -423,21 +434,23 @@ export function getActiveDecisions() {
|
|
|
423
434
|
*/
|
|
424
435
|
export function insertRequirement(r) {
|
|
425
436
|
if (!currentDb)
|
|
426
|
-
throw new GSDError(GSD_STALE_STATE,
|
|
427
|
-
currentDb
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
437
|
+
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
438
|
+
currentDb
|
|
439
|
+
.prepare(`INSERT INTO requirements (id, class, status, description, why, source, primary_owner, supporting_slices, validation, notes, full_content, superseded_by)
|
|
440
|
+
VALUES (:id, :class, :status, :description, :why, :source, :primary_owner, :supporting_slices, :validation, :notes, :full_content, :superseded_by)`)
|
|
441
|
+
.run({
|
|
442
|
+
":id": r.id,
|
|
443
|
+
":class": r.class,
|
|
444
|
+
":status": r.status,
|
|
445
|
+
":description": r.description,
|
|
446
|
+
":why": r.why,
|
|
447
|
+
":source": r.source,
|
|
448
|
+
":primary_owner": r.primary_owner,
|
|
449
|
+
":supporting_slices": r.supporting_slices,
|
|
450
|
+
":validation": r.validation,
|
|
451
|
+
":notes": r.notes,
|
|
452
|
+
":full_content": r.full_content,
|
|
453
|
+
":superseded_by": r.superseded_by,
|
|
441
454
|
});
|
|
442
455
|
}
|
|
443
456
|
/**
|
|
@@ -446,22 +459,24 @@ export function insertRequirement(r) {
|
|
|
446
459
|
export function getRequirementById(id) {
|
|
447
460
|
if (!currentDb)
|
|
448
461
|
return null;
|
|
449
|
-
const row = currentDb
|
|
462
|
+
const row = currentDb
|
|
463
|
+
.prepare("SELECT * FROM requirements WHERE id = ?")
|
|
464
|
+
.get(id);
|
|
450
465
|
if (!row)
|
|
451
466
|
return null;
|
|
452
467
|
return {
|
|
453
|
-
id: row[
|
|
454
|
-
class: row[
|
|
455
|
-
status: row[
|
|
456
|
-
description: row[
|
|
457
|
-
why: row[
|
|
458
|
-
source: row[
|
|
459
|
-
primary_owner: row[
|
|
460
|
-
supporting_slices: row[
|
|
461
|
-
validation: row[
|
|
462
|
-
notes: row[
|
|
463
|
-
full_content: row[
|
|
464
|
-
superseded_by: row[
|
|
468
|
+
id: row["id"],
|
|
469
|
+
class: row["class"],
|
|
470
|
+
status: row["status"],
|
|
471
|
+
description: row["description"],
|
|
472
|
+
why: row["why"],
|
|
473
|
+
source: row["source"],
|
|
474
|
+
primary_owner: row["primary_owner"],
|
|
475
|
+
supporting_slices: row["supporting_slices"],
|
|
476
|
+
validation: row["validation"],
|
|
477
|
+
notes: row["notes"],
|
|
478
|
+
full_content: row["full_content"],
|
|
479
|
+
superseded_by: row["superseded_by"] ?? null,
|
|
465
480
|
};
|
|
466
481
|
}
|
|
467
482
|
/**
|
|
@@ -470,19 +485,19 @@ export function getRequirementById(id) {
|
|
|
470
485
|
export function getActiveRequirements() {
|
|
471
486
|
if (!currentDb)
|
|
472
487
|
return [];
|
|
473
|
-
const rows = currentDb.prepare(
|
|
474
|
-
return rows.map(row => ({
|
|
475
|
-
id: row[
|
|
476
|
-
class: row[
|
|
477
|
-
status: row[
|
|
478
|
-
description: row[
|
|
479
|
-
why: row[
|
|
480
|
-
source: row[
|
|
481
|
-
primary_owner: row[
|
|
482
|
-
supporting_slices: row[
|
|
483
|
-
validation: row[
|
|
484
|
-
notes: row[
|
|
485
|
-
full_content: row[
|
|
488
|
+
const rows = currentDb.prepare("SELECT * FROM active_requirements").all();
|
|
489
|
+
return rows.map((row) => ({
|
|
490
|
+
id: row["id"],
|
|
491
|
+
class: row["class"],
|
|
492
|
+
status: row["status"],
|
|
493
|
+
description: row["description"],
|
|
494
|
+
why: row["why"],
|
|
495
|
+
source: row["source"],
|
|
496
|
+
primary_owner: row["primary_owner"],
|
|
497
|
+
supporting_slices: row["supporting_slices"],
|
|
498
|
+
validation: row["validation"],
|
|
499
|
+
notes: row["notes"],
|
|
500
|
+
full_content: row["full_content"],
|
|
486
501
|
superseded_by: null,
|
|
487
502
|
}));
|
|
488
503
|
}
|
|
@@ -520,17 +535,19 @@ export function _resetProvider() {
|
|
|
520
535
|
*/
|
|
521
536
|
export function upsertDecision(d) {
|
|
522
537
|
if (!currentDb)
|
|
523
|
-
throw new GSDError(GSD_STALE_STATE,
|
|
524
|
-
currentDb
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
538
|
+
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
539
|
+
currentDb
|
|
540
|
+
.prepare(`INSERT OR REPLACE INTO decisions (id, when_context, scope, decision, choice, rationale, revisable, superseded_by)
|
|
541
|
+
VALUES (:id, :when_context, :scope, :decision, :choice, :rationale, :revisable, :superseded_by)`)
|
|
542
|
+
.run({
|
|
543
|
+
":id": d.id,
|
|
544
|
+
":when_context": d.when_context,
|
|
545
|
+
":scope": d.scope,
|
|
546
|
+
":decision": d.decision,
|
|
547
|
+
":choice": d.choice,
|
|
548
|
+
":rationale": d.rationale,
|
|
549
|
+
":revisable": d.revisable,
|
|
550
|
+
":superseded_by": d.superseded_by ?? null,
|
|
534
551
|
});
|
|
535
552
|
}
|
|
536
553
|
/**
|
|
@@ -538,21 +555,23 @@ export function upsertDecision(d) {
|
|
|
538
555
|
*/
|
|
539
556
|
export function upsertRequirement(r) {
|
|
540
557
|
if (!currentDb)
|
|
541
|
-
throw new GSDError(GSD_STALE_STATE,
|
|
542
|
-
currentDb
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
558
|
+
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
559
|
+
currentDb
|
|
560
|
+
.prepare(`INSERT OR REPLACE INTO requirements (id, class, status, description, why, source, primary_owner, supporting_slices, validation, notes, full_content, superseded_by)
|
|
561
|
+
VALUES (:id, :class, :status, :description, :why, :source, :primary_owner, :supporting_slices, :validation, :notes, :full_content, :superseded_by)`)
|
|
562
|
+
.run({
|
|
563
|
+
":id": r.id,
|
|
564
|
+
":class": r.class,
|
|
565
|
+
":status": r.status,
|
|
566
|
+
":description": r.description,
|
|
567
|
+
":why": r.why,
|
|
568
|
+
":source": r.source,
|
|
569
|
+
":primary_owner": r.primary_owner,
|
|
570
|
+
":supporting_slices": r.supporting_slices,
|
|
571
|
+
":validation": r.validation,
|
|
572
|
+
":notes": r.notes,
|
|
573
|
+
":full_content": r.full_content,
|
|
574
|
+
":superseded_by": r.superseded_by ?? null,
|
|
556
575
|
});
|
|
557
576
|
}
|
|
558
577
|
/**
|
|
@@ -568,7 +587,7 @@ export function clearArtifacts() {
|
|
|
568
587
|
if (!currentDb)
|
|
569
588
|
return;
|
|
570
589
|
try {
|
|
571
|
-
currentDb.exec(
|
|
590
|
+
currentDb.exec("DELETE FROM artifacts");
|
|
572
591
|
}
|
|
573
592
|
catch {
|
|
574
593
|
// Clearing a cache should never be fatal
|
|
@@ -576,15 +595,141 @@ export function clearArtifacts() {
|
|
|
576
595
|
}
|
|
577
596
|
export function insertArtifact(a) {
|
|
578
597
|
if (!currentDb)
|
|
579
|
-
throw new GSDError(GSD_STALE_STATE,
|
|
580
|
-
currentDb
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
598
|
+
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
599
|
+
currentDb
|
|
600
|
+
.prepare(`INSERT OR REPLACE INTO artifacts (path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at)
|
|
601
|
+
VALUES (:path, :artifact_type, :milestone_id, :slice_id, :task_id, :full_content, :imported_at)`)
|
|
602
|
+
.run({
|
|
603
|
+
":path": a.path,
|
|
604
|
+
":artifact_type": a.artifact_type,
|
|
605
|
+
":milestone_id": a.milestone_id,
|
|
606
|
+
":slice_id": a.slice_id,
|
|
607
|
+
":task_id": a.task_id,
|
|
608
|
+
":full_content": a.full_content,
|
|
609
|
+
":imported_at": new Date().toISOString(),
|
|
589
610
|
});
|
|
590
611
|
}
|
|
612
|
+
// ─── Worktree DB Helpers ──────────────────────────────────────────────────
|
|
613
|
+
export function copyWorktreeDb(srcDbPath, destDbPath) {
|
|
614
|
+
try {
|
|
615
|
+
if (!existsSync(srcDbPath))
|
|
616
|
+
return false;
|
|
617
|
+
const destDir = dirname(destDbPath);
|
|
618
|
+
mkdirSync(destDir, { recursive: true });
|
|
619
|
+
copyFileSync(srcDbPath, destDbPath);
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
622
|
+
catch (err) {
|
|
623
|
+
process.stderr.write(`gsd-db: failed to copy DB to worktree: ${err.message}\n`);
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
|
|
628
|
+
const zero = {
|
|
629
|
+
decisions: 0,
|
|
630
|
+
requirements: 0,
|
|
631
|
+
artifacts: 0,
|
|
632
|
+
conflicts: [],
|
|
633
|
+
};
|
|
634
|
+
if (!existsSync(worktreeDbPath))
|
|
635
|
+
return zero;
|
|
636
|
+
if (worktreeDbPath.includes("'")) {
|
|
637
|
+
process.stderr.write(`gsd-db: worktree DB reconciliation failed: path contains unsafe characters\n`);
|
|
638
|
+
return zero;
|
|
639
|
+
}
|
|
640
|
+
if (!currentDb) {
|
|
641
|
+
const opened = openDatabase(mainDbPath);
|
|
642
|
+
if (!opened) {
|
|
643
|
+
process.stderr.write(`gsd-db: worktree DB reconciliation failed: cannot open main DB\n`);
|
|
644
|
+
return zero;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
const adapter = currentDb;
|
|
648
|
+
const conflicts = [];
|
|
649
|
+
try {
|
|
650
|
+
adapter.exec(`ATTACH DATABASE '${worktreeDbPath}' AS wt`);
|
|
651
|
+
try {
|
|
652
|
+
const decConf = adapter
|
|
653
|
+
.prepare(`SELECT m.id FROM decisions m INNER JOIN wt.decisions w ON m.id = w.id WHERE m.decision != w.decision OR m.choice != w.choice OR m.rationale != w.rationale OR m.superseded_by IS NOT w.superseded_by`)
|
|
654
|
+
.all();
|
|
655
|
+
for (const row of decConf)
|
|
656
|
+
conflicts.push(`decision ${row["id"]}: modified in both`);
|
|
657
|
+
const reqConf = adapter
|
|
658
|
+
.prepare(`SELECT m.id FROM requirements m INNER JOIN wt.requirements w ON m.id = w.id WHERE m.description != w.description OR m.status != w.status OR m.notes != w.notes OR m.superseded_by IS NOT w.superseded_by`)
|
|
659
|
+
.all();
|
|
660
|
+
for (const row of reqConf)
|
|
661
|
+
conflicts.push(`requirement ${row["id"]}: modified in both`);
|
|
662
|
+
const merged = { decisions: 0, requirements: 0, artifacts: 0 };
|
|
663
|
+
adapter.exec("BEGIN");
|
|
664
|
+
try {
|
|
665
|
+
const dR = adapter
|
|
666
|
+
.prepare(`
|
|
667
|
+
INSERT OR REPLACE INTO decisions (
|
|
668
|
+
id, when_context, scope, decision, choice, rationale, revisable, superseded_by
|
|
669
|
+
)
|
|
670
|
+
SELECT
|
|
671
|
+
id, when_context, scope, decision, choice, rationale, revisable, superseded_by
|
|
672
|
+
FROM wt.decisions
|
|
673
|
+
`)
|
|
674
|
+
.run();
|
|
675
|
+
merged.decisions =
|
|
676
|
+
typeof dR === "object" && dR !== null
|
|
677
|
+
? (dR.changes ?? 0)
|
|
678
|
+
: 0;
|
|
679
|
+
const rR = adapter
|
|
680
|
+
.prepare(`
|
|
681
|
+
INSERT OR REPLACE INTO requirements (
|
|
682
|
+
id, class, status, description, why, source, primary_owner,
|
|
683
|
+
supporting_slices, validation, notes, full_content, superseded_by
|
|
684
|
+
)
|
|
685
|
+
SELECT
|
|
686
|
+
id, class, status, description, why, source, primary_owner,
|
|
687
|
+
supporting_slices, validation, notes, full_content, superseded_by
|
|
688
|
+
FROM wt.requirements
|
|
689
|
+
`)
|
|
690
|
+
.run();
|
|
691
|
+
merged.requirements =
|
|
692
|
+
typeof rR === "object" && rR !== null
|
|
693
|
+
? (rR.changes ?? 0)
|
|
694
|
+
: 0;
|
|
695
|
+
const aR = adapter
|
|
696
|
+
.prepare(`
|
|
697
|
+
INSERT OR REPLACE INTO artifacts (
|
|
698
|
+
path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at
|
|
699
|
+
)
|
|
700
|
+
SELECT
|
|
701
|
+
path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at
|
|
702
|
+
FROM wt.artifacts
|
|
703
|
+
`)
|
|
704
|
+
.run();
|
|
705
|
+
merged.artifacts =
|
|
706
|
+
typeof aR === "object" && aR !== null
|
|
707
|
+
? (aR.changes ?? 0)
|
|
708
|
+
: 0;
|
|
709
|
+
adapter.exec("COMMIT");
|
|
710
|
+
}
|
|
711
|
+
catch (txErr) {
|
|
712
|
+
try {
|
|
713
|
+
adapter.exec("ROLLBACK");
|
|
714
|
+
}
|
|
715
|
+
catch {
|
|
716
|
+
/* best-effort */
|
|
717
|
+
}
|
|
718
|
+
throw txErr;
|
|
719
|
+
}
|
|
720
|
+
return { ...merged, conflicts };
|
|
721
|
+
}
|
|
722
|
+
finally {
|
|
723
|
+
try {
|
|
724
|
+
adapter.exec("DETACH DATABASE wt");
|
|
725
|
+
}
|
|
726
|
+
catch {
|
|
727
|
+
/* best-effort */
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
catch (err) {
|
|
732
|
+
process.stderr.write(`gsd-db: worktree DB reconciliation failed: ${err.message}\n`);
|
|
733
|
+
return { ...zero, conflicts };
|
|
734
|
+
}
|
|
735
|
+
}
|