@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/dist/index.d.ts CHANGED
@@ -33,8 +33,9 @@ declare function expandCrossLingual(token: string): string[];
33
33
  * Features:
34
34
  * • CJK → character bigrams
35
35
  * • Katakana → additionally emits Hepburn romaji tokens (enables romaji queries)
36
- * • Arabic/Hebrew → word tokenization with light Arabic stemming
37
- * • All scripts → cross-lingual synonym expansion (EN↔JA↔ZH↔KO↔AR↔HE)
36
+ * • Arabic → word tokenization + iterative prefix stemming (al-, wa-, bi-, li-, fa-)
37
+ * • Hebrew → word tokenization + iterative prefix stemming (ha-, ve-, be-, le-, mi-, ke-)
38
+ * • All scripts → cross-lingual synonym expansion (EN↔JA↔ZH↔KO↔AR↔HE↔DE↔FR↔ES↔IT↔PT)
38
39
  */
39
40
  declare function tokenize(text: string): string[];
40
41
  /**
package/dist/index.js CHANGED
@@ -47,7 +47,7 @@ import { Redis } from 'ioredis';
47
47
  const API_URL = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
48
48
  const JWT = process.env.CACHLY_JWT ?? '';
49
49
  const EMBED_MODEL = process.env.CACHLY_EMBED_MODEL ?? '';
50
- const CURRENT_VERSION = '0.5.48';
50
+ const CURRENT_VERSION = '0.5.49';
51
51
  /**
52
52
  * ┌──────────────────────────────────────────────────────────────────────┐
53
53
  * │ EMBEDDING PROVIDER — pluggable, client-side first │
@@ -409,6 +409,10 @@ const CJK_RE = /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uac00-\ud7
409
409
  const SEGMENT_RE = /([\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uac00-\ud7af]+|[^\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uac00-\ud7af]+)/g;
410
410
  // RTL Unicode ranges: Arabic (U+0600–U+06FF + extended), Hebrew (U+0590–U+05FF)
411
411
  const RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufb4f\ufb50-\ufdff\ufe70-\ufeff]/;
412
+ // Distinguish Arabic from Hebrew within RTL segments
413
+ const HEBREW_CHAR_RE = /[\u0590-\u05ff\ufb1d-\ufb4f]/;
414
+ const ARABIC_CHAR_RE = /[\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb50-\ufdff\ufe70-\ufeff]/;
415
+ ;
412
416
  // ── Katakana → Romaji conversion table (Hepburn system) ──────────────────────
413
417
  // Digraphs must be listed before single chars so they match first.
414
418
  const KATA_ROMAJI_TABLE = [
@@ -509,6 +513,33 @@ function arabicLightStem(word) {
509
513
  }
510
514
  return result;
511
515
  }
516
+ /**
517
+ * Hebrew light stemmer — strips the definite article and common prefix particles
518
+ * that attach directly to words (no space) in Hebrew.
519
+ *
520
+ * Handles:
521
+ * ה (ha-) — definite article: הפריסה → פריסה
522
+ * ו (ve-/u-) — conjunction “and”: ופריסה → פריסה
523
+ * ב (be-/bi-) — preposition “in”: בסביבה → סביבה
524
+ * ל (le-/li-) — preposition “for”: לשרת → שרת
525
+ * מ (mi-/me-) — preposition “from”: מהשרת → השרת → שרת
526
+ * כ (ke-/ki-) — preposition “like”: כשרת → שרת
527
+ *
528
+ * Iterative (max 3 passes): מהפריסה → הפריסה → פריסה.
529
+ * Only strips when result is still ≥ 3 chars.
530
+ */
531
+ function hebrewLightStem(word) {
532
+ let result = word;
533
+ for (let i = 0; i < 3; i++) {
534
+ const prev = result;
535
+ if (result.length > 3 && 'הובלמכ'.includes(result[0]) && HEBREW_CHAR_RE.test(result[1])) {
536
+ result = result.slice(1);
537
+ }
538
+ if (result === prev)
539
+ break;
540
+ }
541
+ return result;
542
+ }
512
543
  // ── Cross-lingual tech term dictionary ────────────────────────────────────────
513
544
  // Maps tokens (English, Japanese katakana, Chinese, Korean, Arabic, Hebrew)
514
545
  // to their equivalents in other languages. Used to expand tokens at search time
@@ -517,8 +548,8 @@ function arabicLightStem(word) {
517
548
  // Keys must be lowercase. CJK values will be bigram-expanded at use time.
518
549
  const CROSS_LINGUAL_MAP = new Map([
519
550
  // English → all
520
- ['deploy', ['デプロイ', '部署', '배포', 'deployment', 'deploying', 'نشر', 'bereitstellen', 'bereitstellung', 'déployer', 'déploiement', 'desplegar', 'despliegue', 'distribuire', 'distribuzione', 'implantar', 'implantação']],
521
- ['deployment', ['デプロイ', '部署', '배포', 'deploy', 'bereitstellung', 'déploiement', 'despliegue', 'distribuzione', 'implantação']],
551
+ ['deploy', ['デプロイ', '部署', '배포', 'deployment', 'deploying', 'نشر', 'פריסה', 'bereitstellen', 'bereitstellung', 'déployer', 'déploiement', 'desplegar', 'despliegue', 'distribuire', 'distribuzione', 'implantar', 'implantação']],
552
+ ['deployment', ['デプロイ', '部署', '배포', 'deploy', 'نشر', 'פריסה', 'bereitstellung', 'déploiement', 'despliegue', 'distribuzione', 'implantação']],
522
553
  ['container', ['コンテナ', '容器', '컨테이너', 'docker']],
523
554
  ['server', ['サーバー', 'サーバ', '服务器', '서버', 'سيرفر', 'שרת']],
524
555
  ['database', ['データベース', '数据库', '데이터베이스', 'db', 'قاعدة البيانات', 'datenbank', 'base de données', 'base de datos', 'banco de dados']],
@@ -530,6 +561,9 @@ const CROSS_LINGUAL_MAP = new Map([
530
561
  ['test', ['テスト', '测试', '테스트', 'اختبار', 'בדיקה', 'testen', 'tester', 'probar', 'testare', 'testar']],
531
562
  ['auth', ['認証', '认证', '인증', 'authentication', 'login', 'oauth', 'مصادقة', 'אימות', 'authentifizierung', 'authentification', 'autenticación', 'autenticazione', 'autenticação']],
532
563
  ['authentication', ['auth', 'مصادقة', 'אימות', '認証', '认证', '인증', 'login', 'oauth']],
564
+ ['environment', ['環境', '环境', '환경', 'env', 'بيئة', 'סביבה', 'umgebung', 'environnement', 'entorno', 'ambiente']],
565
+ ['secret', ['key', 'token', 'password', 'jwt', 'مفتاح', 'מפתח', 'geheimnis', 'schlüssel', 'clave', 'clé', 'chiave', 'chave']],
566
+ ['problem', ['error', 'issue', 'bug', 'failure', 'مشكلة', 'بعيا', '問題', '問題', '오류', 'fehler', 'problème', 'problema']],
533
567
  ['kubernetes', ['クベルネテス', 'k8s', 'kube']],
534
568
  ['network', ['ネットワーク', '网络', '네트워크', 'شبكة', 'רשת', 'netzwerk', 'réseau', 'red', 'rete', 'rede']],
535
569
  ['timeout', ['タイムアウト', '超时', '타임아웃', 'مهلة']],
@@ -707,6 +741,13 @@ const CROSS_LINGUAL_MAP = new Map([
707
741
  ['قرص', ['disk', 'storage']],
708
742
  ['خادم', ['server']],
709
743
  ['برنامج', ['application', 'software', 'app']],
744
+ // Arabic terms from blog examples
745
+ ['مشكلة', ['error', 'issue', 'bug', 'problem', 'failure']],
746
+ ['بيئة', ['environment', 'env']],
747
+ ['متغيرات', ['variables', 'env', 'environment']],
748
+ ['إنتاج', ['production', 'prod']],
749
+ ['سري', ['secret', 'key', 'token']],
750
+ ['مفتاح سري', ['secret', 'key']],
710
751
  // Hebrew → English
711
752
  ['שגיאה', ['error']],
712
753
  ['תיקון', ['fix']],
@@ -742,6 +783,13 @@ const CROSS_LINGUAL_MAP = new Map([
742
783
  ['ממשק תכנות', ['api', 'interface']],
743
784
  ['ביצועים', ['performance']],
744
785
  ['אחסון', ['storage', 'disk']],
786
+ // Hebrew terms from blog examples
787
+ ['פריסה', ['deploy', 'deployment']],
788
+ ['בעיה', ['error', 'issue', 'bug', 'problem']],
789
+ ['סביבה', ['environment', 'env']],
790
+ ['ייצור', ['production', 'prod']],
791
+ ['סודי', ['secret', 'key', 'token']],
792
+ ['מפתח סודי', ['secret', 'key']],
745
793
  // ── European languages → English ─────────────────────────────────────────
746
794
  // German (Deutsch) → English
747
795
  ['fehler', ['error', 'err']],
@@ -939,8 +987,9 @@ function expandCrossLingual(token) {
939
987
  * Features:
940
988
  * • CJK → character bigrams
941
989
  * • Katakana → additionally emits Hepburn romaji tokens (enables romaji queries)
942
- * • Arabic/Hebrew → word tokenization with light Arabic stemming
943
- * • All scripts → cross-lingual synonym expansion (EN↔JA↔ZH↔KO↔AR↔HE)
990
+ * • Arabic → word tokenization + iterative prefix stemming (al-, wa-, bi-, li-, fa-)
991
+ * • Hebrew → word tokenization + iterative prefix stemming (ha-, ve-, be-, le-, mi-, ke-)
992
+ * • All scripts → cross-lingual synonym expansion (EN↔JA↔ZH↔KO↔AR↔HE↔DE↔FR↔ES↔IT↔PT)
944
993
  */
945
994
  function tokenize(text) {
946
995
  const tokens = [];
@@ -979,12 +1028,15 @@ function tokenize(text) {
979
1028
  }
980
1029
  }
981
1030
  else if (RTL_RE.test(seg)) {
982
- // RTL segment (Arabic / Hebrew) — space-separated words with light stemming
1031
+ // RTL segment — split on spaces, apply language-specific prefix stemming
1032
+ // Arabic: strips ال/و/ب/ل/ف/ك prefixes iteratively
1033
+ // Hebrew: strips ה/ו/ב/ל/מ/כ prefixes iteratively
1034
+ const isHebrew = HEBREW_CHAR_RE.test(seg);
983
1035
  const words = seg.trim().split(/\s+/);
984
1036
  for (const w of words) {
985
1037
  if (w.length <= 1 || STOPWORDS.has(w))
986
1038
  continue;
987
- const stemmed = arabicLightStem(w);
1039
+ const stemmed = isHebrew ? hebrewLightStem(w) : arabicLightStem(w);
988
1040
  if (stemmed.length > 1 && !STOPWORDS.has(stemmed))
989
1041
  tokens.push(stemmed);
990
1042
  }