@cachly-dev/mcp-server 0.5.50 → 0.5.60

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.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.50';
50
+ const CURRENT_VERSION = '0.5.60';
51
51
  /**
52
52
  * ┌──────────────────────────────────────────────────────────────────────┐
53
53
  * │ EMBEDDING PROVIDER — pluggable, client-side first │
@@ -420,6 +420,23 @@ const STOPWORDS = new Set([
420
420
  'मैं', 'तुम', 'उन', 'इन', 'ने', 'बहुत', 'सब', 'कुछ', 'फिर',
421
421
  'अब', 'तो', 'ही', 'तरह', 'जैसे', 'करना', 'होना', 'रहा', 'रही',
422
422
  'रहे', 'गया', 'गई', 'गए', 'किया', 'किए', 'कर', 'हुआ', 'हुई',
423
+ // ── Russian — common prepositions, pronouns, auxiliary verbs ───────────────
424
+ 'в', 'на', 'не', 'с', 'и', 'а', 'но', 'по', 'за', 'из', 'от', 'до',
425
+ 'к', 'у', 'о', 'об', 'во', 'при', 'под', 'над', 'без', 'для',
426
+ 'что', 'как', 'это', 'все', 'так', 'уже', 'или', 'же', 'ли',
427
+ 'если', 'то', 'да', 'нет', 'был', 'была', 'были', 'быть', 'есть',
428
+ 'его', 'её', 'их', 'этот', 'эта', 'эти', 'который', 'которая',
429
+ 'которые', 'который', 'мой', 'моя', 'мои', 'ваш', 'ваша', 'ваши',
430
+ 'он', 'она', 'они', 'мы', 'вы', 'я', 'ты', 'тот', 'та', 'те',
431
+ 'очень', 'тоже', 'также', 'когда', 'где', 'как', 'почему', 'зачем',
432
+ 'сейчас', 'здесь', 'там', 'потому', 'поэтому', 'чтобы', 'тут',
433
+ // ── Turkish — common particles, postpositions, copulas ─────────────────────
434
+ 'bir', 'bu', 'şu', 'o', 've', 'ile', 'de', 'da', 'için', 'gibi',
435
+ 'ama', 'fakat', 'ancak', 'ya', 'ne', 'ki', 'mi', 'mı', 'mu', 'mü',
436
+ 'var', 'yok', 'olan', 'oldu', 'olur', 'olarak', 'ise', 'daha',
437
+ 'çok', 'en', 'her', 'hiç', 'bazı', 'tüm', 'bütün', 'hem', 'veya',
438
+ 'bu', 'şu', 'ben', 'sen', 'biz', 'siz', 'onlar', 'benim', 'senin',
439
+ 'nasıl', 'neden', 'niçin', 'nerede', 'ne', 'hangi', 'kaç',
423
440
  ]);
424
441
  // CJK Unicode ranges used for bigram extraction
425
442
  const CJK_RE = /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uac00-\ud7af]/;
@@ -433,6 +450,10 @@ const ARABIC_CHAR_RE = /[\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb50-\ufdff\uf
433
450
  const FARSI_CHAR_RE = /[\u067e\u0686\u0698\u06af\u06cc]/;
434
451
  // Devanagari (Hindi, Sanskrit, Marathi …) — U+0900–U+097F + extended
435
452
  const DEVANAGARI_RE = /[\u0900-\u097f]/;
453
+ // Cyrillic (Russian, Bulgarian, Ukrainian, Serbian …) — U+0400–U+04FF
454
+ const CYRILLIC_RE = /[\u0400-\u04ff]/;
455
+ // Turkish uses Latin alphabet but has unique chars (ğ, ş, ı, ö, ü, ç) — detected by these
456
+ const TURKISH_CHAR_RE = /[\u011f\u015f\u0131\u0130\u00e7]/; // ğ ş ı İ ç
436
457
  // ── Katakana → Romaji conversion table (Hepburn system) ──────────────────────
437
458
  // Digraphs must be listed before single chars so they match first.
438
459
  const KATA_ROMAJI_TABLE = [
@@ -610,9 +631,155 @@ function hindiLightStem(word) {
610
631
  if (result.endsWith('\u0907\u092f\u093e\u0902') && result.length > 5)
611
632
  return result.slice(0, -4); // \u0907\u092f\u093e\u0902 (iyāṃ) f plural
612
633
  if (result.endsWith('\u0940\u092f\u093e\u0902') && result.length > 5)
613
- return result.slice(0, -4); // \u0940\u092f\u093e\u0902 (īyāṃ)
634
+ return result.slice(0, -4); // \u0940\u092f\u093e\u0102 (īyāṃ)
614
635
  return result;
615
636
  }
637
+ /**
638
+ * Russian light stemmer — strips common inflectional suffixes.
639
+ * Handles the most frequent verb/noun/adjective endings to improve recall.
640
+ * Only strips when result is still ≥ 3 chars.
641
+ *
642
+ * Covers:
643
+ * ошибки/ошибка → ошибк (noun plural/genitive)
644
+ * развёртывание → развёртыва (gerund → stem)
645
+ * установить → установ (infinitive)
646
+ * настройки → настройк (genitive plural)
647
+ */
648
+ function russianLightStem(word) {
649
+ let w = word;
650
+ // Longest first to avoid partial stripping
651
+ // Verb infinitives / gerunds
652
+ if (w.endsWith('ывание') && w.length > 7)
653
+ return w.slice(0, -6);
654
+ if (w.endsWith('ивание') && w.length > 7)
655
+ return w.slice(0, -6);
656
+ if (w.endsWith('ование') && w.length > 7)
657
+ return w.slice(0, -6);
658
+ if (w.endsWith('вание') && w.length > 6)
659
+ return w.slice(0, -5);
660
+ if (w.endsWith('ение') && w.length > 5)
661
+ return w.slice(0, -4);
662
+ if (w.endsWith('ание') && w.length > 5)
663
+ return w.slice(0, -4);
664
+ if (w.endsWith('ить') && w.length > 4)
665
+ return w.slice(0, -3);
666
+ if (w.endsWith('ать') && w.length > 4)
667
+ return w.slice(0, -3);
668
+ if (w.endsWith('еть') && w.length > 4)
669
+ return w.slice(0, -3);
670
+ if (w.endsWith('уть') && w.length > 4)
671
+ return w.slice(0, -3);
672
+ // Noun plural/genitive
673
+ if (w.endsWith('ки') && w.length > 4)
674
+ return w.slice(0, -2);
675
+ if (w.endsWith('ги') && w.length > 4)
676
+ return w.slice(0, -2);
677
+ if (w.endsWith('ов') && w.length > 4)
678
+ return w.slice(0, -2);
679
+ if (w.endsWith('ев') && w.length > 4)
680
+ return w.slice(0, -2);
681
+ if (w.endsWith('ей') && w.length > 4)
682
+ return w.slice(0, -2);
683
+ if (w.endsWith('ий') && w.length > 4)
684
+ return w.slice(0, -2);
685
+ if (w.endsWith('ый') && w.length > 4)
686
+ return w.slice(0, -2);
687
+ if (w.endsWith('ая') && w.length > 4)
688
+ return w.slice(0, -2);
689
+ if (w.endsWith('ое') && w.length > 4)
690
+ return w.slice(0, -2);
691
+ if (w.endsWith('ую') && w.length > 4)
692
+ return w.slice(0, -2);
693
+ // Verb present tense
694
+ if (w.endsWith('ет') && w.length > 4)
695
+ return w.slice(0, -2);
696
+ if (w.endsWith('ют') && w.length > 4)
697
+ return w.slice(0, -2);
698
+ if (w.endsWith('ут') && w.length > 4)
699
+ return w.slice(0, -2);
700
+ if (w.endsWith('ят') && w.length > 4)
701
+ return w.slice(0, -2);
702
+ return w;
703
+ }
704
+ /**
705
+ * Turkish light stemmer — strips common agglutinative suffixes.
706
+ * Turkish is highly agglutinative; this covers the most common inflectional ends.
707
+ *
708
+ * Covers:
709
+ * hatalar → hata (-lar/-ler plural)
710
+ * sunucuda → sunucu (-da/-de locative)
711
+ * dağıtımı → dağıtım (-ı/-i/-u/-ü accusative)
712
+ * yüklemek → yükle (-mek/-mak infinitive)
713
+ */
714
+ function turkishLightStem(word) {
715
+ let w = word;
716
+ // Suffixes longest-first
717
+ if (w.endsWith('lardaki') && w.length > 7)
718
+ return w.slice(0, -7);
719
+ if (w.endsWith('lerdeki') && w.length > 7)
720
+ return w.slice(0, -7);
721
+ if (w.endsWith('ların') && w.length > 6)
722
+ return w.slice(0, -5);
723
+ if (w.endsWith('lerin') && w.length > 6)
724
+ return w.slice(0, -5);
725
+ if (w.endsWith('larda') && w.length > 6)
726
+ return w.slice(0, -5);
727
+ if (w.endsWith('lerde') && w.length > 6)
728
+ return w.slice(0, -5);
729
+ if (w.endsWith('ları') && w.length > 5)
730
+ return w.slice(0, -4);
731
+ if (w.endsWith('leri') && w.length > 5)
732
+ return w.slice(0, -4);
733
+ if (w.endsWith('mek') && w.length > 4)
734
+ return w.slice(0, -3);
735
+ if (w.endsWith('mak') && w.length > 4)
736
+ return w.slice(0, -3);
737
+ if (w.endsWith('ler') && w.length > 4)
738
+ return w.slice(0, -3);
739
+ if (w.endsWith('lar') && w.length > 4)
740
+ return w.slice(0, -3);
741
+ if (w.endsWith('nın') && w.length > 4)
742
+ return w.slice(0, -3);
743
+ if (w.endsWith('nin') && w.length > 4)
744
+ return w.slice(0, -3);
745
+ if (w.endsWith('nun') && w.length > 4)
746
+ return w.slice(0, -3);
747
+ if (w.endsWith('nün') && w.length > 4)
748
+ return w.slice(0, -3);
749
+ if (w.endsWith('da') && w.length > 4)
750
+ return w.slice(0, -2);
751
+ if (w.endsWith('de') && w.length > 4)
752
+ return w.slice(0, -2);
753
+ if (w.endsWith('ta') && w.length > 4)
754
+ return w.slice(0, -2);
755
+ if (w.endsWith('te') && w.length > 4)
756
+ return w.slice(0, -2);
757
+ if (w.endsWith('dan') && w.length > 4)
758
+ return w.slice(0, -3);
759
+ if (w.endsWith('den') && w.length > 4)
760
+ return w.slice(0, -3);
761
+ if (w.endsWith('tan') && w.length > 4)
762
+ return w.slice(0, -3);
763
+ if (w.endsWith('ten') && w.length > 4)
764
+ return w.slice(0, -3);
765
+ if (w.endsWith('ın') && w.length > 4)
766
+ return w.slice(0, -2);
767
+ if (w.endsWith('in') && w.length > 4)
768
+ return w.slice(0, -2);
769
+ if (w.endsWith('un') && w.length > 4)
770
+ return w.slice(0, -2);
771
+ if (w.endsWith('ün') && w.length > 4)
772
+ return w.slice(0, -2);
773
+ if (w.endsWith('ı') && w.length > 4)
774
+ return w.slice(0, -1);
775
+ if (w.endsWith('i') && w.length > 4)
776
+ return w.slice(0, -1);
777
+ if (w.endsWith('u') && w.length > 4)
778
+ return w.slice(0, -1);
779
+ if (w.endsWith('ü') && w.length > 4)
780
+ return w.slice(0, -1);
781
+ return w;
782
+ }
616
783
  // Maps tokens (English, Japanese katakana, Chinese, Korean, Arabic, Hebrew)
617
784
  // to their equivalents in other languages. Used to expand tokens at search time
618
785
  // so "deploy" finds documents containing "デプロイ" / "部署" / "배포", and vice versa.
@@ -620,45 +787,45 @@ function hindiLightStem(word) {
620
787
  // Keys must be lowercase. CJK values will be bigram-expanded at use time.
621
788
  const CROSS_LINGUAL_MAP = new Map([
622
789
  // English → all
623
- ['deploy', ['デプロイ', '部署', '배포', 'deployment', 'deploying', 'نشر', 'פריסה', 'استقرار', 'तैनाती', 'bereitstellen', 'bereitstellung', 'déployer', 'déploiement', 'desplegar', 'despliegue', 'distribuire', 'distribuzione', 'implantar', 'implantação']],
624
- ['deployment', ['デプロイ', '部署', '배포', 'deploy', 'نشر', 'פריסה', 'استقرار', 'तैनाती', 'bereitstellung', 'déploiement', 'despliegue', 'distribuzione', 'implantação']],
790
+ ['deploy', ['デプロイ', '部署', '배포', 'deployment', 'deploying', 'نشر', 'פריסה', 'استقرار', 'तैनाती', 'bereitstellen', 'bereitstellung', 'déployer', 'déploiement', 'desplegar', 'despliegue', 'distribuire', 'distribuzione', 'implantar', 'implantação', 'развёртывание', 'развертывание', 'dağıtım', 'dağıtmak']],
791
+ ['deployment', ['デプロイ', '部署', '배포', 'deploy', 'نشر', 'פריסה', 'استقرار', 'तैनाती', 'bereitstellung', 'déploiement', 'despliegue', 'distribuzione', 'implantação', 'развёртывание', 'dağıtım']],
625
792
  ['container', ['コンテナ', '容器', '컨테이너', 'docker']],
626
- ['server', ['サーバー', 'サーバ', '服务器', '서버', 'سيرفر', 'שרת', 'سرور', 'सर्वर']],
793
+ ['server', ['サーバー', 'サーバ', '服务器', '서버', 'سيرفر', 'שרת', 'سرور', 'सर्वर', 'сервер', 'sunucu']],
627
794
  ['database', ['データベース', '数据库', '데이터베이스', 'db', 'قاعدة البيانات', 'datenbank', 'پایگاه داده', 'डेटाबेس', 'base de données', 'base de datos', 'banco de dados']],
628
- ['cache', ['キャッシュ', '缓存', '캐시', 'كاش', 'מטמון', 'کش', 'कैश']],
629
- ['error', ['エラー', '错误', '오류', 'خطأ', 'شגיאה', 'خطا', 'त्रुटि', 'गलती', 'exception', 'err', 'fehler', 'erreur', 'fallo', 'errore', 'erro']],
795
+ ['cache', ['キャッシュ', '缓存', '캐시', 'كاش', 'מטמון', 'کش', 'कैश', 'кэш', 'önbellek']],
796
+ ['error', ['エラー', '错误', '오류', 'خطأ', 'שגיאה', 'خطا', 'त्रुटि', 'गलती', 'exception', 'err', 'fehler', 'erreur', 'fallo', 'errore', 'erro', 'ошибка', 'hata', 'hatalı']],
630
797
  ['bug', ['バグ', '缺陷', '버그', 'issue', 'defect', 'خلل', 'اشکال', 'बग']],
631
- ['fix', ['修正', '修复', '수정', 'bugfix', 'patch', 'hotfix', 'إصلاح', 'תיקון', 'رفع', 'सुधार', 'beheben', 'behoben', 'réparer', 'correction', 'arreglar', 'corrección', 'correggere', 'corrigir']],
632
- ['build', ['ビルド', '构建', '빌드', 'بناء', 'בנייה', 'ساخت', 'निर्माण', 'bauen', 'construire', 'construir', 'costruire']],
633
- ['test', ['テスト', '测试', '테스트', 'اختبار', 'בדיקה', 'آزمایش', 'परीक्षण', 'testen', 'tester', 'probar', 'testare', 'testar']],
634
- ['auth', ['認証', '认证', '인증', 'authentication', 'login', 'oauth', 'مصادقة', 'אימות', 'احراز هویت', 'प्रमाणीकरण', 'authentifizierung', 'authentification', 'autenticación', 'autenticazione', 'autenticação']],
635
- ['authentication', ['auth', 'مصادقة', 'אימות', 'احراز هویت', 'प्रमाणीकरण', '認証', '认证', '인증', 'login', 'oauth']],
636
- ['environment', ['環境', '环境', '환경', 'env', 'بيئة', 'סביבה', 'umgebung', 'environnement', 'entorno', 'ambiente']],
637
- ['secret', ['key', 'token', 'password', 'jwt', 'مفتاح', 'מפתח', 'geheimnis', 'schlüssel', 'clave', 'clé', 'chiave', 'chave']],
638
- ['problem', ['error', 'issue', 'bug', 'failure', 'مشكلة', 'بعيا', '問題', '問題', '오류', 'fehler', 'problème', 'problema']],
798
+ ['fix', ['修正', '修复', '수정', 'bugfix', 'patch', 'hotfix', 'إصلاح', 'תיקון', 'رفع', 'सुधार', 'beheben', 'behoben', 'réparer', 'correction', 'arreglar', 'corrección', 'correggere', 'corrigir', 'исправить', 'исправление', 'düzeltmek', 'düzeltme']],
799
+ ['build', ['ビルド', '构建', '빌드', 'بناء', 'בנייה', 'ساخت', 'निर्माण', 'bauen', 'construire', 'construir', 'costruire', 'сборка', 'собрать', 'derleme', 'derlemek']],
800
+ ['test', ['テスト', '测试', '테스트', 'اختبار', 'בדיקה', 'آزمایش', 'परीक्षण', 'testen', 'tester', 'probar', 'testare', 'testar', 'тест', 'тестирование', 'test', 'testlemek']],
801
+ ['auth', ['認証', '认证', '인증', 'authentication', 'login', 'oauth', 'مصادقة', 'אימות', 'احراز هویت', 'प्रमाणीकरण', 'authentifizierung', 'authentification', 'autenticación', 'autenticazione', 'autenticação', 'аутентификация', 'авторизация', 'kimlik doğrulama', 'yetkilendirme']],
802
+ ['authentication', ['auth', 'مصادقة', 'אימות', 'احراز هویت', 'प्रमाणीकरण', '認証', '认证', '인증', 'login', 'oauth', 'аутентификация', 'авторизация', 'kimlik doğrulama']],
803
+ ['environment', ['環境', '环境', '환경', 'env', 'بيئة', 'סביבה', 'umgebung', 'environnement', 'entorno', 'ambiente', 'среда', 'окружение', 'ortam']],
804
+ ['secret', ['key', 'token', 'password', 'jwt', 'مفتاح', 'מפתח', 'geheimnis', 'schlüssel', 'clave', 'clé', 'chiave', 'chave', 'секрет', 'ключ', 'gizli', 'anahtar']],
805
+ ['problem', ['error', 'issue', 'bug', 'failure', 'مشكلة', '問題', '오류', 'fehler', 'problème', 'problema', 'проблема', 'ошибка', 'sorun', 'problem']],
639
806
  ['kubernetes', ['クベルネテス', 'k8s', 'kube']],
640
- ['network', ['ネットワーク', '网络', '네트워크', 'شبكة', 'רשת', 'شبکه', 'नेटवर्क', 'netzwerk', 'réseau', 'red', 'rete', 'rede']],
807
+ ['network', ['ネットワーク', '网络', '네트워크', 'شبكة', 'רשת', 'شبکه', 'नेटवर्क', 'netzwerk', 'réseau', 'red', 'rete', 'rede', 'сеть', 'ağ']],
641
808
  ['timeout', ['タイムアウト', '超时', '타임아웃', 'مهلة']],
642
- ['memory', ['メモリ', '内存', '메모리', 'ram', 'ذاكرة', 'זיכרון', 'حافظه', 'मेमोरी', 'speicher', 'mémoire', 'memoria', 'memória']],
643
- ['config', ['設定', '配置', '설정', 'configuration', 'settings', 'conf', 'إعدادات', 'הגדרות', 'پیکربندی', 'कॉन्फ़िग', 'konfiguration', 'einstellungen', 'configuración', 'configurazione', 'configuração']],
644
- ['install', ['インストール', '安装', '설치', 'تثبيت', 'התקנה', 'نصب', 'इंस्टॉल', 'installieren', 'installer', 'instalar', 'installare']],
645
- ['update', ['アップデート', '更新', '업데이트', 'upgrade', 'تحديث', 'עדכון', 'به‌روزرسانی', 'अपडेट', 'aktualisieren', 'aktualisierung', 'actualizar', 'aggiornare', 'atualizar']],
646
- ['log', ['ログ', '日志', '로그', 'logging', 'سجل', 'יומן', 'ثبت', 'लॉग', 'protokoll', 'protokolle', 'journal', 'registro']],
809
+ ['memory', ['メモリ', '内存', '메모리', 'ram', 'ذاكرة', 'זיכרון', 'حافظه', 'मेमोरी', 'speicher', 'mémoire', 'memoria', 'memória', 'память', 'bellek', 'hafıza']],
810
+ ['config', ['設定', '配置', '설정', 'configuration', 'settings', 'conf', 'إعدادات', 'הגדרות', 'پیکربندی', 'कॉन्फ़िग', 'konfiguration', 'einstellungen', 'configuración', 'configurazione', 'configuração', 'конфигурация', 'настройки', 'yapılandırma', 'ayarlar']],
811
+ ['install', ['インストール', '安装', '설치', 'تثبيت', 'התקנה', 'نصب', 'इंस्टॉल', 'installieren', 'installer', 'instalar', 'installare', 'установить', 'установка', 'yüklemek', 'kurulum']],
812
+ ['update', ['アップデート', '更新', '업데이트', 'upgrade', 'تحديث', 'עדכון', 'به‌روزرسانی', 'अपडेट', 'aktualisieren', 'aktualisierung', 'actualizar', 'aggiornare', 'atualizar', 'обновление', 'обновить', 'güncelleme']],
813
+ ['log', ['ログ', '日志', '로그', 'logging', 'سجل', 'יומן', 'ثبت', 'लॉग', 'protokoll', 'protokolle', 'journal', 'registro', 'журнал', 'лог', 'günlük']],
647
814
  ['port', ['ポート', '端口', '포트', 'منفذ', 'פורט']],
648
815
  ['file', ['ファイル', '文件', '파일', 'ملف', 'קובץ']],
649
816
  ['image', ['イメージ', '镜像', '이미지', 'صورة', 'תמונה', 'abbild', 'imagen', 'imagem']],
650
817
  ['volume', ['ボリューム', '卷', '볼륨']],
651
818
  ['cluster', ['クラスター', '集群', '클러스터']],
652
819
  ['node', ['ノード', '节点', '노드']],
653
- ['service', ['サービス', '服务', '서비스', 'svc', 'خدمة', 'שירות', 'dienst', 'servicio', 'servizio', 'serviço']],
820
+ ['service', ['サービス', '服务', '서비스', 'svc', 'خدمة', 'שירות', 'dienst', 'servicio', 'servizio', 'serviço', 'сервис', 'служба', 'servis', 'hizmet']],
654
821
  ['certificate', ['証明書', '证书', '인증서', 'cert', 'ssl', 'tls', 'شهادة', 'תעודה']],
655
822
  ['password', ['パスワード', '密码', '비밀번호', 'passwd', 'pwd', 'كلمة المرور', 'סיסמה']],
656
823
  ['token', ['トークン', '令牌', '토큰', 'jwt', 'secret', 'رمز', 'אסימון']],
657
824
  ['health', ['ヘルス', '健康', '헬스', 'healthcheck', 'probe', 'صحة']],
658
825
  ['migration', ['マイグレーション', '迁移', '마이그레이션', 'migrate', 'هجرة']],
659
- ['backup', ['バックアップ', '备份', '백업', 'نسخ احتياطي', 'גיבוי', 'پشتیبان‌گیری', 'बैकअप', 'sicherung', 'sauvegarde', 'respaldo', 'cópia de segurança']],
660
- ['monitor', ['モニター', '监控', '모니터링', 'monitoring', 'metrics', 'alert', 'مراقبة', 'ניטור', 'نظارت', 'निगरानी', 'überwachung', 'surveiller', 'monitoreo', 'monitorare', 'monitorar']],
661
- ['performance', ['パフォーマンス', '性能', '성능', 'latency', 'throughput', 'عملکرد', 'प्रदर्शन', 'leistung', 'performances', 'rendimiento', 'prestazioni', 'desempenho']],
826
+ ['backup', ['バックアップ', '备份', '백업', 'نسخ احتياطي', 'גיבוי', 'پشتیبان‌گیری', 'बैकअप', 'sicherung', 'sauvegarde', 'respaldo', 'cópia de segurança', 'резервная копия', 'резерв', 'yedekleme']],
827
+ ['monitor', ['モニター', '监控', '모니터링', 'monitoring', 'metrics', 'alert', 'مراقبة', 'ניטור', 'نظارت', 'निगरानी', 'überwachung', 'surveiller', 'monitoreo', 'monitorare', 'monitorar', 'мониторинг', 'наблюдение', 'izleme']],
828
+ ['performance', ['パフォーマンス', '性能', '성능', 'latency', 'throughput', 'عملکرد', 'प्रदर्शन', 'leistung', 'performances', 'rendimiento', 'prestazioni', 'desempenho', 'производительность', 'быстродействие', 'performans']],
662
829
  ['connection', ['接続', '连接', '연결', 'conn', 'socket', 'اتصال', 'חיבור', 'اتصال', 'कनेक्शन', 'verbindung', 'connexion', 'conexión', 'connessione', 'conexão']],
663
830
  ['queue', ['キュー', '队列', '큐', 'طابور', 'warteschlange', 'file d\'attente', 'cola', 'coda', 'fila']],
664
831
  ['redis', ['レディス', '레디스']],
@@ -673,10 +840,10 @@ const CROSS_LINGUAL_MAP = new Map([
673
840
  ['git', ['version control', 'repo', 'repository', 'commit', 'push', 'pull']],
674
841
  ['ci', ['pipeline', 'github actions', 'gitlab', 'jenkins', 'build']],
675
842
  ['cd', ['deploy', 'deployment', 'release']],
676
- ['debug', ['デバッグ', '调试', '디버그', 'debugging', 'breakpoint', 'تصحيح', 'איתור באגים', 'اشکال‌زدایی', 'डीबग', 'debuggen', 'déboguer', 'depurar']],
677
- ['crash', ['クラッシュ', '崩溃', '크래시', 'panic', 'segfault', 'انهيار', 'קריסה', 'خرابی', 'क्रैश', 'absturz', 'panne', 'plantage', 'caída', 'falha']],
843
+ ['debug', ['デバッグ', '调试', '디버그', 'debugging', 'breakpoint', 'تصحيح', 'איתור באגים', 'اشکال‌زدایی', 'डीबग', 'debuggen', 'déboguer', 'depurar', 'отладка', 'дебаг', 'hata ayıklama']],
844
+ ['crash', ['クラッシュ', '崩溃', '크래시', 'panic', 'segfault', 'انهيار', 'קריסה', 'خرابی', 'क्रैश', 'absturz', 'panne', 'plantage', 'caída', 'falha', 'сбой', 'крэш', 'çökme', 'çöküş']],
678
845
  ['restart', ['再起動', '重启', '재시작', 'reboot', 'إعادة تشغيل', 'הפעלה מחדש', 'راه‌اندازی مجدد', 'पुنरारंभ', 'neustart', 'redémarrer', 'reiniciar', 'riavviare']],
679
- ['permission', ['権限', '权限', '권한', 'access', 'acl', 'chmod', 'صلاحية', 'הרשאה', 'مجوز', 'अनुमति', 'berechtigung', 'berechtigungen', 'permiso', 'permesso', 'permissão']],
846
+ ['permission', ['権限', '权限', '권한', 'access', 'acl', 'chmod', 'صلاحية', 'הרשאה', 'مجوز', 'अनुमति', 'berechtigung', 'berechtigungen', 'permiso', 'permesso', 'permissão', 'разрешение', 'доступ', 'izin', 'yetki']],
680
847
  // Japanese katakana → English
681
848
  ['デプロイ', ['deploy', 'deployment']],
682
849
  ['コンテナ', ['container', 'docker']],
@@ -1104,6 +1271,103 @@ const CROSS_LINGUAL_MAP = new Map([
1104
1271
  ['ambiente', ['environment', 'env']],
1105
1272
  ['fila', ['queue']],
1106
1273
  ['imagem', ['image']],
1274
+ // ── Russian (Cyrillic) → English ──────────────────────────────────────────
1275
+ ['ошибка', ['error', 'err']],
1276
+ ['ошибки', ['error', 'err']],
1277
+ ['сбой', ['crash', 'failure', 'error']],
1278
+ ['крэш', ['crash', 'panic']],
1279
+ ['баг', ['bug', 'issue']],
1280
+ ['исправить', ['fix', 'bugfix']],
1281
+ ['исправление', ['fix', 'patch']],
1282
+ ['сборка', ['build']],
1283
+ ['собрать', ['build']],
1284
+ ['тест', ['test']],
1285
+ ['тестирование', ['test', 'testing']],
1286
+ ['аутентификация', ['auth', 'authentication']],
1287
+ ['авторизация', ['auth', 'authorization']],
1288
+ ['конфигурация', ['config', 'configuration']],
1289
+ ['настройки', ['config', 'settings']],
1290
+ ['установить', ['install']],
1291
+ ['установка', ['install']],
1292
+ ['обновление', ['update', 'upgrade']],
1293
+ ['обновить', ['update', 'upgrade']],
1294
+ ['журнал', ['log']],
1295
+ ['лог', ['log', 'logs']],
1296
+ ['сервер', ['server']],
1297
+ ['база данных', ['database', 'db']],
1298
+ ['сеть', ['network']],
1299
+ ['память', ['memory', 'ram']],
1300
+ ['кэш', ['cache']],
1301
+ ['отладка', ['debug', 'debugging']],
1302
+ ['перезапуск', ['restart', 'reboot']],
1303
+ ['перезагрузка', ['restart', 'reboot']],
1304
+ ['разрешение', ['permission', 'access']],
1305
+ ['доступ', ['permission', 'access']],
1306
+ ['резерв', ['backup']],
1307
+ ['производительность', ['performance']],
1308
+ ['проблема', ['problem', 'issue', 'error']],
1309
+ ['сервис', ['service']],
1310
+ ['служба', ['service']],
1311
+ ['подключение', ['connection', 'conn']],
1312
+ ['соединение', ['connection', 'conn']],
1313
+ ['развёртывание', ['deploy', 'deployment']],
1314
+ ['развертывание', ['deploy', 'deployment']],
1315
+ ['контейнер', ['container', 'docker']],
1316
+ ['образ', ['image']],
1317
+ ['кластер', ['cluster']],
1318
+ ['узел', ['node']],
1319
+ ['секрет', ['secret', 'key', 'token']],
1320
+ ['ключ', ['key', 'token', 'secret']],
1321
+ ['пароль', ['password']],
1322
+ ['токен', ['token', 'secret']],
1323
+ ['мониторинг', ['monitor', 'monitoring']],
1324
+ ['наблюдение', ['monitor', 'monitoring']],
1325
+ ['мигрировать', ['migrate', 'migration']],
1326
+ ['миграция', ['migrate', 'migration']],
1327
+ // ── Turkish (Latin + special chars ğ/ş/ı) → English ─────────────────────
1328
+ ['hata', ['error', 'err']],
1329
+ ['hatalı', ['error', 'err']],
1330
+ ['çökme', ['crash', 'panic']],
1331
+ ['çöküş', ['crash', 'failure']],
1332
+ ['düzeltmek', ['fix', 'bugfix']],
1333
+ ['düzeltme', ['fix', 'patch']],
1334
+ ['derleme', ['build']],
1335
+ ['derlemek', ['build']],
1336
+ ['test', ['test']],
1337
+ ['testlemek', ['test', 'testing']],
1338
+ ['kimlik doğrulama', ['auth', 'authentication']],
1339
+ ['yetkilendirme', ['auth', 'authorization']],
1340
+ ['yapılandırma', ['config', 'configuration']],
1341
+ ['ayarlar', ['config', 'settings']],
1342
+ ['yüklemek', ['install']],
1343
+ ['kurulum', ['install']],
1344
+ ['güncelleme', ['update', 'upgrade']],
1345
+ ['günlük', ['log']],
1346
+ ['sunucu', ['server']],
1347
+ ['veritabanı', ['database', 'db']],
1348
+ ['ağ', ['network']],
1349
+ ['bellek', ['memory', 'ram']],
1350
+ ['önbellek', ['cache']],
1351
+ ['hata ayıklama', ['debug', 'debugging']],
1352
+ ['yeniden başlatma', ['restart', 'reboot']],
1353
+ ['izin', ['permission', 'access']],
1354
+ ['yetki', ['permission', 'access', 'acl']],
1355
+ ['yedekleme', ['backup']],
1356
+ ['performans', ['performance']],
1357
+ ['sorun', ['problem', 'issue', 'error']],
1358
+ ['servis', ['service']],
1359
+ ['hizmet', ['service']],
1360
+ ['bağlantı', ['connection', 'conn']],
1361
+ ['dağıtım', ['deploy', 'deployment']],
1362
+ ['dağıtmak', ['deploy']],
1363
+ ['konteyner', ['container', 'docker']],
1364
+ ['küme', ['cluster']],
1365
+ ['düğüm', ['node']],
1366
+ ['gizli', ['secret', 'key']],
1367
+ ['anahtar', ['key', 'token', 'secret']],
1368
+ ['şifre', ['password']],
1369
+ ['izleme', ['monitor', 'monitoring']],
1370
+ ['göç', ['migrate', 'migration']],
1107
1371
  ]);
1108
1372
  /**
1109
1373
  * Expand a token to its cross-lingual synonyms.
@@ -1194,13 +1458,34 @@ function tokenize(text) {
1194
1458
  tokens.push(stemmed);
1195
1459
  }
1196
1460
  }
1461
+ else if (CYRILLIC_RE.test(seg)) {
1462
+ // Cyrillic segment (Russian, Bulgarian, Ukrainian, Serbian …)
1463
+ const words = seg.trim().split(/\s+/);
1464
+ for (const w of words) {
1465
+ const cyr = w.replace(/[^\u0400-\u04ff]/g, '');
1466
+ if (cyr.length <= 1 || STOPWORDS.has(cyr))
1467
+ continue;
1468
+ const stemmed = russianLightStem(cyr);
1469
+ if (stemmed.length > 1 && !STOPWORDS.has(stemmed))
1470
+ tokens.push(stemmed);
1471
+ }
1472
+ }
1197
1473
  else {
1198
- // Latin / other — existing logic preserved exactly
1474
+ // Latin / other (includes Turkish with special chars ğ/ş/ı)
1199
1475
  const words = seg
1200
- .replace(/[^a-záàâãäåæçéèêëíìîïñóòôõöúùûüýÿßœ0-9:_\-./]/g, ' ')
1476
+ .replace(/[^a-záàâãäåæçéèêëíìîïñóòôõöúùûüýÿßœğşıİ0-9:_\-./]/g, ' ')
1201
1477
  .split(/\s+/)
1202
1478
  .filter(w => w.length > 1 && !STOPWORDS.has(w));
1203
- tokens.push(...words);
1479
+ // Turkish: apply light stemmer to words with Turkish-specific chars
1480
+ for (const w of words) {
1481
+ if (TURKISH_CHAR_RE.test(w)) {
1482
+ const stemmed = turkishLightStem(w);
1483
+ if (stemmed !== w && stemmed.length > 1 && !STOPWORDS.has(stemmed)) {
1484
+ tokens.push(stemmed);
1485
+ }
1486
+ }
1487
+ tokens.push(w);
1488
+ }
1204
1489
  }
1205
1490
  }
1206
1491
  // ── Cross-lingual expansion ──────────────────────────────────────────────
@@ -1242,10 +1527,30 @@ function tokenize(text) {
1242
1527
  if (stemmed.length > 1 && !STOPWORDS.has(stemmed))
1243
1528
  expansions.push(stemmed);
1244
1529
  }
1530
+ else if (CYRILLIC_RE.test(syn)) {
1531
+ // Cyrillic synonym (Russian)
1532
+ const cyr = syn.replace(/[^\u0400-\u04ff]/g, '');
1533
+ const stemmed = russianLightStem(cyr);
1534
+ if (stemmed.length > 1 && !STOPWORDS.has(stemmed))
1535
+ expansions.push(stemmed);
1536
+ }
1245
1537
  else {
1246
- // Latin synonym — split in case of multi-word (e.g. "load balancer")
1247
- const parts = syn.split(/\s+/).filter(w => w.length > 1 && !STOPWORDS.has(w));
1248
- expansions.push(...parts);
1538
+ // Latin synonym (includes Turkish) — split in case of multi-word
1539
+ const parts = syn.split(/\s+/).filter(w => {
1540
+ if (w.length <= 1 || STOPWORDS.has(w))
1541
+ return false;
1542
+ return true;
1543
+ });
1544
+ for (const p of parts) {
1545
+ expansions.push(p);
1546
+ // Apply Turkish stemmer if it has Turkish-specific characters
1547
+ if (TURKISH_CHAR_RE.test(p)) {
1548
+ const stemmed = turkishLightStem(p);
1549
+ if (stemmed !== p && stemmed.length > 1 && !STOPWORDS.has(stemmed)) {
1550
+ expansions.push(stemmed);
1551
+ }
1552
+ }
1553
+ }
1249
1554
  }
1250
1555
  }
1251
1556
  }
@@ -1271,8 +1576,8 @@ function splitMultiQuery(query) {
1271
1576
  const semiParts = query.split(/[;\n]+/).filter(s => s.trim().length > 2);
1272
1577
  if (semiParts.length >= 2)
1273
1578
  return semiParts.map(s => s.trim());
1274
- // Conjunctions (EN + DE + FR + ES)
1275
- const conjParts = query.split(/\b(?:and also|also noch|außerdem|plus|additionally|furthermore|de plus|además|inoltre|além disso)\b/i).filter(s => s.trim().length > 2);
1579
+ // Conjunctions (EN + DE + FR + ES + IT + PT + RU + TR)
1580
+ const conjParts = query.split(/\b(?:and also|also noch|außerdem|plus|additionally|furthermore|de plus|además|inoltre|além disso|а также|и также|кроме того|плюс|ayrıca|bunun yanı sıra|همچنین|وأيضاً|وكذلك|وهمچنین|और भी|इसके अलावा)\b/i).filter(s => s.trim().length > 2);
1276
1581
  if (conjParts.length >= 2)
1277
1582
  return conjParts.map(s => s.trim());
1278
1583
  // Comma-separated with 3+ parts (likely a list)
@@ -1502,6 +1807,11 @@ async function keywordSearch(redis, patterns, query, topK = 10) {
1502
1807
  score *= 1 + 0.5 * (bigramHits / queryBigrams.size);
1503
1808
  }
1504
1809
  }
1810
+ // Phrase-match boost: if the raw (lowercased) query appears verbatim in content, 2× boost
1811
+ // This rewards docs where the exact phrase exists (vs scattered tokens).
1812
+ if (sq.length >= 4 && doc.content.toLowerCase().includes(sq.toLowerCase())) {
1813
+ score *= 2.0;
1814
+ }
1505
1815
  // Recency boost
1506
1816
  score *= recencyBoost(doc.timestamp);
1507
1817
  if (score > 0) {