hebbian 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +23 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/bin/hebbian.js +1012 -38
- package/dist/bin/hebbian.js.map +1 -1
- package/dist/constants.d.ts +6 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/digest.d.ts +30 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/episode.d.ts +16 -0
- package/dist/episode.d.ts.map +1 -0
- package/dist/hooks.d.ts +20 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/inbox.d.ts +28 -0
- package/dist/inbox.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +906 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/hebbian.js
CHANGED
|
@@ -10,7 +10,15 @@ var __export = (target, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// src/constants.ts
|
|
13
|
-
|
|
13
|
+
import { resolve } from "path";
|
|
14
|
+
import { existsSync } from "fs";
|
|
15
|
+
function resolveBrainRoot(brainFlag) {
|
|
16
|
+
if (brainFlag) return resolve(brainFlag);
|
|
17
|
+
if (process.env.HEBBIAN_BRAIN) return resolve(process.env.HEBBIAN_BRAIN);
|
|
18
|
+
if (existsSync(resolve("./brain"))) return resolve("./brain");
|
|
19
|
+
return resolve(process.env.HOME || "~", "hebbian", "brain");
|
|
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;
|
|
14
22
|
var init_constants = __esm({
|
|
15
23
|
"src/constants.ts"() {
|
|
16
24
|
"use strict";
|
|
@@ -64,6 +72,10 @@ var init_constants = __esm({
|
|
|
64
72
|
SIGNAL_TYPES = ["dopamine", "bomb", "memory"];
|
|
65
73
|
MARKER_START = "<!-- HEBBIAN:START -->";
|
|
66
74
|
MARKER_END = "<!-- HEBBIAN:END -->";
|
|
75
|
+
HOOK_MARKER = "[hebbian]";
|
|
76
|
+
MAX_CORRECTIONS_PER_SESSION = 10;
|
|
77
|
+
MIN_CORRECTION_LENGTH = 15;
|
|
78
|
+
DIGEST_LOG_DIR = "hippocampus/digest_log";
|
|
67
79
|
}
|
|
68
80
|
});
|
|
69
81
|
|
|
@@ -72,10 +84,10 @@ var init_exports = {};
|
|
|
72
84
|
__export(init_exports, {
|
|
73
85
|
initBrain: () => initBrain
|
|
74
86
|
});
|
|
75
|
-
import { mkdirSync, writeFileSync, existsSync, readdirSync } from "fs";
|
|
87
|
+
import { mkdirSync, writeFileSync, existsSync as existsSync2, readdirSync } from "fs";
|
|
76
88
|
import { join } from "path";
|
|
77
89
|
function initBrain(brainPath) {
|
|
78
|
-
if (
|
|
90
|
+
if (existsSync2(brainPath)) {
|
|
79
91
|
const entries = readdirSync(brainPath);
|
|
80
92
|
if (entries.some((e) => REGIONS.includes(e))) {
|
|
81
93
|
console.log(`\u26A0\uFE0F Brain already exists at ${brainPath}`);
|
|
@@ -108,7 +120,7 @@ ${template.description}
|
|
|
108
120
|
console.log(` 7 regions created: ${REGIONS.join(", ")}`);
|
|
109
121
|
console.log("");
|
|
110
122
|
console.log(" Next steps:");
|
|
111
|
-
console.log(` hebbian grow brainstem
|
|
123
|
+
console.log(` hebbian grow brainstem/NO_your_rule --brain ${brainPath}`);
|
|
112
124
|
console.log(` hebbian emit claude --brain ${brainPath}`);
|
|
113
125
|
}
|
|
114
126
|
var REGION_TEMPLATES;
|
|
@@ -119,7 +131,7 @@ var init_init = __esm({
|
|
|
119
131
|
REGION_TEMPLATES = {
|
|
120
132
|
brainstem: {
|
|
121
133
|
description: "Absolute principles. Immutable. Read-only conscience.\nP0 \u2014 highest priority. bomb here halts EVERYTHING.",
|
|
122
|
-
starters: ["
|
|
134
|
+
starters: ["NO_fallback", "DO_execute_not_debate"]
|
|
123
135
|
},
|
|
124
136
|
limbic: {
|
|
125
137
|
description: "Emotional filters and somatic markers.\nP1 \u2014 automatic, influences downstream regions.",
|
|
@@ -154,13 +166,13 @@ var scanner_exports = {};
|
|
|
154
166
|
__export(scanner_exports, {
|
|
155
167
|
scanBrain: () => scanBrain
|
|
156
168
|
});
|
|
157
|
-
import { readdirSync as readdirSync2, statSync, readFileSync, existsSync as
|
|
169
|
+
import { readdirSync as readdirSync2, statSync, readFileSync, existsSync as existsSync3 } from "fs";
|
|
158
170
|
import { join as join2, relative, sep } from "path";
|
|
159
171
|
function scanBrain(brainRoot) {
|
|
160
172
|
const regions = [];
|
|
161
173
|
for (const regionName of REGIONS) {
|
|
162
174
|
const regionPath = join2(brainRoot, regionName);
|
|
163
|
-
if (!
|
|
175
|
+
if (!existsSync3(regionPath)) {
|
|
164
176
|
regions.push({
|
|
165
177
|
name: regionName,
|
|
166
178
|
priority: REGION_PRIORITY[regionName],
|
|
@@ -279,7 +291,7 @@ function walkRegion(dir, regionRoot, depth) {
|
|
|
279
291
|
}
|
|
280
292
|
function readAxons(regionPath) {
|
|
281
293
|
const axonPath = join2(regionPath, ".axon");
|
|
282
|
-
if (!
|
|
294
|
+
if (!existsSync3(axonPath)) return [];
|
|
283
295
|
try {
|
|
284
296
|
const content = readFileSync(axonPath, "utf8").trim();
|
|
285
297
|
return content.split(/[\n,]+/).map((s) => s.trim()).filter(Boolean);
|
|
@@ -352,7 +364,7 @@ __export(emit_exports, {
|
|
|
352
364
|
printDiag: () => printDiag,
|
|
353
365
|
writeAllTiers: () => writeAllTiers
|
|
354
366
|
});
|
|
355
|
-
import { existsSync as
|
|
367
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
356
368
|
import { join as join3, dirname } from "path";
|
|
357
369
|
function emitBootstrap(result, brain) {
|
|
358
370
|
const lines = [];
|
|
@@ -522,7 +534,7 @@ function writeAllTiers(brainRoot, result, brain) {
|
|
|
522
534
|
const indexContent = emitIndex(result, brain);
|
|
523
535
|
writeFileSync2(join3(brainRoot, "_index.md"), indexContent, "utf8");
|
|
524
536
|
for (const region of result.activeRegions) {
|
|
525
|
-
if (
|
|
537
|
+
if (existsSync4(region.path)) {
|
|
526
538
|
const rulesContent = emitRegionRules(region);
|
|
527
539
|
writeFileSync2(join3(region.path, "_rules.md"), rulesContent, "utf8");
|
|
528
540
|
}
|
|
@@ -530,10 +542,10 @@ function writeAllTiers(brainRoot, result, brain) {
|
|
|
530
542
|
}
|
|
531
543
|
function writeTarget(filePath, content) {
|
|
532
544
|
const dir = dirname(filePath);
|
|
533
|
-
if (dir !== "." && !
|
|
545
|
+
if (dir !== "." && !existsSync4(dir)) {
|
|
534
546
|
mkdirSync2(dir, { recursive: true });
|
|
535
547
|
}
|
|
536
|
-
if (
|
|
548
|
+
if (existsSync4(filePath)) {
|
|
537
549
|
const existing = readFileSync2(filePath, "utf8");
|
|
538
550
|
const startIdx = existing.indexOf(MARKER_START);
|
|
539
551
|
const endIdx = existing.indexOf(MARKER_END);
|
|
@@ -582,8 +594,8 @@ function sortedActive(neurons, n) {
|
|
|
582
594
|
return [...neurons].filter((neuron) => !neuron.isDormant).sort((a, b) => b.counter - a.counter).slice(0, n);
|
|
583
595
|
}
|
|
584
596
|
function strengthPrefix(counter) {
|
|
585
|
-
if (counter >= 10) return "**[
|
|
586
|
-
if (counter >= 5) return "**[
|
|
597
|
+
if (counter >= 10) return "**[ABSOLUTE]** ";
|
|
598
|
+
if (counter >= 5) return "**[MUST]** ";
|
|
587
599
|
return "";
|
|
588
600
|
}
|
|
589
601
|
var init_emit = __esm({
|
|
@@ -601,11 +613,11 @@ __export(fire_exports, {
|
|
|
601
613
|
fireNeuron: () => fireNeuron,
|
|
602
614
|
getCurrentCounter: () => getCurrentCounter
|
|
603
615
|
});
|
|
604
|
-
import { readdirSync as readdirSync3, renameSync, writeFileSync as writeFileSync3, existsSync as
|
|
616
|
+
import { readdirSync as readdirSync3, renameSync, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
605
617
|
import { join as join4 } from "path";
|
|
606
618
|
function fireNeuron(brainRoot, neuronPath) {
|
|
607
619
|
const fullPath = join4(brainRoot, neuronPath);
|
|
608
|
-
if (!
|
|
620
|
+
if (!existsSync5(fullPath)) {
|
|
609
621
|
mkdirSync3(fullPath, { recursive: true });
|
|
610
622
|
writeFileSync3(join4(fullPath, "1.neuron"), "", "utf8");
|
|
611
623
|
console.log(`\u{1F331} grew + fired: ${neuronPath} (1)`);
|
|
@@ -676,11 +688,11 @@ var grow_exports = {};
|
|
|
676
688
|
__export(grow_exports, {
|
|
677
689
|
growNeuron: () => growNeuron
|
|
678
690
|
});
|
|
679
|
-
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, existsSync as
|
|
691
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, existsSync as existsSync6, readdirSync as readdirSync4 } from "fs";
|
|
680
692
|
import { join as join5, relative as relative2 } from "path";
|
|
681
693
|
function growNeuron(brainRoot, neuronPath) {
|
|
682
694
|
const fullPath = join5(brainRoot, neuronPath);
|
|
683
|
-
if (
|
|
695
|
+
if (existsSync6(fullPath)) {
|
|
684
696
|
const counter = fireNeuron(brainRoot, neuronPath);
|
|
685
697
|
return { action: "fired", path: neuronPath, counter };
|
|
686
698
|
}
|
|
@@ -692,7 +704,7 @@ function growNeuron(brainRoot, neuronPath) {
|
|
|
692
704
|
const leafName = parts[parts.length - 1];
|
|
693
705
|
const newTokens = tokenize(leafName);
|
|
694
706
|
const regionPath = join5(brainRoot, regionName);
|
|
695
|
-
if (
|
|
707
|
+
if (existsSync6(regionPath)) {
|
|
696
708
|
const match = findSimilar(regionPath, regionPath, newTokens);
|
|
697
709
|
if (match) {
|
|
698
710
|
const matchRelPath = regionName + "/" + relative2(regionPath, match);
|
|
@@ -773,14 +785,14 @@ var signal_exports = {};
|
|
|
773
785
|
__export(signal_exports, {
|
|
774
786
|
signalNeuron: () => signalNeuron
|
|
775
787
|
});
|
|
776
|
-
import { writeFileSync as writeFileSync5, existsSync as
|
|
788
|
+
import { writeFileSync as writeFileSync5, existsSync as existsSync7, readdirSync as readdirSync5 } from "fs";
|
|
777
789
|
import { join as join7 } from "path";
|
|
778
790
|
function signalNeuron(brainRoot, neuronPath, signalType) {
|
|
779
791
|
if (!SIGNAL_TYPES.includes(signalType)) {
|
|
780
792
|
throw new Error(`Invalid signal type: ${signalType}. Valid: ${SIGNAL_TYPES.join(", ")}`);
|
|
781
793
|
}
|
|
782
794
|
const fullPath = join7(brainRoot, neuronPath);
|
|
783
|
-
if (!
|
|
795
|
+
if (!existsSync7(fullPath)) {
|
|
784
796
|
throw new Error(`Neuron not found: ${neuronPath}`);
|
|
785
797
|
}
|
|
786
798
|
switch (signalType) {
|
|
@@ -828,7 +840,7 @@ var decay_exports = {};
|
|
|
828
840
|
__export(decay_exports, {
|
|
829
841
|
runDecay: () => runDecay
|
|
830
842
|
});
|
|
831
|
-
import { readdirSync as readdirSync6, statSync as statSync2, writeFileSync as writeFileSync6, existsSync as
|
|
843
|
+
import { readdirSync as readdirSync6, statSync as statSync2, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "fs";
|
|
832
844
|
import { join as join8 } from "path";
|
|
833
845
|
function runDecay(brainRoot, days) {
|
|
834
846
|
const threshold = Date.now() - days * 24 * 60 * 60 * 1e3;
|
|
@@ -836,7 +848,7 @@ function runDecay(brainRoot, days) {
|
|
|
836
848
|
let decayed = 0;
|
|
837
849
|
for (const regionName of REGIONS) {
|
|
838
850
|
const regionPath = join8(brainRoot, regionName);
|
|
839
|
-
if (!
|
|
851
|
+
if (!existsSync8(regionPath)) continue;
|
|
840
852
|
const result = decayWalk(regionPath, threshold, 0);
|
|
841
853
|
scanned += result.scanned;
|
|
842
854
|
decayed += result.decayed;
|
|
@@ -960,10 +972,10 @@ __export(snapshot_exports, {
|
|
|
960
972
|
gitSnapshot: () => gitSnapshot
|
|
961
973
|
});
|
|
962
974
|
import { execSync } from "child_process";
|
|
963
|
-
import { existsSync as
|
|
975
|
+
import { existsSync as existsSync9 } from "fs";
|
|
964
976
|
import { join as join10 } from "path";
|
|
965
977
|
function gitSnapshot(brainRoot) {
|
|
966
|
-
if (!
|
|
978
|
+
if (!existsSync9(join10(brainRoot, ".git"))) {
|
|
967
979
|
try {
|
|
968
980
|
execSync("git rev-parse --is-inside-work-tree", { cwd: brainRoot, stdio: "pipe" });
|
|
969
981
|
} catch {
|
|
@@ -1024,11 +1036,11 @@ async function startWatch(brainRoot) {
|
|
|
1024
1036
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
1025
1037
|
debounceTimer = setTimeout(recompile, 200);
|
|
1026
1038
|
});
|
|
1027
|
-
await new Promise((
|
|
1039
|
+
await new Promise((resolve4) => {
|
|
1028
1040
|
process.on("SIGINT", () => {
|
|
1029
1041
|
watcher.close();
|
|
1030
1042
|
console.log("\n\u{1F44B} watch stopped.");
|
|
1031
|
-
|
|
1043
|
+
resolve4();
|
|
1032
1044
|
});
|
|
1033
1045
|
});
|
|
1034
1046
|
} catch (err) {
|
|
@@ -1051,11 +1063,908 @@ var init_watch = __esm({
|
|
|
1051
1063
|
}
|
|
1052
1064
|
});
|
|
1053
1065
|
|
|
1066
|
+
// src/episode.ts
|
|
1067
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5, existsSync as existsSync10 } from "fs";
|
|
1068
|
+
import { join as join11 } from "path";
|
|
1069
|
+
function logEpisode(brainRoot, type, path, detail) {
|
|
1070
|
+
const logDir = join11(brainRoot, SESSION_LOG_DIR);
|
|
1071
|
+
if (!existsSync10(logDir)) {
|
|
1072
|
+
mkdirSync5(logDir, { recursive: true });
|
|
1073
|
+
}
|
|
1074
|
+
const nextSlot = getNextSlot(logDir);
|
|
1075
|
+
const episode = {
|
|
1076
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1077
|
+
type,
|
|
1078
|
+
path,
|
|
1079
|
+
detail
|
|
1080
|
+
};
|
|
1081
|
+
writeFileSync8(
|
|
1082
|
+
join11(logDir, `memory${nextSlot}.neuron`),
|
|
1083
|
+
JSON.stringify(episode),
|
|
1084
|
+
"utf8"
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
function getNextSlot(logDir) {
|
|
1088
|
+
let maxSlot = 0;
|
|
1089
|
+
try {
|
|
1090
|
+
for (const entry of readdirSync7(logDir)) {
|
|
1091
|
+
if (entry.startsWith("memory") && entry.endsWith(".neuron")) {
|
|
1092
|
+
const n = parseInt(entry.replace("memory", "").replace(".neuron", ""), 10);
|
|
1093
|
+
if (!isNaN(n) && n > maxSlot) maxSlot = n;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
} catch {
|
|
1097
|
+
}
|
|
1098
|
+
const next = maxSlot + 1;
|
|
1099
|
+
return next > MAX_EPISODES ? maxSlot % MAX_EPISODES + 1 : next;
|
|
1100
|
+
}
|
|
1101
|
+
var MAX_EPISODES, SESSION_LOG_DIR;
|
|
1102
|
+
var init_episode = __esm({
|
|
1103
|
+
"src/episode.ts"() {
|
|
1104
|
+
"use strict";
|
|
1105
|
+
MAX_EPISODES = 100;
|
|
1106
|
+
SESSION_LOG_DIR = "hippocampus/session_log";
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
// src/inbox.ts
|
|
1111
|
+
var inbox_exports = {};
|
|
1112
|
+
__export(inbox_exports, {
|
|
1113
|
+
appendCorrection: () => appendCorrection,
|
|
1114
|
+
ensureInbox: () => ensureInbox,
|
|
1115
|
+
processInbox: () => processInbox
|
|
1116
|
+
});
|
|
1117
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync9, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
|
|
1118
|
+
import { join as join12 } from "path";
|
|
1119
|
+
function processInbox(brainRoot) {
|
|
1120
|
+
const inboxPath = join12(brainRoot, INBOX_DIR, CORRECTIONS_FILE);
|
|
1121
|
+
if (!existsSync11(inboxPath)) {
|
|
1122
|
+
return { processed: 0, skipped: 0, errors: [] };
|
|
1123
|
+
}
|
|
1124
|
+
const content = readFileSync4(inboxPath, "utf8").trim();
|
|
1125
|
+
if (!content) {
|
|
1126
|
+
return { processed: 0, skipped: 0, errors: [] };
|
|
1127
|
+
}
|
|
1128
|
+
const lines = content.split("\n").filter(Boolean);
|
|
1129
|
+
let processed = 0;
|
|
1130
|
+
let skipped = 0;
|
|
1131
|
+
const errors = [];
|
|
1132
|
+
for (const line of lines) {
|
|
1133
|
+
let correction;
|
|
1134
|
+
try {
|
|
1135
|
+
correction = JSON.parse(line);
|
|
1136
|
+
} catch {
|
|
1137
|
+
errors.push(`Malformed JSON: ${line.slice(0, 80)}`);
|
|
1138
|
+
skipped++;
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
if (!correction.path || !correction.type) {
|
|
1142
|
+
errors.push(`Missing path or type: ${line.slice(0, 80)}`);
|
|
1143
|
+
skipped++;
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
if (!isPathSafe(correction.path)) {
|
|
1147
|
+
errors.push(`Path traversal blocked: ${correction.path}`);
|
|
1148
|
+
skipped++;
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
const region = correction.path.split("/")[0];
|
|
1152
|
+
if (!region || !REGIONS.includes(region)) {
|
|
1153
|
+
errors.push(`Invalid region in path: ${correction.path}`);
|
|
1154
|
+
skipped++;
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
try {
|
|
1158
|
+
applyCorrection(brainRoot, correction);
|
|
1159
|
+
processed++;
|
|
1160
|
+
} catch (err) {
|
|
1161
|
+
errors.push(`Failed to apply ${correction.path}: ${err.message}`);
|
|
1162
|
+
skipped++;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
writeFileSync9(inboxPath, "", "utf8");
|
|
1166
|
+
console.log(`\u{1F4E5} inbox: processed ${processed}, skipped ${skipped}`);
|
|
1167
|
+
if (errors.length > 0) {
|
|
1168
|
+
for (const err of errors) {
|
|
1169
|
+
console.log(` \u26A0\uFE0F ${err}`);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
return { processed, skipped, errors };
|
|
1173
|
+
}
|
|
1174
|
+
function applyCorrection(brainRoot, correction) {
|
|
1175
|
+
const neuronPath = correction.path;
|
|
1176
|
+
const fullPath = join12(brainRoot, neuronPath);
|
|
1177
|
+
const counterAdd = Math.max(1, correction.counter_add || 1);
|
|
1178
|
+
if (existsSync11(fullPath)) {
|
|
1179
|
+
for (let i = 0; i < counterAdd; i++) {
|
|
1180
|
+
fireNeuron(brainRoot, neuronPath);
|
|
1181
|
+
}
|
|
1182
|
+
} else {
|
|
1183
|
+
growNeuron(brainRoot, neuronPath);
|
|
1184
|
+
for (let i = 1; i < counterAdd; i++) {
|
|
1185
|
+
fireNeuron(brainRoot, neuronPath);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
if (correction.dopamine && correction.dopamine > 0) {
|
|
1189
|
+
const author = (correction.author || "").toLowerCase();
|
|
1190
|
+
if (DOPAMINE_ALLOWED_ROLES.includes(author)) {
|
|
1191
|
+
signalNeuron(brainRoot, neuronPath, "dopamine");
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
logEpisode(brainRoot, "inbox", neuronPath, correction.text || "");
|
|
1195
|
+
}
|
|
1196
|
+
function isPathSafe(path) {
|
|
1197
|
+
if (path.includes("..")) return false;
|
|
1198
|
+
if (path.startsWith("/")) return false;
|
|
1199
|
+
if (path.includes("\\")) return false;
|
|
1200
|
+
if (path.includes("\0")) return false;
|
|
1201
|
+
return true;
|
|
1202
|
+
}
|
|
1203
|
+
function ensureInbox(brainRoot) {
|
|
1204
|
+
const inboxDir = join12(brainRoot, INBOX_DIR);
|
|
1205
|
+
if (!existsSync11(inboxDir)) {
|
|
1206
|
+
mkdirSync6(inboxDir, { recursive: true });
|
|
1207
|
+
}
|
|
1208
|
+
const filePath = join12(inboxDir, CORRECTIONS_FILE);
|
|
1209
|
+
if (!existsSync11(filePath)) {
|
|
1210
|
+
writeFileSync9(filePath, "", "utf8");
|
|
1211
|
+
}
|
|
1212
|
+
return filePath;
|
|
1213
|
+
}
|
|
1214
|
+
function appendCorrection(brainRoot, correction) {
|
|
1215
|
+
const filePath = ensureInbox(brainRoot);
|
|
1216
|
+
const line = JSON.stringify(correction) + "\n";
|
|
1217
|
+
const existing = readFileSync4(filePath, "utf8");
|
|
1218
|
+
writeFileSync9(filePath, existing + line, "utf8");
|
|
1219
|
+
}
|
|
1220
|
+
var INBOX_DIR, CORRECTIONS_FILE, DOPAMINE_ALLOWED_ROLES;
|
|
1221
|
+
var init_inbox = __esm({
|
|
1222
|
+
"src/inbox.ts"() {
|
|
1223
|
+
"use strict";
|
|
1224
|
+
init_constants();
|
|
1225
|
+
init_grow();
|
|
1226
|
+
init_fire();
|
|
1227
|
+
init_signal();
|
|
1228
|
+
init_episode();
|
|
1229
|
+
INBOX_DIR = "_inbox";
|
|
1230
|
+
CORRECTIONS_FILE = "corrections.jsonl";
|
|
1231
|
+
DOPAMINE_ALLOWED_ROLES = ["pm", "admin", "lead"];
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
// src/api.ts
|
|
1236
|
+
var api_exports = {};
|
|
1237
|
+
__export(api_exports, {
|
|
1238
|
+
clearReports: () => clearReports,
|
|
1239
|
+
getLastActivity: () => getLastActivity,
|
|
1240
|
+
getPendingReports: () => getPendingReports,
|
|
1241
|
+
startAPI: () => startAPI
|
|
1242
|
+
});
|
|
1243
|
+
import { createServer } from "http";
|
|
1244
|
+
function buildHealthJSON(brainRoot) {
|
|
1245
|
+
const brain = scanBrain(brainRoot);
|
|
1246
|
+
const result = runSubsumption(brain);
|
|
1247
|
+
return {
|
|
1248
|
+
status: "ok",
|
|
1249
|
+
brain: brainRoot,
|
|
1250
|
+
neurons: result.totalNeurons,
|
|
1251
|
+
activeNeurons: result.firedNeurons,
|
|
1252
|
+
totalActivation: result.totalCounter,
|
|
1253
|
+
bombSource: result.bombSource || null,
|
|
1254
|
+
lastActivity: new Date(lastAPIActivity).toISOString(),
|
|
1255
|
+
uptime: process.uptime()
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
function buildBrainJSON(brainRoot) {
|
|
1259
|
+
const brain = scanBrain(brainRoot);
|
|
1260
|
+
const result = runSubsumption(brain);
|
|
1261
|
+
return {
|
|
1262
|
+
root: brain.root,
|
|
1263
|
+
regions: brain.regions.map((region) => ({
|
|
1264
|
+
name: region.name,
|
|
1265
|
+
icon: REGION_ICONS[region.name] || "",
|
|
1266
|
+
ko: REGION_KO[region.name] || "",
|
|
1267
|
+
priority: region.priority,
|
|
1268
|
+
hasBomb: region.hasBomb,
|
|
1269
|
+
neurons: region.neurons.map((n) => ({
|
|
1270
|
+
name: n.name,
|
|
1271
|
+
path: n.path,
|
|
1272
|
+
counter: n.counter,
|
|
1273
|
+
contra: n.contra,
|
|
1274
|
+
dopamine: n.dopamine,
|
|
1275
|
+
hasBomb: n.hasBomb,
|
|
1276
|
+
hasMemory: n.hasMemory,
|
|
1277
|
+
isDormant: n.isDormant,
|
|
1278
|
+
depth: n.depth,
|
|
1279
|
+
modTime: n.modTime.getTime()
|
|
1280
|
+
})),
|
|
1281
|
+
axons: region.axons
|
|
1282
|
+
})),
|
|
1283
|
+
bombSource: result.bombSource || null,
|
|
1284
|
+
firedNeurons: result.firedNeurons,
|
|
1285
|
+
totalNeurons: result.totalNeurons,
|
|
1286
|
+
totalCounter: result.totalCounter
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
function json(res, data, status = 200) {
|
|
1290
|
+
const body = JSON.stringify(data);
|
|
1291
|
+
res.writeHead(status, {
|
|
1292
|
+
"Content-Type": "application/json",
|
|
1293
|
+
"Access-Control-Allow-Origin": "*",
|
|
1294
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
1295
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
1296
|
+
});
|
|
1297
|
+
res.end(body);
|
|
1298
|
+
}
|
|
1299
|
+
function error(res, message, status = 400) {
|
|
1300
|
+
json(res, { error: message }, status);
|
|
1301
|
+
}
|
|
1302
|
+
async function readBody(req) {
|
|
1303
|
+
return new Promise((resolve4, reject) => {
|
|
1304
|
+
const chunks = [];
|
|
1305
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
1306
|
+
req.on("end", () => resolve4(Buffer.concat(chunks).toString("utf8")));
|
|
1307
|
+
req.on("error", reject);
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
async function parseJSON(req) {
|
|
1311
|
+
const body = await readBody(req);
|
|
1312
|
+
if (!body.trim()) return {};
|
|
1313
|
+
return JSON.parse(body);
|
|
1314
|
+
}
|
|
1315
|
+
async function handleRequest(req, res, brainRoot) {
|
|
1316
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
1317
|
+
const path = url.pathname;
|
|
1318
|
+
const method = req.method || "GET";
|
|
1319
|
+
if (method === "OPTIONS") {
|
|
1320
|
+
json(res, null, 204);
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
const isMutation = method === "POST";
|
|
1324
|
+
if (isMutation) lastAPIActivity = Date.now();
|
|
1325
|
+
try {
|
|
1326
|
+
if (method === "GET") {
|
|
1327
|
+
switch (path) {
|
|
1328
|
+
case "/api/health":
|
|
1329
|
+
json(res, buildHealthJSON(brainRoot));
|
|
1330
|
+
return;
|
|
1331
|
+
case "/api/brain":
|
|
1332
|
+
json(res, buildBrainJSON(brainRoot));
|
|
1333
|
+
return;
|
|
1334
|
+
case "/api/read": {
|
|
1335
|
+
const region = url.searchParams.get("region");
|
|
1336
|
+
if (!region || !REGIONS.includes(region)) {
|
|
1337
|
+
error(res, `Invalid region. Valid: ${REGIONS.join(", ")}`);
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
const brain = scanBrain(brainRoot);
|
|
1341
|
+
const result = runSubsumption(brain);
|
|
1342
|
+
const regionData = result.activeRegions.find((r) => r.name === region);
|
|
1343
|
+
if (!regionData) {
|
|
1344
|
+
error(res, `Region "${region}" is blocked or empty`);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
const top3 = [...regionData.neurons].filter((n) => !n.isDormant).sort((a, b) => b.counter - a.counter).slice(0, 3);
|
|
1348
|
+
for (const n of top3) {
|
|
1349
|
+
fireNeuron(brainRoot, `${region}/${n.path}`);
|
|
1350
|
+
}
|
|
1351
|
+
json(res, {
|
|
1352
|
+
region,
|
|
1353
|
+
neurons: regionData.neurons,
|
|
1354
|
+
fired: top3.map((n) => n.path)
|
|
1355
|
+
});
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
case "/api/reports":
|
|
1359
|
+
json(res, { reports: pendingReports });
|
|
1360
|
+
return;
|
|
1361
|
+
default:
|
|
1362
|
+
error(res, "Not found", 404);
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
if (method === "POST") {
|
|
1367
|
+
const body = await parseJSON(req);
|
|
1368
|
+
switch (path) {
|
|
1369
|
+
case "/api/grow": {
|
|
1370
|
+
const neuronPath = body.path;
|
|
1371
|
+
if (!neuronPath) {
|
|
1372
|
+
error(res, 'Missing "path"');
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
const result = growNeuron(brainRoot, neuronPath);
|
|
1376
|
+
json(res, result);
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
case "/api/fire": {
|
|
1380
|
+
const neuronPath = body.path;
|
|
1381
|
+
if (!neuronPath) {
|
|
1382
|
+
error(res, 'Missing "path"');
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
const counter = fireNeuron(brainRoot, neuronPath);
|
|
1386
|
+
json(res, { path: neuronPath, counter });
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
case "/api/signal": {
|
|
1390
|
+
const neuronPath = body.path;
|
|
1391
|
+
const signalType = body.type;
|
|
1392
|
+
if (!neuronPath || !signalType) {
|
|
1393
|
+
error(res, 'Missing "path" or "type"');
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
signalNeuron(brainRoot, neuronPath, signalType);
|
|
1397
|
+
json(res, { path: neuronPath, type: signalType });
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
case "/api/rollback": {
|
|
1401
|
+
const neuronPath = body.path;
|
|
1402
|
+
if (!neuronPath) {
|
|
1403
|
+
error(res, 'Missing "path"');
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
const counter = rollbackNeuron(brainRoot, neuronPath);
|
|
1407
|
+
json(res, { path: neuronPath, counter });
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
case "/api/decay": {
|
|
1411
|
+
const days = typeof body.days === "number" ? body.days : 30;
|
|
1412
|
+
const result = runDecay(brainRoot, days);
|
|
1413
|
+
json(res, result);
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
case "/api/dedup": {
|
|
1417
|
+
const result = runDedup(brainRoot);
|
|
1418
|
+
json(res, result);
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
case "/api/inject": {
|
|
1422
|
+
const brain = scanBrain(brainRoot);
|
|
1423
|
+
const result = runSubsumption(brain);
|
|
1424
|
+
writeAllTiers(brainRoot, result, brain);
|
|
1425
|
+
json(res, { injected: true });
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
case "/api/inbox": {
|
|
1429
|
+
const result = processInbox(brainRoot);
|
|
1430
|
+
json(res, result);
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
case "/api/report": {
|
|
1434
|
+
const message = body.message;
|
|
1435
|
+
const priority = body.priority || "normal";
|
|
1436
|
+
if (!message) {
|
|
1437
|
+
error(res, 'Missing "message"');
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
const entry = {
|
|
1441
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1442
|
+
message,
|
|
1443
|
+
priority
|
|
1444
|
+
};
|
|
1445
|
+
pendingReports.push(entry);
|
|
1446
|
+
json(res, entry);
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
default:
|
|
1450
|
+
error(res, "Not found", 404);
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
error(res, "Method not allowed", 405);
|
|
1455
|
+
} catch (err) {
|
|
1456
|
+
error(res, err.message, 500);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
function startAPI(brainRoot, port = 9090) {
|
|
1460
|
+
const server = createServer((req, res) => {
|
|
1461
|
+
handleRequest(req, res, brainRoot).catch((err) => {
|
|
1462
|
+
error(res, err.message, 500);
|
|
1463
|
+
});
|
|
1464
|
+
});
|
|
1465
|
+
server.listen(port, () => {
|
|
1466
|
+
console.log(`\u{1F9E0} hebbian API listening on http://localhost:${port}`);
|
|
1467
|
+
console.log(` Brain: ${brainRoot}`);
|
|
1468
|
+
console.log("");
|
|
1469
|
+
console.log(" Endpoints:");
|
|
1470
|
+
console.log(" GET /api/health Process health + brain stats");
|
|
1471
|
+
console.log(" GET /api/brain Full brain state JSON");
|
|
1472
|
+
console.log(" GET /api/read?region=X Read region (auto-fires top 3)");
|
|
1473
|
+
console.log(" GET /api/reports List pending reports");
|
|
1474
|
+
console.log(' POST /api/grow {"path":"cortex/..."}');
|
|
1475
|
+
console.log(' POST /api/fire {"path":"cortex/..."}');
|
|
1476
|
+
console.log(' POST /api/signal {"path":"...","type":"dopamine"}');
|
|
1477
|
+
console.log(' POST /api/rollback {"path":"cortex/..."}');
|
|
1478
|
+
console.log(' POST /api/decay {"days":30}');
|
|
1479
|
+
console.log(" POST /api/dedup Batch merge similar neurons");
|
|
1480
|
+
console.log(" POST /api/inject Force re-emit all tiers");
|
|
1481
|
+
console.log(" POST /api/inbox Process corrections inbox");
|
|
1482
|
+
console.log(' POST /api/report {"message":"...","priority":"normal"}');
|
|
1483
|
+
});
|
|
1484
|
+
return server;
|
|
1485
|
+
}
|
|
1486
|
+
function getLastActivity() {
|
|
1487
|
+
return lastAPIActivity;
|
|
1488
|
+
}
|
|
1489
|
+
function getPendingReports() {
|
|
1490
|
+
return pendingReports;
|
|
1491
|
+
}
|
|
1492
|
+
function clearReports() {
|
|
1493
|
+
pendingReports.length = 0;
|
|
1494
|
+
}
|
|
1495
|
+
var lastAPIActivity, pendingReports;
|
|
1496
|
+
var init_api = __esm({
|
|
1497
|
+
"src/api.ts"() {
|
|
1498
|
+
"use strict";
|
|
1499
|
+
init_scanner();
|
|
1500
|
+
init_subsumption();
|
|
1501
|
+
init_fire();
|
|
1502
|
+
init_rollback();
|
|
1503
|
+
init_grow();
|
|
1504
|
+
init_signal();
|
|
1505
|
+
init_decay();
|
|
1506
|
+
init_dedup();
|
|
1507
|
+
init_emit();
|
|
1508
|
+
init_inbox();
|
|
1509
|
+
init_constants();
|
|
1510
|
+
lastAPIActivity = Date.now();
|
|
1511
|
+
pendingReports = [];
|
|
1512
|
+
}
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
// src/hooks.ts
|
|
1516
|
+
var hooks_exports = {};
|
|
1517
|
+
__export(hooks_exports, {
|
|
1518
|
+
checkHooks: () => checkHooks,
|
|
1519
|
+
installHooks: () => installHooks,
|
|
1520
|
+
uninstallHooks: () => uninstallHooks
|
|
1521
|
+
});
|
|
1522
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync10, existsSync as existsSync12, mkdirSync as mkdirSync7 } from "fs";
|
|
1523
|
+
import { join as join13, resolve as resolve2 } from "path";
|
|
1524
|
+
function installHooks(brainRoot, projectRoot) {
|
|
1525
|
+
const root = projectRoot || process.cwd();
|
|
1526
|
+
const settingsDir = join13(root, SETTINGS_DIR);
|
|
1527
|
+
const settingsPath = join13(settingsDir, SETTINGS_FILE);
|
|
1528
|
+
const defaultBrain = resolve2(root, "brain");
|
|
1529
|
+
const resolvedBrain = resolve2(brainRoot);
|
|
1530
|
+
const brainFlag = resolvedBrain === defaultBrain ? "" : ` --brain ${resolvedBrain}`;
|
|
1531
|
+
let settings = {};
|
|
1532
|
+
if (existsSync12(settingsPath)) {
|
|
1533
|
+
try {
|
|
1534
|
+
settings = JSON.parse(readFileSync5(settingsPath, "utf8"));
|
|
1535
|
+
} catch {
|
|
1536
|
+
console.log(`\u26A0\uFE0F settings.local.json was malformed, overwriting`);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (!settings.hooks || typeof settings.hooks !== "object") {
|
|
1540
|
+
settings.hooks = {};
|
|
1541
|
+
}
|
|
1542
|
+
const hooks = settings.hooks;
|
|
1543
|
+
const hebbianHooks = [
|
|
1544
|
+
{
|
|
1545
|
+
event: "SessionStart",
|
|
1546
|
+
matcher: "startup|resume",
|
|
1547
|
+
entry: {
|
|
1548
|
+
type: "command",
|
|
1549
|
+
command: `hebbian emit claude${brainFlag}`,
|
|
1550
|
+
timeout: 10,
|
|
1551
|
+
statusMessage: `${HOOK_MARKER} refreshing brain`
|
|
1552
|
+
}
|
|
1553
|
+
},
|
|
1554
|
+
{
|
|
1555
|
+
event: "Stop",
|
|
1556
|
+
entry: {
|
|
1557
|
+
type: "command",
|
|
1558
|
+
command: `hebbian digest${brainFlag}`,
|
|
1559
|
+
timeout: 30,
|
|
1560
|
+
statusMessage: `${HOOK_MARKER} digesting session`
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
];
|
|
1564
|
+
for (const { event, matcher, entry } of hebbianHooks) {
|
|
1565
|
+
if (!hooks[event]) {
|
|
1566
|
+
hooks[event] = [];
|
|
1567
|
+
}
|
|
1568
|
+
const existingIdx = hooks[event].findIndex(
|
|
1569
|
+
(group2) => group2.hooks.some((h) => h.statusMessage?.startsWith(HOOK_MARKER))
|
|
1570
|
+
);
|
|
1571
|
+
const group = {
|
|
1572
|
+
...matcher ? { matcher } : {},
|
|
1573
|
+
hooks: [entry]
|
|
1574
|
+
};
|
|
1575
|
+
if (existingIdx >= 0) {
|
|
1576
|
+
hooks[event][existingIdx] = group;
|
|
1577
|
+
} else {
|
|
1578
|
+
hooks[event].push(group);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
if (!existsSync12(settingsDir)) {
|
|
1582
|
+
mkdirSync7(settingsDir, { recursive: true });
|
|
1583
|
+
}
|
|
1584
|
+
writeFileSync10(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
1585
|
+
console.log(`\u2705 hebbian hooks installed at ${settingsPath}`);
|
|
1586
|
+
console.log(` SessionStart \u2192 hebbian emit claude${brainFlag}`);
|
|
1587
|
+
console.log(` Stop \u2192 hebbian digest${brainFlag}`);
|
|
1588
|
+
}
|
|
1589
|
+
function uninstallHooks(projectRoot) {
|
|
1590
|
+
const root = projectRoot || process.cwd();
|
|
1591
|
+
const settingsPath = join13(root, SETTINGS_DIR, SETTINGS_FILE);
|
|
1592
|
+
if (!existsSync12(settingsPath)) {
|
|
1593
|
+
console.log("No hooks installed (settings.local.json not found)");
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
let settings;
|
|
1597
|
+
try {
|
|
1598
|
+
settings = JSON.parse(readFileSync5(settingsPath, "utf8"));
|
|
1599
|
+
} catch {
|
|
1600
|
+
console.log("settings.local.json is malformed, nothing to uninstall");
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
if (!settings.hooks || typeof settings.hooks !== "object") {
|
|
1604
|
+
console.log("No hooks found in settings.local.json");
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
const hooks = settings.hooks;
|
|
1608
|
+
let removed = 0;
|
|
1609
|
+
for (const event of Object.keys(hooks)) {
|
|
1610
|
+
const before = hooks[event].length;
|
|
1611
|
+
hooks[event] = hooks[event].filter(
|
|
1612
|
+
(group) => !group.hooks.some((h) => h.statusMessage?.startsWith(HOOK_MARKER))
|
|
1613
|
+
);
|
|
1614
|
+
removed += before - hooks[event].length;
|
|
1615
|
+
if (hooks[event].length === 0) {
|
|
1616
|
+
delete hooks[event];
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
if (Object.keys(hooks).length === 0) {
|
|
1620
|
+
delete settings.hooks;
|
|
1621
|
+
}
|
|
1622
|
+
writeFileSync10(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
1623
|
+
console.log(`\u2705 removed ${removed} hebbian hook(s) from ${settingsPath}`);
|
|
1624
|
+
}
|
|
1625
|
+
function checkHooks(projectRoot) {
|
|
1626
|
+
const root = projectRoot || process.cwd();
|
|
1627
|
+
const settingsPath = join13(root, SETTINGS_DIR, SETTINGS_FILE);
|
|
1628
|
+
const status = {
|
|
1629
|
+
installed: false,
|
|
1630
|
+
path: settingsPath,
|
|
1631
|
+
events: []
|
|
1632
|
+
};
|
|
1633
|
+
if (!existsSync12(settingsPath)) {
|
|
1634
|
+
console.log(`\u274C hebbian hooks not installed (${settingsPath} not found)`);
|
|
1635
|
+
return status;
|
|
1636
|
+
}
|
|
1637
|
+
let settings;
|
|
1638
|
+
try {
|
|
1639
|
+
settings = JSON.parse(readFileSync5(settingsPath, "utf8"));
|
|
1640
|
+
} catch {
|
|
1641
|
+
console.log(`\u274C settings.local.json is malformed`);
|
|
1642
|
+
return status;
|
|
1643
|
+
}
|
|
1644
|
+
if (!settings.hooks || typeof settings.hooks !== "object") {
|
|
1645
|
+
console.log(`\u274C no hooks in ${settingsPath}`);
|
|
1646
|
+
return status;
|
|
1647
|
+
}
|
|
1648
|
+
const hooks = settings.hooks;
|
|
1649
|
+
for (const event of Object.keys(hooks)) {
|
|
1650
|
+
const hasHebbian = hooks[event].some(
|
|
1651
|
+
(group) => group.hooks.some((h) => h.statusMessage?.startsWith(HOOK_MARKER))
|
|
1652
|
+
);
|
|
1653
|
+
if (hasHebbian) {
|
|
1654
|
+
status.events.push(event);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
status.installed = status.events.length > 0;
|
|
1658
|
+
if (status.installed) {
|
|
1659
|
+
console.log(`\u2705 hebbian hooks installed at ${settingsPath}`);
|
|
1660
|
+
for (const event of status.events) {
|
|
1661
|
+
console.log(` ${event} \u2714`);
|
|
1662
|
+
}
|
|
1663
|
+
} else {
|
|
1664
|
+
console.log(`\u274C hebbian hooks not found in ${settingsPath}`);
|
|
1665
|
+
}
|
|
1666
|
+
return status;
|
|
1667
|
+
}
|
|
1668
|
+
var SETTINGS_DIR, SETTINGS_FILE;
|
|
1669
|
+
var init_hooks = __esm({
|
|
1670
|
+
"src/hooks.ts"() {
|
|
1671
|
+
"use strict";
|
|
1672
|
+
init_constants();
|
|
1673
|
+
SETTINGS_DIR = ".claude";
|
|
1674
|
+
SETTINGS_FILE = "settings.local.json";
|
|
1675
|
+
}
|
|
1676
|
+
});
|
|
1677
|
+
|
|
1678
|
+
// src/digest.ts
|
|
1679
|
+
var digest_exports = {};
|
|
1680
|
+
__export(digest_exports, {
|
|
1681
|
+
digestTranscript: () => digestTranscript,
|
|
1682
|
+
extractCorrections: () => extractCorrections,
|
|
1683
|
+
readHookInput: () => readHookInput
|
|
1684
|
+
});
|
|
1685
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync11, existsSync as existsSync13, mkdirSync as mkdirSync8 } from "fs";
|
|
1686
|
+
import { join as join14, basename } from "path";
|
|
1687
|
+
function readHookInput(stdin) {
|
|
1688
|
+
if (!stdin.trim()) return null;
|
|
1689
|
+
try {
|
|
1690
|
+
const input = JSON.parse(stdin);
|
|
1691
|
+
if (input.transcript_path) {
|
|
1692
|
+
const sessionId = input.session_id || basename(input.transcript_path, ".jsonl");
|
|
1693
|
+
return { transcriptPath: input.transcript_path, sessionId };
|
|
1694
|
+
}
|
|
1695
|
+
return null;
|
|
1696
|
+
} catch {
|
|
1697
|
+
return null;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
function digestTranscript(brainRoot, transcriptPath, sessionId) {
|
|
1701
|
+
if (!existsSync13(transcriptPath)) {
|
|
1702
|
+
throw new Error(`Transcript not found: ${transcriptPath}`);
|
|
1703
|
+
}
|
|
1704
|
+
const resolvedSessionId = sessionId || basename(transcriptPath, ".jsonl");
|
|
1705
|
+
const logDir = join14(brainRoot, DIGEST_LOG_DIR);
|
|
1706
|
+
const logPath = join14(logDir, `${resolvedSessionId}.jsonl`);
|
|
1707
|
+
if (existsSync13(logPath)) {
|
|
1708
|
+
console.log(`\u23ED already digested session ${resolvedSessionId}, skip`);
|
|
1709
|
+
return { corrections: 0, skipped: 0, transcriptPath, sessionId: resolvedSessionId };
|
|
1710
|
+
}
|
|
1711
|
+
const messages = parseTranscript(transcriptPath);
|
|
1712
|
+
const corrections = extractCorrections(messages);
|
|
1713
|
+
if (corrections.length === 0) {
|
|
1714
|
+
console.log(`\u{1F4DD} digest: no corrections found in session ${resolvedSessionId}`);
|
|
1715
|
+
writeAuditLog(brainRoot, resolvedSessionId, []);
|
|
1716
|
+
return { corrections: 0, skipped: messages.length, transcriptPath, sessionId: resolvedSessionId };
|
|
1717
|
+
}
|
|
1718
|
+
let applied = 0;
|
|
1719
|
+
const auditEntries = [];
|
|
1720
|
+
for (const correction of corrections) {
|
|
1721
|
+
try {
|
|
1722
|
+
growNeuron(brainRoot, correction.path);
|
|
1723
|
+
logEpisode(brainRoot, "digest", correction.path, correction.text);
|
|
1724
|
+
auditEntries.push({ correction, applied: true });
|
|
1725
|
+
applied++;
|
|
1726
|
+
} catch (err) {
|
|
1727
|
+
console.log(` \u26A0\uFE0F failed to apply: ${correction.path} \u2014 ${err.message}`);
|
|
1728
|
+
auditEntries.push({ correction, applied: false });
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
writeAuditLog(brainRoot, resolvedSessionId, auditEntries);
|
|
1732
|
+
console.log(`\u{1F4DD} digest: ${applied} correction(s) from session ${resolvedSessionId}`);
|
|
1733
|
+
return {
|
|
1734
|
+
corrections: applied,
|
|
1735
|
+
skipped: messages.length - corrections.length,
|
|
1736
|
+
transcriptPath,
|
|
1737
|
+
sessionId: resolvedSessionId
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
function parseTranscript(transcriptPath) {
|
|
1741
|
+
const content = readFileSync6(transcriptPath, "utf8");
|
|
1742
|
+
const lines = content.split("\n").filter(Boolean);
|
|
1743
|
+
const messages = [];
|
|
1744
|
+
for (const line of lines) {
|
|
1745
|
+
let entry;
|
|
1746
|
+
try {
|
|
1747
|
+
entry = JSON.parse(line);
|
|
1748
|
+
} catch {
|
|
1749
|
+
continue;
|
|
1750
|
+
}
|
|
1751
|
+
if (entry.type !== "user") continue;
|
|
1752
|
+
if (!entry.message || entry.message.role !== "user") continue;
|
|
1753
|
+
const text = extractText(entry.message.content);
|
|
1754
|
+
if (text) messages.push(text);
|
|
1755
|
+
}
|
|
1756
|
+
return messages;
|
|
1757
|
+
}
|
|
1758
|
+
function extractText(content) {
|
|
1759
|
+
if (!content) return null;
|
|
1760
|
+
if (typeof content === "string") return content;
|
|
1761
|
+
if (Array.isArray(content)) {
|
|
1762
|
+
const texts = content.filter((block) => block.type === "text" && block.text).map((block) => block.text);
|
|
1763
|
+
return texts.length > 0 ? texts.join("\n") : null;
|
|
1764
|
+
}
|
|
1765
|
+
return null;
|
|
1766
|
+
}
|
|
1767
|
+
function extractCorrections(messages) {
|
|
1768
|
+
const corrections = [];
|
|
1769
|
+
for (const text of messages) {
|
|
1770
|
+
if (corrections.length >= MAX_CORRECTIONS_PER_SESSION) break;
|
|
1771
|
+
if (text.length < MIN_CORRECTION_LENGTH) continue;
|
|
1772
|
+
if (/^[\/!]/.test(text.trim())) continue;
|
|
1773
|
+
if (text.trim().endsWith("?")) continue;
|
|
1774
|
+
const correction = detectCorrection(text);
|
|
1775
|
+
if (correction) {
|
|
1776
|
+
corrections.push(correction);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
return corrections;
|
|
1780
|
+
}
|
|
1781
|
+
function detectCorrection(text) {
|
|
1782
|
+
const isNegation = NEGATION_PATTERNS.some((p) => p.test(text));
|
|
1783
|
+
const isAffirmation = AFFIRMATION_PATTERNS.some((p) => p.test(text));
|
|
1784
|
+
if (!isNegation && !isAffirmation) return null;
|
|
1785
|
+
const prefix = isNegation ? "NO" : "DO";
|
|
1786
|
+
const keywords = extractKeywords(text);
|
|
1787
|
+
if (keywords.length === 0) return null;
|
|
1788
|
+
const pathSegment = `${prefix}_${keywords.slice(0, 4).join("_")}`;
|
|
1789
|
+
const path = `cortex/${pathSegment}`;
|
|
1790
|
+
return { text, path, prefix, keywords };
|
|
1791
|
+
}
|
|
1792
|
+
function extractKeywords(text) {
|
|
1793
|
+
const STOP_WORDS = /* @__PURE__ */ new Set([
|
|
1794
|
+
"the",
|
|
1795
|
+
"a",
|
|
1796
|
+
"an",
|
|
1797
|
+
"is",
|
|
1798
|
+
"are",
|
|
1799
|
+
"was",
|
|
1800
|
+
"were",
|
|
1801
|
+
"be",
|
|
1802
|
+
"been",
|
|
1803
|
+
"being",
|
|
1804
|
+
"have",
|
|
1805
|
+
"has",
|
|
1806
|
+
"had",
|
|
1807
|
+
"do",
|
|
1808
|
+
"does",
|
|
1809
|
+
"did",
|
|
1810
|
+
"will",
|
|
1811
|
+
"would",
|
|
1812
|
+
"could",
|
|
1813
|
+
"should",
|
|
1814
|
+
"may",
|
|
1815
|
+
"might",
|
|
1816
|
+
"shall",
|
|
1817
|
+
"can",
|
|
1818
|
+
"need",
|
|
1819
|
+
"dare",
|
|
1820
|
+
"ought",
|
|
1821
|
+
"to",
|
|
1822
|
+
"of",
|
|
1823
|
+
"in",
|
|
1824
|
+
"for",
|
|
1825
|
+
"on",
|
|
1826
|
+
"with",
|
|
1827
|
+
"at",
|
|
1828
|
+
"by",
|
|
1829
|
+
"from",
|
|
1830
|
+
"as",
|
|
1831
|
+
"into",
|
|
1832
|
+
"through",
|
|
1833
|
+
"during",
|
|
1834
|
+
"before",
|
|
1835
|
+
"after",
|
|
1836
|
+
"above",
|
|
1837
|
+
"below",
|
|
1838
|
+
"and",
|
|
1839
|
+
"but",
|
|
1840
|
+
"or",
|
|
1841
|
+
"nor",
|
|
1842
|
+
"not",
|
|
1843
|
+
"so",
|
|
1844
|
+
"yet",
|
|
1845
|
+
"both",
|
|
1846
|
+
"either",
|
|
1847
|
+
"neither",
|
|
1848
|
+
"each",
|
|
1849
|
+
"every",
|
|
1850
|
+
"all",
|
|
1851
|
+
"any",
|
|
1852
|
+
"few",
|
|
1853
|
+
"more",
|
|
1854
|
+
"most",
|
|
1855
|
+
"other",
|
|
1856
|
+
"some",
|
|
1857
|
+
"such",
|
|
1858
|
+
"no",
|
|
1859
|
+
"only",
|
|
1860
|
+
"own",
|
|
1861
|
+
"same",
|
|
1862
|
+
"than",
|
|
1863
|
+
"too",
|
|
1864
|
+
"very",
|
|
1865
|
+
"just",
|
|
1866
|
+
"because",
|
|
1867
|
+
"until",
|
|
1868
|
+
"while",
|
|
1869
|
+
"that",
|
|
1870
|
+
"this",
|
|
1871
|
+
"these",
|
|
1872
|
+
"those",
|
|
1873
|
+
"it",
|
|
1874
|
+
"its",
|
|
1875
|
+
"i",
|
|
1876
|
+
"me",
|
|
1877
|
+
"my",
|
|
1878
|
+
"we",
|
|
1879
|
+
"us",
|
|
1880
|
+
"you",
|
|
1881
|
+
"your",
|
|
1882
|
+
"he",
|
|
1883
|
+
"she",
|
|
1884
|
+
"they",
|
|
1885
|
+
"them",
|
|
1886
|
+
"what",
|
|
1887
|
+
"which",
|
|
1888
|
+
"who",
|
|
1889
|
+
"whom",
|
|
1890
|
+
// Correction-specific stop words
|
|
1891
|
+
"don",
|
|
1892
|
+
"dont",
|
|
1893
|
+
"stop",
|
|
1894
|
+
"never",
|
|
1895
|
+
"always",
|
|
1896
|
+
"instead",
|
|
1897
|
+
"use",
|
|
1898
|
+
"avoid",
|
|
1899
|
+
"please",
|
|
1900
|
+
"must",
|
|
1901
|
+
"should",
|
|
1902
|
+
"like",
|
|
1903
|
+
"want",
|
|
1904
|
+
"think"
|
|
1905
|
+
]);
|
|
1906
|
+
const tokens = tokenize(text);
|
|
1907
|
+
return tokens.filter((t) => !STOP_WORDS.has(t) && t.length > 2);
|
|
1908
|
+
}
|
|
1909
|
+
function writeAuditLog(brainRoot, sessionId, entries) {
|
|
1910
|
+
const logDir = join14(brainRoot, DIGEST_LOG_DIR);
|
|
1911
|
+
if (!existsSync13(logDir)) {
|
|
1912
|
+
mkdirSync8(logDir, { recursive: true });
|
|
1913
|
+
}
|
|
1914
|
+
const logPath = join14(logDir, `${sessionId}.jsonl`);
|
|
1915
|
+
const lines = entries.map(
|
|
1916
|
+
(e) => JSON.stringify({
|
|
1917
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1918
|
+
path: e.correction.path,
|
|
1919
|
+
text: e.correction.text,
|
|
1920
|
+
prefix: e.correction.prefix,
|
|
1921
|
+
keywords: e.correction.keywords,
|
|
1922
|
+
applied: e.applied
|
|
1923
|
+
})
|
|
1924
|
+
);
|
|
1925
|
+
writeFileSync11(logPath, lines.join("\n") + (lines.length > 0 ? "\n" : ""), "utf8");
|
|
1926
|
+
}
|
|
1927
|
+
var NEGATION_PATTERNS, AFFIRMATION_PATTERNS;
|
|
1928
|
+
var init_digest = __esm({
|
|
1929
|
+
"src/digest.ts"() {
|
|
1930
|
+
"use strict";
|
|
1931
|
+
init_constants();
|
|
1932
|
+
init_grow();
|
|
1933
|
+
init_episode();
|
|
1934
|
+
init_similarity();
|
|
1935
|
+
NEGATION_PATTERNS = [
|
|
1936
|
+
/\bdon[''\u2019]?t\b/i,
|
|
1937
|
+
/\bdo not\b/i,
|
|
1938
|
+
/\bstop\s+\w+ing\b/i,
|
|
1939
|
+
/\bnever\b/i,
|
|
1940
|
+
/\binstead\b/i,
|
|
1941
|
+
/^no[,.\s!]/i,
|
|
1942
|
+
/\bdon[''\u2019]?t\s+use\b/i,
|
|
1943
|
+
/\bavoid\b/i,
|
|
1944
|
+
// Korean negation
|
|
1945
|
+
/하지\s*마/,
|
|
1946
|
+
/안\s*돼/,
|
|
1947
|
+
/대신/,
|
|
1948
|
+
/쓰지\s*마/,
|
|
1949
|
+
/않/
|
|
1950
|
+
];
|
|
1951
|
+
AFFIRMATION_PATTERNS = [
|
|
1952
|
+
/\balways\b/i,
|
|
1953
|
+
/\bmust\b/i,
|
|
1954
|
+
/\bshould\s+always\b/i,
|
|
1955
|
+
/\buse\s+\w+\s+instead\b/i,
|
|
1956
|
+
// Korean affirmation
|
|
1957
|
+
/항상/,
|
|
1958
|
+
/반드시/
|
|
1959
|
+
];
|
|
1960
|
+
}
|
|
1961
|
+
});
|
|
1962
|
+
|
|
1054
1963
|
// src/cli.ts
|
|
1964
|
+
init_constants();
|
|
1055
1965
|
import { parseArgs } from "util";
|
|
1056
|
-
import { resolve } from "path";
|
|
1057
|
-
|
|
1058
|
-
var VERSION = "0.1.0";
|
|
1966
|
+
import { resolve as resolve3 } from "path";
|
|
1967
|
+
var VERSION = "0.3.0";
|
|
1059
1968
|
var HELP = `
|
|
1060
1969
|
hebbian v${VERSION} \u2014 Folder-as-neuron brain for any AI agent.
|
|
1061
1970
|
|
|
@@ -1075,6 +1984,10 @@ COMMANDS:
|
|
|
1075
1984
|
dedup Batch merge similar neurons (Jaccard >= 0.6)
|
|
1076
1985
|
snapshot Git commit current brain state
|
|
1077
1986
|
watch Watch for changes + auto-recompile
|
|
1987
|
+
api [--port N] Start REST API server (default 9090)
|
|
1988
|
+
inbox Process corrections inbox
|
|
1989
|
+
claude install|uninstall|status Manage Claude Code hooks
|
|
1990
|
+
digest [--transcript <path>] Extract corrections from conversation
|
|
1078
1991
|
diag Print brain diagnostics
|
|
1079
1992
|
stats Print brain statistics
|
|
1080
1993
|
|
|
@@ -1085,16 +1998,26 @@ OPTIONS:
|
|
|
1085
1998
|
|
|
1086
1999
|
EXAMPLES:
|
|
1087
2000
|
hebbian init ./my-brain
|
|
1088
|
-
hebbian grow cortex/frontend
|
|
1089
|
-
hebbian fire cortex/frontend
|
|
2001
|
+
hebbian grow cortex/frontend/NO_console_log --brain ./my-brain
|
|
2002
|
+
hebbian fire cortex/frontend/NO_console_log --brain ./my-brain
|
|
1090
2003
|
hebbian emit claude --brain ./my-brain
|
|
1091
2004
|
hebbian emit all
|
|
1092
2005
|
`.trim();
|
|
1093
|
-
function
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
2006
|
+
function readStdin() {
|
|
2007
|
+
return new Promise((resolve4) => {
|
|
2008
|
+
if (process.stdin.isTTY) {
|
|
2009
|
+
resolve4("");
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
const chunks = [];
|
|
2013
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
2014
|
+
process.stdin.on("end", () => resolve4(Buffer.concat(chunks).toString("utf8")));
|
|
2015
|
+
process.stdin.on("error", () => resolve4(""));
|
|
2016
|
+
setTimeout(() => {
|
|
2017
|
+
process.stdin.destroy();
|
|
2018
|
+
resolve4(Buffer.concat(chunks).toString("utf8"));
|
|
2019
|
+
}, 1e3);
|
|
2020
|
+
});
|
|
1098
2021
|
}
|
|
1099
2022
|
async function main(argv) {
|
|
1100
2023
|
const { values, positionals } = parseArgs({
|
|
@@ -1103,6 +2026,7 @@ async function main(argv) {
|
|
|
1103
2026
|
brain: { type: "string", short: "b" },
|
|
1104
2027
|
days: { type: "string", short: "d" },
|
|
1105
2028
|
port: { type: "string", short: "p" },
|
|
2029
|
+
transcript: { type: "string", short: "t" },
|
|
1106
2030
|
help: { type: "boolean", short: "h" },
|
|
1107
2031
|
version: { type: "boolean", short: "v" }
|
|
1108
2032
|
},
|
|
@@ -1127,7 +2051,7 @@ async function main(argv) {
|
|
|
1127
2051
|
process.exit(1);
|
|
1128
2052
|
}
|
|
1129
2053
|
const { initBrain: initBrain2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
1130
|
-
await initBrain2(
|
|
2054
|
+
await initBrain2(resolve3(target));
|
|
1131
2055
|
break;
|
|
1132
2056
|
}
|
|
1133
2057
|
case "emit": {
|
|
@@ -1202,6 +2126,56 @@ async function main(argv) {
|
|
|
1202
2126
|
await startWatch2(brainRoot);
|
|
1203
2127
|
break;
|
|
1204
2128
|
}
|
|
2129
|
+
case "api": {
|
|
2130
|
+
const port = values.port ? parseInt(values.port, 10) : 9090;
|
|
2131
|
+
const { startAPI: startAPI2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
2132
|
+
startAPI2(brainRoot, port);
|
|
2133
|
+
await new Promise(() => {
|
|
2134
|
+
});
|
|
2135
|
+
break;
|
|
2136
|
+
}
|
|
2137
|
+
case "inbox": {
|
|
2138
|
+
const { processInbox: processInbox2 } = await Promise.resolve().then(() => (init_inbox(), inbox_exports));
|
|
2139
|
+
processInbox2(brainRoot);
|
|
2140
|
+
break;
|
|
2141
|
+
}
|
|
2142
|
+
case "claude": {
|
|
2143
|
+
const sub = positionals[1];
|
|
2144
|
+
const { installHooks: installHooks2, uninstallHooks: uninstallHooks2, checkHooks: checkHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
|
|
2145
|
+
switch (sub) {
|
|
2146
|
+
case "install":
|
|
2147
|
+
installHooks2(brainRoot);
|
|
2148
|
+
break;
|
|
2149
|
+
case "uninstall":
|
|
2150
|
+
uninstallHooks2();
|
|
2151
|
+
break;
|
|
2152
|
+
case "status":
|
|
2153
|
+
checkHooks2();
|
|
2154
|
+
break;
|
|
2155
|
+
default:
|
|
2156
|
+
console.error("Usage: hebbian claude <install|uninstall|status>");
|
|
2157
|
+
process.exit(1);
|
|
2158
|
+
}
|
|
2159
|
+
break;
|
|
2160
|
+
}
|
|
2161
|
+
case "digest": {
|
|
2162
|
+
const transcriptFlag = values.transcript;
|
|
2163
|
+
const { digestTranscript: digestTranscript2, readHookInput: readHookInput2 } = await Promise.resolve().then(() => (init_digest(), digest_exports));
|
|
2164
|
+
if (transcriptFlag) {
|
|
2165
|
+
digestTranscript2(brainRoot, resolve3(transcriptFlag));
|
|
2166
|
+
} else {
|
|
2167
|
+
const stdin = await readStdin();
|
|
2168
|
+
const hookInput = readHookInput2(stdin);
|
|
2169
|
+
if (hookInput) {
|
|
2170
|
+
digestTranscript2(brainRoot, hookInput.transcriptPath, hookInput.sessionId);
|
|
2171
|
+
} else {
|
|
2172
|
+
console.error("Usage: hebbian digest --transcript <path>");
|
|
2173
|
+
console.error(" Or pipe hook input via stdin (Claude Code Stop hook)");
|
|
2174
|
+
process.exit(1);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
break;
|
|
2178
|
+
}
|
|
1205
2179
|
case "diag":
|
|
1206
2180
|
case "stats": {
|
|
1207
2181
|
const { scanBrain: scanBrain2 } = await Promise.resolve().then(() => (init_scanner(), scanner_exports));
|