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.
@@ -10,7 +10,15 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/constants.ts
13
- var REGIONS, REGION_PRIORITY, REGION_ICONS, REGION_KO, EMIT_THRESHOLD, SPOTLIGHT_DAYS, JACCARD_THRESHOLD, MAX_DEPTH, EMIT_TARGETS, SIGNAL_TYPES, MARKER_START, MARKER_END;
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 (existsSync(brainPath)) {
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/\u7981your_rule --brain ${brainPath}`);
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: ["\u7981fallback", "\u63A8execute_not_debate"]
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 existsSync2 } from "fs";
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 (!existsSync2(regionPath)) {
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 (!existsSync2(axonPath)) return [];
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 existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
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 (existsSync3(region.path)) {
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 !== "." && !existsSync3(dir)) {
545
+ if (dir !== "." && !existsSync4(dir)) {
534
546
  mkdirSync2(dir, { recursive: true });
535
547
  }
536
- if (existsSync3(filePath)) {
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 "**[\uC808\uB300]** ";
586
- if (counter >= 5) return "**[\uBC18\uB4DC\uC2DC]** ";
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 existsSync4, mkdirSync as mkdirSync3 } from "fs";
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 (!existsSync4(fullPath)) {
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 existsSync5, readdirSync as readdirSync4 } from "fs";
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 (existsSync5(fullPath)) {
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 (existsSync5(regionPath)) {
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 existsSync6, readdirSync as readdirSync5 } from "fs";
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 (!existsSync6(fullPath)) {
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 existsSync7 } from "fs";
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 (!existsSync7(regionPath)) continue;
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 existsSync8 } from "fs";
975
+ import { existsSync as existsSync9 } from "fs";
964
976
  import { join as join10 } from "path";
965
977
  function gitSnapshot(brainRoot) {
966
- if (!existsSync8(join10(brainRoot, ".git"))) {
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((resolve2) => {
1039
+ await new Promise((resolve4) => {
1028
1040
  process.on("SIGINT", () => {
1029
1041
  watcher.close();
1030
1042
  console.log("\n\u{1F44B} watch stopped.");
1031
- resolve2();
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
- import { existsSync as existsSync9 } from "fs";
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/\u7981console_log --brain ./my-brain
1089
- hebbian fire cortex/frontend/\u7981console_log --brain ./my-brain
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 resolveBrainRoot(brainFlag) {
1094
- if (brainFlag) return resolve(brainFlag);
1095
- if (process.env.HEBBIAN_BRAIN) return resolve(process.env.HEBBIAN_BRAIN);
1096
- if (existsSync9(resolve("./brain"))) return resolve("./brain");
1097
- return resolve(process.env.HOME || "~", "hebbian", "brain");
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(resolve(target));
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));