@cachly-dev/mcp-server 0.5.48 → 0.5.49

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cachly-dev/mcp-server",
3
- "version": "0.5.48",
3
+ "version": "0.5.49",
4
4
  "mcpName": "io.github.cachly-dev/mcp-server",
5
5
  "description": "AI memory brain for Claude Code, Cursor, Copilot & Windsurf — persistent sessions, lessons learned, semantic search, team brain. 51 MCP tools.",
6
6
  "type": "module",
package/src/index.ts CHANGED
@@ -55,7 +55,7 @@ import { Redis } from 'ioredis';
55
55
  const API_URL = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
56
56
  const JWT = process.env.CACHLY_JWT ?? '';
57
57
  const EMBED_MODEL = process.env.CACHLY_EMBED_MODEL ?? '';
58
- const CURRENT_VERSION = '0.5.48';
58
+ const CURRENT_VERSION = '0.5.49';
59
59
  /**
60
60
  * ┌──────────────────────────────────────────────────────────────────────┐
61
61
  * │ EMBEDDING PROVIDER — pluggable, client-side first │
@@ -425,6 +425,9 @@ const SEGMENT_RE = /([\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uac00
425
425
 
426
426
  // RTL Unicode ranges: Arabic (U+0600–U+06FF + extended), Hebrew (U+0590–U+05FF)
427
427
  const RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufb4f\ufb50-\ufdff\ufe70-\ufeff]/;
428
+ // Distinguish Arabic from Hebrew within RTL segments
429
+ const HEBREW_CHAR_RE = /[\u0590-\u05ff\ufb1d-\ufb4f]/;
430
+ const ARABIC_CHAR_RE = /[\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb50-\ufdff\ufe70-\ufeff]/;;
428
431
 
429
432
  // ── Katakana → Romaji conversion table (Hepburn system) ──────────────────────
430
433
  // Digraphs must be listed before single chars so they match first.
@@ -522,6 +525,33 @@ function arabicLightStem(word: string): string {
522
525
  return result;
523
526
  }
524
527
 
528
+ /**
529
+ * Hebrew light stemmer — strips the definite article and common prefix particles
530
+ * that attach directly to words (no space) in Hebrew.
531
+ *
532
+ * Handles:
533
+ * ה (ha-) — definite article: הפריסה → פריסה
534
+ * ו (ve-/u-) — conjunction “and”: ופריסה → פריסה
535
+ * ב (be-/bi-) — preposition “in”: בסביבה → סביבה
536
+ * ל (le-/li-) — preposition “for”: לשרת → שרת
537
+ * מ (mi-/me-) — preposition “from”: מהשרת → השרת → שרת
538
+ * כ (ke-/ki-) — preposition “like”: כשרת → שרת
539
+ *
540
+ * Iterative (max 3 passes): מהפריסה → הפריסה → פריסה.
541
+ * Only strips when result is still ≥ 3 chars.
542
+ */
543
+ function hebrewLightStem(word: string): string {
544
+ let result = word;
545
+ for (let i = 0; i < 3; i++) {
546
+ const prev = result;
547
+ if (result.length > 3 && 'הובלמכ'.includes(result[0]) && HEBREW_CHAR_RE.test(result[1])) {
548
+ result = result.slice(1);
549
+ }
550
+ if (result === prev) break;
551
+ }
552
+ return result;
553
+ }
554
+
525
555
  // ── Cross-lingual tech term dictionary ────────────────────────────────────────
526
556
  // Maps tokens (English, Japanese katakana, Chinese, Korean, Arabic, Hebrew)
527
557
  // to their equivalents in other languages. Used to expand tokens at search time
@@ -530,8 +560,8 @@ function arabicLightStem(word: string): string {
530
560
  // Keys must be lowercase. CJK values will be bigram-expanded at use time.
531
561
  const CROSS_LINGUAL_MAP = new Map<string, string[]>([
532
562
  // English → all
533
- ['deploy', ['デプロイ', '部署', '배포', 'deployment', 'deploying', 'نشر', 'bereitstellen', 'bereitstellung', 'déployer', 'déploiement', 'desplegar', 'despliegue', 'distribuire', 'distribuzione', 'implantar', 'implantação']],
534
- ['deployment', ['デプロイ', '部署', '배포', 'deploy', 'bereitstellung', 'déploiement', 'despliegue', 'distribuzione', 'implantação']],
563
+ ['deploy', ['デプロイ', '部署', '배포', 'deployment', 'deploying', 'نشر', 'פריסה', 'bereitstellen', 'bereitstellung', 'déployer', 'déploiement', 'desplegar', 'despliegue', 'distribuire', 'distribuzione', 'implantar', 'implantação']],
564
+ ['deployment', ['デプロイ', '部署', '배포', 'deploy', 'نشر', 'פריסה', 'bereitstellung', 'déploiement', 'despliegue', 'distribuzione', 'implantação']],
535
565
  ['container', ['コンテナ', '容器', '컨테이너', 'docker']],
536
566
  ['server', ['サーバー', 'サーバ', '服务器', '서버', 'سيرفر', 'שרת']],
537
567
  ['database', ['データベース', '数据库', '데이터베이스', 'db', 'قاعدة البيانات', 'datenbank', 'base de données', 'base de datos', 'banco de dados']],
@@ -543,6 +573,9 @@ const CROSS_LINGUAL_MAP = new Map<string, string[]>([
543
573
  ['test', ['テスト', '测试', '테스트', 'اختبار', 'בדיקה', 'testen', 'tester', 'probar', 'testare', 'testar']],
544
574
  ['auth', ['認証', '认证', '인증', 'authentication', 'login', 'oauth', 'مصادقة', 'אימות', 'authentifizierung', 'authentification', 'autenticación', 'autenticazione', 'autenticação']],
545
575
  ['authentication', ['auth', 'مصادقة', 'אימות', '認証', '认证', '인증', 'login', 'oauth']],
576
+ ['environment', ['環境', '环境', '환경', 'env', 'بيئة', 'סביבה', 'umgebung', 'environnement', 'entorno', 'ambiente']],
577
+ ['secret', ['key', 'token', 'password', 'jwt', 'مفتاح', 'מפתח', 'geheimnis', 'schlüssel', 'clave', 'clé', 'chiave', 'chave']],
578
+ ['problem', ['error', 'issue', 'bug', 'failure', 'مشكلة', 'بعيا', '問題', '問題', '오류', 'fehler', 'problème', 'problema']],
546
579
  ['kubernetes', ['クベルネテス', 'k8s', 'kube']],
547
580
  ['network', ['ネットワーク', '网络', '네트워크', 'شبكة', 'רשת', 'netzwerk', 'réseau', 'red', 'rete', 'rede']],
548
581
  ['timeout', ['タイムアウト', '超时', '타임아웃', 'مهلة']],
@@ -720,6 +753,13 @@ const CROSS_LINGUAL_MAP = new Map<string, string[]>([
720
753
  ['قرص', ['disk', 'storage']],
721
754
  ['خادم', ['server']],
722
755
  ['برنامج', ['application', 'software', 'app']],
756
+ // Arabic terms from blog examples
757
+ ['مشكلة', ['error', 'issue', 'bug', 'problem', 'failure']],
758
+ ['بيئة', ['environment', 'env']],
759
+ ['متغيرات', ['variables', 'env', 'environment']],
760
+ ['إنتاج', ['production', 'prod']],
761
+ ['سري', ['secret', 'key', 'token']],
762
+ ['مفتاح سري', ['secret', 'key']],
723
763
  // Hebrew → English
724
764
  ['שגיאה', ['error']],
725
765
  ['תיקון', ['fix']],
@@ -755,6 +795,13 @@ const CROSS_LINGUAL_MAP = new Map<string, string[]>([
755
795
  ['ממשק תכנות', ['api', 'interface']],
756
796
  ['ביצועים', ['performance']],
757
797
  ['אחסון', ['storage', 'disk']],
798
+ // Hebrew terms from blog examples
799
+ ['פריסה', ['deploy', 'deployment']],
800
+ ['בעיה', ['error', 'issue', 'bug', 'problem']],
801
+ ['סביבה', ['environment', 'env']],
802
+ ['ייצור', ['production', 'prod']],
803
+ ['סודי', ['secret', 'key', 'token']],
804
+ ['מפתח סודי', ['secret', 'key']],
758
805
 
759
806
  // ── European languages → English ─────────────────────────────────────────
760
807
  // German (Deutsch) → English
@@ -959,8 +1006,9 @@ function expandCrossLingual(token: string): string[] {
959
1006
  * Features:
960
1007
  * • CJK → character bigrams
961
1008
  * • Katakana → additionally emits Hepburn romaji tokens (enables romaji queries)
962
- * • Arabic/Hebrew → word tokenization with light Arabic stemming
963
- * • All scripts → cross-lingual synonym expansion (EN↔JA↔ZH↔KO↔AR↔HE)
1009
+ * • Arabic → word tokenization + iterative prefix stemming (al-, wa-, bi-, li-, fa-)
1010
+ * • Hebrew → word tokenization + iterative prefix stemming (ha-, ve-, be-, le-, mi-, ke-)
1011
+ * • All scripts → cross-lingual synonym expansion (EN↔JA↔ZH↔KO↔AR↔HE↔DE↔FR↔ES↔IT↔PT)
964
1012
  */
965
1013
  function tokenize(text: string): string[] {
966
1014
  const tokens: string[] = [];
@@ -998,11 +1046,14 @@ function tokenize(text: string): string[] {
998
1046
  }
999
1047
  }
1000
1048
  } else if (RTL_RE.test(seg)) {
1001
- // RTL segment (Arabic / Hebrew) — space-separated words with light stemming
1049
+ // RTL segment — split on spaces, apply language-specific prefix stemming
1050
+ // Arabic: strips ال/و/ب/ل/ف/ك prefixes iteratively
1051
+ // Hebrew: strips ה/ו/ב/ל/מ/כ prefixes iteratively
1052
+ const isHebrew = HEBREW_CHAR_RE.test(seg);
1002
1053
  const words = seg.trim().split(/\s+/);
1003
1054
  for (const w of words) {
1004
1055
  if (w.length <= 1 || STOPWORDS.has(w)) continue;
1005
- const stemmed = arabicLightStem(w);
1056
+ const stemmed = isHebrew ? hebrewLightStem(w) : arabicLightStem(w);
1006
1057
  if (stemmed.length > 1 && !STOPWORDS.has(stemmed)) tokens.push(stemmed);
1007
1058
  }
1008
1059
  } else {