@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,64 @@
1
+ /**
2
+ * snippet.js — 搜索结果摘要提取
3
+ *
4
+ * 以查询词命中位置为中心,截取内容片段作为摘要。
5
+ * 在词边界截断,前后加省略号标记。
6
+ */
7
+
8
+ import { SNIPPET_MAX_CHARS, SNIPPET_CONTEXT_CHARS } from '../constants.js';
9
+
10
+ /**
11
+ * 提取摘要。
12
+ * @param {string} content 原始内容
13
+ * @param {string} query 查询词(用于定位中心)
14
+ * @param {number} [maxLen] 摘要最大字符数
15
+ * @param {number} [contextChars] 命中位置前的上下文字符数
16
+ * @returns {string}
17
+ */
18
+ export function extractSnippet(
19
+ content,
20
+ query,
21
+ maxLen = SNIPPET_MAX_CHARS,
22
+ contextChars = SNIPPET_CONTEXT_CHARS
23
+ ) {
24
+ if (!content) return '';
25
+ const normalized = content.replace(/\s+/g, ' ').trim();
26
+ if (normalized.length <= maxLen) return normalized;
27
+
28
+ // 找到查询词在内容中的位置
29
+ const tokens = query.split(/\s+/).filter(Boolean);
30
+ let bestIndex = -1;
31
+ const lowerContent = normalized.toLowerCase();
32
+ for (const token of tokens) {
33
+ const idx = lowerContent.indexOf(token.toLowerCase());
34
+ if (idx >= 0) {
35
+ bestIndex = idx;
36
+ break;
37
+ }
38
+ }
39
+
40
+ // 没命中则从头截取
41
+ const start = bestIndex >= 0 ? Math.max(0, bestIndex - contextChars) : 0;
42
+ let end = Math.min(normalized.length, start + maxLen);
43
+
44
+ // 在窗口内找词边界截断
45
+ const lastBreak = findLastWordBoundary(normalized.slice(start, end), maxLen - 25);
46
+ if (lastBreak >= 0) end = start + lastBreak;
47
+
48
+ const prefix = start > 0 ? '...' : '';
49
+ const suffix = end < normalized.length ? '...' : '';
50
+ return `${prefix}${normalized.slice(start, end).trim()}${suffix}`;
51
+ }
52
+
53
+ /**
54
+ * 在文本中找最后一个词边界位置(空格或标点)。
55
+ * @param {string} text
56
+ * @param {number} minPos 最小位置
57
+ * @returns {number}
58
+ */
59
+ function findLastWordBoundary(text, minPos) {
60
+ for (let i = text.length - 1; i >= minPos; i--) {
61
+ if (/\s|[,。;,.;!?、]/.test(text[i])) return i;
62
+ }
63
+ return -1;
64
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * dir-store.js — 目录存储方案
3
+ *
4
+ * 将文档以 .md 文件形式存放在 docs/{catalog}/{path}.md。
5
+ * 优势:写入快、调试方便、可 git 管理。
6
+ *
7
+ * 路径穿越防护:documentId 中禁止 .. 和绝对路径。
8
+ */
9
+
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import DocStore from './doc-store.js';
13
+ import { PathSafetyError } from '../utils/path-safety.js';
14
+
15
+ export default class DirectoryDocStore extends DocStore {
16
+ /**
17
+ * @param {string} docsDir 文档根目录
18
+ */
19
+ constructor(docsDir) {
20
+ super();
21
+ this.docsDir = docsDir;
22
+ }
23
+
24
+ /**
25
+ * 写入文档(自动创建父目录)。
26
+ * @param {string} documentId
27
+ * @param {string} content
28
+ * @returns {Promise<void>}
29
+ */
30
+ async write(documentId, content) {
31
+ const filePath = this.resolvePath(documentId);
32
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
33
+ await fs.promises.writeFile(filePath, content, 'utf-8');
34
+ }
35
+
36
+ /**
37
+ * 读取文档。不存在时返回 null。
38
+ * @param {string} documentId
39
+ * @returns {Promise<string|null>}
40
+ */
41
+ async read(documentId) {
42
+ const filePath = this.resolvePath(documentId);
43
+ try {
44
+ return await fs.promises.readFile(filePath, 'utf-8');
45
+ } catch (error) {
46
+ if (/** @type {NodeJS.ErrnoException} */ (error).code === 'ENOENT') return null;
47
+ throw error;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * 删除文档(不存在时静默)。
53
+ * @param {string} documentId
54
+ * @returns {Promise<void>}
55
+ */
56
+ async delete(documentId) {
57
+ const filePath = this.resolvePath(documentId);
58
+ await fs.promises.rm(filePath, { force: true });
59
+ }
60
+
61
+ /**
62
+ * 列出所有文档 ID(递归遍历)。
63
+ * @returns {Promise<string[]>}
64
+ */
65
+ async list() {
66
+ /** @type {string[]} */
67
+ const results = [];
68
+
69
+ const walk = async (dir, prefix) => {
70
+ let entries;
71
+ try {
72
+ entries = await fs.promises.readdir(dir, { withFileTypes: true });
73
+ } catch (error) {
74
+ if (/** @type {NodeJS.ErrnoException} */ (error).code === 'ENOENT') return;
75
+ throw error;
76
+ }
77
+ for (const entry of entries) {
78
+ const fullPath = path.join(dir, entry.name);
79
+ if (entry.isDirectory()) {
80
+ await walk(fullPath, `${prefix}${entry.name}/`);
81
+ } else if (entry.name.endsWith('.md')) {
82
+ results.push(`${prefix}${entry.name.replace(/\.md$/, '')}`);
83
+ }
84
+ }
85
+ };
86
+
87
+ await walk(this.docsDir, '');
88
+ return results;
89
+ }
90
+
91
+ /**
92
+ * 将 documentId 解析为文件路径(含路径穿越防护)。
93
+ * @param {string} documentId
94
+ * @returns {string}
95
+ */
96
+ resolvePath(documentId) {
97
+ const normalized = documentId.replace(/\\/g, '/');
98
+ if (normalized.split('/').includes('..')) {
99
+ throw new PathSafetyError('Invalid document ID: path traversal detected.');
100
+ }
101
+ return path.join(this.docsDir, `${normalized}.md`);
102
+ }
103
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * doc-store.js — 文档仓库抽象接口
3
+ *
4
+ * 定义文档存储的统一接口(write / read / delete / list),
5
+ * 使存储方式可插拔(目录、zip、内存等),上层不感知具体实现。
6
+ */
7
+
8
+ /**
9
+ * @typedef {Object} DocStore
10
+ * @property {(documentId:string, content:string) => Promise<void>} write
11
+ * @property {(documentId:string) => Promise<string|null>} read
12
+ * @property {(documentId:string) => Promise<void>} delete
13
+ * @property {() => Promise<string[]>} list
14
+ */
15
+
16
+ export default class DocStore {
17
+ /** @param {string} _documentId @param {string} _content */
18
+ async write(_documentId, _content) {
19
+ throw new Error('DocStore.write() not implemented');
20
+ }
21
+
22
+ /** @param {string} _documentId @returns {Promise<string|null>} */
23
+ async read(_documentId) {
24
+ throw new Error('DocStore.read() not implemented');
25
+ }
26
+
27
+ /** @param {string} _documentId */
28
+ async delete(_documentId) {
29
+ throw new Error('DocStore.delete() not implemented');
30
+ }
31
+
32
+ /** @returns {Promise<string[]>} */
33
+ async list() {
34
+ throw new Error('DocStore.list() not implemented');
35
+ }
36
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * lexicon.js — 词典文件管理
3
+ *
4
+ * 读取领域术语词典、同义词表、停用词表;
5
+ * 支持同义词扩展与词典指纹计算(用于判断是否需要重建索引)。
6
+ *
7
+ * 词典文件位置由 lexiconDir 指定,支持自定义覆盖内置默认词典。
8
+ */
9
+
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import { sha256Text } from '../utils/hash.js';
13
+
14
+ export default class Lexicon {
15
+ /**
16
+ * @param {string} lexiconDir 词典目录
17
+ */
18
+ constructor(lexiconDir) {
19
+ this.lexiconDir = lexiconDir;
20
+ /** @type {Map<string, Set<string>>|null} */
21
+ this.synonymMap = null;
22
+ /** @type {Set<string>|null} */
23
+ this.stopWords = null;
24
+ this.termsHash = null;
25
+ this.synonymsHash = null;
26
+ }
27
+
28
+ /**
29
+ * 读取术语词典(一行一个)。
30
+ * 文件不存在时返回空数组。
31
+ * @returns {string[]}
32
+ */
33
+ readTerms() {
34
+ try {
35
+ const content = fs.readFileSync(path.join(this.lexiconDir, 'terms.txt'), 'utf-8');
36
+ return content.split('\n').map((l) => l.trim()).filter(Boolean);
37
+ } catch {
38
+ return [];
39
+ }
40
+ }
41
+
42
+ /**
43
+ * 读取同义词表(JSON 数组)。
44
+ * @returns {string[][]}
45
+ */
46
+ readSynonyms() {
47
+ try {
48
+ const content = fs.readFileSync(path.join(this.lexiconDir, 'synonyms.json'), 'utf-8');
49
+ const parsed = JSON.parse(content);
50
+ return Array.isArray(parsed) ? parsed : [];
51
+ } catch {
52
+ return [];
53
+ }
54
+ }
55
+
56
+ /**
57
+ * 读取停用词表。
58
+ * @returns {Set<string>}
59
+ */
60
+ readStopWords() {
61
+ if (this.stopWords) return this.stopWords;
62
+ try {
63
+ const content = fs.readFileSync(path.join(this.lexiconDir, 'stopwords.txt'), 'utf-8');
64
+ this.stopWords = new Set(content.split('\n').map((l) => l.trim()).filter(Boolean));
65
+ } catch {
66
+ this.stopWords = new Set();
67
+ }
68
+ return this.stopWords;
69
+ }
70
+
71
+ /**
72
+ * 构建同义词映射(双向)。
73
+ * @returns {Map<string, Set<string>>}
74
+ */
75
+ getSynonymMap() {
76
+ if (this.synonymMap) return this.synonymMap;
77
+
78
+ this.synonymMap = new Map();
79
+ const groups = this.readSynonyms();
80
+ for (const group of groups) {
81
+ if (!Array.isArray(group) || group.length < 2) continue;
82
+ for (const word of group) {
83
+ const key = word.toLowerCase();
84
+ if (!this.synonymMap.has(key)) {
85
+ this.synonymMap.set(key, new Set());
86
+ }
87
+ for (const other of group) {
88
+ if (other !== word) this.synonymMap.get(key).add(other);
89
+ }
90
+ }
91
+ }
92
+ return this.synonymMap;
93
+ }
94
+
95
+ /**
96
+ * 同义词扩展:将查询中的词扩展为同义词集合。
97
+ * @param {string} query 原始查询(空格分隔)
98
+ * @param {number} [maxParts=8] 最大扩展词条数
99
+ * @returns {string} 扩展后的查询(空格分隔)
100
+ */
101
+ expandSynonyms(query, maxParts = 8) {
102
+ const map = this.getSynonymMap();
103
+ const parts = query.split(/\s+/).filter(Boolean);
104
+ /** @type {Set<string>} */
105
+ const expanded = new Set();
106
+
107
+ for (let i = 0; i < parts.length && expanded.size < maxParts; i++) {
108
+ expanded.add(parts[i]);
109
+ const syns = map.get(parts[i].toLowerCase());
110
+ if (syns) {
111
+ for (const s of syns) {
112
+ if (expanded.size >= maxParts) break;
113
+ expanded.add(s);
114
+ }
115
+ }
116
+ }
117
+ return [...expanded].join(' ');
118
+ }
119
+
120
+ /**
121
+ * 术语词典指纹(SHA256)。
122
+ * @returns {string}
123
+ */
124
+ getTermsHash() {
125
+ if (!this.termsHash) {
126
+ try {
127
+ const content = fs.readFileSync(path.join(this.lexiconDir, 'terms.txt'), 'utf-8');
128
+ this.termsHash = sha256Text(content);
129
+ } catch {
130
+ this.termsHash = 'empty';
131
+ }
132
+ }
133
+ return this.termsHash;
134
+ }
135
+
136
+ /**
137
+ * 同义词表指纹(SHA256)。
138
+ * @returns {string}
139
+ */
140
+ getSynonymsHash() {
141
+ if (!this.synonymsHash) {
142
+ try {
143
+ const content = fs.readFileSync(path.join(this.lexiconDir, 'synonyms.json'), 'utf-8');
144
+ this.synonymsHash = sha256Text(content);
145
+ } catch {
146
+ this.synonymsHash = 'empty';
147
+ }
148
+ }
149
+ return this.synonymsHash;
150
+ }
151
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * tokenizer.js — jieba-wasm 分词器
3
+ *
4
+ * 职责:
5
+ * - 索引时使用搜索引擎模式(cutForSearch,更细粒度,高召回)。
6
+ * - 查询时使用精确模式(cut,高准确率)。
7
+ * - 合并装饰器(@ + Component → @Component)。
8
+ * - 去停用词、保留大小写变体。
9
+ *
10
+ * WASM 模块懒加载 + 单例缓存;若 jieba-wasm 未安装,
11
+ * 回退到基于正则的简易分词(中英文按词切分),保证可用性。
12
+ */
13
+
14
+ import path from 'node:path';
15
+
16
+ export default class Tokenizer {
17
+ /**
18
+ * @param {string} lexiconDir 词典目录
19
+ * @param {import('../tokenizer/lexicon.js').default} lexicon 词典管理器
20
+ * @param {string[]} [customTerms=[]] 自定义术语
21
+ */
22
+ constructor(lexiconDir, lexicon, customTerms = []) {
23
+ this.lexiconDir = lexiconDir;
24
+ this.lexicon = lexicon;
25
+ this.customTerms = customTerms;
26
+ /** @type {any|null} */
27
+ this.jieba = null;
28
+ this.initPromise = null;
29
+ }
30
+
31
+ /**
32
+ * 初始化 jieba-wasm(懒加载单例)。
33
+ * @returns {Promise<void>}
34
+ */
35
+ async init() {
36
+ await this.getJieba();
37
+ }
38
+
39
+ /**
40
+ * 获取 jieba 实例(带并发安全)。
41
+ * @returns {Promise<any>}
42
+ */
43
+ async getJieba() {
44
+ if (this.jieba) return this.jieba;
45
+ if (!this.initPromise) {
46
+ this.initPromise = this.createJieba().then((j) => {
47
+ this.jieba = j;
48
+ return j;
49
+ });
50
+ }
51
+ return this.initPromise;
52
+ }
53
+
54
+ /**
55
+ * 创建 jieba 实例,加载领域词典。
56
+ * 失败时回退到简易分词。
57
+ * @returns {Promise<any>}
58
+ */
59
+ async createJieba() {
60
+ try {
61
+ const mod = await import('jieba-wasm');
62
+ const { cut, cut_for_search, with_dict } = mod;
63
+
64
+ // 加载领域词典
65
+ const terms = this.lexicon.readTerms();
66
+ const allTerms = [...terms, ...this.customTerms];
67
+ if (allTerms.length > 0 && typeof with_dict === 'function') {
68
+ with_dict(allTerms.join('\n'));
69
+ }
70
+
71
+ return {
72
+ cut: (text, hmm) => cut(text, hmm),
73
+ cutForSearch: (text, hmm) => cut_for_search(text, hmm),
74
+ };
75
+ } catch {
76
+ // 回退:简易分词
77
+ return {
78
+ cut: (text) => simpleTokenize(text),
79
+ cutForSearch: (text) => simpleTokenize(text),
80
+ };
81
+ }
82
+ }
83
+
84
+ /**
85
+ * 索引时分词(搜索引擎模式,细粒度)。
86
+ * @param {string} text
87
+ * @returns {Promise<string[]>}
88
+ */
89
+ async tokenizeForIndex(text) {
90
+ const jieba = await this.getJieba();
91
+ return this.normalize(jieba.cutForSearch(text, true));
92
+ }
93
+
94
+ /**
95
+ * 查询时分词(精确模式)。
96
+ * @param {string} text
97
+ * @returns {Promise<string[]>}
98
+ */
99
+ async tokenizeForQuery(text) {
100
+ const jieba = await this.getJieba();
101
+ return this.normalize(jieba.cut(text, true));
102
+ }
103
+
104
+ /**
105
+ * 添加自定义术语(影响 jieba 分词)。
106
+ * @param {string[]} words
107
+ */
108
+ addTerms(words) {
109
+ this.customTerms.push(...words);
110
+ // 重置 jieba 以便下次重新加载词典
111
+ this.jieba = null;
112
+ this.initPromise = null;
113
+ }
114
+
115
+ /**
116
+ * 规范化 token:合并装饰器、去停用词、保留大小写变体。
117
+ * @param {string[]} tokens
118
+ * @returns {string[]}
119
+ */
120
+ normalize(tokens) {
121
+ const stops = this.lexicon.readStopWords();
122
+ /** @type {string[]} */
123
+ const out = [];
124
+ for (const raw of this.mergeDecorators(tokens)) {
125
+ const token = raw.trim();
126
+ if (!token || stops.has(token)) continue;
127
+ out.push(token.toLowerCase());
128
+ // 保留原始大小写(FTS5 对大小写敏感)
129
+ if (/[A-Z]/.test(token)) out.push(token);
130
+ }
131
+ return out;
132
+ }
133
+
134
+ /**
135
+ * 合并 "@" + "Component" → "@Component"。
136
+ * @param {string[]} tokens
137
+ * @returns {string[]}
138
+ */
139
+ mergeDecorators(tokens) {
140
+ /** @type {string[]} */
141
+ const merged = [];
142
+ for (let i = 0; i < tokens.length; i++) {
143
+ const current = tokens[i]?.trim();
144
+ const next = tokens[i + 1]?.trim();
145
+ if (current === '@' && next && /^[A-Z][a-zA-Z0-9]*$/.test(next)) {
146
+ merged.push(`@${next}`);
147
+ i++;
148
+ } else if (current) {
149
+ merged.push(current);
150
+ }
151
+ }
152
+ return merged;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * 简易分词回退方案(jieba-wasm 不可用时使用)。
158
+ * 中文按字符切分,英文/数字按词切分。
159
+ * @param {string} text
160
+ * @returns {string[]}
161
+ */
162
+ function simpleTokenize(text) {
163
+ if (!text) return [];
164
+ // 匹配:英文词、数字、中文字符
165
+ const matches = text.match(/[a-zA-Z_][a-zA-Z0-9_]*|\d+|[\u4e00-\u9fa5]/g);
166
+ return matches || [];
167
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * types.js — 类型定义(JSDoc 形式)
3
+ *
4
+ * 本文件以 JSDoc typedef 定义对外接口的数据结构,
5
+ * 供 IDE 智能提示和调用方文档参考,不产生运行时副作用。
6
+ */
7
+
8
+ /**
9
+ * @typedef {Object} CatalogConfig
10
+ * @property {string} id 目录标识,如 'guides'
11
+ * @property {string} title 目录中文名
12
+ * @property {number} [searchBoost] 搜索权重倍数(>1 提升)
13
+ * @property {boolean} [tailInGlobalSearch] 全局搜索时是否排到尾部
14
+ */
15
+
16
+ /**
17
+ * @typedef {Object} EngineOptions
18
+ * @property {string} dataDir 数据根目录
19
+ * @property {CatalogConfig[]} [catalogs] 目录配置
20
+ * @property {string} [lexiconDir] 词典目录(不传用内置默认)
21
+ * @property {'directory'|'zip'} [storageMode] 存储方式,默认 directory
22
+ * @property {string} [dbPath] 索引数据库路径
23
+ * @property {boolean} [autoIndex=true] 写入时自动分词建索引
24
+ * @property {number} [maxSectionsPerDoc=28] 单文档最大段数
25
+ * @property {number} [defaultLimit=10] 搜索默认上限
26
+ */
27
+
28
+ /**
29
+ * @typedef {Object} DocumentInput
30
+ * @property {string} documentId 文档唯一标识
31
+ * @property {string} catalog 所属目录 ID
32
+ * @property {string} [title] 文档标题
33
+ * @property {string} content Markdown 原文
34
+ * @property {Record<string,string>} [metadata] 自定义元数据
35
+ */
36
+
37
+ /**
38
+ * @typedef {Object} SearchQuery
39
+ * @property {string} query 搜索关键词(空格分隔)
40
+ * @property {string} [catalog] 限定目录
41
+ * @property {number} [limit] 结果数量上限
42
+ * @property {boolean} [includeSnippet=true] 是否包含摘要
43
+ * @property {number} [snippetMaxChars=200] 摘要最大字符数
44
+ */
45
+
46
+ /**
47
+ * @typedef {Object} SearchOptions
48
+ * @property {boolean} [preferAnd] 是否优先 AND 匹配
49
+ * @property {boolean} [expandSynonyms=true] 是否启用同义词扩展
50
+ * @property {number} [synonymExpandLimit=8] 同义词扩展上限
51
+ */
52
+
53
+ /**
54
+ * @typedef {Object} SearchResult
55
+ * @property {string} documentId
56
+ * @property {string} title
57
+ * @property {string} sectionTitle
58
+ * @property {string} snippet
59
+ * @property {number} score 相关度(越小越相关)
60
+ * @property {string} catalog
61
+ * @property {number} [rawScore] Rerank 前 BM25 分数
62
+ */
63
+
64
+ /**
65
+ * @typedef {Object} DocumentRecord
66
+ * @property {string} documentId
67
+ * @property {string} title
68
+ * @property {string} catalog
69
+ * @property {string} content
70
+ * @property {number} segmentCount
71
+ * @property {Record<string,string>} [metadata]
72
+ * @property {number} createdAt
73
+ * @property {number} updatedAt
74
+ */
75
+
76
+ /**
77
+ * @typedef {Object} EngineStatus
78
+ * @property {boolean} ready
79
+ * @property {number} documentCount
80
+ * @property {number} segmentCount
81
+ * @property {number} dbSizeBytes
82
+ * @property {string} indexVersion
83
+ * @property {number|null} lastBuiltAt
84
+ * @property {boolean} isWriting
85
+ * @property {boolean} dbSizeWarning — DB 大小超过 GIT_WARN_BYTES 时为 true
86
+ */
@@ -0,0 +1,16 @@
1
+ /**
2
+ * hash.js — SHA256 文本哈希工具
3
+ *
4
+ * 用于词典指纹计算(判断是否需要重建索引)。
5
+ */
6
+
7
+ import { createHash } from 'node:crypto';
8
+
9
+ /**
10
+ * 计算文本的 SHA256 指纹(十六进制字符串)。
11
+ * @param {string} text
12
+ * @returns {string} 64 位十六进制哈希
13
+ */
14
+ export function sha256Text(text) {
15
+ return createHash('sha256').update(text, 'utf-8').digest('hex');
16
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * lock.js — 简易文件锁
3
+ *
4
+ * 基于原子性 `fs.open(..., 'wx')` 实现独占写入锁。
5
+ * 写入 PID 与时间戳,崩溃后可通过 stale 时间自动复用。
6
+ *
7
+ * 返回 release 函数供 finally 调用,确保锁释放。
8
+ */
9
+
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import { LOCK_STALE_MS } from '../constants.js';
13
+
14
+ /**
15
+ * 获取一个文件锁。
16
+ * @param {string} lockPath 锁文件路径
17
+ * @param {number} [staleMs] stale 时间(毫秒)
18
+ * @returns {Promise<() => Promise<void>>} release 函数
19
+ */
20
+ export async function acquireLock(lockPath, staleMs = LOCK_STALE_MS) {
21
+ await fs.promises.mkdir(path.dirname(lockPath), { recursive: true });
22
+
23
+ // 轮询获取锁(带 stale 检测)
24
+ // eslint-disable-next-line no-constant-condition
25
+ while (true) {
26
+ try {
27
+ const handle = await fs.promises.open(lockPath, 'wx');
28
+ const payload = JSON.stringify({ pid: process.pid, ts: Date.now() });
29
+ await handle.writeFile(payload, 'utf-8');
30
+ await handle.close();
31
+ return () => fs.promises.unlink(lockPath).catch(() => {});
32
+ } catch (error) {
33
+ if (/** @type {NodeJS.ErrnoException} */ (error).code !== 'EEXIST') {
34
+ throw error;
35
+ }
36
+ }
37
+
38
+ // 锁已被占用,判断是否 stale
39
+ const stale = await isStale(lockPath, staleMs);
40
+ if (stale) {
41
+ // 强制释放后下一轮重试
42
+ await fs.promises.unlink(lockPath).catch(() => {});
43
+ continue;
44
+ }
45
+
46
+ // 等待后重试
47
+ await sleep(200);
48
+ }
49
+ }
50
+
51
+ /**
52
+ * 判断锁是否过期。
53
+ * @param {string} lockPath
54
+ * @param {number} staleMs
55
+ * @returns {Promise<boolean>}
56
+ */
57
+ async function isStale(lockPath, staleMs) {
58
+ try {
59
+ const raw = await fs.promises.readFile(lockPath, 'utf-8');
60
+ const info = JSON.parse(raw);
61
+ return Date.now() - info.ts > staleMs;
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * @param {number} ms
69
+ * @returns {Promise<void>}
70
+ */
71
+ function sleep(ms) {
72
+ return new Promise((resolve) => setTimeout(resolve, ms));
73
+ }