@yemi33/minions 0.1.2370 → 0.1.2371
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/bin/minions.js +38 -11
- package/dashboard/js/render-other.js +2 -30
- package/dashboard/js/render-work-items.js +0 -34
- package/dashboard/js/settings.js +12 -27
- package/dashboard/pages/tools.html +1 -1
- package/dashboard.js +80 -257
- package/docs/README.md +2 -3
- package/docs/architecture.excalidraw +2 -2
- package/docs/auto-discovery.md +3 -3
- package/docs/completion-reports.md +0 -121
- package/docs/copilot-cli-schema.md +9 -17
- package/docs/deprecated.json +0 -51
- package/docs/design-state-storage.md +4 -4
- package/docs/harness-propagation.md +33 -263
- package/docs/human-vs-automated.md +1 -1
- package/docs/named-agents.md +0 -2
- package/docs/plan-lifecycle.md +5 -6
- package/docs/qa-runbook-lifecycle.md +10 -25
- package/docs/shared-lifecycle-module-map.md +2 -10
- package/engine/ado-comment.js +5 -11
- package/engine/ado.js +10 -5
- package/engine/cleanup.js +16 -36
- package/engine/cli.js +13 -175
- package/engine/comment-format.js +8 -182
- package/engine/db/index.js +60 -10
- package/engine/db/migrations/018-sql-only-cutover.js +56 -0
- package/engine/dispatch-store.js +0 -114
- package/engine/dispatch.js +1 -6
- package/engine/gh-comment.js +2 -10
- package/engine/github.js +8 -6
- package/engine/lifecycle.js +58 -173
- package/engine/llm.js +9 -11
- package/engine/managed-spawn.js +1 -6
- package/engine/memory-store.js +8 -6
- package/engine/metrics-store.js +4 -128
- package/engine/pipeline.js +4 -7
- package/engine/playbook.js +8 -196
- package/engine/prd-store.js +115 -141
- package/engine/preflight.js +8 -55
- package/engine/projects.js +34 -41
- package/engine/pull-requests-store.js +9 -234
- package/engine/qa-runs.js +4 -15
- package/engine/qa-sessions.js +5 -17
- package/engine/queries.js +23 -103
- package/engine/routing.js +1 -1
- package/engine/runtimes/claude.js +1 -2
- package/engine/runtimes/codex.js +0 -2
- package/engine/runtimes/copilot.js +1 -4
- package/engine/shared-branch-pr-reconcile.js +2 -5
- package/engine/shared.js +174 -533
- package/engine/small-state-store.js +12 -647
- package/engine/spawn-agent.js +5 -96
- package/engine/state-operations.js +166 -0
- package/engine/supervisor.js +0 -1
- package/engine/watch-actions.js +2 -3
- package/engine/watches-store.js +2 -128
- package/engine/work-items-store.js +3 -254
- package/engine.js +102 -334
- package/package.json +2 -2
- package/playbooks/build-fix-complex.md +0 -4
- package/playbooks/fix.md +0 -4
- package/playbooks/implement-shared.md +0 -4
- package/playbooks/implement.md +0 -4
- package/playbooks/plan-to-prd.md +16 -11
- package/playbooks/plan.md +0 -4
- package/playbooks/review.md +0 -6
- package/playbooks/shared-rules.md +0 -65
- package/docs/harness-transparency.md +0 -199
- package/docs/project-skills.md +0 -193
- package/engine/discover-project-skills.js +0 -673
- package/engine/playbook-intents.js +0 -76
|
@@ -6,14 +6,7 @@
|
|
|
6
6
|
// managed-processes.json <-> managed_processes ({specs:[]} → row-per-name)
|
|
7
7
|
// worktree-pool.json <-> worktree_pool ({entries:[]} → row-per-entry)
|
|
8
8
|
//
|
|
9
|
-
// They share
|
|
10
|
-
// writer, withTransaction wiring) that it's clearer to keep them in one
|
|
11
|
-
// module — each store is small, and the JSON-shape transformations are
|
|
12
|
-
// the only per-file divergence.
|
|
13
|
-
|
|
14
|
-
const fs = require('fs');
|
|
15
|
-
const path = require('path');
|
|
16
|
-
const crypto = require('crypto');
|
|
9
|
+
// They share transaction and JSON-shape transformation infrastructure.
|
|
17
10
|
|
|
18
11
|
function _toMs(v) {
|
|
19
12
|
if (v == null) return null;
|
|
@@ -22,66 +15,10 @@ function _toMs(v) {
|
|
|
22
15
|
return Number.isFinite(parsed) ? parsed : null;
|
|
23
16
|
}
|
|
24
17
|
|
|
25
|
-
function _fileContentHash(filePath) {
|
|
26
|
-
try {
|
|
27
|
-
const buf = fs.readFileSync(filePath);
|
|
28
|
-
return crypto.createHash('sha1').update(buf).digest('hex');
|
|
29
|
-
}
|
|
30
|
-
catch { return null; }
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function _readJson(filePath) {
|
|
34
|
-
let raw;
|
|
35
|
-
try { raw = fs.readFileSync(filePath, 'utf8'); }
|
|
36
|
-
catch { return null; }
|
|
37
|
-
try { return JSON.parse(raw); }
|
|
38
|
-
catch (e) {
|
|
39
|
-
try {
|
|
40
|
-
// eslint-disable-next-line no-console
|
|
41
|
-
console.warn(`[small-state-store] corrupt JSON in ${filePath}: ${e.message}`);
|
|
42
|
-
} catch { /* console wrapped */ }
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function _resolveFilePath(rel) {
|
|
48
|
-
const shared = require('./shared');
|
|
49
|
-
return path.join(shared.MINIONS_DIR, 'engine', rel);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
18
|
// ─── schedule_runs ──────────────────────────────────────────────────────────
|
|
53
19
|
// Shape: { [scheduleId]: { lastRun, lastWorkItemId, lastResult, lastCompletedAt } }
|
|
54
20
|
// SQL: row per scheduleId, data = the value blob.
|
|
55
21
|
|
|
56
|
-
let _scheduleRunsHash = null;
|
|
57
|
-
|
|
58
|
-
function _hydrateScheduleRuns(db) {
|
|
59
|
-
const fp = _resolveFilePath('schedule-runs.json');
|
|
60
|
-
const raw = _readJson(fp) || {};
|
|
61
|
-
db.prepare('DELETE FROM schedule_runs').run();
|
|
62
|
-
const now = Date.now();
|
|
63
|
-
const ins = db.prepare('INSERT INTO schedule_runs (schedule_id, data, updated_at) VALUES (?, ?, ?) ON CONFLICT(schedule_id) DO NOTHING');
|
|
64
|
-
for (const [id, value] of Object.entries(raw)) {
|
|
65
|
-
ins.run(id, JSON.stringify(value), now);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function _resyncScheduleRunsIfDiverged(db) {
|
|
70
|
-
const fp = _resolveFilePath('schedule-runs.json');
|
|
71
|
-
const currentHash = _fileContentHash(fp);
|
|
72
|
-
if (currentHash == null) return;
|
|
73
|
-
if (_scheduleRunsHash != null && currentHash === _scheduleRunsHash) return;
|
|
74
|
-
if (_scheduleRunsHash == null) {
|
|
75
|
-
const sqlHas = db.prepare('SELECT 1 FROM schedule_runs LIMIT 1').get();
|
|
76
|
-
if (sqlHas) {
|
|
77
|
-
_scheduleRunsHash = currentHash;
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
_hydrateScheduleRuns(db);
|
|
82
|
-
_scheduleRunsHash = currentHash;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
22
|
function _readScheduleRunsFromSql(db) {
|
|
86
23
|
const rows = db.prepare('SELECT schedule_id, data FROM schedule_runs').all();
|
|
87
24
|
const out = {};
|
|
@@ -94,15 +31,7 @@ function _readScheduleRunsFromSql(db) {
|
|
|
94
31
|
|
|
95
32
|
function readScheduleRuns() {
|
|
96
33
|
const { getDb } = require('./db');
|
|
97
|
-
|
|
98
|
-
_resyncScheduleRunsIfDiverged(db);
|
|
99
|
-
const out = _readScheduleRunsFromSql(db);
|
|
100
|
-
if (Object.keys(out).length === 0) {
|
|
101
|
-
const fallback = _readJson(_resolveFilePath('schedule-runs.json'));
|
|
102
|
-
if (fallback && Object.keys(fallback).length > 0) return fallback;
|
|
103
|
-
return {};
|
|
104
|
-
}
|
|
105
|
-
return out;
|
|
34
|
+
return _readScheduleRunsFromSql(getDb());
|
|
106
35
|
}
|
|
107
36
|
|
|
108
37
|
function applyScheduleRunsMutation(mutator) {
|
|
@@ -112,7 +41,6 @@ function applyScheduleRunsMutation(mutator) {
|
|
|
112
41
|
catch (e) { throw new Error(`small-state-store: SQLite unavailable (${e.message})`); }
|
|
113
42
|
|
|
114
43
|
return withTransaction(db, () => {
|
|
115
|
-
_resyncScheduleRunsIfDiverged(db);
|
|
116
44
|
const before = _readScheduleRunsFromSql(db);
|
|
117
45
|
const beforeSnap = JSON.parse(JSON.stringify(before));
|
|
118
46
|
const next = mutator(before);
|
|
@@ -140,56 +68,10 @@ function applyScheduleRunsMutation(mutator) {
|
|
|
140
68
|
});
|
|
141
69
|
}
|
|
142
70
|
|
|
143
|
-
function _mirrorScheduleRunsJson(filePath) {
|
|
144
|
-
try {
|
|
145
|
-
const shared = require('./shared');
|
|
146
|
-
const { getDb } = require('./db');
|
|
147
|
-
const obj = _readScheduleRunsFromSql(getDb());
|
|
148
|
-
const target = filePath || _resolveFilePath('schedule-runs.json');
|
|
149
|
-
shared.safeWrite(target, obj);
|
|
150
|
-
const h = _fileContentHash(target);
|
|
151
|
-
if (h != null) _scheduleRunsHash = h;
|
|
152
|
-
} catch { /* mirror best-effort */ }
|
|
153
|
-
}
|
|
154
|
-
|
|
155
71
|
// ─── pipeline_runs ──────────────────────────────────────────────────────────
|
|
156
72
|
// Shape: { [pipelineId]: [run, run, ...] }
|
|
157
73
|
// SQL: row per (pipelineId, runId). Reconstruct on read by grouping.
|
|
158
74
|
|
|
159
|
-
let _pipelineRunsHash = null;
|
|
160
|
-
|
|
161
|
-
function _hydratePipelineRuns(db) {
|
|
162
|
-
const fp = _resolveFilePath('pipeline-runs.json');
|
|
163
|
-
const raw = _readJson(fp) || {};
|
|
164
|
-
db.prepare('DELETE FROM pipeline_runs').run();
|
|
165
|
-
const now = Date.now();
|
|
166
|
-
const ins = db.prepare(`
|
|
167
|
-
INSERT INTO pipeline_runs (pipeline_id, run_id, status, started_at, data, updated_at)
|
|
168
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
169
|
-
ON CONFLICT(pipeline_id, run_id) DO NOTHING
|
|
170
|
-
`);
|
|
171
|
-
for (const [pipelineId, runs] of Object.entries(raw)) {
|
|
172
|
-
if (!Array.isArray(runs)) continue;
|
|
173
|
-
for (const run of runs) {
|
|
174
|
-
if (!run || !run.runId) continue;
|
|
175
|
-
ins.run(pipelineId, String(run.runId), run.status || null, _toMs(run.startedAt), JSON.stringify(run), now);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function _resyncPipelineRunsIfDiverged(db) {
|
|
181
|
-
const fp = _resolveFilePath('pipeline-runs.json');
|
|
182
|
-
const currentHash = _fileContentHash(fp);
|
|
183
|
-
if (currentHash == null) return;
|
|
184
|
-
if (_pipelineRunsHash != null && currentHash === _pipelineRunsHash) return;
|
|
185
|
-
if (_pipelineRunsHash == null) {
|
|
186
|
-
const sqlHas = db.prepare('SELECT 1 FROM pipeline_runs LIMIT 1').get();
|
|
187
|
-
if (sqlHas) { _pipelineRunsHash = currentHash; return; }
|
|
188
|
-
}
|
|
189
|
-
_hydratePipelineRuns(db);
|
|
190
|
-
_pipelineRunsHash = currentHash;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
75
|
function _readPipelineRunsFromSql(db) {
|
|
194
76
|
const rows = db.prepare('SELECT pipeline_id, data FROM pipeline_runs ORDER BY pipeline_id, rowid').all();
|
|
195
77
|
const out = {};
|
|
@@ -205,15 +87,7 @@ function _readPipelineRunsFromSql(db) {
|
|
|
205
87
|
|
|
206
88
|
function readPipelineRuns() {
|
|
207
89
|
const { getDb } = require('./db');
|
|
208
|
-
|
|
209
|
-
_resyncPipelineRunsIfDiverged(db);
|
|
210
|
-
const out = _readPipelineRunsFromSql(db);
|
|
211
|
-
if (Object.keys(out).length === 0) {
|
|
212
|
-
const fallback = _readJson(_resolveFilePath('pipeline-runs.json'));
|
|
213
|
-
if (fallback && Object.keys(fallback).length > 0) return fallback;
|
|
214
|
-
return {};
|
|
215
|
-
}
|
|
216
|
-
return out;
|
|
90
|
+
return _readPipelineRunsFromSql(getDb());
|
|
217
91
|
}
|
|
218
92
|
|
|
219
93
|
function applyPipelineRunsMutation(mutator) {
|
|
@@ -223,7 +97,6 @@ function applyPipelineRunsMutation(mutator) {
|
|
|
223
97
|
catch (e) { throw new Error(`small-state-store: SQLite unavailable (${e.message})`); }
|
|
224
98
|
|
|
225
99
|
return withTransaction(db, () => {
|
|
226
|
-
_resyncPipelineRunsIfDiverged(db);
|
|
227
100
|
const before = _readPipelineRunsFromSql(db);
|
|
228
101
|
const beforeSnap = JSON.parse(JSON.stringify(before));
|
|
229
102
|
const next = mutator(before);
|
|
@@ -270,50 +143,10 @@ function applyPipelineRunsMutation(mutator) {
|
|
|
270
143
|
});
|
|
271
144
|
}
|
|
272
145
|
|
|
273
|
-
function _mirrorPipelineRunsJson(filePath) {
|
|
274
|
-
try {
|
|
275
|
-
const shared = require('./shared');
|
|
276
|
-
const { getDb } = require('./db');
|
|
277
|
-
const obj = _readPipelineRunsFromSql(getDb());
|
|
278
|
-
const target = filePath || _resolveFilePath('pipeline-runs.json');
|
|
279
|
-
shared.safeWrite(target, obj);
|
|
280
|
-
const h = _fileContentHash(target);
|
|
281
|
-
if (h != null) _pipelineRunsHash = h;
|
|
282
|
-
} catch { /* mirror best-effort */ }
|
|
283
|
-
}
|
|
284
|
-
|
|
285
146
|
// ─── managed_processes ─────────────────────────────────────────────────────
|
|
286
147
|
// Shape: { specs: [ {name, ...}, ... ] }
|
|
287
148
|
// SQL: row per name.
|
|
288
149
|
|
|
289
|
-
let _managedProcessesHash = null;
|
|
290
|
-
|
|
291
|
-
function _hydrateManagedProcesses(db) {
|
|
292
|
-
const fp = _resolveFilePath('managed-processes.json');
|
|
293
|
-
const raw = _readJson(fp) || {};
|
|
294
|
-
db.prepare('DELETE FROM managed_processes').run();
|
|
295
|
-
const specs = Array.isArray(raw.specs) ? raw.specs : [];
|
|
296
|
-
const now = Date.now();
|
|
297
|
-
const ins = db.prepare('INSERT INTO managed_processes (name, data, updated_at) VALUES (?, ?, ?) ON CONFLICT(name) DO NOTHING');
|
|
298
|
-
for (const spec of specs) {
|
|
299
|
-
if (!spec || !spec.name) continue;
|
|
300
|
-
ins.run(String(spec.name), JSON.stringify(spec), now);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function _resyncManagedProcessesIfDiverged(db) {
|
|
305
|
-
const fp = _resolveFilePath('managed-processes.json');
|
|
306
|
-
const currentHash = _fileContentHash(fp);
|
|
307
|
-
if (currentHash == null) return;
|
|
308
|
-
if (_managedProcessesHash != null && currentHash === _managedProcessesHash) return;
|
|
309
|
-
if (_managedProcessesHash == null) {
|
|
310
|
-
const sqlHas = db.prepare('SELECT 1 FROM managed_processes LIMIT 1').get();
|
|
311
|
-
if (sqlHas) { _managedProcessesHash = currentHash; return; }
|
|
312
|
-
}
|
|
313
|
-
_hydrateManagedProcesses(db);
|
|
314
|
-
_managedProcessesHash = currentHash;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
150
|
function _readManagedProcessesFromSql(db) {
|
|
318
151
|
const rows = db.prepare('SELECT data FROM managed_processes ORDER BY rowid').all();
|
|
319
152
|
const specs = [];
|
|
@@ -325,15 +158,7 @@ function _readManagedProcessesFromSql(db) {
|
|
|
325
158
|
|
|
326
159
|
function readManagedProcesses() {
|
|
327
160
|
const { getDb } = require('./db');
|
|
328
|
-
|
|
329
|
-
_resyncManagedProcessesIfDiverged(db);
|
|
330
|
-
const out = _readManagedProcessesFromSql(db);
|
|
331
|
-
if (out.specs.length === 0) {
|
|
332
|
-
const fallback = _readJson(_resolveFilePath('managed-processes.json'));
|
|
333
|
-
if (fallback && Array.isArray(fallback.specs) && fallback.specs.length > 0) return fallback;
|
|
334
|
-
return { specs: [] };
|
|
335
|
-
}
|
|
336
|
-
return out;
|
|
161
|
+
return _readManagedProcessesFromSql(getDb());
|
|
337
162
|
}
|
|
338
163
|
|
|
339
164
|
function applyManagedProcessesMutation(mutator) {
|
|
@@ -343,7 +168,6 @@ function applyManagedProcessesMutation(mutator) {
|
|
|
343
168
|
catch (e) { throw new Error(`small-state-store: SQLite unavailable (${e.message})`); }
|
|
344
169
|
|
|
345
170
|
return withTransaction(db, () => {
|
|
346
|
-
_resyncManagedProcessesIfDiverged(db);
|
|
347
171
|
const before = _readManagedProcessesFromSql(db);
|
|
348
172
|
const beforeSnap = JSON.parse(JSON.stringify(before));
|
|
349
173
|
const next = mutator(before);
|
|
@@ -382,56 +206,15 @@ function applyManagedProcessesMutation(mutator) {
|
|
|
382
206
|
});
|
|
383
207
|
}
|
|
384
208
|
|
|
385
|
-
function _mirrorManagedProcessesJson(filePath) {
|
|
386
|
-
try {
|
|
387
|
-
const shared = require('./shared');
|
|
388
|
-
const { getDb } = require('./db');
|
|
389
|
-
const obj = _readManagedProcessesFromSql(getDb());
|
|
390
|
-
const target = filePath || _resolveFilePath('managed-processes.json');
|
|
391
|
-
shared.safeWrite(target, obj);
|
|
392
|
-
const h = _fileContentHash(target);
|
|
393
|
-
if (h != null) _managedProcessesHash = h;
|
|
394
|
-
} catch { /* mirror best-effort */ }
|
|
395
|
-
}
|
|
396
|
-
|
|
397
209
|
// ─── worktree_pool ─────────────────────────────────────────────────────────
|
|
398
210
|
// Shape: { entries: [ {path, ...}, ... ] }
|
|
399
211
|
// SQL: row per entry, keyed by the worktree path (unique).
|
|
400
212
|
|
|
401
|
-
let _worktreePoolHash = null;
|
|
402
|
-
|
|
403
213
|
function _entryKey(entry) {
|
|
404
214
|
if (!entry || typeof entry !== 'object') return null;
|
|
405
215
|
return entry.path || entry.id || null;
|
|
406
216
|
}
|
|
407
217
|
|
|
408
|
-
function _hydrateWorktreePool(db) {
|
|
409
|
-
const fp = _resolveFilePath('worktree-pool.json');
|
|
410
|
-
const raw = _readJson(fp) || {};
|
|
411
|
-
db.prepare('DELETE FROM worktree_pool').run();
|
|
412
|
-
const entries = Array.isArray(raw.entries) ? raw.entries : [];
|
|
413
|
-
const now = Date.now();
|
|
414
|
-
const ins = db.prepare('INSERT INTO worktree_pool (entry_id, data, updated_at) VALUES (?, ?, ?) ON CONFLICT(entry_id) DO NOTHING');
|
|
415
|
-
for (const entry of entries) {
|
|
416
|
-
const id = _entryKey(entry);
|
|
417
|
-
if (!id) continue;
|
|
418
|
-
ins.run(String(id), JSON.stringify(entry), now);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function _resyncWorktreePoolIfDiverged(db) {
|
|
423
|
-
const fp = _resolveFilePath('worktree-pool.json');
|
|
424
|
-
const currentHash = _fileContentHash(fp);
|
|
425
|
-
if (currentHash == null) return;
|
|
426
|
-
if (_worktreePoolHash != null && currentHash === _worktreePoolHash) return;
|
|
427
|
-
if (_worktreePoolHash == null) {
|
|
428
|
-
const sqlHas = db.prepare('SELECT 1 FROM worktree_pool LIMIT 1').get();
|
|
429
|
-
if (sqlHas) { _worktreePoolHash = currentHash; return; }
|
|
430
|
-
}
|
|
431
|
-
_hydrateWorktreePool(db);
|
|
432
|
-
_worktreePoolHash = currentHash;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
218
|
function _readWorktreePoolFromSql(db) {
|
|
436
219
|
const rows = db.prepare('SELECT data FROM worktree_pool ORDER BY rowid').all();
|
|
437
220
|
const entries = [];
|
|
@@ -443,15 +226,7 @@ function _readWorktreePoolFromSql(db) {
|
|
|
443
226
|
|
|
444
227
|
function readWorktreePool() {
|
|
445
228
|
const { getDb } = require('./db');
|
|
446
|
-
|
|
447
|
-
_resyncWorktreePoolIfDiverged(db);
|
|
448
|
-
const out = _readWorktreePoolFromSql(db);
|
|
449
|
-
if (out.entries.length === 0) {
|
|
450
|
-
const fallback = _readJson(_resolveFilePath('worktree-pool.json'));
|
|
451
|
-
if (fallback && Array.isArray(fallback.entries) && fallback.entries.length > 0) return fallback;
|
|
452
|
-
return { entries: [] };
|
|
453
|
-
}
|
|
454
|
-
return out;
|
|
229
|
+
return _readWorktreePoolFromSql(getDb());
|
|
455
230
|
}
|
|
456
231
|
|
|
457
232
|
function applyWorktreePoolMutation(mutator) {
|
|
@@ -461,7 +236,6 @@ function applyWorktreePoolMutation(mutator) {
|
|
|
461
236
|
catch (e) { throw new Error(`small-state-store: SQLite unavailable (${e.message})`); }
|
|
462
237
|
|
|
463
238
|
return withTransaction(db, () => {
|
|
464
|
-
_resyncWorktreePoolIfDiverged(db);
|
|
465
239
|
const before = _readWorktreePoolFromSql(db);
|
|
466
240
|
const beforeSnap = JSON.parse(JSON.stringify(before));
|
|
467
241
|
const next = mutator(before);
|
|
@@ -501,67 +275,12 @@ function applyWorktreePoolMutation(mutator) {
|
|
|
501
275
|
});
|
|
502
276
|
}
|
|
503
277
|
|
|
504
|
-
function _mirrorWorktreePoolJson(filePath) {
|
|
505
|
-
try {
|
|
506
|
-
const shared = require('./shared');
|
|
507
|
-
const { getDb } = require('./db');
|
|
508
|
-
const obj = _readWorktreePoolFromSql(getDb());
|
|
509
|
-
const target = filePath || _resolveFilePath('worktree-pool.json');
|
|
510
|
-
shared.safeWrite(target, obj);
|
|
511
|
-
const h = _fileContentHash(target);
|
|
512
|
-
if (h != null) _worktreePoolHash = h;
|
|
513
|
-
} catch { /* mirror best-effort */ }
|
|
514
|
-
}
|
|
515
|
-
|
|
516
278
|
// ─── qa_runs ───────────────────────────────────────────────────────────────
|
|
517
279
|
// Shape: [ {id, runbookId, targetName, project, workItemId, status, startedAt,
|
|
518
280
|
// completedAt, createdAt, artifacts, summary, ...}, ... ]
|
|
519
281
|
// SQL: row per id, extracted query columns + JSON blob `data`.
|
|
520
282
|
// Pattern: mirrors watches-store (top-level array, id-keyed diff).
|
|
521
283
|
|
|
522
|
-
let _qaRunsHash = null;
|
|
523
|
-
|
|
524
|
-
function _hydrateQaRuns(db) {
|
|
525
|
-
const fp = _resolveFilePath('qa-runs.json');
|
|
526
|
-
const raw = _readJson(fp) || [];
|
|
527
|
-
if (!Array.isArray(raw)) return;
|
|
528
|
-
db.prepare('DELETE FROM qa_runs').run();
|
|
529
|
-
const now = Date.now();
|
|
530
|
-
const ins = db.prepare(`
|
|
531
|
-
INSERT INTO qa_runs (id, runbook_id, target_name, project, work_item_id, status, started_at, completed_at, created_at, data)
|
|
532
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
533
|
-
ON CONFLICT(id) DO NOTHING
|
|
534
|
-
`);
|
|
535
|
-
for (const run of raw) {
|
|
536
|
-
if (!run || !run.id) continue;
|
|
537
|
-
ins.run(
|
|
538
|
-
String(run.id),
|
|
539
|
-
String(run.runbookId || ''),
|
|
540
|
-
String(run.targetName || ''),
|
|
541
|
-
run.project || null,
|
|
542
|
-
run.workItemId || null,
|
|
543
|
-
String(run.status || 'pending'),
|
|
544
|
-
_toMs(run.startedAt),
|
|
545
|
-
_toMs(run.completedAt),
|
|
546
|
-
_toMs(run.createdAt) || now,
|
|
547
|
-
JSON.stringify(run),
|
|
548
|
-
);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
function _resyncQaRunsIfDiverged(db) {
|
|
553
|
-
const fp = _resolveFilePath('qa-runs.json');
|
|
554
|
-
const currentHash = _fileContentHash(fp);
|
|
555
|
-
if (currentHash == null) return;
|
|
556
|
-
if (_qaRunsHash != null && currentHash === _qaRunsHash) return;
|
|
557
|
-
if (_qaRunsHash == null) {
|
|
558
|
-
const sqlHas = db.prepare('SELECT 1 FROM qa_runs LIMIT 1').get();
|
|
559
|
-
if (sqlHas) { _qaRunsHash = currentHash; return; }
|
|
560
|
-
}
|
|
561
|
-
_hydrateQaRuns(db);
|
|
562
|
-
_qaRunsHash = currentHash;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
284
|
function _readQaRunsFromSqlOnly(db) {
|
|
566
285
|
const rows = db.prepare('SELECT data FROM qa_runs ORDER BY created_at, rowid').all();
|
|
567
286
|
const out = [];
|
|
@@ -573,15 +292,7 @@ function _readQaRunsFromSqlOnly(db) {
|
|
|
573
292
|
|
|
574
293
|
function readQaRuns() {
|
|
575
294
|
const { getDb } = require('./db');
|
|
576
|
-
|
|
577
|
-
_resyncQaRunsIfDiverged(db);
|
|
578
|
-
const out = _readQaRunsFromSqlOnly(db);
|
|
579
|
-
if (out.length === 0) {
|
|
580
|
-
const fallback = _readJson(_resolveFilePath('qa-runs.json'));
|
|
581
|
-
if (Array.isArray(fallback) && fallback.length > 0) return fallback;
|
|
582
|
-
return [];
|
|
583
|
-
}
|
|
584
|
-
return out;
|
|
295
|
+
return _readQaRunsFromSqlOnly(getDb());
|
|
585
296
|
}
|
|
586
297
|
|
|
587
298
|
function applyQaRunsMutation(mutator) {
|
|
@@ -591,7 +302,6 @@ function applyQaRunsMutation(mutator) {
|
|
|
591
302
|
catch (e) { throw new Error(`small-state-store: SQLite unavailable (${e.message})`); }
|
|
592
303
|
|
|
593
304
|
return withTransaction(db, () => {
|
|
594
|
-
_resyncQaRunsIfDiverged(db);
|
|
595
305
|
const before = _readQaRunsFromSqlOnly(db);
|
|
596
306
|
const beforeSnap = JSON.parse(JSON.stringify(before));
|
|
597
307
|
const next = mutator(before);
|
|
@@ -651,26 +361,12 @@ function applyQaRunsMutation(mutator) {
|
|
|
651
361
|
});
|
|
652
362
|
}
|
|
653
363
|
|
|
654
|
-
function _mirrorQaRunsJson(filePath) {
|
|
655
|
-
try {
|
|
656
|
-
const shared = require('./shared');
|
|
657
|
-
const { getDb } = require('./db');
|
|
658
|
-
const arr = _readQaRunsFromSqlOnly(getDb());
|
|
659
|
-
const target = filePath || _resolveFilePath('qa-runs.json');
|
|
660
|
-
shared.safeWrite(target, arr);
|
|
661
|
-
const h = _fileContentHash(target);
|
|
662
|
-
if (h != null) _qaRunsHash = h;
|
|
663
|
-
} catch { /* mirror best-effort */ }
|
|
664
|
-
}
|
|
665
|
-
|
|
666
364
|
// ─── qa_sessions ───────────────────────────────────────────────────────────
|
|
667
365
|
// Shape: [ {id, state, spec:{...}, primaryProject, coServices, setupStatus,
|
|
668
366
|
// workItems, managedSpawnName, qaRunId, testFile, summary,
|
|
669
367
|
// failureClass, error, createdAt, updatedAt, completedAt, ...}, ... ]
|
|
670
368
|
// SQL: row per id, extracted query columns + JSON blob.
|
|
671
369
|
|
|
672
|
-
let _qaSessionsHash = null;
|
|
673
|
-
|
|
674
370
|
function _qaSessionPrimaryProject(session) {
|
|
675
371
|
if (!session || typeof session !== 'object') return null;
|
|
676
372
|
if (session.primaryProject) return String(session.primaryProject);
|
|
@@ -681,45 +377,6 @@ function _qaSessionPrimaryProject(session) {
|
|
|
681
377
|
return null;
|
|
682
378
|
}
|
|
683
379
|
|
|
684
|
-
function _hydrateQaSessions(db) {
|
|
685
|
-
const fp = _resolveFilePath('qa-sessions.json');
|
|
686
|
-
const raw = _readJson(fp) || [];
|
|
687
|
-
if (!Array.isArray(raw)) return;
|
|
688
|
-
db.prepare('DELETE FROM qa_sessions').run();
|
|
689
|
-
const now = Date.now();
|
|
690
|
-
const ins = db.prepare(`
|
|
691
|
-
INSERT INTO qa_sessions (id, state, primary_project, qa_run_id, created_at, updated_at, completed_at, data)
|
|
692
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
693
|
-
ON CONFLICT(id) DO NOTHING
|
|
694
|
-
`);
|
|
695
|
-
for (const session of raw) {
|
|
696
|
-
if (!session || !session.id) continue;
|
|
697
|
-
ins.run(
|
|
698
|
-
String(session.id),
|
|
699
|
-
String(session.state || 'pending'),
|
|
700
|
-
_qaSessionPrimaryProject(session),
|
|
701
|
-
session.qaRunId || null,
|
|
702
|
-
_toMs(session.createdAt) || now,
|
|
703
|
-
_toMs(session.updatedAt) || _toMs(session.createdAt) || now,
|
|
704
|
-
_toMs(session.completedAt),
|
|
705
|
-
JSON.stringify(session),
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
function _resyncQaSessionsIfDiverged(db) {
|
|
711
|
-
const fp = _resolveFilePath('qa-sessions.json');
|
|
712
|
-
const currentHash = _fileContentHash(fp);
|
|
713
|
-
if (currentHash == null) return;
|
|
714
|
-
if (_qaSessionsHash != null && currentHash === _qaSessionsHash) return;
|
|
715
|
-
if (_qaSessionsHash == null) {
|
|
716
|
-
const sqlHas = db.prepare('SELECT 1 FROM qa_sessions LIMIT 1').get();
|
|
717
|
-
if (sqlHas) { _qaSessionsHash = currentHash; return; }
|
|
718
|
-
}
|
|
719
|
-
_hydrateQaSessions(db);
|
|
720
|
-
_qaSessionsHash = currentHash;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
380
|
function _readQaSessionsFromSqlOnly(db) {
|
|
724
381
|
const rows = db.prepare('SELECT data FROM qa_sessions ORDER BY created_at, rowid').all();
|
|
725
382
|
const out = [];
|
|
@@ -731,15 +388,7 @@ function _readQaSessionsFromSqlOnly(db) {
|
|
|
731
388
|
|
|
732
389
|
function readQaSessions() {
|
|
733
390
|
const { getDb } = require('./db');
|
|
734
|
-
|
|
735
|
-
_resyncQaSessionsIfDiverged(db);
|
|
736
|
-
const out = _readQaSessionsFromSqlOnly(db);
|
|
737
|
-
if (out.length === 0) {
|
|
738
|
-
const fallback = _readJson(_resolveFilePath('qa-sessions.json'));
|
|
739
|
-
if (Array.isArray(fallback) && fallback.length > 0) return fallback;
|
|
740
|
-
return [];
|
|
741
|
-
}
|
|
742
|
-
return out;
|
|
391
|
+
return _readQaSessionsFromSqlOnly(getDb());
|
|
743
392
|
}
|
|
744
393
|
|
|
745
394
|
function applyQaSessionsMutation(mutator) {
|
|
@@ -749,7 +398,6 @@ function applyQaSessionsMutation(mutator) {
|
|
|
749
398
|
catch (e) { throw new Error(`small-state-store: SQLite unavailable (${e.message})`); }
|
|
750
399
|
|
|
751
400
|
return withTransaction(db, () => {
|
|
752
|
-
_resyncQaSessionsIfDiverged(db);
|
|
753
401
|
const before = _readQaSessionsFromSqlOnly(db);
|
|
754
402
|
const beforeSnap = JSON.parse(JSON.stringify(before));
|
|
755
403
|
const next = mutator(before);
|
|
@@ -805,52 +453,10 @@ function applyQaSessionsMutation(mutator) {
|
|
|
805
453
|
});
|
|
806
454
|
}
|
|
807
455
|
|
|
808
|
-
function _mirrorQaSessionsJson(filePath) {
|
|
809
|
-
try {
|
|
810
|
-
const shared = require('./shared');
|
|
811
|
-
const { getDb } = require('./db');
|
|
812
|
-
const arr = _readQaSessionsFromSqlOnly(getDb());
|
|
813
|
-
const target = filePath || _resolveFilePath('qa-sessions.json');
|
|
814
|
-
shared.safeWrite(target, arr);
|
|
815
|
-
const h = _fileContentHash(target);
|
|
816
|
-
if (h != null) _qaSessionsHash = h;
|
|
817
|
-
} catch { /* mirror best-effort */ }
|
|
818
|
-
}
|
|
819
|
-
|
|
820
456
|
// ─── pr_links ───────────────────────────────────────────────────────────────
|
|
821
457
|
// Shape: { [prId]: <value> } where <value> is typically array of item IDs
|
|
822
458
|
// but may also be a bare string (legacy). Readers normalize.
|
|
823
459
|
|
|
824
|
-
let _prLinksHash = null;
|
|
825
|
-
|
|
826
|
-
function _hydratePrLinks(db) {
|
|
827
|
-
const fp = _resolveFilePath('pr-links.json');
|
|
828
|
-
const raw = _readJson(fp) || {};
|
|
829
|
-
db.prepare('DELETE FROM pr_links').run();
|
|
830
|
-
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return;
|
|
831
|
-
const now = Date.now();
|
|
832
|
-
const ins = db.prepare('INSERT INTO pr_links (pr_id, data, updated_at) VALUES (?, ?, ?) ON CONFLICT(pr_id) DO NOTHING');
|
|
833
|
-
for (const [prId, value] of Object.entries(raw)) {
|
|
834
|
-
ins.run(String(prId), JSON.stringify(value), now);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
function _resyncPrLinksIfDiverged(db) {
|
|
839
|
-
const fp = _resolveFilePath('pr-links.json');
|
|
840
|
-
const currentHash = _fileContentHash(fp);
|
|
841
|
-
if (currentHash == null) return;
|
|
842
|
-
if (_prLinksHash != null && currentHash === _prLinksHash) return;
|
|
843
|
-
if (_prLinksHash == null) {
|
|
844
|
-
const sqlHas = db.prepare('SELECT 1 FROM pr_links LIMIT 1').get();
|
|
845
|
-
if (sqlHas) {
|
|
846
|
-
_prLinksHash = currentHash;
|
|
847
|
-
return;
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
_hydratePrLinks(db);
|
|
851
|
-
_prLinksHash = currentHash;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
460
|
function _readPrLinksFromSql(db) {
|
|
855
461
|
const rows = db.prepare('SELECT pr_id, data FROM pr_links').all();
|
|
856
462
|
const out = {};
|
|
@@ -863,18 +469,7 @@ function _readPrLinksFromSql(db) {
|
|
|
863
469
|
|
|
864
470
|
function readPrLinks() {
|
|
865
471
|
const { getDb } = require('./db');
|
|
866
|
-
|
|
867
|
-
try { _resyncPrLinksIfDiverged(db); }
|
|
868
|
-
catch { /* table may be missing on a stale install — fall back to JSON */ }
|
|
869
|
-
let out;
|
|
870
|
-
try { out = _readPrLinksFromSql(db); }
|
|
871
|
-
catch { return _readJson(_resolveFilePath('pr-links.json')) || {}; }
|
|
872
|
-
if (Object.keys(out).length === 0) {
|
|
873
|
-
const fallback = _readJson(_resolveFilePath('pr-links.json'));
|
|
874
|
-
if (fallback && Object.keys(fallback).length > 0) return fallback;
|
|
875
|
-
return {};
|
|
876
|
-
}
|
|
877
|
-
return out;
|
|
472
|
+
return _readPrLinksFromSql(getDb());
|
|
878
473
|
}
|
|
879
474
|
|
|
880
475
|
function applyPrLinksMutation(mutator) {
|
|
@@ -884,7 +479,6 @@ function applyPrLinksMutation(mutator) {
|
|
|
884
479
|
catch (e) { throw new Error(`small-state-store: SQLite unavailable (${e.message})`); }
|
|
885
480
|
|
|
886
481
|
return withTransaction(db, () => {
|
|
887
|
-
_resyncPrLinksIfDiverged(db);
|
|
888
482
|
const before = _readPrLinksFromSql(db);
|
|
889
483
|
const beforeSnap = JSON.parse(JSON.stringify(before));
|
|
890
484
|
const next = mutator(before);
|
|
@@ -915,49 +509,10 @@ function applyPrLinksMutation(mutator) {
|
|
|
915
509
|
});
|
|
916
510
|
}
|
|
917
511
|
|
|
918
|
-
function _mirrorPrLinksJson(filePath) {
|
|
919
|
-
try {
|
|
920
|
-
const shared = require('./shared');
|
|
921
|
-
const { getDb } = require('./db');
|
|
922
|
-
const obj = _readPrLinksFromSql(getDb());
|
|
923
|
-
const target = filePath || _resolveFilePath('pr-links.json');
|
|
924
|
-
shared.safeWrite(target, obj);
|
|
925
|
-
const h = _fileContentHash(target);
|
|
926
|
-
if (h != null) _prLinksHash = h;
|
|
927
|
-
} catch { /* mirror best-effort */ }
|
|
928
|
-
}
|
|
929
|
-
|
|
930
512
|
// ─── cooldowns ─────────────────────────────────────────────────────────────
|
|
931
513
|
// Shape: { [key]: { timestamp, failures, ... } }
|
|
932
514
|
// SQL: row per key.
|
|
933
515
|
|
|
934
|
-
let _cooldownsHash = null;
|
|
935
|
-
|
|
936
|
-
function _hydrateCooldowns(db) {
|
|
937
|
-
const fp = _resolveFilePath('cooldowns.json');
|
|
938
|
-
const raw = _readJson(fp) || {};
|
|
939
|
-
db.prepare('DELETE FROM cooldowns').run();
|
|
940
|
-
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return;
|
|
941
|
-
const now = Date.now();
|
|
942
|
-
const ins = db.prepare('INSERT INTO cooldowns (key, data, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO NOTHING');
|
|
943
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
944
|
-
ins.run(String(key), JSON.stringify(value), now);
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
function _resyncCooldownsIfDiverged(db) {
|
|
949
|
-
const fp = _resolveFilePath('cooldowns.json');
|
|
950
|
-
const currentHash = _fileContentHash(fp);
|
|
951
|
-
if (currentHash == null) return;
|
|
952
|
-
if (_cooldownsHash != null && currentHash === _cooldownsHash) return;
|
|
953
|
-
if (_cooldownsHash == null) {
|
|
954
|
-
const sqlHas = db.prepare('SELECT 1 FROM cooldowns LIMIT 1').get();
|
|
955
|
-
if (sqlHas) { _cooldownsHash = currentHash; return; }
|
|
956
|
-
}
|
|
957
|
-
_hydrateCooldowns(db);
|
|
958
|
-
_cooldownsHash = currentHash;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
516
|
function _readCooldownsFromSql(db) {
|
|
962
517
|
const rows = db.prepare('SELECT key, data FROM cooldowns').all();
|
|
963
518
|
const out = {};
|
|
@@ -970,18 +525,7 @@ function _readCooldownsFromSql(db) {
|
|
|
970
525
|
|
|
971
526
|
function readCooldowns() {
|
|
972
527
|
const { getDb } = require('./db');
|
|
973
|
-
|
|
974
|
-
try { _resyncCooldownsIfDiverged(db); }
|
|
975
|
-
catch { /* table may be missing on stale install */ }
|
|
976
|
-
let out;
|
|
977
|
-
try { out = _readCooldownsFromSql(db); }
|
|
978
|
-
catch { return _readJson(_resolveFilePath('cooldowns.json')) || {}; }
|
|
979
|
-
if (Object.keys(out).length === 0) {
|
|
980
|
-
const fallback = _readJson(_resolveFilePath('cooldowns.json'));
|
|
981
|
-
if (fallback && Object.keys(fallback).length > 0) return fallback;
|
|
982
|
-
return {};
|
|
983
|
-
}
|
|
984
|
-
return out;
|
|
528
|
+
return _readCooldownsFromSql(getDb());
|
|
985
529
|
}
|
|
986
530
|
|
|
987
531
|
function applyCooldownsMutation(mutator) {
|
|
@@ -991,7 +535,6 @@ function applyCooldownsMutation(mutator) {
|
|
|
991
535
|
catch (e) { throw new Error(`small-state-store: SQLite unavailable (${e.message})`); }
|
|
992
536
|
|
|
993
537
|
return withTransaction(db, () => {
|
|
994
|
-
_resyncCooldownsIfDiverged(db);
|
|
995
538
|
const before = _readCooldownsFromSql(db);
|
|
996
539
|
const beforeSnap = JSON.parse(JSON.stringify(before));
|
|
997
540
|
const next = mutator(before);
|
|
@@ -1022,51 +565,11 @@ function applyCooldownsMutation(mutator) {
|
|
|
1022
565
|
});
|
|
1023
566
|
}
|
|
1024
567
|
|
|
1025
|
-
function _mirrorCooldownsJson(filePath) {
|
|
1026
|
-
try {
|
|
1027
|
-
const shared = require('./shared');
|
|
1028
|
-
const { getDb } = require('./db');
|
|
1029
|
-
const obj = _readCooldownsFromSql(getDb());
|
|
1030
|
-
const target = filePath || _resolveFilePath('cooldowns.json');
|
|
1031
|
-
shared.safeWrite(target, obj);
|
|
1032
|
-
const h = _fileContentHash(target);
|
|
1033
|
-
if (h != null) _cooldownsHash = h;
|
|
1034
|
-
} catch { /* mirror best-effort */ }
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
568
|
// ─── pending_rebases ───────────────────────────────────────────────────────
|
|
1038
569
|
// Shape: [ { prId, branch, projectName, mergedItemId, queuedAt, attempts, ... }, ... ]
|
|
1039
570
|
// SQL: row per array entry (rowid auto-increment preserves insertion order).
|
|
1040
571
|
// No stable natural key, so mutations are full clear+insert.
|
|
1041
572
|
|
|
1042
|
-
let _pendingRebasesHash = null;
|
|
1043
|
-
|
|
1044
|
-
function _hydratePendingRebases(db) {
|
|
1045
|
-
const fp = _resolveFilePath('pending-rebases.json');
|
|
1046
|
-
const raw = _readJson(fp) || [];
|
|
1047
|
-
db.prepare('DELETE FROM pending_rebases').run();
|
|
1048
|
-
if (!Array.isArray(raw)) return;
|
|
1049
|
-
const now = Date.now();
|
|
1050
|
-
const ins = db.prepare('INSERT INTO pending_rebases (data, updated_at) VALUES (?, ?)');
|
|
1051
|
-
for (const entry of raw) {
|
|
1052
|
-
if (!entry || typeof entry !== 'object') continue;
|
|
1053
|
-
ins.run(JSON.stringify(entry), now);
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
function _resyncPendingRebasesIfDiverged(db) {
|
|
1058
|
-
const fp = _resolveFilePath('pending-rebases.json');
|
|
1059
|
-
const currentHash = _fileContentHash(fp);
|
|
1060
|
-
if (currentHash == null) return;
|
|
1061
|
-
if (_pendingRebasesHash != null && currentHash === _pendingRebasesHash) return;
|
|
1062
|
-
if (_pendingRebasesHash == null) {
|
|
1063
|
-
const sqlHas = db.prepare('SELECT 1 FROM pending_rebases LIMIT 1').get();
|
|
1064
|
-
if (sqlHas) { _pendingRebasesHash = currentHash; return; }
|
|
1065
|
-
}
|
|
1066
|
-
_hydratePendingRebases(db);
|
|
1067
|
-
_pendingRebasesHash = currentHash;
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
573
|
function _readPendingRebasesFromSql(db) {
|
|
1071
574
|
const rows = db.prepare('SELECT data FROM pending_rebases ORDER BY seq').all();
|
|
1072
575
|
const out = [];
|
|
@@ -1079,18 +582,7 @@ function _readPendingRebasesFromSql(db) {
|
|
|
1079
582
|
|
|
1080
583
|
function readPendingRebases() {
|
|
1081
584
|
const { getDb } = require('./db');
|
|
1082
|
-
|
|
1083
|
-
try { _resyncPendingRebasesIfDiverged(db); }
|
|
1084
|
-
catch { /* table missing */ }
|
|
1085
|
-
let out;
|
|
1086
|
-
try { out = _readPendingRebasesFromSql(db); }
|
|
1087
|
-
catch { return _readJson(_resolveFilePath('pending-rebases.json')) || []; }
|
|
1088
|
-
if (out.length === 0) {
|
|
1089
|
-
const fallback = _readJson(_resolveFilePath('pending-rebases.json'));
|
|
1090
|
-
if (Array.isArray(fallback) && fallback.length > 0) return fallback;
|
|
1091
|
-
return [];
|
|
1092
|
-
}
|
|
1093
|
-
return out;
|
|
585
|
+
return _readPendingRebasesFromSql(getDb());
|
|
1094
586
|
}
|
|
1095
587
|
|
|
1096
588
|
function applyPendingRebasesMutation(mutator) {
|
|
@@ -1100,7 +592,6 @@ function applyPendingRebasesMutation(mutator) {
|
|
|
1100
592
|
catch (e) { throw new Error(`small-state-store: SQLite unavailable (${e.message})`); }
|
|
1101
593
|
|
|
1102
594
|
return withTransaction(db, () => {
|
|
1103
|
-
_resyncPendingRebasesIfDiverged(db);
|
|
1104
595
|
const before = _readPendingRebasesFromSql(db);
|
|
1105
596
|
const beforeSnap = JSON.parse(JSON.stringify(before));
|
|
1106
597
|
const next = mutator(before);
|
|
@@ -1123,50 +614,10 @@ function applyPendingRebasesMutation(mutator) {
|
|
|
1123
614
|
});
|
|
1124
615
|
}
|
|
1125
616
|
|
|
1126
|
-
function _mirrorPendingRebasesJson(filePath) {
|
|
1127
|
-
try {
|
|
1128
|
-
const shared = require('./shared');
|
|
1129
|
-
const { getDb } = require('./db');
|
|
1130
|
-
const arr = _readPendingRebasesFromSql(getDb());
|
|
1131
|
-
const target = filePath || _resolveFilePath('pending-rebases.json');
|
|
1132
|
-
shared.safeWrite(target, arr);
|
|
1133
|
-
const h = _fileContentHash(target);
|
|
1134
|
-
if (h != null) _pendingRebasesHash = h;
|
|
1135
|
-
} catch { /* mirror best-effort */ }
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
617
|
// ─── cc_sessions ───────────────────────────────────────────────────────────
|
|
1139
618
|
// Shape: [ { id, sessionId, title, _promptHash, lastActiveAt, ... }, ... ]
|
|
1140
619
|
// SQL: row per id (each entry has a unique `id` tab key).
|
|
1141
620
|
|
|
1142
|
-
let _ccSessionsHash = null;
|
|
1143
|
-
|
|
1144
|
-
function _hydrateCcSessions(db) {
|
|
1145
|
-
const fp = _resolveFilePath('cc-sessions.json');
|
|
1146
|
-
const raw = _readJson(fp) || [];
|
|
1147
|
-
db.prepare('DELETE FROM cc_sessions').run();
|
|
1148
|
-
if (!Array.isArray(raw)) return;
|
|
1149
|
-
const now = Date.now();
|
|
1150
|
-
const ins = db.prepare('INSERT INTO cc_sessions (id, data, updated_at) VALUES (?, ?, ?) ON CONFLICT(id) DO NOTHING');
|
|
1151
|
-
for (const entry of raw) {
|
|
1152
|
-
if (!entry || typeof entry !== 'object' || !entry.id) continue;
|
|
1153
|
-
ins.run(String(entry.id), JSON.stringify(entry), now);
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
function _resyncCcSessionsIfDiverged(db) {
|
|
1158
|
-
const fp = _resolveFilePath('cc-sessions.json');
|
|
1159
|
-
const currentHash = _fileContentHash(fp);
|
|
1160
|
-
if (currentHash == null) return;
|
|
1161
|
-
if (_ccSessionsHash != null && currentHash === _ccSessionsHash) return;
|
|
1162
|
-
if (_ccSessionsHash == null) {
|
|
1163
|
-
const sqlHas = db.prepare('SELECT 1 FROM cc_sessions LIMIT 1').get();
|
|
1164
|
-
if (sqlHas) { _ccSessionsHash = currentHash; return; }
|
|
1165
|
-
}
|
|
1166
|
-
_hydrateCcSessions(db);
|
|
1167
|
-
_ccSessionsHash = currentHash;
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
621
|
function _readCcSessionsFromSql(db) {
|
|
1171
622
|
const rows = db.prepare('SELECT data FROM cc_sessions ORDER BY updated_at, id').all();
|
|
1172
623
|
const out = [];
|
|
@@ -1179,18 +630,7 @@ function _readCcSessionsFromSql(db) {
|
|
|
1179
630
|
|
|
1180
631
|
function readCcSessions() {
|
|
1181
632
|
const { getDb } = require('./db');
|
|
1182
|
-
|
|
1183
|
-
try { _resyncCcSessionsIfDiverged(db); }
|
|
1184
|
-
catch { /* table missing */ }
|
|
1185
|
-
let out;
|
|
1186
|
-
try { out = _readCcSessionsFromSql(db); }
|
|
1187
|
-
catch { return _readJson(_resolveFilePath('cc-sessions.json')) || []; }
|
|
1188
|
-
if (out.length === 0) {
|
|
1189
|
-
const fallback = _readJson(_resolveFilePath('cc-sessions.json'));
|
|
1190
|
-
if (Array.isArray(fallback) && fallback.length > 0) return fallback;
|
|
1191
|
-
return [];
|
|
1192
|
-
}
|
|
1193
|
-
return out;
|
|
633
|
+
return _readCcSessionsFromSql(getDb());
|
|
1194
634
|
}
|
|
1195
635
|
|
|
1196
636
|
function applyCcSessionsMutation(mutator) {
|
|
@@ -1200,7 +640,6 @@ function applyCcSessionsMutation(mutator) {
|
|
|
1200
640
|
catch (e) { throw new Error(`small-state-store: SQLite unavailable (${e.message})`); }
|
|
1201
641
|
|
|
1202
642
|
return withTransaction(db, () => {
|
|
1203
|
-
_resyncCcSessionsIfDiverged(db);
|
|
1204
643
|
const before = _readCcSessionsFromSql(db);
|
|
1205
644
|
const beforeSnap = JSON.parse(JSON.stringify(before));
|
|
1206
645
|
const next = mutator(before);
|
|
@@ -1232,49 +671,10 @@ function applyCcSessionsMutation(mutator) {
|
|
|
1232
671
|
});
|
|
1233
672
|
}
|
|
1234
673
|
|
|
1235
|
-
function _mirrorCcSessionsJson(filePath) {
|
|
1236
|
-
try {
|
|
1237
|
-
const shared = require('./shared');
|
|
1238
|
-
const { getDb } = require('./db');
|
|
1239
|
-
const arr = _readCcSessionsFromSql(getDb());
|
|
1240
|
-
const target = filePath || _resolveFilePath('cc-sessions.json');
|
|
1241
|
-
shared.safeWrite(target, arr);
|
|
1242
|
-
const h = _fileContentHash(target);
|
|
1243
|
-
if (h != null) _ccSessionsHash = h;
|
|
1244
|
-
} catch { /* mirror best-effort */ }
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
674
|
// ─── doc_sessions ──────────────────────────────────────────────────────────
|
|
1248
675
|
// Shape: { [filePath]: { sessionId, lastActiveAt, turnCount, ... } }
|
|
1249
676
|
// SQL: row per filePath key.
|
|
1250
677
|
|
|
1251
|
-
let _docSessionsHash = null;
|
|
1252
|
-
|
|
1253
|
-
function _hydrateDocSessions(db) {
|
|
1254
|
-
const fp = _resolveFilePath('doc-sessions.json');
|
|
1255
|
-
const raw = _readJson(fp) || {};
|
|
1256
|
-
db.prepare('DELETE FROM doc_sessions').run();
|
|
1257
|
-
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return;
|
|
1258
|
-
const now = Date.now();
|
|
1259
|
-
const ins = db.prepare('INSERT INTO doc_sessions (key, data, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO NOTHING');
|
|
1260
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
1261
|
-
ins.run(String(key), JSON.stringify(value), now);
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
function _resyncDocSessionsIfDiverged(db) {
|
|
1266
|
-
const fp = _resolveFilePath('doc-sessions.json');
|
|
1267
|
-
const currentHash = _fileContentHash(fp);
|
|
1268
|
-
if (currentHash == null) return;
|
|
1269
|
-
if (_docSessionsHash != null && currentHash === _docSessionsHash) return;
|
|
1270
|
-
if (_docSessionsHash == null) {
|
|
1271
|
-
const sqlHas = db.prepare('SELECT 1 FROM doc_sessions LIMIT 1').get();
|
|
1272
|
-
if (sqlHas) { _docSessionsHash = currentHash; return; }
|
|
1273
|
-
}
|
|
1274
|
-
_hydrateDocSessions(db);
|
|
1275
|
-
_docSessionsHash = currentHash;
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
678
|
function _readDocSessionsFromSql(db) {
|
|
1279
679
|
const rows = db.prepare('SELECT key, data FROM doc_sessions').all();
|
|
1280
680
|
const out = {};
|
|
@@ -1287,18 +687,7 @@ function _readDocSessionsFromSql(db) {
|
|
|
1287
687
|
|
|
1288
688
|
function readDocSessions() {
|
|
1289
689
|
const { getDb } = require('./db');
|
|
1290
|
-
|
|
1291
|
-
try { _resyncDocSessionsIfDiverged(db); }
|
|
1292
|
-
catch { /* table missing */ }
|
|
1293
|
-
let out;
|
|
1294
|
-
try { out = _readDocSessionsFromSql(db); }
|
|
1295
|
-
catch { return _readJson(_resolveFilePath('doc-sessions.json')) || {}; }
|
|
1296
|
-
if (Object.keys(out).length === 0) {
|
|
1297
|
-
const fallback = _readJson(_resolveFilePath('doc-sessions.json'));
|
|
1298
|
-
if (fallback && Object.keys(fallback).length > 0) return fallback;
|
|
1299
|
-
return {};
|
|
1300
|
-
}
|
|
1301
|
-
return out;
|
|
690
|
+
return _readDocSessionsFromSql(getDb());
|
|
1302
691
|
}
|
|
1303
692
|
|
|
1304
693
|
function applyDocSessionsMutation(mutator) {
|
|
@@ -1308,7 +697,6 @@ function applyDocSessionsMutation(mutator) {
|
|
|
1308
697
|
catch (e) { throw new Error(`small-state-store: SQLite unavailable (${e.message})`); }
|
|
1309
698
|
|
|
1310
699
|
return withTransaction(db, () => {
|
|
1311
|
-
_resyncDocSessionsIfDiverged(db);
|
|
1312
700
|
const before = _readDocSessionsFromSql(db);
|
|
1313
701
|
const beforeSnap = JSON.parse(JSON.stringify(before));
|
|
1314
702
|
const next = mutator(before);
|
|
@@ -1339,61 +727,38 @@ function applyDocSessionsMutation(mutator) {
|
|
|
1339
727
|
});
|
|
1340
728
|
}
|
|
1341
729
|
|
|
1342
|
-
function _mirrorDocSessionsJson(filePath) {
|
|
1343
|
-
try {
|
|
1344
|
-
const shared = require('./shared');
|
|
1345
|
-
const { getDb } = require('./db');
|
|
1346
|
-
const obj = _readDocSessionsFromSql(getDb());
|
|
1347
|
-
const target = filePath || _resolveFilePath('doc-sessions.json');
|
|
1348
|
-
shared.safeWrite(target, obj);
|
|
1349
|
-
const h = _fileContentHash(target);
|
|
1350
|
-
if (h != null) _docSessionsHash = h;
|
|
1351
|
-
} catch { /* mirror best-effort */ }
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
730
|
module.exports = {
|
|
1355
731
|
// schedule_runs
|
|
1356
732
|
readScheduleRuns,
|
|
1357
733
|
applyScheduleRunsMutation,
|
|
1358
|
-
_mirrorScheduleRunsJson,
|
|
1359
734
|
// pipeline_runs
|
|
1360
735
|
readPipelineRuns,
|
|
1361
736
|
applyPipelineRunsMutation,
|
|
1362
|
-
_mirrorPipelineRunsJson,
|
|
1363
737
|
// managed_processes
|
|
1364
738
|
readManagedProcesses,
|
|
1365
739
|
applyManagedProcessesMutation,
|
|
1366
|
-
_mirrorManagedProcessesJson,
|
|
1367
740
|
// worktree_pool
|
|
1368
741
|
readWorktreePool,
|
|
1369
742
|
applyWorktreePoolMutation,
|
|
1370
|
-
_mirrorWorktreePoolJson,
|
|
1371
743
|
// qa_runs
|
|
1372
744
|
readQaRuns,
|
|
1373
745
|
applyQaRunsMutation,
|
|
1374
|
-
_mirrorQaRunsJson,
|
|
1375
746
|
// qa_sessions
|
|
1376
747
|
readQaSessions,
|
|
1377
748
|
applyQaSessionsMutation,
|
|
1378
|
-
_mirrorQaSessionsJson,
|
|
1379
749
|
// pr_links
|
|
1380
750
|
readPrLinks,
|
|
1381
751
|
applyPrLinksMutation,
|
|
1382
|
-
_mirrorPrLinksJson,
|
|
1383
752
|
// cooldowns
|
|
1384
753
|
readCooldowns,
|
|
1385
754
|
applyCooldownsMutation,
|
|
1386
|
-
_mirrorCooldownsJson,
|
|
1387
755
|
// pending_rebases
|
|
1388
756
|
readPendingRebases,
|
|
1389
757
|
applyPendingRebasesMutation,
|
|
1390
|
-
_mirrorPendingRebasesJson,
|
|
1391
758
|
// cc_sessions
|
|
1392
759
|
readCcSessions,
|
|
1393
760
|
applyCcSessionsMutation,
|
|
1394
|
-
_mirrorCcSessionsJson,
|
|
1395
761
|
// doc_sessions
|
|
1396
762
|
readDocSessions,
|
|
1397
763
|
applyDocSessionsMutation,
|
|
1398
|
-
_mirrorDocSessionsJson,
|
|
1399
764
|
};
|