@yemi33/minions 0.1.2116 → 0.1.2117
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/engine/db/index.js +39 -10
- package/engine/dispatch-store.js +1 -3
- package/engine/dispatch.js +24 -59
- package/engine/pull-requests-store.js +3 -25
- package/engine/shared.js +105 -191
- package/engine/small-state-store.js +11 -33
- package/engine/work-items-store.js +6 -33
- package/package.json +1 -1
package/engine/db/index.js
CHANGED
|
@@ -88,16 +88,45 @@ function getDb() {
|
|
|
88
88
|
try {
|
|
89
89
|
_installExperimentalWarningFilter();
|
|
90
90
|
const { DatabaseSync } = require('node:sqlite');
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
// Concurrent cold-openers (parallel child processes in tests; engine
|
|
92
|
+
// + dashboard cold-starting together) can race on the exclusive lock
|
|
93
|
+
// that SQLite needs to either create the file or upgrade
|
|
94
|
+
// journal_mode to WAL on the first connection. `busy_timeout` only
|
|
95
|
+
// applies to PRAGMAs and queries AFTER the connection is open, so
|
|
96
|
+
// `new DatabaseSync(dbPath)` itself and the initial WAL PRAGMA can
|
|
97
|
+
// still return SQLITE_BUSY. Retry the whole open+PRAGMA sequence
|
|
98
|
+
// with backoff so a brief lock contention isn't a hard failure.
|
|
99
|
+
// Phase 9.4 removed the JSON fallback that previously hid this
|
|
100
|
+
// race — without retry, parallel test runs fail intermittently with
|
|
101
|
+
// "small-state-store: SQLite unavailable (... database is locked)".
|
|
102
|
+
const maxAttempts = 20;
|
|
103
|
+
let lastErr = null;
|
|
104
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
105
|
+
try {
|
|
106
|
+
_db = new DatabaseSync(dbPath);
|
|
107
|
+
// busy_timeout MUST be set before any other PRAGMA so subsequent
|
|
108
|
+
// statements wait instead of failing immediately on contention.
|
|
109
|
+
_db.exec('PRAGMA busy_timeout = 5000');
|
|
110
|
+
_db.exec('PRAGMA journal_mode = WAL');
|
|
111
|
+
_db.exec('PRAGMA foreign_keys = ON');
|
|
112
|
+
_db.exec('PRAGMA synchronous = NORMAL');
|
|
113
|
+
_dbPath = dbPath;
|
|
114
|
+
lastErr = null;
|
|
115
|
+
break;
|
|
116
|
+
} catch (e) {
|
|
117
|
+
lastErr = e;
|
|
118
|
+
const msg = String(e && e.message || '');
|
|
119
|
+
const isBusy = /database is locked|SQLITE_BUSY|SQLITE_LOCKED/i.test(msg);
|
|
120
|
+
if (!isBusy || attempt === maxAttempts - 1) throw e;
|
|
121
|
+
if (_db) { try { _db.close(); } catch { /* best-effort */ } _db = null; }
|
|
122
|
+
// Synchronous backoff: 10ms, 20ms, 40ms... capped at 250ms.
|
|
123
|
+
// Total worst-case wait ~3s, still under the 5s busy_timeout.
|
|
124
|
+
const delayMs = Math.min(250, 10 * (1 << Math.min(attempt, 5)));
|
|
125
|
+
const end = Date.now() + delayMs;
|
|
126
|
+
while (Date.now() < end) { /* busy-wait — sub-300ms total */ }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (lastErr) throw lastErr;
|
|
101
130
|
const { runMigrations } = require('./migrate');
|
|
102
131
|
runMigrations(_db);
|
|
103
132
|
return _db;
|
package/engine/dispatch-store.js
CHANGED
|
@@ -36,9 +36,7 @@ function _parseRow(row) {
|
|
|
36
36
|
|
|
37
37
|
function readDispatchSectioned() {
|
|
38
38
|
const { getDb } = require('./db');
|
|
39
|
-
|
|
40
|
-
try { db = getDb(); }
|
|
41
|
-
catch { return _readDispatchJsonFallback(); } // SQLite unavailable
|
|
39
|
+
const db = getDb();
|
|
42
40
|
|
|
43
41
|
// All rows, all statuses. Matches the legacy dispatch.json reader's
|
|
44
42
|
// semantics — returns the complete dispatch state regardless of how old
|
package/engine/dispatch.js
CHANGED
|
@@ -10,11 +10,11 @@ const queries = require('./queries');
|
|
|
10
10
|
const { setCooldown, setCooldownFailure } = require('./cooldown');
|
|
11
11
|
const dispatchEvents = require('./dispatch-events');
|
|
12
12
|
|
|
13
|
-
const { safeJsonArr,
|
|
13
|
+
const { safeJsonArr, mutateWorkItems,
|
|
14
14
|
mutatePullRequests, getProjects, projectPrPath, log, ts, dateStamp,
|
|
15
15
|
sidecarDispatchPrompt, deleteDispatchPromptSidecar,
|
|
16
16
|
WI_STATUS, WORK_TYPE, DISPATCH_RESULT, ENGINE_DEFAULTS, AGENT_STATUS, FAILURE_CLASS, PR_STATUS } = shared;
|
|
17
|
-
const { getConfig,
|
|
17
|
+
const { getConfig, INBOX_DIR } = queries;
|
|
18
18
|
|
|
19
19
|
const MINIONS_DIR = shared.MINIONS_DIR;
|
|
20
20
|
|
|
@@ -58,72 +58,37 @@ function _sidecarChangedPrompts(dispatch, prevSnap) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
// SQL-backed mutator (Phase
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
// Fallback path: if SQLite is unavailable (Node < 22.5, the DB init failed,
|
|
70
|
-
// the table doesn't exist yet because the migration hasn't run) we fall
|
|
71
|
-
// back to the legacy mutateJsonFileLocked flow against dispatch.json.
|
|
72
|
-
// The migration leaves dispatch.json.pre-sql-<ts> on disk, so an operator
|
|
73
|
-
// pinning to an older release can rename it back.
|
|
61
|
+
// SQL-backed mutator (Phase 9.4 SQL-only). mutator receives a
|
|
62
|
+
// `{ pending, active, completed, review }` object, mutates in place
|
|
63
|
+
// (or returns a replacement), and the changes land transactionally in
|
|
64
|
+
// the dispatches table. The diff-then-apply trick in dispatch-store
|
|
65
|
+
// preserves every field via the `data` JSON column, so new dispatch
|
|
66
|
+
// fields the engine adds in future commits don't need any migration —
|
|
67
|
+
// they round-trip automatically. SQLite failures propagate; the CLI
|
|
68
|
+
// shim in bin/minions.js guarantees node:sqlite is loadable.
|
|
74
69
|
function mutateDispatch(mutator) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const store = require('./dispatch-store');
|
|
78
|
-
const { wrote, result } = store.applyDispatchMutation((dispatch) => {
|
|
79
|
-
dispatch.pending = Array.isArray(dispatch.pending) ? dispatch.pending : [];
|
|
80
|
-
dispatch.active = Array.isArray(dispatch.active) ? dispatch.active : [];
|
|
81
|
-
dispatch.completed = Array.isArray(dispatch.completed) ? dispatch.completed : [];
|
|
82
|
-
dispatch.review = Array.isArray(dispatch.review) ? dispatch.review : [];
|
|
83
|
-
const prevSnap = _snapshotPrompts(dispatch);
|
|
84
|
-
const next = mutator(dispatch) ?? dispatch;
|
|
85
|
-
// Prompt-size guard: only scan items whose prompt changed (or new items),
|
|
86
|
-
// so a 100-item status-flip doesn't re-byte-count every prompt.
|
|
87
|
-
_sidecarChangedPrompts(next, prevSnap);
|
|
88
|
-
return next;
|
|
89
|
-
});
|
|
90
|
-
if (wrote) {
|
|
91
|
-
try { require('./queries').invalidateDispatchCache(); } catch {}
|
|
92
|
-
try { require('./db-events').emitStateEvent('dispatch'); } catch { /* optional */ }
|
|
93
|
-
// Mirror back to dispatch.json for tests + tools that fs.readFileSync
|
|
94
|
-
// the file directly. SQL is the source of truth; the JSON file is
|
|
95
|
-
// regenerated from SQL on every successful mutation, never independently
|
|
96
|
-
// mutated. Cheap to delete once those direct-JSON readers are gone.
|
|
97
|
-
try { store._mirrorJsonFromSql(); } catch { /* best-effort */ }
|
|
98
|
-
}
|
|
99
|
-
return result;
|
|
100
|
-
} catch (e) {
|
|
101
|
-
// Only fall back if the failure looks like a "DB not available" case,
|
|
102
|
-
// not a programming error. Surface mutator exceptions to the caller.
|
|
103
|
-
if (e && /SQLite unavailable|no such table|node:sqlite/.test(String(e.message))) {
|
|
104
|
-
// fall through to legacy JSON path below
|
|
105
|
-
} else {
|
|
106
|
-
throw e;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Legacy JSON path (pre-Phase 1 fallback). Same code as before.
|
|
111
|
-
const defaultDispatch = { pending: [], active: [], completed: [] };
|
|
112
|
-
const result = mutateJsonFileLocked(DISPATCH_PATH, (dispatch) => {
|
|
70
|
+
const store = require('./dispatch-store');
|
|
71
|
+
const { wrote, result } = store.applyDispatchMutation((dispatch) => {
|
|
113
72
|
dispatch.pending = Array.isArray(dispatch.pending) ? dispatch.pending : [];
|
|
114
73
|
dispatch.active = Array.isArray(dispatch.active) ? dispatch.active : [];
|
|
115
74
|
dispatch.completed = Array.isArray(dispatch.completed) ? dispatch.completed : [];
|
|
75
|
+
dispatch.review = Array.isArray(dispatch.review) ? dispatch.review : [];
|
|
116
76
|
const prevSnap = _snapshotPrompts(dispatch);
|
|
117
77
|
const next = mutator(dispatch) ?? dispatch;
|
|
78
|
+
// Prompt-size guard: only scan items whose prompt changed (or new items),
|
|
79
|
+
// so a 100-item status-flip doesn't re-byte-count every prompt.
|
|
118
80
|
_sidecarChangedPrompts(next, prevSnap);
|
|
119
81
|
return next;
|
|
120
|
-
}, {
|
|
121
|
-
defaultValue: defaultDispatch,
|
|
122
|
-
onWrote: () => {
|
|
123
|
-
try { require('./db-events').emitStateEvent('dispatch'); } catch { /* optional */ }
|
|
124
|
-
},
|
|
125
82
|
});
|
|
126
|
-
|
|
83
|
+
if (wrote) {
|
|
84
|
+
try { require('./queries').invalidateDispatchCache(); } catch {}
|
|
85
|
+
try { require('./db-events').emitStateEvent('dispatch'); } catch { /* optional */ }
|
|
86
|
+
// Mirror back to dispatch.json for tests + tools that fs.readFileSync
|
|
87
|
+
// the file directly. SQL is the source of truth; the JSON file is
|
|
88
|
+
// regenerated from SQL on every successful mutation, never independently
|
|
89
|
+
// mutated. Cheap to delete once those direct-JSON readers are gone.
|
|
90
|
+
try { store._mirrorJsonFromSql(); } catch { /* best-effort */ }
|
|
91
|
+
}
|
|
127
92
|
return result;
|
|
128
93
|
}
|
|
129
94
|
|
|
@@ -123,9 +123,7 @@ function _resyncScopeIfJsonDiverged(db, scope) {
|
|
|
123
123
|
|
|
124
124
|
function readPullRequestsForScope(scope) {
|
|
125
125
|
const { getDb } = require('./db');
|
|
126
|
-
|
|
127
|
-
try { db = getDb(); }
|
|
128
|
-
catch { return _readJsonArrayFallback(scope); }
|
|
126
|
+
const db = getDb();
|
|
129
127
|
|
|
130
128
|
_resyncScopeIfJsonDiverged(db, scope);
|
|
131
129
|
|
|
@@ -170,23 +168,7 @@ function _enumerateJsonScopes() {
|
|
|
170
168
|
|
|
171
169
|
function readAllPullRequests() {
|
|
172
170
|
const { getDb } = require('./db');
|
|
173
|
-
|
|
174
|
-
try { db = getDb(); }
|
|
175
|
-
catch {
|
|
176
|
-
// Issue #3035: SQLite unavailable (Node 22.x without
|
|
177
|
-
// --experimental-sqlite) — read every JSON scope on disk so the
|
|
178
|
-
// aggregate readers (queries.getPullRequests, shared.getPrLinks)
|
|
179
|
-
// continue to return real data instead of [].
|
|
180
|
-
const out = [];
|
|
181
|
-
for (const scope of _enumerateJsonScopes()) {
|
|
182
|
-
for (const pr of _readJsonArrayFallback(scope)) {
|
|
183
|
-
if (!pr || typeof pr !== 'object') continue;
|
|
184
|
-
pr._scope = scope;
|
|
185
|
-
out.push(pr);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return out;
|
|
189
|
-
}
|
|
171
|
+
const db = getDb();
|
|
190
172
|
|
|
191
173
|
try { _resyncScopeIfJsonDiverged(db, 'central'); } catch {}
|
|
192
174
|
const knownScopes = db.prepare('SELECT DISTINCT scope FROM pull_requests').all().map(r => r.scope);
|
|
@@ -273,11 +255,7 @@ function _applyPullRequestsDiff(db, diff) {
|
|
|
273
255
|
|
|
274
256
|
function applyPullRequestsMutation(scope, mutator) {
|
|
275
257
|
const { getDb, withTransaction } = require('./db');
|
|
276
|
-
|
|
277
|
-
try { db = getDb(); }
|
|
278
|
-
catch (e) {
|
|
279
|
-
throw new Error(`engine/pull-requests-store: SQLite unavailable (${e.message}); cannot mutate pull_requests`);
|
|
280
|
-
}
|
|
258
|
+
const db = getDb();
|
|
281
259
|
|
|
282
260
|
return withTransaction(db, () => {
|
|
283
261
|
_resyncScopeIfJsonDiverged(db, scope);
|
package/engine/shared.js
CHANGED
|
@@ -591,9 +591,11 @@ function _routeJsonReadToSql(p) {
|
|
|
591
591
|
const store = require('./pull-requests-store');
|
|
592
592
|
return { value: store.readPullRequestsForScope('central') };
|
|
593
593
|
}
|
|
594
|
-
} catch {
|
|
595
|
-
//
|
|
596
|
-
|
|
594
|
+
} catch (e) {
|
|
595
|
+
// Phase 9.4: store/load failures (not SQLite-unavailable — the CLI shim
|
|
596
|
+
// in bin/minions.js guarantees node:sqlite is loadable) propagate up so
|
|
597
|
+
// the caller can decide whether to retry or surface.
|
|
598
|
+
throw e;
|
|
597
599
|
}
|
|
598
600
|
return null;
|
|
599
601
|
}
|
|
@@ -1373,34 +1375,38 @@ function _tryRouteMutateToSql(filePath, mutateFn, onWrote) {
|
|
|
1373
1375
|
// Don't hijack writes to ad-hoc paths that happen to share the basename.
|
|
1374
1376
|
if (smallRoute) {
|
|
1375
1377
|
if (!fpNorm.endsWith('/engine/' + baseName)) return null;
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1378
|
+
const store = require('./small-state-store');
|
|
1379
|
+
// Hold the JSON file lock across the SQL transaction AND the mirror
|
|
1380
|
+
// write. The SQL transaction itself is cross-process serialized via
|
|
1381
|
+
// BEGIN IMMEDIATE inside the SQL store, but the JSON mirror is a
|
|
1382
|
+
// separate read-from-SQL → atomic-rename that can race across
|
|
1383
|
+
// processes: an earlier mirror's stale snapshot can land AFTER a
|
|
1384
|
+
// later mirror's complete snapshot and lose committed rows from the
|
|
1385
|
+
// on-disk JSON file. Pre-Phase-9.4 this was masked by the JSON
|
|
1386
|
+
// fallback (concurrent writers serialized through the same lock at
|
|
1387
|
+
// the bottom of mutateJsonFileLocked); after the fallback removal,
|
|
1388
|
+
// the bare mirror races. Locking around the SQL+mirror block puts
|
|
1389
|
+
// those two operations back into one cross-process critical section.
|
|
1390
|
+
const lockPath = `${filePath}.lock`;
|
|
1391
|
+
return withFileLock(lockPath, () => {
|
|
1379
1392
|
const out = store[smallRoute.fn]((data) => {
|
|
1380
1393
|
const next = mutateFn(data);
|
|
1381
1394
|
if (onWrote) onWrote();
|
|
1382
1395
|
return next;
|
|
1383
1396
|
});
|
|
1384
|
-
result = out && Object.prototype.hasOwnProperty.call(out, 'result') ? out.result : undefined;
|
|
1397
|
+
const result = out && Object.prototype.hasOwnProperty.call(out, 'result') ? out.result : undefined;
|
|
1385
1398
|
try { store[smallRoute.mirror](filePath); } catch { /* mirror best-effort */ }
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1399
|
+
if (!fs.existsSync(filePath)) {
|
|
1400
|
+
try {
|
|
1401
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1402
|
+
const fallback = smallRoute.defaultShape === 'array'
|
|
1403
|
+
? (Array.isArray(result) ? result : [])
|
|
1404
|
+
: (result && typeof result === 'object' && !Array.isArray(result) ? result : {});
|
|
1405
|
+
safeWrite(filePath, fallback);
|
|
1406
|
+
} catch { /* best-effort */ }
|
|
1391
1407
|
}
|
|
1392
|
-
|
|
1393
|
-
}
|
|
1394
|
-
if (!fs.existsSync(filePath)) {
|
|
1395
|
-
try {
|
|
1396
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1397
|
-
const fallback = smallRoute.defaultShape === 'array'
|
|
1398
|
-
? (Array.isArray(result) ? result : [])
|
|
1399
|
-
: (result && typeof result === 'object' && !Array.isArray(result) ? result : {});
|
|
1400
|
-
safeWrite(filePath, fallback);
|
|
1401
|
-
} catch { /* best-effort */ }
|
|
1402
|
-
}
|
|
1403
|
-
return { routed: result };
|
|
1408
|
+
return { routed: result };
|
|
1409
|
+
});
|
|
1404
1410
|
}
|
|
1405
1411
|
|
|
1406
1412
|
let result;
|
|
@@ -3206,32 +3212,20 @@ const WATCH_ACTION_TYPE = {
|
|
|
3206
3212
|
|
|
3207
3213
|
/**
|
|
3208
3214
|
* Phase 7 — small state file mutators. Each routes through the
|
|
3209
|
-
* small-state-store
|
|
3210
|
-
* event on real writes.
|
|
3211
|
-
*
|
|
3215
|
+
* small-state-store and mirrors back to its JSON file, then emits a topic
|
|
3216
|
+
* event on real writes. SQL-canonical (Phase 9.4): SQLite failures
|
|
3217
|
+
* propagate; the CLI shim in bin/minions.js guarantees `node:sqlite` is
|
|
3218
|
+
* loadable on every supported Node version.
|
|
3212
3219
|
*/
|
|
3213
|
-
function _smallStateMutator({ filePath, applyMutation, mirror, topic
|
|
3220
|
+
function _smallStateMutator({ filePath, applyMutation, mirror, topic }) {
|
|
3214
3221
|
return (mutator) => {
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
if (
|
|
3219
|
-
|
|
3220
|
-
try { require('./db-events').emitStateEvent(topic); } catch { /* optional */ }
|
|
3221
|
-
}
|
|
3222
|
-
return result;
|
|
3223
|
-
} catch (e) {
|
|
3224
|
-
if (!e || !/SQLite unavailable|no such table|node:sqlite/.test(String(e.message))) throw e;
|
|
3222
|
+
const store = require('./small-state-store');
|
|
3223
|
+
const { wrote, result } = store[applyMutation]((obj) => mutator(obj) || obj);
|
|
3224
|
+
if (wrote) {
|
|
3225
|
+
try { if (mirror && typeof store[mirror] === 'function') store[mirror](filePath); } catch { /* mirror best-effort */ }
|
|
3226
|
+
try { require('./db-events').emitStateEvent(topic); } catch { /* optional */ }
|
|
3225
3227
|
}
|
|
3226
|
-
return
|
|
3227
|
-
if (data == null) data = defaultValue();
|
|
3228
|
-
return mutator(data) || data;
|
|
3229
|
-
}, {
|
|
3230
|
-
defaultValue: defaultValue(),
|
|
3231
|
-
onWrote: () => {
|
|
3232
|
-
try { require('./db-events').emitStateEvent(topic); } catch { /* optional */ }
|
|
3233
|
-
},
|
|
3234
|
-
});
|
|
3228
|
+
return result;
|
|
3235
3229
|
};
|
|
3236
3230
|
}
|
|
3237
3231
|
|
|
@@ -3287,31 +3281,18 @@ function _qaDualWriteEnabled() {
|
|
|
3287
3281
|
function _qaMutator({ filePath, applyMutation, mirror, topic }) {
|
|
3288
3282
|
return (mutator) => {
|
|
3289
3283
|
return withFileLock(filePath + '.lock', () => {
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
if (
|
|
3297
|
-
if (
|
|
3298
|
-
try { if (mirror && typeof store[mirror] === 'function') store[mirror](filePath); } catch { /* mirror best-effort */ }
|
|
3299
|
-
}
|
|
3300
|
-
try { require('./db-events').emitStateEvent(topic); } catch { /* optional */ }
|
|
3284
|
+
const store = require('./small-state-store');
|
|
3285
|
+
const { wrote, result } = store[applyMutation]((arr) => {
|
|
3286
|
+
if (!Array.isArray(arr)) arr = [];
|
|
3287
|
+
return mutator(arr) || arr;
|
|
3288
|
+
});
|
|
3289
|
+
if (wrote) {
|
|
3290
|
+
if (_qaDualWriteEnabled()) {
|
|
3291
|
+
try { if (mirror && typeof store[mirror] === 'function') store[mirror](filePath); } catch { /* mirror best-effort */ }
|
|
3301
3292
|
}
|
|
3302
|
-
|
|
3303
|
-
} catch (e) {
|
|
3304
|
-
if (!e || !/SQLite unavailable|no such table|node:sqlite/.test(String(e.message))) throw e;
|
|
3305
|
-
return mutateJsonFileLocked(filePath, (data) => {
|
|
3306
|
-
if (!Array.isArray(data)) data = [];
|
|
3307
|
-
return mutator(data) || data;
|
|
3308
|
-
}, {
|
|
3309
|
-
defaultValue: [],
|
|
3310
|
-
onWrote: () => {
|
|
3311
|
-
try { require('./db-events').emitStateEvent(topic); } catch { /* optional */ }
|
|
3312
|
-
},
|
|
3313
|
-
});
|
|
3293
|
+
try { require('./db-events').emitStateEvent(topic); } catch { /* optional */ }
|
|
3314
3294
|
}
|
|
3295
|
+
return result;
|
|
3315
3296
|
}, { timeoutMs: 5000, retries: 3 });
|
|
3316
3297
|
};
|
|
3317
3298
|
}
|
|
@@ -3334,36 +3315,20 @@ const mutateQaSessions = _qaMutator({
|
|
|
3334
3315
|
* Route a watches mutation through the SQL store. Same shape as
|
|
3335
3316
|
* mutateWorkItems / mutatePullRequests: mutator receives the watches
|
|
3336
3317
|
* array, mutates in place or returns a replacement, and the store
|
|
3337
|
-
* diffs by id.
|
|
3338
|
-
* SQLite failure.
|
|
3318
|
+
* diffs by id. SQL-canonical (Phase 9.4); SQLite failures propagate.
|
|
3339
3319
|
*/
|
|
3340
3320
|
function mutateWatches(mutator) {
|
|
3341
3321
|
const watchesPath = path.join(MINIONS_DIR, 'engine', 'watches.json');
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
return mutator(arr) || arr;
|
|
3347
|
-
});
|
|
3348
|
-
if (wrote) {
|
|
3349
|
-
try { store._mirrorJsonFromSql(watchesPath); } catch { /* mirror best-effort */ }
|
|
3350
|
-
try { require('./db-events').emitStateEvent('watches'); } catch { /* optional */ }
|
|
3351
|
-
}
|
|
3352
|
-
return result;
|
|
3353
|
-
} catch (e) {
|
|
3354
|
-
if (!e || !/SQLite unavailable|no such table|node:sqlite/.test(String(e.message))) {
|
|
3355
|
-
throw e;
|
|
3356
|
-
}
|
|
3357
|
-
}
|
|
3358
|
-
return mutateJsonFileLocked(watchesPath, (data) => {
|
|
3359
|
-
if (!Array.isArray(data)) data = [];
|
|
3360
|
-
return mutator(data) || data;
|
|
3361
|
-
}, {
|
|
3362
|
-
defaultValue: [],
|
|
3363
|
-
onWrote: () => {
|
|
3364
|
-
try { require('./db-events').emitStateEvent('watches'); } catch { /* optional */ }
|
|
3365
|
-
},
|
|
3322
|
+
const store = require('./watches-store');
|
|
3323
|
+
const { wrote, result } = store.applyWatchesMutation((arr) => {
|
|
3324
|
+
if (!Array.isArray(arr)) arr = [];
|
|
3325
|
+
return mutator(arr) || arr;
|
|
3366
3326
|
});
|
|
3327
|
+
if (wrote) {
|
|
3328
|
+
try { store._mirrorJsonFromSql(watchesPath); } catch { /* mirror best-effort */ }
|
|
3329
|
+
try { require('./db-events').emitStateEvent('watches'); } catch { /* optional */ }
|
|
3330
|
+
}
|
|
3331
|
+
return result;
|
|
3367
3332
|
}
|
|
3368
3333
|
|
|
3369
3334
|
/**
|
|
@@ -3371,37 +3336,20 @@ function mutateWatches(mutator) {
|
|
|
3371
3336
|
* mirror. Same shape as mutateWorkItems / mutatePullRequests: mutator
|
|
3372
3337
|
* receives the full legacy-shape metrics object, mutates in place or
|
|
3373
3338
|
* returns a replacement, and the store diffs by (kind, key) row.
|
|
3374
|
-
*
|
|
3375
|
-
* Falls back to the legacy mutateJsonFileLocked path on SQLite failure
|
|
3376
|
-
* so a node:sqlite-broken install keeps recording metrics.
|
|
3339
|
+
* SQL-canonical (Phase 9.4); SQLite failures propagate.
|
|
3377
3340
|
*/
|
|
3378
3341
|
function mutateMetrics(mutator) {
|
|
3379
3342
|
const metricsPath = path.join(MINIONS_DIR, 'engine', 'metrics.json');
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
return mutator(m) || m;
|
|
3385
|
-
});
|
|
3386
|
-
if (wrote) {
|
|
3387
|
-
try { store._mirrorJsonFromSql(metricsPath); } catch { /* mirror best-effort */ }
|
|
3388
|
-
try { require('./db-events').emitStateEvent('metrics'); } catch { /* optional */ }
|
|
3389
|
-
}
|
|
3390
|
-
return result;
|
|
3391
|
-
} catch (e) {
|
|
3392
|
-
if (!e || !/SQLite unavailable|no such table|node:sqlite/.test(String(e.message))) {
|
|
3393
|
-
throw e;
|
|
3394
|
-
}
|
|
3395
|
-
}
|
|
3396
|
-
return mutateJsonFileLocked(metricsPath, (metrics) => {
|
|
3397
|
-
if (!metrics || typeof metrics !== 'object') metrics = {};
|
|
3398
|
-
return mutator(metrics) || metrics;
|
|
3399
|
-
}, {
|
|
3400
|
-
defaultValue: {},
|
|
3401
|
-
onWrote: () => {
|
|
3402
|
-
try { require('./db-events').emitStateEvent('metrics'); } catch { /* optional */ }
|
|
3403
|
-
},
|
|
3343
|
+
const store = require('./metrics-store');
|
|
3344
|
+
const { wrote, result } = store.applyMetricsMutation((m) => {
|
|
3345
|
+
if (!m || typeof m !== 'object') m = {};
|
|
3346
|
+
return mutator(m) || m;
|
|
3404
3347
|
});
|
|
3348
|
+
if (wrote) {
|
|
3349
|
+
try { store._mirrorJsonFromSql(metricsPath); } catch { /* mirror best-effort */ }
|
|
3350
|
+
try { require('./db-events').emitStateEvent('metrics'); } catch { /* optional */ }
|
|
3351
|
+
}
|
|
3352
|
+
return result;
|
|
3405
3353
|
}
|
|
3406
3354
|
|
|
3407
3355
|
/** Update per-agent review metrics (prsApproved/prsRejected). Only writes for configured agents. */
|
|
@@ -5167,23 +5115,11 @@ function addPrLink(prId, itemId, { project = null, url = '', prNumber = null } =
|
|
|
5167
5115
|
links[effectivePrId] = [...mergedCurrent];
|
|
5168
5116
|
return links;
|
|
5169
5117
|
};
|
|
5170
|
-
// Phase 9.
|
|
5171
|
-
// is a
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
try {
|
|
5175
|
-
const store = require('./small-state-store');
|
|
5176
|
-
store.applyPrLinksMutation(mutator);
|
|
5177
|
-
try { store._mirrorPrLinksJson(); } catch { /* mirror best-effort */ }
|
|
5178
|
-
routedViaSql = true;
|
|
5179
|
-
} catch (e) {
|
|
5180
|
-
if (!e || !/SQLite unavailable|no such table|node:sqlite/.test(String(e.message))) {
|
|
5181
|
-
throw e;
|
|
5182
|
-
}
|
|
5183
|
-
}
|
|
5184
|
-
if (!routedViaSql) {
|
|
5185
|
-
mutateJsonFileLocked(PR_LINKS_PATH, mutator, { defaultValue: {} });
|
|
5186
|
-
}
|
|
5118
|
+
// Phase 9.4: pr-links is SQL-only via small-state-store; the JSON file
|
|
5119
|
+
// is a write-only mirror artifact for legacy direct-disk readers.
|
|
5120
|
+
const store = require('./small-state-store');
|
|
5121
|
+
store.applyPrLinksMutation(mutator);
|
|
5122
|
+
try { store._mirrorPrLinksJson(); } catch { /* mirror best-effort */ }
|
|
5187
5123
|
|
|
5188
5124
|
if (!project) return;
|
|
5189
5125
|
const prPath = projectPrPath(project);
|
|
@@ -5575,42 +5511,27 @@ function listProcessReachable(rootPids, allProcesses = null) {
|
|
|
5575
5511
|
* @param {Function} mutator - Receives the array, mutates in place or returns new value
|
|
5576
5512
|
*/
|
|
5577
5513
|
function mutateWorkItems(filePath, mutator) {
|
|
5578
|
-
// Phase
|
|
5579
|
-
//
|
|
5580
|
-
//
|
|
5581
|
-
//
|
|
5582
|
-
//
|
|
5583
|
-
// derived from the file path's last two segments. Ad-hoc file paths
|
|
5584
|
-
// outside the MINIONS_DIR layout (e.g. tests using createTmpDir()) can't
|
|
5585
|
-
// be mapped to a stable scope, so we short-circuit to the legacy
|
|
5586
|
-
// JSON path for those. Production callers always use
|
|
5587
|
-
// shared.projectWorkItemsPath(p) / MINIONS_DIR/work-items.json.
|
|
5588
|
-
//
|
|
5589
|
-
// SQLite failures fall through to the legacy JSON path below — keeps a
|
|
5590
|
-
// node:sqlite-broken install fully functional.
|
|
5514
|
+
// Phase 9.4 SQL-only. Route through work-items-store; SQL is the canonical
|
|
5515
|
+
// (and only) store. The legacy JSON path below remains ONLY for ad-hoc
|
|
5516
|
+
// file paths outside the MINIONS_DIR layout (e.g. tests using
|
|
5517
|
+
// createTmpDir()) that can't be mapped to a stable scope. Production
|
|
5518
|
+
// callers always use shared.projectWorkItemsPath(p) / MINIONS_DIR/work-items.json.
|
|
5591
5519
|
const fpNorm = String(filePath).replace(/\\/g, '/');
|
|
5592
5520
|
const minionsNorm = String(MINIONS_DIR).replace(/\\/g, '/');
|
|
5593
5521
|
const insideMinionsDir = fpNorm.startsWith(minionsNorm + '/') || fpNorm === minionsNorm + '/work-items.json';
|
|
5594
5522
|
if (insideMinionsDir) {
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
try { require('./db-events').emitStateEvent('work_items'); } catch { /* optional */ }
|
|
5605
|
-
}
|
|
5606
|
-
try { require('./queries').invalidateWorkItemsCache(); } catch { /* queries not loaded */ }
|
|
5607
|
-
return result;
|
|
5608
|
-
} catch (e) {
|
|
5609
|
-
if (!e || !/SQLite unavailable|no such table|node:sqlite/.test(String(e.message))) {
|
|
5610
|
-
throw e;
|
|
5611
|
-
}
|
|
5612
|
-
// Fall through to the legacy JSON path on SQLite errors only.
|
|
5523
|
+
const store = require('./work-items-store');
|
|
5524
|
+
const scope = store.scopeForFilePath(filePath);
|
|
5525
|
+
const { wrote, result } = store.applyWorkItemsMutation(scope, (items) => {
|
|
5526
|
+
if (!Array.isArray(items)) items = [];
|
|
5527
|
+
return mutator(items) || items;
|
|
5528
|
+
});
|
|
5529
|
+
if (wrote) {
|
|
5530
|
+
try { store._mirrorJsonFromSql(scope, filePath); } catch { /* mirror best-effort */ }
|
|
5531
|
+
try { require('./db-events').emitStateEvent('work_items'); } catch { /* optional */ }
|
|
5613
5532
|
}
|
|
5533
|
+
try { require('./queries').invalidateWorkItemsCache(); } catch { /* queries not loaded */ }
|
|
5534
|
+
return result;
|
|
5614
5535
|
}
|
|
5615
5536
|
|
|
5616
5537
|
const result = mutateJsonFileLocked(filePath, (data) => {
|
|
@@ -5648,31 +5569,24 @@ function reopenWorkItem(wi) {
|
|
|
5648
5569
|
* @param {Function} mutator - Receives the array, mutates in place or returns new value
|
|
5649
5570
|
*/
|
|
5650
5571
|
function mutatePullRequests(filePath, mutator) {
|
|
5651
|
-
// Phase
|
|
5652
|
-
//
|
|
5653
|
-
//
|
|
5572
|
+
// Phase 9.4 SQL-only. Route through pull-requests-store when filePath sits
|
|
5573
|
+
// under MINIONS_DIR. Ad-hoc tmp paths (legacy tests using createTmpDir)
|
|
5574
|
+
// still fall through to the JSON path.
|
|
5654
5575
|
const fpNorm = String(filePath).replace(/\\/g, '/');
|
|
5655
5576
|
const minionsNorm = String(MINIONS_DIR).replace(/\\/g, '/');
|
|
5656
5577
|
const insideMinionsDir = fpNorm.startsWith(minionsNorm + '/') || fpNorm === minionsNorm + '/pull-requests.json';
|
|
5657
5578
|
if (insideMinionsDir) {
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
try { require('./db-events').emitStateEvent('pull_requests'); } catch { /* optional */ }
|
|
5668
|
-
}
|
|
5669
|
-
return result;
|
|
5670
|
-
} catch (e) {
|
|
5671
|
-
if (!e || !/SQLite unavailable|no such table|node:sqlite/.test(String(e.message))) {
|
|
5672
|
-
throw e;
|
|
5673
|
-
}
|
|
5674
|
-
// Fall through to legacy JSON path on SQLite errors only.
|
|
5579
|
+
const store = require('./pull-requests-store');
|
|
5580
|
+
const scope = store.scopeForFilePath(filePath);
|
|
5581
|
+
const { wrote, result } = store.applyPullRequestsMutation(scope, (prs) => {
|
|
5582
|
+
if (!Array.isArray(prs)) prs = [];
|
|
5583
|
+
return mutator(prs) || prs;
|
|
5584
|
+
});
|
|
5585
|
+
if (wrote) {
|
|
5586
|
+
try { store._mirrorJsonFromSql(scope, filePath); } catch { /* mirror best-effort */ }
|
|
5587
|
+
try { require('./db-events').emitStateEvent('pull_requests'); } catch { /* optional */ }
|
|
5675
5588
|
}
|
|
5589
|
+
return result;
|
|
5676
5590
|
}
|
|
5677
5591
|
|
|
5678
5592
|
return mutateJsonFileLocked(filePath, (data) => {
|
|
@@ -94,9 +94,7 @@ function _readScheduleRunsFromSql(db) {
|
|
|
94
94
|
|
|
95
95
|
function readScheduleRuns() {
|
|
96
96
|
const { getDb } = require('./db');
|
|
97
|
-
|
|
98
|
-
try { db = getDb(); }
|
|
99
|
-
catch { return _readJson(_resolveFilePath('schedule-runs.json')) || {}; }
|
|
97
|
+
const db = getDb();
|
|
100
98
|
_resyncScheduleRunsIfDiverged(db);
|
|
101
99
|
const out = _readScheduleRunsFromSql(db);
|
|
102
100
|
if (Object.keys(out).length === 0) {
|
|
@@ -207,9 +205,7 @@ function _readPipelineRunsFromSql(db) {
|
|
|
207
205
|
|
|
208
206
|
function readPipelineRuns() {
|
|
209
207
|
const { getDb } = require('./db');
|
|
210
|
-
|
|
211
|
-
try { db = getDb(); }
|
|
212
|
-
catch { return _readJson(_resolveFilePath('pipeline-runs.json')) || {}; }
|
|
208
|
+
const db = getDb();
|
|
213
209
|
_resyncPipelineRunsIfDiverged(db);
|
|
214
210
|
const out = _readPipelineRunsFromSql(db);
|
|
215
211
|
if (Object.keys(out).length === 0) {
|
|
@@ -329,9 +325,7 @@ function _readManagedProcessesFromSql(db) {
|
|
|
329
325
|
|
|
330
326
|
function readManagedProcesses() {
|
|
331
327
|
const { getDb } = require('./db');
|
|
332
|
-
|
|
333
|
-
try { db = getDb(); }
|
|
334
|
-
catch { return _readJson(_resolveFilePath('managed-processes.json')) || { specs: [] }; }
|
|
328
|
+
const db = getDb();
|
|
335
329
|
_resyncManagedProcessesIfDiverged(db);
|
|
336
330
|
const out = _readManagedProcessesFromSql(db);
|
|
337
331
|
if (out.specs.length === 0) {
|
|
@@ -449,9 +443,7 @@ function _readWorktreePoolFromSql(db) {
|
|
|
449
443
|
|
|
450
444
|
function readWorktreePool() {
|
|
451
445
|
const { getDb } = require('./db');
|
|
452
|
-
|
|
453
|
-
try { db = getDb(); }
|
|
454
|
-
catch { return _readJson(_resolveFilePath('worktree-pool.json')) || { entries: [] }; }
|
|
446
|
+
const db = getDb();
|
|
455
447
|
_resyncWorktreePoolIfDiverged(db);
|
|
456
448
|
const out = _readWorktreePoolFromSql(db);
|
|
457
449
|
if (out.entries.length === 0) {
|
|
@@ -581,9 +573,7 @@ function _readQaRunsFromSqlOnly(db) {
|
|
|
581
573
|
|
|
582
574
|
function readQaRuns() {
|
|
583
575
|
const { getDb } = require('./db');
|
|
584
|
-
|
|
585
|
-
try { db = getDb(); }
|
|
586
|
-
catch { return _readJson(_resolveFilePath('qa-runs.json')) || []; }
|
|
576
|
+
const db = getDb();
|
|
587
577
|
_resyncQaRunsIfDiverged(db);
|
|
588
578
|
const out = _readQaRunsFromSqlOnly(db);
|
|
589
579
|
if (out.length === 0) {
|
|
@@ -741,9 +731,7 @@ function _readQaSessionsFromSqlOnly(db) {
|
|
|
741
731
|
|
|
742
732
|
function readQaSessions() {
|
|
743
733
|
const { getDb } = require('./db');
|
|
744
|
-
|
|
745
|
-
try { db = getDb(); }
|
|
746
|
-
catch { return _readJson(_resolveFilePath('qa-sessions.json')) || []; }
|
|
734
|
+
const db = getDb();
|
|
747
735
|
_resyncQaSessionsIfDiverged(db);
|
|
748
736
|
const out = _readQaSessionsFromSqlOnly(db);
|
|
749
737
|
if (out.length === 0) {
|
|
@@ -875,9 +863,7 @@ function _readPrLinksFromSql(db) {
|
|
|
875
863
|
|
|
876
864
|
function readPrLinks() {
|
|
877
865
|
const { getDb } = require('./db');
|
|
878
|
-
|
|
879
|
-
try { db = getDb(); }
|
|
880
|
-
catch { return _readJson(_resolveFilePath('pr-links.json')) || {}; }
|
|
866
|
+
const db = getDb();
|
|
881
867
|
try { _resyncPrLinksIfDiverged(db); }
|
|
882
868
|
catch { /* table may be missing on a stale install — fall back to JSON */ }
|
|
883
869
|
let out;
|
|
@@ -984,9 +970,7 @@ function _readCooldownsFromSql(db) {
|
|
|
984
970
|
|
|
985
971
|
function readCooldowns() {
|
|
986
972
|
const { getDb } = require('./db');
|
|
987
|
-
|
|
988
|
-
try { db = getDb(); }
|
|
989
|
-
catch { return _readJson(_resolveFilePath('cooldowns.json')) || {}; }
|
|
973
|
+
const db = getDb();
|
|
990
974
|
try { _resyncCooldownsIfDiverged(db); }
|
|
991
975
|
catch { /* table may be missing on stale install */ }
|
|
992
976
|
let out;
|
|
@@ -1095,9 +1079,7 @@ function _readPendingRebasesFromSql(db) {
|
|
|
1095
1079
|
|
|
1096
1080
|
function readPendingRebases() {
|
|
1097
1081
|
const { getDb } = require('./db');
|
|
1098
|
-
|
|
1099
|
-
try { db = getDb(); }
|
|
1100
|
-
catch { return _readJson(_resolveFilePath('pending-rebases.json')) || []; }
|
|
1082
|
+
const db = getDb();
|
|
1101
1083
|
try { _resyncPendingRebasesIfDiverged(db); }
|
|
1102
1084
|
catch { /* table missing */ }
|
|
1103
1085
|
let out;
|
|
@@ -1197,9 +1179,7 @@ function _readCcSessionsFromSql(db) {
|
|
|
1197
1179
|
|
|
1198
1180
|
function readCcSessions() {
|
|
1199
1181
|
const { getDb } = require('./db');
|
|
1200
|
-
|
|
1201
|
-
try { db = getDb(); }
|
|
1202
|
-
catch { return _readJson(_resolveFilePath('cc-sessions.json')) || []; }
|
|
1182
|
+
const db = getDb();
|
|
1203
1183
|
try { _resyncCcSessionsIfDiverged(db); }
|
|
1204
1184
|
catch { /* table missing */ }
|
|
1205
1185
|
let out;
|
|
@@ -1307,9 +1287,7 @@ function _readDocSessionsFromSql(db) {
|
|
|
1307
1287
|
|
|
1308
1288
|
function readDocSessions() {
|
|
1309
1289
|
const { getDb } = require('./db');
|
|
1310
|
-
|
|
1311
|
-
try { db = getDb(); }
|
|
1312
|
-
catch { return _readJson(_resolveFilePath('doc-sessions.json')) || {}; }
|
|
1290
|
+
const db = getDb();
|
|
1313
1291
|
try { _resyncDocSessionsIfDiverged(db); }
|
|
1314
1292
|
catch { /* table missing */ }
|
|
1315
1293
|
let out;
|
|
@@ -75,9 +75,7 @@ function _readJsonArrayFallback(scope) {
|
|
|
75
75
|
|
|
76
76
|
function readWorkItemsForScope(scope) {
|
|
77
77
|
const { getDb } = require('./db');
|
|
78
|
-
|
|
79
|
-
try { db = getDb(); }
|
|
80
|
-
catch { return _readJsonArrayFallback(scope); }
|
|
78
|
+
const db = getDb();
|
|
81
79
|
|
|
82
80
|
_resyncScopeIfJsonDiverged(db, scope);
|
|
83
81
|
|
|
@@ -88,10 +86,9 @@ function readWorkItemsForScope(scope) {
|
|
|
88
86
|
`).all(scope);
|
|
89
87
|
|
|
90
88
|
if (rows.length === 0) {
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
// call sites working without touching every helper.
|
|
89
|
+
// SQL empty AND JSON has content means a test seeded via fs.writeFileSync
|
|
90
|
+
// (legacy helper) or a fresh install pre-migration. Returning the JSON
|
|
91
|
+
// keeps those call sites working without touching every helper.
|
|
95
92
|
const fallback = _readJsonArrayFallback(scope);
|
|
96
93
|
if (fallback.length > 0) return fallback;
|
|
97
94
|
return [];
|
|
@@ -161,29 +158,9 @@ function _enumerateJsonScopes() {
|
|
|
161
158
|
// Read all rows across all scopes — used by queries.getWorkItems which
|
|
162
159
|
// needs to surface central + every project's items in a single shot,
|
|
163
160
|
// tagged with their source scope.
|
|
164
|
-
//
|
|
165
|
-
// Issue #3035: SQL-unavailable installs (Node 22.x without
|
|
166
|
-
// --experimental-sqlite, prior to v0.1.2113) returned [] from this path
|
|
167
|
-
// because `getDb()` throws and the legacy `return null` signal was
|
|
168
|
-
// swallowed by `queries.getWorkItems`'s `|| []`. Mirror the per-scope
|
|
169
|
-
// reader's JSON-fallback shape so the aggregate API stays useful on
|
|
170
|
-
// Node versions that don't have node:sqlite enabled.
|
|
171
161
|
function readAllWorkItems() {
|
|
172
162
|
const { getDb } = require('./db');
|
|
173
|
-
|
|
174
|
-
try { db = getDb(); }
|
|
175
|
-
catch {
|
|
176
|
-
// SQLite unavailable — read every JSON scope on disk directly.
|
|
177
|
-
const out = [];
|
|
178
|
-
for (const scope of _enumerateJsonScopes()) {
|
|
179
|
-
for (const wi of _readJsonArrayFallback(scope)) {
|
|
180
|
-
if (!wi || typeof wi !== 'object') continue;
|
|
181
|
-
wi._source = scope;
|
|
182
|
-
out.push(wi);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return out;
|
|
186
|
-
}
|
|
163
|
+
const db = getDb();
|
|
187
164
|
|
|
188
165
|
// Pick up any external JSON edits for every scope SQL knows about.
|
|
189
166
|
// Also resync 'central' explicitly so first-time reads on a JSON-only
|
|
@@ -326,11 +303,7 @@ function _hydrateScopeFromJson(db, scope) {
|
|
|
326
303
|
|
|
327
304
|
function applyWorkItemsMutation(scope, mutator) {
|
|
328
305
|
const { getDb, withTransaction } = require('./db');
|
|
329
|
-
|
|
330
|
-
try { db = getDb(); }
|
|
331
|
-
catch (e) {
|
|
332
|
-
throw new Error(`engine/work-items-store: SQLite unavailable (${e.message}); cannot mutate work_items`);
|
|
333
|
-
}
|
|
306
|
+
const db = getDb();
|
|
334
307
|
|
|
335
308
|
return withTransaction(db, () => {
|
|
336
309
|
// Re-hydrate SQL from JSON if the file was touched outside the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2117",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|