hebbian 0.3.2 → 0.3.3

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/digest.d.ts CHANGED
@@ -7,7 +7,7 @@ export interface DigestResult {
7
7
  export interface ExtractedCorrection {
8
8
  text: string;
9
9
  path: string;
10
- prefix: 'NO' | 'DO';
10
+ prefix: 'NO' | 'DO' | 'MUST' | 'WARN';
11
11
  keywords: string[];
12
12
  }
13
13
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,YAAY;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,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,CAAC;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACnB;AAyCD;;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,CAsD5G;AA6CD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,mBAAmB,EAAE,CAuB5E"}
1
+ {"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,YAAY;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,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;AAwDD;;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,CAsD5G;AA6CD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,mBAAmB,EAAE,CAuB5E"}
@@ -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;AAMxE;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAuE7E;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;AAgCD;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAgCvE"}
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;AAMxE;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAuE7E;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,CAgCvE"}
@@ -1 +1 @@
1
- {"version":3,"file":"grow.d.ts","sourceRoot":"","sources":["../src/grow.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,CAoC5E"}
1
+ {"version":3,"file":"grow.d.ts","sourceRoot":"","sources":["../src/grow.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,CAwC5E"}
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,UAAU;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;CACjB;AAcD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAyF1E;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CA6CzD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,CAmD3D"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,UAAU;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;CACjB;AAcD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAkG1E;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CA6CzD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,CAmD3D"}
package/dist/index.js CHANGED
@@ -239,7 +239,7 @@ function runSubsumption(brain) {
239
239
 
240
240
  // src/similarity.ts
241
241
  function tokenize(name) {
242
- return name.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[_\-\s]+/g, " ").toLowerCase().split(" ").map(stem).filter((t) => t.length > 1);
242
+ 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);
243
243
  }
244
244
  function stem(word) {
245
245
  const suffixes = ["ing", "tion", "sion", "ness", "ment", "able", "ible", "ful", "less", "ous", "ive", "ity", "ies", "ed", "er", "es", "ly", "al", "en"];
@@ -376,10 +376,12 @@ function growNeuron(brainRoot, neuronPath) {
376
376
  throw new Error(`Invalid region: ${regionName}. Valid: ${REGIONS.join(", ")}`);
377
377
  }
378
378
  const leafName = parts[parts.length - 1];
379
- const newTokens = tokenize(leafName);
379
+ const newPrefix = leafName.match(/^(NO|DO|MUST|WARN)_/)?.[1] || "";
380
+ const newStripped = leafName.replace(/^(NO|DO|MUST|WARN)_/, "");
381
+ const newTokens = tokenize(newStripped);
380
382
  const regionPath = join5(brainRoot, regionName);
381
383
  if (existsSync5(regionPath)) {
382
- const match = findSimilar(regionPath, regionPath, newTokens);
384
+ const match = findSimilar(regionPath, regionPath, newTokens, newPrefix);
383
385
  if (match) {
384
386
  const matchRelPath = regionName + "/" + relative2(regionPath, match);
385
387
  console.log(`\u{1F504} consolidation: "${neuronPath}" \u2248 "${matchRelPath}" (firing existing)`);
@@ -392,7 +394,7 @@ function growNeuron(brainRoot, neuronPath) {
392
394
  console.log(`\u{1F331} grew: ${neuronPath} (1)`);
393
395
  return { action: "grew", path: neuronPath, counter: 1 };
394
396
  }
395
- function findSimilar(dir, regionRoot, targetTokens) {
397
+ function findSimilar(dir, regionRoot, targetTokens, targetPrefix) {
396
398
  let entries;
397
399
  try {
398
400
  entries = readdirSync4(dir, { withFileTypes: true });
@@ -402,16 +404,19 @@ function findSimilar(dir, regionRoot, targetTokens) {
402
404
  const hasNeuron = entries.some((e) => e.isFile() && e.name.endsWith(".neuron"));
403
405
  if (hasNeuron) {
404
406
  const folderName = dir.split("/").pop() || "";
405
- const existingTokens = tokenize(folderName);
407
+ const existingPrefix = folderName.match(/^(NO|DO|MUST|WARN)_/)?.[1] || "";
408
+ const existingStripped = folderName.replace(/^(NO|DO|MUST|WARN)_/, "");
409
+ const existingTokens = tokenize(existingStripped);
406
410
  const similarity = jaccardSimilarity(targetTokens, existingTokens);
407
- if (similarity >= JACCARD_THRESHOLD) {
411
+ if (targetPrefix !== existingPrefix && targetTokens.length <= 2) {
412
+ } else if (similarity >= JACCARD_THRESHOLD) {
408
413
  return dir;
409
414
  }
410
415
  }
411
416
  for (const entry of entries) {
412
417
  if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
413
418
  if (entry.isDirectory()) {
414
- const match = findSimilar(join5(dir, entry.name), regionRoot, targetTokens);
419
+ const match = findSimilar(join5(dir, entry.name), regionRoot, targetTokens, targetPrefix);
415
420
  if (match) return match;
416
421
  }
417
422
  }
@@ -752,6 +757,8 @@ function writeTarget(filePath, content) {
752
757
  writeFileSync6(filePath, before + content + after, "utf8");
753
758
  return;
754
759
  }
760
+ writeFileSync6(filePath, content + "\n\n" + existing, "utf8");
761
+ return;
755
762
  }
756
763
  writeFileSync6(filePath, content, "utf8");
757
764
  }
@@ -1342,6 +1349,7 @@ function clearReports() {
1342
1349
 
1343
1350
  // src/hooks.ts
1344
1351
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync10, existsSync as existsSync12, mkdirSync as mkdirSync7, readdirSync as readdirSync8 } from "fs";
1352
+ import { execSync as execSync2 } from "child_process";
1345
1353
  import { join as join13, resolve as resolve2 } from "path";
1346
1354
  var SETTINGS_DIR = ".claude";
1347
1355
  var SETTINGS_FILE = "settings.local.json";
@@ -1355,6 +1363,11 @@ function installHooks(brainRoot, projectRoot) {
1355
1363
  const settingsPath = join13(settingsDir, SETTINGS_FILE);
1356
1364
  const defaultBrain = resolve2(root, "brain");
1357
1365
  const brainFlag = resolvedBrain === defaultBrain ? "" : ` --brain ${resolvedBrain}`;
1366
+ let npxBin = "npx";
1367
+ try {
1368
+ npxBin = execSync2("which npx", { encoding: "utf8" }).trim();
1369
+ } catch {
1370
+ }
1358
1371
  let settings = {};
1359
1372
  if (existsSync12(settingsPath)) {
1360
1373
  try {
@@ -1373,7 +1386,7 @@ function installHooks(brainRoot, projectRoot) {
1373
1386
  matcher: "startup|resume",
1374
1387
  entry: {
1375
1388
  type: "command",
1376
- command: `hebbian emit claude${brainFlag}`,
1389
+ command: `${npxBin} hebbian emit claude${brainFlag}`,
1377
1390
  timeout: 10,
1378
1391
  statusMessage: `${HOOK_MARKER} refreshing brain`
1379
1392
  }
@@ -1382,7 +1395,7 @@ function installHooks(brainRoot, projectRoot) {
1382
1395
  event: "Stop",
1383
1396
  entry: {
1384
1397
  type: "command",
1385
- command: `hebbian digest${brainFlag}`,
1398
+ command: `${npxBin} hebbian digest${brainFlag}`,
1386
1399
  timeout: 30,
1387
1400
  statusMessage: `${HOOK_MARKER} digesting session`
1388
1401
  }
@@ -1410,8 +1423,8 @@ function installHooks(brainRoot, projectRoot) {
1410
1423
  }
1411
1424
  writeFileSync10(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
1412
1425
  console.log(`\u2705 hebbian hooks installed at ${settingsPath}`);
1413
- console.log(` SessionStart \u2192 hebbian emit claude${brainFlag}`);
1414
- console.log(` Stop \u2192 hebbian digest${brainFlag}`);
1426
+ console.log(` SessionStart \u2192 ${npxBin} hebbian emit claude${brainFlag}`);
1427
+ console.log(` Stop \u2192 ${npxBin} hebbian digest${brainFlag}`);
1415
1428
  }
1416
1429
  function uninstallHooks(projectRoot) {
1417
1430
  const root = projectRoot || process.cwd();
@@ -1523,13 +1536,24 @@ var NEGATION_PATTERNS = [
1523
1536
  ];
1524
1537
  var AFFIRMATION_PATTERNS = [
1525
1538
  /\balways\b/i,
1526
- /\bmust\b/i,
1527
1539
  /\bshould\s+always\b/i,
1528
1540
  /\buse\s+\w+\s+instead\b/i,
1529
1541
  // Korean affirmation
1530
- /항상/,
1542
+ /항상/
1543
+ ];
1544
+ var MUST_PATTERNS = [
1545
+ /\bmust\b/i,
1546
+ /\brequired\b/i,
1547
+ // Korean
1531
1548
  /반드시/
1532
1549
  ];
1550
+ var WARN_PATTERNS = [
1551
+ /\bcareful\b/i,
1552
+ /\bwatch\s+out\b/i,
1553
+ /\bwarning\b/i,
1554
+ // Korean
1555
+ /주의/
1556
+ ];
1533
1557
  function readHookInput(stdin) {
1534
1558
  if (!stdin.trim()) return null;
1535
1559
  try {
@@ -1626,17 +1650,24 @@ function extractCorrections(messages) {
1626
1650
  }
1627
1651
  function detectCorrection(text) {
1628
1652
  const isNegation = NEGATION_PATTERNS.some((p) => p.test(text));
1653
+ const isMust = MUST_PATTERNS.some((p) => p.test(text));
1654
+ const isWarn = WARN_PATTERNS.some((p) => p.test(text));
1629
1655
  const isAffirmation = AFFIRMATION_PATTERNS.some((p) => p.test(text));
1630
- if (!isNegation && !isAffirmation) return null;
1631
- const prefix = isNegation ? "NO" : "DO";
1656
+ if (!isNegation && !isMust && !isWarn && !isAffirmation) return null;
1657
+ let prefix;
1658
+ if (isNegation) prefix = "NO";
1659
+ else if (isMust) prefix = "MUST";
1660
+ else if (isWarn) prefix = "WARN";
1661
+ else prefix = "DO";
1632
1662
  const keywords = extractKeywords(text);
1633
1663
  if (keywords.length === 0) return null;
1634
- const pathSegment = `${prefix}_${keywords.slice(0, 4).join("_")}`;
1664
+ const pathSegment = `${prefix}_${keywords.slice(0, 3).join("_")}`;
1635
1665
  const path = `cortex/${pathSegment}`;
1636
1666
  return { text, path, prefix, keywords };
1637
1667
  }
1638
1668
  function extractKeywords(text) {
1639
1669
  const STOP_WORDS = /* @__PURE__ */ new Set([
1670
+ // English stop words
1640
1671
  "the",
1641
1672
  "a",
1642
1673
  "an",
@@ -1718,7 +1749,6 @@ function extractKeywords(text) {
1718
1749
  "those",
1719
1750
  "it",
1720
1751
  "its",
1721
- "i",
1722
1752
  "me",
1723
1753
  "my",
1724
1754
  "we",
@@ -1747,10 +1777,33 @@ function extractKeywords(text) {
1747
1777
  "should",
1748
1778
  "like",
1749
1779
  "want",
1750
- "think"
1780
+ "think",
1781
+ "way",
1782
+ "make",
1783
+ "sure",
1784
+ "keep",
1785
+ "try",
1786
+ "let",
1787
+ "get",
1788
+ "put",
1789
+ "set",
1790
+ "new",
1791
+ "also",
1792
+ "using",
1793
+ "used",
1794
+ "when",
1795
+ "where",
1796
+ "how",
1797
+ "why",
1798
+ "here",
1799
+ "there",
1800
+ "careful",
1801
+ "warning",
1802
+ "watch",
1803
+ "out",
1804
+ "required"
1751
1805
  ]);
1752
- const tokens = tokenize(text);
1753
- return tokens.filter((t) => !STOP_WORDS.has(t) && t.length > 2);
1806
+ return text.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[^a-zA-Z0-9\u3000-\u9FFF\uAC00-\uD7AF]+/g, " ").toLowerCase().split(/\s+/).filter((t) => t.length > 2 && !STOP_WORDS.has(t));
1754
1807
  }
1755
1808
  function writeAuditLog(brainRoot, sessionId, entries) {
1756
1809
  const logDir = join14(brainRoot, DIGEST_LOG_DIR);