@ohos-cpf/3rdloop 0.0.8 → 0.0.10

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.
Files changed (220) hide show
  1. package/README.md +43 -0
  2. package/lib/cli.js +38 -5
  3. package/lib/doctor.js +111 -10
  4. package/lib/knowledge.js +440 -0
  5. package/lib/mcp.js +498 -0
  6. package/package.json +6 -2
  7. package/vendor/Archive/data/.index/meta.json +7 -0
  8. package/vendor/Archive/data/.index/search.db +0 -0
  9. package/vendor/Archive/data/docs/experiences/arkts-check-tool-pitfalls-baseline-comparison.md +13 -0
  10. package/vendor/Archive/data/docs/experiences/arkts-deep-path-import-recognition.md +31 -0
  11. package/vendor/Archive/data/docs/experiences/arkts-demo-coverage-entry-file-case-mismatch.md +25 -0
  12. package/vendor/Archive/data/docs/experiences/arkts-demo-coverage-import-trailing-slash.md +20 -0
  13. package/vendor/Archive/data/docs/experiences/arkts-library-source-files-may-be-ts.md +18 -0
  14. package/vendor/Archive/data/docs/experiences/arkts-typedarray-buffer-interface-cast-compile-fixes.md +36 -0
  15. package/vendor/Archive/data/docs/experiences/arkts-xts-compile-error-clustering-fix.md +24 -0
  16. package/vendor/Archive/data/docs/experiences/arkts-xts-doc-expected-value-runtime-probe.md +35 -0
  17. package/vendor/Archive/data/docs/experiences/arkts-xts-fixture-rawfile-testability-injection.md +33 -0
  18. package/vendor/Archive/data/docs/experiences/code-linter-chinese-comment-false-positive.md +16 -0
  19. package/vendor/Archive/data/docs/experiences/esobject-new-expression-variable-tracking.md +33 -0
  20. package/vendor/Archive/data/docs/experiences/filesystem-case-sensitivity-module-resolution.md +31 -0
  21. package/vendor/Archive/data/docs/experiences/gitcode-clone-redirect-warning-normal.md +13 -0
  22. package/vendor/Archive/data/docs/experiences/kn_msgvq1w585ec8319.md +77 -0
  23. package/vendor/Archive/data/docs/experiences/kn_msovnm6rd46de72c.md +98 -0
  24. package/vendor/Archive/data/docs/experiences/kn_mspt8vntfe708576.md +87 -0
  25. package/vendor/Archive/data/docs/experiences/kn_mspu1ei77adbbb48.md +133 -0
  26. package/vendor/Archive/data/docs/experiences/kn_mspubgak8d30bfce.md +128 -0
  27. package/vendor/Archive/data/docs/experiences/kn_mspug2de3cb1f7c3.md +115 -0
  28. package/vendor/Archive/data/docs/experiences/kn_skill_opt_001.md +38 -0
  29. package/vendor/Archive/data/docs/experiences/loop-artifacts-verification-evidence-triangle.md +19 -0
  30. package/vendor/Archive/data/docs/experiences/macos-bash-replace-build-ps1.md +19 -0
  31. package/vendor/Archive/data/docs/experiences/macos-bsd-diff-q-illegal-option.md +15 -0
  32. package/vendor/Archive/data/docs/experiences/migrate-cjs-compatible-api-version-map-limit.md +17 -0
  33. package/vendor/Archive/data/docs/experiences/mp3agic-arkts-library-behavior-quirks.md +21 -0
  34. package/vendor/Archive/data/docs/experiences/ohos-face-detection-scan-api-mapping.md +15 -0
  35. package/vendor/Archive/data/docs/experiences/pr-metadata-and-diff-integrity-verification.md +12 -0
  36. package/vendor/Archive/data/docs/experiences/rn-coverage-index-signature-pseudo-member-false-negative.md +32 -0
  37. package/vendor/Archive/data/docs/experiences/rn-memory-check-severity-calibration.md +21 -0
  38. package/vendor/Archive/data/docs/experiences/rn-platform-specific-api-exclusion-evidence.md +24 -0
  39. package/vendor/Archive/data/docs/experiences/rn-upstream-version-diff-pitfalls.md +17 -0
  40. package/vendor/Archive/data/docs/experiences/rn-version-gap-risk-rating-rules.md +13 -0
  41. package/vendor/Archive/data/docs/experiences/rn-view-lib-native-contract-diff-fabric-spec.md +16 -0
  42. package/vendor/Archive/data/docs/experiences/rnoh-adapter-package-version-field-semantics.md +18 -0
  43. package/vendor/Archive/data/docs/experiences/rnoh-arkts-sqlite-rows-array-format.md +18 -0
  44. package/vendor/Archive/data/docs/experiences/rnoh-dependency-residue-scan-strategy.md +18 -0
  45. package/vendor/Archive/data/docs/experiences/rnoh-example-local-lib-dep-use-tgz.md +53 -0
  46. package/vendor/Archive/data/docs/experiences/rnoh-example-prereserve-rntesterlist.md +17 -0
  47. package/vendor/Archive/data/docs/experiences/rnoh-example-template-compatiblesdkversion-upgrade.md +29 -0
  48. package/vendor/Archive/data/docs/experiences/rnoh-example-turbomodule-shell-adaptation-checklist.md +41 -0
  49. package/vendor/Archive/data/docs/experiences/rnoh-mcp-build-project-tool-risk.md +18 -0
  50. package/vendor/Archive/data/docs/experiences/rnoh-npm-devdependencies-version-precheck.md +29 -0
  51. package/vendor/Archive/data/docs/experiences/rnoh-repo-baseline-version-verification.md +13 -0
  52. package/vendor/Archive/data/docs/experiences/rnoh-repo-upstream-directory-mapping.md +18 -0
  53. package/vendor/Archive/data/docs/experiences/rnoh-stateless-library-zero-leak-verification.md +31 -0
  54. package/vendor/Archive/data/docs/experiences/rnoh-template-npmrc-credential-removal.md +15 -0
  55. package/vendor/Archive/data/docs/experiences/rnoh-turbomodule-codegen-wiring-verification.md +19 -0
  56. package/vendor/Archive/data/docs/experiences/rnoh-turbomodule-cpp-methodmap-compatibility-check.md +20 -0
  57. package/vendor/Archive/data/docs/experiences/scenario-doc-vs-source-signature-diff-four-patterns.md +24 -0
  58. package/vendor/Archive/data/docs/experiences/static-analysis-grep-cross-validation.md +29 -0
  59. package/vendor/Archive/demo.js +489 -0
  60. package/vendor/Archive/index.js +43 -0
  61. package/vendor/Archive/package.json +16 -0
  62. package/vendor/Archive/src/constants.js +83 -0
  63. package/vendor/Archive/src/data/stopwords.txt +110 -0
  64. package/vendor/Archive/src/data/synonyms.json +25 -0
  65. package/vendor/Archive/src/data/terms.txt +59 -0
  66. package/vendor/Archive/src/db/sqlite-backend.js +312 -0
  67. package/vendor/Archive/src/db/sqlite-schema.js +64 -0
  68. package/vendor/Archive/src/engine.js +501 -0
  69. package/vendor/Archive/src/index/index-manager.js +203 -0
  70. package/vendor/Archive/src/index/index-state.js +94 -0
  71. package/vendor/Archive/src/index/search-text-builder.js +106 -0
  72. package/vendor/Archive/src/index.js +22 -0
  73. package/vendor/Archive/src/parser/api-identifiers.js +121 -0
  74. package/vendor/Archive/src/parser/api-suffixes.js +48 -0
  75. package/vendor/Archive/src/parser/heading-normalize.js +40 -0
  76. package/vendor/Archive/src/parser/markdown-splitter.js +215 -0
  77. package/vendor/Archive/src/search/catalog-routing.js +87 -0
  78. package/vendor/Archive/src/search/fts-search.js +289 -0
  79. package/vendor/Archive/src/search/query-normalizer.js +159 -0
  80. package/vendor/Archive/src/search/query-rewriter.js +72 -0
  81. package/vendor/Archive/src/search/search-engine.js +72 -0
  82. package/vendor/Archive/src/search/snippet.js +64 -0
  83. package/vendor/Archive/src/store/dir-store.js +103 -0
  84. package/vendor/Archive/src/store/doc-store.js +36 -0
  85. package/vendor/Archive/src/tokenizer/lexicon.js +151 -0
  86. package/vendor/Archive/src/tokenizer/tokenizer.js +167 -0
  87. package/vendor/Archive/src/types.js +86 -0
  88. package/vendor/Archive/src/utils/hash.js +16 -0
  89. package/vendor/Archive/src/utils/lock.js +73 -0
  90. package/vendor/Archive/src/utils/path-safety.js +96 -0
  91. package/vendor/Archive/src/utils/paths.js +39 -0
  92. package/vendor/BadCase/rn/INDEX.md +127 -0
  93. package/vendor/BadCase/rn/cases/KI-001-webview/350/247/206/351/242/221/346/227/240/346/263/225/346/250/252/345/261/217.md +52 -0
  94. package/vendor/BadCase/rn/cases/KI-002-fastImage/347/203/255/346/233/264/346/226/260/345/233/276/347/211/207/345/244/261/350/264/245.md +37 -0
  95. package/vendor/BadCase/rn/cases/KI-003-wechat/347/274/251/347/225/245/345/233/276/346/234/254/345/234/260/345/233/276/347/211/207.md +62 -0
  96. package/vendor/BadCase/rn/cases/KI-004-wechat/345/233/236/350/260/203/346/227/266/345/272/217.md +47 -0
  97. package/vendor/BadCase/rn/cases/KI-005-geolocation/345/257/274/345/205/245/350/267/257/345/276/204/345/220/216/347/274/200.md +31 -0
  98. package/vendor/BadCase/rn/cases/KI-006-SmartRefreshLayout/346/231/272/350/203/275/346/214/207/351/222/210/345/264/251/346/272/203.md +55 -0
  99. package/vendor/BadCase/rn/cases/KI-007-orientation-locker/344/274/240/346/204/237/345/231/250/345/210/235/345/247/213/345/214/226.md +58 -0
  100. package/vendor/BadCase/rn/cases/KI-008-orientation-locker/344/274/240/346/204/237/345/231/250/345/274/225/347/224/250/350/256/241/346/225/260.md +47 -0
  101. package/vendor/BadCase/rn/cases/KI-009-pager-view/345/265/214/345/245/227/346/273/221/345/212/250.md +38 -0
  102. package/vendor/BadCase/rn/cases/KI-010-screens/346/211/213/345/212/277/345/206/262/347/252/201.md +48 -0
  103. package/vendor/BadCase/rn/cases/KI-011-spring-scrollview/346/211/213/345/212/277/351/207/215/345/244/215/347/273/221/345/256/232.md +49 -0
  104. package/vendor/BadCase/rn/cases/KI-012-qr-camera/346/250/252/345/261/217/351/200/202/351/205/215.md +59 -0
  105. package/vendor/BadCase/rn/cases/KI-013-webview/346/267/261/350/211/262/346/250/241/345/274/217/350/203/214/346/231/257/350/211/262.md +37 -0
  106. package/vendor/BadCase/rn/cases/KI-014-amap-geolocation/345/233/236/350/260/203/347/274/272/345/244/261.md +42 -0
  107. package/vendor/BadCase/rn/cases/KI-015-reanimated/350/277/220/350/241/214/346/227/266/346/236/220/346/236/204/345/264/251/346/272/203.md +54 -0
  108. package/vendor/BadCase/rn/cases/KI-016-fs/346/226/207/344/273/266/350/257/273/345/217/226/347/274/226/347/240/201.md +58 -0
  109. package/vendor/BadCase/rn/cases/KI-017-blob-util/345/217/202/346/225/260/347/274/226/347/240/201/351/224/231/350/257/257.md +39 -0
  110. package/vendor/BadCase/rn/cases/KI-018-modal/345/274/271/347/252/227/351/227/252/347/203/201.md +53 -0
  111. package/vendor/BadCase/rn/cases/KI-019-code-push/351/207/215/345/220/257/351/200/273/350/276/221/345/244/261/346/225/210.md +79 -0
  112. package/vendor/BadCase/rn/cases/KI-020-netinfo/346/225/217/346/204/237/344/277/241/346/201/257/346/263/204/351/234/262.md +60 -0
  113. package/vendor/BadCase/rn/cases/KI-021-video/346/234/254/345/234/260/350/247/206/351/242/221/346/222/255/346/224/276/345/274/202/345/270/270.md +79 -0
  114. package/vendor/BadCase/rn/cases/KI-022-audio-recorder-player/346/236/232/344/270/276/347/274/272/345/244/261.md +38 -0
  115. package/vendor/BadCase/rn/cases/KI-023-svg/346/270/220/345/217/230/347/273/247/346/211/277/345/244/261/346/225/210.md +62 -0
  116. package/vendor/BadCase/rn/generalization-log.md +55 -0
  117. package/vendor/BadCase/rn/pr-info/rntpc_react-native-SmartRefreshLayout-32.json +328 -0
  118. package/vendor/BadCase/rn/pr-info/rntpc_react-native-SmartRefreshLayout-32.md +382 -0
  119. package/vendor/BadCase/rn/pr-info/rntpc_react-native-amap-geolocation-27.json +1351 -0
  120. package/vendor/BadCase/rn/pr-info/rntpc_react-native-amap-geolocation-27.md +1176 -0
  121. package/vendor/BadCase/rn/pr-info/rntpc_react-native-audio-recorder-player-22.json +538 -0
  122. package/vendor/BadCase/rn/pr-info/rntpc_react-native-audio-recorder-player-22.md +1488 -0
  123. package/vendor/BadCase/rn/pr-info/rntpc_react-native-blob-util-23.json +211 -0
  124. package/vendor/BadCase/rn/pr-info/rntpc_react-native-blob-util-23.md +35 -0
  125. package/vendor/BadCase/rn/pr-info/rntpc_react-native-code-push-17.json +2048 -0
  126. package/vendor/BadCase/rn/pr-info/rntpc_react-native-code-push-17.md +3866 -0
  127. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fast-image-102.json +198 -0
  128. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fast-image-102.md +40 -0
  129. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fs-38.json +751 -0
  130. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fs-38.md +883 -0
  131. package/vendor/BadCase/rn/pr-info/rntpc_react-native-geolocation-24.json +1544 -0
  132. package/vendor/BadCase/rn/pr-info/rntpc_react-native-geolocation-24.md +2598 -0
  133. package/vendor/BadCase/rn/pr-info/rntpc_react-native-modal-20.json +246 -0
  134. package/vendor/BadCase/rn/pr-info/rntpc_react-native-modal-20.md +554 -0
  135. package/vendor/BadCase/rn/pr-info/rntpc_react-native-netinfo-31.json +211 -0
  136. package/vendor/BadCase/rn/pr-info/rntpc_react-native-netinfo-31.md +54 -0
  137. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-45.json +1626 -0
  138. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-45.md +2213 -0
  139. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-51.json +208 -0
  140. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-51.md +77 -0
  141. package/vendor/BadCase/rn/pr-info/rntpc_react-native-pager-view-32.json +377 -0
  142. package/vendor/BadCase/rn/pr-info/rntpc_react-native-pager-view-32.md +174 -0
  143. package/vendor/BadCase/rn/pr-info/rntpc_react-native-qr-decode-image-camera-37.json +232 -0
  144. package/vendor/BadCase/rn/pr-info/rntpc_react-native-qr-decode-image-camera-37.md +194 -0
  145. package/vendor/BadCase/rn/pr-info/rntpc_react-native-reanimated-82.json +198 -0
  146. package/vendor/BadCase/rn/pr-info/rntpc_react-native-reanimated-82.md +67 -0
  147. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-87.json +198 -0
  148. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-87.md +38 -0
  149. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-93.json +208 -0
  150. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-93.md +34 -0
  151. package/vendor/BadCase/rn/pr-info/rntpc_react-native-spring-scrollview-38.json +222 -0
  152. package/vendor/BadCase/rn/pr-info/rntpc_react-native-spring-scrollview-38.md +56 -0
  153. package/vendor/BadCase/rn/pr-info/rntpc_react-native-svg-117.json +463 -0
  154. package/vendor/BadCase/rn/pr-info/rntpc_react-native-svg-117.md +383 -0
  155. package/vendor/BadCase/rn/pr-info/rntpc_react-native-video-66.json +464 -0
  156. package/vendor/BadCase/rn/pr-info/rntpc_react-native-video-66.md +565 -0
  157. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-112.json +198 -0
  158. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-112.md +47 -0
  159. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-121.json +232 -0
  160. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-121.md +422 -0
  161. package/vendor/BadCase/rn/pr-info/rntpc_react-native-wechat-lib-15.json +295 -0
  162. package/vendor/BadCase/rn/pr-info/rntpc_react-native-wechat-lib-15.md +1038 -0
  163. package/vendor/BadCase/rn/rules/R-01-/345/205/250/345/261/217/347/252/227/345/217/243/347/212/266/346/200/201/347/256/241/347/220/206/345/256/214/346/225/264/346/200/247.md +20 -0
  164. package/vendor/BadCase/rn/rules/R-02-/350/265/204/346/272/220URI/345/275/242/346/200/201/345/210/206/346/224/257/350/246/206/347/233/226.md +21 -0
  165. package/vendor/BadCase/rn/rules/R-03-/351/231/204/345/261/236/346/223/215/344/275/234/345/244/261/350/264/245/351/230/273/346/226/255/344/270/273/346/265/201/347/250/213.md +20 -0
  166. package/vendor/BadCase/rn/rules/R-04-/345/274/202/346/255/245/345/233/236/350/260/203/346/263/250/345/206/214/346/227/266/345/272/217.md +20 -0
  167. package/vendor/BadCase/rn/rules/R-05-/346/250/241/345/235/227/345/257/274/345/205/245/350/267/257/345/276/204/350/247/204/350/214/203.md +19 -0
  168. package/vendor/BadCase/rn/rules/R-06-/344/274/240/346/204/237/345/231/250/347/241/254/344/273/266/346/235/203/351/231/220/346/214/211/351/234/200/350/216/267/345/217/226.md +21 -0
  169. package/vendor/BadCase/rn/rules/R-07-/346/231/272/350/203/275/346/214/207/351/222/210/347/224/237/345/221/275/345/221/250/346/234/237/347/256/241/347/220/206.md +20 -0
  170. package/vendor/BadCase/rn/rules/R-08-/346/211/213/345/212/277/344/272/213/344/273/266/346/263/250/345/206/214/344/270/216/345/206/262/347/252/201/345/244/204/347/220/206.md +22 -0
  171. package/vendor/BadCase/rn/rules/R-09-/346/250/252/347/253/226/345/261/217/345/244/232/345/275/242/346/200/201/345/270/203/345/261/200/351/200/202/351/205/215.md +20 -0
  172. package/vendor/BadCase/rn/rules/R-10-/346/267/261/350/211/262/346/250/241/345/274/217/344/270/273/351/242/230/351/242/234/350/211/262/351/200/202/351/205/215.md +19 -0
  173. package/vendor/BadCase/rn/rules/R-11-/346/216/245/345/217/243/345/233/236/350/260/203/345/256/236/347/216/260/345/256/214/346/225/264/346/200/247.md +20 -0
  174. package/vendor/BadCase/rn/rules/R-12-/345/216/237/347/224/237/350/277/220/350/241/214/346/227/266/350/265/204/346/272/220/346/236/220/346/236/204/346/227/266/345/272/217.md +20 -0
  175. package/vendor/BadCase/rn/rules/R-13-/346/226/207/344/273/266/346/225/260/346/215/256/347/274/226/347/240/201/344/270/216/346/240/274/345/274/217/345/205/274/345/256/271.md +21 -0
  176. package/vendor/BadCase/rn/rules/R-14-/345/212/250/347/224/273/345/210/235/345/247/213/345/214/226/346/227/266/345/272/217/344/270/216/347/212/266/346/200/201/345/220/214/346/255/245.md +20 -0
  177. package/vendor/BadCase/rn/rules/R-15-/347/212/266/346/200/201/346/214/201/344/271/205/345/214/226/344/270/216/351/207/215/345/220/257/351/200/273/350/276/221/344/270/200/350/207/264/346/200/247.md +20 -0
  178. package/vendor/BadCase/rn/rules/R-16-/346/225/217/346/204/237/344/277/241/346/201/257/346/227/245/345/277/227/346/263/204/351/234/262.md +19 -0
  179. package/vendor/BadCase/rn/rules/R-17-/345/252/222/344/275/223/350/265/204/346/272/220/347/224/237/345/221/275/345/221/250/346/234/237/344/270/216/351/224/231/350/257/257/345/244/204/347/220/206.md +21 -0
  180. package/vendor/BadCase/rn/rules/R-18-/350/203/275/345/212/233/346/236/232/344/270/276/351/205/215/347/275/256/345/256/214/346/225/264/346/200/247.md +19 -0
  181. package/vendor/BadCase/rn/rules/R-19-/345/233/276/345/275/242/346/270/262/346/237/223/345/261/236/346/200/247/347/273/247/346/211/277/344/270/216/351/207/215/347/275/256.md +19 -0
  182. package/vendor/MCP/README.md +490 -0
  183. package/vendor/MCP/config.json +29 -0
  184. package/vendor/MCP/package.json +23 -0
  185. package/vendor/MCP/scripts/analyze-demo-coverage.cjs +1203 -0
  186. package/vendor/MCP/scripts/analyze-xts-coverage.cjs +1245 -0
  187. package/vendor/MCP/scripts/deveco_docs.js +247 -0
  188. package/vendor/MCP/scripts/extract-interfaces.cjs +1487 -0
  189. package/vendor/MCP/scripts/flutter-analyze-demo-coverage.cjs +666 -0
  190. package/vendor/MCP/scripts/flutter-analyze-xts-coverage.cjs +684 -0
  191. package/vendor/MCP/scripts/flutter-extract-interfaces.cjs +1251 -0
  192. package/vendor/MCP/scripts/flutter-render-spec-doc.cjs +351 -0
  193. package/vendor/MCP/scripts/render-spec-doc.cjs +347 -0
  194. package/vendor/MCP/scripts/rn-analyze-demo-coverage.cjs +645 -0
  195. package/vendor/MCP/scripts/rn-analyze-test-coverage.cjs +614 -0
  196. package/vendor/MCP/scripts/rn-extract-interfaces.cjs +1462 -0
  197. package/vendor/MCP/scripts/rn-render-spec-doc.cjs +327 -0
  198. package/vendor/MCP/scripts/rn-scan-exports.cjs +1336 -0
  199. package/vendor/MCP/src/client/MCPClientPool.js +220 -0
  200. package/vendor/MCP/src/config/ConfigLoader.js +189 -0
  201. package/vendor/MCP/src/executor/ScriptExecutor.js +110 -0
  202. package/vendor/MCP/src/gateway/Gateway.js +451 -0
  203. package/vendor/MCP/src/index.js +87 -0
  204. package/vendor/MCP/src/library/KBToolProvider.js +229 -0
  205. package/vendor/MCP/src/library/logger.js +60 -0
  206. package/vendor/MCP/src/registry/ScriptRegistry.js +238 -0
  207. package/vendor/MCP/src/registry/ToolRegistry.js +111 -0
  208. package/vendor/MCP/src/types.js +115 -0
  209. package/vendor/Server/Skills/arkts-library-test-coverage-check/references/MCP_TOOL_USAGE.md +4 -2
  210. package/vendor/Server/Skills/arkts-library-xts-coverage/SKILL.md +3 -3
  211. package/vendor/Server/Skills/knowledge-import/SKILL.md +1 -1
  212. package/vendor/Server/Skills/knowledge-import/references/MCP_TOOL_USAGE.md +12 -4
  213. package/vendor/Server/Skills/ohos-lib-pr-push/SKILL.md +5 -4
  214. package/vendor/Server/Skills/rn-library-issue-generalizer/SKILL.md +2 -2
  215. package/vendor/Server/Skills/rn-library-known-issue-check/SKILL.md +2 -2
  216. package/vendor/Server/Skills/rnoh-lib-demo-coverage/SKILL.md +1 -2
  217. package/vendor/Server/Skills/rnoh-lib-test-align/references/port-test-demo-guide.md +1 -1
  218. package/vendor/Server/Skills/rnoh-lib-xts-coverage/SKILL.md +1 -2
  219. package/vendor/Server/package.json +5 -1
  220. package/vendor/VERSION +3 -3
@@ -0,0 +1,87 @@
1
+ /**
2
+ * catalog-routing.js — 目录权重路由
3
+ *
4
+ * 根据查询特征(API 符号、中文/英文混合、"如何/步骤"等)
5
+ * 动态计算各目录的搜索 boost 倍数。
6
+ */
7
+
8
+ /**
9
+ * @typedef {Object} CatalogBoostResult
10
+ * @property {number} catalogId
11
+ * @property {number} boost
12
+ */
13
+
14
+ /**
15
+ * 计算目录 boost。
16
+ * 规则:
17
+ * - @ohos. 查询 → references ×1.5
18
+ * - "如何/步骤" 查询 → guides ×1.45
19
+ * - 中文+英文API混合 → guides ×1.45 + references ×1.35
20
+ *
21
+ * @param {string} query 规范化后的查询
22
+ * @param {string[]} tokens 分词后的 token
23
+ * @param {Map<string, {id:string,title:string,searchBoost?:number,tailInGlobalSearch?:boolean}>} catalogMap
24
+ * @param {function(string):number} catalogToId 将 catalog id 转为数字 ID
25
+ * @returns {Map<number, number>} catalogId → boost 倍数
26
+ */
27
+ export function computeCatalogBoosts(query, tokens, catalogMap, catalogToId) {
28
+ /** @type {Map<number, number>} */
29
+ const boosts = new Map();
30
+
31
+ // 初始化:所有目录使用配置的 searchBoost(默认 1.0)
32
+ for (const config of catalogMap.values()) {
33
+ boosts.set(catalogToId(config.id), config.searchBoost ?? 1.0);
34
+ }
35
+
36
+ const hasOhosModule = /@[a-z]+\.[a-z]/i.test(query);
37
+ const hasHowTo = /如何|怎么|步骤|教程|入门|guide/i.test(query);
38
+ const hasChinese = /[\u4e00-\u9fa5]/.test(query);
39
+ const hasEnglishApi = tokens.some((t) => /^[A-Z][a-zA-Z0-9]+|@[a-z]+\.[a-z]/i.test(t));
40
+
41
+ if (hasOhosModule) {
42
+ applyBoost(boosts, catalogMap, catalogToId, 'references', 1.5);
43
+ }
44
+
45
+ if (hasHowTo) {
46
+ applyBoost(boosts, catalogMap, catalogToId, 'guides', 1.45);
47
+ }
48
+
49
+ if (hasChinese && hasEnglishApi) {
50
+ applyBoost(boosts, catalogMap, catalogToId, 'guides', 1.45);
51
+ applyBoost(boosts, catalogMap, catalogToId, 'references', 1.35);
52
+ }
53
+
54
+ return boosts;
55
+ }
56
+
57
+ /**
58
+ * 应用 boost(取较大值,不覆盖更高的已有 boost)。
59
+ * @param {Map<number, number>} boosts
60
+ * @param {Map<string, any>} catalogMap
61
+ * @param {function(string):number} catalogToId
62
+ * @param {string} catalogId
63
+ * @param {number} value
64
+ */
65
+ function applyBoost(boosts, catalogMap, catalogToId, catalogId, value) {
66
+ if (!catalogMap.has(catalogId)) return;
67
+ const numId = catalogToId(catalogId);
68
+ const current = boosts.get(numId) ?? 1.0;
69
+ boosts.set(numId, Math.max(current, value));
70
+ }
71
+
72
+ /**
73
+ * 获取全局搜索时排到尾部的目录 ID 集合。
74
+ * @param {Map<string, any>} catalogMap
75
+ * @param {function(string):number} catalogToId
76
+ * @returns {Set<number>}
77
+ */
78
+ export function getTailCatalogIds(catalogMap, catalogToId) {
79
+ /** @type {Set<number>} */
80
+ const tail = new Set();
81
+ for (const config of catalogMap.values()) {
82
+ if (config.tailInGlobalSearch) {
83
+ tail.add(catalogToId(config.id));
84
+ }
85
+ }
86
+ return tail;
87
+ }
@@ -0,0 +1,289 @@
1
+ /**
2
+ * fts-search.js — FTS5 查询执行 + Rerank 重排序
3
+ *
4
+ * 职责:
5
+ * 1. 执行 FTS5 MATCH 查询,获取 BM25 排序结果。
6
+ * 2. 多层 boost 调整:
7
+ * - 目录权重(catalogBoost)
8
+ * - 标题匹配 boost(精确/前缀/全分词包含)
9
+ * - API 符号匹配 boost(Manager API 强提升)
10
+ * 3. 同文档多段去重(只保留最佳段)。
11
+ * 4. AND/OR 回退策略。
12
+ *
13
+ * 查询使用只读内存 DB(反序列化),高性能无磁盘 I/O。
14
+ */
15
+
16
+ import { RERANK_POOL_FACTOR } from '../constants.js';
17
+ import {
18
+ buildFtsOrMatch,
19
+ buildFtsAndMatch,
20
+ } from './query-normalizer.js';
21
+ import { extractSnippet } from './snippet.js';
22
+ import { matchApiSymbol } from '../parser/api-identifiers.js';
23
+ import { isManagerApi } from '../parser/api-suffixes.js';
24
+ import {
25
+ computeCatalogBoosts,
26
+ getTailCatalogIds,
27
+ } from './catalog-routing.js';
28
+
29
+ /**
30
+ * @typedef {import('../types.js').SearchResult} SearchResult
31
+ */
32
+
33
+ /**
34
+ * FTS5 查询基础 SQL(全局)。
35
+ */
36
+ const FTS_SEARCH_SQL = `
37
+ SELECT
38
+ d.document_id,
39
+ d.doc_title,
40
+ s.section_title,
41
+ s.lead_text,
42
+ s.excerpt_truncated,
43
+ d.catalog_id,
44
+ s.id AS segment_id,
45
+ bm25(segments_fts) AS bm25
46
+ FROM segments_fts
47
+ JOIN segments s ON s.id = segments_fts.rowid
48
+ JOIN documents d ON d.id = s.doc_id
49
+ WHERE segments_fts MATCH ?
50
+ ORDER BY bm25(segments_fts)
51
+ LIMIT ?;
52
+ `;
53
+
54
+ /**
55
+ * FTS5 查询基础 SQL(目录限定)。
56
+ */
57
+ const FTS_SEARCH_CATALOG_SQL = `
58
+ SELECT
59
+ d.document_id,
60
+ d.doc_title,
61
+ s.section_title,
62
+ s.lead_text,
63
+ s.excerpt_truncated,
64
+ d.catalog_id,
65
+ s.id AS segment_id,
66
+ bm25(segments_fts) AS bm25
67
+ FROM segments_fts
68
+ JOIN segments s ON s.id = segments_fts.rowid
69
+ JOIN documents d ON d.id = s.doc_id
70
+ WHERE segments_fts MATCH ? AND d.catalog_id = ?
71
+ ORDER BY bm25(segments_fts)
72
+ LIMIT ?;
73
+ `;
74
+
75
+ export default class FtsSearch {
76
+ /**
77
+ * @param {import('../db/sqlite-backend.js').default} backend
78
+ * @param {string} dbPath
79
+ * @param {Map<string, any>} catalogMap
80
+ * @param {function(string):number} catalogToId
81
+ * @param {function(number):string} idToCatalog
82
+ */
83
+ constructor(backend, dbPath, catalogMap, catalogToId, idToCatalog) {
84
+ this.backend = backend;
85
+ this.dbPath = dbPath;
86
+ this.catalogMap = catalogMap;
87
+ this.catalogToId = catalogToId;
88
+ this.idToCatalog = idToCatalog;
89
+ }
90
+
91
+ /**
92
+ * 执行搜索(含 AND/OR 回退 + Rerank + 去重)。
93
+ * @param {{rawQuery:string, tokens:string[], preferAnd:boolean, ftsMatch:string|null}} prepared
94
+ * @param {{catalog?:string, limit?:number, includeSnippet?:boolean, snippetMaxChars?:number}} query
95
+ * @param {string[]} [apiSymbols] 当前段 API 符号(可选,用于 boost)
96
+ * @returns {Promise<SearchResult[]>}
97
+ */
98
+ async search(prepared, query) {
99
+ if (!prepared.tokens || prepared.tokens.length === 0) return [];
100
+
101
+ const limit = query.limit || 10;
102
+ const poolSize = limit * RERANK_POOL_FACTOR;
103
+ const catalogId = query.catalog ? this.catalogToId(query.catalog) : undefined;
104
+
105
+ // AND/OR 回退策略
106
+ let rows = [];
107
+ if (prepared.preferAnd) {
108
+ const andMatch = buildFtsAndMatch(prepared.tokens);
109
+ const andRows = await this.runMatch(andMatch, catalogId, poolSize);
110
+ if (andRows.length >= limit) {
111
+ rows = andRows;
112
+ } else {
113
+ // 不足 limit,用 OR 补全
114
+ const orMatch = buildFtsOrMatch(prepared.tokens);
115
+ const orRows = await this.runMatch(orMatch, catalogId, poolSize);
116
+ rows = mergeAndDedupe(andRows, orRows, poolSize);
117
+ }
118
+ } else {
119
+ const match = prepared.ftsMatch || buildFtsOrMatch(prepared.tokens);
120
+ rows = await this.runMatch(match, catalogId, poolSize);
121
+ }
122
+
123
+ // Rerank
124
+ const reranked = this.rerank(rows, prepared);
125
+
126
+ // 同文档去重
127
+ const deduped = pickBestSegmentForDocument(reranked);
128
+
129
+ // 截取 limit
130
+ const topResults = deduped.slice(0, limit);
131
+
132
+ // 转换为 SearchResult
133
+ const includeSnippet = query.includeSnippet !== false;
134
+ const snippetMaxChars = query.snippetMaxChars || 200;
135
+
136
+ return topResults.map((row) => ({
137
+ documentId: row.document_id,
138
+ title: row.doc_title,
139
+ sectionTitle: row.section_title || row.doc_title,
140
+ snippet: includeSnippet
141
+ ? extractSnippet(row.lead_text || '', prepared.rawQuery, snippetMaxChars)
142
+ : '',
143
+ score: row.effectiveScore,
144
+ rawScore: row.bm25,
145
+ catalog: this.idToCatalog(row.catalog_id),
146
+ }));
147
+ }
148
+
149
+ /**
150
+ * 执行一次 FTS MATCH 查询。
151
+ * @param {string} matchExpr FTS MATCH 表达式
152
+ * @param {number|undefined} catalogId 目录限定
153
+ * @param {number} limit
154
+ * @returns {Promise<any[]>}
155
+ */
156
+ async runMatch(matchExpr, catalogId, limit) {
157
+ if (!matchExpr) return [];
158
+ const db = await this.backend.openReadDb(this.dbPath);
159
+
160
+ let stmt;
161
+ if (catalogId !== undefined) {
162
+ stmt = db.prepare(FTS_SEARCH_CATALOG_SQL);
163
+ stmt.bind([matchExpr, catalogId, limit]);
164
+ } else {
165
+ stmt = db.prepare(FTS_SEARCH_SQL);
166
+ stmt.bind([matchExpr, limit]);
167
+ }
168
+
169
+ /** @type {any[]} */
170
+ const rows = [];
171
+ while (stmt.step()) {
172
+ rows.push(stmt.get());
173
+ }
174
+ stmt.finalize();
175
+ return rows;
176
+ }
177
+
178
+ /**
179
+ * Rerank:多层 boost 调整 effectiveScore。
180
+ * @param {any[]} rows
181
+ * @param {{rawQuery:string, tokens:string[]}} prepared
182
+ * @returns {any[]}
183
+ */
184
+ rerank(rows, prepared) {
185
+ const boosts = computeCatalogBoosts(
186
+ prepared.rawQuery,
187
+ prepared.tokens,
188
+ this.catalogMap,
189
+ this.catalogToId
190
+ );
191
+ const tailIds = getTailCatalogIds(this.catalogMap, this.catalogToId);
192
+
193
+ for (const row of rows) {
194
+ const catalogBoost = boosts.get(row.catalog_id) ?? 1.0;
195
+ const tailPenalty = tailIds.has(row.catalog_id) ? 1.2 : 1.0;
196
+
197
+ // baseScore: BM25 除以目录 boost(boost 越大分数越小 = 更靠前)
198
+ const baseScore = (row.bm25 || 0) / catalogBoost * tailPenalty;
199
+
200
+ // titleBoost
201
+ const titleLower = (row.section_title || row.doc_title || '').toLowerCase();
202
+ let titleMultiplier = 1.0;
203
+ const queryLower = prepared.rawQuery.toLowerCase();
204
+ if (titleLower === queryLower) {
205
+ titleMultiplier = 4.0;
206
+ } else if (titleLower.startsWith(queryLower.slice(0, 6)) && queryLower.length >= 6) {
207
+ titleMultiplier = 1.8;
208
+ } else {
209
+ // 标题包含所有分词
210
+ const allInTitle = prepared.tokens.every((t) => titleLower.includes(t.toLowerCase()));
211
+ if (allInTitle) titleMultiplier = 1.35;
212
+ }
213
+
214
+ // apiSymbolBoost: 判断标题是否含 Manager API
215
+ let apiMultiplier = 1.0;
216
+ if (isManagerApi(row.section_title || '')) {
217
+ apiMultiplier = 3.5;
218
+ if (this.idToCatalog(row.catalog_id) === 'references') {
219
+ apiMultiplier *= 1.4;
220
+ }
221
+ } else {
222
+ // 检查 token 是否匹配 API 符号
223
+ const symbols = [row.section_title, row.doc_title].filter(Boolean);
224
+ const anyMatch = prepared.tokens.some((t) => matchApiSymbol(t, symbols));
225
+ if (anyMatch) apiMultiplier = 1.5;
226
+ }
227
+
228
+ row.effectiveScore = baseScore / (titleMultiplier * apiMultiplier);
229
+ row.rawScore = row.bm25;
230
+ }
231
+
232
+ // 按 effectiveScore 升序(越小越相关)
233
+ rows.sort((a, b) => a.effectiveScore - b.effectiveScore);
234
+ return rows;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * AND + OR 结果合并去重。
240
+ * @param {any[]} andRows
241
+ * @param {any[]} orRows
242
+ * @param {number} limit
243
+ * @returns {any[]}
244
+ */
245
+ function mergeAndDedupe(andRows, orRows, limit) {
246
+ /** @type {Set<string>} */
247
+ const seen = new Set();
248
+ /** @type {any[]} */
249
+ const result = [];
250
+ for (const row of andRows) {
251
+ const key = `${row.document_id}:${row.segment_id}`;
252
+ if (!seen.has(key)) {
253
+ seen.add(key);
254
+ result.push(row);
255
+ }
256
+ }
257
+ for (const row of orRows) {
258
+ const key = `${row.document_id}:${row.segment_id}`;
259
+ if (!seen.has(key)) {
260
+ seen.add(key);
261
+ result.push(row);
262
+ }
263
+ if (result.length >= limit) break;
264
+ }
265
+ return result;
266
+ }
267
+
268
+ /**
269
+ * 同文档多段去重:只保留最佳段。
270
+ * @param {any[]} rows 已按 effectiveScore 排序
271
+ * @returns {any[]}
272
+ */
273
+ function pickBestSegmentForDocument(rows) {
274
+ /** @type {Map<string, any>} */
275
+ const bestByDoc = new Map();
276
+ /** @type {any[]} */
277
+ const ordered = [];
278
+
279
+ for (const row of rows) {
280
+ const docId = row.document_id;
281
+ if (!bestByDoc.has(docId)) {
282
+ bestByDoc.set(docId, row);
283
+ ordered.push(row);
284
+ }
285
+ // 已有该文档的段,跳过(保留第一个 = 最佳)
286
+ }
287
+
288
+ return ordered;
289
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * query-normalizer.js — 查询预处理
3
+ *
4
+ * 对用户输入的原始查询进行规范化:
5
+ * - 合并多余空白、截断超长查询。
6
+ * - 识别特殊查询模式(API 短语拆分、单个 API 符号)。
7
+ * - 与 jieba 分词 + 同义词扩展协同产出 FTS token。
8
+ */
9
+
10
+ import { QUERY_MAX_RAW_CHARS, QUERY_MAX_FTS_TOKENS, QUERY_AND_MAX_TOKENS } from '../constants.js';
11
+
12
+ /**
13
+ * @typedef {Object} PreparedQuery
14
+ * @property {string} rawQuery 原始查询
15
+ * @property {string} expandedQuery 同义词扩展后的查询
16
+ * @property {string[]} tokens FTS token 列表
17
+ * @property {boolean} preferAnd 是否优先 AND 匹配
18
+ * @property {string|null} ftsMatch 预构建的 FTS MATCH 表达式
19
+ */
20
+
21
+ /**
22
+ * 规范化搜索输入:合并空格 + 截断。
23
+ * @param {string} raw
24
+ * @returns {string}
25
+ */
26
+ export function normalizeSearchInput(raw) {
27
+ if (!raw) return '';
28
+ return raw.replace(/\s+/g, ' ').trim().slice(0, QUERY_MAX_RAW_CHARS);
29
+ }
30
+
31
+ /**
32
+ * 判断是否为单个 API 模块符号(如 @ohos.camera)。
33
+ * @param {string} query
34
+ * @returns {boolean}
35
+ */
36
+ function isSingleApiSymbol(query) {
37
+ return /^@[a-z]+\.[a-z][a-zA-Z0-9.]*$/i.test(query.trim());
38
+ }
39
+
40
+ /**
41
+ * 检测 API 短语拆分场景(如 "camera manager")。
42
+ * @param {string} query
43
+ * @returns {{left:string, right:string}|null}
44
+ */
45
+ function detectSplitApiPhrase(query) {
46
+ const parts = query.trim().split(/\s+/);
47
+ if (parts.length === 2) {
48
+ // 形如 "camera manager" → left=camera, right=manager
49
+ if (/^[a-z]/i.test(parts[0]) && /^[a-z]/i.test(parts[1])) {
50
+ return { left: parts[0], right: parts[1] };
51
+ }
52
+ }
53
+ return null;
54
+ }
55
+
56
+ /**
57
+ * 预处理查询,产出 PreparedQuery。
58
+ *
59
+ * @param {string} rawQuery 用户原始查询
60
+ * @param {import('../tokenizer/tokenizer.js').default} tokenizer
61
+ * @param {import('../tokenizer/lexicon.js').default} lexicon
62
+ * @param {{expandSynonyms?:boolean, synonymExpandLimit?:number, preferAnd?:boolean}} [options]
63
+ * @returns {Promise<PreparedQuery>}
64
+ */
65
+ export async function prepareQuery(rawQuery, tokenizer, lexicon, options = {}) {
66
+ const normalized = normalizeSearchInput(rawQuery);
67
+ if (!normalized) {
68
+ return { rawQuery: '', expandedQuery: '', tokens: [], preferAnd: false, ftsMatch: null };
69
+ }
70
+
71
+ const expandSynonyms = options.expandSynonyms !== false;
72
+ const synonymLimit = options.synonymExpandLimit || 8;
73
+
74
+ // 同义词扩展
75
+ const expandedQuery = expandSynonyms
76
+ ? lexicon.expandSynonyms(normalized, synonymLimit)
77
+ : normalized;
78
+
79
+ // jieba 分词(查询模式)
80
+ let tokens = await tokenizer.tokenizeForQuery(expandedQuery);
81
+
82
+ // 特殊查询模式处理
83
+ let ftsMatch = null;
84
+
85
+ if (isSingleApiSymbol(normalized)) {
86
+ // 单个 API 符号:OR 匹配变体
87
+ const variants = await tokenizer.tokenizeForQuery(normalized);
88
+ tokens = dedupe([...tokens, ...variants]).slice(0, QUERY_MAX_FTS_TOKENS);
89
+ ftsMatch = buildFtsOrMatch(tokens);
90
+ } else {
91
+ const split = detectSplitApiPhrase(normalized);
92
+ if (split) {
93
+ const leftTokens = await tokenizer.tokenizeForQuery(split.left);
94
+ const rightTokens = await tokenizer.tokenizeForQuery(split.right);
95
+ ftsMatch = buildSplitApiAndMatch(leftTokens, rightTokens);
96
+ }
97
+ }
98
+
99
+ // 限制 token 数量
100
+ tokens = tokens.slice(0, QUERY_MAX_FTS_TOKENS);
101
+
102
+ // 策略选择:2-4 个 token → preferAnd
103
+ let preferAnd = options.preferAnd;
104
+ if (preferAnd === undefined) {
105
+ preferAnd = tokens.length >= 2 && tokens.length <= QUERY_AND_MAX_TOKENS;
106
+ }
107
+
108
+ return { rawQuery: normalized, expandedQuery, tokens, preferAnd, ftsMatch };
109
+ }
110
+
111
+ /**
112
+ * 去重。
113
+ * @param {string[]} arr
114
+ * @returns {string[]}
115
+ */
116
+ function dedupe(arr) {
117
+ return [...new Set(arr)];
118
+ }
119
+
120
+ /**
121
+ * FTS5 token 转义(防注入)。
122
+ * @param {string} token
123
+ * @returns {string}
124
+ */
125
+ export function escapeFtsToken(token) {
126
+ return `"${token.replace(/"/g, '""')}"`;
127
+ }
128
+
129
+ /**
130
+ * 构建 OR 匹配表达式。
131
+ * @param {string[]} tokens
132
+ * @returns {string}
133
+ */
134
+ export function buildFtsOrMatch(tokens) {
135
+ const filtered = tokens.filter(Boolean);
136
+ if (filtered.length === 0) return '';
137
+ return filtered.map(escapeFtsToken).join(' OR ');
138
+ }
139
+
140
+ /**
141
+ * 构建 AND 匹配表达式。
142
+ * @param {string[]} tokens
143
+ * @returns {string}
144
+ */
145
+ export function buildFtsAndMatch(tokens) {
146
+ const filtered = tokens.filter(Boolean);
147
+ if (filtered.length === 0) return '';
148
+ return filtered.map(escapeFtsToken).join(' AND ');
149
+ }
150
+
151
+ /**
152
+ * 构建拆分 API 的 AND 匹配。
153
+ * @param {string[]} left
154
+ * @param {string[]} right
155
+ * @returns {string}
156
+ */
157
+ function buildSplitApiAndMatch(left, right) {
158
+ return `(${buildFtsOrMatch(left)}) AND (${buildFtsOrMatch(right)})`;
159
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * query-rewriter.js — 查询重写器
3
+ *
4
+ * 封装同义词扩展的动态注册能力:
5
+ * - 运行时注册自定义同义词组。
6
+ * - 与 Lexicon 的文件词典合并,不影响磁盘词典。
7
+ *
8
+ * 通过内存同义词组叠加到 Lexicon 的 getSynonymMap() 之上,
9
+ * 避免频繁修改磁盘文件。
10
+ */
11
+
12
+ export default class QueryRewriter {
13
+ constructor() {
14
+ /** @type {string[][]} */
15
+ this.customGroups = [];
16
+ }
17
+
18
+ /**
19
+ * 注册自定义同义词组。
20
+ * @param {string[]} words
21
+ */
22
+ addSynonyms(words) {
23
+ if (Array.isArray(words) && words.length >= 2) {
24
+ this.customGroups.push([...words]);
25
+ }
26
+ }
27
+
28
+ /**
29
+ * 将自定义同义词组合并进 Lexicon 的同义词映射。
30
+ * @param {import('../tokenizer/lexicon.js').default} lexicon
31
+ * @returns {Map<string, Set<string>>}
32
+ */
33
+ mergeWithLexicon(lexicon) {
34
+ const map = new Map(lexicon.getSynonymMap());
35
+ for (const group of this.customGroups) {
36
+ for (const word of group) {
37
+ const key = word.toLowerCase();
38
+ if (!map.has(key)) map.set(key, new Set());
39
+ for (const other of group) {
40
+ if (other !== word) map.get(key).add(other);
41
+ }
42
+ }
43
+ }
44
+ return map;
45
+ }
46
+
47
+ /**
48
+ * 扩展查询(使用合并后的同义词映射)。
49
+ * @param {string} query
50
+ * @param {import('../tokenizer/lexicon.js').default} lexicon
51
+ * @param {number} [maxParts=8]
52
+ * @returns {string}
53
+ */
54
+ expand(query, lexicon, maxParts = 8) {
55
+ const map = this.mergeWithLexicon(lexicon);
56
+ const parts = query.split(/\s+/).filter(Boolean);
57
+ /** @type {Set<string>} */
58
+ const expanded = new Set();
59
+
60
+ for (let i = 0; i < parts.length && expanded.size < maxParts; i++) {
61
+ expanded.add(parts[i]);
62
+ const syns = map.get(parts[i].toLowerCase());
63
+ if (syns) {
64
+ for (const s of syns) {
65
+ if (expanded.size >= maxParts) break;
66
+ expanded.add(s);
67
+ }
68
+ }
69
+ }
70
+ return [...expanded].join(' ');
71
+ }
72
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * search-engine.js — 搜索调度层
3
+ *
4
+ * 编排查询预处理 → FTS5 检索 → Rerank → 摘要提取的完整管道。
5
+ * 作为 IndexManager/Engine 与底层 FtsSearch 之间的中间层,
6
+ * 隔离查询逻辑与索引写入逻辑。
7
+ */
8
+
9
+ import { prepareQuery } from './query-normalizer.js';
10
+ import FtsSearch from './fts-search.js';
11
+
12
+ export default class SearchEngine {
13
+ /**
14
+ * @param {import('../db/sqlite-backend.js').default} backend
15
+ * @param {import('../tokenizer/tokenizer.js').default} tokenizer
16
+ * @param {import('../tokenizer/lexicon.js').default} lexicon
17
+ * @param {string} dbPath
18
+ * @param {Map<string, any>} catalogMap
19
+ * @param {function(string):number} catalogToId
20
+ * @param {function(number):string} idToCatalog
21
+ * @param {import('./query-rewriter.js').default} [rewriter]
22
+ */
23
+ constructor(backend, tokenizer, lexicon, dbPath, catalogMap, catalogToId, idToCatalog, rewriter) {
24
+ this.backend = backend;
25
+ this.tokenizer = tokenizer;
26
+ this.lexicon = lexicon;
27
+ this.dbPath = dbPath;
28
+ this.catalogMap = catalogMap;
29
+ this.catalogToId = catalogToId;
30
+ this.idToCatalog = idToCatalog;
31
+ this.rewriter = rewriter || null;
32
+
33
+ this.fts = new FtsSearch(backend, dbPath, catalogMap, catalogToId, idToCatalog);
34
+ }
35
+
36
+ /**
37
+ * 执行搜索。
38
+ * @param {import('../types.js').SearchQuery} query
39
+ * @param {import('../types.js').SearchOptions} [options]
40
+ * @returns {Promise<import('../types.js').SearchResult[]>}
41
+ */
42
+ async search(query, options = {}) {
43
+ if (!query.query || !query.query.trim()) return [];
44
+
45
+ // 如果有自定义重写器,先扩展同义词
46
+ let effectiveQuery = query.query;
47
+ if (this.rewriter && options.expandSynonyms !== false) {
48
+ effectiveQuery = this.rewriter.expand(
49
+ query.query,
50
+ this.lexicon,
51
+ options.synonymExpandLimit
52
+ );
53
+ }
54
+
55
+ // 查询预处理
56
+ const prepared = await prepareQuery(
57
+ effectiveQuery,
58
+ this.tokenizer,
59
+ this.lexicon,
60
+ {
61
+ expandSynonyms: options.expandSynonyms,
62
+ synonymExpandLimit: options.synonymExpandLimit,
63
+ preferAnd: options.preferAnd,
64
+ }
65
+ );
66
+
67
+ if (!prepared.tokens || prepared.tokens.length === 0) return [];
68
+
69
+ // FTS5 检索 + Rerank
70
+ return this.fts.search(prepared, query);
71
+ }
72
+ }