crbro-memory 2.8.0 → 2.9.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/README.md +15 -4
- package/dist/daemon/endpoint.d.ts.map +1 -1
- package/dist/daemon/endpoint.js +11 -0
- package/dist/daemon/endpoint.js.map +1 -1
- package/dist/engine/brain.d.ts.map +1 -1
- package/dist/engine/brain.js +30 -0
- package/dist/engine/brain.js.map +1 -1
- package/dist/engine/cortex.d.ts +55 -1
- package/dist/engine/cortex.d.ts.map +1 -1
- package/dist/engine/cortex.js +183 -3
- package/dist/engine/cortex.js.map +1 -1
- package/dist/engine/maintenance.d.ts +22 -0
- package/dist/engine/maintenance.d.ts.map +1 -1
- package/dist/engine/maintenance.js +59 -2
- package/dist/engine/maintenance.js.map +1 -1
- package/dist/engine/shelf.d.ts +97 -0
- package/dist/engine/shelf.d.ts.map +1 -0
- package/dist/engine/shelf.js +343 -0
- package/dist/engine/shelf.js.map +1 -0
- package/dist/engine/source.d.ts +6 -0
- package/dist/engine/source.d.ts.map +1 -0
- package/dist/engine/source.js +101 -0
- package/dist/engine/source.js.map +1 -0
- package/dist/search/index.d.ts +6 -0
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +46 -1
- package/dist/search/index.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +213 -32
- package/dist/server.js.map +1 -1
- package/dist/sync/materialize.d.ts +4 -0
- package/dist/sync/materialize.d.ts.map +1 -1
- package/dist/sync/materialize.js +80 -1
- package/dist/sync/materialize.js.map +1 -1
- package/dist/sync/ops.d.ts +29 -2
- package/dist/sync/ops.d.ts.map +1 -1
- package/dist/sync/ops.js.map +1 -1
- package/dist/sync/space.d.ts.map +1 -1
- package/dist/sync/space.js +15 -2
- package/dist/sync/space.js.map +1 -1
- package/dist/types/index.d.ts +47 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/engine/cortex.js
CHANGED
|
@@ -13,6 +13,7 @@ const ids_js_1 = require("../utils/ids.js");
|
|
|
13
13
|
const hash_js_1 = require("../utils/hash.js");
|
|
14
14
|
const ops_js_1 = require("../sync/ops.js");
|
|
15
15
|
const secrets_js_1 = require("./secrets.js");
|
|
16
|
+
const shelf_js_1 = require("./shelf.js");
|
|
16
17
|
const TYPE_PREFIX_RE = /^(project_|tech_|lang_|person_|domain_|process_|protocol_)/;
|
|
17
18
|
/**
|
|
18
19
|
* Similarity above which a new fact is flagged as a near-duplicate of one
|
|
@@ -360,6 +361,14 @@ function unionNeuron(target, source) {
|
|
|
360
361
|
hit.keys = keys;
|
|
361
362
|
else
|
|
362
363
|
delete hit.keys;
|
|
364
|
+
// The later check is the newer evidence; the more volatile explicit class
|
|
365
|
+
// wins, as in a team space (a needless warning is cheaper than a missing one).
|
|
366
|
+
const verificado = (0, shelf_js_1.latestOf)(hit.verified, sf.verified);
|
|
367
|
+
if (verificado)
|
|
368
|
+
hit.verified = verificado;
|
|
369
|
+
const vida = (0, shelf_js_1.mostVolatile)(hit.shelf_life, sf.shelf_life);
|
|
370
|
+
if (vida)
|
|
371
|
+
hit.shelf_life = vida;
|
|
363
372
|
}
|
|
364
373
|
// ── Decisions: by normalised text, the target's rationale wins ──
|
|
365
374
|
const decisions = target.decisions.map(d => ({ ...d }));
|
|
@@ -435,6 +444,22 @@ function unionNeuron(target, source) {
|
|
|
435
444
|
neuron.entry_status = estadosVivos;
|
|
436
445
|
else
|
|
437
446
|
delete neuron.entry_status;
|
|
447
|
+
const verificadas = { ...(source.entry_verified || {}) };
|
|
448
|
+
for (const [k, v] of Object.entries(target.entry_verified || {})) {
|
|
449
|
+
const l = (0, shelf_js_1.latestOf)(verificadas[k], v);
|
|
450
|
+
if (l)
|
|
451
|
+
verificadas[k] = l;
|
|
452
|
+
else if (!verificadas[k])
|
|
453
|
+
verificadas[k] = v;
|
|
454
|
+
}
|
|
455
|
+
const verificadasVivas = {};
|
|
456
|
+
for (const k of Object.keys(verificadas).sort())
|
|
457
|
+
if (vivos.has(k))
|
|
458
|
+
verificadasVivas[k] = verificadas[k];
|
|
459
|
+
if (Object.keys(verificadasVivas).length)
|
|
460
|
+
neuron.entry_verified = verificadasVivas;
|
|
461
|
+
else
|
|
462
|
+
delete neuron.entry_verified;
|
|
438
463
|
const origenes = { ...(source.entry_source || {}), ...(target.entry_source || {}) };
|
|
439
464
|
const origenesVivos = {};
|
|
440
465
|
for (const k of Object.keys(origenes).sort())
|
|
@@ -750,6 +775,11 @@ class Cortex {
|
|
|
750
775
|
let duplicate = false;
|
|
751
776
|
let updatedInPlace = false;
|
|
752
777
|
let confirmations;
|
|
778
|
+
let shelf;
|
|
779
|
+
let reconfirmed = false;
|
|
780
|
+
let shelfLengthened = false;
|
|
781
|
+
let verificar = null;
|
|
782
|
+
const vidaPedida = (0, shelf_js_1.isShelfLife)(options?.shelfLife) ? options.shelfLife : undefined;
|
|
753
783
|
let skippedRetired = null;
|
|
754
784
|
// A retired decision, pattern, error or debt must not come back through
|
|
755
785
|
// learn: the sidecar says so, and the index already hides it.
|
|
@@ -822,6 +852,16 @@ class Cortex {
|
|
|
822
852
|
vistos.add(testigo);
|
|
823
853
|
confirmations = existente.confirmations ?? 1;
|
|
824
854
|
let cambiado = false;
|
|
855
|
+
if (vidaPedida && vidaPedida !== existente.shelf_life) {
|
|
856
|
+
// Lengthening an explicit class (volatile → durable, say). On a
|
|
857
|
+
// shared neuron the more volatile value comes back from the
|
|
858
|
+
// log on the next sync; the server warns about it.
|
|
859
|
+
if (existente.shelf_life && (0, shelf_js_1.mostVolatile)(existente.shelf_life, vidaPedida) === existente.shelf_life) {
|
|
860
|
+
shelfLengthened = true;
|
|
861
|
+
}
|
|
862
|
+
existente.shelf_life = vidaPedida;
|
|
863
|
+
cambiado = true;
|
|
864
|
+
}
|
|
825
865
|
if (options?.confidence !== undefined && options.confidence !== existente.confidence) {
|
|
826
866
|
existente.confidence = options.confidence;
|
|
827
867
|
cambiado = true;
|
|
@@ -843,6 +883,24 @@ class Cortex {
|
|
|
843
883
|
cambiado = true;
|
|
844
884
|
}
|
|
845
885
|
}
|
|
886
|
+
// Saying the same line again, and nothing else, is a check
|
|
887
|
+
// (shelf life): the clock restarts. Not when the call also
|
|
888
|
+
// brings keywords, a confidence or a class — that is editing the
|
|
889
|
+
// line, and the server itself asks for exactly that re-learn to
|
|
890
|
+
// add keywords, so counting it would mark an unchecked line as
|
|
891
|
+
// checked. Not for the miner either: re-reading an old
|
|
892
|
+
// transcript is not looking at the source.
|
|
893
|
+
if (options?.source !== 'miner' && !cambiado) {
|
|
894
|
+
existente.verified = (0, fs_js_1.now)();
|
|
895
|
+
reconfirmed = true;
|
|
896
|
+
// One op per line per session: a retry or the re-save after a
|
|
897
|
+
// compaction must not add a line to the team's log each time.
|
|
898
|
+
const yaVerificadas = (this.tally.verifiedEmitted ??= new Set());
|
|
899
|
+
if (!yaVerificadas.has(testigo)) {
|
|
900
|
+
yaVerificadas.add(testigo);
|
|
901
|
+
verificar = { eid: existente.id || (0, hash_js_1.factId)(existente.text), at: existente.verified };
|
|
902
|
+
}
|
|
903
|
+
}
|
|
846
904
|
if (cambiado) {
|
|
847
905
|
// Note for shared neurons: materialize takes max(conf) and
|
|
848
906
|
// unions keys, so lowering confidence or dropping an alias
|
|
@@ -850,8 +908,11 @@ class Cortex {
|
|
|
850
908
|
updatedInPlace = true;
|
|
851
909
|
this.tally.topics.add(n.id);
|
|
852
910
|
emitir = { kind: 'fact', text: existente.text, fid: existente.id || id,
|
|
853
|
-
conf: existente.confidence ?? 1, at: existente.added, src: existente.source, keys: existente.keys
|
|
911
|
+
conf: existente.confidence ?? 1, at: existente.added, src: existente.source, keys: existente.keys,
|
|
912
|
+
...(existente.shelf_life ? { shelf: existente.shelf_life } : {}) };
|
|
854
913
|
}
|
|
914
|
+
const sv = (0, shelf_js_1.shelfOfFact)(existente);
|
|
915
|
+
shelf = { shelf_life: sv.shelf, inferred: sv.inferred, ...(sv.reason ? { reason: sv.reason } : {}) };
|
|
855
916
|
}
|
|
856
917
|
if (!isDuplicate) {
|
|
857
918
|
if (options?.supersedes?.length) {
|
|
@@ -876,13 +937,18 @@ class Cortex {
|
|
|
876
937
|
fact.supersedes = options.supersedes;
|
|
877
938
|
if (keys.length)
|
|
878
939
|
fact.keys = keys;
|
|
940
|
+
if (vidaPedida)
|
|
941
|
+
fact.shelf_life = vidaPedida;
|
|
942
|
+
const sv = (0, shelf_js_1.shelfOfFact)(fact);
|
|
943
|
+
shelf = { shelf_life: sv.shelf, inferred: sv.inferred, ...(sv.reason ? { reason: sv.reason } : {}) };
|
|
879
944
|
n.facts.push(fact);
|
|
880
945
|
if (fact.source !== 'miner')
|
|
881
946
|
(this.tally.witnessed ??= new Set()).add(`${n.id}|${id}`);
|
|
882
947
|
this.tally.facts++;
|
|
883
948
|
this.tally.topics.add(n.id);
|
|
884
949
|
emitir = { kind: 'fact', text: content, fid: id,
|
|
885
|
-
conf: fact.confidence, at: fact.added, src: fact.source, keys: fact.keys
|
|
950
|
+
conf: fact.confidence, at: fact.added, src: fact.source, keys: fact.keys,
|
|
951
|
+
...(fact.shelf_life ? { shelf: fact.shelf_life } : {}) };
|
|
886
952
|
}
|
|
887
953
|
break;
|
|
888
954
|
}
|
|
@@ -970,11 +1036,18 @@ class Cortex {
|
|
|
970
1036
|
// key and the least likely to be worth sharing.
|
|
971
1037
|
if (emitir)
|
|
972
1038
|
await this.emit(final.id, emitir);
|
|
1039
|
+
// A reconfirming re-learn reaches the team as a check: the latest wins.
|
|
1040
|
+
const v = verificar;
|
|
1041
|
+
if (v)
|
|
1042
|
+
await this.emit(final.id, { kind: 'verify', eid: v.eid, ekind: 'fact', at: v.at });
|
|
973
1043
|
return {
|
|
974
1044
|
neuron: final, action, superseded, supersedes_unmatched: supersedesUnmatched,
|
|
975
1045
|
near_duplicates: nearDuplicates, redacted: limpio.found,
|
|
976
1046
|
duplicate, updated_in_place: updatedInPlace, skipped_retired: null,
|
|
977
1047
|
...(confirmations !== undefined ? { confirmations } : {}),
|
|
1048
|
+
...(shelf ? { shelf } : {}),
|
|
1049
|
+
...(reconfirmed ? { reconfirmed } : {}),
|
|
1050
|
+
...(shelfLengthened ? { shelf_lengthened: true } : {}),
|
|
978
1051
|
};
|
|
979
1052
|
}
|
|
980
1053
|
/**
|
|
@@ -1025,6 +1098,93 @@ class Cortex {
|
|
|
1025
1098
|
}
|
|
1026
1099
|
return { neuron: (actualizada || neuron), revised, unmatched };
|
|
1027
1100
|
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Reconfirm: the caller checked these lines against their source and they
|
|
1103
|
+
* still hold, so their shelf-life clock restarts now.
|
|
1104
|
+
*
|
|
1105
|
+
* `facts` match ACTIVE facts by id or exact text (trimmed, case-insensitive);
|
|
1106
|
+
* `entries` match live decisions and patterns by entry id or exact text —
|
|
1107
|
+
* the two kinds whose class is durable. A retired fact or entry is not
|
|
1108
|
+
* verifiable: it comes back in `retired` with its status (reactivate it with
|
|
1109
|
+
* status active first, if it holds again), and in `unmatched` like any miss.
|
|
1110
|
+
* Preferences, errors and debts never go stale, so there is nothing to
|
|
1111
|
+
* reconfirm on them; they are unmatched.
|
|
1112
|
+
*
|
|
1113
|
+
* Each stamp is emitted as a verify op, so on a shared neuron a teammate's
|
|
1114
|
+
* clock restarts too: the fact is shared, and so is the world it describes.
|
|
1115
|
+
*/
|
|
1116
|
+
async verify(neuronRef, targets) {
|
|
1117
|
+
const facts = (targets.facts || []).filter(t => typeof t === 'string');
|
|
1118
|
+
const entries = (targets.entries || []).filter(t => typeof t === 'string');
|
|
1119
|
+
const neuron = (await this.peek(neuronRef)) || (await this.findByName(neuronRef));
|
|
1120
|
+
if (!neuron)
|
|
1121
|
+
return { neuron: null, verified: [], unmatched: [...facts, ...entries], retired: [] };
|
|
1122
|
+
const verified = [];
|
|
1123
|
+
let unmatched = [];
|
|
1124
|
+
let retired = [];
|
|
1125
|
+
const emitir = [];
|
|
1126
|
+
const actualizada = await (0, fs_js_1.updateJSON)(this.brain.paths.neuron(neuron.id), current => {
|
|
1127
|
+
const n = current || neuron;
|
|
1128
|
+
const cuando = (0, fs_js_1.now)();
|
|
1129
|
+
verified.length = 0;
|
|
1130
|
+
unmatched = [];
|
|
1131
|
+
retired = [];
|
|
1132
|
+
emitir.length = 0;
|
|
1133
|
+
for (const t of facts) {
|
|
1134
|
+
const w = t.trim().toLowerCase();
|
|
1135
|
+
const hits = n.facts.filter(f => (f.id || (0, hash_js_1.factId)(f.text)).toLowerCase() === w || f.text.trim().toLowerCase() === w);
|
|
1136
|
+
const vivos = hits.filter(f => f.status !== 'superseded' && f.status !== 'retracted');
|
|
1137
|
+
if (vivos.length === 0) {
|
|
1138
|
+
const r = hits.find(f => f.status === 'superseded' || f.status === 'retracted');
|
|
1139
|
+
if (r)
|
|
1140
|
+
retired.push({ target: t, id: r.id || (0, hash_js_1.factId)(r.text), status: r.status });
|
|
1141
|
+
unmatched.push(t);
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
for (const f of vivos) {
|
|
1145
|
+
f.id = f.id || (0, hash_js_1.factId)(f.text);
|
|
1146
|
+
f.verified = cuando;
|
|
1147
|
+
if (!verified.includes(f.id))
|
|
1148
|
+
verified.push(f.id);
|
|
1149
|
+
emitir.push({ eid: f.id, ekind: 'fact', at: cuando });
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
const candidatas = [
|
|
1153
|
+
...n.decisions.map(d => ({ text: d.text || '', ids: [(0, ops_js_1.entryId)(d.text || ''), ...(d.id ? [d.id] : [])] })),
|
|
1154
|
+
...n.patterns.map(p => ({ text: p, ids: [(0, ops_js_1.entryId)(p)] })),
|
|
1155
|
+
];
|
|
1156
|
+
for (const t of entries) {
|
|
1157
|
+
const w = (0, ops_js_1.normalizeText)(t).toLowerCase();
|
|
1158
|
+
const hits = candidatas.filter(c => c.ids.some(i => i.toLowerCase() === w) || (0, ops_js_1.normalizeText)(c.text).toLowerCase() === w);
|
|
1159
|
+
const vivas = hits.filter(c => !n.entry_status?.[(0, ops_js_1.entryId)(c.text)]);
|
|
1160
|
+
if (vivas.length === 0) {
|
|
1161
|
+
const r = hits[0];
|
|
1162
|
+
const est = r ? n.entry_status?.[(0, ops_js_1.entryId)(r.text)] : undefined;
|
|
1163
|
+
if (r && est)
|
|
1164
|
+
retired.push({ target: t, id: (0, ops_js_1.entryId)(r.text), status: est.status });
|
|
1165
|
+
unmatched.push(t);
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
for (const c of vivas) {
|
|
1169
|
+
const k = (0, ops_js_1.entryId)(c.text);
|
|
1170
|
+
(n.entry_verified ||= {})[k] = cuando;
|
|
1171
|
+
if (!verified.includes(k))
|
|
1172
|
+
verified.push(k);
|
|
1173
|
+
emitir.push({ eid: k, ekind: 'entry', at: cuando });
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (verified.length === 0)
|
|
1177
|
+
return null;
|
|
1178
|
+
n.last_accessed = cuando;
|
|
1179
|
+
return n;
|
|
1180
|
+
});
|
|
1181
|
+
const final = (actualizada || neuron);
|
|
1182
|
+
// No reindex: neither the text nor the indexed date changed. `since`
|
|
1183
|
+
// filters on when a line was recorded, not on when it was last checked.
|
|
1184
|
+
for (const e of emitir)
|
|
1185
|
+
await this.emit(final.id, { kind: 'verify', ...e });
|
|
1186
|
+
return { neuron: final, verified: [...verified], unmatched, retired };
|
|
1187
|
+
}
|
|
1028
1188
|
/**
|
|
1029
1189
|
* Which active facts does this text closely resemble?
|
|
1030
1190
|
*
|
|
@@ -1438,6 +1598,16 @@ class Cortex {
|
|
|
1438
1598
|
if (Object.keys(current.entry_status).length === 0)
|
|
1439
1599
|
delete current.entry_status;
|
|
1440
1600
|
}
|
|
1601
|
+
// Verifications go with their entry too.
|
|
1602
|
+
if (current.entry_verified) {
|
|
1603
|
+
const vivos = liveEntryKeys(current);
|
|
1604
|
+
for (const k of Object.keys(current.entry_verified)) {
|
|
1605
|
+
if (!vivos.has(k))
|
|
1606
|
+
delete current.entry_verified[k];
|
|
1607
|
+
}
|
|
1608
|
+
if (Object.keys(current.entry_verified).length === 0)
|
|
1609
|
+
delete current.entry_verified;
|
|
1610
|
+
}
|
|
1441
1611
|
// And for provenance: a forgotten teammate line that comes back through
|
|
1442
1612
|
// a sync replay must be labelled by that replay, not by a stale key.
|
|
1443
1613
|
if (current.entry_source) {
|
|
@@ -1627,7 +1797,10 @@ class Cortex {
|
|
|
1627
1797
|
const fid = f.id || (0, hash_js_1.factId)(f.text);
|
|
1628
1798
|
if (teniaFact.has(fid))
|
|
1629
1799
|
continue;
|
|
1630
|
-
await this.emit(final.id, { kind: 'fact', text: f.text, fid, conf: f.confidence ?? 1, at: f.added, src: f.source, keys: f.keys
|
|
1800
|
+
await this.emit(final.id, { kind: 'fact', text: f.text, fid, conf: f.confidence ?? 1, at: f.added, src: f.source, keys: f.keys,
|
|
1801
|
+
...(f.shelf_life ? { shelf: f.shelf_life } : {}) });
|
|
1802
|
+
if (f.verified)
|
|
1803
|
+
await this.emit(final.id, { kind: 'verify', eid: fid, ekind: 'fact', at: f.verified });
|
|
1631
1804
|
// A moved fact that was already retired must arrive retired, or the
|
|
1632
1805
|
// teammates would see as current what the source had superseded.
|
|
1633
1806
|
if (f.status === 'superseded' || f.status === 'retracted') {
|
|
@@ -1656,6 +1829,12 @@ class Cortex {
|
|
|
1656
1829
|
if (!teniaDebt.has((0, ops_js_1.normalizeText)(d)))
|
|
1657
1830
|
await this.emit(final.id, { kind: 'debt', text: d, at: fecha(d) });
|
|
1658
1831
|
}
|
|
1832
|
+
// Checks of decisions and patterns that arrived with them, or are newer than the target's.
|
|
1833
|
+
for (const [k, at] of Object.entries(final.entry_verified || {})) {
|
|
1834
|
+
if (antes.entry_verified?.[k] === at)
|
|
1835
|
+
continue;
|
|
1836
|
+
await this.emit(final.id, { kind: 'verify', eid: k, ekind: 'entry', at });
|
|
1837
|
+
}
|
|
1659
1838
|
// Preferences are never emitted, here as in learn.
|
|
1660
1839
|
}
|
|
1661
1840
|
/**
|
|
@@ -1700,6 +1879,7 @@ class Cortex {
|
|
|
1700
1879
|
tags: [], connections: [], summary: '', heat: 0, access_count: 0,
|
|
1701
1880
|
entry_dates: { ...(from.entry_dates || {}) },
|
|
1702
1881
|
entry_status: { ...(from.entry_status || {}) },
|
|
1882
|
+
entry_verified: { ...(from.entry_verified || {}) },
|
|
1703
1883
|
};
|
|
1704
1884
|
delete parte.map;
|
|
1705
1885
|
const textos = [
|