@ohos-cpf/3rdloop 0.0.8 → 0.0.9

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 (219) hide show
  1. package/README.md +25 -0
  2. package/lib/cli.js +22 -5
  3. package/lib/doctor.js +111 -10
  4. package/lib/mcp.js +498 -0
  5. package/package.json +6 -2
  6. package/vendor/Archive/data/.index/meta.json +7 -0
  7. package/vendor/Archive/data/.index/search.db +0 -0
  8. package/vendor/Archive/data/docs/experiences/arkts-check-tool-pitfalls-baseline-comparison.md +13 -0
  9. package/vendor/Archive/data/docs/experiences/arkts-deep-path-import-recognition.md +31 -0
  10. package/vendor/Archive/data/docs/experiences/arkts-demo-coverage-entry-file-case-mismatch.md +25 -0
  11. package/vendor/Archive/data/docs/experiences/arkts-demo-coverage-import-trailing-slash.md +20 -0
  12. package/vendor/Archive/data/docs/experiences/arkts-library-source-files-may-be-ts.md +18 -0
  13. package/vendor/Archive/data/docs/experiences/arkts-typedarray-buffer-interface-cast-compile-fixes.md +36 -0
  14. package/vendor/Archive/data/docs/experiences/arkts-xts-compile-error-clustering-fix.md +24 -0
  15. package/vendor/Archive/data/docs/experiences/arkts-xts-doc-expected-value-runtime-probe.md +35 -0
  16. package/vendor/Archive/data/docs/experiences/arkts-xts-fixture-rawfile-testability-injection.md +33 -0
  17. package/vendor/Archive/data/docs/experiences/code-linter-chinese-comment-false-positive.md +16 -0
  18. package/vendor/Archive/data/docs/experiences/esobject-new-expression-variable-tracking.md +33 -0
  19. package/vendor/Archive/data/docs/experiences/filesystem-case-sensitivity-module-resolution.md +31 -0
  20. package/vendor/Archive/data/docs/experiences/gitcode-clone-redirect-warning-normal.md +13 -0
  21. package/vendor/Archive/data/docs/experiences/kn_msgvq1w585ec8319.md +77 -0
  22. package/vendor/Archive/data/docs/experiences/kn_msovnm6rd46de72c.md +98 -0
  23. package/vendor/Archive/data/docs/experiences/kn_mspt8vntfe708576.md +87 -0
  24. package/vendor/Archive/data/docs/experiences/kn_mspu1ei77adbbb48.md +133 -0
  25. package/vendor/Archive/data/docs/experiences/kn_mspubgak8d30bfce.md +128 -0
  26. package/vendor/Archive/data/docs/experiences/kn_mspug2de3cb1f7c3.md +115 -0
  27. package/vendor/Archive/data/docs/experiences/kn_skill_opt_001.md +38 -0
  28. package/vendor/Archive/data/docs/experiences/loop-artifacts-verification-evidence-triangle.md +19 -0
  29. package/vendor/Archive/data/docs/experiences/macos-bash-replace-build-ps1.md +19 -0
  30. package/vendor/Archive/data/docs/experiences/macos-bsd-diff-q-illegal-option.md +15 -0
  31. package/vendor/Archive/data/docs/experiences/migrate-cjs-compatible-api-version-map-limit.md +17 -0
  32. package/vendor/Archive/data/docs/experiences/mp3agic-arkts-library-behavior-quirks.md +21 -0
  33. package/vendor/Archive/data/docs/experiences/ohos-face-detection-scan-api-mapping.md +15 -0
  34. package/vendor/Archive/data/docs/experiences/pr-metadata-and-diff-integrity-verification.md +12 -0
  35. package/vendor/Archive/data/docs/experiences/rn-coverage-index-signature-pseudo-member-false-negative.md +32 -0
  36. package/vendor/Archive/data/docs/experiences/rn-memory-check-severity-calibration.md +21 -0
  37. package/vendor/Archive/data/docs/experiences/rn-platform-specific-api-exclusion-evidence.md +24 -0
  38. package/vendor/Archive/data/docs/experiences/rn-upstream-version-diff-pitfalls.md +17 -0
  39. package/vendor/Archive/data/docs/experiences/rn-version-gap-risk-rating-rules.md +13 -0
  40. package/vendor/Archive/data/docs/experiences/rn-view-lib-native-contract-diff-fabric-spec.md +16 -0
  41. package/vendor/Archive/data/docs/experiences/rnoh-adapter-package-version-field-semantics.md +18 -0
  42. package/vendor/Archive/data/docs/experiences/rnoh-arkts-sqlite-rows-array-format.md +18 -0
  43. package/vendor/Archive/data/docs/experiences/rnoh-dependency-residue-scan-strategy.md +18 -0
  44. package/vendor/Archive/data/docs/experiences/rnoh-example-local-lib-dep-use-tgz.md +53 -0
  45. package/vendor/Archive/data/docs/experiences/rnoh-example-prereserve-rntesterlist.md +17 -0
  46. package/vendor/Archive/data/docs/experiences/rnoh-example-template-compatiblesdkversion-upgrade.md +29 -0
  47. package/vendor/Archive/data/docs/experiences/rnoh-example-turbomodule-shell-adaptation-checklist.md +41 -0
  48. package/vendor/Archive/data/docs/experiences/rnoh-mcp-build-project-tool-risk.md +18 -0
  49. package/vendor/Archive/data/docs/experiences/rnoh-npm-devdependencies-version-precheck.md +29 -0
  50. package/vendor/Archive/data/docs/experiences/rnoh-repo-baseline-version-verification.md +13 -0
  51. package/vendor/Archive/data/docs/experiences/rnoh-repo-upstream-directory-mapping.md +18 -0
  52. package/vendor/Archive/data/docs/experiences/rnoh-stateless-library-zero-leak-verification.md +31 -0
  53. package/vendor/Archive/data/docs/experiences/rnoh-template-npmrc-credential-removal.md +15 -0
  54. package/vendor/Archive/data/docs/experiences/rnoh-turbomodule-codegen-wiring-verification.md +19 -0
  55. package/vendor/Archive/data/docs/experiences/rnoh-turbomodule-cpp-methodmap-compatibility-check.md +20 -0
  56. package/vendor/Archive/data/docs/experiences/scenario-doc-vs-source-signature-diff-four-patterns.md +24 -0
  57. package/vendor/Archive/data/docs/experiences/static-analysis-grep-cross-validation.md +29 -0
  58. package/vendor/Archive/demo.js +489 -0
  59. package/vendor/Archive/index.js +43 -0
  60. package/vendor/Archive/package.json +16 -0
  61. package/vendor/Archive/src/constants.js +83 -0
  62. package/vendor/Archive/src/data/stopwords.txt +110 -0
  63. package/vendor/Archive/src/data/synonyms.json +25 -0
  64. package/vendor/Archive/src/data/terms.txt +59 -0
  65. package/vendor/Archive/src/db/sqlite-backend.js +312 -0
  66. package/vendor/Archive/src/db/sqlite-schema.js +64 -0
  67. package/vendor/Archive/src/engine.js +501 -0
  68. package/vendor/Archive/src/index/index-manager.js +203 -0
  69. package/vendor/Archive/src/index/index-state.js +94 -0
  70. package/vendor/Archive/src/index/search-text-builder.js +106 -0
  71. package/vendor/Archive/src/index.js +22 -0
  72. package/vendor/Archive/src/parser/api-identifiers.js +121 -0
  73. package/vendor/Archive/src/parser/api-suffixes.js +48 -0
  74. package/vendor/Archive/src/parser/heading-normalize.js +40 -0
  75. package/vendor/Archive/src/parser/markdown-splitter.js +215 -0
  76. package/vendor/Archive/src/search/catalog-routing.js +87 -0
  77. package/vendor/Archive/src/search/fts-search.js +289 -0
  78. package/vendor/Archive/src/search/query-normalizer.js +159 -0
  79. package/vendor/Archive/src/search/query-rewriter.js +72 -0
  80. package/vendor/Archive/src/search/search-engine.js +72 -0
  81. package/vendor/Archive/src/search/snippet.js +64 -0
  82. package/vendor/Archive/src/store/dir-store.js +103 -0
  83. package/vendor/Archive/src/store/doc-store.js +36 -0
  84. package/vendor/Archive/src/tokenizer/lexicon.js +151 -0
  85. package/vendor/Archive/src/tokenizer/tokenizer.js +167 -0
  86. package/vendor/Archive/src/types.js +86 -0
  87. package/vendor/Archive/src/utils/hash.js +16 -0
  88. package/vendor/Archive/src/utils/lock.js +73 -0
  89. package/vendor/Archive/src/utils/path-safety.js +96 -0
  90. package/vendor/Archive/src/utils/paths.js +39 -0
  91. package/vendor/BadCase/rn/INDEX.md +127 -0
  92. 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
  93. 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
  94. 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
  95. package/vendor/BadCase/rn/cases/KI-004-wechat/345/233/236/350/260/203/346/227/266/345/272/217.md +47 -0
  96. 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
  97. 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
  98. 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
  99. 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
  100. package/vendor/BadCase/rn/cases/KI-009-pager-view/345/265/214/345/245/227/346/273/221/345/212/250.md +38 -0
  101. package/vendor/BadCase/rn/cases/KI-010-screens/346/211/213/345/212/277/345/206/262/347/252/201.md +48 -0
  102. 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
  103. package/vendor/BadCase/rn/cases/KI-012-qr-camera/346/250/252/345/261/217/351/200/202/351/205/215.md +59 -0
  104. 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
  105. package/vendor/BadCase/rn/cases/KI-014-amap-geolocation/345/233/236/350/260/203/347/274/272/345/244/261.md +42 -0
  106. 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
  107. 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
  108. 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
  109. package/vendor/BadCase/rn/cases/KI-018-modal/345/274/271/347/252/227/351/227/252/347/203/201.md +53 -0
  110. 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
  111. 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
  112. 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
  113. 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
  114. 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
  115. package/vendor/BadCase/rn/generalization-log.md +55 -0
  116. package/vendor/BadCase/rn/pr-info/rntpc_react-native-SmartRefreshLayout-32.json +328 -0
  117. package/vendor/BadCase/rn/pr-info/rntpc_react-native-SmartRefreshLayout-32.md +382 -0
  118. package/vendor/BadCase/rn/pr-info/rntpc_react-native-amap-geolocation-27.json +1351 -0
  119. package/vendor/BadCase/rn/pr-info/rntpc_react-native-amap-geolocation-27.md +1176 -0
  120. package/vendor/BadCase/rn/pr-info/rntpc_react-native-audio-recorder-player-22.json +538 -0
  121. package/vendor/BadCase/rn/pr-info/rntpc_react-native-audio-recorder-player-22.md +1488 -0
  122. package/vendor/BadCase/rn/pr-info/rntpc_react-native-blob-util-23.json +211 -0
  123. package/vendor/BadCase/rn/pr-info/rntpc_react-native-blob-util-23.md +35 -0
  124. package/vendor/BadCase/rn/pr-info/rntpc_react-native-code-push-17.json +2048 -0
  125. package/vendor/BadCase/rn/pr-info/rntpc_react-native-code-push-17.md +3866 -0
  126. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fast-image-102.json +198 -0
  127. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fast-image-102.md +40 -0
  128. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fs-38.json +751 -0
  129. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fs-38.md +883 -0
  130. package/vendor/BadCase/rn/pr-info/rntpc_react-native-geolocation-24.json +1544 -0
  131. package/vendor/BadCase/rn/pr-info/rntpc_react-native-geolocation-24.md +2598 -0
  132. package/vendor/BadCase/rn/pr-info/rntpc_react-native-modal-20.json +246 -0
  133. package/vendor/BadCase/rn/pr-info/rntpc_react-native-modal-20.md +554 -0
  134. package/vendor/BadCase/rn/pr-info/rntpc_react-native-netinfo-31.json +211 -0
  135. package/vendor/BadCase/rn/pr-info/rntpc_react-native-netinfo-31.md +54 -0
  136. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-45.json +1626 -0
  137. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-45.md +2213 -0
  138. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-51.json +208 -0
  139. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-51.md +77 -0
  140. package/vendor/BadCase/rn/pr-info/rntpc_react-native-pager-view-32.json +377 -0
  141. package/vendor/BadCase/rn/pr-info/rntpc_react-native-pager-view-32.md +174 -0
  142. package/vendor/BadCase/rn/pr-info/rntpc_react-native-qr-decode-image-camera-37.json +232 -0
  143. package/vendor/BadCase/rn/pr-info/rntpc_react-native-qr-decode-image-camera-37.md +194 -0
  144. package/vendor/BadCase/rn/pr-info/rntpc_react-native-reanimated-82.json +198 -0
  145. package/vendor/BadCase/rn/pr-info/rntpc_react-native-reanimated-82.md +67 -0
  146. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-87.json +198 -0
  147. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-87.md +38 -0
  148. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-93.json +208 -0
  149. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-93.md +34 -0
  150. package/vendor/BadCase/rn/pr-info/rntpc_react-native-spring-scrollview-38.json +222 -0
  151. package/vendor/BadCase/rn/pr-info/rntpc_react-native-spring-scrollview-38.md +56 -0
  152. package/vendor/BadCase/rn/pr-info/rntpc_react-native-svg-117.json +463 -0
  153. package/vendor/BadCase/rn/pr-info/rntpc_react-native-svg-117.md +383 -0
  154. package/vendor/BadCase/rn/pr-info/rntpc_react-native-video-66.json +464 -0
  155. package/vendor/BadCase/rn/pr-info/rntpc_react-native-video-66.md +565 -0
  156. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-112.json +198 -0
  157. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-112.md +47 -0
  158. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-121.json +232 -0
  159. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-121.md +422 -0
  160. package/vendor/BadCase/rn/pr-info/rntpc_react-native-wechat-lib-15.json +295 -0
  161. package/vendor/BadCase/rn/pr-info/rntpc_react-native-wechat-lib-15.md +1038 -0
  162. 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
  163. 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
  164. 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
  165. 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
  166. 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
  167. 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
  168. 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
  169. 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
  170. 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
  171. 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
  172. 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
  173. 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
  174. 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
  175. 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
  176. 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
  177. 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
  178. 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
  179. 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
  180. 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
  181. package/vendor/MCP/README.md +490 -0
  182. package/vendor/MCP/config.json +29 -0
  183. package/vendor/MCP/package.json +23 -0
  184. package/vendor/MCP/scripts/analyze-demo-coverage.cjs +1203 -0
  185. package/vendor/MCP/scripts/analyze-xts-coverage.cjs +1245 -0
  186. package/vendor/MCP/scripts/deveco_docs.js +247 -0
  187. package/vendor/MCP/scripts/extract-interfaces.cjs +1487 -0
  188. package/vendor/MCP/scripts/flutter-analyze-demo-coverage.cjs +666 -0
  189. package/vendor/MCP/scripts/flutter-analyze-xts-coverage.cjs +684 -0
  190. package/vendor/MCP/scripts/flutter-extract-interfaces.cjs +1251 -0
  191. package/vendor/MCP/scripts/flutter-render-spec-doc.cjs +351 -0
  192. package/vendor/MCP/scripts/render-spec-doc.cjs +347 -0
  193. package/vendor/MCP/scripts/rn-analyze-demo-coverage.cjs +645 -0
  194. package/vendor/MCP/scripts/rn-analyze-test-coverage.cjs +614 -0
  195. package/vendor/MCP/scripts/rn-extract-interfaces.cjs +1462 -0
  196. package/vendor/MCP/scripts/rn-render-spec-doc.cjs +327 -0
  197. package/vendor/MCP/scripts/rn-scan-exports.cjs +1336 -0
  198. package/vendor/MCP/src/client/MCPClientPool.js +220 -0
  199. package/vendor/MCP/src/config/ConfigLoader.js +189 -0
  200. package/vendor/MCP/src/executor/ScriptExecutor.js +110 -0
  201. package/vendor/MCP/src/gateway/Gateway.js +451 -0
  202. package/vendor/MCP/src/index.js +87 -0
  203. package/vendor/MCP/src/library/KBToolProvider.js +229 -0
  204. package/vendor/MCP/src/library/logger.js +60 -0
  205. package/vendor/MCP/src/registry/ScriptRegistry.js +238 -0
  206. package/vendor/MCP/src/registry/ToolRegistry.js +111 -0
  207. package/vendor/MCP/src/types.js +115 -0
  208. package/vendor/Server/Skills/arkts-library-test-coverage-check/references/MCP_TOOL_USAGE.md +4 -2
  209. package/vendor/Server/Skills/arkts-library-xts-coverage/SKILL.md +3 -3
  210. package/vendor/Server/Skills/knowledge-import/SKILL.md +1 -1
  211. package/vendor/Server/Skills/knowledge-import/references/MCP_TOOL_USAGE.md +12 -4
  212. package/vendor/Server/Skills/ohos-lib-pr-push/SKILL.md +5 -4
  213. package/vendor/Server/Skills/rn-library-issue-generalizer/SKILL.md +2 -2
  214. package/vendor/Server/Skills/rn-library-known-issue-check/SKILL.md +2 -2
  215. package/vendor/Server/Skills/rnoh-lib-demo-coverage/SKILL.md +1 -2
  216. package/vendor/Server/Skills/rnoh-lib-test-align/references/port-test-demo-guide.md +1 -1
  217. package/vendor/Server/Skills/rnoh-lib-xts-coverage/SKILL.md +1 -2
  218. package/vendor/Server/package.json +5 -1
  219. package/vendor/VERSION +3 -3
@@ -0,0 +1,94 @@
1
+ /**
2
+ * index-state.js — 索引状态管理
3
+ *
4
+ * 判断索引是否就绪、是否需要重建(版本变更、词典指纹变更等),
5
+ * 并持久化索引元数据到 meta.json。
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import { INDEX_VERSION } from '../constants.js';
10
+
11
+ /**
12
+ * @typedef {Object} IndexMeta
13
+ * @property {string} indexVersion
14
+ * @property {string} termsHash
15
+ * @property {string} synonymsHash
16
+ * @property {number} segmentCount
17
+ * @property {number} builtAt
18
+ */
19
+
20
+ /** @typedef {'no-index'|'engine-upgraded'|'terms-changed'|'synonyms-changed'} RebuildReason */
21
+
22
+ export default class IndexState {
23
+ /**
24
+ * @param {string} dbPath
25
+ * @param {string} metaPath
26
+ * @param {import('../tokenizer/lexicon.js').default} lexicon
27
+ */
28
+ constructor(dbPath, metaPath, lexicon) {
29
+ this.dbPath = dbPath;
30
+ this.metaPath = metaPath;
31
+ this.lexicon = lexicon;
32
+ }
33
+
34
+ /**
35
+ * 索引是否就绪(DB 文件存在且有段数据)。
36
+ * @returns {Promise<boolean>}
37
+ */
38
+ async isReady() {
39
+ if (!fs.existsSync(this.dbPath)) return false;
40
+ const meta = await this.readMeta();
41
+ if (!meta || meta.segmentCount === 0) return false;
42
+ return true;
43
+ }
44
+
45
+ /**
46
+ * 判断是否需要重建及原因。
47
+ * @returns {Promise<RebuildReason|null>}
48
+ */
49
+ async needsRebuild() {
50
+ const meta = await this.readMeta();
51
+ if (!meta || meta.segmentCount === 0) return 'no-index';
52
+ if (meta.indexVersion !== INDEX_VERSION) return 'engine-upgraded';
53
+ if (meta.termsHash !== this.lexicon.getTermsHash()) return 'terms-changed';
54
+ if (meta.synonymsHash !== this.lexicon.getSynonymsHash()) return 'synonyms-changed';
55
+ return null;
56
+ }
57
+
58
+ /**
59
+ * 读取元数据。
60
+ * @returns {Promise<IndexMeta|null>}
61
+ */
62
+ async readMeta() {
63
+ try {
64
+ const raw = await fs.promises.readFile(this.metaPath, 'utf-8');
65
+ return JSON.parse(raw);
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * 写入元数据。
73
+ * @param {IndexMeta} meta
74
+ * @returns {Promise<void>}
75
+ */
76
+ async writeMeta(meta) {
77
+ await fs.promises.writeFile(this.metaPath, JSON.stringify(meta, null, 2), 'utf-8');
78
+ }
79
+
80
+ /**
81
+ * 更新构建完成后的元数据。
82
+ * @param {number} segmentCount
83
+ * @returns {Promise<void>}
84
+ */
85
+ async markBuilt(segmentCount) {
86
+ await this.writeMeta({
87
+ indexVersion: INDEX_VERSION,
88
+ termsHash: this.lexicon.getTermsHash(),
89
+ synonymsHash: this.lexicon.getSynonymsHash(),
90
+ segmentCount,
91
+ builtAt: Date.now(),
92
+ });
93
+ }
94
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * search-text-builder.js — 搜索文本组装
3
+ *
4
+ * 为每个 segment 按字符预算组装 search_text。
5
+ * 标题和 API 符号占比较高,确保精确匹配优先。
6
+ *
7
+ * 段级与文档级有不同的预算(见 constants.js)。
8
+ */
9
+
10
+ import {
11
+ BUDGET_TITLE,
12
+ BUDGET_API_SYMBOLS,
13
+ BUDGET_HEADINGS,
14
+ BUDGET_BODY,
15
+ SECTION_BUDGET_TITLE,
16
+ SECTION_BUDGET_API_SYMBOLS,
17
+ SECTION_BUDGET_HEADINGS,
18
+ SECTION_BUDGET_BODY,
19
+ } from '../constants.js';
20
+
21
+ /**
22
+ * 为 segment 构建 search_text。
23
+ * @param {import('../parser/markdown-splitter.js').DocumentIndexSource} source
24
+ * @param {import('../tokenizer/tokenizer.js').default} tokenizer
25
+ * @returns {Promise<string>}
26
+ */
27
+ export async function buildSearchText(source, tokenizer) {
28
+ const isSection = Boolean(source.sectionTitle);
29
+
30
+ const budgets = isSection
31
+ ? {
32
+ title: SECTION_BUDGET_TITLE,
33
+ api: SECTION_BUDGET_API_SYMBOLS,
34
+ headings: SECTION_BUDGET_HEADINGS,
35
+ body: SECTION_BUDGET_BODY,
36
+ }
37
+ : {
38
+ title: BUDGET_TITLE,
39
+ api: BUDGET_API_SYMBOLS,
40
+ headings: BUDGET_HEADINGS,
41
+ body: BUDGET_BODY,
42
+ };
43
+
44
+ const titleText = (source.titleTokens || []).join(' ');
45
+ const apiText = (source.apiSymbols || []).join(' ');
46
+
47
+ const parts = await Promise.all([
48
+ tokenizeAndCap(titleText, tokenizer, budgets.title),
49
+ capApiSymbols(source.apiSymbols || [], budgets.api),
50
+ tokenizeAndCap(source.headingsText, tokenizer, budgets.headings),
51
+ tokenizeAndCap(source.bodySample, tokenizer, budgets.body),
52
+ ]);
53
+
54
+ return parts.filter(Boolean).join(' ');
55
+ }
56
+
57
+ /**
58
+ * 分词后截断到指定字符数。
59
+ * @param {string} text
60
+ * @param {import('../tokenizer/tokenizer.js').default} tokenizer
61
+ * @param {number} maxChars
62
+ * @returns {Promise<string>}
63
+ */
64
+ async function tokenizeAndCap(text, tokenizer, maxChars) {
65
+ if (!text) return '';
66
+ const tokens = await tokenizer.tokenizeForIndex(text);
67
+ return capTokens(tokens, maxChars);
68
+ }
69
+
70
+ /**
71
+ * API 符号直接拼接截断(不分词,保留完整性)。
72
+ * @param {string[]} symbols
73
+ * @param {number} maxChars
74
+ * @returns {string}
75
+ */
76
+ function capApiSymbols(symbols, maxChars) {
77
+ if (!symbols || symbols.length === 0) return '';
78
+ /** @type {string[]} */
79
+ const out = [];
80
+ let total = 0;
81
+ for (const sym of symbols) {
82
+ if (total + sym.length + 1 > maxChars) break;
83
+ out.push(sym);
84
+ total += sym.length + 1;
85
+ }
86
+ return out.join(' ');
87
+ }
88
+
89
+ /**
90
+ * token 数组截断到指定字符数。
91
+ * @param {string[]} tokens
92
+ * @param {number} maxChars
93
+ * @returns {string}
94
+ */
95
+ function capTokens(tokens, maxChars) {
96
+ if (!tokens || tokens.length === 0) return '';
97
+ /** @type {string[]} */
98
+ const out = [];
99
+ let total = 0;
100
+ for (const token of tokens) {
101
+ if (total + token.length + 1 > maxChars) break;
102
+ out.push(token);
103
+ total += token.length + 1;
104
+ }
105
+ return out.join(' ');
106
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * index.js — 对外导出入口
3
+ *
4
+ * 导出核心类 DocSearchEngine 和相关类型/常量,
5
+ * 供外部 SDK / 库形式调用。
6
+ *
7
+ * 使用示例:
8
+ * import { DocSearchEngine } from './Archive/src/index.js';
9
+ *
10
+ * const engine = new DocSearchEngine();
11
+ * await engine.init({ dataDir: '/data/my-docs' });
12
+ * await engine.addDocument({ documentId: 'guides/intro', catalog: 'guides', content: '# 入门' });
13
+ * const results = await engine.search({ query: '入门', limit: 5 });
14
+ * await engine.close();
15
+ */
16
+
17
+ export { default as DocSearchEngine } from './engine.js';
18
+ export { default } from './engine.js';
19
+
20
+ export { DEFAULT_CATALOGS, INDEX_VERSION } from './constants.js';
21
+
22
+ export { PathSafetyError } from './utils/path-safety.js';
@@ -0,0 +1,121 @@
1
+ /**
2
+ * api-identifiers.js — API 符号提取与匹配
3
+ *
4
+ * 从文本和代码块中提取 API 符号,用于索引阶段增强检索召回。
5
+ * 规则:
6
+ * 1. 模块路径:@ohos.camera → camera, @ohos.camera
7
+ * 2. camelCase:cameraManager → cameraManager, cameramanager
8
+ * 3. PascalCase:CameraManager(过滤太通用的词)
9
+ * 4. 装饰器:@Component, @Builder
10
+ *
11
+ * 提取后去除被包含的子串(如 "camera" 被 "cameraManager" 包含时去掉)。
12
+ */
13
+
14
+ /** 太通用的 PascalCase 词,过滤掉 */
15
+ const GENERIC = new Set([
16
+ 'API', 'Type', 'Value', 'Error', 'Action', 'Method', 'Class',
17
+ 'Object', 'String', 'Number', 'Boolean', 'Array', 'Function',
18
+ 'Param', 'Params', 'Config', 'Options', 'Result', 'Data', 'Info',
19
+ ]);
20
+
21
+ /** 缩写词保留 */
22
+ const ACRONYMS = new Set([
23
+ 'HTTP', 'URL', 'JSON', 'XML', 'HTML', 'CSS', 'SQL', 'API',
24
+ 'UI', 'UX', 'ID', 'UUID', 'URI', 'SSL', 'TLS', 'DNS', 'TCP',
25
+ 'UDP', 'RPC', 'SDK', 'WASM', 'FTS', 'GIL',
26
+ ]);
27
+
28
+ /**
29
+ * 判断 PascalCase 符号是否有价值(非通用词、足够长)。
30
+ * @param {string} symbol
31
+ * @returns {boolean}
32
+ */
33
+ function isValuable(symbol) {
34
+ if (GENERIC.has(symbol)) return false;
35
+ if (ACRONYMS.has(symbol)) return true;
36
+ if (symbol.length <= 3) return false;
37
+ return /^[A-Z]/.test(symbol);
38
+ }
39
+
40
+ /**
41
+ * 为符号添加大小写变体。
42
+ * @param {Set<string>} set
43
+ * @param {string} symbol
44
+ */
45
+ function appendVariants(set, symbol) {
46
+ if (!symbol) return;
47
+ set.add(symbol);
48
+ // camelCase → 全小写
49
+ if (/^[a-z]/.test(symbol)) {
50
+ set.add(symbol.toLowerCase());
51
+ }
52
+ }
53
+
54
+ /**
55
+ * 去除被其他符号包含的子串。
56
+ * 例如 "camera" 被 "cameraManager" 包含时去掉 "camera"。
57
+ * @param {string[]} symbols
58
+ * @returns {string[]}
59
+ */
60
+ function filterSubsumed(symbols) {
61
+ /** @type {string[]} */
62
+ const result = [];
63
+ const sorted = [...symbols].sort((a, b) => b.length - a.length);
64
+ for (const sym of sorted) {
65
+ const lower = sym.toLowerCase();
66
+ if (result.some((r) => r.toLowerCase().includes(lower) && r.toLowerCase() !== lower)) {
67
+ continue;
68
+ }
69
+ result.push(sym);
70
+ }
71
+ return result;
72
+ }
73
+
74
+ /**
75
+ * 从文本和代码块中提取 API 符号。
76
+ * @param {string} text
77
+ * @param {string[]} codeBlocks
78
+ * @returns {string[]}
79
+ */
80
+ export function extractApiSymbols(text, codeBlocks) {
81
+ /** @type {Set<string>} */
82
+ const symbols = new Set();
83
+
84
+ for (const source of [text, ...codeBlocks]) {
85
+ if (!source) continue;
86
+
87
+ // 1. 模块路径: @ohos.camera
88
+ for (const mod of source.matchAll(/@[a-z]+\.[a-z][a-zA-Z0-9.]+/gi)) {
89
+ appendVariants(symbols, mod[0]);
90
+ }
91
+
92
+ // 2. camelCase: cameraManager
93
+ for (const camel of source.matchAll(/\b[a-z][a-z0-9]*[A-Z][a-zA-Z0-9]+\b/g)) {
94
+ appendVariants(symbols, camel[0]);
95
+ }
96
+
97
+ // 3. PascalCase: CameraManager
98
+ for (const pascal of source.matchAll(/[A-Z][a-zA-Z0-9]{2,}/g)) {
99
+ if (isValuable(pascal[0])) symbols.add(pascal[0]);
100
+ }
101
+
102
+ // 4. 装饰器: @Component, @Builder
103
+ for (const deco of source.matchAll(/@[A-Z][a-zA-Z]+/g)) {
104
+ symbols.add(deco[0]);
105
+ }
106
+ }
107
+
108
+ return filterSubsumed([...symbols]);
109
+ }
110
+
111
+ /**
112
+ * 判断查询 token 是否匹配文档的 API 符号集合。
113
+ * @param {string} token
114
+ * @param {string[]} apiSymbols
115
+ * @returns {boolean}
116
+ */
117
+ export function matchApiSymbol(token, apiSymbols) {
118
+ if (!token || apiSymbols.length === 0) return false;
119
+ const lower = token.toLowerCase();
120
+ return apiSymbols.some((sym) => sym.toLowerCase() === lower);
121
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * api-suffixes.js — API 命名后缀规则
3
+ *
4
+ * 用于 API 符号提取与查询匹配,识别 HarmonyOS / ArkTS 常见的
5
+ * API 命名后缀(Manager, Controller, Service 等)。
6
+ */
7
+
8
+ /** API 类名常见后缀(用于判断符号是否为 API 类) */
9
+ export const API_SUFFIXES = [
10
+ 'Manager',
11
+ 'Controller',
12
+ 'Service',
13
+ 'Provider',
14
+ 'Helper',
15
+ 'Factory',
16
+ 'Builder',
17
+ 'Observer',
18
+ 'Listener',
19
+ 'Callback',
20
+ 'Interface',
21
+ 'Adapter',
22
+ 'Wrapper',
23
+ 'Handler',
24
+ ];
25
+
26
+ /** API 后缀集合(O(1) 判断) */
27
+ const API_SUFFIX_SET = new Set(API_SUFFIXES);
28
+
29
+ /**
30
+ * 判断符号是否以已知 API 后缀结尾。
31
+ * @param {string} symbol
32
+ * @returns {boolean}
33
+ */
34
+ export function hasApiSuffix(symbol) {
35
+ for (const suffix of API_SUFFIXES) {
36
+ if (symbol.endsWith(suffix) && symbol.length > suffix.length) return true;
37
+ }
38
+ return false;
39
+ }
40
+
41
+ /**
42
+ * 判断符号是否为 Manager 类 API(用于 rerank 强 boost)。
43
+ * @param {string} symbol
44
+ * @returns {boolean}
45
+ */
46
+ export function isManagerApi(symbol) {
47
+ return symbol.endsWith('Manager') && symbol.length > 'Manager'.length;
48
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * heading-normalize.js — Markdown 标题规范化
3
+ *
4
+ * 统一不同 Markdown 变体下的标题文本:
5
+ * - 去掉前导/尾随空白与 Markdown 标记符 (#, *)
6
+ * - 合并连续空白
7
+ * - 去掉行内代码标记(`code` → code)
8
+ * - 折叠链接文本([text](url) → text)
9
+ *
10
+ * 不依赖 AST 解析器,纯文本处理,作为 splitter 的辅助。
11
+ */
12
+
13
+ /**
14
+ * 规范化标题文本。
15
+ * @param {string} raw
16
+ * @returns {string}
17
+ */
18
+ export function normalizeHeading(raw) {
19
+ if (!raw) return '';
20
+ return raw
21
+ .replace(/^#+\s*/, '') // 去掉前导 #
22
+ .replace(/[*_~]+/g, '') // 去掉强调标记
23
+ .replace(/`([^`]*)`/g, '$1') // 行内代码去标记
24
+ .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1') // 链接取文本
25
+ .replace(/\s+/g, ' ')
26
+ .trim();
27
+ }
28
+
29
+ /**
30
+ * 判断标题是否像文件名(不应作为文档标题)。
31
+ * 例如 "getting-started"、"api/camera"。
32
+ * @param {string} title
33
+ * @returns {boolean}
34
+ */
35
+ export function isFilenameLike(title) {
36
+ if (!title) return true;
37
+ // 全小写、含连字符或斜杠、无中文 → 疑似文件名
38
+ if (/^[a-z0-9\-_/.]+$/.test(title) && !title.includes(' ')) return true;
39
+ return false;
40
+ }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * markdown-splitter.js — Markdown 文档拆分为索引段
3
+ *
4
+ * 流程:
5
+ * 1. 按行扫描 Markdown,识别 H1-H6 标题与代码块边界。
6
+ * 2. 按 H4 heading 将文档拆分为多个 segment。
7
+ * 3. 对每个段生成 DocumentIndexSource:
8
+ * sectionTitle / leadText / apiSymbols / headingsText /
9
+ * titleTokens / bodySample / excerptTruncated。
10
+ * 4. 限制单文档最大段数(MAX_SECTIONS_PER_DOC),超出截断。
11
+ *
12
+ * 不依赖 unified/remark 等重型 AST 库,使用轻量正则 + 行扫描,
13
+ * 既满足拆段需求,又减少依赖体积。
14
+ */
15
+
16
+ import {
17
+ MAX_SECTIONS_PER_DOC,
18
+ LEAD_TEXT_CHARS,
19
+ BUDGET_BODY,
20
+ SECTION_BUDGET_BODY,
21
+ } from '../constants.js';
22
+ import { normalizeHeading, isFilenameLike } from './heading-normalize.js';
23
+ import { extractApiSymbols } from './api-identifiers.js';
24
+
25
+ /** H4 标题行正则 */
26
+ const H4_RE = /^####\s+(.*)$/;
27
+ /** 任意标题行正则(捕获层级与文本) */
28
+ const HEADING_RE = /^(#{1,6})\s+(.*)$/;
29
+ /** 代码块围栏正则 */
30
+ const FENCE_RE = /^(```|~~~)/;
31
+
32
+ /**
33
+ * @typedef {Object} DocumentIndexSource
34
+ * @property {string} sectionTitle
35
+ * @property {string} leadText
36
+ * @property {string[]} apiSymbols
37
+ * @property {string} headingsText
38
+ * @property {string[]} titleTokens
39
+ * @property {string} bodySample
40
+ * @property {boolean} excerptTruncated
41
+ */
42
+
43
+ /**
44
+ * 解析文档标题(优先级:传入 > H1 > 文件名兜底)。
45
+ * @param {string} markdown
46
+ * @param {string} [explicitTitle]
47
+ * @param {string} [documentId]
48
+ * @returns {string}
49
+ */
50
+ export function resolveDocTitle(markdown, explicitTitle, documentId) {
51
+ if (explicitTitle && !isFilenameLike(explicitTitle)) return explicitTitle;
52
+
53
+ for (const line of markdown.split('\n')) {
54
+ const m = line.match(/^#\s+(.*)$/);
55
+ if (m) {
56
+ const title = normalizeHeading(m[1]);
57
+ if (title && !isFilenameLike(title)) return title;
58
+ }
59
+ }
60
+
61
+ // 兜底:取 documentId 最后一段
62
+ if (documentId) {
63
+ const last = documentId.split('/').pop() || documentId;
64
+ return last.replace(/[-_]/g, ' ');
65
+ }
66
+ return 'Untitled';
67
+ }
68
+
69
+ /**
70
+ * 将 Markdown 拆分为索引段列表。
71
+ * @param {string} markdown
72
+ * @param {{documentId:string, docTitle:string}} ctx
73
+ * @returns {DocumentIndexSource[]}
74
+ */
75
+ export function splitMarkdown(markdown, ctx) {
76
+ const lines = markdown.split('\n');
77
+
78
+ /** @type {Array<{title:string, level:number, lines:string[]}>} */
79
+ const sections = [];
80
+ /** @type {{title:string, level:number, lines:string[]}|null} */
81
+ let current = { title: '', level: 0, lines: [] };
82
+
83
+ let inFence = false;
84
+
85
+ for (const line of lines) {
86
+ // 代码块边界切换
87
+ if (FENCE_RE.test(line.trim())) {
88
+ inFence = !inFence;
89
+ current.lines.push(line);
90
+ continue;
91
+ }
92
+
93
+ // 代码块内不识别标题
94
+ if (!inFence) {
95
+ const h4 = line.match(H4_RE);
96
+ if (h4) {
97
+ // 结束当前段
98
+ if (current.lines.length > 0 || current.title) {
99
+ sections.push(current);
100
+ }
101
+ current = {
102
+ title: normalizeHeading(h4[1]),
103
+ level: 4,
104
+ lines: [],
105
+ };
106
+ continue;
107
+ }
108
+ }
109
+
110
+ current.lines.push(line);
111
+ }
112
+ // 尾段
113
+ if (current.lines.length > 0 || current.title) {
114
+ sections.push(current);
115
+ }
116
+
117
+ // 限制段数
118
+ const limited = sections.slice(0, MAX_SECTIONS_PER_DOC);
119
+ const truncated = sections.length > MAX_SECTIONS_PER_DOC;
120
+
121
+ /** @type {DocumentIndexSource[]} */
122
+ const sources = limited.map((section, idx) => {
123
+ const body = section.lines.join('\n');
124
+ const isSection = Boolean(section.title);
125
+
126
+ // 提取段内所有子标题
127
+ const headings = [];
128
+ /** @type {string[]} */
129
+ const codeBlocks = [];
130
+ let inCode = false;
131
+ /** @type {string[]} */
132
+ let codeBuf = [];
133
+
134
+ for (const line of section.lines) {
135
+ if (FENCE_RE.test(line.trim())) {
136
+ if (inCode) {
137
+ codeBlocks.push(codeBuf.join('\n'));
138
+ codeBuf = [];
139
+ }
140
+ inCode = !inCode;
141
+ continue;
142
+ }
143
+ if (inCode) {
144
+ codeBuf.push(line);
145
+ continue;
146
+ }
147
+ const h = line.match(HEADING_RE);
148
+ if (h) {
149
+ headings.push(normalizeHeading(h[2]));
150
+ }
151
+ }
152
+
153
+ const leadText = truncateAtWordBoundary(body.replace(/\s+/g, ' ').trim(), LEAD_TEXT_CHARS);
154
+ const apiSymbols = extractApiSymbols(body, codeBlocks);
155
+ const headingsText = headings.filter(Boolean).join(' ');
156
+ const titleTokens = [ctx.docTitle, section.title].filter(Boolean);
157
+ const bodySample = buildBodySample(body, isSection);
158
+
159
+ return {
160
+ sectionTitle: section.title || (idx === 0 ? ctx.docTitle : ''),
161
+ leadText,
162
+ apiSymbols,
163
+ headingsText,
164
+ titleTokens,
165
+ bodySample,
166
+ excerptTruncated: truncated && idx === limited.length - 1,
167
+ };
168
+ });
169
+
170
+ // 保证至少有一段
171
+ if (sources.length === 0) {
172
+ sources.push({
173
+ sectionTitle: ctx.docTitle,
174
+ leadText: '',
175
+ apiSymbols: [],
176
+ headingsText: '',
177
+ titleTokens: [ctx.docTitle],
178
+ bodySample: '',
179
+ excerptTruncated: false,
180
+ });
181
+ }
182
+
183
+ return sources;
184
+ }
185
+
186
+ /**
187
+ * 在词边界处截断文本。
188
+ * @param {string} text
189
+ * @param {number} maxLen
190
+ * @returns {string}
191
+ */
192
+ function truncateAtWordBoundary(text, maxLen) {
193
+ if (text.length <= maxLen) return text;
194
+ const slice = text.slice(0, maxLen);
195
+ const lastSpace = slice.lastIndexOf(' ');
196
+ if (lastSpace > maxLen * 0.5) return slice.slice(0, lastSpace);
197
+ return slice;
198
+ }
199
+
200
+ /**
201
+ * 构建正文样本(head + tail)。
202
+ * 段级与文档级有不同的预算。
203
+ * @param {string} body
204
+ * @param {boolean} isSection
205
+ * @returns {string}
206
+ */
207
+ function buildBodySample(body, isSection) {
208
+ const budget = isSection ? SECTION_BUDGET_BODY : BUDGET_BODY;
209
+ const normalized = body.replace(/\s+/g, ' ').trim();
210
+ if (normalized.length <= budget) return normalized;
211
+
212
+ const headLen = Math.floor(budget * 0.7);
213
+ const tailLen = budget - headLen;
214
+ return normalized.slice(0, headLen) + ' ' + normalized.slice(-tailLen);
215
+ }