@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,684 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @tool flutter_analyze_xts_coverage
4
+ * @description 分析 Flutter 三方库的测试用例覆盖率。从库源码提取接口规格,扫描 test/ + example/test/ + example/integration_test/ 下的测试源码,执行正则覆盖率匹配,输出 Markdown 覆盖率报告。用于评估测试用例对 Flutter 库 API 的覆盖程度。
5
+ * @param libroot: string - 库根目录路径(包含 lib/ 和 test/ 子目录)
6
+ * @param spec: string - 已有的 interface-spec.json 路径(可选,与 libroot 二选一)
7
+ * @param output: string - 输出 Markdown 报告路径(可选,默认 03-xxx-XTS覆盖率报告.md)
8
+ * @hidden
9
+ * flutter-analyze-xts-coverage.cjs — Flutter 测试用例覆盖率分析脚本
10
+ *
11
+ * 从库源码提取接口规格,针对测试源码目录(test/ + example/test/ + example/integration_test/)
12
+ * 执行正则覆盖率匹配,直接渲染输出 Markdown 覆盖率报告。
13
+ *
14
+ * 用法:
15
+ * 方式一(读 JSON 中间件):
16
+ * node flutter-analyze-xts-coverage.cjs --spec <interface-spec.json> --libroot <库根目录> \
17
+ * [--output <report.md>] [--title <标题>]
18
+ * 方式二(直接从源码提取):
19
+ * node flutter-analyze-xts-coverage.cjs --libroot <库根目录> \
20
+ * [--output <report.md>] [--title <标题>]
21
+ *
22
+ * 参数:
23
+ * --spec interface-spec.json 路径
24
+ * --libroot 库根目录(测试源码默认取 <libroot>/test + <libroot>/example/test + <libroot>/example/integration_test)
25
+ * --output 输出 Markdown 报告路径,默认 03-xxx-XTS覆盖率报告.md
26
+ * --title 报告标题
27
+ * --lib 库名覆盖
28
+ *
29
+ * 使用示例:
30
+ * node flutter-analyze-xts-coverage.cjs --libroot /path/to/advertising_id --output 03-advertising_id-XTS覆盖率报告.md
31
+ * node flutter-analyze-xts-coverage.cjs --spec advertising_id-interface-spec.json --libroot /path/to/advertising_id
32
+ */
33
+
34
+ const fs = require('fs');
35
+ const path = require('path');
36
+ const { generateSpec, detectMainPackage, parsePubspec } = require('./flutter-extract-interfaces.cjs');
37
+
38
+ // ── 参数解析 + 工具函数 ──────────────────────────────────────
39
+
40
+ function parseArgs() {
41
+ const args = process.argv.slice(2);
42
+ // MCP Gateway 模式:第一个参数是 JSON 字符串
43
+ if (args.length > 0 && args[0].startsWith('{')) {
44
+ try { return JSON.parse(args[0]); } catch (e) { /* 降级到 CLI 模式 */ }
45
+ }
46
+ const parsed = {};
47
+ for (let i = 0; i < args.length; i++) {
48
+ if (args[i].startsWith('--')) {
49
+ parsed[args[i].substring(2)] = args[i + 1];
50
+ i++;
51
+ }
52
+ }
53
+ return parsed;
54
+ }
55
+
56
+ function escapeRegex(str) {
57
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
58
+ }
59
+
60
+ function escapeMd(text) {
61
+ if (!text || text === '-') return '-';
62
+ return text.replace(/\|/g, '\\|').replace(/\n/g, ' ');
63
+ }
64
+
65
+ function formatParams(params) {
66
+ if (!params || params.length === 0) return '';
67
+ return params.map(p => {
68
+ const opt = p.optional ? '?' : '';
69
+ return `${p.name}${opt}: ${escapeMd(p.type)}`;
70
+ }).join(', ');
71
+ }
72
+
73
+ function coverageBadge(rate) {
74
+ const r = parseFloat(rate);
75
+ if (r >= 80) return '高';
76
+ if (r >= 50) return '中';
77
+ if (r >= 20) return '低';
78
+ return '极低';
79
+ }
80
+
81
+ // ── 文件扫描 ──────────────────────────────────────────────
82
+
83
+ const EXCLUDE_DIRS = ['.dart_tool', 'build', '.fvm', 'ios', 'android', 'ohos', 'macos', 'windows', 'web', 'linux'];
84
+
85
+ function walkDir(dir, excludeDirs) {
86
+ const results = [];
87
+ if (!fs.existsSync(dir)) return results;
88
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
89
+ for (const entry of entries) {
90
+ const fullPath = path.join(dir, entry.name);
91
+ if (entry.isDirectory()) {
92
+ if (!excludeDirs.includes(entry.name)) {
93
+ results.push(...walkDir(fullPath, excludeDirs));
94
+ }
95
+ } else if (entry.isFile() && entry.name.endsWith('.dart') && !entry.name.endsWith('.g.dart') && !entry.name.endsWith('.freezed.dart')) {
96
+ results.push(fullPath);
97
+ }
98
+ }
99
+ return results;
100
+ }
101
+
102
+ // ── 测试源码目录定位 ──────────────────────────────────────
103
+
104
+ function locateTestDirs(libRoot) {
105
+ const dirs = [];
106
+ const mainPkgDir = detectMainPackage(libRoot);
107
+
108
+ const candidates = [
109
+ path.join(mainPkgDir, 'test'),
110
+ path.join(mainPkgDir, 'example', 'test'),
111
+ path.join(mainPkgDir, 'example', 'integration_test'),
112
+ ];
113
+ // 如果 mainPkgDir !== libRoot,也检查 libRoot 下的目录
114
+ if (mainPkgDir !== libRoot) {
115
+ candidates.push(
116
+ path.join(libRoot, 'test'),
117
+ path.join(libRoot, 'example', 'test'),
118
+ path.join(libRoot, 'example', 'integration_test'),
119
+ );
120
+ }
121
+
122
+ const seen = new Set();
123
+ for (const c of candidates) {
124
+ const resolved = path.resolve(c);
125
+ if (fs.existsSync(c) && !seen.has(resolved)) {
126
+ dirs.push(c);
127
+ seen.add(resolved);
128
+ }
129
+ }
130
+ return dirs.length > 0 ? dirs : null;
131
+ }
132
+
133
+ function getPackageName(libRoot) {
134
+ const mainPkgDir = detectMainPackage(libRoot);
135
+ return parsePubspec(mainPkgDir).name;
136
+ }
137
+
138
+ // ── 正则覆盖率匹配引擎 ──────────────────────────────────────
139
+
140
+ /**
141
+ * 提取 import 语句中的导入符号
142
+ * Dart import 语法:
143
+ * import 'package:pkg/file.dart'; → 全部符号导入
144
+ * import 'package:pkg/file.dart' show ClassA, ClassB; → 仅 ClassA, ClassB
145
+ * import 'package:pkg/file.dart' hide PrivateClass; → 全部除 PrivateClass
146
+ * import 'package:pkg/file.dart' as alias; → 命名空间导入
147
+ */
148
+ function extractImports(content, packageName) {
149
+ const imports = {
150
+ direct: new Set(), // 直接导入的符号(无前缀使用)
151
+ aliased: new Map(), // alias → 包名
152
+ showList: null, // show 列表
153
+ hideList: new Set(), // hide 列表
154
+ hasImport: false,
155
+ };
156
+
157
+ // 匹配 import 'package:xxx/...' [show/hide/as ...]
158
+ const importRegex = /^import\s+['"]package:([^/]+)\/[^'"]+['"](?:\s+(show|hide|as)\s+([\w,\s]+))?/gm;
159
+ let m;
160
+ while ((m = importRegex.exec(content)) !== null) {
161
+ const pkgName = m[1];
162
+ if (pkgName !== packageName && !pkgName.includes(packageName)) continue;
163
+ imports.hasImport = true;
164
+
165
+ if (m[2] === 'show') {
166
+ const names = m[3].split(',').map(s => s.trim()).filter(Boolean);
167
+ imports.showList = imports.showList || new Set();
168
+ names.forEach(n => imports.showList.add(n));
169
+ } else if (m[2] === 'hide') {
170
+ const names = m[3].split(',').map(s => s.trim()).filter(Boolean);
171
+ names.forEach(n => imports.hideList.add(n));
172
+ } else if (m[2] === 'as') {
173
+ const alias = m[3].trim();
174
+ imports.aliased.set(alias, pkgName);
175
+ } else {
176
+ // 无 show/hide/as,全部导入
177
+ imports.direct.add('*');
178
+ }
179
+ }
180
+
181
+ return imports;
182
+ }
183
+
184
+ /**
185
+ * 追踪实例变量 → 类名映射
186
+ * Dart:
187
+ * Type varName = ClassName(...) → varMap['varName'] = 'ClassName'
188
+ * ClassName varName = ClassName(...)
189
+ * var varName = ClassName.named(...)
190
+ * final exif = FlutterExif.fromBytes(...)
191
+ */
192
+ function trackInstanceVariables(content, importedClassNames) {
193
+ const varMap = {};
194
+ const classSet = new Set(importedClassNames);
195
+
196
+ for (const className of classSet) {
197
+ // var/final/const/Type name = ClassName(...)
198
+ const patterns = [
199
+ new RegExp(`(?:var|final|const)\\s+(\\w+)\\s*=\\s*${escapeRegex(className)}\\s*[.(]`, 'g'),
200
+ new RegExp(`(?:var|final|const)\\s+(\\w+)\\s*=\\s*new\\s+${escapeRegex(className)}\\s*\\(`, 'g'),
201
+ new RegExp(`${escapeRegex(className)}\\s+(\\w+)\\s*=\\s*${escapeRegex(className)}\\s*[.(]`, 'g'),
202
+ new RegExp(`${escapeRegex(className)}\\s+(\\w+)\\s*=\\s*new\\s+${escapeRegex(className)}\\s*\\(`, 'g'),
203
+ ];
204
+ for (const regex of patterns) {
205
+ let m;
206
+ while ((m = regex.exec(content)) !== null) {
207
+ const varName = m[1];
208
+ if (varName && !varMap[varName]) varMap[varName] = [];
209
+ if (varName && !varMap[varName].includes(className)) varMap[varName].push(className);
210
+ }
211
+ }
212
+ }
213
+
214
+ // 从类型注解追踪: Type name (参数声明、字段声明)
215
+ for (const className of classSet) {
216
+ const typePattern = new RegExp(`\\b${escapeRegex(className)}\\s+(\\w+)\\b`, 'g');
217
+ let m;
218
+ while ((m = typePattern.exec(content)) !== null) {
219
+ const varName = m[1];
220
+ // 排除关键字
221
+ if (['extends', 'implements', 'with', 'class', 'enum', 'mixin', 'return', 'final', 'const', 'var', 'static', 'void', 'get', 'set', 'new', 'this', 'super'].includes(varName)) continue;
222
+ if (varName && !varMap[varName]) varMap[varName] = [];
223
+ if (varName && !varMap[varName].includes(className)) varMap[varName].push(className);
224
+ }
225
+ }
226
+
227
+ return varMap;
228
+ }
229
+
230
+ /**
231
+ * 正则覆盖率匹配核心
232
+ */
233
+ function matchCoverage(interfaces, files, packageName) {
234
+ const fileCoverageMap = {};
235
+ const allClassNames = [...new Set(interfaces.map(i => i.className).filter(n => n !== null))];
236
+
237
+ for (const filePath of files) {
238
+ const content = fs.readFileSync(filePath, 'utf-8');
239
+ const fileName = path.relative(path.dirname(filePath), filePath);
240
+ const imports = extractImports(content, packageName);
241
+
242
+ if (!imports.hasImport && imports.aliased.size === 0) continue;
243
+
244
+ const varMap = trackInstanceVariables(content, allClassNames);
245
+ const fileCovered = [];
246
+
247
+ for (const iface of interfaces) {
248
+ if (fileCovered.includes(iface.fullName)) continue;
249
+
250
+ // 顶层函数/变量
251
+ if (iface.className === null) {
252
+ if (iface.kind === 'function') {
253
+ const regex = new RegExp(`\\b${escapeRegex(iface.memberName)}\\s*\\(`, 'g');
254
+ if (regex.test(content)) fileCovered.push(iface.fullName);
255
+ } else if (iface.kind === 'variable' || iface.kind === 'type') {
256
+ const regex = new RegExp(`\\b${escapeRegex(iface.memberName)}\\b`, 'g');
257
+ if (regex.test(content)) fileCovered.push(iface.fullName);
258
+ }
259
+ continue;
260
+ }
261
+
262
+ const className = iface.className;
263
+ const memberName = iface.memberName;
264
+
265
+ // 1. 静态方法/属性: ClassName.memberName(
266
+ if (iface.isStatic) {
267
+ if (iface.kind === 'method' || iface.kind === 'constructor') {
268
+ const regex = new RegExp(`${escapeRegex(className)}\\s*\\.\\s*${escapeRegex(memberName)}\\s*\\(`, 'g');
269
+ if (regex.test(content)) { fileCovered.push(iface.fullName); continue; }
270
+ } else if (iface.kind === 'property' || iface.kind === 'field') {
271
+ const regex = new RegExp(`${escapeRegex(className)}\\s*\\.\\s*${escapeRegex(memberName)}\\b`, 'g');
272
+ if (regex.test(content)) { fileCovered.push(iface.fullName); continue; }
273
+ }
274
+ }
275
+
276
+ // 2. 构造函数: ClassName() 或 ClassName.named()
277
+ if (iface.kind === 'constructor') {
278
+ if (memberName === 'constructor') {
279
+ const regex = new RegExp(`new\\s+${escapeRegex(className)}\\s*\\(`, 'g');
280
+ if (regex.test(content)) { fileCovered.push(iface.fullName); continue; }
281
+ const regex2 = new RegExp(`\\b${escapeRegex(className)}\\s*\\(`, 'g');
282
+ if (regex2.test(content)) { fileCovered.push(iface.fullName); continue; }
283
+ } else {
284
+ const regex = new RegExp(`${escapeRegex(className)}\\s*\\.\\s*${escapeRegex(memberName)}\\s*\\(`, 'g');
285
+ if (regex.test(content)) { fileCovered.push(iface.fullName); continue; }
286
+ }
287
+ }
288
+
289
+ // 3. 枚举值: ClassName.valueName
290
+ if (iface.kind === 'enumValue') {
291
+ const regex = new RegExp(`${escapeRegex(className)}\\s*\\.\\s*${escapeRegex(memberName)}\\b`, 'g');
292
+ if (regex.test(content)) { fileCovered.push(iface.fullName); continue; }
293
+ }
294
+
295
+ // 4. 实例方法/属性: varName.memberName
296
+ if (iface.kind === 'method' || iface.kind === 'property') {
297
+ // 对于 extension 方法,同时匹配 onType(extension 所扩展的类型)
298
+ const matchClassNames = [className];
299
+ if (iface.onType) matchClassNames.push(iface.onType);
300
+ // 通过实例变量追踪
301
+ for (const [varName, classNames] of Object.entries(varMap)) {
302
+ if (matchClassNames.some(cn => classNames.includes(cn))) {
303
+ if (iface.kind === 'method') {
304
+ const regex = new RegExp(`${escapeRegex(varName)}\\s*(?:\\?\\.|\\.)\\s*${escapeRegex(memberName)}\\s*\\(`, 'g');
305
+ if (regex.test(content)) { fileCovered.push(iface.fullName); break; }
306
+ } else if (iface.kind === 'property') {
307
+ const regex = new RegExp(`${escapeRegex(varName)}\\s*(?:\\?\\.|\\.)\\s*${escapeRegex(memberName)}\\b`, 'g');
308
+ if (regex.test(content)) { fileCovered.push(iface.fullName); break; }
309
+ }
310
+ }
311
+ }
312
+ if (fileCovered.includes(iface.fullName)) continue;
313
+
314
+ // 直接链式调用: ClassName().methodName( (也支持 onType 链式)
315
+ if (iface.kind === 'method') {
316
+ for (const cn of matchClassNames) {
317
+ const regex = new RegExp(`${escapeRegex(cn)}\\s*\\([^)]*\\)\\s*\\.\\s*${escapeRegex(memberName)}\\s*\\(`, 'g');
318
+ if (regex.test(content)) { fileCovered.push(iface.fullName); break; }
319
+ }
320
+ if (fileCovered.includes(iface.fullName)) continue;
321
+ }
322
+ }
323
+
324
+ // 5. 字段: 直接引用名称
325
+ if (iface.kind === 'field') {
326
+ const regex = new RegExp(`\\b${escapeRegex(memberName)}\\b`, 'g');
327
+ if (regex.test(content)) { fileCovered.push(iface.fullName); continue; }
328
+ }
329
+ }
330
+
331
+ // 6. 对象字面量/命名参数兜底: propertyName: 或 propertyName,
332
+ for (const iface of interfaces) {
333
+ if (fileCovered.includes(iface.fullName)) continue;
334
+ if ((iface.kind === 'property' || iface.kind === 'field') && iface.memberName && iface.memberName.length >= 2) {
335
+ const regex = new RegExp(`\\b${escapeRegex(iface.memberName)}\\??\\s*:`, 'g');
336
+ if (regex.test(content)) fileCovered.push(iface.fullName);
337
+ }
338
+ }
339
+
340
+ // 7. aliased import 兜底: alias.ClassName.memberName
341
+ for (const [alias, pkgName] of imports.aliased) {
342
+ if (pkgName !== packageName && !pkgName.includes(packageName)) continue;
343
+ for (const iface of interfaces) {
344
+ if (fileCovered.includes(iface.fullName)) continue;
345
+ if (!iface.className) continue;
346
+ if (iface.kind === 'method') {
347
+ const regex = new RegExp(`${escapeRegex(alias)}\\s*\\.\\s*${escapeRegex(iface.className)}\\s*\\.\\s*${escapeRegex(iface.memberName)}\\s*\\(`, 'g');
348
+ if (regex.test(content)) fileCovered.push(iface.fullName);
349
+ } else if (iface.kind === 'property' || iface.kind === 'field') {
350
+ const regex = new RegExp(`${escapeRegex(alias)}\\s*\\.\\s*${escapeRegex(iface.className)}\\s*\\.\\s*${escapeRegex(iface.memberName)}\\b`, 'g');
351
+ if (regex.test(content)) fileCovered.push(iface.fullName);
352
+ }
353
+ }
354
+ }
355
+
356
+ for (const fullName of fileCovered) {
357
+ if (!fileCoverageMap[fileName]) fileCoverageMap[fileName] = { count: 0, interfaces: [] };
358
+ fileCoverageMap[fileName].count++;
359
+ fileCoverageMap[fileName].interfaces.push(fullName);
360
+ }
361
+ }
362
+
363
+ return aggregateResults(interfaces, fileCoverageMap, packageName, files.length);
364
+ }
365
+
366
+ // ── 结果聚合 ──────────────────────────────────────────────
367
+
368
+ function aggregateResults(interfaces, fileCoverageMap, libName, demoFileCount) {
369
+ // 不要求覆盖率的类型:
370
+ // constant — const/final 常量,只读值无业务逻辑
371
+ // enumValue — 枚举常量,同上
372
+ // constructor — 实例化入口,Demo 中常通过间接方式获得对象
373
+ // type — 类型别名 (typedef),不是可调用的方法/属性
374
+ const EXCLUDE_KINDS = ['constant', 'enumValue', 'constructor', 'type'];
375
+ // 框架生命周期方法,通过 testWidgets/pumpWidget 间接覆盖,正则无法追踪
376
+ // Object 基类内置成员:所有 Dart 对象自动继承,不属于库自身公开 API
377
+ const EXCLUDE_METHODS = ['createState', 'hashCode', 'toString', 'noSuchMethod', 'runtimeType'];
378
+ const countedInterfaces = interfaces.filter(i =>
379
+ !EXCLUDE_KINDS.includes(i.kind) &&
380
+ !(i.kind === 'method' && EXCLUDE_METHODS.includes(i.memberName))
381
+ );
382
+
383
+ const coveredSet = new Set();
384
+ for (const fc of Object.values(fileCoverageMap)) {
385
+ for (const intf of fc.interfaces) coveredSet.add(intf);
386
+ }
387
+
388
+ const covered = [];
389
+ const uncovered = [];
390
+ const classCoverageMap = {};
391
+
392
+ for (const iface of countedInterfaces) {
393
+ const className = iface.className || '';
394
+ if (!classCoverageMap[className]) classCoverageMap[className] = { total: 0, covered: 0 };
395
+ classCoverageMap[className].total++;
396
+ if (coveredSet.has(iface.fullName)) {
397
+ classCoverageMap[className].covered++;
398
+ covered.push({
399
+ ...iface,
400
+ files: Object.entries(fileCoverageMap)
401
+ .filter(([_, fc]) => fc.interfaces.includes(iface.fullName))
402
+ .map(([fn]) => fn)
403
+ });
404
+ } else {
405
+ uncovered.push(iface);
406
+ }
407
+ }
408
+
409
+ const N = countedInterfaces.length;
410
+ const M = covered.filter(i => coveredSet.has(i.fullName)).length;
411
+ const X = N > 0 ? (M / N * 100).toFixed(2) : '0.00';
412
+
413
+ const byKind = {};
414
+ const kindNames = ['method', 'constructor', 'property', 'field', 'constant', 'enumValue', 'function', 'type', 'variable'];
415
+ for (const kind of kindNames) {
416
+ const kindInterfaces = interfaces.filter(i => i.kind === kind);
417
+ const kindCovered = kindInterfaces.filter(i => coveredSet.has(i.fullName));
418
+ byKind[kind] = {
419
+ total: kindInterfaces.length,
420
+ covered: kindCovered.length,
421
+ rate: kindInterfaces.length > 0 ? (kindCovered.length / kindInterfaces.length * 100).toFixed(2) : '-',
422
+ excluded: EXCLUDE_KINDS.includes(kind),
423
+ };
424
+ }
425
+
426
+ const classCoverage = [];
427
+ for (const [className, data] of Object.entries(classCoverageMap)) {
428
+ if (className === '') continue;
429
+ classCoverage.push({
430
+ className, total: data.total, covered: data.covered,
431
+ uncovered: data.total - data.covered,
432
+ rate: data.total > 0 ? (data.covered / data.total * 100).toFixed(2) : '-'
433
+ });
434
+ }
435
+
436
+ const S = Object.keys(classCoverageMap).filter(k => k !== '').length;
437
+
438
+ return {
439
+ libraryName: libName,
440
+ totalInterfacesClassLevel: S,
441
+ totalInterfacesMethodLevel: N,
442
+ coveredMethodCount: M,
443
+ coverageRateMethodLevel: X,
444
+ byKind,
445
+ classCoverage,
446
+ covered,
447
+ uncovered,
448
+ fileCoverage: Object.entries(fileCoverageMap).map(([file, data]) => ({
449
+ file, count: data.count, interfaces: data.interfaces
450
+ })),
451
+ demoFileCount,
452
+ };
453
+ }
454
+
455
+ // ── Markdown 渲染 ──────────────────────────────────────────
456
+
457
+ function renderHeader(result, title) {
458
+ const lines = [];
459
+ lines.push(`# ${title || result.libraryName + ' XTS 覆盖率分析报告'}`);
460
+ lines.push('');
461
+ lines.push('> 本报告由 `flutter-analyze-xts-coverage.cjs` 自动生成。');
462
+ lines.push('');
463
+ lines.push('## 基本信息');
464
+ lines.push('');
465
+ lines.push('| 属性 | 值 |');
466
+ lines.push('|------|----|');
467
+ lines.push(`| 库名 | \`${result.libraryName}\` |`);
468
+ lines.push(`| 源文件数 | ${result.demoFileCount || '-'} |`);
469
+ lines.push(`| 生成时间 | ${new Date(Date.now() + 8 * 3600 * 1000).toISOString().replace('T', ' ').replace('Z', '+08:00')} |`);
470
+ lines.push('');
471
+ return lines.join('\n');
472
+ }
473
+
474
+ function renderOverview(result) {
475
+ const lines = [];
476
+ lines.push('## 覆盖率总览');
477
+ lines.push('');
478
+ const rate = parseFloat(result.coverageRateMethodLevel) || 0;
479
+ lines.push(`### 方法级覆盖率: ${result.coverageRateMethodLevel}% (${coverageBadge(result.coverageRateMethodLevel)})`);
480
+ lines.push('');
481
+ lines.push('| 统计项 | 数量 |');
482
+ lines.push('|--------|------|');
483
+ lines.push(`| 类/接口总数 (S) | ${result.totalInterfacesClassLevel} |`);
484
+ lines.push(`| 方法/属性总数 (N) | ${result.totalInterfacesMethodLevel} |`);
485
+ lines.push(`| 已覆盖 (M) | ${result.coveredMethodCount} |`);
486
+ lines.push(`| 未覆盖 | ${result.totalInterfacesMethodLevel - result.coveredMethodCount} |`);
487
+ lines.push(`| 排除类型 | 常量 (constant) / 枚举值 (enumValue) / 构造函数 (constructor) / 类型别名 (type) / 框架方法 (createState) / Object 内置方法 (hashCode/toString/noSuchMethod/runtimeType) |`);
488
+ lines.push('');
489
+ const filled = Math.round(rate / 5);
490
+ const empty = 20 - filled;
491
+ const bar = '█'.repeat(filled) + '░'.repeat(empty);
492
+ lines.push('```');
493
+ lines.push(`覆盖率进度: [${bar}] ${rate.toFixed(2)}%`);
494
+ lines.push('```');
495
+ lines.push('');
496
+ return lines.join('\n');
497
+ }
498
+
499
+ function renderByKind(result) {
500
+ const lines = [];
501
+ lines.push('### 按接口类型统计');
502
+ lines.push('');
503
+ lines.push('| 类型 | 总数 | 已覆盖 | 覆盖率 | 状态 | 备注 |');
504
+ lines.push('|------|------|--------|--------|------|------|');
505
+ for (const [kind, data] of Object.entries(result.byKind || {})) {
506
+ const badge = coverageBadge(data.rate);
507
+ const note = data.excluded ? '不计入覆盖率' : '';
508
+ lines.push(`| ${kind} | ${data.total} | ${data.covered} | ${data.rate}% | ${badge} | ${note} |`);
509
+ }
510
+ lines.push('');
511
+ return lines.join('\n');
512
+ }
513
+
514
+ function renderClassCoverage(result) {
515
+ const lines = [];
516
+ lines.push('## 类级覆盖率');
517
+ lines.push('');
518
+ lines.push('| # | 类名 | 总数 | 已覆盖 | 未覆盖 | 覆盖率 | 状态 |');
519
+ lines.push('|---|------|------|--------|--------|--------|------|');
520
+ const sorted = [...(result.classCoverage || [])].sort((a, b) => parseFloat(a.rate) - parseFloat(b.rate));
521
+ sorted.forEach((cls, i) => {
522
+ lines.push(`| ${i + 1} | \`${cls.className}\` | ${cls.total} | ${cls.covered} | ${cls.uncovered} | ${cls.rate}% | ${coverageBadge(cls.rate)} |`);
523
+ });
524
+ lines.push('');
525
+ return lines.join('\n');
526
+ }
527
+
528
+ function renderUncoveredDetails(result) {
529
+ const lines = [];
530
+ lines.push('## 未覆盖接口详情');
531
+ lines.push('');
532
+ const uncovered = result.uncovered || [];
533
+ if (uncovered.length === 0) {
534
+ lines.push('*所有接口均已覆盖!*');
535
+ lines.push('');
536
+ return lines.join('\n');
537
+ }
538
+ const grouped = {};
539
+ for (const u of uncovered) {
540
+ const cn = u.className || '(顶层)';
541
+ if (!grouped[cn]) grouped[cn] = [];
542
+ grouped[cn].push(u);
543
+ }
544
+ let idx = 0;
545
+ for (const [className, members] of Object.entries(grouped)) {
546
+ idx++;
547
+ lines.push(`### ${idx}. ${className} (${members.length} 个未覆盖)`);
548
+ lines.push('');
549
+ lines.push('| # | 接口 | 类型 | 静态 | 参数 | 返回类型 | 说明 |');
550
+ lines.push('|---|------|------|------|------|----------|------|');
551
+ members.forEach((m, i) => {
552
+ lines.push(`| ${i + 1} | \`${m.memberName}\` | ${m.kind} | ${m.isStatic ? '是' : '否'} | ${escapeMd(formatParams(m.params))} | ${escapeMd(m.returnType)} | ${escapeMd(m.description)} |`);
553
+ });
554
+ lines.push('');
555
+ }
556
+ return lines.join('\n');
557
+ }
558
+
559
+ function renderCoveredDetails(result) {
560
+ const lines = [];
561
+ lines.push('## 已覆盖接口详情');
562
+ lines.push('');
563
+ const covered = result.covered || [];
564
+ if (covered.length === 0) {
565
+ lines.push('*暂无已覆盖接口*');
566
+ lines.push('');
567
+ return lines.join('\n');
568
+ }
569
+ const grouped = {};
570
+ for (const c of covered) {
571
+ const cn = c.className || '(顶层)';
572
+ if (!grouped[cn]) grouped[cn] = [];
573
+ grouped[cn].push(c);
574
+ }
575
+ let idx = 0;
576
+ for (const [className, members] of Object.entries(grouped)) {
577
+ idx++;
578
+ lines.push(`### ${idx}. ${className}`);
579
+ lines.push('');
580
+ lines.push('| # | 接口 | 类型 | 静态 | 签名 | 覆盖文件 |');
581
+ lines.push('|---|------|------|------|------|----------|');
582
+ members.forEach((m, i) => {
583
+ const files = (m.files || []).slice(0, 3).map(f => `\`${f}\``).join(', ');
584
+ const more = m.files && m.files.length > 3 ? ` +${m.files.length - 3}` : '';
585
+ lines.push(`| ${i + 1} | \`${m.memberName}\` | ${m.kind} | ${m.isStatic ? '是' : '否'} | \`${escapeMd(m.signature)}\` | ${files}${more} |`);
586
+ });
587
+ lines.push('');
588
+ }
589
+ return lines.join('\n');
590
+ }
591
+
592
+ function renderFileCoverage(result) {
593
+ const lines = [];
594
+ lines.push('## 文件覆盖详情');
595
+ lines.push('');
596
+ lines.push('| # | 文件 | 覆盖接口数 | 覆盖的接口 |');
597
+ lines.push('|---|------|-----------|-----------|');
598
+ const sorted = [...(result.fileCoverage || [])].sort((a, b) => b.count - a.count);
599
+ sorted.forEach((fc, i) => {
600
+ const ifaces = fc.interfaces.slice(0, 10).map(i => `\`${i}\``).join(', ');
601
+ const more = fc.interfaces.length > 10 ? ` ... (+${fc.interfaces.length - 10})` : '';
602
+ lines.push(`| ${i + 1} | \`${fc.file}\` | ${fc.count} | ${ifaces}${more} |`);
603
+ });
604
+ lines.push('');
605
+ return lines.join('\n');
606
+ }
607
+
608
+ // ── 入口 ──────────────────────────────────────────────────
609
+
610
+ const args = parseArgs();
611
+ const specPath = args.spec;
612
+ const title = args.title;
613
+
614
+ if (!specPath && !args.libroot && !args.repoDir) {
615
+ console.error('用法: node flutter-analyze-xts-coverage.cjs --spec <interface-spec.json> --libroot <库根目录> [--output <report.md>]');
616
+ console.error(' 或: node flutter-analyze-xts-coverage.cjs --libroot <库根目录> [--output <report.md>]');
617
+ process.exit(1);
618
+ }
619
+
620
+ const libRoot = args.libroot || args.repoDir || (specPath ? path.dirname(specPath) : '');
621
+
622
+ // 获取接口规格数据
623
+ let specData;
624
+ let specBasename;
625
+ if (specPath) {
626
+ specData = JSON.parse(fs.readFileSync(specPath, 'utf-8'));
627
+ specBasename = path.basename(specPath, '-interface-spec.json');
628
+ console.log(`[XTS 覆盖率分析]`);
629
+ console.log(`接口规格: ${specPath} (${specData.interfaces ? specData.interfaces.length : 0} 个接口)`);
630
+ } else {
631
+ console.log(`[XTS 覆盖率分析]`);
632
+ console.log(`从源码目录提取接口: ${libRoot}`);
633
+ specData = generateSpec(libRoot);
634
+ specBasename = specData.libraryName || path.basename(libRoot);
635
+ console.log(`提取完成: ${specData.interfaces ? specData.interfaces.length : 0} 个接口, 库名: ${specData.libraryName}`);
636
+ }
637
+
638
+ // 测试源码目录
639
+ const testDirs = args.src ? [args.src] : locateTestDirs(libRoot);
640
+ const packageName = args.lib || getPackageName(libRoot);
641
+ const resolvedLibName = packageName;
642
+
643
+ const outputPath = args.output || path.join(process.cwd(), `03-${specBasename}-XTS覆盖率报告.md`);
644
+
645
+ console.log(`库名: ${resolvedLibName}`);
646
+ console.log(`测试源码目录: ${testDirs ? testDirs.join(', ') : '(未找到)'}`);
647
+
648
+ if (!testDirs || testDirs.length === 0) {
649
+ console.error(`测试源码目录不存在: 未找到 test/ 或 example/test/`);
650
+ process.exit(1);
651
+ }
652
+
653
+ // 扫描测试源文件
654
+ let testFiles = [];
655
+ for (const dir of testDirs) {
656
+ testFiles.push(...walkDir(dir, EXCLUDE_DIRS));
657
+ }
658
+ console.log(`扫描到 ${testFiles.length} 个 .dart 文件`);
659
+
660
+ const interfaces = specData.interfaces || [];
661
+
662
+ // 执行匹配
663
+ console.log('使用正则模式匹配...');
664
+ const result = matchCoverage(interfaces, testFiles, resolvedLibName);
665
+ result.specSummary = specData.summary;
666
+
667
+ console.log(`XTS 匹配完成: ${result.coveredMethodCount}/${result.totalInterfacesMethodLevel} (${result.coverageRateMethodLevel}%)`);
668
+
669
+ // 渲染 Markdown
670
+ const resolvedTitle = title || `${resolvedLibName} XTS 覆盖率分析报告`;
671
+ const parts = [];
672
+ parts.push(renderHeader(result, resolvedTitle));
673
+ parts.push(renderOverview(result));
674
+ parts.push(renderByKind(result));
675
+ parts.push(renderClassCoverage(result));
676
+ parts.push(renderUncoveredDetails(result));
677
+ parts.push(renderCoveredDetails(result));
678
+ parts.push(renderFileCoverage(result));
679
+
680
+ const markdown = parts.join('\n---\n\n');
681
+
682
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
683
+ fs.writeFileSync(outputPath, markdown, 'utf-8');
684
+ console.log(`XTS 报告渲染完成: ${outputPath}`);