@plur-ai/core 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +17 -5
- package/dist/index.js +179 -32
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1234,8 +1234,9 @@ declare function detectPlurStorage(explicitPath?: string): PlurPaths;
|
|
|
1234
1234
|
declare class IndexedStorage {
|
|
1235
1235
|
private dbPath;
|
|
1236
1236
|
private engramsPath;
|
|
1237
|
+
private stores;
|
|
1237
1238
|
private db;
|
|
1238
|
-
constructor(engramsPath: string, dbPath: string);
|
|
1239
|
+
constructor(engramsPath: string, dbPath: string, stores?: StoreEntry[]);
|
|
1239
1240
|
private getDb;
|
|
1240
1241
|
/** Load all engrams from SQLite index. Auto-rebuilds if db missing. */
|
|
1241
1242
|
loadAll(): Engram[];
|
|
@@ -1249,7 +1250,7 @@ declare class IndexedStorage {
|
|
|
1249
1250
|
count(filter?: {
|
|
1250
1251
|
status?: string;
|
|
1251
1252
|
}): number;
|
|
1252
|
-
/** Sync SQLite index from YAML source of truth. */
|
|
1253
|
+
/** Sync SQLite index from YAML source of truth (primary + all stores). */
|
|
1253
1254
|
syncFromYaml(): void;
|
|
1254
1255
|
/** Drop and rebuild the entire index from YAML. */
|
|
1255
1256
|
reindex(): void;
|
|
@@ -1303,9 +1304,20 @@ declare class Plur {
|
|
|
1303
1304
|
private paths;
|
|
1304
1305
|
private config;
|
|
1305
1306
|
private indexedStorage;
|
|
1307
|
+
private _engramCache;
|
|
1306
1308
|
constructor(options?: {
|
|
1307
1309
|
path?: string;
|
|
1308
1310
|
});
|
|
1311
|
+
/**
|
|
1312
|
+
* Load engrams from primary store + all configured stores, with mtime-based caching.
|
|
1313
|
+
* Store engram IDs get namespaced: ENG-2026-0401-001 → ENG-DF-2026-0401-001.
|
|
1314
|
+
* Primary engrams are returned unchanged.
|
|
1315
|
+
*/
|
|
1316
|
+
private _loadAllEngrams;
|
|
1317
|
+
/** Load engrams from a path with mtime-based caching */
|
|
1318
|
+
private _loadCached;
|
|
1319
|
+
/** Find which store owns an engram by ID. For namespaced IDs, strips prefix to find in store. */
|
|
1320
|
+
private _findEngramStore;
|
|
1309
1321
|
/** Create engram, detect conflicts, save. Returns the created engram. */
|
|
1310
1322
|
learn(statement: string, context?: LearnContext): Engram;
|
|
1311
1323
|
/**
|
|
@@ -1328,7 +1340,7 @@ declare class Plur {
|
|
|
1328
1340
|
recallExpanded(query: string, options: RecallOptions & {
|
|
1329
1341
|
llm: LlmFunction;
|
|
1330
1342
|
}): Promise<Engram[]>;
|
|
1331
|
-
/** Get a single engram by ID, regardless of status.
|
|
1343
|
+
/** Get a single engram by ID, regardless of status. Searches primary + all stores. */
|
|
1332
1344
|
getById(id: string): Engram | null;
|
|
1333
1345
|
/** List all active engrams, optionally filtered by scope/domain. No search — returns all matches. */
|
|
1334
1346
|
list(options?: {
|
|
@@ -1345,7 +1357,7 @@ declare class Plur {
|
|
|
1345
1357
|
/** Scored injection with embedding boost when available. Falls back to BM25 if embeddings not installed. */
|
|
1346
1358
|
injectHybrid(task: string, options?: InjectOptions): Promise<InjectionResult>;
|
|
1347
1359
|
private _formatInjection;
|
|
1348
|
-
/** Update feedback_signals and adjust retrieval_strength. Searches
|
|
1360
|
+
/** Update feedback_signals and adjust retrieval_strength. Searches primary, stores, then packs. */
|
|
1349
1361
|
feedback(id: string, signal: 'positive' | 'negative' | 'neutral'): void;
|
|
1350
1362
|
/** Save extracted meta-engrams to the engram store. Skips IDs that already exist. */
|
|
1351
1363
|
saveMetaEngrams(metas: Engram[]): {
|
|
@@ -1354,7 +1366,7 @@ declare class Plur {
|
|
|
1354
1366
|
};
|
|
1355
1367
|
/** Update an existing engram in the store by ID. Returns true if found and updated. */
|
|
1356
1368
|
updateEngram(updated: Engram): boolean;
|
|
1357
|
-
/** Set engram status to 'retired'. */
|
|
1369
|
+
/** Set engram status to 'retired'. Supports primary and store engrams. */
|
|
1358
1370
|
forget(id: string, reason?: string): void;
|
|
1359
1371
|
/** Remove retired engrams from storage. Returns count of removed and remaining. */
|
|
1360
1372
|
compact(): {
|
package/dist/index.js
CHANGED
|
@@ -295,6 +295,16 @@ function loadAllPacks(packsDir) {
|
|
|
295
295
|
}
|
|
296
296
|
return packs;
|
|
297
297
|
}
|
|
298
|
+
function storePrefix(scope) {
|
|
299
|
+
const parts = scope.split(/[:\-_./]/).filter(Boolean);
|
|
300
|
+
if (parts.length >= 2) {
|
|
301
|
+
const p2 = parts[1];
|
|
302
|
+
return (parts[0][0] + p2[0] + (p2[1] || p2[0])).toUpperCase();
|
|
303
|
+
}
|
|
304
|
+
const w = parts[0] || scope;
|
|
305
|
+
if (w.length >= 3) return (w[0] + w[Math.floor(w.length / 2)] + w[w.length - 1]).toUpperCase();
|
|
306
|
+
return (w[0] + (w[1] || w[0]) + (w[2] || w[0])).toUpperCase();
|
|
307
|
+
}
|
|
298
308
|
function generateEngramId(existing) {
|
|
299
309
|
const now = /* @__PURE__ */ new Date();
|
|
300
310
|
const date = now.toISOString().slice(0, 10).replace(/-/g, "");
|
|
@@ -322,10 +332,12 @@ function getDatabase() {
|
|
|
322
332
|
var IndexedStorage = class {
|
|
323
333
|
dbPath;
|
|
324
334
|
engramsPath;
|
|
335
|
+
stores;
|
|
325
336
|
db = null;
|
|
326
|
-
constructor(engramsPath, dbPath) {
|
|
337
|
+
constructor(engramsPath, dbPath, stores) {
|
|
327
338
|
this.engramsPath = engramsPath;
|
|
328
339
|
this.dbPath = dbPath;
|
|
340
|
+
this.stores = stores ?? [];
|
|
329
341
|
}
|
|
330
342
|
getDb() {
|
|
331
343
|
if (!this.db) {
|
|
@@ -345,6 +357,11 @@ var IndexedStorage = class {
|
|
|
345
357
|
CREATE INDEX IF NOT EXISTS idx_scope ON engrams(scope);
|
|
346
358
|
CREATE INDEX IF NOT EXISTS idx_domain ON engrams(domain);
|
|
347
359
|
`);
|
|
360
|
+
try {
|
|
361
|
+
this.db.exec("ALTER TABLE engrams ADD COLUMN source TEXT NOT NULL DEFAULT 'primary'");
|
|
362
|
+
} catch {
|
|
363
|
+
}
|
|
364
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS idx_source ON engrams(source)");
|
|
348
365
|
}
|
|
349
366
|
return this.db;
|
|
350
367
|
}
|
|
@@ -392,25 +409,41 @@ var IndexedStorage = class {
|
|
|
392
409
|
}
|
|
393
410
|
return db.prepare("SELECT COUNT(*) as c FROM engrams").get().c;
|
|
394
411
|
}
|
|
395
|
-
/** Sync SQLite index from YAML source of truth. */
|
|
412
|
+
/** Sync SQLite index from YAML source of truth (primary + all stores). */
|
|
396
413
|
syncFromYaml() {
|
|
397
|
-
const engrams = loadEngrams(this.engramsPath);
|
|
398
414
|
const db = this.getDb();
|
|
399
415
|
const upsert = db.prepare(`
|
|
400
|
-
INSERT OR REPLACE INTO engrams (id, status, scope, domain, last_accessed, data)
|
|
401
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
416
|
+
INSERT OR REPLACE INTO engrams (id, status, scope, domain, last_accessed, data, source)
|
|
417
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
402
418
|
`);
|
|
403
|
-
const
|
|
404
|
-
const
|
|
405
|
-
const dbIds = new Set(
|
|
406
|
-
db.prepare("SELECT id FROM engrams").all().map((r) => r.id)
|
|
407
|
-
);
|
|
419
|
+
const allSyncedIds = /* @__PURE__ */ new Set();
|
|
420
|
+
const validSources = /* @__PURE__ */ new Set(["primary"]);
|
|
408
421
|
const tx = db.transaction(() => {
|
|
409
|
-
|
|
410
|
-
|
|
422
|
+
const primaryEngrams = loadEngrams(this.engramsPath);
|
|
423
|
+
for (const e of primaryEngrams) {
|
|
424
|
+
upsert.run(e.id, e.status, e.scope, e.domain ?? null, e.activation.last_accessed, JSON.stringify(e), "primary");
|
|
425
|
+
allSyncedIds.add(e.id);
|
|
411
426
|
}
|
|
412
|
-
for (const
|
|
413
|
-
|
|
427
|
+
for (const store of this.stores) {
|
|
428
|
+
validSources.add(store.path);
|
|
429
|
+
const storeEngrams = loadEngrams(store.path);
|
|
430
|
+
const prefix = storePrefix(store.scope);
|
|
431
|
+
for (const e of storeEngrams) {
|
|
432
|
+
if (e.scope !== "global" && e.scope !== store.scope && !e.scope.startsWith(store.scope)) {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const nsId = e.id.replace(/^(ENG|ABS|META)-/, `$1-${prefix}-`);
|
|
436
|
+
const scope = e.scope === "global" ? store.scope : e.scope;
|
|
437
|
+
upsert.run(nsId, e.status, scope, e.domain ?? null, e.activation.last_accessed, JSON.stringify({ ...e, id: nsId, scope }), store.path);
|
|
438
|
+
allSyncedIds.add(nsId);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const dbRows = db.prepare("SELECT id, source FROM engrams").all();
|
|
442
|
+
const deleteStmt = db.prepare("DELETE FROM engrams WHERE id = ?");
|
|
443
|
+
for (const row of dbRows) {
|
|
444
|
+
if (!allSyncedIds.has(row.id)) {
|
|
445
|
+
deleteStmt.run(row.id);
|
|
446
|
+
}
|
|
414
447
|
}
|
|
415
448
|
});
|
|
416
449
|
tx();
|
|
@@ -456,7 +489,7 @@ var PlurConfigSchema = z3.object({
|
|
|
456
489
|
co_access: z3.boolean().default(true)
|
|
457
490
|
}).default({}),
|
|
458
491
|
allow_secrets: z3.boolean().default(false),
|
|
459
|
-
index: z3.boolean().default(
|
|
492
|
+
index: z3.boolean().default(true),
|
|
460
493
|
stores: z3.array(StoreEntrySchema).default([])
|
|
461
494
|
}).partial();
|
|
462
495
|
|
|
@@ -1978,13 +2011,79 @@ var Plur = class {
|
|
|
1978
2011
|
paths;
|
|
1979
2012
|
config;
|
|
1980
2013
|
indexedStorage = null;
|
|
2014
|
+
_engramCache = /* @__PURE__ */ new Map();
|
|
1981
2015
|
constructor(options) {
|
|
1982
2016
|
this.paths = detectPlurStorage(options?.path);
|
|
1983
2017
|
this.config = loadConfig(this.paths.config);
|
|
1984
2018
|
if (this.config.index) {
|
|
1985
|
-
this.indexedStorage = new IndexedStorage(this.paths.engrams, this.paths.db);
|
|
2019
|
+
this.indexedStorage = new IndexedStorage(this.paths.engrams, this.paths.db, this.config.stores);
|
|
1986
2020
|
}
|
|
1987
2021
|
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Load engrams from primary store + all configured stores, with mtime-based caching.
|
|
2024
|
+
* Store engram IDs get namespaced: ENG-2026-0401-001 → ENG-DF-2026-0401-001.
|
|
2025
|
+
* Primary engrams are returned unchanged.
|
|
2026
|
+
*/
|
|
2027
|
+
_loadAllEngrams() {
|
|
2028
|
+
const primary = this._loadCached(this.paths.engrams);
|
|
2029
|
+
const stores = this.config.stores ?? [];
|
|
2030
|
+
if (stores.length === 0) return primary;
|
|
2031
|
+
const all = [...primary];
|
|
2032
|
+
for (const store of stores) {
|
|
2033
|
+
const storeEngrams = this._loadCached(store.path);
|
|
2034
|
+
const prefix = storePrefix(store.scope);
|
|
2035
|
+
for (const e of storeEngrams) {
|
|
2036
|
+
if (e.scope !== "global" && e.scope !== store.scope && !e.scope.startsWith(store.scope)) {
|
|
2037
|
+
logger.debug(`Skipping engram ${e.id} from store ${store.scope}: scope mismatch (${e.scope})`);
|
|
2038
|
+
continue;
|
|
2039
|
+
}
|
|
2040
|
+
const cloned = { ...e };
|
|
2041
|
+
if (cloned.scope === "global") {
|
|
2042
|
+
cloned.scope = store.scope;
|
|
2043
|
+
}
|
|
2044
|
+
const originalId = cloned.id;
|
|
2045
|
+
cloned.id = cloned.id.replace(/^(ENG|ABS|META)-/, `$1-${prefix}-`);
|
|
2046
|
+
cloned._originalId = originalId;
|
|
2047
|
+
cloned._storeScope = store.scope;
|
|
2048
|
+
all.push(cloned);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
return all;
|
|
2052
|
+
}
|
|
2053
|
+
/** Load engrams from a path with mtime-based caching */
|
|
2054
|
+
_loadCached(path2) {
|
|
2055
|
+
let mtime = 0;
|
|
2056
|
+
try {
|
|
2057
|
+
mtime = fs3.statSync(path2).mtimeMs;
|
|
2058
|
+
} catch {
|
|
2059
|
+
return [];
|
|
2060
|
+
}
|
|
2061
|
+
const cached = this._engramCache.get(path2);
|
|
2062
|
+
if (cached && cached.mtime === mtime) return cached.engrams;
|
|
2063
|
+
const engrams = loadEngrams(path2);
|
|
2064
|
+
this._engramCache.set(path2, { mtime, engrams });
|
|
2065
|
+
return engrams;
|
|
2066
|
+
}
|
|
2067
|
+
/** Find which store owns an engram by ID. For namespaced IDs, strips prefix to find in store. */
|
|
2068
|
+
_findEngramStore(id) {
|
|
2069
|
+
const primaryEngrams = this._loadCached(this.paths.engrams);
|
|
2070
|
+
if (primaryEngrams.find((e) => e.id === id)) {
|
|
2071
|
+
return { path: this.paths.engrams, readonly: false, originalId: id };
|
|
2072
|
+
}
|
|
2073
|
+
const stores = this.config.stores ?? [];
|
|
2074
|
+
for (const store of stores) {
|
|
2075
|
+
const prefix = storePrefix(store.scope);
|
|
2076
|
+
const nsPattern = new RegExp(`^(ENG|ABS|META)-${prefix}-`);
|
|
2077
|
+
if (nsPattern.test(id)) {
|
|
2078
|
+
const originalId = id.replace(nsPattern, "$1-");
|
|
2079
|
+
const storeEngrams = this._loadCached(store.path);
|
|
2080
|
+
if (storeEngrams.find((e) => e.id === originalId)) {
|
|
2081
|
+
return { path: store.path, readonly: store.readonly ?? false, originalId };
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
return null;
|
|
2086
|
+
}
|
|
1988
2087
|
/** Create engram, detect conflicts, save. Returns the created engram. */
|
|
1989
2088
|
learn(statement, context) {
|
|
1990
2089
|
if (!this.config.allow_secrets) {
|
|
@@ -1995,10 +2094,11 @@ var Plur = class {
|
|
|
1995
2094
|
}
|
|
1996
2095
|
return withLock(this.paths.engrams, () => {
|
|
1997
2096
|
const engrams = loadEngrams(this.paths.engrams);
|
|
1998
|
-
const
|
|
2097
|
+
const allEngrams = this._loadAllEngrams();
|
|
2098
|
+
const id = generateEngramId(allEngrams);
|
|
1999
2099
|
const scope = context?.scope ?? "global";
|
|
2000
2100
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2001
|
-
const conflictingEngrams = detectConflicts({ statement, scope },
|
|
2101
|
+
const conflictingEngrams = detectConflicts({ statement, scope }, allEngrams);
|
|
2002
2102
|
const conflictIds = conflictingEngrams.map((e) => e.id);
|
|
2003
2103
|
const engram = {
|
|
2004
2104
|
id,
|
|
@@ -2091,9 +2191,9 @@ var Plur = class {
|
|
|
2091
2191
|
this._reactivateResults(results);
|
|
2092
2192
|
return results;
|
|
2093
2193
|
}
|
|
2094
|
-
/** Get a single engram by ID, regardless of status.
|
|
2194
|
+
/** Get a single engram by ID, regardless of status. Searches primary + all stores. */
|
|
2095
2195
|
getById(id) {
|
|
2096
|
-
const engrams =
|
|
2196
|
+
const engrams = this._loadAllEngrams();
|
|
2097
2197
|
return engrams.find((e) => e.id === id) ?? null;
|
|
2098
2198
|
}
|
|
2099
2199
|
/** List all active engrams, optionally filtered by scope/domain. No search — returns all matches. */
|
|
@@ -2110,7 +2210,7 @@ var Plur = class {
|
|
|
2110
2210
|
domain: options?.domain
|
|
2111
2211
|
});
|
|
2112
2212
|
} else {
|
|
2113
|
-
engrams =
|
|
2213
|
+
engrams = this._loadAllEngrams();
|
|
2114
2214
|
engrams = engrams.filter((e) => e.status === "active");
|
|
2115
2215
|
if (options?.domain) {
|
|
2116
2216
|
engrams = engrams.filter((e) => e.domain?.startsWith(options.domain));
|
|
@@ -2136,9 +2236,12 @@ var Plur = class {
|
|
|
2136
2236
|
/** Reactivate accessed engrams and update co-access associations */
|
|
2137
2237
|
_reactivateResults(results) {
|
|
2138
2238
|
if (results.length === 0) return;
|
|
2239
|
+
const isStoreEngram = (e) => e._originalId || /^(ENG|ABS|META)-[A-Z]{3}-/.test(e.id);
|
|
2240
|
+
const primaryResults = results.filter((e) => !isStoreEngram(e));
|
|
2241
|
+
if (primaryResults.length === 0) return;
|
|
2139
2242
|
withLock(this.paths.engrams, () => {
|
|
2140
2243
|
const allEngrams = loadEngrams(this.paths.engrams);
|
|
2141
|
-
const resultIds = new Set(
|
|
2244
|
+
const resultIds = new Set(primaryResults.map((e) => e.id));
|
|
2142
2245
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2143
2246
|
let modified = false;
|
|
2144
2247
|
for (const e of allEngrams) {
|
|
@@ -2194,7 +2297,7 @@ var Plur = class {
|
|
|
2194
2297
|
async injectHybrid(task, options) {
|
|
2195
2298
|
let embeddingBoosts;
|
|
2196
2299
|
try {
|
|
2197
|
-
const engrams =
|
|
2300
|
+
const engrams = this._loadAllEngrams().filter((e) => e.status === "active");
|
|
2198
2301
|
const results = await embeddingSearch(engrams, task, engrams.length, this.paths.root);
|
|
2199
2302
|
if (results.length > 0) {
|
|
2200
2303
|
embeddingBoosts = /* @__PURE__ */ new Map();
|
|
@@ -2207,7 +2310,7 @@ var Plur = class {
|
|
|
2207
2310
|
return this._formatInjection(task, options, embeddingBoosts);
|
|
2208
2311
|
}
|
|
2209
2312
|
_formatInjection(task, options, embeddingBoosts) {
|
|
2210
|
-
const engrams =
|
|
2313
|
+
const engrams = this._loadAllEngrams();
|
|
2211
2314
|
const packs = loadAllPacks(this.paths.packs);
|
|
2212
2315
|
const budget = options?.budget ?? this.config.injection_budget ?? 2e3;
|
|
2213
2316
|
const result = selectAndSpread(
|
|
@@ -2247,7 +2350,7 @@ var Plur = class {
|
|
|
2247
2350
|
injected_ids
|
|
2248
2351
|
};
|
|
2249
2352
|
}
|
|
2250
|
-
/** Update feedback_signals and adjust retrieval_strength. Searches
|
|
2353
|
+
/** Update feedback_signals and adjust retrieval_strength. Searches primary, stores, then packs. */
|
|
2251
2354
|
feedback(id, signal) {
|
|
2252
2355
|
const found = withLock(this.paths.engrams, () => {
|
|
2253
2356
|
const engrams = loadEngrams(this.paths.engrams);
|
|
@@ -2267,6 +2370,29 @@ var Plur = class {
|
|
|
2267
2370
|
return true;
|
|
2268
2371
|
});
|
|
2269
2372
|
if (found) return;
|
|
2373
|
+
const storeInfo = this._findEngramStore(id);
|
|
2374
|
+
if (storeInfo && storeInfo.path !== this.paths.engrams) {
|
|
2375
|
+
if (storeInfo.readonly) {
|
|
2376
|
+
throw new Error("Engram is in a readonly store");
|
|
2377
|
+
}
|
|
2378
|
+
const storeEngrams = loadEngrams(storeInfo.path);
|
|
2379
|
+
const engram = storeEngrams.find((e) => e.id === storeInfo.originalId);
|
|
2380
|
+
if (engram) {
|
|
2381
|
+
if (!engram.feedback_signals) {
|
|
2382
|
+
engram.feedback_signals = { positive: 0, negative: 0, neutral: 0 };
|
|
2383
|
+
}
|
|
2384
|
+
engram.feedback_signals[signal] += 1;
|
|
2385
|
+
if (signal === "positive") {
|
|
2386
|
+
engram.activation.retrieval_strength = Math.min(1, engram.activation.retrieval_strength + 0.05);
|
|
2387
|
+
} else if (signal === "negative") {
|
|
2388
|
+
engram.activation.retrieval_strength = Math.max(0, engram.activation.retrieval_strength - 0.1);
|
|
2389
|
+
}
|
|
2390
|
+
saveEngrams(storeInfo.path, storeEngrams);
|
|
2391
|
+
this._engramCache.delete(storeInfo.path);
|
|
2392
|
+
this._syncIndex();
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2270
2396
|
this._feedbackPack(id, signal);
|
|
2271
2397
|
}
|
|
2272
2398
|
/** Save extracted meta-engrams to the engram store. Skips IDs that already exist. */
|
|
@@ -2303,19 +2429,40 @@ var Plur = class {
|
|
|
2303
2429
|
return true;
|
|
2304
2430
|
});
|
|
2305
2431
|
}
|
|
2306
|
-
/** Set engram status to 'retired'. */
|
|
2432
|
+
/** Set engram status to 'retired'. Supports primary and store engrams. */
|
|
2307
2433
|
forget(id, reason) {
|
|
2308
|
-
withLock(this.paths.engrams, () => {
|
|
2434
|
+
const foundInPrimary = withLock(this.paths.engrams, () => {
|
|
2309
2435
|
const engrams = loadEngrams(this.paths.engrams);
|
|
2310
2436
|
const engram = engrams.find((e) => e.id === id);
|
|
2311
|
-
if (!engram)
|
|
2437
|
+
if (!engram) return false;
|
|
2312
2438
|
engram.status = "retired";
|
|
2313
2439
|
if (reason && !engram.rationale) {
|
|
2314
2440
|
engram.rationale = `Retired: ${reason}`;
|
|
2315
2441
|
}
|
|
2316
2442
|
saveEngrams(this.paths.engrams, engrams);
|
|
2317
2443
|
this._syncIndex();
|
|
2444
|
+
return true;
|
|
2318
2445
|
});
|
|
2446
|
+
if (foundInPrimary) return;
|
|
2447
|
+
const storeInfo = this._findEngramStore(id);
|
|
2448
|
+
if (storeInfo && storeInfo.path !== this.paths.engrams) {
|
|
2449
|
+
if (storeInfo.readonly) {
|
|
2450
|
+
throw new Error("Cannot retire engram from readonly store");
|
|
2451
|
+
}
|
|
2452
|
+
const storeEngrams = loadEngrams(storeInfo.path);
|
|
2453
|
+
const engram = storeEngrams.find((e) => e.id === storeInfo.originalId);
|
|
2454
|
+
if (engram) {
|
|
2455
|
+
engram.status = "retired";
|
|
2456
|
+
if (reason && !engram.rationale) {
|
|
2457
|
+
engram.rationale = `Retired: ${reason}`;
|
|
2458
|
+
}
|
|
2459
|
+
saveEngrams(storeInfo.path, storeEngrams);
|
|
2460
|
+
this._engramCache.delete(storeInfo.path);
|
|
2461
|
+
this._syncIndex();
|
|
2462
|
+
return;
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
throw new Error(`Engram not found: ${id}`);
|
|
2319
2466
|
}
|
|
2320
2467
|
/** Remove retired engrams from storage. Returns count of removed and remaining. */
|
|
2321
2468
|
compact() {
|
|
@@ -2333,7 +2480,7 @@ var Plur = class {
|
|
|
2333
2480
|
/** Rebuild SQLite index from YAML source of truth. Only works when index: true. */
|
|
2334
2481
|
reindex() {
|
|
2335
2482
|
if (!this.indexedStorage) {
|
|
2336
|
-
this.indexedStorage = new IndexedStorage(this.paths.engrams, this.paths.db);
|
|
2483
|
+
this.indexedStorage = new IndexedStorage(this.paths.engrams, this.paths.db, this.config.stores);
|
|
2337
2484
|
}
|
|
2338
2485
|
this.indexedStorage.reindex();
|
|
2339
2486
|
}
|
|
@@ -2430,7 +2577,7 @@ var Plur = class {
|
|
|
2430
2577
|
}
|
|
2431
2578
|
/** Return system health info. */
|
|
2432
2579
|
status() {
|
|
2433
|
-
const engrams =
|
|
2580
|
+
const engrams = this._loadAllEngrams();
|
|
2434
2581
|
const episodes = queryTimeline(this.paths.episodes);
|
|
2435
2582
|
const packs = listPacks(this.paths.packs);
|
|
2436
2583
|
return {
|
|
@@ -2470,12 +2617,12 @@ var Plur = class {
|
|
|
2470
2617
|
scope: "global",
|
|
2471
2618
|
shared: false,
|
|
2472
2619
|
readonly: false,
|
|
2473
|
-
engram_count:
|
|
2620
|
+
engram_count: this._loadCached(this.paths.engrams).filter((e) => e.status !== "retired").length
|
|
2474
2621
|
};
|
|
2475
2622
|
const additional = stores.map((s) => {
|
|
2476
2623
|
let count = 0;
|
|
2477
2624
|
try {
|
|
2478
|
-
count =
|
|
2625
|
+
count = this._loadCached(s.path).filter((e) => e.status !== "retired").length;
|
|
2479
2626
|
} catch {
|
|
2480
2627
|
}
|
|
2481
2628
|
return { ...s, engram_count: count };
|