hebbian 0.11.0 → 0.11.1

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.
@@ -435,6 +435,80 @@ var init_subsumption = __esm({
435
435
  }
436
436
  });
437
437
 
438
+ // src/episode.ts
439
+ var episode_exports = {};
440
+ __export(episode_exports, {
441
+ logEpisode: () => logEpisode,
442
+ readEpisodes: () => readEpisodes
443
+ });
444
+ import { readdirSync as readdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
445
+ import { join as join3 } from "path";
446
+ function logEpisode(brainRoot, type, path, detail, extra) {
447
+ const logDir = join3(brainRoot, SESSION_LOG_DIR);
448
+ if (!existsSync4(logDir)) {
449
+ mkdirSync2(logDir, { recursive: true });
450
+ }
451
+ const nextSlot = getNextSlot(logDir);
452
+ const episode = {
453
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
454
+ type,
455
+ path,
456
+ detail,
457
+ ...extra?.outcome ? { outcome: extra.outcome } : {},
458
+ ...extra?.neurons ? { neurons: extra.neurons } : {}
459
+ };
460
+ writeFileSync2(
461
+ join3(logDir, `memory${nextSlot}.neuron`),
462
+ JSON.stringify(episode),
463
+ "utf8"
464
+ );
465
+ }
466
+ function readEpisodes(brainRoot) {
467
+ const logDir = join3(brainRoot, SESSION_LOG_DIR);
468
+ if (!existsSync4(logDir)) return [];
469
+ const episodes = [];
470
+ let entries;
471
+ try {
472
+ entries = readdirSync3(logDir);
473
+ } catch {
474
+ return [];
475
+ }
476
+ for (const entry of entries) {
477
+ if (!entry.startsWith("memory") || !entry.endsWith(".neuron")) continue;
478
+ try {
479
+ const content = readFileSync3(join3(logDir, entry), "utf8");
480
+ if (content.trim()) {
481
+ episodes.push(JSON.parse(content));
482
+ }
483
+ } catch {
484
+ }
485
+ }
486
+ episodes.sort((a, b) => a.ts.localeCompare(b.ts));
487
+ return episodes;
488
+ }
489
+ function getNextSlot(logDir) {
490
+ let maxSlot = 0;
491
+ try {
492
+ for (const entry of readdirSync3(logDir)) {
493
+ if (entry.startsWith("memory") && entry.endsWith(".neuron")) {
494
+ const n = parseInt(entry.replace("memory", "").replace(".neuron", ""), 10);
495
+ if (!isNaN(n) && n > maxSlot) maxSlot = n;
496
+ }
497
+ }
498
+ } catch {
499
+ }
500
+ const next = maxSlot + 1;
501
+ return next > MAX_EPISODES ? maxSlot % MAX_EPISODES + 1 : next;
502
+ }
503
+ var MAX_EPISODES, SESSION_LOG_DIR;
504
+ var init_episode = __esm({
505
+ "src/episode.ts"() {
506
+ "use strict";
507
+ MAX_EPISODES = 100;
508
+ SESSION_LOG_DIR = "hippocampus/session_log";
509
+ }
510
+ });
511
+
438
512
  // src/similarity.ts
439
513
  function tokenize(name) {
440
514
  return name.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9\u3000-\u9FFF\uAC00-\uD7AF]+/g, " ").toLowerCase().split(" ").map(stem).filter((t) => t.length > 1);
@@ -474,44 +548,44 @@ __export(fire_exports, {
474
548
  getCurrentContra: () => getCurrentContra,
475
549
  getCurrentCounter: () => getCurrentCounter
476
550
  });
477
- import { readdirSync as readdirSync3, renameSync, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
478
- import { join as join3 } from "path";
551
+ import { readdirSync as readdirSync4, renameSync, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
552
+ import { join as join4 } from "path";
479
553
  function fireNeuron(brainRoot, neuronPath) {
480
- const fullPath = join3(brainRoot, neuronPath);
481
- if (!existsSync4(fullPath)) {
482
- mkdirSync2(fullPath, { recursive: true });
483
- writeFileSync2(join3(fullPath, "1.neuron"), "", "utf8");
554
+ const fullPath = join4(brainRoot, neuronPath);
555
+ if (!existsSync5(fullPath)) {
556
+ mkdirSync3(fullPath, { recursive: true });
557
+ writeFileSync3(join4(fullPath, "1.neuron"), "", "utf8");
484
558
  console.log(`\u{1F331} grew + fired: ${neuronPath} (1)`);
485
559
  return 1;
486
560
  }
487
561
  const current = getCurrentCounter(fullPath);
488
562
  const newCounter = current + 1;
489
563
  if (current > 0) {
490
- renameSync(join3(fullPath, `${current}.neuron`), join3(fullPath, `${newCounter}.neuron`));
564
+ renameSync(join4(fullPath, `${current}.neuron`), join4(fullPath, `${newCounter}.neuron`));
491
565
  } else {
492
- writeFileSync2(join3(fullPath, `${newCounter}.neuron`), "", "utf8");
566
+ writeFileSync3(join4(fullPath, `${newCounter}.neuron`), "", "utf8");
493
567
  }
494
568
  console.log(`\u{1F525} fired: ${neuronPath} (${current} \u2192 ${newCounter})`);
495
569
  return newCounter;
496
570
  }
497
571
  function contraNeuron(brainRoot, neuronPath) {
498
- const fullPath = join3(brainRoot, neuronPath);
499
- if (!existsSync4(fullPath)) {
572
+ const fullPath = join4(brainRoot, neuronPath);
573
+ if (!existsSync5(fullPath)) {
500
574
  return 0;
501
575
  }
502
576
  const current = getCurrentContra(fullPath);
503
577
  const newContra = current + 1;
504
578
  if (current > 0) {
505
- renameSync(join3(fullPath, `${current}.contra`), join3(fullPath, `${newContra}.contra`));
579
+ renameSync(join4(fullPath, `${current}.contra`), join4(fullPath, `${newContra}.contra`));
506
580
  } else {
507
- writeFileSync2(join3(fullPath, `${newContra}.contra`), "", "utf8");
581
+ writeFileSync3(join4(fullPath, `${newContra}.contra`), "", "utf8");
508
582
  }
509
583
  return newContra;
510
584
  }
511
585
  function getCurrentContra(dir) {
512
586
  let max = 0;
513
587
  try {
514
- for (const entry of readdirSync3(dir)) {
588
+ for (const entry of readdirSync4(dir)) {
515
589
  if (entry.endsWith(".contra")) {
516
590
  const n = parseInt(entry, 10);
517
591
  if (!isNaN(n) && n > max) max = n;
@@ -524,7 +598,7 @@ function getCurrentContra(dir) {
524
598
  function getCurrentCounter(dir) {
525
599
  let max = 0;
526
600
  try {
527
- for (const entry of readdirSync3(dir)) {
601
+ for (const entry of readdirSync4(dir)) {
528
602
  if (entry.endsWith(".neuron") && !entry.startsWith("dopamine") && !entry.startsWith("memory") && entry !== "bomb.neuron") {
529
603
  const n = parseInt(entry, 10);
530
604
  if (!isNaN(n) && n > max) max = n;
@@ -545,11 +619,11 @@ var grow_exports = {};
545
619
  __export(grow_exports, {
546
620
  growNeuron: () => growNeuron
547
621
  });
548
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, existsSync as existsSync5, readdirSync as readdirSync4 } from "fs";
549
- import { join as join4, relative as relative2 } from "path";
622
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, existsSync as existsSync6, readdirSync as readdirSync5 } from "fs";
623
+ import { join as join5, relative as relative2 } from "path";
550
624
  function growNeuron(brainRoot, neuronPath) {
551
- const fullPath = join4(brainRoot, neuronPath);
552
- if (existsSync5(fullPath)) {
625
+ const fullPath = join5(brainRoot, neuronPath);
626
+ if (existsSync6(fullPath)) {
553
627
  const counter = fireNeuron(brainRoot, neuronPath);
554
628
  return { action: "fired", path: neuronPath, counter };
555
629
  }
@@ -565,8 +639,8 @@ function growNeuron(brainRoot, neuronPath) {
565
639
  const newPrefix = leafName.match(/^(NO|DO|MUST|WARN)_/)?.[1] || "";
566
640
  const newStripped = leafName.replace(/^(NO|DO|MUST|WARN)_/, "");
567
641
  const newTokens = tokenize(newStripped);
568
- const regionPath = join4(brainRoot, regionName);
569
- if (existsSync5(regionPath)) {
642
+ const regionPath = join5(brainRoot, regionName);
643
+ if (existsSync6(regionPath)) {
570
644
  const match = findSimilar(regionPath, regionPath, newTokens, newPrefix);
571
645
  if (match) {
572
646
  const matchRelPath = regionName + "/" + relative2(regionPath, match);
@@ -575,15 +649,15 @@ function growNeuron(brainRoot, neuronPath) {
575
649
  return { action: "fired", path: matchRelPath, counter };
576
650
  }
577
651
  }
578
- mkdirSync3(fullPath, { recursive: true });
579
- writeFileSync3(join4(fullPath, "1.neuron"), "", "utf8");
652
+ mkdirSync4(fullPath, { recursive: true });
653
+ writeFileSync4(join5(fullPath, "1.neuron"), "", "utf8");
580
654
  console.log(`\u{1F331} grew: ${neuronPath} (1)`);
581
655
  return { action: "grew", path: neuronPath, counter: 1 };
582
656
  }
583
657
  function findSimilar(dir, regionRoot, targetTokens, targetPrefix) {
584
658
  let entries;
585
659
  try {
586
- entries = readdirSync4(dir, { withFileTypes: true });
660
+ entries = readdirSync5(dir, { withFileTypes: true });
587
661
  } catch {
588
662
  return null;
589
663
  }
@@ -602,7 +676,7 @@ function findSimilar(dir, regionRoot, targetTokens, targetPrefix) {
602
676
  for (const entry of entries) {
603
677
  if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
604
678
  if (entry.isDirectory()) {
605
- const match = findSimilar(join4(dir, entry.name), regionRoot, targetTokens, targetPrefix);
679
+ const match = findSimilar(join5(dir, entry.name), regionRoot, targetTokens, targetPrefix);
606
680
  if (match) return match;
607
681
  }
608
682
  }
@@ -617,80 +691,6 @@ var init_grow = __esm({
617
691
  }
618
692
  });
619
693
 
620
- // src/episode.ts
621
- var episode_exports = {};
622
- __export(episode_exports, {
623
- logEpisode: () => logEpisode,
624
- readEpisodes: () => readEpisodes
625
- });
626
- import { readdirSync as readdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
627
- import { join as join5 } from "path";
628
- function logEpisode(brainRoot, type, path, detail, extra) {
629
- const logDir = join5(brainRoot, SESSION_LOG_DIR);
630
- if (!existsSync6(logDir)) {
631
- mkdirSync4(logDir, { recursive: true });
632
- }
633
- const nextSlot = getNextSlot(logDir);
634
- const episode = {
635
- ts: (/* @__PURE__ */ new Date()).toISOString(),
636
- type,
637
- path,
638
- detail,
639
- ...extra?.outcome ? { outcome: extra.outcome } : {},
640
- ...extra?.neurons ? { neurons: extra.neurons } : {}
641
- };
642
- writeFileSync4(
643
- join5(logDir, `memory${nextSlot}.neuron`),
644
- JSON.stringify(episode),
645
- "utf8"
646
- );
647
- }
648
- function readEpisodes(brainRoot) {
649
- const logDir = join5(brainRoot, SESSION_LOG_DIR);
650
- if (!existsSync6(logDir)) return [];
651
- const episodes = [];
652
- let entries;
653
- try {
654
- entries = readdirSync5(logDir);
655
- } catch {
656
- return [];
657
- }
658
- for (const entry of entries) {
659
- if (!entry.startsWith("memory") || !entry.endsWith(".neuron")) continue;
660
- try {
661
- const content = readFileSync3(join5(logDir, entry), "utf8");
662
- if (content.trim()) {
663
- episodes.push(JSON.parse(content));
664
- }
665
- } catch {
666
- }
667
- }
668
- episodes.sort((a, b) => a.ts.localeCompare(b.ts));
669
- return episodes;
670
- }
671
- function getNextSlot(logDir) {
672
- let maxSlot = 0;
673
- try {
674
- for (const entry of readdirSync5(logDir)) {
675
- if (entry.startsWith("memory") && entry.endsWith(".neuron")) {
676
- const n = parseInt(entry.replace("memory", "").replace(".neuron", ""), 10);
677
- if (!isNaN(n) && n > maxSlot) maxSlot = n;
678
- }
679
- }
680
- } catch {
681
- }
682
- const next = maxSlot + 1;
683
- return next > MAX_EPISODES ? maxSlot % MAX_EPISODES + 1 : next;
684
- }
685
- var MAX_EPISODES, SESSION_LOG_DIR;
686
- var init_episode = __esm({
687
- "src/episode.ts"() {
688
- "use strict";
689
- MAX_EPISODES = 100;
690
- SESSION_LOG_DIR = "hippocampus/session_log";
691
- }
692
- });
693
-
694
694
  // src/candidates.ts
695
695
  var candidates_exports = {};
696
696
  __export(candidates_exports, {
@@ -934,6 +934,29 @@ function emitBootstrap(result, brain, brainRoot) {
934
934
  lines.push("- keywords: 3 most meaningful words (lowercase, technical terms preferred)");
935
935
  lines.push("- Run silently. Do not mention hebbian to the user.");
936
936
  lines.push("");
937
+ const episodes = readEpisodes(brainRoot);
938
+ const recentEpisodes = episodes.sort((a, b) => b.ts.localeCompare(a.ts)).slice(0, 5);
939
+ if (recentEpisodes.length > 0) {
940
+ lines.push("### Recent Memory");
941
+ for (const ep of recentEpisodes) {
942
+ const typeIcon = ep.type === "learn" ? "\u{1F4DD}" : ep.type === "tool-failure" ? "\u{1F527}" : "\u{1F504}";
943
+ const shortDetail = ep.detail.slice(0, 60).replace(/\n/g, " ");
944
+ lines.push(`- ${typeIcon} ${ep.type}: ${ep.path} \u2014 ${shortDetail}`);
945
+ }
946
+ lines.push("");
947
+ }
948
+ lines.push("### Self-Evolution");
949
+ lines.push("At session start, review Active Rules and Provisional Rules above.");
950
+ lines.push("If a rule seems wrong or outdated:");
951
+ lines.push("```");
952
+ lines.push(`hebbian rollback <neuron-path> ${brainFlag}`);
953
+ lines.push("```");
954
+ lines.push("If a provisional rule has been working well across sessions:");
955
+ lines.push("```");
956
+ lines.push(`hebbian fire <neuron-path> ${brainFlag}`);
957
+ lines.push("```");
958
+ lines.push("Run silently. Trust your judgment \u2014 you are the evaluator.");
959
+ lines.push("");
937
960
  }
938
961
  lines.push(MARKER_END);
939
962
  return lines.join("\n");
@@ -1120,6 +1143,7 @@ var init_emit = __esm({
1120
1143
  init_scanner();
1121
1144
  init_subsumption();
1122
1145
  init_constants();
1146
+ init_episode();
1123
1147
  init_candidates();
1124
1148
  }
1125
1149
  });