@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
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
// engine/work-items-store.js — SQL-backed
|
|
2
|
-
// per-file work-items array. Same external contract as the legacy
|
|
3
|
-
// JSON-file reader:
|
|
1
|
+
// engine/work-items-store.js — SQL-backed scoped work-item storage.
|
|
4
2
|
//
|
|
5
3
|
// readWorkItemsForScope(scope) -> [wi, wi, ...]
|
|
6
4
|
// applyWorkItemsMutation(scope, fn) -> runs fn against the array,
|
|
@@ -8,16 +6,11 @@
|
|
|
8
6
|
// transaction, returns
|
|
9
7
|
// { wrote, result }.
|
|
10
8
|
//
|
|
11
|
-
// `scope` is 'central'
|
|
12
|
-
// name for projects/<name>/work-items.json. Mirrors the file convention
|
|
13
|
-
// established by migration 003.
|
|
9
|
+
// `scope` is 'central' or a project name.
|
|
14
10
|
//
|
|
15
11
|
// All callers go through shared.mutateWorkItems and
|
|
16
12
|
// queries.getWorkItems — those are thin wrappers over the helpers here.
|
|
17
13
|
|
|
18
|
-
const path = require('path');
|
|
19
|
-
const fs = require('fs');
|
|
20
|
-
|
|
21
14
|
function _toMs(v) {
|
|
22
15
|
if (v == null) return null;
|
|
23
16
|
if (typeof v === 'number') return Number.isFinite(v) ? v : null;
|
|
@@ -40,60 +33,22 @@ function scopeForFilePath(filePath) {
|
|
|
40
33
|
return 'central';
|
|
41
34
|
}
|
|
42
35
|
|
|
43
|
-
// JSON fallback path resolution. Used both for the empty-SQL fallback
|
|
44
|
-
// (test seeding pattern from Phase 1) and for the post-write JSON mirror.
|
|
45
|
-
function _filePathForScope(scope) {
|
|
46
|
-
const shared = require('./shared');
|
|
47
|
-
if (scope === 'central') {
|
|
48
|
-
return path.join(shared.MINIONS_DIR, 'work-items.json');
|
|
49
|
-
}
|
|
50
|
-
return path.join(shared.MINIONS_DIR, 'projects', scope, 'work-items.json');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
36
|
function _parseRow(row) {
|
|
54
37
|
if (!row || !row.data) return null;
|
|
55
38
|
try { return JSON.parse(row.data); }
|
|
56
39
|
catch { return null; }
|
|
57
40
|
}
|
|
58
41
|
|
|
59
|
-
function _readJsonArrayFallback(scope) {
|
|
60
|
-
const fp = _filePathForScope(scope);
|
|
61
|
-
let raw;
|
|
62
|
-
try { raw = fs.readFileSync(fp, 'utf8'); }
|
|
63
|
-
catch { return []; }
|
|
64
|
-
try {
|
|
65
|
-
const parsed = JSON.parse(raw);
|
|
66
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
67
|
-
} catch (e) {
|
|
68
|
-
try {
|
|
69
|
-
// eslint-disable-next-line no-console
|
|
70
|
-
console.warn(`[work-items-store] corrupt JSON in ${fp}: ${e.message}`);
|
|
71
|
-
} catch { /* console may be wrapped in tests */ }
|
|
72
|
-
return [];
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
42
|
function readWorkItemsForScope(scope) {
|
|
77
43
|
const { getDb } = require('./db');
|
|
78
44
|
const db = getDb();
|
|
79
45
|
|
|
80
|
-
_resyncScopeIfJsonDiverged(db, scope);
|
|
81
|
-
|
|
82
46
|
const rows = db.prepare(`
|
|
83
47
|
SELECT data FROM work_items
|
|
84
48
|
WHERE scope = ?
|
|
85
49
|
ORDER BY rowid
|
|
86
50
|
`).all(scope);
|
|
87
51
|
|
|
88
|
-
if (rows.length === 0) {
|
|
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.
|
|
92
|
-
const fallback = _readJsonArrayFallback(scope);
|
|
93
|
-
if (fallback.length > 0) return fallback;
|
|
94
|
-
return [];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
52
|
const out = [];
|
|
98
53
|
for (const row of rows) {
|
|
99
54
|
const wi = _parseRow(row);
|
|
@@ -102,58 +57,6 @@ function readWorkItemsForScope(scope) {
|
|
|
102
57
|
return out;
|
|
103
58
|
}
|
|
104
59
|
|
|
105
|
-
// If the JSON mirror's mtime advanced since our last mirror write, the
|
|
106
|
-
// file was rewritten outside the store (test cleanup + safeWrite, manual
|
|
107
|
-
// operator edit, external tool). Treat the JSON as canonical and rebuild
|
|
108
|
-
// SQL for that scope from it. Called from BOTH read and write paths so
|
|
109
|
-
// readers don't see stale SQL state and writers don't compute diffs
|
|
110
|
-
// against a SQL snapshot that no longer matches what callers expect.
|
|
111
|
-
//
|
|
112
|
-
// Safe to call inside `withTransaction` — DELETE + INSERT against a
|
|
113
|
-
// scope is bounded by the JSON's row count.
|
|
114
|
-
function _resyncScopeIfJsonDiverged(db, scope) {
|
|
115
|
-
const jsonPath = _filePathForScope(scope);
|
|
116
|
-
const currentHash = _fileContentHash(jsonPath);
|
|
117
|
-
const lastHash = _lastMirrorHashByScope.get(scope);
|
|
118
|
-
if (currentHash == null) return;
|
|
119
|
-
if (lastHash != null && currentHash === lastHash) return;
|
|
120
|
-
// First touch in this process (lastHash == null): if SQL already holds rows
|
|
121
|
-
// for the scope, SQL is the established source of truth (post-Phase-9.4) —
|
|
122
|
-
// adopt the current JSON hash and DO NOT hydrate. Hydrating here would let a
|
|
123
|
-
// crash-stale / .backup-restored / git-checked-out older JSON mirror DELETE +
|
|
124
|
-
// overwrite newer SQL. Only an empty-in-SQL scope (genuine first-install
|
|
125
|
-
// migration) falls through to hydrate-from-JSON, which is purely additive.
|
|
126
|
-
if (lastHash == null) {
|
|
127
|
-
const sqlHas = db.prepare('SELECT 1 FROM work_items WHERE scope = ? LIMIT 1').get(scope);
|
|
128
|
-
if (sqlHas) {
|
|
129
|
-
_lastMirrorHashByScope.set(scope, currentHash);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
_hydrateScopeFromJson(db, scope);
|
|
134
|
-
_lastMirrorHashByScope.set(scope, currentHash);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Enumerate scopes that have a JSON file on disk — used as the SQL-
|
|
138
|
-
// unavailable fallback for `readAllWorkItems`. Walks `<MINIONS_DIR>/
|
|
139
|
-
// projects/*/work-items.json` plus the central `<MINIONS_DIR>/work-
|
|
140
|
-
// items.json`. Silently swallows missing dirs (fresh installs).
|
|
141
|
-
function _enumerateJsonScopes() {
|
|
142
|
-
const shared = require('./shared');
|
|
143
|
-
const scopes = [];
|
|
144
|
-
const centralPath = path.join(shared.MINIONS_DIR, 'work-items.json');
|
|
145
|
-
if (fs.existsSync(centralPath)) scopes.push('central');
|
|
146
|
-
const projectsDir = path.join(shared.MINIONS_DIR, 'projects');
|
|
147
|
-
try {
|
|
148
|
-
for (const d of fs.readdirSync(projectsDir, { withFileTypes: true })) {
|
|
149
|
-
if (!d.isDirectory()) continue;
|
|
150
|
-
const fp = path.join(projectsDir, d.name, 'work-items.json');
|
|
151
|
-
if (fs.existsSync(fp)) scopes.push(d.name);
|
|
152
|
-
}
|
|
153
|
-
} catch { /* projects dir missing on fresh install */ }
|
|
154
|
-
return scopes;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
60
|
// Read all rows across all scopes — used by queries.getWorkItems which
|
|
158
61
|
// needs to surface central + every project's items in a single shot,
|
|
159
62
|
// tagged with their source scope.
|
|
@@ -161,17 +64,6 @@ function readAllWorkItems() {
|
|
|
161
64
|
const { getDb } = require('./db');
|
|
162
65
|
const db = getDb();
|
|
163
66
|
|
|
164
|
-
// Pick up any external JSON edits for every scope SQL knows about.
|
|
165
|
-
// Also resync 'central' explicitly so first-time reads on a JSON-only
|
|
166
|
-
// install hydrate the central scope. Per-project scopes get checked
|
|
167
|
-
// lazily on next read or mutation of that scope.
|
|
168
|
-
try { _resyncScopeIfJsonDiverged(db, 'central'); } catch {}
|
|
169
|
-
const knownScopes = db.prepare('SELECT DISTINCT scope FROM work_items').all().map(r => r.scope);
|
|
170
|
-
for (const s of knownScopes) {
|
|
171
|
-
if (s === 'central') continue;
|
|
172
|
-
try { _resyncScopeIfJsonDiverged(db, s); } catch {}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
67
|
const rows = db.prepare('SELECT data, scope FROM work_items ORDER BY rowid').all();
|
|
176
68
|
const out = [];
|
|
177
69
|
for (const row of rows) {
|
|
@@ -255,83 +147,11 @@ function _applyWorkItemsDiff(db, diff) {
|
|
|
255
147
|
// wrote — true iff at least one INSERT/UPDATE/DELETE landed
|
|
256
148
|
// result — the post-mutation array (legacy return shape of
|
|
257
149
|
// mutateJsonFileLocked)
|
|
258
|
-
|
|
259
|
-
// edits are detected unambiguously. (mtime, size) is too coarse: same-
|
|
260
|
-
// length timestamp swaps (last_checked / last_triggered / completed_at)
|
|
261
|
-
// preserve both axes. SHA-1 is sub-ms on a few-hundred-KB file.
|
|
262
|
-
const _lastMirrorHashByScope = new Map();
|
|
263
|
-
|
|
264
|
-
const crypto = require('crypto');
|
|
265
|
-
|
|
266
|
-
function _fileContentHash(filePath) {
|
|
267
|
-
try {
|
|
268
|
-
const buf = fs.readFileSync(filePath);
|
|
269
|
-
return crypto.createHash('sha1').update(buf).digest('hex');
|
|
270
|
-
}
|
|
271
|
-
catch { return null; }
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function _hydrateScopeFromJson(db, scope) {
|
|
275
|
-
const jsonItems = _readJsonArrayFallback(scope);
|
|
276
|
-
// Preserve the SQL-only prd_item_id FK across the delete+reinsert. It is owned
|
|
277
|
-
// by the PRD dual-write (prd-store.js#_upsertPrd), NOT represented in the JSON
|
|
278
|
-
// mirror, so a naive rebuild-from-JSON silently zeroes it for the whole scope —
|
|
279
|
-
// regressing the WI↔PRD link every time the JSON diverges (the count would
|
|
280
|
-
// oscillate as the PRD mirror re-stamps and the next hydrate wipes it again).
|
|
281
|
-
// Snapshot before the DELETE, restore onto the rows that survive the rebuild.
|
|
282
|
-
const fkById = new Map();
|
|
283
|
-
try {
|
|
284
|
-
for (const r of db.prepare('SELECT id, prd_item_id FROM work_items WHERE scope = ? AND prd_item_id IS NOT NULL').all(scope)) {
|
|
285
|
-
fkById.set(r.id, r.prd_item_id);
|
|
286
|
-
}
|
|
287
|
-
} catch { /* column absent on a pre-v15 schema — nothing to preserve */ }
|
|
288
|
-
// DELETE before re-insert: callers that wrote a smaller JSON file
|
|
289
|
-
// (test cleanup() removing items) must end up with a smaller SQL state.
|
|
290
|
-
db.prepare('DELETE FROM work_items WHERE scope = ?').run(scope);
|
|
291
|
-
if (jsonItems.length === 0) return;
|
|
292
|
-
const now = Date.now();
|
|
293
|
-
const ins = db.prepare(`
|
|
294
|
-
INSERT INTO work_items (id, scope, status, type, agent, parent_id, created_at, completed_at, data, updated_at)
|
|
295
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
296
|
-
ON CONFLICT(scope, id) DO NOTHING
|
|
297
|
-
`);
|
|
298
|
-
for (const wi of jsonItems) {
|
|
299
|
-
if (!wi || !wi.id) continue;
|
|
300
|
-
ins.run(
|
|
301
|
-
String(wi.id),
|
|
302
|
-
scope,
|
|
303
|
-
String(wi.status || 'pending'),
|
|
304
|
-
wi.type || null,
|
|
305
|
-
wi.dispatched_to || wi.agent || null,
|
|
306
|
-
wi.parent_id || null,
|
|
307
|
-
_toMs(wi.created_at),
|
|
308
|
-
_toMs(wi.completed_at || wi.completedAt),
|
|
309
|
-
JSON.stringify(wi),
|
|
310
|
-
now,
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
// Restore the FK for rows that still exist after the rebuild (a WI dropped
|
|
314
|
-
// from the JSON is gone, and its link goes with it — correct).
|
|
315
|
-
if (fkById.size) {
|
|
316
|
-
const restore = db.prepare('UPDATE work_items SET prd_item_id = ? WHERE scope = ? AND id = ?');
|
|
317
|
-
for (const [id, fk] of fkById) {
|
|
318
|
-
try { restore.run(fk, scope, id); } catch { /* best effort */ }
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function applyWorkItemsMutation(scope, mutator, options = {}) {
|
|
150
|
+
function applyWorkItemsMutation(scope, mutator) {
|
|
324
151
|
const { getDb, withTransaction } = require('./db');
|
|
325
152
|
const db = getDb();
|
|
326
153
|
|
|
327
154
|
return withTransaction(db, () => {
|
|
328
|
-
// Re-hydrate SQL from JSON if the file was touched outside the
|
|
329
|
-
// store (external edit, test re-seed, first-time hydrate after a
|
|
330
|
-
// fresh install). Without this, the diff would compare against a
|
|
331
|
-
// stale SQL snapshot and the mirror write would overwrite the JSON
|
|
332
|
-
// with that stale state.
|
|
333
|
-
_resyncScopeIfJsonDiverged(db, scope);
|
|
334
|
-
|
|
335
155
|
const before = readWorkItemsForScope(scope);
|
|
336
156
|
// Strip the _source decoration before handing to the mutator — it's
|
|
337
157
|
// added by readAllWorkItems only and would round-trip as garbage into
|
|
@@ -344,63 +164,10 @@ function applyWorkItemsMutation(scope, mutator, options = {}) {
|
|
|
344
164
|
: (Array.isArray(next) ? next : before);
|
|
345
165
|
const diff = _computeWorkItemsDiff(scope, beforeSnapshot, after);
|
|
346
166
|
const wrote = _applyWorkItemsDiff(db, diff);
|
|
347
|
-
// W-mr9vcxvy000cdb4e — phantom-WI race fix: mirror the JSON sidecar
|
|
348
|
-
// INSIDE this same SQL write transaction, BEFORE it commits, instead
|
|
349
|
-
// of after (as the caller used to do post-return). SQLite only allows
|
|
350
|
-
// one writer transaction at a time (BEGIN IMMEDIATE holds an
|
|
351
|
-
// exclusive writer lock), so mirroring here guarantees the on-disk
|
|
352
|
-
// JSON file is fully up to date by the time this commit becomes
|
|
353
|
-
// visible to any other reader/writer — dashboard and engine are
|
|
354
|
-
// SEPARATE Node processes, each with its own in-memory
|
|
355
|
-
// `_lastMirrorHashByScope` cache, so without this ordering a sibling
|
|
356
|
-
// process could observe a freshly committed SQL row before the mirror
|
|
357
|
-
// file caught up, decide (via its own stale hash) that the JSON file
|
|
358
|
-
// was "externally edited", and call `_hydrateScopeFromJson` — which
|
|
359
|
-
// DELETEs + reinserts the whole scope from that stale file, silently
|
|
360
|
-
// dropping the row a POST /api/work-items caller had just been told
|
|
361
|
-
// was created successfully (visible to the engine's dispatch loop
|
|
362
|
-
// one moment, gone from work_items the next). Mirroring pre-commit
|
|
363
|
-
// means "SQL says the row exists" and "JSON mirror has the row" can
|
|
364
|
-
// never observably disagree across processes.
|
|
365
|
-
if (wrote) {
|
|
366
|
-
try { _mirrorJsonFromSql(scope, options.mirrorPath); } catch { /* best-effort — mirror failure must not block the SQL write */ }
|
|
367
|
-
}
|
|
368
167
|
return { wrote, result: after };
|
|
369
168
|
});
|
|
370
169
|
}
|
|
371
170
|
|
|
372
|
-
// Mirror SQL → JSON file. Phase 2 keeps the file as a read-only derivative
|
|
373
|
-
// so legacy direct-readers keep working; SQL is the source of truth.
|
|
374
|
-
function _mirrorJsonFromSql(scope, filePath) {
|
|
375
|
-
try {
|
|
376
|
-
const shared = require('./shared');
|
|
377
|
-
const { getDb } = require('./db');
|
|
378
|
-
// Read SQL directly for this scope — bypass the JSON fallback so a
|
|
379
|
-
// mutation that empties SQL for the scope doesn't resurrect stale
|
|
380
|
-
// JSON content. (See pull-requests-store and watches-store for the
|
|
381
|
-
// same defense.)
|
|
382
|
-
const db = getDb();
|
|
383
|
-
const rows = db.prepare('SELECT data FROM work_items WHERE scope = ? ORDER BY rowid').all(scope);
|
|
384
|
-
const items = [];
|
|
385
|
-
for (const row of rows) {
|
|
386
|
-
const wi = _parseRow(row);
|
|
387
|
-
if (!wi) continue;
|
|
388
|
-
if (wi._source) delete wi._source;
|
|
389
|
-
items.push(wi);
|
|
390
|
-
}
|
|
391
|
-
const target = filePath || _filePathForScope(scope);
|
|
392
|
-
shared.safeWrite(target, items);
|
|
393
|
-
const h = _fileContentHash(target);
|
|
394
|
-
if (h != null) _lastMirrorHashByScope.set(scope, h);
|
|
395
|
-
} catch {
|
|
396
|
-
// Mirror failures are non-fatal: SQL has already committed.
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// Drop every SQL row for `scope` and forget its mirror-hash. Called by
|
|
401
|
-
// removeProject after the JSON file is archived so safeJson shim reads no
|
|
402
|
-
// longer surface the removed project's records.
|
|
403
|
-
//
|
|
404
171
|
// Per CLAUDE.md's mutator contract, every mutator wrapper must call
|
|
405
172
|
// emitStateEvent() so the dashboard's MAX(events.id) cache-version
|
|
406
173
|
// advances — otherwise the dashboard keeps serving stale cached
|
|
@@ -411,21 +178,6 @@ function dropScope(scope) {
|
|
|
411
178
|
try {
|
|
412
179
|
const { getDb } = require('./db');
|
|
413
180
|
const result = getDb().prepare('DELETE FROM work_items WHERE scope = ?').run(scope);
|
|
414
|
-
// Do NOT simply forget the mirror-hash here. _resyncScopeIfJsonDiverged
|
|
415
|
-
// treats "no recorded hash" + "zero SQL rows for the scope" as a genuine
|
|
416
|
-
// first-install hydrate and rebuilds SQL from whatever JSON is on disk.
|
|
417
|
-
// When the caller is removeProject with dataMode:'keep', the JSON file for
|
|
418
|
-
// this scope is deliberately left untouched — so the very next read/write
|
|
419
|
-
// against this scope would silently resurrect the just-dropped project's
|
|
420
|
-
// stale rows straight back into SQL. Instead, adopt the JSON file's
|
|
421
|
-
// *current* hash (if it still exists) so the next resync correctly reads
|
|
422
|
-
// that as "unchanged" and skips the rehydrate. If the file is genuinely
|
|
423
|
-
// gone (dataMode: 'purge'/'archive'), forgetting the hash is safe since
|
|
424
|
-
// _resyncScopeIfJsonDiverged no-ops when the file can't be hashed.
|
|
425
|
-
const jsonPath = _filePathForScope(scope);
|
|
426
|
-
const currentHash = _fileContentHash(jsonPath);
|
|
427
|
-
if (currentHash == null) _lastMirrorHashByScope.delete(scope);
|
|
428
|
-
else _lastMirrorHashByScope.set(scope, currentHash);
|
|
429
181
|
if (result && result.changes > 0) {
|
|
430
182
|
try { require('./db-events').emitStateEvent('work_items'); } catch { /* optional */ }
|
|
431
183
|
try { require('./queries').invalidateWorkItemsCache(); } catch { /* queries not loaded */ }
|
|
@@ -439,7 +191,4 @@ module.exports = {
|
|
|
439
191
|
readAllWorkItems,
|
|
440
192
|
applyWorkItemsMutation,
|
|
441
193
|
dropScope,
|
|
442
|
-
_filePathForScope,
|
|
443
|
-
_mirrorJsonFromSql,
|
|
444
|
-
_hydrateScopeFromJson, // exported for testing (prd_item_id-preservation)
|
|
445
194
|
};
|