@cachly-dev/mcp-server 0.5.36 → 0.5.37
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 +26 -3
- package/dist/index.js +390 -5
- package/dist/index.js.map +1 -1
- package/dist/search.test.js +173 -3
- package/dist/search.test.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +382 -5
- package/src/search.test.ts +217 -3
package/src/index.ts
CHANGED
|
@@ -301,8 +301,11 @@ function embedProviderHint(): string {
|
|
|
301
301
|
// • Recency boost — newer entries rank higher (exponential decay, 7-day half-life)
|
|
302
302
|
// • Multi-query splitting — numbered lists, semicolons, conjunctions
|
|
303
303
|
// • Fuzzy matching with Levenshtein distance ≤ 2
|
|
304
|
-
// • Multilingual stopwords (EN, DE, FR, ES, IT, PT, ZH, JA, KO)
|
|
304
|
+
// • Multilingual stopwords (EN, DE, FR, ES, IT, PT, ZH, JA, KO, AR, HE)
|
|
305
305
|
// • CJK character bigram extraction — handles Chinese, Japanese, Korean
|
|
306
|
+
// • RTL language support (Arabic, Hebrew) — word tokenization + light stemming
|
|
307
|
+
// • Romanization matching — katakana → romaji tokens for romaji queries
|
|
308
|
+
// • Cross-language retrieval — tech term synonyms EN↔JA↔ZH↔KO↔AR↔HE
|
|
306
309
|
// • Pipeline Redis reads for performance
|
|
307
310
|
//
|
|
308
311
|
|
|
@@ -400,16 +403,331 @@ const STOPWORDS = new Set([
|
|
|
400
403
|
'이', '가', '은', '는', '을', '를', '의', '에', '와', '과', '도',
|
|
401
404
|
'로', '에서', '한', '하', '있', '없', '것', '수', '않', '들',
|
|
402
405
|
'이다', '하다', '이고', '이며', '라고', '에게', '에서', '으로',
|
|
406
|
+
// ── Arabic — high-frequency function words & particles ──
|
|
407
|
+
'في', 'من', 'إلى', 'على', 'عن', 'مع', 'هذا', 'هذه', 'ذلك', 'تلك',
|
|
408
|
+
'الذي', 'التي', 'الذين', 'اللواتي', 'هو', 'هي', 'هم', 'هن', 'نحن',
|
|
409
|
+
'أنت', 'أنتم', 'أنا', 'كان', 'كانت', 'كانوا', 'يكون', 'تكون',
|
|
410
|
+
'أن', 'إن', 'لأن', 'لكن', 'أو', 'بل', 'ثم', 'حتى', 'إذا', 'كي',
|
|
411
|
+
'قد', 'لقد', 'لم', 'لن', 'ما', 'لا', 'ليس', 'غير', 'بعض', 'كل',
|
|
412
|
+
'جميع', 'أي', 'كيف', 'متى', 'أين', 'لماذا', 'ماذا', 'هل', 'ال',
|
|
413
|
+
'و', 'ف', 'ب', 'ل', 'ك', 'يا', 'أم', 'إلا', 'عند', 'بين',
|
|
414
|
+
// ── Hebrew — common particles, pronouns, conjunctions ──
|
|
415
|
+
'של', 'את', 'אל', 'על', 'עם', 'לא', 'הוא', 'היא', 'הם', 'הן',
|
|
416
|
+
'אני', 'אתה', 'אנחנו', 'אתם', 'כי', 'אם', 'אבל', 'גם', 'כבר',
|
|
417
|
+
'רק', 'עוד', 'יש', 'אין', 'מה', 'זה', 'זאת', 'אלה', 'כל', 'כן',
|
|
418
|
+
'לו', 'לה', 'להם', 'בו', 'בה', 'בהם', 'שם', 'כך', 'כן', 'מי',
|
|
419
|
+
'אשר', 'אחרי', 'לפני', 'תחת', 'בין', 'מאז', 'עד', 'כמו', 'אז',
|
|
403
420
|
]);
|
|
404
421
|
|
|
405
422
|
// CJK Unicode ranges used for bigram extraction
|
|
406
423
|
const CJK_RE = /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uac00-\ud7af]/;
|
|
407
424
|
const SEGMENT_RE = /([\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uac00-\ud7af]+|[^\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uac00-\ud7af]+)/g;
|
|
408
425
|
|
|
426
|
+
// RTL Unicode ranges: Arabic (U+0600–U+06FF + extended), Hebrew (U+0590–U+05FF)
|
|
427
|
+
const RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufb4f\ufb50-\ufdff\ufe70-\ufeff]/;
|
|
428
|
+
|
|
429
|
+
// ── Katakana → Romaji conversion table (Hepburn system) ──────────────────────
|
|
430
|
+
// Digraphs must be listed before single chars so they match first.
|
|
431
|
+
const KATA_ROMAJI_TABLE: [string, string][] = [
|
|
432
|
+
// Special combinations for loanwords
|
|
433
|
+
['ファ', 'fa'], ['フィ', 'fi'], ['フェ', 'fe'], ['フォ', 'fo'],
|
|
434
|
+
['ティ', 'ti'], ['ディ', 'di'], ['トゥ', 'tu'], ['ドゥ', 'du'],
|
|
435
|
+
['ウィ', 'wi'], ['ウェ', 'we'], ['ウォ', 'wo'],
|
|
436
|
+
['チェ', 'che'], ['ジェ', 'je'], ['シェ', 'she'],
|
|
437
|
+
['イェ', 'ye'], ['ヴァ', 'va'], ['ヴィ', 'vi'], ['ヴェ', 've'], ['ヴォ', 'vo'],
|
|
438
|
+
// Digraphs (2-char → romaji)
|
|
439
|
+
['キャ', 'kya'], ['キュ', 'kyu'], ['キョ', 'kyo'],
|
|
440
|
+
['シャ', 'sha'], ['シュ', 'shu'], ['ショ', 'sho'],
|
|
441
|
+
['チャ', 'cha'], ['チュ', 'chu'], ['チョ', 'cho'],
|
|
442
|
+
['ニャ', 'nya'], ['ニュ', 'nyu'], ['ニョ', 'nyo'],
|
|
443
|
+
['ヒャ', 'hya'], ['ヒュ', 'hyu'], ['ヒョ', 'hyo'],
|
|
444
|
+
['ミャ', 'mya'], ['ミュ', 'myu'], ['ミョ', 'myo'],
|
|
445
|
+
['リャ', 'rya'], ['リュ', 'ryu'], ['リョ', 'ryo'],
|
|
446
|
+
['ギャ', 'gya'], ['ギュ', 'gyu'], ['ギョ', 'gyo'],
|
|
447
|
+
['ジャ', 'ja'], ['ジュ', 'ju'], ['ジョ', 'jo'],
|
|
448
|
+
['ビャ', 'bya'], ['ビュ', 'byu'], ['ビョ', 'byo'],
|
|
449
|
+
['ピャ', 'pya'], ['ピュ', 'pyu'], ['ピョ', 'pyo'],
|
|
450
|
+
// Single chars
|
|
451
|
+
['ア', 'a'], ['イ', 'i'], ['ウ', 'u'], ['エ', 'e'], ['オ', 'o'],
|
|
452
|
+
['カ', 'ka'], ['キ', 'ki'], ['ク', 'ku'], ['ケ', 'ke'], ['コ', 'ko'],
|
|
453
|
+
['サ', 'sa'], ['シ', 'shi'], ['ス', 'su'], ['セ', 'se'], ['ソ', 'so'],
|
|
454
|
+
['タ', 'ta'], ['チ', 'chi'], ['ツ', 'tsu'], ['テ', 'te'], ['ト', 'to'],
|
|
455
|
+
['ナ', 'na'], ['ニ', 'ni'], ['ヌ', 'nu'], ['ネ', 'ne'], ['ノ', 'no'],
|
|
456
|
+
['ハ', 'ha'], ['ヒ', 'hi'], ['フ', 'fu'], ['ヘ', 'he'], ['ホ', 'ho'],
|
|
457
|
+
['マ', 'ma'], ['ミ', 'mi'], ['ム', 'mu'], ['メ', 'me'], ['モ', 'mo'],
|
|
458
|
+
['ヤ', 'ya'], ['ユ', 'yu'], ['ヨ', 'yo'],
|
|
459
|
+
['ラ', 'ra'], ['リ', 'ri'], ['ル', 'ru'], ['レ', 're'], ['ロ', 'ro'],
|
|
460
|
+
['ワ', 'wa'], ['ヲ', 'wo'], ['ン', 'n'],
|
|
461
|
+
// Voiced
|
|
462
|
+
['ガ', 'ga'], ['ギ', 'gi'], ['グ', 'gu'], ['ゲ', 'ge'], ['ゴ', 'go'],
|
|
463
|
+
['ザ', 'za'], ['ジ', 'ji'], ['ズ', 'zu'], ['ゼ', 'ze'], ['ゾ', 'zo'],
|
|
464
|
+
['ダ', 'da'], ['ヂ', 'ji'], ['ヅ', 'zu'], ['デ', 'de'], ['ド', 'do'],
|
|
465
|
+
['バ', 'ba'], ['ビ', 'bi'], ['ブ', 'bu'], ['ベ', 'be'], ['ボ', 'bo'],
|
|
466
|
+
['パ', 'pa'], ['ピ', 'pi'], ['プ', 'pu'], ['ペ', 'pe'], ['ポ', 'po'],
|
|
467
|
+
['ヴ', 'v'],
|
|
468
|
+
// Long vowel / small chars
|
|
469
|
+
['ー', ''], ['ァ', 'a'], ['ィ', 'i'], ['ゥ', 'u'], ['ェ', 'e'], ['ォ', 'o'],
|
|
470
|
+
['ッ', ''], // handled separately (doubles next consonant)
|
|
471
|
+
];
|
|
472
|
+
|
|
473
|
+
const KATA_MAP = new Map<string, string>(KATA_ROMAJI_TABLE);
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Convert a katakana string to Hepburn romaji.
|
|
477
|
+
* Handles digraphs, geminate consonants (ッ), and long vowel marks (ー).
|
|
478
|
+
*/
|
|
479
|
+
function katakanaToRomaji(kata: string): string {
|
|
480
|
+
let result = '';
|
|
481
|
+
let i = 0;
|
|
482
|
+
while (i < kata.length) {
|
|
483
|
+
// Geminate consonant: ッ doubles the following consonant
|
|
484
|
+
if (kata[i] === 'ッ' && i + 1 < kata.length) {
|
|
485
|
+
const next = KATA_MAP.get(kata[i + 1]) ?? KATA_MAP.get(kata[i + 1] + kata[i + 2]) ?? '';
|
|
486
|
+
if (next.length > 0) result += next[0]; // double first consonant
|
|
487
|
+
i++;
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
// Try 2-char digraph first
|
|
491
|
+
if (i + 1 < kata.length) {
|
|
492
|
+
const two = kata[i] + kata[i + 1];
|
|
493
|
+
const r2 = KATA_MAP.get(two);
|
|
494
|
+
if (r2 !== undefined) { result += r2; i += 2; continue; }
|
|
495
|
+
}
|
|
496
|
+
// Single char
|
|
497
|
+
const r1 = KATA_MAP.get(kata[i]);
|
|
498
|
+
if (r1 !== undefined) result += r1;
|
|
499
|
+
i++;
|
|
500
|
+
}
|
|
501
|
+
return result;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Arabic light stemmer — strips definite article and common prefix particles.
|
|
506
|
+
* Handles: ال (al-), و (wa-), ب (bi-), ل (li-), ف (fa-), ك (ka-).
|
|
507
|
+
* Only strips when the result is still ≥ 3 chars to avoid over-stemming.
|
|
508
|
+
*/
|
|
509
|
+
function arabicLightStem(word: string): string {
|
|
510
|
+
if (word.startsWith('ال') && word.length > 4) return word.slice(2);
|
|
511
|
+
if (word.length > 3 && 'وبلفك'.includes(word[0]) && RTL_RE.test(word[1])) return word.slice(1);
|
|
512
|
+
return word;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// ── Cross-lingual tech term dictionary ────────────────────────────────────────
|
|
516
|
+
// Maps tokens (English, Japanese katakana, Chinese, Korean, Arabic, Hebrew)
|
|
517
|
+
// to their equivalents in other languages. Used to expand tokens at search time
|
|
518
|
+
// so "deploy" finds documents containing "デプロイ" / "部署" / "배포", and vice versa.
|
|
519
|
+
//
|
|
520
|
+
// Keys must be lowercase. CJK values will be bigram-expanded at use time.
|
|
521
|
+
const CROSS_LINGUAL_MAP = new Map<string, string[]>([
|
|
522
|
+
// English → all
|
|
523
|
+
['deploy', ['デプロイ', '部署', '배포', 'deployment', 'deploying']],
|
|
524
|
+
['deployment', ['デプロイ', '部署', '배포', 'deploy']],
|
|
525
|
+
['container', ['コンテナ', '容器', '컨테이너', 'docker']],
|
|
526
|
+
['server', ['サーバー', 'サーバ', '服务器', '서버', 'سيرفر', 'שרת']],
|
|
527
|
+
['database', ['データベース', '数据库', '데이터베이스', 'db', 'قاعدة البيانات']],
|
|
528
|
+
['cache', ['キャッシュ', '缓存', '캐시', 'كاش', 'מטמון']],
|
|
529
|
+
['error', ['エラー', '错误', '오류', 'خطأ', 'שגיאה', 'exception', 'err']],
|
|
530
|
+
['bug', ['バグ', '缺陷', '버그', 'issue', 'defect', 'خلل']],
|
|
531
|
+
['fix', ['修正', '修复', '수정', 'bugfix', 'patch', 'hotfix', 'إصلاح', 'תיקון']],
|
|
532
|
+
['build', ['ビルド', '构建', '빌드', 'بناء', 'בנייה']],
|
|
533
|
+
['test', ['テスト', '测试', '테스트', 'اختبار', 'בדיקה']],
|
|
534
|
+
['auth', ['認証', '认证', '인증', 'authentication', 'login', 'oauth', 'مصادقة', 'אימות']],
|
|
535
|
+
['kubernetes', ['クベルネテス', 'k8s', 'kube']],
|
|
536
|
+
['network', ['ネットワーク', '网络', '네트워크', 'شبكة', 'רשת']],
|
|
537
|
+
['timeout', ['タイムアウト', '超时', '타임아웃', 'مهلة']],
|
|
538
|
+
['memory', ['メモリ', '内存', '메모리', 'ram', 'ذاكرة', 'זיכרון']],
|
|
539
|
+
['config', ['設定', '配置', '설정', 'configuration', 'settings', 'conf', 'إعدادات', 'הגדרות']],
|
|
540
|
+
['install', ['インストール', '安装', '설치', 'تثبيت', 'התקנה']],
|
|
541
|
+
['update', ['アップデート', '更新', '업데이트', 'upgrade', 'تحديث', 'עדכון']],
|
|
542
|
+
['log', ['ログ', '日志', '로그', 'logging', 'سجل', 'יומן']],
|
|
543
|
+
['port', ['ポート', '端口', '포트', 'منفذ', 'פורט']],
|
|
544
|
+
['file', ['ファイル', '文件', '파일', 'ملف', 'קובץ']],
|
|
545
|
+
['image', ['イメージ', '镜像', '이미지', 'صورة', 'תמונה']],
|
|
546
|
+
['volume', ['ボリューム', '卷', '볼륨']],
|
|
547
|
+
['cluster', ['クラスター', '集群', '클러스터']],
|
|
548
|
+
['node', ['ノード', '节点', '노드']],
|
|
549
|
+
['service', ['サービス', '服务', '서비스', 'svc', 'خدمة', 'שירות']],
|
|
550
|
+
['certificate', ['証明書', '证书', '인증서', 'cert', 'ssl', 'tls', 'شهادة', 'תעודה']],
|
|
551
|
+
['password', ['パスワード', '密码', '비밀번호', 'passwd', 'pwd', 'كلمة المرور', 'סיסמה']],
|
|
552
|
+
['token', ['トークン', '令牌', '토큰', 'jwt', 'secret', 'رمز', 'אסימון']],
|
|
553
|
+
['health', ['ヘルス', '健康', '헬스', 'healthcheck', 'probe', 'صحة']],
|
|
554
|
+
['migration', ['マイグレーション', '迁移', '마이그레이션', 'migrate', 'هجرة']],
|
|
555
|
+
['backup', ['バックアップ', '备份', '백업', 'نسخ احتياطي', 'גיבוי']],
|
|
556
|
+
['monitor', ['モニター', '监控', '모니터링', 'monitoring', 'metrics', 'alert', 'مراقبة', 'ניטור']],
|
|
557
|
+
['performance', ['パフォーマンス', '性能', '성능', 'latency', 'throughput']],
|
|
558
|
+
['connection', ['接続', '连接', '연결', 'conn', 'socket', 'اتصال', 'חיבור']],
|
|
559
|
+
['queue', ['キュー', '队列', '큐', 'طابور']],
|
|
560
|
+
['redis', ['レディス', '레디스']],
|
|
561
|
+
['nginx', ['エンジンエックス']],
|
|
562
|
+
['linux', ['リナックス', 'لينكس']],
|
|
563
|
+
['api', ['エーピーアイ', '接口', 'endpoint', 'واجهة برمجية', 'ממשק']],
|
|
564
|
+
['healthcheck', ['ヘルスチェック', 'health', 'probe', 'فحص الصحة']],
|
|
565
|
+
['loadbalancer',['ロードバランサー', '负载均衡', '로드밸런서', 'lb']],
|
|
566
|
+
['ssl', ['tls', 'https', 'certificate', 'cert']],
|
|
567
|
+
['tls', ['ssl', 'https', 'certificate', 'cert']],
|
|
568
|
+
['docker', ['container', 'コンテナ', '容器', '컨테이너', 'دوكر']],
|
|
569
|
+
['git', ['version control', 'repo', 'repository', 'commit', 'push', 'pull']],
|
|
570
|
+
['ci', ['pipeline', 'github actions', 'gitlab', 'jenkins', 'build']],
|
|
571
|
+
['cd', ['deploy', 'deployment', 'release']],
|
|
572
|
+
['debug', ['デバッグ', '调试', '디버그', 'debugging', 'breakpoint', 'تصحيح', 'איתור באגים']],
|
|
573
|
+
['crash', ['クラッシュ', '崩溃', '크래시', 'panic', 'segfault', 'انهيار']],
|
|
574
|
+
['restart', ['再起動', '重启', '재시작', 'reboot', 'إعادة تشغيل', 'הפעלה מחדש']],
|
|
575
|
+
['permission', ['権限', '权限', '권한', 'access', 'acl', 'chmod', 'صلاحية', 'הרשאה']],
|
|
576
|
+
// Japanese katakana → English
|
|
577
|
+
['デプロイ', ['deploy', 'deployment']],
|
|
578
|
+
['コンテナ', ['container', 'docker']],
|
|
579
|
+
['サーバー', ['server']],
|
|
580
|
+
['サーバ', ['server']],
|
|
581
|
+
['データベース',['database', 'db']],
|
|
582
|
+
['キャッシュ', ['cache']],
|
|
583
|
+
['エラー', ['error', 'err']],
|
|
584
|
+
['バグ', ['bug', 'issue']],
|
|
585
|
+
['ビルド', ['build']],
|
|
586
|
+
['テスト', ['test']],
|
|
587
|
+
['認証', ['auth', 'authentication']],
|
|
588
|
+
['設定', ['config', 'configuration']],
|
|
589
|
+
['インストール',['install']],
|
|
590
|
+
['アップデート',['update', 'upgrade']],
|
|
591
|
+
['ログ', ['log', 'logs']],
|
|
592
|
+
['ポート', ['port']],
|
|
593
|
+
['ファイル', ['file']],
|
|
594
|
+
['イメージ', ['image']],
|
|
595
|
+
['クラスター', ['cluster']],
|
|
596
|
+
['ノード', ['node']],
|
|
597
|
+
['サービス', ['service']],
|
|
598
|
+
['パスワード', ['password']],
|
|
599
|
+
['トークン', ['token']],
|
|
600
|
+
['バックアップ',['backup']],
|
|
601
|
+
['モニター', ['monitor', 'monitoring']],
|
|
602
|
+
['接続', ['connection']],
|
|
603
|
+
['キュー', ['queue']],
|
|
604
|
+
['ヘルスチェック',['healthcheck', 'health check', 'health']],
|
|
605
|
+
['ロードバランサー',['loadbalancer', 'load balancer']],
|
|
606
|
+
['デバッグ', ['debug']],
|
|
607
|
+
['クラッシュ', ['crash', 'panic']],
|
|
608
|
+
['再起動', ['restart', 'reboot']],
|
|
609
|
+
['権限', ['permission', 'access']],
|
|
610
|
+
// Chinese → English
|
|
611
|
+
['部署', ['deploy', 'deployment']],
|
|
612
|
+
['容器', ['container', 'docker']],
|
|
613
|
+
['服务器', ['server']],
|
|
614
|
+
['数据库', ['database']],
|
|
615
|
+
['缓存', ['cache']],
|
|
616
|
+
['错误', ['error']],
|
|
617
|
+
['修复', ['fix', 'bugfix']],
|
|
618
|
+
['构建', ['build']],
|
|
619
|
+
['测试', ['test']],
|
|
620
|
+
['认证', ['auth']],
|
|
621
|
+
['配置', ['config']],
|
|
622
|
+
['安装', ['install']],
|
|
623
|
+
['更新', ['update']],
|
|
624
|
+
['日志', ['log']],
|
|
625
|
+
['端口', ['port']],
|
|
626
|
+
['镜像', ['image']],
|
|
627
|
+
['集群', ['cluster']],
|
|
628
|
+
['节点', ['node']],
|
|
629
|
+
['服务', ['service']],
|
|
630
|
+
['密码', ['password']],
|
|
631
|
+
['令牌', ['token']],
|
|
632
|
+
['备份', ['backup']],
|
|
633
|
+
['监控', ['monitor']],
|
|
634
|
+
['连接', ['connection']],
|
|
635
|
+
['队列', ['queue']],
|
|
636
|
+
['性能', ['performance']],
|
|
637
|
+
['内存', ['memory']],
|
|
638
|
+
['调试', ['debug']],
|
|
639
|
+
['崩溃', ['crash']],
|
|
640
|
+
['重启', ['restart']],
|
|
641
|
+
['权限', ['permission']],
|
|
642
|
+
// Korean → English
|
|
643
|
+
['배포', ['deploy', 'deployment']],
|
|
644
|
+
['컨테이너', ['container']],
|
|
645
|
+
['서버', ['server']],
|
|
646
|
+
['데이터베이스',['database']],
|
|
647
|
+
['캐시', ['cache']],
|
|
648
|
+
['오류', ['error']],
|
|
649
|
+
['수정', ['fix']],
|
|
650
|
+
['빌드', ['build']],
|
|
651
|
+
['테스트', ['test']],
|
|
652
|
+
['인증', ['auth']],
|
|
653
|
+
['설정', ['config']],
|
|
654
|
+
['설치', ['install']],
|
|
655
|
+
['업데이트', ['update']],
|
|
656
|
+
['로그', ['log']],
|
|
657
|
+
['포트', ['port']],
|
|
658
|
+
['이미지', ['image']],
|
|
659
|
+
['클러스터', ['cluster']],
|
|
660
|
+
['노드', ['node']],
|
|
661
|
+
['서비스', ['service']],
|
|
662
|
+
['토큰', ['token']],
|
|
663
|
+
['백업', ['backup']],
|
|
664
|
+
['모니터링', ['monitor']],
|
|
665
|
+
['연결', ['connection']],
|
|
666
|
+
['큐', ['queue']],
|
|
667
|
+
['성능', ['performance']],
|
|
668
|
+
['메모리', ['memory']],
|
|
669
|
+
['디버그', ['debug']],
|
|
670
|
+
['크래시', ['crash']],
|
|
671
|
+
['재시작', ['restart']],
|
|
672
|
+
['권한', ['permission']],
|
|
673
|
+
// Arabic → English
|
|
674
|
+
['خطأ', ['error']],
|
|
675
|
+
['إصلاح', ['fix']],
|
|
676
|
+
['بناء', ['build']],
|
|
677
|
+
['اختبار', ['test']],
|
|
678
|
+
['مصادقة', ['auth']],
|
|
679
|
+
['إعدادات', ['config']],
|
|
680
|
+
['تثبيت', ['install']],
|
|
681
|
+
['تحديث', ['update']],
|
|
682
|
+
['سجل', ['log']],
|
|
683
|
+
['صورة', ['image']],
|
|
684
|
+
['خدمة', ['service']],
|
|
685
|
+
['نسخ احتياطي', ['backup']],
|
|
686
|
+
['مراقبة', ['monitor']],
|
|
687
|
+
['اتصال', ['connection']],
|
|
688
|
+
['ذاكرة', ['memory']],
|
|
689
|
+
['تصحيح', ['debug']],
|
|
690
|
+
['شبكة', ['network']],
|
|
691
|
+
// Hebrew → English
|
|
692
|
+
['שגיאה', ['error']],
|
|
693
|
+
['תיקון', ['fix']],
|
|
694
|
+
['בנייה', ['build']],
|
|
695
|
+
['בדיקה', ['test']],
|
|
696
|
+
['אימות', ['auth']],
|
|
697
|
+
['הגדרות', ['config']],
|
|
698
|
+
['התקנה', ['install']],
|
|
699
|
+
['עדכון', ['update']],
|
|
700
|
+
['יומן', ['log']],
|
|
701
|
+
['שרת', ['server']],
|
|
702
|
+
['שירות', ['service']],
|
|
703
|
+
['גיבוי', ['backup']],
|
|
704
|
+
['ניטור', ['monitor']],
|
|
705
|
+
['חיבור', ['connection']],
|
|
706
|
+
['זיכרון', ['memory']],
|
|
707
|
+
['רשת', ['network']],
|
|
708
|
+
['תמונה', ['image']],
|
|
709
|
+
['סיסמה', ['password']],
|
|
710
|
+
['הרשאה', ['permission']],
|
|
711
|
+
]);
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Expand a token to its cross-lingual synonyms.
|
|
715
|
+
* Returns synonyms to be added to the token stream (pre-tokenized).
|
|
716
|
+
* CJK synonyms are NOT pre-bigram'd here — caller handles that.
|
|
717
|
+
*/
|
|
718
|
+
function expandCrossLingual(token: string): string[] {
|
|
719
|
+
return CROSS_LINGUAL_MAP.get(token) ?? [];
|
|
720
|
+
}
|
|
721
|
+
|
|
409
722
|
/**
|
|
410
723
|
* Tokenize text into meaningful keywords.
|
|
411
|
-
* Handles accented Latin
|
|
412
|
-
*
|
|
724
|
+
* Handles accented Latin, RTL (Arabic, Hebrew), and CJK (Chinese, Japanese, Korean).
|
|
725
|
+
*
|
|
726
|
+
* Features:
|
|
727
|
+
* • CJK → character bigrams
|
|
728
|
+
* • Katakana → additionally emits Hepburn romaji tokens (enables romaji queries)
|
|
729
|
+
* • Arabic/Hebrew → word tokenization with light Arabic stemming
|
|
730
|
+
* • All scripts → cross-lingual synonym expansion (EN↔JA↔ZH↔KO↔AR↔HE)
|
|
413
731
|
*/
|
|
414
732
|
function tokenize(text: string): string[] {
|
|
415
733
|
const tokens: string[] = [];
|
|
@@ -429,8 +747,33 @@ function tokenize(text: string): string[] {
|
|
|
429
747
|
const last = seg[seg.length - 1];
|
|
430
748
|
if (!STOPWORDS.has(last)) tokens.push(last);
|
|
431
749
|
}
|
|
750
|
+
// Whole-segment cross-lingual lookup (for known tech terms like デプロイ, 部署, 배포)
|
|
751
|
+
// This ensures CJK full-words map to their English equivalents
|
|
752
|
+
const segSyns = expandCrossLingual(seg);
|
|
753
|
+
for (const syn of segSyns) {
|
|
754
|
+
if (!CJK_RE.test(syn)) {
|
|
755
|
+
syn.split(/\s+/).filter(w => w.length > 1 && !STOPWORDS.has(w)).forEach(w => tokens.push(w));
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
// Romanization: extract katakana-only portion and emit romaji tokens.
|
|
759
|
+
// This allows users to search "kontena" or "depuroi" to find katakana docs.
|
|
760
|
+
const kataOnly = seg.replace(/[^\u30a0-\u30ff]/g, '');
|
|
761
|
+
if (kataOnly.length >= 2) {
|
|
762
|
+
const romaji = katakanaToRomaji(kataOnly);
|
|
763
|
+
if (romaji.length >= 2 && romaji.length <= 40 && !STOPWORDS.has(romaji)) {
|
|
764
|
+
tokens.push(romaji);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
} else if (RTL_RE.test(seg)) {
|
|
768
|
+
// RTL segment (Arabic / Hebrew) — space-separated words with light stemming
|
|
769
|
+
const words = seg.trim().split(/\s+/);
|
|
770
|
+
for (const w of words) {
|
|
771
|
+
if (w.length <= 1 || STOPWORDS.has(w)) continue;
|
|
772
|
+
const stemmed = arabicLightStem(w);
|
|
773
|
+
if (stemmed.length > 1 && !STOPWORDS.has(stemmed)) tokens.push(stemmed);
|
|
774
|
+
}
|
|
432
775
|
} else {
|
|
433
|
-
// Latin / other — existing logic
|
|
776
|
+
// Latin / other — existing logic preserved exactly
|
|
434
777
|
const words = seg
|
|
435
778
|
.replace(/[^a-záàâãäåæçéèêëíìîïñóòôõöúùûüýÿßœ0-9:_\-./]/g, ' ')
|
|
436
779
|
.split(/\s+/)
|
|
@@ -439,6 +782,39 @@ function tokenize(text: string): string[] {
|
|
|
439
782
|
}
|
|
440
783
|
}
|
|
441
784
|
|
|
785
|
+
// ── Cross-lingual expansion ──────────────────────────────────────────────
|
|
786
|
+
// For each token, look up synonyms in other languages and add them.
|
|
787
|
+
// CJK synonyms are bigram-expanded inline; Latin/RTL synonyms added as-is.
|
|
788
|
+
const expansions: string[] = [];
|
|
789
|
+
const seen = new Set(tokens);
|
|
790
|
+
for (const tok of tokens) {
|
|
791
|
+
const syns = expandCrossLingual(tok);
|
|
792
|
+
for (const syn of syns) {
|
|
793
|
+
if (seen.has(syn)) continue;
|
|
794
|
+
seen.add(syn);
|
|
795
|
+
if (CJK_RE.test(syn)) {
|
|
796
|
+
// CJK synonym → expand to character bigrams
|
|
797
|
+
for (let i = 0; i < syn.length - 1; i++) {
|
|
798
|
+
const bg = syn[i] + syn[i + 1];
|
|
799
|
+
if (!STOPWORDS.has(syn[i]) && !STOPWORDS.has(bg)) expansions.push(bg);
|
|
800
|
+
}
|
|
801
|
+
if (syn.length >= 1) {
|
|
802
|
+
const last = syn[syn.length - 1];
|
|
803
|
+
if (!STOPWORDS.has(last)) expansions.push(last);
|
|
804
|
+
}
|
|
805
|
+
} else if (RTL_RE.test(syn)) {
|
|
806
|
+
// RTL synonym
|
|
807
|
+
const stemmed = arabicLightStem(syn);
|
|
808
|
+
if (stemmed.length > 1 && !STOPWORDS.has(stemmed)) expansions.push(stemmed);
|
|
809
|
+
} else {
|
|
810
|
+
// Latin synonym — split in case of multi-word (e.g. "load balancer")
|
|
811
|
+
const parts = syn.split(/\s+/).filter(w => w.length > 1 && !STOPWORDS.has(w));
|
|
812
|
+
expansions.push(...parts);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
tokens.push(...expansions);
|
|
817
|
+
|
|
442
818
|
return tokens;
|
|
443
819
|
}
|
|
444
820
|
|
|
@@ -752,7 +1128,8 @@ async function keywordSearch(
|
|
|
752
1128
|
}
|
|
753
1129
|
|
|
754
1130
|
// ── Exported for testing ──────────────────────────────────────────────────────
|
|
755
|
-
export { tokenize, splitMultiQuery, levenshtein, recencyBoost, extractTimestamp, STOPWORDS
|
|
1131
|
+
export { tokenize, splitMultiQuery, levenshtein, recencyBoost, extractTimestamp, STOPWORDS,
|
|
1132
|
+
katakanaToRomaji, arabicLightStem, expandCrossLingual, CROSS_LINGUAL_MAP };
|
|
756
1133
|
|
|
757
1134
|
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
758
1135
|
|