hebbian 0.3.4 → 0.5.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 +155 -70
- package/dist/bin/hebbian.js +707 -79
- package/dist/bin/hebbian.js.map +1 -1
- package/dist/candidates.d.ts +32 -0
- package/dist/candidates.d.ts.map +1 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/doctor.d.ts +7 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/emit.d.ts.map +1 -1
- package/dist/episode.d.ts +6 -1
- package/dist/episode.d.ts.map +1 -1
- package/dist/evolve.d.ts +1 -1
- package/dist/evolve.d.ts.map +1 -1
- package/dist/fire.d.ts +10 -0
- package/dist/fire.d.ts.map +1 -1
- package/dist/hooks.d.ts +3 -3
- package/dist/hooks.d.ts.map +1 -1
- package/dist/inbox.d.ts.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +459 -69
- package/dist/index.js.map +1 -1
- package/dist/outcome.d.ts +42 -0
- package/dist/outcome.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/bin/hebbian.js
CHANGED
|
@@ -18,7 +18,7 @@ function resolveBrainRoot(brainFlag) {
|
|
|
18
18
|
if (existsSync(resolve("./brain"))) return resolve("./brain");
|
|
19
19
|
return resolve(process.env.HOME || "~", "hebbian", "brain");
|
|
20
20
|
}
|
|
21
|
-
var REGIONS, REGION_PRIORITY, REGION_ICONS, REGION_KO, EMIT_THRESHOLD, SPOTLIGHT_DAYS, JACCARD_THRESHOLD, MAX_DEPTH, EMIT_TARGETS, SIGNAL_TYPES, MARKER_START, MARKER_END, HOOK_MARKER, MAX_CORRECTIONS_PER_SESSION, MIN_CORRECTION_LENGTH, DIGEST_LOG_DIR;
|
|
21
|
+
var REGIONS, REGION_PRIORITY, REGION_ICONS, REGION_KO, EMIT_THRESHOLD, SPOTLIGHT_DAYS, JACCARD_THRESHOLD, MAX_DEPTH, EMIT_TARGETS, SIGNAL_TYPES, MARKER_START, MARKER_END, HOOK_MARKER, MAX_CORRECTIONS_PER_SESSION, MIN_CORRECTION_LENGTH, DIGEST_LOG_DIR, SESSION_STATE_DIR, PROTECTED_REGIONS_CONTRA;
|
|
22
22
|
var init_constants = __esm({
|
|
23
23
|
"src/constants.ts"() {
|
|
24
24
|
"use strict";
|
|
@@ -76,6 +76,8 @@ var init_constants = __esm({
|
|
|
76
76
|
MAX_CORRECTIONS_PER_SESSION = 10;
|
|
77
77
|
MIN_CORRECTION_LENGTH = 15;
|
|
78
78
|
DIGEST_LOG_DIR = "hippocampus/digest_log";
|
|
79
|
+
SESSION_STATE_DIR = "hippocampus/session_state";
|
|
80
|
+
PROTECTED_REGIONS_CONTRA = ["brainstem", "limbic", "sensors"];
|
|
79
81
|
}
|
|
80
82
|
});
|
|
81
83
|
|
|
@@ -417,7 +419,7 @@ function emitBootstrap(result, brain) {
|
|
|
417
419
|
lines.push("|--------|---------|------------|");
|
|
418
420
|
for (const region of result.activeRegions) {
|
|
419
421
|
const active = region.neurons.filter((n) => !n.isDormant);
|
|
420
|
-
const activation = active.reduce((sum, n) => sum + n.
|
|
422
|
+
const activation = active.reduce((sum, n) => sum + n.intensity, 0);
|
|
421
423
|
const icon = REGION_ICONS[region.name] || "";
|
|
422
424
|
lines.push(`| ${icon} ${region.name} | ${active.length} | ${activation} |`);
|
|
423
425
|
}
|
|
@@ -439,7 +441,7 @@ function emitIndex(result, brain) {
|
|
|
439
441
|
const allNeurons = result.activeRegions.flatMap(
|
|
440
442
|
(r) => r.neurons.filter((n) => !n.isDormant && n.counter >= EMIT_THRESHOLD)
|
|
441
443
|
);
|
|
442
|
-
allNeurons.sort((a, b) => b.
|
|
444
|
+
allNeurons.sort((a, b) => b.intensity - a.intensity);
|
|
443
445
|
lines.push("## Top 10 Active Neurons");
|
|
444
446
|
lines.push("| # | Path | Counter | Strength |");
|
|
445
447
|
lines.push("|---|------|---------|----------|");
|
|
@@ -465,7 +467,7 @@ function emitIndex(result, brain) {
|
|
|
465
467
|
for (const region of result.activeRegions) {
|
|
466
468
|
const active = region.neurons.filter((n) => !n.isDormant);
|
|
467
469
|
const dormant = region.neurons.filter((n) => n.isDormant);
|
|
468
|
-
const activation = active.reduce((sum, n) => sum + n.
|
|
470
|
+
const activation = active.reduce((sum, n) => sum + n.intensity, 0);
|
|
469
471
|
const icon = REGION_ICONS[region.name] || "";
|
|
470
472
|
lines.push(`| ${icon} ${region.name} | ${active.length} | ${dormant.length} | ${activation} | [_rules.md](${region.name}/_rules.md) |`);
|
|
471
473
|
}
|
|
@@ -477,7 +479,7 @@ function emitRegionRules(region) {
|
|
|
477
479
|
const ko = REGION_KO[region.name] || "";
|
|
478
480
|
const active = region.neurons.filter((n) => !n.isDormant);
|
|
479
481
|
const dormant = region.neurons.filter((n) => n.isDormant);
|
|
480
|
-
const activation = active.reduce((sum, n) => sum + n.
|
|
482
|
+
const activation = active.reduce((sum, n) => sum + n.intensity, 0);
|
|
481
483
|
const lines = [];
|
|
482
484
|
lines.push(`# ${icon} ${region.name} (${ko})`);
|
|
483
485
|
lines.push(`> Active: ${active.length} | Dormant: ${dormant.length} | Activation: ${activation}`);
|
|
@@ -491,7 +493,7 @@ function emitRegionRules(region) {
|
|
|
491
493
|
}
|
|
492
494
|
if (active.length > 0) {
|
|
493
495
|
lines.push("## Rules");
|
|
494
|
-
const sorted = [...active].sort((a, b) => b.
|
|
496
|
+
const sorted = [...active].sort((a, b) => b.intensity - a.intensity);
|
|
495
497
|
for (const n of sorted) {
|
|
496
498
|
const indent = " ".repeat(Math.min(n.depth, 4));
|
|
497
499
|
const prefix = strengthPrefix(n.counter);
|
|
@@ -574,7 +576,7 @@ function printDiag(brain, result) {
|
|
|
574
576
|
const icon = REGION_ICONS[region.name] || "";
|
|
575
577
|
const active = region.neurons.filter((n) => !n.isDormant);
|
|
576
578
|
const dormant = region.neurons.filter((n) => n.isDormant);
|
|
577
|
-
const activation = active.reduce((sum, n) => sum + n.
|
|
579
|
+
const activation = active.reduce((sum, n) => sum + n.intensity, 0);
|
|
578
580
|
const isBlocked = result.blockedRegions.some((r) => r.name === region.name);
|
|
579
581
|
const status = region.hasBomb ? "\u{1F4A3} BOMB" : isBlocked ? "\u{1F6AB} BLOCKED" : "\u2705 ACTIVE";
|
|
580
582
|
console.log(` ${icon} ${region.name} [${status}]`);
|
|
@@ -584,7 +586,8 @@ function printDiag(brain, result) {
|
|
|
584
586
|
}
|
|
585
587
|
const top3 = sortedActive(region.neurons, 3);
|
|
586
588
|
for (const n of top3) {
|
|
587
|
-
|
|
589
|
+
const contraStr = n.contra > 0 ? ` contra:${n.contra}` : "";
|
|
590
|
+
console.log(` \u251C ${n.path} (counter:${n.counter}${contraStr} intensity:${n.intensity})`);
|
|
588
591
|
}
|
|
589
592
|
}
|
|
590
593
|
console.log("");
|
|
@@ -593,7 +596,7 @@ function pathToSentence(path) {
|
|
|
593
596
|
return path.replace(/\//g, " > ").replace(/_/g, " ");
|
|
594
597
|
}
|
|
595
598
|
function sortedActive(neurons, n) {
|
|
596
|
-
return [...neurons].filter((neuron) => !neuron.isDormant).sort((a, b) => b.
|
|
599
|
+
return [...neurons].filter((neuron) => !neuron.isDormant).sort((a, b) => b.intensity - a.intensity).slice(0, n);
|
|
597
600
|
}
|
|
598
601
|
function strengthPrefix(counter) {
|
|
599
602
|
if (counter >= 10) return "**[ABSOLUTE]** ";
|
|
@@ -758,7 +761,9 @@ var init_update_check = __esm({
|
|
|
758
761
|
// src/fire.ts
|
|
759
762
|
var fire_exports = {};
|
|
760
763
|
__export(fire_exports, {
|
|
764
|
+
contraNeuron: () => contraNeuron,
|
|
761
765
|
fireNeuron: () => fireNeuron,
|
|
766
|
+
getCurrentContra: () => getCurrentContra,
|
|
762
767
|
getCurrentCounter: () => getCurrentCounter
|
|
763
768
|
});
|
|
764
769
|
import { readdirSync as readdirSync3, renameSync, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
@@ -781,6 +786,33 @@ function fireNeuron(brainRoot, neuronPath) {
|
|
|
781
786
|
console.log(`\u{1F525} fired: ${neuronPath} (${current} \u2192 ${newCounter})`);
|
|
782
787
|
return newCounter;
|
|
783
788
|
}
|
|
789
|
+
function contraNeuron(brainRoot, neuronPath) {
|
|
790
|
+
const fullPath = join5(brainRoot, neuronPath);
|
|
791
|
+
if (!existsSync6(fullPath)) {
|
|
792
|
+
return 0;
|
|
793
|
+
}
|
|
794
|
+
const current = getCurrentContra(fullPath);
|
|
795
|
+
const newContra = current + 1;
|
|
796
|
+
if (current > 0) {
|
|
797
|
+
renameSync(join5(fullPath, `${current}.contra`), join5(fullPath, `${newContra}.contra`));
|
|
798
|
+
} else {
|
|
799
|
+
writeFileSync4(join5(fullPath, `${newContra}.contra`), "", "utf8");
|
|
800
|
+
}
|
|
801
|
+
return newContra;
|
|
802
|
+
}
|
|
803
|
+
function getCurrentContra(dir) {
|
|
804
|
+
let max = 0;
|
|
805
|
+
try {
|
|
806
|
+
for (const entry of readdirSync3(dir)) {
|
|
807
|
+
if (entry.endsWith(".contra")) {
|
|
808
|
+
const n = parseInt(entry, 10);
|
|
809
|
+
if (!isNaN(n) && n > max) max = n;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
} catch {
|
|
813
|
+
}
|
|
814
|
+
return max;
|
|
815
|
+
}
|
|
784
816
|
function getCurrentCounter(dir) {
|
|
785
817
|
let max = 0;
|
|
786
818
|
try {
|
|
@@ -1216,41 +1248,174 @@ var init_watch = __esm({
|
|
|
1216
1248
|
}
|
|
1217
1249
|
});
|
|
1218
1250
|
|
|
1251
|
+
// src/candidates.ts
|
|
1252
|
+
var candidates_exports = {};
|
|
1253
|
+
__export(candidates_exports, {
|
|
1254
|
+
CANDIDATE_DECAY_DAYS: () => CANDIDATE_DECAY_DAYS,
|
|
1255
|
+
CANDIDATE_THRESHOLD: () => CANDIDATE_THRESHOLD,
|
|
1256
|
+
fromCandidatePath: () => fromCandidatePath,
|
|
1257
|
+
growCandidate: () => growCandidate,
|
|
1258
|
+
listCandidates: () => listCandidates,
|
|
1259
|
+
promoteCandidates: () => promoteCandidates,
|
|
1260
|
+
toCandidatePath: () => toCandidatePath
|
|
1261
|
+
});
|
|
1262
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync7, renameSync as renameSync3, rmSync, statSync as statSync4 } from "fs";
|
|
1263
|
+
import { join as join12, dirname as dirname2, relative as relative3 } from "path";
|
|
1264
|
+
function toCandidatePath(neuronPath) {
|
|
1265
|
+
const slash = neuronPath.indexOf("/");
|
|
1266
|
+
if (slash === -1) throw new Error(`Invalid neuron path (missing region): ${neuronPath}`);
|
|
1267
|
+
return `${neuronPath.slice(0, slash)}/${CANDIDATE_SEGMENT}/${neuronPath.slice(slash + 1)}`;
|
|
1268
|
+
}
|
|
1269
|
+
function fromCandidatePath(candidatePath) {
|
|
1270
|
+
return candidatePath.replace(`/${CANDIDATE_SEGMENT}/`, "/");
|
|
1271
|
+
}
|
|
1272
|
+
function growCandidate(brainRoot, neuronPath) {
|
|
1273
|
+
const candidatePath = toCandidatePath(neuronPath);
|
|
1274
|
+
const result = growNeuron(brainRoot, candidatePath);
|
|
1275
|
+
if (result.counter >= CANDIDATE_THRESHOLD) {
|
|
1276
|
+
const ok = moveCandidate(brainRoot, candidatePath, neuronPath);
|
|
1277
|
+
return { ...result, path: ok ? neuronPath : result.path, promoted: ok };
|
|
1278
|
+
}
|
|
1279
|
+
console.log(` \u{1F331} candidate (${result.counter}/${CANDIDATE_THRESHOLD}): ${candidatePath}`);
|
|
1280
|
+
return { ...result, promoted: false };
|
|
1281
|
+
}
|
|
1282
|
+
function moveCandidate(brainRoot, candidatePath, targetPath) {
|
|
1283
|
+
const src = join12(brainRoot, candidatePath);
|
|
1284
|
+
if (!existsSync11(src)) return false;
|
|
1285
|
+
const dst = join12(brainRoot, targetPath);
|
|
1286
|
+
if (existsSync11(dst)) {
|
|
1287
|
+
fireNeuron(brainRoot, targetPath);
|
|
1288
|
+
rmSync(src, { recursive: true, force: true });
|
|
1289
|
+
} else {
|
|
1290
|
+
mkdirSync6(dirname2(dst), { recursive: true });
|
|
1291
|
+
renameSync3(src, dst);
|
|
1292
|
+
}
|
|
1293
|
+
console.log(`\u{1F393} promoted: ${candidatePath} \u2192 ${targetPath}`);
|
|
1294
|
+
return true;
|
|
1295
|
+
}
|
|
1296
|
+
function promoteCandidates(brainRoot) {
|
|
1297
|
+
const promoted = [];
|
|
1298
|
+
const decayed = [];
|
|
1299
|
+
const decayMs = CANDIDATE_DECAY_DAYS * 24 * 60 * 60 * 1e3;
|
|
1300
|
+
const now = Date.now();
|
|
1301
|
+
for (const region of REGIONS) {
|
|
1302
|
+
const candidateRoot = join12(brainRoot, region, CANDIDATE_SEGMENT);
|
|
1303
|
+
walkNeuronDirs(candidateRoot, (neuronDir) => {
|
|
1304
|
+
const rel = relative3(join12(brainRoot, region), neuronDir);
|
|
1305
|
+
const candidatePath = `${region}/${rel}`;
|
|
1306
|
+
const targetPath = fromCandidatePath(candidatePath);
|
|
1307
|
+
const counter = readCounter(neuronDir);
|
|
1308
|
+
const mtime = statSync4(neuronDir).mtimeMs;
|
|
1309
|
+
if (counter >= CANDIDATE_THRESHOLD) {
|
|
1310
|
+
moveCandidate(brainRoot, candidatePath, targetPath);
|
|
1311
|
+
promoted.push(targetPath);
|
|
1312
|
+
} else if (now - mtime > decayMs) {
|
|
1313
|
+
rmSync(neuronDir, { recursive: true, force: true });
|
|
1314
|
+
decayed.push(candidatePath);
|
|
1315
|
+
console.log(`\u{1F480} candidate decayed: ${candidatePath}`);
|
|
1316
|
+
}
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
return { promoted, decayed };
|
|
1320
|
+
}
|
|
1321
|
+
function listCandidates(brainRoot) {
|
|
1322
|
+
const results = [];
|
|
1323
|
+
const now = Date.now();
|
|
1324
|
+
for (const region of REGIONS) {
|
|
1325
|
+
const candidateRoot = join12(brainRoot, region, CANDIDATE_SEGMENT);
|
|
1326
|
+
walkNeuronDirs(candidateRoot, (neuronDir) => {
|
|
1327
|
+
const rel = relative3(join12(brainRoot, region), neuronDir);
|
|
1328
|
+
const candidatePath = `${region}/${rel}`;
|
|
1329
|
+
const targetPath = fromCandidatePath(candidatePath);
|
|
1330
|
+
const counter = readCounter(neuronDir);
|
|
1331
|
+
const mtime = statSync4(neuronDir).mtimeMs;
|
|
1332
|
+
const daysInactive = Math.floor((now - mtime) / (24 * 60 * 60 * 1e3));
|
|
1333
|
+
results.push({ candidatePath, targetPath, counter, daysInactive });
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
return results;
|
|
1337
|
+
}
|
|
1338
|
+
function walkNeuronDirs(dir, cb) {
|
|
1339
|
+
if (!existsSync11(dir)) return;
|
|
1340
|
+
try {
|
|
1341
|
+
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
1342
|
+
const hasNeuron = entries.some((e) => e.isFile() && e.name.endsWith(".neuron"));
|
|
1343
|
+
if (hasNeuron) {
|
|
1344
|
+
cb(dir);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
for (const entry of entries) {
|
|
1348
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
1349
|
+
walkNeuronDirs(join12(dir, entry.name), cb);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
} catch {
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
function readCounter(dir) {
|
|
1356
|
+
try {
|
|
1357
|
+
const files = readdirSync7(dir).filter((f) => /^\d+\.neuron$/.test(f));
|
|
1358
|
+
if (files.length === 0) return 0;
|
|
1359
|
+
return Math.max(...files.map((f) => parseInt(f, 10)));
|
|
1360
|
+
} catch {
|
|
1361
|
+
return 0;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
var CANDIDATE_THRESHOLD, CANDIDATE_DECAY_DAYS, CANDIDATE_SEGMENT;
|
|
1365
|
+
var init_candidates = __esm({
|
|
1366
|
+
"src/candidates.ts"() {
|
|
1367
|
+
"use strict";
|
|
1368
|
+
init_constants();
|
|
1369
|
+
init_grow();
|
|
1370
|
+
init_fire();
|
|
1371
|
+
CANDIDATE_THRESHOLD = 3;
|
|
1372
|
+
CANDIDATE_DECAY_DAYS = 14;
|
|
1373
|
+
CANDIDATE_SEGMENT = "_candidates";
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1219
1377
|
// src/episode.ts
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1378
|
+
var episode_exports = {};
|
|
1379
|
+
__export(episode_exports, {
|
|
1380
|
+
logEpisode: () => logEpisode,
|
|
1381
|
+
readEpisodes: () => readEpisodes
|
|
1382
|
+
});
|
|
1383
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, existsSync as existsSync12 } from "fs";
|
|
1384
|
+
import { join as join13 } from "path";
|
|
1385
|
+
function logEpisode(brainRoot, type, path, detail, extra) {
|
|
1386
|
+
const logDir = join13(brainRoot, SESSION_LOG_DIR);
|
|
1387
|
+
if (!existsSync12(logDir)) {
|
|
1388
|
+
mkdirSync7(logDir, { recursive: true });
|
|
1226
1389
|
}
|
|
1227
1390
|
const nextSlot = getNextSlot(logDir);
|
|
1228
1391
|
const episode = {
|
|
1229
1392
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1230
1393
|
type,
|
|
1231
1394
|
path,
|
|
1232
|
-
detail
|
|
1395
|
+
detail,
|
|
1396
|
+
...extra?.outcome ? { outcome: extra.outcome } : {},
|
|
1397
|
+
...extra?.neurons ? { neurons: extra.neurons } : {}
|
|
1233
1398
|
};
|
|
1234
1399
|
writeFileSync9(
|
|
1235
|
-
|
|
1400
|
+
join13(logDir, `memory${nextSlot}.neuron`),
|
|
1236
1401
|
JSON.stringify(episode),
|
|
1237
1402
|
"utf8"
|
|
1238
1403
|
);
|
|
1239
1404
|
}
|
|
1240
1405
|
function readEpisodes(brainRoot) {
|
|
1241
|
-
const logDir =
|
|
1242
|
-
if (!
|
|
1406
|
+
const logDir = join13(brainRoot, SESSION_LOG_DIR);
|
|
1407
|
+
if (!existsSync12(logDir)) return [];
|
|
1243
1408
|
const episodes = [];
|
|
1244
1409
|
let entries;
|
|
1245
1410
|
try {
|
|
1246
|
-
entries =
|
|
1411
|
+
entries = readdirSync8(logDir);
|
|
1247
1412
|
} catch {
|
|
1248
1413
|
return [];
|
|
1249
1414
|
}
|
|
1250
1415
|
for (const entry of entries) {
|
|
1251
1416
|
if (!entry.startsWith("memory") || !entry.endsWith(".neuron")) continue;
|
|
1252
1417
|
try {
|
|
1253
|
-
const content = readFileSync4(
|
|
1418
|
+
const content = readFileSync4(join13(logDir, entry), "utf8");
|
|
1254
1419
|
if (content.trim()) {
|
|
1255
1420
|
episodes.push(JSON.parse(content));
|
|
1256
1421
|
}
|
|
@@ -1263,7 +1428,7 @@ function readEpisodes(brainRoot) {
|
|
|
1263
1428
|
function getNextSlot(logDir) {
|
|
1264
1429
|
let maxSlot = 0;
|
|
1265
1430
|
try {
|
|
1266
|
-
for (const entry of
|
|
1431
|
+
for (const entry of readdirSync8(logDir)) {
|
|
1267
1432
|
if (entry.startsWith("memory") && entry.endsWith(".neuron")) {
|
|
1268
1433
|
const n = parseInt(entry.replace("memory", "").replace(".neuron", ""), 10);
|
|
1269
1434
|
if (!isNaN(n) && n > maxSlot) maxSlot = n;
|
|
@@ -1290,11 +1455,11 @@ __export(inbox_exports, {
|
|
|
1290
1455
|
ensureInbox: () => ensureInbox,
|
|
1291
1456
|
processInbox: () => processInbox
|
|
1292
1457
|
});
|
|
1293
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync10, existsSync as
|
|
1294
|
-
import { join as
|
|
1458
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync10, existsSync as existsSync13, mkdirSync as mkdirSync8 } from "fs";
|
|
1459
|
+
import { join as join14 } from "path";
|
|
1295
1460
|
function processInbox(brainRoot) {
|
|
1296
|
-
const inboxPath =
|
|
1297
|
-
if (!
|
|
1461
|
+
const inboxPath = join14(brainRoot, INBOX_DIR, CORRECTIONS_FILE);
|
|
1462
|
+
if (!existsSync13(inboxPath)) {
|
|
1298
1463
|
return { processed: 0, skipped: 0, errors: [] };
|
|
1299
1464
|
}
|
|
1300
1465
|
const content = readFileSync5(inboxPath, "utf8").trim();
|
|
@@ -1349,16 +1514,18 @@ function processInbox(brainRoot) {
|
|
|
1349
1514
|
}
|
|
1350
1515
|
function applyCorrection(brainRoot, correction) {
|
|
1351
1516
|
const neuronPath = correction.path;
|
|
1352
|
-
const fullPath =
|
|
1517
|
+
const fullPath = join14(brainRoot, neuronPath);
|
|
1353
1518
|
const counterAdd = Math.max(1, correction.counter_add || 1);
|
|
1354
|
-
if (
|
|
1519
|
+
if (existsSync13(fullPath)) {
|
|
1355
1520
|
for (let i = 0; i < counterAdd; i++) {
|
|
1356
1521
|
fireNeuron(brainRoot, neuronPath);
|
|
1357
1522
|
}
|
|
1358
1523
|
} else {
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1524
|
+
const candResult = growCandidate(brainRoot, neuronPath);
|
|
1525
|
+
if (candResult.promoted) {
|
|
1526
|
+
for (let i = 1; i < counterAdd; i++) {
|
|
1527
|
+
fireNeuron(brainRoot, neuronPath);
|
|
1528
|
+
}
|
|
1362
1529
|
}
|
|
1363
1530
|
}
|
|
1364
1531
|
if (correction.dopamine && correction.dopamine > 0) {
|
|
@@ -1377,12 +1544,12 @@ function isPathSafe(path) {
|
|
|
1377
1544
|
return true;
|
|
1378
1545
|
}
|
|
1379
1546
|
function ensureInbox(brainRoot) {
|
|
1380
|
-
const inboxDir =
|
|
1381
|
-
if (!
|
|
1382
|
-
|
|
1547
|
+
const inboxDir = join14(brainRoot, INBOX_DIR);
|
|
1548
|
+
if (!existsSync13(inboxDir)) {
|
|
1549
|
+
mkdirSync8(inboxDir, { recursive: true });
|
|
1383
1550
|
}
|
|
1384
|
-
const filePath =
|
|
1385
|
-
if (!
|
|
1551
|
+
const filePath = join14(inboxDir, CORRECTIONS_FILE);
|
|
1552
|
+
if (!existsSync13(filePath)) {
|
|
1386
1553
|
writeFileSync10(filePath, "", "utf8");
|
|
1387
1554
|
}
|
|
1388
1555
|
return filePath;
|
|
@@ -1398,7 +1565,7 @@ var init_inbox = __esm({
|
|
|
1398
1565
|
"src/inbox.ts"() {
|
|
1399
1566
|
"use strict";
|
|
1400
1567
|
init_constants();
|
|
1401
|
-
|
|
1568
|
+
init_candidates();
|
|
1402
1569
|
init_fire();
|
|
1403
1570
|
init_signal();
|
|
1404
1571
|
init_episode();
|
|
@@ -1695,17 +1862,25 @@ __export(hooks_exports, {
|
|
|
1695
1862
|
installHooks: () => installHooks,
|
|
1696
1863
|
uninstallHooks: () => uninstallHooks
|
|
1697
1864
|
});
|
|
1698
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync11, existsSync as
|
|
1865
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync11, existsSync as existsSync14, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
|
|
1699
1866
|
import { execSync as execSync2 } from "child_process";
|
|
1700
|
-
import { join as
|
|
1701
|
-
function installHooks(brainRoot, projectRoot) {
|
|
1867
|
+
import { join as join15, resolve as resolve2 } from "path";
|
|
1868
|
+
function installHooks(brainRoot, projectRoot, global) {
|
|
1702
1869
|
const root = projectRoot || process.cwd();
|
|
1703
1870
|
const resolvedBrain = resolve2(brainRoot);
|
|
1704
|
-
if (
|
|
1871
|
+
if (global) {
|
|
1872
|
+
const home = process.env.HOME || "~";
|
|
1873
|
+
if (!brainRoot.startsWith("/") && !brainRoot.startsWith(home)) {
|
|
1874
|
+
console.error("\u274C --global requires an absolute --brain path (e.g. --brain ~/brain)");
|
|
1875
|
+
process.exit(1);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
if (!existsSync14(resolvedBrain) || !hasBrainRegions(resolvedBrain)) {
|
|
1705
1879
|
initBrain(resolvedBrain);
|
|
1706
1880
|
}
|
|
1707
|
-
const settingsDir =
|
|
1708
|
-
const
|
|
1881
|
+
const settingsDir = global ? resolve2(process.env.HOME || "~", SETTINGS_DIR) : join15(root, SETTINGS_DIR);
|
|
1882
|
+
const settingsFile = global ? "settings.json" : SETTINGS_FILE;
|
|
1883
|
+
const settingsPath = join15(settingsDir, settingsFile);
|
|
1709
1884
|
const defaultBrain = resolve2(root, "brain");
|
|
1710
1885
|
const brainFlag = resolvedBrain === defaultBrain ? "" : ` --brain ${resolvedBrain}`;
|
|
1711
1886
|
let npxBin = "npx";
|
|
@@ -1714,7 +1889,7 @@ function installHooks(brainRoot, projectRoot) {
|
|
|
1714
1889
|
} catch {
|
|
1715
1890
|
}
|
|
1716
1891
|
let settings = {};
|
|
1717
|
-
if (
|
|
1892
|
+
if (existsSync14(settingsPath)) {
|
|
1718
1893
|
try {
|
|
1719
1894
|
settings = JSON.parse(readFileSync6(settingsPath, "utf8"));
|
|
1720
1895
|
} catch {
|
|
@@ -1731,8 +1906,8 @@ function installHooks(brainRoot, projectRoot) {
|
|
|
1731
1906
|
matcher: "startup|resume",
|
|
1732
1907
|
entry: {
|
|
1733
1908
|
type: "command",
|
|
1734
|
-
command: `${npxBin} hebbian emit claude${brainFlag}`,
|
|
1735
|
-
timeout:
|
|
1909
|
+
command: `${npxBin} hebbian emit claude${brainFlag} && ${npxBin} hebbian session start${brainFlag}`,
|
|
1910
|
+
timeout: 15,
|
|
1736
1911
|
statusMessage: `${HOOK_MARKER} refreshing brain`
|
|
1737
1912
|
}
|
|
1738
1913
|
},
|
|
@@ -1740,7 +1915,7 @@ function installHooks(brainRoot, projectRoot) {
|
|
|
1740
1915
|
event: "Stop",
|
|
1741
1916
|
entry: {
|
|
1742
1917
|
type: "command",
|
|
1743
|
-
command: `${npxBin} hebbian digest${brainFlag}`,
|
|
1918
|
+
command: `${npxBin} hebbian digest${brainFlag}; ${npxBin} hebbian session end${brainFlag}`,
|
|
1744
1919
|
timeout: 30,
|
|
1745
1920
|
statusMessage: `${HOOK_MARKER} digesting session`
|
|
1746
1921
|
}
|
|
@@ -1763,18 +1938,20 @@ function installHooks(brainRoot, projectRoot) {
|
|
|
1763
1938
|
hooks[event].push(group);
|
|
1764
1939
|
}
|
|
1765
1940
|
}
|
|
1766
|
-
if (!
|
|
1767
|
-
|
|
1941
|
+
if (!existsSync14(settingsDir)) {
|
|
1942
|
+
mkdirSync9(settingsDir, { recursive: true });
|
|
1768
1943
|
}
|
|
1769
1944
|
writeFileSync11(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
1770
1945
|
console.log(`\u2705 hebbian hooks installed at ${settingsPath}`);
|
|
1771
1946
|
console.log(` SessionStart \u2192 ${npxBin} hebbian emit claude${brainFlag}`);
|
|
1772
1947
|
console.log(` Stop \u2192 ${npxBin} hebbian digest${brainFlag}`);
|
|
1773
1948
|
}
|
|
1774
|
-
function uninstallHooks(projectRoot) {
|
|
1949
|
+
function uninstallHooks(projectRoot, global) {
|
|
1775
1950
|
const root = projectRoot || process.cwd();
|
|
1776
|
-
const
|
|
1777
|
-
|
|
1951
|
+
const settingsDir = global ? resolve2(process.env.HOME || "~", SETTINGS_DIR) : join15(root, SETTINGS_DIR);
|
|
1952
|
+
const settingsFile = global ? "settings.json" : SETTINGS_FILE;
|
|
1953
|
+
const settingsPath = join15(settingsDir, settingsFile);
|
|
1954
|
+
if (!existsSync14(settingsPath)) {
|
|
1778
1955
|
console.log("No hooks installed (settings.local.json not found)");
|
|
1779
1956
|
return;
|
|
1780
1957
|
}
|
|
@@ -1807,15 +1984,17 @@ function uninstallHooks(projectRoot) {
|
|
|
1807
1984
|
writeFileSync11(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
1808
1985
|
console.log(`\u2705 removed ${removed} hebbian hook(s) from ${settingsPath}`);
|
|
1809
1986
|
}
|
|
1810
|
-
function checkHooks(projectRoot) {
|
|
1987
|
+
function checkHooks(projectRoot, global) {
|
|
1811
1988
|
const root = projectRoot || process.cwd();
|
|
1812
|
-
const
|
|
1989
|
+
const settingsDir = global ? resolve2(process.env.HOME || "~", SETTINGS_DIR) : join15(root, SETTINGS_DIR);
|
|
1990
|
+
const settingsFile = global ? "settings.json" : SETTINGS_FILE;
|
|
1991
|
+
const settingsPath = join15(settingsDir, settingsFile);
|
|
1813
1992
|
const status = {
|
|
1814
1993
|
installed: false,
|
|
1815
1994
|
path: settingsPath,
|
|
1816
1995
|
events: []
|
|
1817
1996
|
};
|
|
1818
|
-
if (!
|
|
1997
|
+
if (!existsSync14(settingsPath)) {
|
|
1819
1998
|
console.log(`\u274C hebbian hooks not installed (${settingsPath} not found)`);
|
|
1820
1999
|
return status;
|
|
1821
2000
|
}
|
|
@@ -1851,9 +2030,9 @@ function checkHooks(projectRoot) {
|
|
|
1851
2030
|
return status;
|
|
1852
2031
|
}
|
|
1853
2032
|
function hasBrainRegions(dir) {
|
|
1854
|
-
if (!
|
|
2033
|
+
if (!existsSync14(dir)) return false;
|
|
1855
2034
|
try {
|
|
1856
|
-
const entries =
|
|
2035
|
+
const entries = readdirSync9(dir);
|
|
1857
2036
|
return REGIONS.some((r) => entries.includes(r));
|
|
1858
2037
|
} catch {
|
|
1859
2038
|
return false;
|
|
@@ -1877,8 +2056,8 @@ __export(digest_exports, {
|
|
|
1877
2056
|
extractCorrections: () => extractCorrections,
|
|
1878
2057
|
readHookInput: () => readHookInput
|
|
1879
2058
|
});
|
|
1880
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync12, existsSync as
|
|
1881
|
-
import { join as
|
|
2059
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync12, existsSync as existsSync15, mkdirSync as mkdirSync10 } from "fs";
|
|
2060
|
+
import { join as join16, basename } from "path";
|
|
1882
2061
|
function readHookInput(stdin) {
|
|
1883
2062
|
if (!stdin.trim()) return null;
|
|
1884
2063
|
try {
|
|
@@ -1893,13 +2072,13 @@ function readHookInput(stdin) {
|
|
|
1893
2072
|
}
|
|
1894
2073
|
}
|
|
1895
2074
|
function digestTranscript(brainRoot, transcriptPath, sessionId) {
|
|
1896
|
-
if (!
|
|
2075
|
+
if (!existsSync15(transcriptPath)) {
|
|
1897
2076
|
throw new Error(`Transcript not found: ${transcriptPath}`);
|
|
1898
2077
|
}
|
|
1899
2078
|
const resolvedSessionId = sessionId || basename(transcriptPath, ".jsonl");
|
|
1900
|
-
const logDir =
|
|
1901
|
-
const logPath =
|
|
1902
|
-
if (
|
|
2079
|
+
const logDir = join16(brainRoot, DIGEST_LOG_DIR);
|
|
2080
|
+
const logPath = join16(logDir, `${resolvedSessionId}.jsonl`);
|
|
2081
|
+
if (existsSync15(logPath)) {
|
|
1903
2082
|
console.log(`\u23ED already digested session ${resolvedSessionId}, skip`);
|
|
1904
2083
|
return { corrections: 0, skipped: 0, transcriptPath, sessionId: resolvedSessionId };
|
|
1905
2084
|
}
|
|
@@ -1914,7 +2093,7 @@ function digestTranscript(brainRoot, transcriptPath, sessionId) {
|
|
|
1914
2093
|
const auditEntries = [];
|
|
1915
2094
|
for (const correction of corrections) {
|
|
1916
2095
|
try {
|
|
1917
|
-
|
|
2096
|
+
growCandidate(brainRoot, correction.path);
|
|
1918
2097
|
logEpisode(brainRoot, "digest", correction.path, correction.text);
|
|
1919
2098
|
auditEntries.push({ correction, applied: true });
|
|
1920
2099
|
applied++;
|
|
@@ -2131,11 +2310,11 @@ function extractKeywords(text) {
|
|
|
2131
2310
|
return text.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[^a-zA-Z0-9\u3000-\u9FFF\uAC00-\uD7AF]+/g, " ").toLowerCase().split(/\s+/).filter((t) => t.length > 2 && !STOP_WORDS.has(t));
|
|
2132
2311
|
}
|
|
2133
2312
|
function writeAuditLog(brainRoot, sessionId, entries) {
|
|
2134
|
-
const logDir =
|
|
2135
|
-
if (!
|
|
2136
|
-
|
|
2313
|
+
const logDir = join16(brainRoot, DIGEST_LOG_DIR);
|
|
2314
|
+
if (!existsSync15(logDir)) {
|
|
2315
|
+
mkdirSync10(logDir, { recursive: true });
|
|
2137
2316
|
}
|
|
2138
|
-
const logPath =
|
|
2317
|
+
const logPath = join16(logDir, `${sessionId}.jsonl`);
|
|
2139
2318
|
const lines = entries.map(
|
|
2140
2319
|
(e) => JSON.stringify({
|
|
2141
2320
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2153,7 +2332,7 @@ var init_digest = __esm({
|
|
|
2153
2332
|
"src/digest.ts"() {
|
|
2154
2333
|
"use strict";
|
|
2155
2334
|
init_constants();
|
|
2156
|
-
|
|
2335
|
+
init_candidates();
|
|
2157
2336
|
init_episode();
|
|
2158
2337
|
NEGATION_PATTERNS = [
|
|
2159
2338
|
/\bdon[''\u2019]?t\b/i,
|
|
@@ -2194,6 +2373,243 @@ var init_digest = __esm({
|
|
|
2194
2373
|
}
|
|
2195
2374
|
});
|
|
2196
2375
|
|
|
2376
|
+
// src/outcome.ts
|
|
2377
|
+
var outcome_exports = {};
|
|
2378
|
+
__export(outcome_exports, {
|
|
2379
|
+
buildOutcomeSummary: () => buildOutcomeSummary,
|
|
2380
|
+
captureSessionStart: () => captureSessionStart,
|
|
2381
|
+
classifyOutcome: () => classifyOutcome,
|
|
2382
|
+
detectOutcome: () => detectOutcome
|
|
2383
|
+
});
|
|
2384
|
+
import { execSync as execSync3 } from "child_process";
|
|
2385
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync13, readFileSync as readFileSync8, readdirSync as readdirSync10, rmSync as rmSync2, statSync as statSync5 } from "fs";
|
|
2386
|
+
import { join as join17 } from "path";
|
|
2387
|
+
import { randomUUID } from "crypto";
|
|
2388
|
+
function captureSessionStart(brainRoot) {
|
|
2389
|
+
let sha;
|
|
2390
|
+
try {
|
|
2391
|
+
sha = execSync3("git rev-parse HEAD", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
2392
|
+
} catch {
|
|
2393
|
+
console.log("\u23ED\uFE0F session start: not a git repo, skipping");
|
|
2394
|
+
return null;
|
|
2395
|
+
}
|
|
2396
|
+
let status;
|
|
2397
|
+
try {
|
|
2398
|
+
const raw = execSync3("git status --porcelain", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
2399
|
+
status = raw ? raw.split("\n") : [];
|
|
2400
|
+
} catch {
|
|
2401
|
+
status = [];
|
|
2402
|
+
}
|
|
2403
|
+
const brain = scanBrain(brainRoot);
|
|
2404
|
+
const result = runSubsumption(brain);
|
|
2405
|
+
const neurons = [];
|
|
2406
|
+
for (const region of result.activeRegions) {
|
|
2407
|
+
for (const neuron of region.neurons) {
|
|
2408
|
+
if (!neuron.isDormant && neuron.counter > 0) {
|
|
2409
|
+
neurons.push(`${region.name}/${neuron.path}`);
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
const uuid = randomUUID();
|
|
2414
|
+
const stateDir = join17(brainRoot, SESSION_STATE_DIR);
|
|
2415
|
+
if (!existsSync16(stateDir)) {
|
|
2416
|
+
mkdirSync11(stateDir, { recursive: true });
|
|
2417
|
+
}
|
|
2418
|
+
const state = { ts: (/* @__PURE__ */ new Date()).toISOString(), sha, status, neurons, uuid };
|
|
2419
|
+
writeFileSync13(join17(stateDir, `state_${uuid}.json`), JSON.stringify(state), "utf8");
|
|
2420
|
+
console.log(`\u{1F4F8} session start: SHA ${sha.slice(0, 7)}, ${neurons.length} active neurons`);
|
|
2421
|
+
return state;
|
|
2422
|
+
}
|
|
2423
|
+
function detectOutcome(brainRoot) {
|
|
2424
|
+
const state = readLatestSessionState(brainRoot);
|
|
2425
|
+
if (!state) {
|
|
2426
|
+
console.log("\u23ED\uFE0F session end: no session state found, skipping");
|
|
2427
|
+
return null;
|
|
2428
|
+
}
|
|
2429
|
+
let currentSha;
|
|
2430
|
+
try {
|
|
2431
|
+
currentSha = execSync3("git rev-parse HEAD", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
2432
|
+
} catch {
|
|
2433
|
+
console.log("\u23ED\uFE0F session end: not a git repo, skipping");
|
|
2434
|
+
cleanupSessionState(brainRoot, state.uuid);
|
|
2435
|
+
return null;
|
|
2436
|
+
}
|
|
2437
|
+
let currentStatus;
|
|
2438
|
+
try {
|
|
2439
|
+
const raw = execSync3("git status --porcelain", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
2440
|
+
currentStatus = raw ? filterHebbianPaths(raw.split("\n")) : [];
|
|
2441
|
+
} catch {
|
|
2442
|
+
currentStatus = [];
|
|
2443
|
+
}
|
|
2444
|
+
const filteredStartStatus = filterHebbianPaths(state.status);
|
|
2445
|
+
const outcome = classifyOutcome(
|
|
2446
|
+
{ ...state, status: filteredStartStatus },
|
|
2447
|
+
currentSha,
|
|
2448
|
+
currentStatus
|
|
2449
|
+
);
|
|
2450
|
+
if (!outcome) {
|
|
2451
|
+
console.log("\u{1F4CA} session end: no changes detected (no-op)");
|
|
2452
|
+
cleanupSessionState(brainRoot, state.uuid);
|
|
2453
|
+
return null;
|
|
2454
|
+
}
|
|
2455
|
+
const neurons = state.neurons;
|
|
2456
|
+
logEpisode(brainRoot, "session-end", "", `outcome:${outcome}`, { outcome, neurons });
|
|
2457
|
+
let result;
|
|
2458
|
+
if (outcome === "revert") {
|
|
2459
|
+
const { affected, skipped } = applyContra(brainRoot, neurons);
|
|
2460
|
+
result = {
|
|
2461
|
+
outcome: "revert",
|
|
2462
|
+
neuronsAffected: affected,
|
|
2463
|
+
protectedSkipped: skipped,
|
|
2464
|
+
detail: `${affected} neurons contra'd (${skipped} protected skipped)`
|
|
2465
|
+
};
|
|
2466
|
+
console.log(`\u{1F4CA} session end: revert \u2014 ${result.detail}`);
|
|
2467
|
+
} else {
|
|
2468
|
+
result = {
|
|
2469
|
+
outcome: "acceptance",
|
|
2470
|
+
neuronsAffected: 0,
|
|
2471
|
+
protectedSkipped: 0,
|
|
2472
|
+
detail: "changes accepted"
|
|
2473
|
+
};
|
|
2474
|
+
console.log("\u{1F4CA} session end: acceptance");
|
|
2475
|
+
}
|
|
2476
|
+
cleanupSessionState(brainRoot, state.uuid);
|
|
2477
|
+
return result;
|
|
2478
|
+
}
|
|
2479
|
+
function classifyOutcome(state, currentSha, currentStatus) {
|
|
2480
|
+
const headMoved = state.sha !== currentSha;
|
|
2481
|
+
const startStatusSet = new Set(state.status);
|
|
2482
|
+
const endStatusSet = new Set(currentStatus);
|
|
2483
|
+
const newItems = currentStatus.filter((s) => !startStatusSet.has(s));
|
|
2484
|
+
const removedItems = state.status.filter((s) => !endStatusSet.has(s));
|
|
2485
|
+
if (!headMoved) {
|
|
2486
|
+
if (newItems.length === 0 && removedItems.length === 0) {
|
|
2487
|
+
return null;
|
|
2488
|
+
}
|
|
2489
|
+
if (newItems.length > 0) {
|
|
2490
|
+
return "acceptance";
|
|
2491
|
+
}
|
|
2492
|
+
if (removedItems.length > 0) {
|
|
2493
|
+
return "revert";
|
|
2494
|
+
}
|
|
2495
|
+
return null;
|
|
2496
|
+
}
|
|
2497
|
+
if (newItems.length > 0) {
|
|
2498
|
+
return "acceptance";
|
|
2499
|
+
}
|
|
2500
|
+
try {
|
|
2501
|
+
const diffStat = execSync3(
|
|
2502
|
+
`git diff ${state.sha}..${currentSha} --stat`,
|
|
2503
|
+
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }
|
|
2504
|
+
).trim();
|
|
2505
|
+
const logOutput = execSync3(
|
|
2506
|
+
`git log --oneline ${state.sha}..${currentSha}`,
|
|
2507
|
+
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }
|
|
2508
|
+
).trim();
|
|
2509
|
+
if (/\brevert\b/i.test(logOutput)) {
|
|
2510
|
+
return "revert";
|
|
2511
|
+
}
|
|
2512
|
+
if (!diffStat) {
|
|
2513
|
+
return "revert";
|
|
2514
|
+
}
|
|
2515
|
+
return "acceptance";
|
|
2516
|
+
} catch {
|
|
2517
|
+
return null;
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
function applyContra(brainRoot, neurons) {
|
|
2521
|
+
let affected = 0;
|
|
2522
|
+
let skipped = 0;
|
|
2523
|
+
for (const neuronPath of neurons) {
|
|
2524
|
+
const region = neuronPath.split("/")[0] || "";
|
|
2525
|
+
if (PROTECTED_REGIONS_CONTRA.includes(region)) {
|
|
2526
|
+
skipped++;
|
|
2527
|
+
continue;
|
|
2528
|
+
}
|
|
2529
|
+
const result = contraNeuron(brainRoot, neuronPath);
|
|
2530
|
+
if (result > 0) {
|
|
2531
|
+
affected++;
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
return { affected, skipped };
|
|
2535
|
+
}
|
|
2536
|
+
function buildOutcomeSummary(brainRoot) {
|
|
2537
|
+
const episodes = readEpisodes(brainRoot);
|
|
2538
|
+
const outcomeEpisodes = episodes.filter((e) => e.outcome && e.neurons);
|
|
2539
|
+
if (outcomeEpisodes.length === 0) return "";
|
|
2540
|
+
const stats = /* @__PURE__ */ new Map();
|
|
2541
|
+
for (const ep of outcomeEpisodes) {
|
|
2542
|
+
for (const neuron of ep.neurons) {
|
|
2543
|
+
const existing = stats.get(neuron) || { sessions: 0, reverts: 0, acceptances: 0 };
|
|
2544
|
+
existing.sessions++;
|
|
2545
|
+
if (ep.outcome === "revert") existing.reverts++;
|
|
2546
|
+
if (ep.outcome === "acceptance") existing.acceptances++;
|
|
2547
|
+
stats.set(neuron, existing);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
const lines = ["## Outcome Signals (from session history)\n"];
|
|
2551
|
+
lines.push("Neurons with high contra_ratio (>0.5) are consistently present in reverted sessions. Consider pruning or modifying them.\n");
|
|
2552
|
+
const sorted = [...stats.entries()].sort((a, b) => {
|
|
2553
|
+
const ratioA = a[1].sessions > 0 ? a[1].reverts / a[1].sessions : 0;
|
|
2554
|
+
const ratioB = b[1].sessions > 0 ? b[1].reverts / b[1].sessions : 0;
|
|
2555
|
+
return ratioB - ratioA;
|
|
2556
|
+
});
|
|
2557
|
+
for (const [neuron, s] of sorted) {
|
|
2558
|
+
const ratio = s.sessions > 0 ? (s.reverts / s.sessions).toFixed(2) : "0.00";
|
|
2559
|
+
const trend = parseFloat(ratio) > 0.5 ? "\u2190 act on this" : parseFloat(ratio) > 0.3 ? "\u2190 watch" : "";
|
|
2560
|
+
lines.push(`- ${neuron}: sessions=${s.sessions} reverts=${s.reverts} acceptances=${s.acceptances} contra_ratio=${ratio} ${trend}`);
|
|
2561
|
+
}
|
|
2562
|
+
lines.push("");
|
|
2563
|
+
return lines.join("\n");
|
|
2564
|
+
}
|
|
2565
|
+
function readLatestSessionState(brainRoot) {
|
|
2566
|
+
const stateDir = join17(brainRoot, SESSION_STATE_DIR);
|
|
2567
|
+
if (!existsSync16(stateDir)) return null;
|
|
2568
|
+
let latest = null;
|
|
2569
|
+
try {
|
|
2570
|
+
for (const entry of readdirSync10(stateDir)) {
|
|
2571
|
+
if (!entry.startsWith("state_") || !entry.endsWith(".json")) continue;
|
|
2572
|
+
const fullPath = join17(stateDir, entry);
|
|
2573
|
+
const mtime = statSync5(fullPath).mtimeMs;
|
|
2574
|
+
if (!latest || mtime > latest.mtime) {
|
|
2575
|
+
latest = { path: fullPath, mtime };
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
} catch {
|
|
2579
|
+
return null;
|
|
2580
|
+
}
|
|
2581
|
+
if (!latest) return null;
|
|
2582
|
+
try {
|
|
2583
|
+
return JSON.parse(readFileSync8(latest.path, "utf8"));
|
|
2584
|
+
} catch {
|
|
2585
|
+
return null;
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
function filterHebbianPaths(statusLines) {
|
|
2589
|
+
const hebbianPatterns = ["hippocampus/session_state", "hippocampus/session_log", "hippocampus/digest_log", "_inbox/"];
|
|
2590
|
+
return statusLines.filter(
|
|
2591
|
+
(line) => !hebbianPatterns.some((p) => line.includes(p))
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
function cleanupSessionState(brainRoot, uuid) {
|
|
2595
|
+
const stateDir = join17(brainRoot, SESSION_STATE_DIR);
|
|
2596
|
+
const filePath = join17(stateDir, `state_${uuid}.json`);
|
|
2597
|
+
try {
|
|
2598
|
+
if (existsSync16(filePath)) rmSync2(filePath);
|
|
2599
|
+
} catch {
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
var init_outcome = __esm({
|
|
2603
|
+
"src/outcome.ts"() {
|
|
2604
|
+
"use strict";
|
|
2605
|
+
init_constants();
|
|
2606
|
+
init_scanner();
|
|
2607
|
+
init_subsumption();
|
|
2608
|
+
init_fire();
|
|
2609
|
+
init_episode();
|
|
2610
|
+
}
|
|
2611
|
+
});
|
|
2612
|
+
|
|
2197
2613
|
// src/evolve.ts
|
|
2198
2614
|
var evolve_exports = {};
|
|
2199
2615
|
__export(evolve_exports, {
|
|
@@ -2214,7 +2630,8 @@ async function runEvolve(brainRoot, dryRun) {
|
|
|
2214
2630
|
const episodes = readEpisodes(brainRoot);
|
|
2215
2631
|
const brain = scanBrain(brainRoot);
|
|
2216
2632
|
const summary = buildBrainSummary(brain);
|
|
2217
|
-
const
|
|
2633
|
+
const outcomeSummary = buildOutcomeSummary(brainRoot);
|
|
2634
|
+
const prompt = buildPrompt(summary, episodes, outcomeSummary);
|
|
2218
2635
|
let rawActions;
|
|
2219
2636
|
try {
|
|
2220
2637
|
rawActions = await callGemini(prompt, apiKey);
|
|
@@ -2262,8 +2679,9 @@ function buildBrainSummary(brain) {
|
|
|
2262
2679
|
}
|
|
2263
2680
|
return lines.join("\n");
|
|
2264
2681
|
}
|
|
2265
|
-
function buildPrompt(summary, episodes) {
|
|
2682
|
+
function buildPrompt(summary, episodes, outcomeSummary) {
|
|
2266
2683
|
const episodeLines = episodes.length > 0 ? episodes.map((e) => `- [${e.ts}] ${e.type}: ${e.path} \u2014 ${e.detail}`).join("\n") : "(no recent episodes)";
|
|
2684
|
+
const outcomeSection = outcomeSummary || "";
|
|
2267
2685
|
return `You are the evolve engine for a hebbian brain \u2014 a filesystem-based memory system for AI agents.
|
|
2268
2686
|
|
|
2269
2687
|
## Axioms
|
|
@@ -2275,6 +2693,7 @@ function buildPrompt(summary, episodes) {
|
|
|
2275
2693
|
## Current Brain
|
|
2276
2694
|
${summary}
|
|
2277
2695
|
|
|
2696
|
+
${outcomeSection}
|
|
2278
2697
|
## Recent Episodes (last ${episodes.length})
|
|
2279
2698
|
${episodeLines}
|
|
2280
2699
|
|
|
@@ -2395,7 +2814,7 @@ function executeActions(brainRoot, actions) {
|
|
|
2395
2814
|
fireNeuron(brainRoot, action.path);
|
|
2396
2815
|
break;
|
|
2397
2816
|
case "grow":
|
|
2398
|
-
|
|
2817
|
+
growCandidate(brainRoot, action.path);
|
|
2399
2818
|
break;
|
|
2400
2819
|
case "signal":
|
|
2401
2820
|
signalNeuron(brainRoot, action.path, action.signal || "dopamine");
|
|
@@ -2439,10 +2858,11 @@ var init_evolve = __esm({
|
|
|
2439
2858
|
init_scanner();
|
|
2440
2859
|
init_constants();
|
|
2441
2860
|
init_fire();
|
|
2442
|
-
|
|
2861
|
+
init_candidates();
|
|
2443
2862
|
init_signal();
|
|
2444
2863
|
init_rollback();
|
|
2445
2864
|
init_decay();
|
|
2865
|
+
init_outcome();
|
|
2446
2866
|
MAX_ACTIONS = 10;
|
|
2447
2867
|
PROTECTED_REGIONS = ["brainstem", "limbic", "sensors"];
|
|
2448
2868
|
DEFAULT_MODEL = "gemini-2.0-flash-lite";
|
|
@@ -2451,11 +2871,151 @@ var init_evolve = __esm({
|
|
|
2451
2871
|
}
|
|
2452
2872
|
});
|
|
2453
2873
|
|
|
2874
|
+
// src/doctor.ts
|
|
2875
|
+
var doctor_exports = {};
|
|
2876
|
+
__export(doctor_exports, {
|
|
2877
|
+
runDoctor: () => runDoctor
|
|
2878
|
+
});
|
|
2879
|
+
import { existsSync as existsSync17, readFileSync as readFileSync9, readdirSync as readdirSync11 } from "fs";
|
|
2880
|
+
import { join as join18 } from "path";
|
|
2881
|
+
import { execSync as execSync4 } from "child_process";
|
|
2882
|
+
async function runDoctor(brainRoot) {
|
|
2883
|
+
let passed = 0, warnings = 0, failed = 0;
|
|
2884
|
+
const ok = (msg) => {
|
|
2885
|
+
console.log(` \u2705 ${msg}`);
|
|
2886
|
+
passed++;
|
|
2887
|
+
};
|
|
2888
|
+
const warn = (msg, fix) => {
|
|
2889
|
+
console.log(` \u26A0\uFE0F ${msg}`);
|
|
2890
|
+
if (fix) console.log(` \u2192 ${fix}`);
|
|
2891
|
+
warnings++;
|
|
2892
|
+
};
|
|
2893
|
+
const fail = (msg, fix) => {
|
|
2894
|
+
console.log(` \u274C ${msg}`);
|
|
2895
|
+
if (fix) console.log(` \u2192 ${fix}`);
|
|
2896
|
+
failed++;
|
|
2897
|
+
};
|
|
2898
|
+
console.log("\n\u{1FA7A} hebbian doctor\n");
|
|
2899
|
+
console.log("Node.js");
|
|
2900
|
+
const nodeVer = process.versions.node;
|
|
2901
|
+
const [major] = nodeVer.split(".").map(Number);
|
|
2902
|
+
if ((major ?? 0) >= 22) {
|
|
2903
|
+
ok(`Node.js ${nodeVer} (>= 22 required)`);
|
|
2904
|
+
} else {
|
|
2905
|
+
fail(`Node.js ${nodeVer} \u2014 need >= 22`, "nvm install 22 && nvm use 22");
|
|
2906
|
+
}
|
|
2907
|
+
console.log("\nnpm package");
|
|
2908
|
+
try {
|
|
2909
|
+
const pkgPath = new URL("../package.json", import.meta.url).pathname;
|
|
2910
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
|
|
2911
|
+
const local = pkg.version || "unknown";
|
|
2912
|
+
let remote = "";
|
|
2913
|
+
try {
|
|
2914
|
+
const out = execSync4("npm view hebbian version 2>/dev/null", { timeout: 5e3 }).toString().trim();
|
|
2915
|
+
remote = out;
|
|
2916
|
+
} catch {
|
|
2917
|
+
}
|
|
2918
|
+
if (remote && remote !== local) {
|
|
2919
|
+
warn(`hebbian ${local} installed, ${remote} available`, "npm i -g hebbian@latest");
|
|
2920
|
+
} else {
|
|
2921
|
+
ok(`hebbian ${local}${remote ? " (up to date)" : ""}`);
|
|
2922
|
+
}
|
|
2923
|
+
} catch {
|
|
2924
|
+
warn("Could not read package.json");
|
|
2925
|
+
}
|
|
2926
|
+
console.log("\nbrain structure");
|
|
2927
|
+
if (!existsSync17(brainRoot)) {
|
|
2928
|
+
fail(`Brain not found at ${brainRoot}`, "hebbian init ./brain");
|
|
2929
|
+
} else {
|
|
2930
|
+
ok(`Brain root: ${brainRoot}`);
|
|
2931
|
+
for (const region of REGIONS) {
|
|
2932
|
+
const regionDir = join18(brainRoot, region);
|
|
2933
|
+
if (existsSync17(regionDir)) {
|
|
2934
|
+
ok(`Region: ${region}`);
|
|
2935
|
+
} else {
|
|
2936
|
+
warn(`Missing region: ${region}`, `mkdir -p ${regionDir}`);
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
console.log("\nClaude Code hooks");
|
|
2941
|
+
const settingsPath = join18(process.cwd(), ".claude", "settings.local.json");
|
|
2942
|
+
if (!existsSync17(settingsPath)) {
|
|
2943
|
+
warn("No .claude/settings.local.json found", "hebbian claude install");
|
|
2944
|
+
} else {
|
|
2945
|
+
try {
|
|
2946
|
+
const settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
|
|
2947
|
+
const hooks = settings.hooks || {};
|
|
2948
|
+
const hasStop = Object.entries(hooks).some(
|
|
2949
|
+
([event, entries]) => event === "Stop" && Array.isArray(entries) && entries.some(
|
|
2950
|
+
(e) => typeof e === "object" && e !== null && "command" in e && typeof e.command === "string" && e.command.includes("hebbian digest")
|
|
2951
|
+
)
|
|
2952
|
+
);
|
|
2953
|
+
const hasStart = Object.entries(hooks).some(
|
|
2954
|
+
([event, entries]) => event === "SessionStart" && Array.isArray(entries) && entries.some(
|
|
2955
|
+
(e) => typeof e === "object" && e !== null && "command" in e && typeof e.command === "string" && e.command.includes("hebbian emit")
|
|
2956
|
+
)
|
|
2957
|
+
);
|
|
2958
|
+
if (hasStop && hasStart) {
|
|
2959
|
+
ok("SessionStart + Stop hooks installed");
|
|
2960
|
+
} else {
|
|
2961
|
+
if (!hasStart) warn("SessionStart hook missing", "hebbian claude install");
|
|
2962
|
+
if (!hasStop) warn("Stop hook missing", "hebbian claude install");
|
|
2963
|
+
}
|
|
2964
|
+
} catch {
|
|
2965
|
+
fail("Malformed .claude/settings.local.json", "hebbian claude install");
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
console.log("\nnpx resolution");
|
|
2969
|
+
try {
|
|
2970
|
+
const resolved = execSync4("which npx", { timeout: 3e3 }).toString().trim();
|
|
2971
|
+
ok(`npx: ${resolved}`);
|
|
2972
|
+
} catch {
|
|
2973
|
+
fail("npx not found in PATH", "Install Node.js from https://nodejs.org");
|
|
2974
|
+
}
|
|
2975
|
+
console.log("\ncandidates");
|
|
2976
|
+
try {
|
|
2977
|
+
let total = 0;
|
|
2978
|
+
for (const region of REGIONS) {
|
|
2979
|
+
const candidateDir = join18(brainRoot, region, "_candidates");
|
|
2980
|
+
if (existsSync17(candidateDir)) {
|
|
2981
|
+
const entries = readdirSync11(candidateDir, { withFileTypes: true });
|
|
2982
|
+
const count = entries.filter((e) => e.isDirectory()).length;
|
|
2983
|
+
total += count;
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
if (total === 0) {
|
|
2987
|
+
ok("No pending candidates");
|
|
2988
|
+
} else {
|
|
2989
|
+
warn(`${total} candidate(s) pending`, "hebbian candidates \u2014 to view");
|
|
2990
|
+
}
|
|
2991
|
+
} catch {
|
|
2992
|
+
warn("Could not scan candidates");
|
|
2993
|
+
}
|
|
2994
|
+
console.log(`
|
|
2995
|
+
${"\u2500".repeat(40)}`);
|
|
2996
|
+
console.log(` passed: ${passed} warnings: ${warnings} failed: ${failed}`);
|
|
2997
|
+
if (failed > 0) {
|
|
2998
|
+
console.log(" Fix the \u274C issues above, then re-run `hebbian doctor`");
|
|
2999
|
+
} else if (warnings > 0) {
|
|
3000
|
+
console.log(" Looking mostly good! Review \u26A0\uFE0F warnings above.");
|
|
3001
|
+
} else {
|
|
3002
|
+
console.log(" All checks passed. \u{1F389}");
|
|
3003
|
+
}
|
|
3004
|
+
console.log("");
|
|
3005
|
+
return { passed, warnings, failed };
|
|
3006
|
+
}
|
|
3007
|
+
var init_doctor = __esm({
|
|
3008
|
+
"src/doctor.ts"() {
|
|
3009
|
+
"use strict";
|
|
3010
|
+
init_constants();
|
|
3011
|
+
}
|
|
3012
|
+
});
|
|
3013
|
+
|
|
2454
3014
|
// src/cli.ts
|
|
2455
3015
|
init_constants();
|
|
2456
3016
|
import { parseArgs } from "util";
|
|
2457
3017
|
import { resolve as resolve3 } from "path";
|
|
2458
|
-
var VERSION = "0.
|
|
3018
|
+
var VERSION = "0.5.0";
|
|
2459
3019
|
var HELP = `
|
|
2460
3020
|
hebbian v${VERSION} \u2014 Folder-as-neuron brain for any AI agent.
|
|
2461
3021
|
|
|
@@ -2479,7 +3039,11 @@ COMMANDS:
|
|
|
2479
3039
|
inbox Process corrections inbox
|
|
2480
3040
|
claude install|uninstall|status Manage Claude Code hooks
|
|
2481
3041
|
digest [--transcript <path>] Extract corrections from conversation
|
|
3042
|
+
candidates [promote] List candidates or promote graduated ones
|
|
2482
3043
|
evolve [--dry-run] LLM-powered brain evolution (Gemini)
|
|
3044
|
+
session start|end Capture/detect session outcomes
|
|
3045
|
+
sessions Show session outcome history
|
|
3046
|
+
doctor Self-diagnostic (hooks, brain, versions)
|
|
2483
3047
|
diag Print brain diagnostics
|
|
2484
3048
|
stats Print brain statistics
|
|
2485
3049
|
|
|
@@ -2521,6 +3085,7 @@ async function main(argv) {
|
|
|
2521
3085
|
port: { type: "string", short: "p" },
|
|
2522
3086
|
transcript: { type: "string", short: "t" },
|
|
2523
3087
|
"dry-run": { type: "boolean" },
|
|
3088
|
+
global: { type: "boolean", short: "g" },
|
|
2524
3089
|
help: { type: "boolean", short: "h" },
|
|
2525
3090
|
version: { type: "boolean", short: "v" }
|
|
2526
3091
|
},
|
|
@@ -2641,18 +3206,23 @@ async function main(argv) {
|
|
|
2641
3206
|
}
|
|
2642
3207
|
case "claude": {
|
|
2643
3208
|
const sub = positionals[1];
|
|
3209
|
+
const isGlobal = values.global === true;
|
|
2644
3210
|
const { installHooks: installHooks2, uninstallHooks: uninstallHooks2, checkHooks: checkHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
|
|
2645
3211
|
switch (sub) {
|
|
2646
3212
|
case "install": {
|
|
2647
|
-
const installBrain = values.brain ? resolve3(values.brain) : resolve3("./brain");
|
|
2648
|
-
|
|
3213
|
+
const installBrain = values.brain ? resolve3(values.brain) : isGlobal ? process.env.HEBBIAN_BRAIN ? resolve3(process.env.HEBBIAN_BRAIN) : "" : resolve3("./brain");
|
|
3214
|
+
if (isGlobal && !installBrain) {
|
|
3215
|
+
console.error("\u274C --global requires --brain <path> or HEBBIAN_BRAIN env var");
|
|
3216
|
+
process.exit(1);
|
|
3217
|
+
}
|
|
3218
|
+
installHooks2(installBrain, void 0, isGlobal);
|
|
2649
3219
|
break;
|
|
2650
3220
|
}
|
|
2651
3221
|
case "uninstall":
|
|
2652
|
-
uninstallHooks2();
|
|
3222
|
+
uninstallHooks2(void 0, isGlobal);
|
|
2653
3223
|
break;
|
|
2654
3224
|
case "status": {
|
|
2655
|
-
checkHooks2();
|
|
3225
|
+
checkHooks2(void 0, isGlobal);
|
|
2656
3226
|
console.log(` version: v${VERSION}`);
|
|
2657
3227
|
const { checkForUpdates: checkUpdates, formatUpdateBanner: formatBanner } = await Promise.resolve().then(() => (init_update_check(), update_check_exports));
|
|
2658
3228
|
const updateStatus = await checkUpdates(VERSION);
|
|
@@ -2661,7 +3231,7 @@ async function main(argv) {
|
|
|
2661
3231
|
break;
|
|
2662
3232
|
}
|
|
2663
3233
|
default:
|
|
2664
|
-
console.error("Usage: hebbian claude <install|uninstall|status>");
|
|
3234
|
+
console.error("Usage: hebbian claude <install|uninstall|status> [--global]");
|
|
2665
3235
|
process.exit(1);
|
|
2666
3236
|
}
|
|
2667
3237
|
break;
|
|
@@ -2684,12 +3254,70 @@ async function main(argv) {
|
|
|
2684
3254
|
}
|
|
2685
3255
|
break;
|
|
2686
3256
|
}
|
|
3257
|
+
case "candidates": {
|
|
3258
|
+
const subCmd = positionals[1];
|
|
3259
|
+
const { listCandidates: listCandidates2, promoteCandidates: promoteCandidates2 } = await Promise.resolve().then(() => (init_candidates(), candidates_exports));
|
|
3260
|
+
if (subCmd === "promote") {
|
|
3261
|
+
const result = promoteCandidates2(brainRoot);
|
|
3262
|
+
console.log(`\u{1F393} promoted: ${result.promoted.length}, decayed: ${result.decayed.length}`);
|
|
3263
|
+
} else {
|
|
3264
|
+
const candidates = listCandidates2(brainRoot);
|
|
3265
|
+
if (candidates.length === 0) {
|
|
3266
|
+
console.log("No pending candidates");
|
|
3267
|
+
} else {
|
|
3268
|
+
console.log(`Candidates (promote at counter=${3}):`);
|
|
3269
|
+
for (const c of candidates) {
|
|
3270
|
+
const bar = "\u2588".repeat(c.counter) + "\u2591".repeat(Math.max(0, 3 - c.counter));
|
|
3271
|
+
console.log(` ${bar} ${c.counter}/3 ${c.targetPath} (${c.daysInactive}d idle)`);
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
break;
|
|
3276
|
+
}
|
|
2687
3277
|
case "evolve": {
|
|
2688
3278
|
const dryRun = values["dry-run"] === true;
|
|
2689
3279
|
const { runEvolve: runEvolve2 } = await Promise.resolve().then(() => (init_evolve(), evolve_exports));
|
|
2690
3280
|
await runEvolve2(brainRoot, dryRun);
|
|
2691
3281
|
break;
|
|
2692
3282
|
}
|
|
3283
|
+
case "session": {
|
|
3284
|
+
const sub = positionals[1];
|
|
3285
|
+
const { captureSessionStart: captureSessionStart2, detectOutcome: detectOutcome2 } = await Promise.resolve().then(() => (init_outcome(), outcome_exports));
|
|
3286
|
+
switch (sub) {
|
|
3287
|
+
case "start":
|
|
3288
|
+
captureSessionStart2(brainRoot);
|
|
3289
|
+
break;
|
|
3290
|
+
case "end":
|
|
3291
|
+
detectOutcome2(brainRoot);
|
|
3292
|
+
break;
|
|
3293
|
+
default:
|
|
3294
|
+
console.error("Usage: hebbian session <start|end>");
|
|
3295
|
+
process.exit(1);
|
|
3296
|
+
}
|
|
3297
|
+
break;
|
|
3298
|
+
}
|
|
3299
|
+
case "sessions": {
|
|
3300
|
+
const { readEpisodes: readEpisodes2 } = await Promise.resolve().then(() => (init_episode(), episode_exports));
|
|
3301
|
+
const episodes = readEpisodes2(brainRoot).filter((e) => e.outcome);
|
|
3302
|
+
if (episodes.length === 0) {
|
|
3303
|
+
console.log("No session outcomes recorded yet");
|
|
3304
|
+
} else {
|
|
3305
|
+
console.log("Session Outcomes:");
|
|
3306
|
+
for (const ep of episodes.reverse()) {
|
|
3307
|
+
const icon = ep.outcome === "revert" ? "\u{1F504}" : "\u2705";
|
|
3308
|
+
const neurons = ep.neurons ? `${ep.neurons.length} neurons` : "";
|
|
3309
|
+
console.log(` ${icon} ${ep.ts.slice(0, 19)} ${ep.outcome} ${neurons}`);
|
|
3310
|
+
}
|
|
3311
|
+
console.log(`
|
|
3312
|
+
Total: ${episodes.length} sessions`);
|
|
3313
|
+
}
|
|
3314
|
+
break;
|
|
3315
|
+
}
|
|
3316
|
+
case "doctor": {
|
|
3317
|
+
const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
3318
|
+
await runDoctor2(brainRoot);
|
|
3319
|
+
break;
|
|
3320
|
+
}
|
|
2693
3321
|
case "diag":
|
|
2694
3322
|
case "stats": {
|
|
2695
3323
|
const { scanBrain: scanBrain2 } = await Promise.resolve().then(() => (init_scanner(), scanner_exports));
|