crbro-memory 2.9.1 → 2.11.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/LICENSE +21 -21
- package/README.md +256 -456
- package/SECURITY.md +135 -8
- package/bin/crbro.mjs +555 -299
- package/dist/build-id.json +10 -0
- package/dist/cli/common.js +211 -0
- package/dist/cli/export.js +522 -0
- package/dist/cli/import.js +683 -0
- package/dist/cli/markdown.js +313 -0
- package/dist/cli/mcpclient.js +107 -0
- package/dist/cli/serve.js +453 -0
- package/dist/cli/view.js +358 -0
- package/dist/cli/viewer-page.js +378 -0
- package/dist/daemon/daemon.js +119 -11
- package/dist/daemon/endpoint.js +222 -21
- package/dist/daemon/lines.js +102 -13
- package/dist/daemon/proxy.js +480 -125
- package/dist/daemon/surface.js +116 -0
- package/dist/daemon/trace.js +73 -0
- package/dist/engine/atrest.js +132 -0
- package/dist/engine/backup.js +143 -19
- package/dist/engine/brain.js +22 -6
- package/dist/engine/codexconfig.js +476 -0
- package/dist/engine/cortex.js +762 -311
- package/dist/engine/doctor.js +676 -0
- package/dist/engine/evidence.js +171 -0
- package/dist/engine/heat.js +81 -37
- package/dist/engine/hippocampus.js +50 -36
- package/dist/engine/hookrefresh.js +296 -0
- package/dist/engine/hookverify.js +15 -3
- package/dist/engine/installer.js +1022 -0
- package/dist/engine/installio.js +279 -0
- package/dist/engine/keychain.js +735 -144
- package/dist/engine/maintenance.js +355 -87
- package/dist/engine/modinstall.js +98 -45
- package/dist/engine/prefrontal.js +126 -69
- package/dist/engine/projects.js +80 -0
- package/dist/engine/quarantine.js +204 -0
- package/dist/engine/secrets.js +41 -5
- package/dist/engine/shelf.js +34 -11
- package/dist/engine/synapses.js +183 -80
- package/dist/engine/triggers.js +141 -11
- package/dist/index.js +37 -3
- package/dist/mcp-surface.json +1 -0
- package/dist/miner/extractor.js +64 -17
- package/dist/miner/scheduler.js +144 -68
- package/dist/search/index.js +810 -176
- package/dist/search/quality.js +31 -0
- package/dist/search/semantic.js +208 -18
- package/dist/search/tokenize.js +44 -0
- package/dist/server.js +649 -343
- package/dist/sync/git.js +203 -72
- package/dist/sync/materialize.js +33 -8
- package/dist/sync/space.js +286 -101
- package/dist/sync/validate.js +204 -0
- package/dist/utils/budget.js +15 -2
- package/dist/utils/fs.js +40 -6
- package/dist/utils/ids.js +128 -14
- package/dist/utils/lock.js +219 -52
- package/hooks/crbro-codex.mjs +284 -0
- package/hooks/crbro-guard.mjs +281 -173
- package/hooks/crbro-hooks.shipped.json +20 -0
- package/hooks/crbro-lifecycle.mjs +16 -4
- package/hooks/crbro-subagent.mjs +180 -180
- package/package.json +12 -8
- package/dist/daemon/daemon.d.ts +0 -25
- package/dist/daemon/daemon.d.ts.map +0 -1
- package/dist/daemon/daemon.js.map +0 -1
- package/dist/daemon/endpoint.d.ts +0 -66
- package/dist/daemon/endpoint.d.ts.map +0 -1
- package/dist/daemon/endpoint.js.map +0 -1
- package/dist/daemon/lines.d.ts +0 -55
- package/dist/daemon/lines.d.ts.map +0 -1
- package/dist/daemon/lines.js.map +0 -1
- package/dist/daemon/proxy.d.ts +0 -55
- package/dist/daemon/proxy.d.ts.map +0 -1
- package/dist/daemon/proxy.js.map +0 -1
- package/dist/engine/backup.d.ts +0 -68
- package/dist/engine/backup.d.ts.map +0 -1
- package/dist/engine/backup.js.map +0 -1
- package/dist/engine/brain.d.ts +0 -79
- package/dist/engine/brain.d.ts.map +0 -1
- package/dist/engine/brain.js.map +0 -1
- package/dist/engine/cortex.d.ts +0 -670
- package/dist/engine/cortex.d.ts.map +0 -1
- package/dist/engine/cortex.js.map +0 -1
- package/dist/engine/dates.d.ts +0 -28
- package/dist/engine/dates.d.ts.map +0 -1
- package/dist/engine/dates.js.map +0 -1
- package/dist/engine/heat.d.ts +0 -28
- package/dist/engine/heat.d.ts.map +0 -1
- package/dist/engine/heat.js.map +0 -1
- package/dist/engine/hippocampus.d.ts +0 -60
- package/dist/engine/hippocampus.d.ts.map +0 -1
- package/dist/engine/hippocampus.js.map +0 -1
- package/dist/engine/hookverify.d.ts +0 -42
- package/dist/engine/hookverify.d.ts.map +0 -1
- package/dist/engine/hookverify.js.map +0 -1
- package/dist/engine/keychain.d.ts +0 -32
- package/dist/engine/keychain.d.ts.map +0 -1
- package/dist/engine/keychain.js.map +0 -1
- package/dist/engine/maintenance.d.ts +0 -218
- package/dist/engine/maintenance.d.ts.map +0 -1
- package/dist/engine/maintenance.js.map +0 -1
- package/dist/engine/modinstall.d.ts +0 -215
- package/dist/engine/modinstall.d.ts.map +0 -1
- package/dist/engine/modinstall.js.map +0 -1
- package/dist/engine/postmortem.d.ts +0 -81
- package/dist/engine/postmortem.d.ts.map +0 -1
- package/dist/engine/postmortem.js.map +0 -1
- package/dist/engine/prefrontal.d.ts +0 -61
- package/dist/engine/prefrontal.d.ts.map +0 -1
- package/dist/engine/prefrontal.js.map +0 -1
- package/dist/engine/secrets.d.ts +0 -30
- package/dist/engine/secrets.d.ts.map +0 -1
- package/dist/engine/secrets.js.map +0 -1
- package/dist/engine/shelf.d.ts +0 -156
- package/dist/engine/shelf.d.ts.map +0 -1
- package/dist/engine/shelf.js.map +0 -1
- package/dist/engine/source.d.ts +0 -6
- package/dist/engine/source.d.ts.map +0 -1
- package/dist/engine/source.js.map +0 -1
- package/dist/engine/synapses.d.ts +0 -82
- package/dist/engine/synapses.d.ts.map +0 -1
- package/dist/engine/synapses.js.map +0 -1
- package/dist/engine/triggers.d.ts +0 -53
- package/dist/engine/triggers.d.ts.map +0 -1
- package/dist/engine/triggers.js.map +0 -1
- package/dist/engine/usage.d.ts +0 -61
- package/dist/engine/usage.d.ts.map +0 -1
- package/dist/engine/usage.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/miner/extractor.d.ts +0 -12
- package/dist/miner/extractor.d.ts.map +0 -1
- package/dist/miner/extractor.js.map +0 -1
- package/dist/miner/index.d.ts +0 -61
- package/dist/miner/index.d.ts.map +0 -1
- package/dist/miner/index.js.map +0 -1
- package/dist/miner/scheduler.d.ts +0 -30
- package/dist/miner/scheduler.d.ts.map +0 -1
- package/dist/miner/scheduler.js.map +0 -1
- package/dist/miner/types.d.ts +0 -11
- package/dist/miner/types.d.ts.map +0 -1
- package/dist/miner/types.js.map +0 -1
- package/dist/search/index.d.ts +0 -323
- package/dist/search/index.d.ts.map +0 -1
- package/dist/search/index.js.map +0 -1
- package/dist/search/semantic.d.ts +0 -80
- package/dist/search/semantic.d.ts.map +0 -1
- package/dist/search/semantic.js.map +0 -1
- package/dist/search/synonyms.d.ts +0 -2
- package/dist/search/synonyms.d.ts.map +0 -1
- package/dist/search/synonyms.js.map +0 -1
- package/dist/search/tokenize.d.ts +0 -39
- package/dist/search/tokenize.d.ts.map +0 -1
- package/dist/search/tokenize.js.map +0 -1
- package/dist/server.d.ts +0 -41
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/sync/git.d.ts +0 -36
- package/dist/sync/git.d.ts.map +0 -1
- package/dist/sync/git.js.map +0 -1
- package/dist/sync/materialize.d.ts +0 -35
- package/dist/sync/materialize.d.ts.map +0 -1
- package/dist/sync/materialize.js.map +0 -1
- package/dist/sync/ops.d.ts +0 -142
- package/dist/sync/ops.d.ts.map +0 -1
- package/dist/sync/ops.js.map +0 -1
- package/dist/sync/space.d.ts +0 -116
- package/dist/sync/space.d.ts.map +0 -1
- package/dist/sync/space.js.map +0 -1
- package/dist/types/index.d.ts +0 -346
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/utils/budget.d.ts +0 -44
- package/dist/utils/budget.d.ts.map +0 -1
- package/dist/utils/budget.js.map +0 -1
- package/dist/utils/fs.d.ts +0 -59
- package/dist/utils/fs.d.ts.map +0 -1
- package/dist/utils/fs.js.map +0 -1
- package/dist/utils/hash.d.ts +0 -20
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hash.js.map +0 -1
- package/dist/utils/ids.d.ts +0 -47
- package/dist/utils/ids.d.ts.map +0 -1
- package/dist/utils/ids.js.map +0 -1
- package/dist/utils/lock.d.ts +0 -11
- package/dist/utils/lock.d.ts.map +0 -1
- package/dist/utils/lock.js.map +0 -1
- package/dist/utils/transcripts.d.ts +0 -63
- package/dist/utils/transcripts.d.ts.map +0 -1
- package/dist/utils/transcripts.js.map +0 -1
- package/dist/version.d.ts +0 -26
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sanitizeReference = sanitizeReference;
|
|
4
|
+
exports.readReference = readReference;
|
|
5
|
+
exports.sanitizeTemporal = sanitizeTemporal;
|
|
6
|
+
exports.makeVerification = makeVerification;
|
|
7
|
+
exports.readVerification = readVerification;
|
|
8
|
+
exports.mergeCheck = mergeCheck;
|
|
9
|
+
exports.mergeFactEvidence = mergeFactEvidence;
|
|
10
|
+
exports.mergeEntryEvidence = mergeEntryEvidence;
|
|
11
|
+
exports.describeEvidence = describeEvidence;
|
|
12
|
+
const secrets_js_1 = require("./secrets.js");
|
|
13
|
+
const shelf_js_1 = require("./shelf.js");
|
|
14
|
+
function safeText(value) {
|
|
15
|
+
if (typeof value !== 'string' || !value.trim() || value.length > 4096 || /[\u0000-\u001f]/.test(value)) {
|
|
16
|
+
throw new Error('Invalid source reference text.');
|
|
17
|
+
}
|
|
18
|
+
return (0, secrets_js_1.redact)(value.trim()).text;
|
|
19
|
+
}
|
|
20
|
+
/** Solo valida y redacta datos. Nunca abre archivos, URLs ni procesos. */
|
|
21
|
+
function sanitizeReference(value) {
|
|
22
|
+
if (!value || typeof value !== 'object')
|
|
23
|
+
throw new Error('Invalid source reference.');
|
|
24
|
+
const r = value;
|
|
25
|
+
if (r.kind !== 'file' && r.kind !== 'url' && r.kind !== 'person')
|
|
26
|
+
throw new Error('Invalid reference kind.');
|
|
27
|
+
const clean = safeText(r.value);
|
|
28
|
+
if (r.kind === 'url') {
|
|
29
|
+
let url;
|
|
30
|
+
try {
|
|
31
|
+
url = new URL(String(r.value));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new Error('Invalid reference URL.');
|
|
35
|
+
}
|
|
36
|
+
const secretKey = /token|secret|password|passwd|api.?key|authorization|credential|signature|^sig$|access.?key/i;
|
|
37
|
+
const unsafeParams = (params) => [...params].some(([key, value]) => secretKey.test(key) || (0, secrets_js_1.redact)(value).found.length > 0);
|
|
38
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || clean !== String(r.value).trim()
|
|
39
|
+
|| unsafeParams(url.searchParams) || unsafeParams(new URLSearchParams(url.hash.slice(1)))) {
|
|
40
|
+
throw new Error('Unsafe reference URL: credentials or secret parameters are not allowed.');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
kind: r.kind, value: clean,
|
|
45
|
+
...(r.field !== undefined ? { field: safeText(r.field) } : {}),
|
|
46
|
+
...(r.project !== undefined ? { project: safeText(r.project) } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function readReference(value) {
|
|
50
|
+
try {
|
|
51
|
+
return sanitizeReference(value);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function sanitizeTemporal(value) {
|
|
58
|
+
if (value === undefined || value === 'current' || value === 'historical')
|
|
59
|
+
return value;
|
|
60
|
+
throw new Error('Invalid temporal value.');
|
|
61
|
+
}
|
|
62
|
+
function makeVerification(evidence, at) {
|
|
63
|
+
if (!Number.isFinite(Date.parse(at)))
|
|
64
|
+
throw new Error('Invalid verification date.');
|
|
65
|
+
if (evidence === undefined)
|
|
66
|
+
return { method: 'unspecified', at };
|
|
67
|
+
if (!evidence || (evidence.method !== 'source' && evidence.method !== 'user'))
|
|
68
|
+
throw new Error('Invalid verification method.');
|
|
69
|
+
const reference = evidence.reference === undefined ? undefined : sanitizeReference(evidence.reference);
|
|
70
|
+
if (evidence.method === 'source' && !reference)
|
|
71
|
+
throw new Error('Source verification requires a reference.');
|
|
72
|
+
return { method: evidence.method, at, ...(reference ? { reference } : {}) };
|
|
73
|
+
}
|
|
74
|
+
function readVerification(value) {
|
|
75
|
+
if (!value || typeof value !== 'object')
|
|
76
|
+
return undefined;
|
|
77
|
+
const v = value;
|
|
78
|
+
if (typeof v.at !== 'string' || !(0, shelf_js_1.latestOf)(undefined, v.at))
|
|
79
|
+
return undefined;
|
|
80
|
+
try {
|
|
81
|
+
if (v.method === 'unspecified')
|
|
82
|
+
return { method: 'unspecified', at: v.at };
|
|
83
|
+
return makeVerification(v, v.at);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Fecha y evidencia forman una unidad: una fecha legacy nueva no hereda una fuente vieja. */
|
|
90
|
+
function mergeCheck(a, b) {
|
|
91
|
+
const invalidated = (0, shelf_js_1.latestOf)(a.verification_invalidated_at, b.verification_invalidated_at);
|
|
92
|
+
const cutoff = invalidated ? Date.parse(invalidated) : -Infinity;
|
|
93
|
+
const candidates = [];
|
|
94
|
+
for (const c of [a, b]) {
|
|
95
|
+
if (typeof c.verified === 'string' && (0, shelf_js_1.latestOf)(undefined, c.verified))
|
|
96
|
+
candidates.push({ at: c.verified });
|
|
97
|
+
const v = readVerification(c.verification);
|
|
98
|
+
if (v)
|
|
99
|
+
candidates.push({ at: v.at, verification: v });
|
|
100
|
+
}
|
|
101
|
+
candidates.sort((x, y) => Date.parse(x.at) - Date.parse(y.at)
|
|
102
|
+
|| compare(JSON.stringify(x.verification || null), JSON.stringify(y.verification || null))
|
|
103
|
+
|| compare(x.at, y.at));
|
|
104
|
+
const chosen = candidates.filter(c => Date.parse(c.at) > cutoff).at(-1);
|
|
105
|
+
return {
|
|
106
|
+
...(invalidated ? { verification_invalidated_at: invalidated } : {}),
|
|
107
|
+
...(chosen ? { verified: chosen.at, ...(chosen.verification ? { verification: chosen.verification } : {}) } : {}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
111
|
+
/** Unión estable de metadatos sin fecha propia. Current gana para no ocultar caducidad. */
|
|
112
|
+
function mergeFactEvidence(target, other) {
|
|
113
|
+
const check = mergeCheck(target, other);
|
|
114
|
+
const resetTime = (f) => Date.parse((0, shelf_js_1.latestOf)(undefined, f.verification_invalidated_at) || '') || 0;
|
|
115
|
+
const a = resetTime(target), b = resetTime(other);
|
|
116
|
+
const metadata = a === b ? [target, other] : [a > b ? target : other];
|
|
117
|
+
const references = metadata.map(f => readReference(f.reference))
|
|
118
|
+
.filter((r) => !!r).sort((a, b) => compare(JSON.stringify(a), JSON.stringify(b)));
|
|
119
|
+
if (references.length)
|
|
120
|
+
target.reference = references.at(-1);
|
|
121
|
+
else
|
|
122
|
+
delete target.reference;
|
|
123
|
+
const temporal = metadata.map(f => f.temporal);
|
|
124
|
+
if (temporal.includes('current'))
|
|
125
|
+
target.temporal = 'current';
|
|
126
|
+
else if (temporal.includes('historical'))
|
|
127
|
+
target.temporal = 'historical';
|
|
128
|
+
else
|
|
129
|
+
delete target.temporal;
|
|
130
|
+
if (check.verification_invalidated_at)
|
|
131
|
+
target.verification_invalidated_at = check.verification_invalidated_at;
|
|
132
|
+
if (check.verified)
|
|
133
|
+
target.verified = check.verified;
|
|
134
|
+
else if (check.verification_invalidated_at)
|
|
135
|
+
delete target.verified;
|
|
136
|
+
if (check.verification)
|
|
137
|
+
target.verification = check.verification;
|
|
138
|
+
else
|
|
139
|
+
delete target.verification;
|
|
140
|
+
}
|
|
141
|
+
function mergeEntryEvidence(target, source, live) {
|
|
142
|
+
const dates = {};
|
|
143
|
+
const evidence = {};
|
|
144
|
+
for (const k of [...live].sort()) {
|
|
145
|
+
const check = mergeCheck({ verified: target.entry_verified?.[k], verification: target.entry_verification?.[k] }, { verified: source.entry_verified?.[k], verification: source.entry_verification?.[k] });
|
|
146
|
+
if (check.verified)
|
|
147
|
+
dates[k] = check.verified;
|
|
148
|
+
if (check.verification)
|
|
149
|
+
evidence[k] = check.verification;
|
|
150
|
+
}
|
|
151
|
+
if (Object.keys(dates).length)
|
|
152
|
+
target.entry_verified = dates;
|
|
153
|
+
else
|
|
154
|
+
delete target.entry_verified;
|
|
155
|
+
if (Object.keys(evidence).length)
|
|
156
|
+
target.entry_verification = evidence;
|
|
157
|
+
else
|
|
158
|
+
delete target.entry_verification;
|
|
159
|
+
}
|
|
160
|
+
/** Proyección para recall/inspect; no modifica el historial ni inventa fuentes legacy. */
|
|
161
|
+
function describeEvidence(neuron, id, kind, text) {
|
|
162
|
+
const f = kind === 'fact' ? neuron.facts.find(f => f.id === id || f.text === text) : undefined;
|
|
163
|
+
const check = mergeCheck(f || { verified: neuron.entry_verified?.[id], verification: neuron.entry_verification?.[id] }, {});
|
|
164
|
+
const reference = readReference(f?.reference);
|
|
165
|
+
const temporal = f?.temporal === 'current' || f?.temporal === 'historical' ? f.temporal : undefined;
|
|
166
|
+
return {
|
|
167
|
+
...(reference ? { reference } : {}), ...(temporal ? { temporal } : {}),
|
|
168
|
+
...(check.verified ? { verification: check.verification || { method: 'unspecified', at: check.verified } } : {}),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=evidence.js.map
|
package/dist/engine/heat.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// ─── CRBRO Heat Score Engine ─────────────────────────────────────
|
|
3
3
|
// Calculates relevance score based on frequency, recency, and connectivity
|
|
4
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
6
|
+
};
|
|
4
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
8
|
exports.HeatEngine = void 0;
|
|
6
9
|
exports.recencyScore = recencyScore;
|
|
7
10
|
exports.frequencyScore = frequencyScore;
|
|
8
11
|
exports.connectivityScore = connectivityScore;
|
|
9
12
|
exports.calculateHeat = calculateHeat;
|
|
13
|
+
exports.heatStatePath = heatStatePath;
|
|
14
|
+
exports.readHeatState = readHeatState;
|
|
15
|
+
const node_fs_1 = require("node:fs");
|
|
16
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
17
|
const fs_js_1 = require("../utils/fs.js");
|
|
11
18
|
// Weight constants
|
|
12
19
|
const WEIGHT_FREQUENCY = 0.35;
|
|
@@ -56,67 +63,104 @@ function calculateHeat(accessCount, lastAccessed, connectionCount, maxAccessCoun
|
|
|
56
63
|
// Round to 3 decimal places
|
|
57
64
|
return Math.round(heat * 1000) / 1000;
|
|
58
65
|
}
|
|
59
|
-
|
|
66
|
+
function heatStatePath(brain) {
|
|
67
|
+
return node_path_1.default.join(brain.paths.prefrontal, 'heat_state.json');
|
|
68
|
+
}
|
|
69
|
+
/** The derived heat state, or null when never computed or unreadable (it is rebuilt then). */
|
|
70
|
+
async function readHeatState(brain) {
|
|
71
|
+
try {
|
|
72
|
+
const s = await (0, fs_js_1.readJSON)(heatStatePath(brain));
|
|
73
|
+
return s && s.version === 1 && s.neurons && typeof s.neurons === 'object' ? s : null;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
60
79
|
class HeatEngine {
|
|
61
80
|
brain;
|
|
62
81
|
constructor(brain) {
|
|
63
82
|
this.brain = brain;
|
|
64
83
|
}
|
|
84
|
+
/** Neuron files the last pass could not parse: skipped, never fatal. */
|
|
85
|
+
lastUnreadable = [];
|
|
86
|
+
/** How many neuron files the last pass actually read (the rest came from the state). */
|
|
87
|
+
lastRead = 0;
|
|
65
88
|
/**
|
|
66
89
|
* Recalculate heat scores for all neurons.
|
|
67
90
|
* Returns updated hot topics list.
|
|
68
91
|
*/
|
|
69
92
|
async recalculate() {
|
|
70
93
|
const ids = await (0, fs_js_1.listJSONFiles)(this.brain.paths.cortex);
|
|
71
|
-
|
|
94
|
+
const previo = (await readHeatState(this.brain))?.neurons || {};
|
|
95
|
+
const entradas = {};
|
|
96
|
+
const ilegibles = [];
|
|
97
|
+
let leidas = 0;
|
|
98
|
+
// First pass — inputs: from the state when the file has not changed,
|
|
99
|
+
// from the file otherwise.
|
|
72
100
|
let maxAccess = 1;
|
|
73
101
|
let maxConnections = 1;
|
|
74
|
-
const neurons = [];
|
|
75
102
|
for (const id of ids) {
|
|
76
|
-
const
|
|
77
|
-
|
|
103
|
+
const file = this.brain.paths.neuron(id);
|
|
104
|
+
let st;
|
|
105
|
+
try {
|
|
106
|
+
st = await node_fs_1.promises.stat(file);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
78
109
|
continue;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (
|
|
83
|
-
|
|
110
|
+
}
|
|
111
|
+
const antes = previo[id];
|
|
112
|
+
let e;
|
|
113
|
+
if (antes && antes.mtime === st.mtimeMs && antes.size === st.size) {
|
|
114
|
+
e = { ...antes };
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
let neuron;
|
|
118
|
+
try {
|
|
119
|
+
neuron = await (0, fs_js_1.readJSON)(file);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
ilegibles.push(id); // corrupted: skipped, the integrity check names it
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!neuron)
|
|
126
|
+
continue;
|
|
127
|
+
leidas++;
|
|
128
|
+
e = {
|
|
129
|
+
heat: 0,
|
|
130
|
+
access_count: neuron.access_count || 0,
|
|
131
|
+
last_accessed: neuron.last_accessed || neuron.created || '',
|
|
132
|
+
connections: (neuron.connections || []).length,
|
|
133
|
+
name: neuron.name || id,
|
|
134
|
+
domain: neuron.domain || 'general',
|
|
135
|
+
size: st.size,
|
|
136
|
+
mtime: st.mtimeMs,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
entradas[id] = e;
|
|
140
|
+
if (e.access_count > maxAccess)
|
|
141
|
+
maxAccess = e.access_count;
|
|
142
|
+
if (e.connections > maxConnections)
|
|
143
|
+
maxConnections = e.connections;
|
|
84
144
|
}
|
|
85
|
-
// Second pass — calculate
|
|
145
|
+
// Second pass — calculate heat
|
|
86
146
|
const hotTopics = [];
|
|
87
|
-
for (const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// neurons on every pass was pure churn — between two consecutive runs
|
|
91
|
-
// not one of them changes — and each rewrite is a window in which a
|
|
92
|
-
// concurrent write from another client gets clobbered.
|
|
93
|
-
if (Math.abs((neuron.heat ?? 0) - heat) > 1e-9) {
|
|
94
|
-
// Under lock and re-reading: heat runs over every neuron, so without
|
|
95
|
-
// this it would happily overwrite whatever another client just saved.
|
|
96
|
-
await (0, fs_js_1.updateJSON)(this.brain.paths.neuron(neuron.id), current => {
|
|
97
|
-
if (!current)
|
|
98
|
-
return null;
|
|
99
|
-
current.heat = heat;
|
|
100
|
-
return current;
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
neuron.heat = heat;
|
|
104
|
-
hotTopics.push({
|
|
105
|
-
id: neuron.id,
|
|
106
|
-
name: neuron.name,
|
|
107
|
-
heat,
|
|
108
|
-
last_access: neuron.last_accessed,
|
|
109
|
-
domain: neuron.domain,
|
|
110
|
-
});
|
|
147
|
+
for (const [id, e] of Object.entries(entradas)) {
|
|
148
|
+
e.heat = calculateHeat(e.access_count, e.last_accessed, e.connections, maxAccess, maxConnections);
|
|
149
|
+
hotTopics.push({ id, name: e.name, heat: e.heat, last_access: e.last_accessed, domain: e.domain });
|
|
111
150
|
}
|
|
112
151
|
// Sort by heat descending
|
|
113
|
-
hotTopics.sort((a, b) => b.heat - a.heat);
|
|
152
|
+
hotTopics.sort((a, b) => b.heat - a.heat || (a.id < b.id ? -1 : 1));
|
|
153
|
+
const cuando = (0, fs_js_1.now)();
|
|
154
|
+
const estado = { version: 1, computed_at: cuando, neurons: entradas };
|
|
155
|
+
await (0, fs_js_1.writeJSON)(heatStatePath(this.brain), estado, { pretty: false });
|
|
114
156
|
// Save hot topics (top 20)
|
|
115
157
|
const result = {
|
|
116
158
|
topics: hotTopics.slice(0, 20),
|
|
117
|
-
last_recalculated:
|
|
159
|
+
last_recalculated: cuando,
|
|
118
160
|
};
|
|
119
161
|
await (0, fs_js_1.writeJSON)(this.brain.paths.hotTopics(), result);
|
|
162
|
+
this.lastUnreadable = ilegibles;
|
|
163
|
+
this.lastRead = leidas;
|
|
120
164
|
return result;
|
|
121
165
|
}
|
|
122
166
|
}
|
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
5
5
|
exports.Hippocampus = void 0;
|
|
6
6
|
exports.normalizeSessionId = normalizeSessionId;
|
|
7
7
|
const fs_js_1 = require("../utils/fs.js");
|
|
8
|
+
const quarantine_js_1 = require("./quarantine.js");
|
|
8
9
|
const ids_js_1 = require("../utils/ids.js");
|
|
9
10
|
const secrets_js_1 = require("./secrets.js");
|
|
10
11
|
const SESSION_PREFIX = 'session_';
|
|
@@ -35,50 +36,63 @@ class Hippocampus {
|
|
|
35
36
|
const limpio = (0, secrets_js_1.redact)(data.summary);
|
|
36
37
|
data = { ...data, summary: limpio.text };
|
|
37
38
|
const id = (0, ids_js_1.sessionId)();
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
39
|
+
let created = false;
|
|
40
|
+
const saved = await (0, fs_js_1.updateJSON)(this.brain.paths.session(id), existing => {
|
|
41
|
+
if (existing) {
|
|
42
|
+
// Append to existing session
|
|
43
|
+
existing.summary += `\n---\n${data.summary}`;
|
|
44
|
+
existing.topics_touched = [...new Set([...existing.topics_touched, ...data.topics_touched])];
|
|
45
|
+
existing.key_facts_added += data.key_facts_added || 0;
|
|
46
|
+
existing.decisions_made += data.decisions_made || 0;
|
|
47
|
+
existing.new_neurons_created += data.new_neurons_created || 0;
|
|
48
|
+
existing.synapses_updated += data.synapses_updated || 0;
|
|
49
|
+
return existing;
|
|
50
|
+
}
|
|
51
|
+
// Create new session log
|
|
52
|
+
const session = {
|
|
53
|
+
session_id: id,
|
|
54
|
+
date: (0, fs_js_1.today)(),
|
|
55
|
+
duration_estimate: 'unknown',
|
|
56
|
+
topics_touched: data.topics_touched,
|
|
57
|
+
summary: data.summary,
|
|
58
|
+
key_facts_added: data.key_facts_added || 0,
|
|
59
|
+
decisions_made: data.decisions_made || 0,
|
|
60
|
+
new_neurons_created: data.new_neurons_created || 0,
|
|
61
|
+
synapses_updated: data.synapses_updated || 0,
|
|
62
|
+
};
|
|
63
|
+
created = true;
|
|
64
|
+
return session;
|
|
65
|
+
});
|
|
64
66
|
// Update manifest
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
if (created) {
|
|
68
|
+
const manifest = await this.brain.getManifest();
|
|
69
|
+
await this.brain.updateManifest({ total_sessions: manifest.total_sessions + 1 });
|
|
70
|
+
}
|
|
71
|
+
return { ...saved, redacted: limpio.found };
|
|
68
72
|
}
|
|
69
73
|
/**
|
|
70
74
|
* List recent sessions.
|
|
71
75
|
*/
|
|
72
76
|
async listSessions(limit = 10) {
|
|
73
|
-
|
|
77
|
+
// The names are session_YYYY-MM-DD: they sort by day, so only the newest
|
|
78
|
+
// `limit` files are read (2.11). Boot asks for 3 and used to parse all
|
|
79
|
+
// 146 logs of the reference brain to keep 3. An unreadable log is skipped.
|
|
80
|
+
const ids = (await (0, fs_js_1.listJSONFiles)(this.brain.paths.hippocampus)).sort().reverse();
|
|
74
81
|
const sessions = [];
|
|
75
82
|
for (const id of ids) {
|
|
76
|
-
|
|
83
|
+
if (sessions.length >= limit)
|
|
84
|
+
break;
|
|
85
|
+
let session = null;
|
|
86
|
+
try {
|
|
87
|
+
session = await (0, fs_js_1.readJSON)(this.brain.paths.session(id));
|
|
88
|
+
}
|
|
89
|
+
catch { /* corrupt: skipped */ }
|
|
77
90
|
if (session)
|
|
78
91
|
sessions.push(session);
|
|
79
92
|
}
|
|
80
|
-
// Sort by date descending (newest first)
|
|
81
|
-
|
|
93
|
+
// Sort by date descending (newest first): a log whose file name is not a
|
|
94
|
+
// day id still lands by its own date among the ones read.
|
|
95
|
+
sessions.sort((a, b) => String(b.date || '').localeCompare(String(a.date || '')));
|
|
82
96
|
return sessions.slice(0, limit);
|
|
83
97
|
}
|
|
84
98
|
/**
|
|
@@ -124,9 +138,9 @@ class Hippocampus {
|
|
|
124
138
|
const log = await (0, fs_js_1.readJSON)(ruta);
|
|
125
139
|
if (!log)
|
|
126
140
|
return { session_id, removed: false, backup: null };
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
await (0,
|
|
141
|
+
// Same quarantine as the cortex: retention, and sealed when encryption at
|
|
142
|
+
// rest is on (throws, deleting nothing, when it is on and keyless).
|
|
143
|
+
const backup = await (0, quarantine_js_1.writeQuarantine)(this.brain.paths.quarantine, session_id, log, 'whole');
|
|
130
144
|
await (0, fs_js_1.deleteJSON)(ruta);
|
|
131
145
|
const manifest = await this.brain.getManifest();
|
|
132
146
|
await this.brain.updateManifest({ total_sessions: Math.max(0, manifest.total_sessions - 1) });
|