hebbian 0.9.0 → 0.10.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/bin/hebbian.js +540 -504
- package/dist/bin/hebbian.js.map +1 -1
- package/dist/digest.d.ts.map +1 -1
- package/dist/emit.d.ts +1 -1
- package/dist/emit.d.ts.map +1 -1
- package/dist/index.js +268 -234
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/digest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,YAAY;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IACtC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,UAAU,cAAc;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;KACpG,CAAC;IACF,aAAa,CAAC,EAAE;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CAClB;AA8CD;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAYjG;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,CA2F5G;AAsDD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,cAAc,EAAE,MAAM,GAAG,WAAW,EAAE,CAGtE;AAqCD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAoB1E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAChC,KAAK,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,EACxF,aAAa,CAAC,EAAE,cAAc,CAAC,eAAe,CAAC,GAC7C,WAAW,GAAG,IAAI,CA8BpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAChC,KAAK,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,EACxF,aAAa,CAAC,EAAE,cAAc,CAAC,eAAe,CAAC,GAC7C,WAAW,GAAG,IAAI,CAiCpB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,mBAAmB,EAAE,CA2C5E"}
|
package/dist/emit.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Region, Brain, SubsumptionResult } from './types';
|
|
|
2
2
|
/**
|
|
3
3
|
* Generate Tier 1 bootstrap content.
|
|
4
4
|
*/
|
|
5
|
-
export declare function emitBootstrap(result: SubsumptionResult, brain: Brain): string;
|
|
5
|
+
export declare function emitBootstrap(result: SubsumptionResult, brain: Brain, brainRoot?: string): string;
|
|
6
6
|
/**
|
|
7
7
|
* Generate Tier 2 brain index content.
|
|
8
8
|
*/
|
package/dist/emit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../src/emit.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAU,MAAM,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../src/emit.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAU,MAAM,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAOxE;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAuFjG;AAMD;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAuDzE;AAMD;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAgDtD;AAMD;;GAEG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAmBpE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAY9F;AAoCD;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAiCvE"}
|
package/dist/index.js
CHANGED
|
@@ -614,9 +614,205 @@ function gitSnapshot(brainRoot) {
|
|
|
614
614
|
import { watch } from "fs";
|
|
615
615
|
|
|
616
616
|
// src/emit.ts
|
|
617
|
-
import { existsSync as
|
|
618
|
-
import { join as
|
|
619
|
-
|
|
617
|
+
import { existsSync as existsSync10, readFileSync as readFileSync3, writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "fs";
|
|
618
|
+
import { join as join11, dirname as dirname2 } from "path";
|
|
619
|
+
|
|
620
|
+
// src/candidates.ts
|
|
621
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync7, renameSync as renameSync3, rmSync, statSync as statSync3 } from "fs";
|
|
622
|
+
import { join as join10, dirname, relative as relative3 } from "path";
|
|
623
|
+
|
|
624
|
+
// src/episode.ts
|
|
625
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync2, writeFileSync as writeFileSync6, mkdirSync as mkdirSync3, existsSync as existsSync8 } from "fs";
|
|
626
|
+
import { join as join9 } from "path";
|
|
627
|
+
var MAX_EPISODES = 100;
|
|
628
|
+
var SESSION_LOG_DIR = "hippocampus/session_log";
|
|
629
|
+
function logEpisode(brainRoot, type, path, detail, extra) {
|
|
630
|
+
const logDir = join9(brainRoot, SESSION_LOG_DIR);
|
|
631
|
+
if (!existsSync8(logDir)) {
|
|
632
|
+
mkdirSync3(logDir, { recursive: true });
|
|
633
|
+
}
|
|
634
|
+
const nextSlot = getNextSlot(logDir);
|
|
635
|
+
const episode = {
|
|
636
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
637
|
+
type,
|
|
638
|
+
path,
|
|
639
|
+
detail,
|
|
640
|
+
...extra?.outcome ? { outcome: extra.outcome } : {},
|
|
641
|
+
...extra?.neurons ? { neurons: extra.neurons } : {}
|
|
642
|
+
};
|
|
643
|
+
writeFileSync6(
|
|
644
|
+
join9(logDir, `memory${nextSlot}.neuron`),
|
|
645
|
+
JSON.stringify(episode),
|
|
646
|
+
"utf8"
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
function readEpisodes(brainRoot) {
|
|
650
|
+
const logDir = join9(brainRoot, SESSION_LOG_DIR);
|
|
651
|
+
if (!existsSync8(logDir)) return [];
|
|
652
|
+
const episodes = [];
|
|
653
|
+
let entries;
|
|
654
|
+
try {
|
|
655
|
+
entries = readdirSync6(logDir);
|
|
656
|
+
} catch {
|
|
657
|
+
return [];
|
|
658
|
+
}
|
|
659
|
+
for (const entry of entries) {
|
|
660
|
+
if (!entry.startsWith("memory") || !entry.endsWith(".neuron")) continue;
|
|
661
|
+
try {
|
|
662
|
+
const content = readFileSync2(join9(logDir, entry), "utf8");
|
|
663
|
+
if (content.trim()) {
|
|
664
|
+
episodes.push(JSON.parse(content));
|
|
665
|
+
}
|
|
666
|
+
} catch {
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
episodes.sort((a, b) => a.ts.localeCompare(b.ts));
|
|
670
|
+
return episodes;
|
|
671
|
+
}
|
|
672
|
+
function getNextSlot(logDir) {
|
|
673
|
+
let maxSlot = 0;
|
|
674
|
+
try {
|
|
675
|
+
for (const entry of readdirSync6(logDir)) {
|
|
676
|
+
if (entry.startsWith("memory") && entry.endsWith(".neuron")) {
|
|
677
|
+
const n = parseInt(entry.replace("memory", "").replace(".neuron", ""), 10);
|
|
678
|
+
if (!isNaN(n) && n > maxSlot) maxSlot = n;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
} catch {
|
|
682
|
+
}
|
|
683
|
+
const next = maxSlot + 1;
|
|
684
|
+
return next > MAX_EPISODES ? maxSlot % MAX_EPISODES + 1 : next;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/candidates.ts
|
|
688
|
+
var CANDIDATE_THRESHOLD = 3;
|
|
689
|
+
var CANDIDATE_DECAY_DAYS = 14;
|
|
690
|
+
var CANDIDATE_SEGMENT = "_candidates";
|
|
691
|
+
function toCandidatePath(neuronPath) {
|
|
692
|
+
const slash = neuronPath.indexOf("/");
|
|
693
|
+
if (slash === -1) throw new Error(`Invalid neuron path (missing region): ${neuronPath}`);
|
|
694
|
+
return `${neuronPath.slice(0, slash)}/${CANDIDATE_SEGMENT}/${neuronPath.slice(slash + 1)}`;
|
|
695
|
+
}
|
|
696
|
+
function fromCandidatePath(candidatePath) {
|
|
697
|
+
return candidatePath.replace(`/${CANDIDATE_SEGMENT}/`, "/");
|
|
698
|
+
}
|
|
699
|
+
function growCandidate(brainRoot, neuronPath) {
|
|
700
|
+
const candidatePath = toCandidatePath(neuronPath);
|
|
701
|
+
const result = growNeuron(brainRoot, candidatePath);
|
|
702
|
+
if (result.counter >= CANDIDATE_THRESHOLD) {
|
|
703
|
+
const ok = moveCandidate(brainRoot, candidatePath, neuronPath);
|
|
704
|
+
if (ok) propagateToShared(brainRoot, neuronPath);
|
|
705
|
+
return { ...result, path: ok ? neuronPath : result.path, promoted: ok };
|
|
706
|
+
}
|
|
707
|
+
console.log(` \u{1F331} candidate (${result.counter}/${CANDIDATE_THRESHOLD}): ${candidatePath}`);
|
|
708
|
+
return { ...result, promoted: false };
|
|
709
|
+
}
|
|
710
|
+
function moveCandidate(brainRoot, candidatePath, targetPath) {
|
|
711
|
+
const src = join10(brainRoot, candidatePath);
|
|
712
|
+
if (!existsSync9(src)) return false;
|
|
713
|
+
const dst = join10(brainRoot, targetPath);
|
|
714
|
+
if (existsSync9(dst)) {
|
|
715
|
+
fireNeuron(brainRoot, targetPath);
|
|
716
|
+
rmSync(src, { recursive: true, force: true });
|
|
717
|
+
} else {
|
|
718
|
+
mkdirSync4(dirname(dst), { recursive: true });
|
|
719
|
+
renameSync3(src, dst);
|
|
720
|
+
}
|
|
721
|
+
console.log(`\u{1F393} promoted: ${candidatePath} \u2192 ${targetPath}`);
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
function promoteCandidates(brainRoot) {
|
|
725
|
+
const promoted = [];
|
|
726
|
+
const decayed = [];
|
|
727
|
+
const decayMs = CANDIDATE_DECAY_DAYS * 24 * 60 * 60 * 1e3;
|
|
728
|
+
const now = Date.now();
|
|
729
|
+
for (const region of REGIONS) {
|
|
730
|
+
const candidateRoot = join10(brainRoot, region, CANDIDATE_SEGMENT);
|
|
731
|
+
walkNeuronDirs(candidateRoot, (neuronDir) => {
|
|
732
|
+
const rel = relative3(join10(brainRoot, region), neuronDir);
|
|
733
|
+
const candidatePath = `${region}/${rel}`;
|
|
734
|
+
const targetPath = fromCandidatePath(candidatePath);
|
|
735
|
+
const counter = readCounter(neuronDir);
|
|
736
|
+
const mtime = statSync3(neuronDir).mtimeMs;
|
|
737
|
+
if (counter >= CANDIDATE_THRESHOLD) {
|
|
738
|
+
moveCandidate(brainRoot, candidatePath, targetPath);
|
|
739
|
+
propagateToShared(brainRoot, targetPath);
|
|
740
|
+
promoted.push(targetPath);
|
|
741
|
+
} else if (now - mtime > decayMs) {
|
|
742
|
+
rmSync(neuronDir, { recursive: true, force: true });
|
|
743
|
+
decayed.push(candidatePath);
|
|
744
|
+
console.log(`\u{1F480} candidate decayed: ${candidatePath}`);
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
return { promoted, decayed };
|
|
749
|
+
}
|
|
750
|
+
function listCandidates(brainRoot) {
|
|
751
|
+
const results = [];
|
|
752
|
+
const now = Date.now();
|
|
753
|
+
for (const region of REGIONS) {
|
|
754
|
+
const candidateRoot = join10(brainRoot, region, CANDIDATE_SEGMENT);
|
|
755
|
+
walkNeuronDirs(candidateRoot, (neuronDir) => {
|
|
756
|
+
const rel = relative3(join10(brainRoot, region), neuronDir);
|
|
757
|
+
const candidatePath = `${region}/${rel}`;
|
|
758
|
+
const targetPath = fromCandidatePath(candidatePath);
|
|
759
|
+
const counter = readCounter(neuronDir);
|
|
760
|
+
const mtime = statSync3(neuronDir).mtimeMs;
|
|
761
|
+
const daysInactive = Math.floor((now - mtime) / (24 * 60 * 60 * 1e3));
|
|
762
|
+
results.push({ candidatePath, targetPath, counter, daysInactive });
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
return results;
|
|
766
|
+
}
|
|
767
|
+
function walkNeuronDirs(dir, cb) {
|
|
768
|
+
if (!existsSync9(dir)) return;
|
|
769
|
+
try {
|
|
770
|
+
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
771
|
+
const hasNeuron = entries.some((e) => e.isFile() && e.name.endsWith(".neuron"));
|
|
772
|
+
if (hasNeuron) {
|
|
773
|
+
cb(dir);
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
for (const entry of entries) {
|
|
777
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
778
|
+
walkNeuronDirs(join10(dir, entry.name), cb);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
} catch {
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
function readCounter(dir) {
|
|
785
|
+
try {
|
|
786
|
+
const files = readdirSync7(dir).filter((f) => /^\d+\.neuron$/.test(f));
|
|
787
|
+
if (files.length === 0) return 0;
|
|
788
|
+
return Math.max(...files.map((f) => parseInt(f, 10)));
|
|
789
|
+
} catch {
|
|
790
|
+
return 0;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
function propagateToShared(brainRoot, targetPath) {
|
|
794
|
+
try {
|
|
795
|
+
const agentsIdx = brainRoot.indexOf("/agents/");
|
|
796
|
+
if (agentsIdx === -1) return false;
|
|
797
|
+
const multiBrainRoot = brainRoot.slice(0, agentsIdx);
|
|
798
|
+
const sharedRoot = join10(multiBrainRoot, "shared");
|
|
799
|
+
if (!existsSync9(sharedRoot)) return false;
|
|
800
|
+
const episodes = readEpisodes(brainRoot);
|
|
801
|
+
const neuronName = targetPath.split("/").pop() || "";
|
|
802
|
+
const hasRelevantEpisode = episodes.some(
|
|
803
|
+
(ep) => PROPAGATION_EPISODE_TYPES.includes(ep.type) && (ep.path.includes(neuronName) || ep.detail.includes(neuronName))
|
|
804
|
+
);
|
|
805
|
+
if (!hasRelevantEpisode) return false;
|
|
806
|
+
growNeuron(sharedRoot, targetPath);
|
|
807
|
+
console.log(` \u{1F4E1} propagated to shared: ${targetPath}`);
|
|
808
|
+
return true;
|
|
809
|
+
} catch {
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// src/emit.ts
|
|
815
|
+
function emitBootstrap(result, brain, brainRoot) {
|
|
620
816
|
const lines = [];
|
|
621
817
|
const now = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "");
|
|
622
818
|
lines.push(MARKER_START);
|
|
@@ -672,6 +868,19 @@ function emitBootstrap(result, brain) {
|
|
|
672
868
|
lines.push(`| ${icon} ${region.name} | ${active.length} | ${activation} |`);
|
|
673
869
|
}
|
|
674
870
|
lines.push("");
|
|
871
|
+
if (brainRoot) {
|
|
872
|
+
const candidates = listCandidates(brainRoot);
|
|
873
|
+
if (candidates.length > 0) {
|
|
874
|
+
const top = candidates.slice(0, 5);
|
|
875
|
+
lines.push("### Provisional Rules (evaluating)");
|
|
876
|
+
lines.push("Follow these during this session. They graduate after 3 sessions.");
|
|
877
|
+
for (const c of top) {
|
|
878
|
+
const bar = "\u2588".repeat(c.counter) + "\u2591".repeat(Math.max(0, 3 - c.counter));
|
|
879
|
+
lines.push(`- ${bar} ${pathToSentence(c.targetPath)}`);
|
|
880
|
+
}
|
|
881
|
+
lines.push("");
|
|
882
|
+
}
|
|
883
|
+
}
|
|
675
884
|
lines.push(MARKER_END);
|
|
676
885
|
return lines.join("\n");
|
|
677
886
|
}
|
|
@@ -766,7 +975,7 @@ function emitRegionRules(region) {
|
|
|
766
975
|
function emitToTarget(brainRoot, target) {
|
|
767
976
|
const brain = scanBrain(brainRoot);
|
|
768
977
|
const result = runSubsumption(brain);
|
|
769
|
-
const content = emitBootstrap(result, brain);
|
|
978
|
+
const content = emitBootstrap(result, brain, brainRoot);
|
|
770
979
|
if (target === "all") {
|
|
771
980
|
for (const [name, filePath] of Object.entries(EMIT_TARGETS)) {
|
|
772
981
|
writeTarget(filePath, content);
|
|
@@ -782,33 +991,33 @@ function emitToTarget(brainRoot, target) {
|
|
|
782
991
|
}
|
|
783
992
|
function writeAllTiers(brainRoot, result, brain) {
|
|
784
993
|
const indexContent = emitIndex(result, brain);
|
|
785
|
-
|
|
994
|
+
writeFileSync7(join11(brainRoot, "_index.md"), indexContent, "utf8");
|
|
786
995
|
for (const region of result.activeRegions) {
|
|
787
|
-
if (
|
|
996
|
+
if (existsSync10(region.path)) {
|
|
788
997
|
const rulesContent = emitRegionRules(region);
|
|
789
|
-
|
|
998
|
+
writeFileSync7(join11(region.path, "_rules.md"), rulesContent, "utf8");
|
|
790
999
|
}
|
|
791
1000
|
}
|
|
792
1001
|
}
|
|
793
1002
|
function writeTarget(filePath, content) {
|
|
794
|
-
const dir =
|
|
795
|
-
if (dir !== "." && !
|
|
796
|
-
|
|
1003
|
+
const dir = dirname2(filePath);
|
|
1004
|
+
if (dir !== "." && !existsSync10(dir)) {
|
|
1005
|
+
mkdirSync5(dir, { recursive: true });
|
|
797
1006
|
}
|
|
798
|
-
if (
|
|
799
|
-
const existing =
|
|
1007
|
+
if (existsSync10(filePath)) {
|
|
1008
|
+
const existing = readFileSync3(filePath, "utf8");
|
|
800
1009
|
const startIdx = existing.indexOf(MARKER_START);
|
|
801
1010
|
const endIdx = existing.indexOf(MARKER_END);
|
|
802
1011
|
if (startIdx !== -1 && endIdx !== -1) {
|
|
803
1012
|
const before = existing.slice(0, startIdx);
|
|
804
1013
|
const after = existing.slice(endIdx + MARKER_END.length);
|
|
805
|
-
|
|
1014
|
+
writeFileSync7(filePath, before + content + after, "utf8");
|
|
806
1015
|
return;
|
|
807
1016
|
}
|
|
808
|
-
|
|
1017
|
+
writeFileSync7(filePath, content + "\n\n" + existing, "utf8");
|
|
809
1018
|
return;
|
|
810
1019
|
}
|
|
811
|
-
|
|
1020
|
+
writeFileSync7(filePath, content, "utf8");
|
|
812
1021
|
}
|
|
813
1022
|
function printDiag(brain, result) {
|
|
814
1023
|
console.log("");
|
|
@@ -897,8 +1106,8 @@ function computeHash(result) {
|
|
|
897
1106
|
}
|
|
898
1107
|
|
|
899
1108
|
// src/init.ts
|
|
900
|
-
import { mkdirSync as
|
|
901
|
-
import { join as
|
|
1109
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync8, readFileSync as readFileSync4, existsSync as existsSync11, readdirSync as readdirSync8, appendFileSync } from "fs";
|
|
1110
|
+
import { join as join12, dirname as dirname3 } from "path";
|
|
902
1111
|
var REGION_TEMPLATES = {
|
|
903
1112
|
brainstem: {
|
|
904
1113
|
description: "Absolute principles. Immutable. Read-only conscience.\nP0 \u2014 highest priority. bomb here halts EVERYTHING.",
|
|
@@ -930,22 +1139,22 @@ var REGION_TEMPLATES = {
|
|
|
930
1139
|
}
|
|
931
1140
|
};
|
|
932
1141
|
function initBrain(brainPath) {
|
|
933
|
-
if (
|
|
934
|
-
const entries =
|
|
1142
|
+
if (existsSync11(brainPath)) {
|
|
1143
|
+
const entries = readdirSync8(brainPath);
|
|
935
1144
|
if (entries.some((e) => REGIONS.includes(e))) {
|
|
936
1145
|
console.log(`\u26A0\uFE0F Brain already exists at ${brainPath}`);
|
|
937
1146
|
return;
|
|
938
1147
|
}
|
|
939
1148
|
}
|
|
940
|
-
|
|
1149
|
+
mkdirSync6(brainPath, { recursive: true });
|
|
941
1150
|
for (const regionName of REGIONS) {
|
|
942
|
-
const regionDir =
|
|
943
|
-
|
|
1151
|
+
const regionDir = join12(brainPath, regionName);
|
|
1152
|
+
mkdirSync6(regionDir, { recursive: true });
|
|
944
1153
|
const template = REGION_TEMPLATES[regionName];
|
|
945
1154
|
const icon = REGION_ICONS[regionName];
|
|
946
1155
|
const ko = REGION_KO[regionName];
|
|
947
|
-
|
|
948
|
-
|
|
1156
|
+
writeFileSync8(
|
|
1157
|
+
join12(regionDir, "_rules.md"),
|
|
949
1158
|
`# ${icon} ${regionName} (${ko})
|
|
950
1159
|
|
|
951
1160
|
${template.description}
|
|
@@ -953,15 +1162,15 @@ ${template.description}
|
|
|
953
1162
|
"utf8"
|
|
954
1163
|
);
|
|
955
1164
|
for (const starter of template.starters) {
|
|
956
|
-
const neuronDir =
|
|
957
|
-
|
|
958
|
-
|
|
1165
|
+
const neuronDir = join12(regionDir, starter);
|
|
1166
|
+
mkdirSync6(neuronDir, { recursive: true });
|
|
1167
|
+
writeFileSync8(join12(neuronDir, "1.neuron"), "", "utf8");
|
|
959
1168
|
}
|
|
960
1169
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1170
|
+
mkdirSync6(join12(brainPath, "_agents", "global_inbox"), { recursive: true });
|
|
1171
|
+
mkdirSync6(join12(brainPath, "skills"), { recursive: true });
|
|
1172
|
+
writeFileSync8(
|
|
1173
|
+
join12(brainPath, "skills", "_rules.md"),
|
|
965
1174
|
"# Skills Library\n\nExecutable patterns learned through experience.\nNot part of the subsumption cascade \u2014 retrieval only.\n",
|
|
966
1175
|
"utf8"
|
|
967
1176
|
);
|
|
@@ -974,13 +1183,13 @@ ${template.description}
|
|
|
974
1183
|
console.log(` hebbian emit claude --brain ${brainPath}`);
|
|
975
1184
|
}
|
|
976
1185
|
function autoGitignore(brainPath) {
|
|
977
|
-
let dir =
|
|
1186
|
+
let dir = dirname3(brainPath);
|
|
978
1187
|
for (let i = 0; i < 10; i++) {
|
|
979
|
-
if (
|
|
980
|
-
const gitignorePath =
|
|
1188
|
+
if (existsSync11(join12(dir, ".git"))) {
|
|
1189
|
+
const gitignorePath = join12(dir, ".gitignore");
|
|
981
1190
|
const brainDirName = brainPath.replace(dir + "/", "") + "/";
|
|
982
|
-
if (
|
|
983
|
-
const content =
|
|
1191
|
+
if (existsSync11(gitignorePath)) {
|
|
1192
|
+
const content = readFileSync4(gitignorePath, "utf8");
|
|
984
1193
|
if (content.includes(brainDirName) || content.includes(brainDirName.replace(/\/$/, ""))) {
|
|
985
1194
|
return;
|
|
986
1195
|
}
|
|
@@ -992,7 +1201,7 @@ ${brainDirName}
|
|
|
992
1201
|
console.log(` \u{1F4DD} Added ${brainDirName} to .gitignore`);
|
|
993
1202
|
return;
|
|
994
1203
|
}
|
|
995
|
-
const parent =
|
|
1204
|
+
const parent = dirname3(dir);
|
|
996
1205
|
if (parent === dir) break;
|
|
997
1206
|
dir = parent;
|
|
998
1207
|
}
|
|
@@ -1004,202 +1213,6 @@ import { createServer } from "http";
|
|
|
1004
1213
|
// src/inbox.ts
|
|
1005
1214
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync9, existsSync as existsSync12, mkdirSync as mkdirSync7 } from "fs";
|
|
1006
1215
|
import { join as join13 } from "path";
|
|
1007
|
-
|
|
1008
|
-
// src/candidates.ts
|
|
1009
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync8, renameSync as renameSync3, rmSync, statSync as statSync3 } from "fs";
|
|
1010
|
-
import { join as join12, dirname as dirname3, relative as relative3 } from "path";
|
|
1011
|
-
|
|
1012
|
-
// src/episode.ts
|
|
1013
|
-
import { readdirSync as readdirSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5, existsSync as existsSync10 } from "fs";
|
|
1014
|
-
import { join as join11 } from "path";
|
|
1015
|
-
var MAX_EPISODES = 100;
|
|
1016
|
-
var SESSION_LOG_DIR = "hippocampus/session_log";
|
|
1017
|
-
function logEpisode(brainRoot, type, path, detail, extra) {
|
|
1018
|
-
const logDir = join11(brainRoot, SESSION_LOG_DIR);
|
|
1019
|
-
if (!existsSync10(logDir)) {
|
|
1020
|
-
mkdirSync5(logDir, { recursive: true });
|
|
1021
|
-
}
|
|
1022
|
-
const nextSlot = getNextSlot(logDir);
|
|
1023
|
-
const episode = {
|
|
1024
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1025
|
-
type,
|
|
1026
|
-
path,
|
|
1027
|
-
detail,
|
|
1028
|
-
...extra?.outcome ? { outcome: extra.outcome } : {},
|
|
1029
|
-
...extra?.neurons ? { neurons: extra.neurons } : {}
|
|
1030
|
-
};
|
|
1031
|
-
writeFileSync8(
|
|
1032
|
-
join11(logDir, `memory${nextSlot}.neuron`),
|
|
1033
|
-
JSON.stringify(episode),
|
|
1034
|
-
"utf8"
|
|
1035
|
-
);
|
|
1036
|
-
}
|
|
1037
|
-
function readEpisodes(brainRoot) {
|
|
1038
|
-
const logDir = join11(brainRoot, SESSION_LOG_DIR);
|
|
1039
|
-
if (!existsSync10(logDir)) return [];
|
|
1040
|
-
const episodes = [];
|
|
1041
|
-
let entries;
|
|
1042
|
-
try {
|
|
1043
|
-
entries = readdirSync7(logDir);
|
|
1044
|
-
} catch {
|
|
1045
|
-
return [];
|
|
1046
|
-
}
|
|
1047
|
-
for (const entry of entries) {
|
|
1048
|
-
if (!entry.startsWith("memory") || !entry.endsWith(".neuron")) continue;
|
|
1049
|
-
try {
|
|
1050
|
-
const content = readFileSync4(join11(logDir, entry), "utf8");
|
|
1051
|
-
if (content.trim()) {
|
|
1052
|
-
episodes.push(JSON.parse(content));
|
|
1053
|
-
}
|
|
1054
|
-
} catch {
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
episodes.sort((a, b) => a.ts.localeCompare(b.ts));
|
|
1058
|
-
return episodes;
|
|
1059
|
-
}
|
|
1060
|
-
function getNextSlot(logDir) {
|
|
1061
|
-
let maxSlot = 0;
|
|
1062
|
-
try {
|
|
1063
|
-
for (const entry of readdirSync7(logDir)) {
|
|
1064
|
-
if (entry.startsWith("memory") && entry.endsWith(".neuron")) {
|
|
1065
|
-
const n = parseInt(entry.replace("memory", "").replace(".neuron", ""), 10);
|
|
1066
|
-
if (!isNaN(n) && n > maxSlot) maxSlot = n;
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
} catch {
|
|
1070
|
-
}
|
|
1071
|
-
const next = maxSlot + 1;
|
|
1072
|
-
return next > MAX_EPISODES ? maxSlot % MAX_EPISODES + 1 : next;
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
// src/candidates.ts
|
|
1076
|
-
var CANDIDATE_THRESHOLD = 3;
|
|
1077
|
-
var CANDIDATE_DECAY_DAYS = 14;
|
|
1078
|
-
var CANDIDATE_SEGMENT = "_candidates";
|
|
1079
|
-
function toCandidatePath(neuronPath) {
|
|
1080
|
-
const slash = neuronPath.indexOf("/");
|
|
1081
|
-
if (slash === -1) throw new Error(`Invalid neuron path (missing region): ${neuronPath}`);
|
|
1082
|
-
return `${neuronPath.slice(0, slash)}/${CANDIDATE_SEGMENT}/${neuronPath.slice(slash + 1)}`;
|
|
1083
|
-
}
|
|
1084
|
-
function fromCandidatePath(candidatePath) {
|
|
1085
|
-
return candidatePath.replace(`/${CANDIDATE_SEGMENT}/`, "/");
|
|
1086
|
-
}
|
|
1087
|
-
function growCandidate(brainRoot, neuronPath) {
|
|
1088
|
-
const candidatePath = toCandidatePath(neuronPath);
|
|
1089
|
-
const result = growNeuron(brainRoot, candidatePath);
|
|
1090
|
-
if (result.counter >= CANDIDATE_THRESHOLD) {
|
|
1091
|
-
const ok = moveCandidate(brainRoot, candidatePath, neuronPath);
|
|
1092
|
-
if (ok) propagateToShared(brainRoot, neuronPath);
|
|
1093
|
-
return { ...result, path: ok ? neuronPath : result.path, promoted: ok };
|
|
1094
|
-
}
|
|
1095
|
-
console.log(` \u{1F331} candidate (${result.counter}/${CANDIDATE_THRESHOLD}): ${candidatePath}`);
|
|
1096
|
-
return { ...result, promoted: false };
|
|
1097
|
-
}
|
|
1098
|
-
function moveCandidate(brainRoot, candidatePath, targetPath) {
|
|
1099
|
-
const src = join12(brainRoot, candidatePath);
|
|
1100
|
-
if (!existsSync11(src)) return false;
|
|
1101
|
-
const dst = join12(brainRoot, targetPath);
|
|
1102
|
-
if (existsSync11(dst)) {
|
|
1103
|
-
fireNeuron(brainRoot, targetPath);
|
|
1104
|
-
rmSync(src, { recursive: true, force: true });
|
|
1105
|
-
} else {
|
|
1106
|
-
mkdirSync6(dirname3(dst), { recursive: true });
|
|
1107
|
-
renameSync3(src, dst);
|
|
1108
|
-
}
|
|
1109
|
-
console.log(`\u{1F393} promoted: ${candidatePath} \u2192 ${targetPath}`);
|
|
1110
|
-
return true;
|
|
1111
|
-
}
|
|
1112
|
-
function promoteCandidates(brainRoot) {
|
|
1113
|
-
const promoted = [];
|
|
1114
|
-
const decayed = [];
|
|
1115
|
-
const decayMs = CANDIDATE_DECAY_DAYS * 24 * 60 * 60 * 1e3;
|
|
1116
|
-
const now = Date.now();
|
|
1117
|
-
for (const region of REGIONS) {
|
|
1118
|
-
const candidateRoot = join12(brainRoot, region, CANDIDATE_SEGMENT);
|
|
1119
|
-
walkNeuronDirs(candidateRoot, (neuronDir) => {
|
|
1120
|
-
const rel = relative3(join12(brainRoot, region), neuronDir);
|
|
1121
|
-
const candidatePath = `${region}/${rel}`;
|
|
1122
|
-
const targetPath = fromCandidatePath(candidatePath);
|
|
1123
|
-
const counter = readCounter(neuronDir);
|
|
1124
|
-
const mtime = statSync3(neuronDir).mtimeMs;
|
|
1125
|
-
if (counter >= CANDIDATE_THRESHOLD) {
|
|
1126
|
-
moveCandidate(brainRoot, candidatePath, targetPath);
|
|
1127
|
-
propagateToShared(brainRoot, targetPath);
|
|
1128
|
-
promoted.push(targetPath);
|
|
1129
|
-
} else if (now - mtime > decayMs) {
|
|
1130
|
-
rmSync(neuronDir, { recursive: true, force: true });
|
|
1131
|
-
decayed.push(candidatePath);
|
|
1132
|
-
console.log(`\u{1F480} candidate decayed: ${candidatePath}`);
|
|
1133
|
-
}
|
|
1134
|
-
});
|
|
1135
|
-
}
|
|
1136
|
-
return { promoted, decayed };
|
|
1137
|
-
}
|
|
1138
|
-
function listCandidates(brainRoot) {
|
|
1139
|
-
const results = [];
|
|
1140
|
-
const now = Date.now();
|
|
1141
|
-
for (const region of REGIONS) {
|
|
1142
|
-
const candidateRoot = join12(brainRoot, region, CANDIDATE_SEGMENT);
|
|
1143
|
-
walkNeuronDirs(candidateRoot, (neuronDir) => {
|
|
1144
|
-
const rel = relative3(join12(brainRoot, region), neuronDir);
|
|
1145
|
-
const candidatePath = `${region}/${rel}`;
|
|
1146
|
-
const targetPath = fromCandidatePath(candidatePath);
|
|
1147
|
-
const counter = readCounter(neuronDir);
|
|
1148
|
-
const mtime = statSync3(neuronDir).mtimeMs;
|
|
1149
|
-
const daysInactive = Math.floor((now - mtime) / (24 * 60 * 60 * 1e3));
|
|
1150
|
-
results.push({ candidatePath, targetPath, counter, daysInactive });
|
|
1151
|
-
});
|
|
1152
|
-
}
|
|
1153
|
-
return results;
|
|
1154
|
-
}
|
|
1155
|
-
function walkNeuronDirs(dir, cb) {
|
|
1156
|
-
if (!existsSync11(dir)) return;
|
|
1157
|
-
try {
|
|
1158
|
-
const entries = readdirSync8(dir, { withFileTypes: true });
|
|
1159
|
-
const hasNeuron = entries.some((e) => e.isFile() && e.name.endsWith(".neuron"));
|
|
1160
|
-
if (hasNeuron) {
|
|
1161
|
-
cb(dir);
|
|
1162
|
-
return;
|
|
1163
|
-
}
|
|
1164
|
-
for (const entry of entries) {
|
|
1165
|
-
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
1166
|
-
walkNeuronDirs(join12(dir, entry.name), cb);
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
} catch {
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
function readCounter(dir) {
|
|
1173
|
-
try {
|
|
1174
|
-
const files = readdirSync8(dir).filter((f) => /^\d+\.neuron$/.test(f));
|
|
1175
|
-
if (files.length === 0) return 0;
|
|
1176
|
-
return Math.max(...files.map((f) => parseInt(f, 10)));
|
|
1177
|
-
} catch {
|
|
1178
|
-
return 0;
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
function propagateToShared(brainRoot, targetPath) {
|
|
1182
|
-
try {
|
|
1183
|
-
const agentsIdx = brainRoot.indexOf("/agents/");
|
|
1184
|
-
if (agentsIdx === -1) return false;
|
|
1185
|
-
const multiBrainRoot = brainRoot.slice(0, agentsIdx);
|
|
1186
|
-
const sharedRoot = join12(multiBrainRoot, "shared");
|
|
1187
|
-
if (!existsSync11(sharedRoot)) return false;
|
|
1188
|
-
const episodes = readEpisodes(brainRoot);
|
|
1189
|
-
const neuronName = targetPath.split("/").pop() || "";
|
|
1190
|
-
const hasRelevantEpisode = episodes.some(
|
|
1191
|
-
(ep) => PROPAGATION_EPISODE_TYPES.includes(ep.type) && (ep.path.includes(neuronName) || ep.detail.includes(neuronName))
|
|
1192
|
-
);
|
|
1193
|
-
if (!hasRelevantEpisode) return false;
|
|
1194
|
-
growNeuron(sharedRoot, targetPath);
|
|
1195
|
-
console.log(` \u{1F4E1} propagated to shared: ${targetPath}`);
|
|
1196
|
-
return true;
|
|
1197
|
-
} catch {
|
|
1198
|
-
return false;
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
// src/inbox.ts
|
|
1203
1216
|
var INBOX_DIR = "_inbox";
|
|
1204
1217
|
var CORRECTIONS_FILE = "corrections.jsonl";
|
|
1205
1218
|
var DOPAMINE_ALLOWED_ROLES = ["pm", "admin", "lead"];
|
|
@@ -1838,7 +1851,8 @@ function digestTranscript(brainRoot, transcriptPath, sessionId) {
|
|
|
1838
1851
|
console.log(`\u{1F527} digest: ${toolFailures.length} tool failure(s), ${retries.length} retry pattern(s) logged`);
|
|
1839
1852
|
}
|
|
1840
1853
|
const corrections = extractCorrections(messages);
|
|
1841
|
-
|
|
1854
|
+
const fired = autoFireCandidates(brainRoot, corrections);
|
|
1855
|
+
if (corrections.length === 0 && toolFailures.length === 0 && fired === 0) {
|
|
1842
1856
|
console.log(`\u{1F4DD} digest: no corrections found in session ${resolvedSessionId}`);
|
|
1843
1857
|
writeAuditLog(brainRoot, resolvedSessionId, [], totalLines);
|
|
1844
1858
|
return { corrections: 0, skipped: messages.length, toolFailures: toolFailures.length, transcriptPath, sessionId: resolvedSessionId };
|
|
@@ -2037,6 +2051,26 @@ function isNarrativeKorean(text) {
|
|
|
2037
2051
|
const markerCount = NARRATIVE_MARKERS.filter((p) => p.test(text)).length;
|
|
2038
2052
|
return markerCount >= 2;
|
|
2039
2053
|
}
|
|
2054
|
+
function autoFireCandidates(brainRoot, corrections) {
|
|
2055
|
+
if (corrections.length > 0) return 0;
|
|
2056
|
+
const candidates = listCandidates(brainRoot);
|
|
2057
|
+
if (candidates.length === 0) return 0;
|
|
2058
|
+
let fired = 0;
|
|
2059
|
+
for (const cand of candidates) {
|
|
2060
|
+
try {
|
|
2061
|
+
const newCounter = fireNeuron(brainRoot, cand.candidatePath);
|
|
2062
|
+
fired++;
|
|
2063
|
+
if (newCounter >= CANDIDATE_THRESHOLD) {
|
|
2064
|
+
growCandidate(brainRoot, cand.targetPath);
|
|
2065
|
+
}
|
|
2066
|
+
} catch {
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
if (fired > 0) {
|
|
2070
|
+
console.log(`\u{1F504} agent-evaluator: ${fired} candidate(s) advanced (session without corrections)`);
|
|
2071
|
+
}
|
|
2072
|
+
return fired;
|
|
2073
|
+
}
|
|
2040
2074
|
function detectCorrection(text) {
|
|
2041
2075
|
const isNegation = NEGATION_PATTERNS.some((p) => p.test(text));
|
|
2042
2076
|
const isMust = MUST_PATTERNS.some((p) => p.test(text));
|