hebbian 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -30
- package/dist/bin/hebbian.js +680 -516
- package/dist/bin/hebbian.js.map +1 -1
- package/dist/digest.d.ts.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/emit.d.ts +1 -1
- package/dist/emit.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +359 -250
- package/dist/index.js.map +1 -1
- package/dist/learn.d.ts +17 -0
- package/dist/learn.d.ts.map +1 -0
- package/package.json +1 -1
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,32 @@ 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
|
+
}
|
|
884
|
+
if (brainRoot) {
|
|
885
|
+
const brainFlag = `--brain ${brainRoot}`;
|
|
886
|
+
lines.push("### Self-Learning");
|
|
887
|
+
lines.push("When the user corrects your behavior (tells you to stop, change, or always do something),");
|
|
888
|
+
lines.push("run this command to learn from it:");
|
|
889
|
+
lines.push("```");
|
|
890
|
+
lines.push(`hebbian learn "<what the user said>" --prefix <NO|DO|MUST|WARN> --keywords "k1,k2,k3" ${brainFlag}`);
|
|
891
|
+
lines.push("```");
|
|
892
|
+
lines.push("- NO: user says don't/stop/never \u2014 DO: user says always/should \u2014 MUST: mandatory \u2014 WARN: be careful");
|
|
893
|
+
lines.push("- keywords: 3 most meaningful words (lowercase, technical terms preferred)");
|
|
894
|
+
lines.push("- Run silently. Do not mention hebbian to the user.");
|
|
895
|
+
lines.push("");
|
|
896
|
+
}
|
|
675
897
|
lines.push(MARKER_END);
|
|
676
898
|
return lines.join("\n");
|
|
677
899
|
}
|
|
@@ -766,7 +988,7 @@ function emitRegionRules(region) {
|
|
|
766
988
|
function emitToTarget(brainRoot, target) {
|
|
767
989
|
const brain = scanBrain(brainRoot);
|
|
768
990
|
const result = runSubsumption(brain);
|
|
769
|
-
const content = emitBootstrap(result, brain);
|
|
991
|
+
const content = emitBootstrap(result, brain, brainRoot);
|
|
770
992
|
if (target === "all") {
|
|
771
993
|
for (const [name, filePath] of Object.entries(EMIT_TARGETS)) {
|
|
772
994
|
writeTarget(filePath, content);
|
|
@@ -782,33 +1004,33 @@ function emitToTarget(brainRoot, target) {
|
|
|
782
1004
|
}
|
|
783
1005
|
function writeAllTiers(brainRoot, result, brain) {
|
|
784
1006
|
const indexContent = emitIndex(result, brain);
|
|
785
|
-
|
|
1007
|
+
writeFileSync7(join11(brainRoot, "_index.md"), indexContent, "utf8");
|
|
786
1008
|
for (const region of result.activeRegions) {
|
|
787
|
-
if (
|
|
1009
|
+
if (existsSync10(region.path)) {
|
|
788
1010
|
const rulesContent = emitRegionRules(region);
|
|
789
|
-
|
|
1011
|
+
writeFileSync7(join11(region.path, "_rules.md"), rulesContent, "utf8");
|
|
790
1012
|
}
|
|
791
1013
|
}
|
|
792
1014
|
}
|
|
793
1015
|
function writeTarget(filePath, content) {
|
|
794
|
-
const dir =
|
|
795
|
-
if (dir !== "." && !
|
|
796
|
-
|
|
1016
|
+
const dir = dirname2(filePath);
|
|
1017
|
+
if (dir !== "." && !existsSync10(dir)) {
|
|
1018
|
+
mkdirSync5(dir, { recursive: true });
|
|
797
1019
|
}
|
|
798
|
-
if (
|
|
799
|
-
const existing =
|
|
1020
|
+
if (existsSync10(filePath)) {
|
|
1021
|
+
const existing = readFileSync3(filePath, "utf8");
|
|
800
1022
|
const startIdx = existing.indexOf(MARKER_START);
|
|
801
1023
|
const endIdx = existing.indexOf(MARKER_END);
|
|
802
1024
|
if (startIdx !== -1 && endIdx !== -1) {
|
|
803
1025
|
const before = existing.slice(0, startIdx);
|
|
804
1026
|
const after = existing.slice(endIdx + MARKER_END.length);
|
|
805
|
-
|
|
1027
|
+
writeFileSync7(filePath, before + content + after, "utf8");
|
|
806
1028
|
return;
|
|
807
1029
|
}
|
|
808
|
-
|
|
1030
|
+
writeFileSync7(filePath, content + "\n\n" + existing, "utf8");
|
|
809
1031
|
return;
|
|
810
1032
|
}
|
|
811
|
-
|
|
1033
|
+
writeFileSync7(filePath, content, "utf8");
|
|
812
1034
|
}
|
|
813
1035
|
function printDiag(brain, result) {
|
|
814
1036
|
console.log("");
|
|
@@ -897,8 +1119,8 @@ function computeHash(result) {
|
|
|
897
1119
|
}
|
|
898
1120
|
|
|
899
1121
|
// src/init.ts
|
|
900
|
-
import { mkdirSync as
|
|
901
|
-
import { join as
|
|
1122
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync8, readFileSync as readFileSync4, existsSync as existsSync11, readdirSync as readdirSync8, appendFileSync } from "fs";
|
|
1123
|
+
import { join as join12, dirname as dirname3 } from "path";
|
|
902
1124
|
var REGION_TEMPLATES = {
|
|
903
1125
|
brainstem: {
|
|
904
1126
|
description: "Absolute principles. Immutable. Read-only conscience.\nP0 \u2014 highest priority. bomb here halts EVERYTHING.",
|
|
@@ -930,22 +1152,22 @@ var REGION_TEMPLATES = {
|
|
|
930
1152
|
}
|
|
931
1153
|
};
|
|
932
1154
|
function initBrain(brainPath) {
|
|
933
|
-
if (
|
|
934
|
-
const entries =
|
|
1155
|
+
if (existsSync11(brainPath)) {
|
|
1156
|
+
const entries = readdirSync8(brainPath);
|
|
935
1157
|
if (entries.some((e) => REGIONS.includes(e))) {
|
|
936
1158
|
console.log(`\u26A0\uFE0F Brain already exists at ${brainPath}`);
|
|
937
1159
|
return;
|
|
938
1160
|
}
|
|
939
1161
|
}
|
|
940
|
-
|
|
1162
|
+
mkdirSync6(brainPath, { recursive: true });
|
|
941
1163
|
for (const regionName of REGIONS) {
|
|
942
|
-
const regionDir =
|
|
943
|
-
|
|
1164
|
+
const regionDir = join12(brainPath, regionName);
|
|
1165
|
+
mkdirSync6(regionDir, { recursive: true });
|
|
944
1166
|
const template = REGION_TEMPLATES[regionName];
|
|
945
1167
|
const icon = REGION_ICONS[regionName];
|
|
946
1168
|
const ko = REGION_KO[regionName];
|
|
947
|
-
|
|
948
|
-
|
|
1169
|
+
writeFileSync8(
|
|
1170
|
+
join12(regionDir, "_rules.md"),
|
|
949
1171
|
`# ${icon} ${regionName} (${ko})
|
|
950
1172
|
|
|
951
1173
|
${template.description}
|
|
@@ -953,15 +1175,15 @@ ${template.description}
|
|
|
953
1175
|
"utf8"
|
|
954
1176
|
);
|
|
955
1177
|
for (const starter of template.starters) {
|
|
956
|
-
const neuronDir =
|
|
957
|
-
|
|
958
|
-
|
|
1178
|
+
const neuronDir = join12(regionDir, starter);
|
|
1179
|
+
mkdirSync6(neuronDir, { recursive: true });
|
|
1180
|
+
writeFileSync8(join12(neuronDir, "1.neuron"), "", "utf8");
|
|
959
1181
|
}
|
|
960
1182
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1183
|
+
mkdirSync6(join12(brainPath, "_agents", "global_inbox"), { recursive: true });
|
|
1184
|
+
mkdirSync6(join12(brainPath, "skills"), { recursive: true });
|
|
1185
|
+
writeFileSync8(
|
|
1186
|
+
join12(brainPath, "skills", "_rules.md"),
|
|
965
1187
|
"# Skills Library\n\nExecutable patterns learned through experience.\nNot part of the subsumption cascade \u2014 retrieval only.\n",
|
|
966
1188
|
"utf8"
|
|
967
1189
|
);
|
|
@@ -974,13 +1196,13 @@ ${template.description}
|
|
|
974
1196
|
console.log(` hebbian emit claude --brain ${brainPath}`);
|
|
975
1197
|
}
|
|
976
1198
|
function autoGitignore(brainPath) {
|
|
977
|
-
let dir =
|
|
1199
|
+
let dir = dirname3(brainPath);
|
|
978
1200
|
for (let i = 0; i < 10; i++) {
|
|
979
|
-
if (
|
|
980
|
-
const gitignorePath =
|
|
1201
|
+
if (existsSync11(join12(dir, ".git"))) {
|
|
1202
|
+
const gitignorePath = join12(dir, ".gitignore");
|
|
981
1203
|
const brainDirName = brainPath.replace(dir + "/", "") + "/";
|
|
982
|
-
if (
|
|
983
|
-
const content =
|
|
1204
|
+
if (existsSync11(gitignorePath)) {
|
|
1205
|
+
const content = readFileSync4(gitignorePath, "utf8");
|
|
984
1206
|
if (content.includes(brainDirName) || content.includes(brainDirName.replace(/\/$/, ""))) {
|
|
985
1207
|
return;
|
|
986
1208
|
}
|
|
@@ -992,7 +1214,7 @@ ${brainDirName}
|
|
|
992
1214
|
console.log(` \u{1F4DD} Added ${brainDirName} to .gitignore`);
|
|
993
1215
|
return;
|
|
994
1216
|
}
|
|
995
|
-
const parent =
|
|
1217
|
+
const parent = dirname3(dir);
|
|
996
1218
|
if (parent === dir) break;
|
|
997
1219
|
dir = parent;
|
|
998
1220
|
}
|
|
@@ -1004,202 +1226,6 @@ import { createServer } from "http";
|
|
|
1004
1226
|
// src/inbox.ts
|
|
1005
1227
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync9, existsSync as existsSync12, mkdirSync as mkdirSync7 } from "fs";
|
|
1006
1228
|
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
1229
|
var INBOX_DIR = "_inbox";
|
|
1204
1230
|
var CORRECTIONS_FILE = "corrections.jsonl";
|
|
1205
1231
|
var DOPAMINE_ALLOWED_ROLES = ["pm", "admin", "lead"];
|
|
@@ -1763,33 +1789,65 @@ var NEGATION_PATTERNS = [
|
|
|
1763
1789
|
/\binstead\b/i,
|
|
1764
1790
|
/^no[,.\s!]/i,
|
|
1765
1791
|
/\bavoid\b/i,
|
|
1766
|
-
// Korean negation —
|
|
1767
|
-
// "X하지 마" (don't X) — must have a verb object before 지 마
|
|
1792
|
+
// Korean negation — imperative corrections:
|
|
1768
1793
|
/[을를은는도이가]\s*[가-힣]+지\s*마/,
|
|
1769
|
-
// "X
|
|
1794
|
+
// "X하지 마" (don't X) with particle
|
|
1770
1795
|
/하면\s*안\s*돼/,
|
|
1771
|
-
// "X
|
|
1772
|
-
/쓰지\s
|
|
1796
|
+
// "X 하면 안 돼" (must not X)
|
|
1797
|
+
/쓰지\s*마/,
|
|
1798
|
+
// "쓰지 마" (don't use)
|
|
1799
|
+
/그만/,
|
|
1800
|
+
// "그만" (stop) — 그만해, 그만 좀
|
|
1801
|
+
/[을를은는]\s*빼/,
|
|
1802
|
+
// "X 빼" (remove X) with particle
|
|
1803
|
+
/지워[줘]?|삭제해/,
|
|
1804
|
+
// "지워/삭제해" (delete it) — not 지우고 (connective)
|
|
1805
|
+
/[가-힣]+지\s*말고/,
|
|
1806
|
+
// "X지 말고" (instead of X-ing)
|
|
1807
|
+
/그거\s*아니/,
|
|
1808
|
+
// "그거 아니야" (that's not right)
|
|
1809
|
+
/ㄴㄴ|노노/,
|
|
1810
|
+
// "ㄴㄴ/노노" (no no — internet-style)
|
|
1811
|
+
/안\s*돼[^요]?\s*[!.]/
|
|
1812
|
+
// "안 돼!" standalone prohibition
|
|
1773
1813
|
];
|
|
1774
1814
|
var AFFIRMATION_PATTERNS = [
|
|
1775
1815
|
/\bshould\s+always\b/i,
|
|
1776
1816
|
/\buse\s+\w+\s+instead\b/i,
|
|
1777
|
-
// Korean affirmation —
|
|
1778
|
-
/항상\s*[가-힣]+[해하]
|
|
1817
|
+
// Korean affirmation — directive commands:
|
|
1818
|
+
/항상\s*[가-힣]+[해하]/,
|
|
1779
1819
|
// "항상 X해" (always do X)
|
|
1820
|
+
/[을를]\s*[가-힣]*해\s*줘/,
|
|
1821
|
+
// "X를 해줘" (do X for me) — literal 해줘, not bare 줘
|
|
1822
|
+
/으로\s*해/,
|
|
1823
|
+
// "X으로 해" (do it as X) — literal 으로, not char class
|
|
1824
|
+
/이렇게\s*해/
|
|
1825
|
+
// "이렇게 해" (do it like this)
|
|
1780
1826
|
];
|
|
1781
1827
|
var MUST_PATTERNS = [
|
|
1782
1828
|
/\bmust\b/i,
|
|
1783
1829
|
/\brequired\b/i,
|
|
1784
|
-
// Korean
|
|
1785
|
-
|
|
1830
|
+
// Korean — strong directives:
|
|
1831
|
+
/반드시/,
|
|
1832
|
+
// "반드시" (absolutely must)
|
|
1833
|
+
/꼭\s*[가-힣]/,
|
|
1834
|
+
// "꼭 X해" (definitely do X)
|
|
1835
|
+
/무조건/,
|
|
1836
|
+
// "무조건" (unconditionally)
|
|
1837
|
+
/필수/
|
|
1838
|
+
// "필수" (mandatory)
|
|
1786
1839
|
];
|
|
1787
1840
|
var WARN_PATTERNS = [
|
|
1788
1841
|
/\bcareful\b/i,
|
|
1789
1842
|
/\bwatch\s+out\b/i,
|
|
1790
1843
|
/\bwarning\b/i,
|
|
1791
|
-
// Korean
|
|
1792
|
-
|
|
1844
|
+
// Korean — cautionary:
|
|
1845
|
+
/주의/,
|
|
1846
|
+
// "주의" (caution)
|
|
1847
|
+
/조심/,
|
|
1848
|
+
// "조심" (be careful)
|
|
1849
|
+
/위험/
|
|
1850
|
+
// "위험" (dangerous)
|
|
1793
1851
|
];
|
|
1794
1852
|
function readHookInput(stdin) {
|
|
1795
1853
|
if (!stdin.trim()) return null;
|
|
@@ -1838,7 +1896,8 @@ function digestTranscript(brainRoot, transcriptPath, sessionId) {
|
|
|
1838
1896
|
console.log(`\u{1F527} digest: ${toolFailures.length} tool failure(s), ${retries.length} retry pattern(s) logged`);
|
|
1839
1897
|
}
|
|
1840
1898
|
const corrections = extractCorrections(messages);
|
|
1841
|
-
|
|
1899
|
+
const fired = autoFireCandidates(brainRoot, corrections);
|
|
1900
|
+
if (corrections.length === 0 && toolFailures.length === 0 && fired === 0) {
|
|
1842
1901
|
console.log(`\u{1F4DD} digest: no corrections found in session ${resolvedSessionId}`);
|
|
1843
1902
|
writeAuditLog(brainRoot, resolvedSessionId, [], totalLines);
|
|
1844
1903
|
return { corrections: 0, skipped: messages.length, toolFailures: toolFailures.length, transcriptPath, sessionId: resolvedSessionId };
|
|
@@ -2003,7 +2062,7 @@ function extractCorrections(messages) {
|
|
|
2003
2062
|
if (/^Base directory for this skill:/i.test(trimmed)) continue;
|
|
2004
2063
|
if (/^[•·▸▶\-\*]\s/.test(trimmed)) continue;
|
|
2005
2064
|
if (/<[a-zA-Z][a-zA-Z-]*>/.test(trimmed) && /<\/[a-zA-Z]/.test(trimmed)) continue;
|
|
2006
|
-
if (
|
|
2065
|
+
if (isNarrative(trimmed)) continue;
|
|
2007
2066
|
const correction = detectCorrection(text);
|
|
2008
2067
|
if (correction) {
|
|
2009
2068
|
corrections.push(correction);
|
|
@@ -2011,7 +2070,7 @@ function extractCorrections(messages) {
|
|
|
2011
2070
|
}
|
|
2012
2071
|
return corrections;
|
|
2013
2072
|
}
|
|
2014
|
-
function
|
|
2073
|
+
function isNarrative(text) {
|
|
2015
2074
|
const NARRATIVE_MARKERS = [
|
|
2016
2075
|
/이유는/,
|
|
2017
2076
|
// "the reason is..."
|
|
@@ -2037,6 +2096,26 @@ function isNarrativeKorean(text) {
|
|
|
2037
2096
|
const markerCount = NARRATIVE_MARKERS.filter((p) => p.test(text)).length;
|
|
2038
2097
|
return markerCount >= 2;
|
|
2039
2098
|
}
|
|
2099
|
+
function autoFireCandidates(brainRoot, corrections) {
|
|
2100
|
+
if (corrections.length > 0) return 0;
|
|
2101
|
+
const candidates = listCandidates(brainRoot);
|
|
2102
|
+
if (candidates.length === 0) return 0;
|
|
2103
|
+
let fired = 0;
|
|
2104
|
+
for (const cand of candidates) {
|
|
2105
|
+
try {
|
|
2106
|
+
const newCounter = fireNeuron(brainRoot, cand.candidatePath);
|
|
2107
|
+
fired++;
|
|
2108
|
+
if (newCounter >= CANDIDATE_THRESHOLD) {
|
|
2109
|
+
growCandidate(brainRoot, cand.targetPath);
|
|
2110
|
+
}
|
|
2111
|
+
} catch {
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
if (fired > 0) {
|
|
2115
|
+
console.log(`\u{1F504} agent-evaluator: ${fired} candidate(s) advanced (session without corrections)`);
|
|
2116
|
+
}
|
|
2117
|
+
return fired;
|
|
2118
|
+
}
|
|
2040
2119
|
function detectCorrection(text) {
|
|
2041
2120
|
const isNegation = NEGATION_PATTERNS.some((p) => p.test(text));
|
|
2042
2121
|
const isMust = MUST_PATTERNS.some((p) => p.test(text));
|
|
@@ -2044,9 +2123,9 @@ function detectCorrection(text) {
|
|
|
2044
2123
|
const isAffirmation = AFFIRMATION_PATTERNS.some((p) => p.test(text));
|
|
2045
2124
|
if (!isNegation && !isMust && !isWarn && !isAffirmation) return null;
|
|
2046
2125
|
const categories = [isNegation, isMust, isWarn, isAffirmation].filter(Boolean).length;
|
|
2047
|
-
const
|
|
2048
|
-
if (
|
|
2049
|
-
if (text.length >
|
|
2126
|
+
const latinRatio = (text.match(/[a-zA-Z]/g) || []).length / Math.max(text.length, 1);
|
|
2127
|
+
if (latinRatio < 0.3 && categories < 2) {
|
|
2128
|
+
if (text.length > 150) return null;
|
|
2050
2129
|
}
|
|
2051
2130
|
let prefix;
|
|
2052
2131
|
if (isNegation) prefix = "NO";
|
|
@@ -2233,6 +2312,35 @@ function writeAuditLog(brainRoot, sessionId, entries, lineCount) {
|
|
|
2233
2312
|
writeFileSync11(logPath, [metaLine, ...entryLines].join("\n") + "\n", "utf8");
|
|
2234
2313
|
}
|
|
2235
2314
|
|
|
2315
|
+
// src/learn.ts
|
|
2316
|
+
var VALID_PREFIXES = /* @__PURE__ */ new Set(["NO", "DO", "MUST", "WARN"]);
|
|
2317
|
+
function learn(brainRoot, opts) {
|
|
2318
|
+
let prefix;
|
|
2319
|
+
let keywords;
|
|
2320
|
+
let source;
|
|
2321
|
+
if (opts.prefix && opts.keywords && opts.keywords.length > 0) {
|
|
2322
|
+
prefix = opts.prefix.toUpperCase();
|
|
2323
|
+
if (!VALID_PREFIXES.has(prefix)) {
|
|
2324
|
+
prefix = "DO";
|
|
2325
|
+
}
|
|
2326
|
+
keywords = opts.keywords.slice(0, 3).map((k) => k.toLowerCase().replace(/[\s\/\\\.,:;!?'"<>{}()\[\]]/g, ""));
|
|
2327
|
+
source = "agent";
|
|
2328
|
+
} else {
|
|
2329
|
+
const corrections = extractCorrections([opts.text]);
|
|
2330
|
+
if (corrections.length === 0) return null;
|
|
2331
|
+
const c = corrections[0];
|
|
2332
|
+
prefix = c.prefix;
|
|
2333
|
+
keywords = c.keywords;
|
|
2334
|
+
source = "regex";
|
|
2335
|
+
}
|
|
2336
|
+
if (keywords.length === 0) return null;
|
|
2337
|
+
const pathSegment = `${prefix}_${keywords.slice(0, 3).join("_")}`;
|
|
2338
|
+
const path = `cortex/${pathSegment}`;
|
|
2339
|
+
growCandidate(brainRoot, path);
|
|
2340
|
+
logEpisode(brainRoot, "learn", path, opts.text);
|
|
2341
|
+
return { path, prefix, keywords, source };
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2236
2344
|
// src/evolve.ts
|
|
2237
2345
|
import { existsSync as existsSync16, readFileSync as readFileSync9, writeFileSync as writeFileSync13 } from "fs";
|
|
2238
2346
|
import { join as join17 } from "path";
|
|
@@ -3036,6 +3144,7 @@ export {
|
|
|
3036
3144
|
installCron,
|
|
3037
3145
|
installHooks,
|
|
3038
3146
|
jaccardSimilarity,
|
|
3147
|
+
learn,
|
|
3039
3148
|
listCandidates,
|
|
3040
3149
|
logEpisode,
|
|
3041
3150
|
parseToolResults,
|