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
|
@@ -5,10 +5,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
5
5
|
exports.Maintenance = void 0;
|
|
6
6
|
const fs_js_1 = require("../utils/fs.js");
|
|
7
7
|
const lock_js_1 = require("../utils/lock.js");
|
|
8
|
+
const quarantine_js_1 = require("./quarantine.js");
|
|
8
9
|
const ops_js_1 = require("../sync/ops.js");
|
|
9
10
|
const dates_js_1 = require("./dates.js");
|
|
10
11
|
const tokenize_js_1 = require("../search/tokenize.js");
|
|
11
12
|
const hash_js_1 = require("../utils/hash.js");
|
|
13
|
+
const extractor_js_1 = require("../miner/extractor.js");
|
|
12
14
|
const shelf_js_1 = require("./shelf.js");
|
|
13
15
|
/**
|
|
14
16
|
* A bulk import, not a person: this many one-line neurons born the same day
|
|
@@ -26,6 +28,10 @@ const SPLIT_MAX_CANDIDATES = 8;
|
|
|
26
28
|
const SPLIT_GROUPS = 5;
|
|
27
29
|
const EXPIRED_SAMPLE = 10;
|
|
28
30
|
const STALE_SAMPLE = 10;
|
|
31
|
+
/** Hygiene report: a neuron this big with no summary is worth two lines describing it. */
|
|
32
|
+
const SUMMARY_MISSING_MIN = 20;
|
|
33
|
+
/** Entry ids listed per suggested part; the count is always whole. */
|
|
34
|
+
const PARTITION_IDS = 50;
|
|
29
35
|
/** Too common to name a subtopic. Short on purpose: the share bounds below do most of the work. */
|
|
30
36
|
const SPLIT_STOP = new Set(('para como pero este esta estos estas desde hasta entre sobre cuando donde porque tambien todos todas cada '
|
|
31
37
|
+ 'tiene tienen hace hacer puede pueden debe deben usar solo antes despues ahora siempre nunca mismo misma nueva nuevo '
|
|
@@ -87,8 +93,14 @@ class Maintenance {
|
|
|
87
93
|
compact_groups: [],
|
|
88
94
|
compactable_neurons: 0,
|
|
89
95
|
compacted_neurons: 0,
|
|
96
|
+
unreadable_neurons: [],
|
|
97
|
+
archivable_ids: [],
|
|
98
|
+
quarantine_pruned: 0,
|
|
99
|
+
quarantine_days: (0, quarantine_js_1.quarantineDays)(),
|
|
90
100
|
notes: [],
|
|
91
101
|
};
|
|
102
|
+
this.ilegibles.clear();
|
|
103
|
+
const compartidas = options?.sharedIds ?? new Set();
|
|
92
104
|
// 0. Bring neurons back from the archive, before anything else could
|
|
93
105
|
// archive them again in the same pass.
|
|
94
106
|
const recuperados = new Set();
|
|
@@ -129,17 +141,22 @@ class Maintenance {
|
|
|
129
141
|
// searchable and has no restore path. One routine call would have swallowed
|
|
130
142
|
// 87% of the memory. Heat decays with time, so a brain that is merely old
|
|
131
143
|
// looks identical to a brain that is worthless.
|
|
132
|
-
|
|
144
|
+
// Protocols are injected at every boot and shared neurons come back on
|
|
145
|
+
// the next sync: neither is ever archived (2.11), however cold it looks.
|
|
146
|
+
const frias = await this.coldNeuronIds(recuperados, compartidas);
|
|
147
|
+
report.archivable_neurons = frias.length;
|
|
148
|
+
report.archivable_ids = dryRun ? frias : frias.slice(0, 20);
|
|
133
149
|
if (options?.archive && !dryRun) {
|
|
134
|
-
report.archived_neurons = await this.archiveColdNeurons(recuperados);
|
|
150
|
+
report.archived_neurons = await this.archiveColdNeurons(recuperados, compartidas);
|
|
135
151
|
report.notes.push(`Archived ${report.archived_neurons} cold neurons to ${this.brain.paths.archives}. ` +
|
|
136
|
-
'They are no longer searchable;
|
|
152
|
+
'They are no longer searchable; their synapses and connections are kept. Pass unarchive:[ids] (or "all") to bring them back.');
|
|
137
153
|
}
|
|
138
154
|
else if (report.archivable_neurons > 0) {
|
|
139
|
-
report.notes.push(`${report.archivable_neurons} neurons are cold enough to archive. Nothing was archived: ` +
|
|
155
|
+
report.notes.push(`${report.archivable_neurons} neurons are cold enough to archive (archivable_ids${dryRun ? '' : ', first 20; dry_run lists all'}). Nothing was archived: ` +
|
|
140
156
|
'pass archive:true if you have reviewed the list and really want them out of the way.');
|
|
141
157
|
}
|
|
142
|
-
|
|
158
|
+
const archivadas = await (0, fs_js_1.listJSONFiles)(this.brain.paths.archives);
|
|
159
|
+
report.archives_count = archivadas.length;
|
|
143
160
|
// 2b. Miner leftovers.
|
|
144
161
|
//
|
|
145
162
|
// Early versions of the miner recorded "Referenced in: <file>" for every
|
|
@@ -153,28 +170,46 @@ class Maintenance {
|
|
|
153
170
|
'Neurons left with nothing in them were not deleted; review them with crbro_inspect view=neurons.');
|
|
154
171
|
}
|
|
155
172
|
else if (report.boilerplate_facts > 0) {
|
|
156
|
-
report.notes.push(`${report.boilerplate_facts} contentless miner fact(s) ("Referenced in: ...") are taking up ` +
|
|
173
|
+
report.notes.push(`${report.boilerplate_facts} contentless miner fact(s) ("Referenced in: ...", checklist lines like "- x Navigate to ...", bare links) are taking up ` +
|
|
157
174
|
'space and index slots. Pass purge_boilerplate:true to remove them.');
|
|
158
175
|
}
|
|
159
176
|
// 2c. Locks abandoned by a process that died mid-write.
|
|
160
177
|
if (!dryRun) {
|
|
161
178
|
report.stale_locks_swept = await (0, lock_js_1.sweepStaleLocks)(this.brain.paths.cortex);
|
|
162
179
|
}
|
|
163
|
-
// 3. Decay and prune weak synapses
|
|
180
|
+
// 3. Decay and prune weak synapses, by elapsed time. The synapses of an
|
|
181
|
+
// archived neuron are left alone: it is out of the way, not gone.
|
|
182
|
+
const enArchivo = new Set(archivadas);
|
|
164
183
|
if (!dryRun) {
|
|
165
|
-
report.pruned_synapses = await this.synapses.decay(0.05);
|
|
184
|
+
report.pruned_synapses = await this.synapses.decay(0.05, { skipNodes: enArchivo });
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
report.prunable_synapses = await this.synapses.decay(0.05, { dryRun: true, skipNodes: enArchivo });
|
|
188
|
+
}
|
|
189
|
+
// 3b. Quarantine retention (2.11): copies older than CRBRO_QUARANTINE_DAYS.
|
|
190
|
+
try {
|
|
191
|
+
const q = await (0, quarantine_js_1.pruneQuarantine)(this.brain.paths.quarantine, { days: report.quarantine_days, dryRun });
|
|
192
|
+
report.quarantine_pruned = q.pruned;
|
|
193
|
+
if (q.pruned > 0) {
|
|
194
|
+
report.notes.push(dryRun
|
|
195
|
+
? `${q.pruned} quarantine cop${q.pruned === 1 ? 'y is' : 'ies are'} older than ${q.days} days and would be removed (CRBRO_QUARANTINE_DAYS; 0 keeps them forever).`
|
|
196
|
+
: `Removed ${q.pruned} quarantine cop${q.pruned === 1 ? 'y' : 'ies'} older than ${q.days} days (CRBRO_QUARANTINE_DAYS; 0 keeps them forever).`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
report.notes.push(`Quarantine retention skipped: ${err.message}`);
|
|
166
201
|
}
|
|
167
202
|
// 4. Integrity check
|
|
168
|
-
report.integrity_issues = await this.checkIntegrity();
|
|
203
|
+
report.integrity_issues = await this.checkIntegrity(enArchivo);
|
|
169
204
|
report.repairable = report.integrity_issues.length;
|
|
170
205
|
// 4b. Repair what the check found. Opt-in and never in a dry run.
|
|
171
206
|
if (options?.repair && !dryRun) {
|
|
172
|
-
report.repairs = await this.repair();
|
|
207
|
+
report.repairs = await this.repair(enArchivo);
|
|
173
208
|
report.repaired = report.repairs.length;
|
|
174
209
|
if (report.repaired > 0) {
|
|
175
210
|
report.notes.push(`Repaired ${report.repaired} integrity issue(s); see repairs[].`);
|
|
176
211
|
// What is left after the fixes, so the report does not list issues that are gone.
|
|
177
|
-
report.integrity_issues = await this.checkIntegrity();
|
|
212
|
+
report.integrity_issues = await this.checkIntegrity(enArchivo);
|
|
178
213
|
}
|
|
179
214
|
}
|
|
180
215
|
else if (report.repairable > 0) {
|
|
@@ -220,10 +255,16 @@ class Maintenance {
|
|
|
220
255
|
// 4d. What the brain still says as current although its own words put a
|
|
221
256
|
// date on it, and what has outgrown a single read. Read-only, both: what
|
|
222
257
|
// to do with a line is a judgement, and maintenance does not make it.
|
|
223
|
-
const revision = await this.reviewEntries();
|
|
258
|
+
const revision = await this.reviewEntries({ hygiene: dryRun });
|
|
224
259
|
report.expired_entries = revision.expired;
|
|
225
260
|
report.expired_sample = revision.sample;
|
|
226
261
|
report.split_candidates = revision.splits;
|
|
262
|
+
if (revision.hygiene) {
|
|
263
|
+
// The hygiene report (2.11): read-only suggestions, dry run only.
|
|
264
|
+
report.split_partitions = revision.hygiene.split_partitions;
|
|
265
|
+
report.merge_candidates = revision.hygiene.merge_candidates;
|
|
266
|
+
report.summary_missing = revision.hygiene.summary_missing;
|
|
267
|
+
}
|
|
227
268
|
if (revision.stale) {
|
|
228
269
|
report.stale_entries = revision.stale.count;
|
|
229
270
|
report.stale_sample = revision.stale.sample;
|
|
@@ -249,7 +290,7 @@ class Maintenance {
|
|
|
249
290
|
// are settled and do not count.
|
|
250
291
|
const sinDisparador = [];
|
|
251
292
|
for (const id of await (0, fs_js_1.listJSONFiles)(this.brain.paths.cortex)) {
|
|
252
|
-
const n = await
|
|
293
|
+
const n = await this.readNeuron(id);
|
|
253
294
|
if (!n || !n.debts || n.debts.length === 0)
|
|
254
295
|
continue;
|
|
255
296
|
const vivas = n.debts.filter(d => !n.entry_status?.[(0, ops_js_1.entryId)(d)]);
|
|
@@ -289,8 +330,37 @@ class Maintenance {
|
|
|
289
330
|
last_consolidation: (0, fs_js_1.now)(),
|
|
290
331
|
});
|
|
291
332
|
}
|
|
333
|
+
// 8. What no step could read (2.11): one bad file used to abort the
|
|
334
|
+
// whole run before the integrity check could even name it.
|
|
335
|
+
for (const id of this.heatEngine.lastUnreadable)
|
|
336
|
+
this.ilegibles.add(id);
|
|
337
|
+
for (const id of this.ilegibles) {
|
|
338
|
+
try {
|
|
339
|
+
await (0, fs_js_1.readJSON)(this.brain.paths.neuron(id));
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
catch { /* still unreadable */ }
|
|
343
|
+
report.unreadable_neurons.push({ id, path: this.brain.paths.neuron(id), error: 'does not parse as JSON', recovery: await this.cortex.recoveryHint(id) });
|
|
344
|
+
}
|
|
345
|
+
report.unreadable_neurons.sort((a, b) => (a.id < b.id ? -1 : 1));
|
|
346
|
+
if (report.unreadable_neurons.length > 0) {
|
|
347
|
+
report.notes.push(`${report.unreadable_neurons.length} neuron file(s) could not be read and were skipped by every step (unreadable_neurons, ` +
|
|
348
|
+
'each with where a good copy may be). Nothing in them was changed.');
|
|
349
|
+
}
|
|
292
350
|
return report;
|
|
293
351
|
}
|
|
352
|
+
/** Neuron files a step met and could not parse, this run. */
|
|
353
|
+
ilegibles = new Set();
|
|
354
|
+
/** A tolerant read for the passes over the cortex: a corrupt file is noted and skipped. */
|
|
355
|
+
async readNeuron(id) {
|
|
356
|
+
try {
|
|
357
|
+
return await (0, fs_js_1.readJSON)(this.brain.paths.neuron(id));
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
this.ilegibles.add(id);
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
294
364
|
/**
|
|
295
365
|
* Consolidate session — called at end of session.
|
|
296
366
|
*/
|
|
@@ -354,8 +424,15 @@ class Maintenance {
|
|
|
354
424
|
// updateContext({}), which rewrote the file and set nothing, so boot
|
|
355
425
|
// reported last_session: null forever.
|
|
356
426
|
await this.prefrontal.setLastSession(log.session_id);
|
|
357
|
-
// Recalculate heat
|
|
427
|
+
// Recalculate heat. Since 2.11 it re-reads only the neurons whose file
|
|
428
|
+
// changed since the last pass, and never rewrites a neuron for it.
|
|
358
429
|
await this.heatEngine.recalculate();
|
|
430
|
+
// Quarantine retention (2.11). Never fatal: a consolidate must log.
|
|
431
|
+
let quarantinePruned = 0;
|
|
432
|
+
try {
|
|
433
|
+
quarantinePruned = (await (0, quarantine_js_1.pruneQuarantine)(this.brain.paths.quarantine, { days: (0, quarantine_js_1.quarantineDays)() })).pruned;
|
|
434
|
+
}
|
|
435
|
+
catch { /* retention is housekeeping */ }
|
|
359
436
|
// Update manifest
|
|
360
437
|
await this.brain.updateManifest({
|
|
361
438
|
total_neurons: neuronCount,
|
|
@@ -375,82 +452,112 @@ class Maintenance {
|
|
|
375
452
|
redacted: log.redacted,
|
|
376
453
|
topics_logged: topicosLog,
|
|
377
454
|
topics_unknown: desconocidos,
|
|
455
|
+
quarantine_pruned: quarantinePruned,
|
|
456
|
+
...(this.heatEngine.lastUnreadable.length ? { unreadable_neurons: [...this.heatEngine.lastUnreadable] } : {}),
|
|
378
457
|
};
|
|
379
458
|
}
|
|
380
459
|
// ─── Private Helpers ──────────────────────────────────────────
|
|
381
|
-
/**
|
|
382
|
-
|
|
383
|
-
|
|
460
|
+
/**
|
|
461
|
+
* "Referenced in: x.md" and "Mined from x.md" carry no information; nor do
|
|
462
|
+
* the checklist lines, bare links and headings the miner stored as facts
|
|
463
|
+
* before 2.11 (isMinerNoise, only for facts whose source is the miner).
|
|
464
|
+
*/
|
|
465
|
+
isBoilerplate(text, source) {
|
|
466
|
+
return /^(Referenced in|Mined from):?\s/i.test((text || '').trim()) || (source === 'miner' && (0, extractor_js_1.isMinerNoise)(text));
|
|
384
467
|
}
|
|
385
468
|
async countBoilerplate() {
|
|
386
469
|
let n = 0;
|
|
387
470
|
for (const id of await this.cortex.allIds()) {
|
|
388
|
-
const neuron = await
|
|
471
|
+
const neuron = await this.readNeuron(id);
|
|
389
472
|
if (!neuron)
|
|
390
473
|
continue;
|
|
391
|
-
n += (neuron.facts || []).filter(f => this.isBoilerplate(f.text)).length;
|
|
474
|
+
n += (neuron.facts || []).filter(f => this.isBoilerplate(f.text, f.source)).length;
|
|
392
475
|
}
|
|
393
476
|
return n;
|
|
394
477
|
}
|
|
395
478
|
async purgeBoilerplate() {
|
|
396
479
|
let removed = 0;
|
|
397
480
|
for (const id of await this.cortex.allIds()) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
481
|
+
try {
|
|
482
|
+
await (0, fs_js_1.updateJSON)(this.brain.paths.neuron(id), async (current) => {
|
|
483
|
+
if (!current)
|
|
484
|
+
return null;
|
|
485
|
+
const fuera = (current.facts || []).filter(f => this.isBoilerplate(f.text, f.source));
|
|
486
|
+
if (fuera.length === 0)
|
|
487
|
+
return null;
|
|
488
|
+
// What leaves goes to quarantine first (2.11), like every other
|
|
489
|
+
// removal: purge used to be the one deletion with no way back.
|
|
490
|
+
const q = {
|
|
491
|
+
quarantine_format: 2, kind: 'entries', neuron_id: current.id, name: current.name, type: current.type,
|
|
492
|
+
domain: current.domain, taken_at: (0, fs_js_1.now)(), facts: fuera, decisions: [], patterns: [], preferences: [], errors: [], debts: [],
|
|
493
|
+
};
|
|
494
|
+
await (0, quarantine_js_1.writeQuarantine)(this.brain.paths.quarantine, current.id, q, 'entries');
|
|
495
|
+
current.facts = (current.facts || []).filter(f => !this.isBoilerplate(f.text, f.source));
|
|
496
|
+
removed += fuera.length;
|
|
497
|
+
return current;
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
catch (err) {
|
|
501
|
+
if (err instanceof fs_js_1.CorruptJSONError) {
|
|
502
|
+
this.ilegibles.add(id);
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
throw err;
|
|
506
|
+
}
|
|
409
507
|
}
|
|
410
508
|
if (removed > 0)
|
|
411
509
|
await this.searchEngine.rebuild();
|
|
412
510
|
return removed;
|
|
413
511
|
}
|
|
414
|
-
/**
|
|
415
|
-
|
|
512
|
+
/**
|
|
513
|
+
* Cold: heat below 0.05 and untouched for more than 90 days. Heat comes
|
|
514
|
+
* from the derived heat state when there is one (2.11), else the neuron's
|
|
515
|
+
* own stored value, as before.
|
|
516
|
+
*/
|
|
517
|
+
isCold(neuron, heat) {
|
|
416
518
|
const lastAccess = new Date(neuron.last_accessed).getTime();
|
|
417
519
|
const diffDays = (Date.now() - lastAccess) / (1000 * 60 * 60 * 24);
|
|
418
|
-
return
|
|
520
|
+
return heat < 0.05 && diffDays > 90;
|
|
419
521
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
522
|
+
/**
|
|
523
|
+
* What archive:true would move: cold, and neither a protocol (boot injects
|
|
524
|
+
* it every session, so "untouched" says nothing) nor shared (the next sync
|
|
525
|
+
* would bring it back from the team log). Sorted by id.
|
|
526
|
+
*/
|
|
527
|
+
async coldNeuronIds(skip = new Set(), shared = new Set()) {
|
|
528
|
+
const calor = await this.cortex.heatMap();
|
|
529
|
+
const out = [];
|
|
530
|
+
for (const id of await this.cortex.allIds()) {
|
|
531
|
+
if (skip.has(id) || shared.has(id) || id.startsWith('protocol_'))
|
|
425
532
|
continue;
|
|
426
|
-
const neuron = await
|
|
427
|
-
if (!neuron)
|
|
533
|
+
const neuron = await this.readNeuron(id);
|
|
534
|
+
if (!neuron || neuron.type === 'protocol')
|
|
428
535
|
continue;
|
|
429
|
-
if (this.isCold(neuron))
|
|
430
|
-
|
|
431
|
-
archived++;
|
|
432
|
-
}
|
|
536
|
+
if (this.isCold(neuron, calor.get(id) ?? neuron.heat))
|
|
537
|
+
out.push(id);
|
|
433
538
|
}
|
|
434
|
-
return
|
|
539
|
+
return out.sort();
|
|
435
540
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
541
|
+
/**
|
|
542
|
+
* Move cold neurons to archives/. Only the neuron file moves: its synapse
|
|
543
|
+
* files and the neighbours' connection lists stay exactly as they are
|
|
544
|
+
* (2.11), and integrity and repair count archives/ as existing, so nothing
|
|
545
|
+
* of the neuron's links is lost and unarchive finds them where they were.
|
|
546
|
+
*/
|
|
547
|
+
async archiveColdNeurons(skip = new Set(), shared = new Set()) {
|
|
548
|
+
let archived = 0;
|
|
549
|
+
for (const id of await this.coldNeuronIds(skip, shared)) {
|
|
550
|
+
await (0, fs_js_1.moveFile)(this.brain.paths.neuron(id), `${this.brain.paths.archives}/${id}.json`);
|
|
551
|
+
await this.searchEngine.removeNeuron(id).catch(() => { });
|
|
552
|
+
archived++;
|
|
447
553
|
}
|
|
448
|
-
return
|
|
554
|
+
return archived;
|
|
449
555
|
}
|
|
450
556
|
/**
|
|
451
557
|
* Move neurons from archives/ back into cortex/ and index them. A neuron
|
|
452
558
|
* that exists in cortex/ again is not overwritten: the archive copy stays
|
|
453
|
-
* and the id is reported in `already`.
|
|
559
|
+
* and the id is reported in `already`. Its synapses never left, so it comes
|
|
560
|
+
* back connected.
|
|
454
561
|
*/
|
|
455
562
|
async unarchive(which) {
|
|
456
563
|
const out = { restored: [], unknown: [], already: [] };
|
|
@@ -485,25 +592,25 @@ class Maintenance {
|
|
|
485
592
|
/**
|
|
486
593
|
* What is inconsistent on disk. Every line here names something repair()
|
|
487
594
|
* knows how to fix, except a corrupted neuron file, which nobody should
|
|
488
|
-
* fix blindly.
|
|
595
|
+
* fix blindly. A neuron in archives/ exists: links to it are not broken.
|
|
489
596
|
*/
|
|
490
|
-
async checkIntegrity() {
|
|
597
|
+
async checkIntegrity(archived = new Set()) {
|
|
491
598
|
const issues = [];
|
|
492
599
|
const ids = await this.cortex.allIds();
|
|
493
|
-
const existe = new Set(ids);
|
|
600
|
+
const existe = new Set([...ids, ...archived]);
|
|
494
601
|
for (const id of ids) {
|
|
495
602
|
let neuron;
|
|
496
603
|
try {
|
|
497
604
|
neuron = await (0, fs_js_1.readJSON)(this.brain.paths.neuron(id));
|
|
498
605
|
}
|
|
499
606
|
catch {
|
|
607
|
+
this.ilegibles.add(id);
|
|
500
608
|
neuron = null;
|
|
501
609
|
}
|
|
502
610
|
if (!neuron) {
|
|
503
611
|
issues.push(`Corrupted neuron file: ${id}`);
|
|
504
612
|
continue;
|
|
505
|
-
}
|
|
506
|
-
// Check for broken connections
|
|
613
|
+
} // Check for broken connections
|
|
507
614
|
for (const connId of neuron.connections || []) {
|
|
508
615
|
if (!existe.has(connId)) {
|
|
509
616
|
issues.push(`Broken connection: ${id} → ${connId} (target missing)`);
|
|
@@ -629,7 +736,7 @@ class Maintenance {
|
|
|
629
736
|
* that gather between 8% and 45% of them — frequent enough to be a
|
|
630
737
|
* subtopic, not so frequent that they are just the neuron's own name.
|
|
631
738
|
*/
|
|
632
|
-
async reviewEntries() {
|
|
739
|
+
async reviewEntries(opts) {
|
|
633
740
|
const hoy = (0, fs_js_1.today)();
|
|
634
741
|
const vencidas = [];
|
|
635
742
|
// Shelf life: the same pass judges every live fact, decision and pattern.
|
|
@@ -644,14 +751,12 @@ class Maintenance {
|
|
|
644
751
|
const enNeuronas = new Map();
|
|
645
752
|
const grandes = [];
|
|
646
753
|
let total = 0;
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
continue;
|
|
654
|
-
}
|
|
754
|
+
// Hygiene report (2.11), gathered in the same pass.
|
|
755
|
+
const sueltas1 = [];
|
|
756
|
+
const sinResumen = [];
|
|
757
|
+
const todosIds = await this.cortex.allIds();
|
|
758
|
+
for (const id of todosIds) {
|
|
759
|
+
const n = await this.readNeuron(id);
|
|
655
760
|
if (!n || n.type === 'protocol')
|
|
656
761
|
continue;
|
|
657
762
|
const retirada = (t) => !!n.entry_status?.[(0, ops_js_1.entryId)(t)];
|
|
@@ -727,7 +832,19 @@ class Maintenance {
|
|
|
727
832
|
for (const w of new Set(palabras.flatMap(set => [...set])))
|
|
728
833
|
enNeuronas.set(w, (enNeuronas.get(w) || 0) + 1);
|
|
729
834
|
if (vivas.length >= SPLIT_MIN_ENTRIES && !(n.tags || []).includes('digest')) {
|
|
730
|
-
grandes.push({ n, vivas: vivas.length, palabras, propias });
|
|
835
|
+
grandes.push({ n, vivas: vivas.length, palabras, propias, eids: vivas.map(e => e.eid) });
|
|
836
|
+
}
|
|
837
|
+
if (opts?.hygiene) {
|
|
838
|
+
const todas = (n.facts || []).length + (n.decisions || []).length + (n.patterns || []).length
|
|
839
|
+
+ (n.preferences || []).length + (n.errors || []).length + (n.debts || []).length;
|
|
840
|
+
if (todas === 1 && vivas.length === 1 && !n.map?.text && !(n.tags || []).includes('digest')) {
|
|
841
|
+
const t = vivas[0].text;
|
|
842
|
+
sueltas1.push({ neuron_id: n.id, name: n.name, domain: n.domain || 'general', created: (n.created || '').slice(0, 10),
|
|
843
|
+
preview: t.length > 100 ? `${t.slice(0, 100).trimEnd()}…` : t });
|
|
844
|
+
}
|
|
845
|
+
if (!(n.summary || '').trim() && vivas.length >= SUMMARY_MISSING_MIN) {
|
|
846
|
+
sinResumen.push({ neuron_id: n.id, name: n.name, entries: vivas.length });
|
|
847
|
+
}
|
|
731
848
|
}
|
|
732
849
|
}
|
|
733
850
|
// A word found in many neurons is how this person writes ("verificado",
|
|
@@ -775,12 +892,152 @@ class Maintenance {
|
|
|
775
892
|
}
|
|
776
893
|
vencidas.sort((a, b) => (a.due < b.due ? -1 : a.due > b.due ? 1 : a.entry_id < b.entry_id ? -1 : 1));
|
|
777
894
|
gordas.sort((a, b) => b.entries - a.entries);
|
|
778
|
-
rancias.sort((a, b) => b.overdue - a.overdue || (a.entry_id < b.entry_id ? -1 : 1));
|
|
895
|
+
rancias.sort((a, b) => b.overdue - a.overdue || (a.entry_id < b.entry_id ? -1 : 1) || (a.neuron_id < b.neuron_id ? -1 : 1));
|
|
896
|
+
const sinOverdue = (r) => { const { overdue: _o, ...rest } = r; return rest; };
|
|
897
|
+
let hygiene;
|
|
898
|
+
if (opts?.hygiene) {
|
|
899
|
+
// A suggested partition for each split candidate: every live entry goes
|
|
900
|
+
// to the first group whose word it holds; the rest stays. Ids only, so
|
|
901
|
+
// crbro_revise move_to can act on it as it is. Nothing is moved here.
|
|
902
|
+
const porId = new Map(grandes.map(g => [g.n.id, g]));
|
|
903
|
+
const split_partitions = [];
|
|
904
|
+
for (const c of gordas.slice(0, SPLIT_MAX_CANDIDATES)) {
|
|
905
|
+
const g = porId.get(c.neuron_id);
|
|
906
|
+
if (!g || c.groups.length === 0)
|
|
907
|
+
continue;
|
|
908
|
+
const parts = c.groups.map(gr => ({ term: gr.term, suggested_name: `${c.name} - ${gr.term}`, entries: 0, entry_ids: [] }));
|
|
909
|
+
let rest = 0;
|
|
910
|
+
g.palabras.forEach((set, i) => {
|
|
911
|
+
const p = parts.find(pt => set.has(pt.term));
|
|
912
|
+
if (!p) {
|
|
913
|
+
rest++;
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
p.entries++;
|
|
917
|
+
if (p.entry_ids.length < PARTITION_IDS)
|
|
918
|
+
p.entry_ids.push(g.eids[i]);
|
|
919
|
+
});
|
|
920
|
+
split_partitions.push({ neuron_id: c.neuron_id, name: c.name, entries: c.entries, parts, rest });
|
|
921
|
+
}
|
|
922
|
+
// Ids made with their prefix twice by 2.10 from an id-shaped topic.
|
|
923
|
+
const existentes = new Set(todosIds);
|
|
924
|
+
const doubled_prefix = todosIds
|
|
925
|
+
.map(id => ({ id, m: id.match(/^(project|tech|lang|person|domain|process|protocol)_\1_(.+)$/) }))
|
|
926
|
+
.filter(x => x.m)
|
|
927
|
+
.map(x => ({ neuron_id: x.id, meant: `${x.m[1]}_${x.m[2]}`, meant_exists: existentes.has(`${x.m[1]}_${x.m[2]}`) }));
|
|
928
|
+
sueltas1.sort((a, b) => (a.created < b.created ? -1 : a.created > b.created ? 1 : a.neuron_id < b.neuron_id ? -1 : 1));
|
|
929
|
+
sinResumen.sort((a, b) => b.entries - a.entries || (a.neuron_id < b.neuron_id ? -1 : 1));
|
|
930
|
+
hygiene = {
|
|
931
|
+
split_partitions,
|
|
932
|
+
merge_candidates: { single_entry: sueltas1.slice(0, 30), single_entry_total: sueltas1.length, doubled_prefix },
|
|
933
|
+
summary_missing: { total: sinResumen.length, neurons: sinResumen.slice(0, 20) },
|
|
934
|
+
};
|
|
935
|
+
}
|
|
779
936
|
return {
|
|
780
937
|
expired: vencidas.length, sample: vencidas.slice(0, EXPIRED_SAMPLE), splits: gordas.slice(0, SPLIT_MAX_CANDIDATES),
|
|
781
|
-
...(vida ? { stale: {
|
|
938
|
+
...(vida ? { stale: {
|
|
939
|
+
count: rancias.length,
|
|
940
|
+
sample: rancias.slice(0, STALE_SAMPLE).map(sinOverdue),
|
|
941
|
+
...(opts?.staleAll ? { all: rancias.map(sinOverdue) } : {}),
|
|
942
|
+
} } : {}),
|
|
943
|
+
...(hygiene ? { hygiene } : {}),
|
|
782
944
|
};
|
|
783
945
|
}
|
|
946
|
+
/**
|
|
947
|
+
* Every stale entry, a page at a time, most overdue first — the list
|
|
948
|
+
* stale_sample only ever showed ten of (2.11). Read-only. `neuronId`
|
|
949
|
+
* narrows it to one neuron.
|
|
950
|
+
*/
|
|
951
|
+
async listStale(options) {
|
|
952
|
+
const r = await this.reviewEntries({ staleAll: true });
|
|
953
|
+
const offset = Math.max(0, Math.floor(options?.offset ?? 0));
|
|
954
|
+
const limit = Math.min(Math.max(Math.floor(options?.limit ?? 50), 1), 200);
|
|
955
|
+
if (!r.stale)
|
|
956
|
+
return { enabled: false, total: 0, offset, returned: 0, has_more: false, entries: [] };
|
|
957
|
+
const todas = (r.stale.all || []).filter(e => !options?.neuronId || e.neuron_id === options.neuronId);
|
|
958
|
+
const pagina = todas.slice(offset, offset + limit);
|
|
959
|
+
return { enabled: true, total: todas.length, offset, returned: pagina.length, has_more: offset + pagina.length < todas.length, entries: pagina };
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Act on many stale entries at once: `verify` restarts their shelf-life
|
|
963
|
+
* clock (they were checked and still hold); `superseded` / `retracted`
|
|
964
|
+
* retires them. Entries are { neuron_id, entry_id } as listStale gives
|
|
965
|
+
* them; facts and decisions/patterns are told apart here, one locked write
|
|
966
|
+
* per neuron. Verification and retirement travel to shared spaces exactly
|
|
967
|
+
* as crbro_revise sends them.
|
|
968
|
+
*/
|
|
969
|
+
async reviewStale(items, action, note) {
|
|
970
|
+
const out = { action, done: [], unmatched: [] };
|
|
971
|
+
const porNeurona = new Map();
|
|
972
|
+
for (const it of items || []) {
|
|
973
|
+
const nid = String(it?.neuron_id || '').trim();
|
|
974
|
+
const eid = String(it?.entry_id || '').trim();
|
|
975
|
+
if (!nid || !eid)
|
|
976
|
+
continue;
|
|
977
|
+
const l = porNeurona.get(nid) || [];
|
|
978
|
+
if (!l.includes(eid))
|
|
979
|
+
l.push(eid);
|
|
980
|
+
porNeurona.set(nid, l);
|
|
981
|
+
}
|
|
982
|
+
for (const [nid, eids] of porNeurona) {
|
|
983
|
+
const n = await this.cortex.peek(nid);
|
|
984
|
+
if (!n) {
|
|
985
|
+
for (const e of eids)
|
|
986
|
+
out.unmatched.push({ neuron_id: nid, entry_id: e, why: 'no such neuron (or unreadable)' });
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
const factIds = new Set((n.facts || []).map(f => (f.id || (0, hash_js_1.factId)(f.text))));
|
|
990
|
+
const entradas = new Map();
|
|
991
|
+
for (const d of n.decisions || []) {
|
|
992
|
+
entradas.set((0, ops_js_1.entryId)(d.text || ''), d.text || '');
|
|
993
|
+
if (d.id)
|
|
994
|
+
entradas.set(d.id, d.text || '');
|
|
995
|
+
}
|
|
996
|
+
for (const p of n.patterns || [])
|
|
997
|
+
entradas.set((0, ops_js_1.entryId)(p), p);
|
|
998
|
+
const facts = eids.filter(e => factIds.has(e));
|
|
999
|
+
const entries = eids.filter(e => !factIds.has(e) && entradas.has(e));
|
|
1000
|
+
for (const e of eids) {
|
|
1001
|
+
if (!factIds.has(e) && !entradas.has(e))
|
|
1002
|
+
out.unmatched.push({ neuron_id: nid, entry_id: e, why: 'not a fact, decision or pattern of this neuron' });
|
|
1003
|
+
}
|
|
1004
|
+
if (action === 'verify') {
|
|
1005
|
+
// Unspecified evidence, as crbro_revise status=verified without a reference.
|
|
1006
|
+
const r = await this.cortex.verify(nid, { facts, entries });
|
|
1007
|
+
const hechos = new Set(r.verified);
|
|
1008
|
+
for (const e of [...facts, ...entries]) {
|
|
1009
|
+
const id = entries.includes(e) ? (0, ops_js_1.entryId)(entradas.get(e)) : e;
|
|
1010
|
+
if (hechos.has(id) || hechos.has(e))
|
|
1011
|
+
out.done.push({ neuron_id: nid, entry_id: e });
|
|
1012
|
+
else
|
|
1013
|
+
out.unmatched.push({ neuron_id: nid, entry_id: e, why: 'retired, or not verifiable' });
|
|
1014
|
+
}
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
if (facts.length) {
|
|
1018
|
+
const r = await this.cortex.revise(nid, facts, { status: action, note });
|
|
1019
|
+
const sin = new Set(r.unmatched);
|
|
1020
|
+
for (const e of facts) {
|
|
1021
|
+
if (sin.has(e))
|
|
1022
|
+
out.unmatched.push({ neuron_id: nid, entry_id: e, why: 'already retired' });
|
|
1023
|
+
else
|
|
1024
|
+
out.done.push({ neuron_id: nid, entry_id: e });
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
if (entries.length) {
|
|
1028
|
+
const textos = entries.map(e => entradas.get(e));
|
|
1029
|
+
const r = await this.cortex.retireEntries(nid, textos, { status: action, note });
|
|
1030
|
+
const sin = new Set(r.unmatched);
|
|
1031
|
+
entries.forEach((e, i) => {
|
|
1032
|
+
if (sin.has(textos[i]))
|
|
1033
|
+
out.unmatched.push({ neuron_id: nid, entry_id: e, why: 'already retired' });
|
|
1034
|
+
else
|
|
1035
|
+
out.done.push({ neuron_id: nid, entry_id: e });
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return out;
|
|
1040
|
+
}
|
|
784
1041
|
/**
|
|
785
1042
|
* Count the undated patterns, preferences, errors and debts, and — when
|
|
786
1043
|
* `write` — date the ones whose own text says when they happened.
|
|
@@ -854,10 +1111,12 @@ class Maintenance {
|
|
|
854
1111
|
* with a missing end, stale entry_dates / entry_status keys, manifest
|
|
855
1112
|
* counters. One line per fix. Corrupted files are left for a human.
|
|
856
1113
|
*/
|
|
857
|
-
async repair() {
|
|
1114
|
+
async repair(archived = new Set()) {
|
|
858
1115
|
const done = [];
|
|
859
1116
|
const ids = await this.cortex.allIds();
|
|
860
|
-
|
|
1117
|
+
// archives/ counts as existing (2.11): repair used to delete for good the
|
|
1118
|
+
// synapses and connections of every archived neuron.
|
|
1119
|
+
const existe = new Set([...ids, ...archived]);
|
|
861
1120
|
// Synapses first: an orphan synapse also shows up as a dangling
|
|
862
1121
|
// connection on its surviving end, and disconnect() cleans both.
|
|
863
1122
|
for (const sid of await (0, fs_js_1.listJSONFiles)(this.brain.paths.synapses)) {
|
|
@@ -873,19 +1132,28 @@ class Maintenance {
|
|
|
873
1132
|
const faltan = synapse.nodes.filter(n => !existe.has(n));
|
|
874
1133
|
if (faltan.length === 0)
|
|
875
1134
|
continue;
|
|
1135
|
+
// A copy first, like every other removal (2.11).
|
|
1136
|
+
await (0, quarantine_js_1.writeQuarantine)(this.brain.paths.quarantine, synapse.id || sid, synapse, 'whole');
|
|
876
1137
|
await (0, fs_js_1.deleteJSON)(this.brain.paths.synapse(sid));
|
|
877
1138
|
for (const node of synapse.nodes) {
|
|
878
|
-
if (!existe.has(node))
|
|
1139
|
+
if (!existe.has(node) || archived.has(node))
|
|
879
1140
|
continue;
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1141
|
+
try {
|
|
1142
|
+
await (0, fs_js_1.updateJSON)(this.brain.paths.neuron(node), current => {
|
|
1143
|
+
if (!current)
|
|
1144
|
+
return null;
|
|
1145
|
+
const otro = synapse.nodes[0] === node ? synapse.nodes[1] : synapse.nodes[0];
|
|
1146
|
+
if (!(current.connections || []).includes(otro))
|
|
1147
|
+
return null;
|
|
1148
|
+
current.connections = current.connections.filter(c => c !== otro);
|
|
1149
|
+
return current;
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
catch (err) {
|
|
1153
|
+
if (!(err instanceof fs_js_1.CorruptJSONError))
|
|
1154
|
+
throw err;
|
|
1155
|
+
this.ilegibles.add(node);
|
|
1156
|
+
}
|
|
889
1157
|
}
|
|
890
1158
|
done.push(`Deleted orphan synapse ${sid} (missing: ${faltan.join(', ')})`);
|
|
891
1159
|
}
|