@ohos-cpf/3rdloop 0.0.7 → 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 (230) hide show
  1. package/README.md +44 -25
  2. package/lib/cli.js +54 -29
  3. package/lib/config-cmd.js +1 -1
  4. package/lib/config.js +32 -0
  5. package/lib/doctor.js +266 -48
  6. package/lib/mcp.js +498 -0
  7. package/lib/opencode.js +75 -29
  8. package/lib/orch.js +32 -10
  9. package/lib/runner.js +8 -5
  10. package/lib/serve.js +162 -149
  11. package/lib/step.js +15 -4
  12. package/lib/web.js +107 -265
  13. package/lib/workflow.js +18 -8
  14. package/package.json +6 -2
  15. package/vendor/Archive/data/.index/meta.json +7 -0
  16. package/vendor/Archive/data/.index/search.db +0 -0
  17. package/vendor/Archive/data/docs/experiences/arkts-check-tool-pitfalls-baseline-comparison.md +13 -0
  18. package/vendor/Archive/data/docs/experiences/arkts-deep-path-import-recognition.md +31 -0
  19. package/vendor/Archive/data/docs/experiences/arkts-demo-coverage-entry-file-case-mismatch.md +25 -0
  20. package/vendor/Archive/data/docs/experiences/arkts-demo-coverage-import-trailing-slash.md +20 -0
  21. package/vendor/Archive/data/docs/experiences/arkts-library-source-files-may-be-ts.md +18 -0
  22. package/vendor/Archive/data/docs/experiences/arkts-typedarray-buffer-interface-cast-compile-fixes.md +36 -0
  23. package/vendor/Archive/data/docs/experiences/arkts-xts-compile-error-clustering-fix.md +24 -0
  24. package/vendor/Archive/data/docs/experiences/arkts-xts-doc-expected-value-runtime-probe.md +35 -0
  25. package/vendor/Archive/data/docs/experiences/arkts-xts-fixture-rawfile-testability-injection.md +33 -0
  26. package/vendor/Archive/data/docs/experiences/code-linter-chinese-comment-false-positive.md +16 -0
  27. package/vendor/Archive/data/docs/experiences/esobject-new-expression-variable-tracking.md +33 -0
  28. package/vendor/Archive/data/docs/experiences/filesystem-case-sensitivity-module-resolution.md +31 -0
  29. package/vendor/Archive/data/docs/experiences/gitcode-clone-redirect-warning-normal.md +13 -0
  30. package/vendor/Archive/data/docs/experiences/kn_msgvq1w585ec8319.md +77 -0
  31. package/vendor/Archive/data/docs/experiences/kn_msovnm6rd46de72c.md +98 -0
  32. package/vendor/Archive/data/docs/experiences/kn_mspt8vntfe708576.md +87 -0
  33. package/vendor/Archive/data/docs/experiences/kn_mspu1ei77adbbb48.md +133 -0
  34. package/vendor/Archive/data/docs/experiences/kn_mspubgak8d30bfce.md +128 -0
  35. package/vendor/Archive/data/docs/experiences/kn_mspug2de3cb1f7c3.md +115 -0
  36. package/vendor/Archive/data/docs/experiences/kn_skill_opt_001.md +38 -0
  37. package/vendor/Archive/data/docs/experiences/loop-artifacts-verification-evidence-triangle.md +19 -0
  38. package/vendor/Archive/data/docs/experiences/macos-bash-replace-build-ps1.md +19 -0
  39. package/vendor/Archive/data/docs/experiences/macos-bsd-diff-q-illegal-option.md +15 -0
  40. package/vendor/Archive/data/docs/experiences/migrate-cjs-compatible-api-version-map-limit.md +17 -0
  41. package/vendor/Archive/data/docs/experiences/mp3agic-arkts-library-behavior-quirks.md +21 -0
  42. package/vendor/Archive/data/docs/experiences/ohos-face-detection-scan-api-mapping.md +15 -0
  43. package/vendor/Archive/data/docs/experiences/pr-metadata-and-diff-integrity-verification.md +12 -0
  44. package/vendor/Archive/data/docs/experiences/rn-coverage-index-signature-pseudo-member-false-negative.md +32 -0
  45. package/vendor/Archive/data/docs/experiences/rn-memory-check-severity-calibration.md +21 -0
  46. package/vendor/Archive/data/docs/experiences/rn-platform-specific-api-exclusion-evidence.md +24 -0
  47. package/vendor/Archive/data/docs/experiences/rn-upstream-version-diff-pitfalls.md +17 -0
  48. package/vendor/Archive/data/docs/experiences/rn-version-gap-risk-rating-rules.md +13 -0
  49. package/vendor/Archive/data/docs/experiences/rn-view-lib-native-contract-diff-fabric-spec.md +16 -0
  50. package/vendor/Archive/data/docs/experiences/rnoh-adapter-package-version-field-semantics.md +18 -0
  51. package/vendor/Archive/data/docs/experiences/rnoh-arkts-sqlite-rows-array-format.md +18 -0
  52. package/vendor/Archive/data/docs/experiences/rnoh-dependency-residue-scan-strategy.md +18 -0
  53. package/vendor/Archive/data/docs/experiences/rnoh-example-local-lib-dep-use-tgz.md +53 -0
  54. package/vendor/Archive/data/docs/experiences/rnoh-example-prereserve-rntesterlist.md +17 -0
  55. package/vendor/Archive/data/docs/experiences/rnoh-example-template-compatiblesdkversion-upgrade.md +29 -0
  56. package/vendor/Archive/data/docs/experiences/rnoh-example-turbomodule-shell-adaptation-checklist.md +41 -0
  57. package/vendor/Archive/data/docs/experiences/rnoh-mcp-build-project-tool-risk.md +18 -0
  58. package/vendor/Archive/data/docs/experiences/rnoh-npm-devdependencies-version-precheck.md +29 -0
  59. package/vendor/Archive/data/docs/experiences/rnoh-repo-baseline-version-verification.md +13 -0
  60. package/vendor/Archive/data/docs/experiences/rnoh-repo-upstream-directory-mapping.md +18 -0
  61. package/vendor/Archive/data/docs/experiences/rnoh-stateless-library-zero-leak-verification.md +31 -0
  62. package/vendor/Archive/data/docs/experiences/rnoh-template-npmrc-credential-removal.md +15 -0
  63. package/vendor/Archive/data/docs/experiences/rnoh-turbomodule-codegen-wiring-verification.md +19 -0
  64. package/vendor/Archive/data/docs/experiences/rnoh-turbomodule-cpp-methodmap-compatibility-check.md +20 -0
  65. package/vendor/Archive/data/docs/experiences/scenario-doc-vs-source-signature-diff-four-patterns.md +24 -0
  66. package/vendor/Archive/data/docs/experiences/static-analysis-grep-cross-validation.md +29 -0
  67. package/vendor/Archive/demo.js +489 -0
  68. package/vendor/Archive/index.js +43 -0
  69. package/vendor/Archive/package.json +16 -0
  70. package/vendor/Archive/src/constants.js +83 -0
  71. package/vendor/Archive/src/data/stopwords.txt +110 -0
  72. package/vendor/Archive/src/data/synonyms.json +25 -0
  73. package/vendor/Archive/src/data/terms.txt +59 -0
  74. package/vendor/Archive/src/db/sqlite-backend.js +312 -0
  75. package/vendor/Archive/src/db/sqlite-schema.js +64 -0
  76. package/vendor/Archive/src/engine.js +501 -0
  77. package/vendor/Archive/src/index/index-manager.js +203 -0
  78. package/vendor/Archive/src/index/index-state.js +94 -0
  79. package/vendor/Archive/src/index/search-text-builder.js +106 -0
  80. package/vendor/Archive/src/index.js +22 -0
  81. package/vendor/Archive/src/parser/api-identifiers.js +121 -0
  82. package/vendor/Archive/src/parser/api-suffixes.js +48 -0
  83. package/vendor/Archive/src/parser/heading-normalize.js +40 -0
  84. package/vendor/Archive/src/parser/markdown-splitter.js +215 -0
  85. package/vendor/Archive/src/search/catalog-routing.js +87 -0
  86. package/vendor/Archive/src/search/fts-search.js +289 -0
  87. package/vendor/Archive/src/search/query-normalizer.js +159 -0
  88. package/vendor/Archive/src/search/query-rewriter.js +72 -0
  89. package/vendor/Archive/src/search/search-engine.js +72 -0
  90. package/vendor/Archive/src/search/snippet.js +64 -0
  91. package/vendor/Archive/src/store/dir-store.js +103 -0
  92. package/vendor/Archive/src/store/doc-store.js +36 -0
  93. package/vendor/Archive/src/tokenizer/lexicon.js +151 -0
  94. package/vendor/Archive/src/tokenizer/tokenizer.js +167 -0
  95. package/vendor/Archive/src/types.js +86 -0
  96. package/vendor/Archive/src/utils/hash.js +16 -0
  97. package/vendor/Archive/src/utils/lock.js +73 -0
  98. package/vendor/Archive/src/utils/path-safety.js +96 -0
  99. package/vendor/Archive/src/utils/paths.js +39 -0
  100. package/vendor/BadCase/rn/INDEX.md +127 -0
  101. 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
  102. 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
  103. 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
  104. package/vendor/BadCase/rn/cases/KI-004-wechat/345/233/236/350/260/203/346/227/266/345/272/217.md +47 -0
  105. 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
  106. 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
  107. 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
  108. 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
  109. package/vendor/BadCase/rn/cases/KI-009-pager-view/345/265/214/345/245/227/346/273/221/345/212/250.md +38 -0
  110. package/vendor/BadCase/rn/cases/KI-010-screens/346/211/213/345/212/277/345/206/262/347/252/201.md +48 -0
  111. 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
  112. package/vendor/BadCase/rn/cases/KI-012-qr-camera/346/250/252/345/261/217/351/200/202/351/205/215.md +59 -0
  113. 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
  114. package/vendor/BadCase/rn/cases/KI-014-amap-geolocation/345/233/236/350/260/203/347/274/272/345/244/261.md +42 -0
  115. 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
  116. 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
  117. 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
  118. package/vendor/BadCase/rn/cases/KI-018-modal/345/274/271/347/252/227/351/227/252/347/203/201.md +53 -0
  119. 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
  120. 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
  121. 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
  122. 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
  123. 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
  124. package/vendor/BadCase/rn/generalization-log.md +55 -0
  125. package/vendor/BadCase/rn/pr-info/rntpc_react-native-SmartRefreshLayout-32.json +328 -0
  126. package/vendor/BadCase/rn/pr-info/rntpc_react-native-SmartRefreshLayout-32.md +382 -0
  127. package/vendor/BadCase/rn/pr-info/rntpc_react-native-amap-geolocation-27.json +1351 -0
  128. package/vendor/BadCase/rn/pr-info/rntpc_react-native-amap-geolocation-27.md +1176 -0
  129. package/vendor/BadCase/rn/pr-info/rntpc_react-native-audio-recorder-player-22.json +538 -0
  130. package/vendor/BadCase/rn/pr-info/rntpc_react-native-audio-recorder-player-22.md +1488 -0
  131. package/vendor/BadCase/rn/pr-info/rntpc_react-native-blob-util-23.json +211 -0
  132. package/vendor/BadCase/rn/pr-info/rntpc_react-native-blob-util-23.md +35 -0
  133. package/vendor/BadCase/rn/pr-info/rntpc_react-native-code-push-17.json +2048 -0
  134. package/vendor/BadCase/rn/pr-info/rntpc_react-native-code-push-17.md +3866 -0
  135. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fast-image-102.json +198 -0
  136. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fast-image-102.md +40 -0
  137. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fs-38.json +751 -0
  138. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fs-38.md +883 -0
  139. package/vendor/BadCase/rn/pr-info/rntpc_react-native-geolocation-24.json +1544 -0
  140. package/vendor/BadCase/rn/pr-info/rntpc_react-native-geolocation-24.md +2598 -0
  141. package/vendor/BadCase/rn/pr-info/rntpc_react-native-modal-20.json +246 -0
  142. package/vendor/BadCase/rn/pr-info/rntpc_react-native-modal-20.md +554 -0
  143. package/vendor/BadCase/rn/pr-info/rntpc_react-native-netinfo-31.json +211 -0
  144. package/vendor/BadCase/rn/pr-info/rntpc_react-native-netinfo-31.md +54 -0
  145. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-45.json +1626 -0
  146. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-45.md +2213 -0
  147. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-51.json +208 -0
  148. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-51.md +77 -0
  149. package/vendor/BadCase/rn/pr-info/rntpc_react-native-pager-view-32.json +377 -0
  150. package/vendor/BadCase/rn/pr-info/rntpc_react-native-pager-view-32.md +174 -0
  151. package/vendor/BadCase/rn/pr-info/rntpc_react-native-qr-decode-image-camera-37.json +232 -0
  152. package/vendor/BadCase/rn/pr-info/rntpc_react-native-qr-decode-image-camera-37.md +194 -0
  153. package/vendor/BadCase/rn/pr-info/rntpc_react-native-reanimated-82.json +198 -0
  154. package/vendor/BadCase/rn/pr-info/rntpc_react-native-reanimated-82.md +67 -0
  155. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-87.json +198 -0
  156. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-87.md +38 -0
  157. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-93.json +208 -0
  158. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-93.md +34 -0
  159. package/vendor/BadCase/rn/pr-info/rntpc_react-native-spring-scrollview-38.json +222 -0
  160. package/vendor/BadCase/rn/pr-info/rntpc_react-native-spring-scrollview-38.md +56 -0
  161. package/vendor/BadCase/rn/pr-info/rntpc_react-native-svg-117.json +463 -0
  162. package/vendor/BadCase/rn/pr-info/rntpc_react-native-svg-117.md +383 -0
  163. package/vendor/BadCase/rn/pr-info/rntpc_react-native-video-66.json +464 -0
  164. package/vendor/BadCase/rn/pr-info/rntpc_react-native-video-66.md +565 -0
  165. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-112.json +198 -0
  166. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-112.md +47 -0
  167. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-121.json +232 -0
  168. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-121.md +422 -0
  169. package/vendor/BadCase/rn/pr-info/rntpc_react-native-wechat-lib-15.json +295 -0
  170. package/vendor/BadCase/rn/pr-info/rntpc_react-native-wechat-lib-15.md +1038 -0
  171. 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
  172. 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
  173. 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
  174. 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
  175. 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
  176. 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
  177. 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
  178. 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
  179. 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
  180. 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
  181. 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
  182. 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
  183. 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
  184. 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
  185. 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
  186. 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
  187. 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
  188. 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
  189. 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
  190. package/vendor/MCP/README.md +490 -0
  191. package/vendor/MCP/config.json +29 -0
  192. package/vendor/MCP/package.json +23 -0
  193. package/vendor/MCP/scripts/analyze-demo-coverage.cjs +1203 -0
  194. package/vendor/MCP/scripts/analyze-xts-coverage.cjs +1245 -0
  195. package/vendor/MCP/scripts/deveco_docs.js +247 -0
  196. package/vendor/MCP/scripts/extract-interfaces.cjs +1487 -0
  197. package/vendor/MCP/scripts/flutter-analyze-demo-coverage.cjs +666 -0
  198. package/vendor/MCP/scripts/flutter-analyze-xts-coverage.cjs +684 -0
  199. package/vendor/MCP/scripts/flutter-extract-interfaces.cjs +1251 -0
  200. package/vendor/MCP/scripts/flutter-render-spec-doc.cjs +351 -0
  201. package/vendor/MCP/scripts/render-spec-doc.cjs +347 -0
  202. package/vendor/MCP/scripts/rn-analyze-demo-coverage.cjs +645 -0
  203. package/vendor/MCP/scripts/rn-analyze-test-coverage.cjs +614 -0
  204. package/vendor/MCP/scripts/rn-extract-interfaces.cjs +1462 -0
  205. package/vendor/MCP/scripts/rn-render-spec-doc.cjs +327 -0
  206. package/vendor/MCP/scripts/rn-scan-exports.cjs +1336 -0
  207. package/vendor/MCP/src/client/MCPClientPool.js +220 -0
  208. package/vendor/MCP/src/config/ConfigLoader.js +189 -0
  209. package/vendor/MCP/src/executor/ScriptExecutor.js +110 -0
  210. package/vendor/MCP/src/gateway/Gateway.js +451 -0
  211. package/vendor/MCP/src/index.js +87 -0
  212. package/vendor/MCP/src/library/KBToolProvider.js +229 -0
  213. package/vendor/MCP/src/library/logger.js +60 -0
  214. package/vendor/MCP/src/registry/ScriptRegistry.js +238 -0
  215. package/vendor/MCP/src/registry/ToolRegistry.js +111 -0
  216. package/vendor/MCP/src/types.js +115 -0
  217. package/vendor/Server/CLI/cli.js +3 -3
  218. package/vendor/Server/Routes/server.js +3 -3
  219. package/vendor/Server/Skills/arkts-library-test-coverage-check/references/MCP_TOOL_USAGE.md +4 -2
  220. package/vendor/Server/Skills/arkts-library-xts-coverage/SKILL.md +3 -3
  221. package/vendor/Server/Skills/knowledge-import/SKILL.md +1 -1
  222. package/vendor/Server/Skills/knowledge-import/references/MCP_TOOL_USAGE.md +12 -4
  223. package/vendor/Server/Skills/ohos-lib-pr-push/SKILL.md +5 -4
  224. package/vendor/Server/Skills/rn-library-issue-generalizer/SKILL.md +2 -2
  225. package/vendor/Server/Skills/rn-library-known-issue-check/SKILL.md +2 -2
  226. package/vendor/Server/Skills/rnoh-lib-demo-coverage/SKILL.md +1 -2
  227. package/vendor/Server/Skills/rnoh-lib-test-align/references/port-test-demo-guide.md +1 -1
  228. package/vendor/Server/Skills/rnoh-lib-xts-coverage/SKILL.md +1 -2
  229. package/vendor/Server/package.json +5 -1
  230. package/vendor/VERSION +3 -3
@@ -0,0 +1,1336 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @tool rn_scan_exports
4
+ * @description 扫描 React Native 三方库的对外导出接口(public API)和 TurboModule Spec。支持单目录扫描(提取 exports/interface/TurboModule 方法签名)和双目录对比(生成接口差异适配检查清单,判断鸿蒙侧是否需要原生适配)。
5
+ * @param dir: string - 源码目录路径(单目录扫描模式)
6
+ * @param compare: boolean - 是否对比两个目录的接口差异(默认 false)
7
+ * @param dir1: string - 对比模式的目录1路径(compare=true 时使用)
8
+ * @param dir2: string - 对比模式的目录2路径(compare=true 时使用)
9
+ * @param entry: string - 指定包入口文件(可选,覆盖自动检测)
10
+ * @param output: string - 输出 Markdown 文件路径(可选,对比模式生成适配检查清单)
11
+ * @hidden
12
+ * scan-exports.cjs
13
+ *
14
+ * 扫描 RN 三方库的对外导出接口 (public API) 和 TurboModule Spec。
15
+ *
16
+ * 用途:
17
+ * 在版本差异分析中, 确定两个版本之间的 JS 层接口变更,
18
+ * 从而判断鸿蒙侧是否需要原生适配 (接口不变 → 无需适配; 接口变更 → 需适配)。
19
+ *
20
+ * 扫描内容:
21
+ * 1. 包入口文件 (index.ts/tsx 或 package.json 的 source/main 字段指向的文件)
22
+ * → 提取所有 export: default, named, type, re-export
23
+ * 2. TurboModule Spec 文件 (匹配 Native*.ts 模式, 通常在 src/ 下)
24
+ * → 提取 Spec interface 的方法名和签名
25
+ * 3. 公共接口/类型定义 (入口文件 re-export 的 interface/type/class)
26
+ * → 提取方法签名
27
+ *
28
+ * 输出:
29
+ * JSON 格式, 包含:
30
+ * - entryFile: 入口文件路径
31
+ * - exports: { default, named[], types[] }
32
+ * - turboModuleSpec: { moduleName, methods[] }
33
+ *
34
+ * 用法:
35
+ * # 扫描单个目录
36
+ * node rn-scan-exports.cjs <源码目录>
37
+ *
38
+ * # 扫描并指定包入口 (覆盖自动检测)
39
+ * node rn-scan-exports.cjs <源码目录> --entry src/index.ts
40
+ *
41
+ * # 对比两个目录, 输出接口差异
42
+ * node rn-scan-exports.cjs --compare <目录1> <目录2>
43
+ *
44
+ * # 只输出 JSON (适合脚本调用)
45
+ * node rn-scan-exports.cjs -q <源码目录>
46
+ *
47
+ * 示例:
48
+ * # 扫描 async-storage v2.2.0
49
+ * node rn-scan-exports.cjs ./diff/v2.2.0/packages/default-storage
50
+ *
51
+ * # 对比两个版本的接口差异
52
+ * node rn-scan-exports.cjs --compare \
53
+ * ./diff/v2.2.0/packages/default-storage \
54
+ * ./diff/v3.1.1/packages/async-storage
55
+ */
56
+
57
+ 'use strict';
58
+
59
+ const fs = require('fs');
60
+ const path = require('path');
61
+
62
+ // ── 共享工具 (依赖 rn-extract-interfaces.cjs) ───────────
63
+ const {
64
+ COLORS,
65
+ info, ok, warn, error,
66
+ readText, rel, walkDir, readPackageJson,
67
+ detectEntryFile, findTurboModuleSpecFiles,
68
+ getLibraryAlias, resolveRelativePath,
69
+ stripComments, extractBalancedBraces, findSignatureEnd, normalizeSignature,
70
+ extractMethodsFromInterfaceBody, extractExports, extractCJSExportNames, extractTurboModuleSpec,
71
+ extractInterfaceMethods, extractClassMethods,
72
+ } = require('./rn-extract-interfaces.cjs');
73
+
74
+ // ── 参数解析 ─────────────────────────────────────────────
75
+
76
+ function parseArgs() {
77
+ const args = process.argv.slice(2);
78
+ const opts = {
79
+ compare: false,
80
+ quiet: false,
81
+ entry: '',
82
+ output: '',
83
+ dirs: [],
84
+ };
85
+
86
+ // MCP Gateway 模式:第一个参数是 JSON 字符串
87
+ if (args.length > 0 && typeof args[0] === 'string' && args[0].startsWith('{')) {
88
+ try {
89
+ const mcpArgs = JSON.parse(args[0]);
90
+ if (mcpArgs.compare) opts.compare = true;
91
+ if (mcpArgs.quiet) opts.quiet = true;
92
+ if (mcpArgs.entry) opts.entry = mcpArgs.entry;
93
+ if (mcpArgs.output) opts.output = mcpArgs.output;
94
+ if (mcpArgs.dir) opts.dirs.push(mcpArgs.dir);
95
+ if (mcpArgs.dir1) opts.dirs.push(mcpArgs.dir1);
96
+ if (mcpArgs.dir2) opts.dirs.push(mcpArgs.dir2);
97
+ if (Array.isArray(mcpArgs.dirs)) opts.dirs.push(...mcpArgs.dirs);
98
+ if (opts.compare && opts.dirs.length < 2) { error('compare 模式需要 dir1 和 dir2 两个目录参数'); }
99
+ if (!opts.compare && opts.dirs.length < 1) { error('请指定源码目录 (dir)'); }
100
+ return opts;
101
+ } catch (e) { /* 降级到 CLI 模式 */ }
102
+ }
103
+
104
+ let i = 0;
105
+ while (i < args.length) {
106
+ switch (args[i]) {
107
+ case '--compare': opts.compare = true; i += 1; break;
108
+ case '-q':
109
+ case '--quiet': opts.quiet = true; i += 1; break;
110
+ case '--entry': opts.entry = args[i + 1]; i += 2; break;
111
+ case '-o':
112
+ case '--output': opts.output = args[i + 1]; i += 2; break;
113
+ case '-h':
114
+ case '--help':
115
+ console.log(`
116
+ rn-scan-exports.cjs
117
+ 扫描 RN 三方库的对外导出接口和 TurboModule Spec
118
+
119
+ 用法:
120
+ node rn-scan-exports.cjs [选项] <源码目录>
121
+ node rn-scan-exports.cjs --compare [选项] <目录1> <目录2>
122
+
123
+ 选项:
124
+ --compare 对比两个目录的接口差异
125
+ -q, --quiet 只输出 JSON (适合脚本调用)
126
+ --entry <f> 指定包入口文件 (覆盖自动检测)
127
+ -o, --output <f> 将结果写入 Markdown 文件 (对比模式生成适配检查清单)
128
+ -h, --help 显示帮助
129
+
130
+ 示例:
131
+ # 扫描单个目录
132
+ node rn-scan-exports.cjs ./packages/async-storage
133
+
134
+ # 对比两个版本, 输出到文件
135
+ node rn-scan-exports.cjs --compare \\
136
+ ./diff/v2.2.0/packages/default-storage \\
137
+ ./diff/v3.1.1/packages/async-storage \\
138
+ --output ./api-diff.md
139
+ `);
140
+ process.exit(0);
141
+ default:
142
+ opts.dirs.push(args[i]); i += 1;
143
+ }
144
+ }
145
+
146
+ if (opts.compare && opts.dirs.length < 2) {
147
+ error('--compare 需要两个目录参数');
148
+ }
149
+ if (!opts.compare && opts.dirs.length < 1) {
150
+ error('请指定源码目录');
151
+ }
152
+
153
+ return opts;
154
+ }
155
+
156
+ // ── 文件查找 ─────────────────────────────────────────────
157
+ // readPackageJson, detectEntryFile, findTurboModuleSpecFiles,
158
+ // scanDirForNativeSpec 已移至 shared-utils.cjs
159
+
160
+ // detectEntryFile 已移至 shared-utils.cjs
161
+
162
+ // findTurboModuleSpecFiles, scanDirForNativeSpec 已移至 shared-utils.cjs
163
+
164
+ // ── TS 解析: export 提取 ─────────────────────────────────
165
+ // extractExports 已移至 shared-utils.cjs
166
+
167
+ // ── TS 解析: TurboModule Spec 提取 ───────────────────────
168
+ // extractTurboModuleSpec, extractBalancedBraces, findSignatureEnd,
169
+ // normalizeSignature 已移至 shared-utils.cjs
170
+
171
+ // ── 扫描主流程 ───────────────────────────────────────────
172
+
173
+ /**
174
+ * 当 package.json 的 types/typings 路径不存在时, 在该路径的父目录树下搜索 index.d.ts。
175
+ * 修复场景: cos-sdk v1.2.1 声明 types=lib/typescript/index.d.ts,
176
+ * 但实际文件在 lib/typescript/src/index.d.ts (打包路径错误)。
177
+ *
178
+ * @param {string} pkgDir 包根目录
179
+ * @param {string} typesPath package.json 中声明的 types 路径
180
+ * @returns {string|null} 找到的 .d.ts 绝对路径, 或 null
181
+ */
182
+ function findDtsFallback(pkgDir, typesPath) {
183
+ const typesDir = path.dirname(path.resolve(pkgDir, typesPath));
184
+ if (!fs.existsSync(typesDir) || !fs.statSync(typesDir).isDirectory()) return null;
185
+ // 快速候选: 常见子目录模式
186
+ const quick = [
187
+ path.join(typesDir, 'src', 'index.d.ts'),
188
+ path.join(typesDir, 'index.d.ts'),
189
+ ];
190
+ for (const c of quick) {
191
+ if (fs.existsSync(c) && fs.statSync(c).isFile()) return c;
192
+ }
193
+ // 递归搜索 (深度 ≤ 3)
194
+ const walk = (dir, depth) => {
195
+ if (depth > 3) return null;
196
+ let entries;
197
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return null; }
198
+ for (const e of entries) {
199
+ if (e.isDirectory()) {
200
+ const candidate = path.join(dir, e.name, 'index.d.ts');
201
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate;
202
+ const found = walk(path.join(dir, e.name), depth + 1);
203
+ if (found) return found;
204
+ }
205
+ }
206
+ return null;
207
+ };
208
+ return walk(typesDir, 0);
209
+ }
210
+
211
+ /**
212
+ * 扫描一个源码目录的导出接口。
213
+ *
214
+ * @param {string} dir 源码目录
215
+ * @param {string} explicitEntry 指定的入口文件 (可选)
216
+ * @returns {Object} 扫描结果
217
+ */
218
+ function scanDirectory(dir, explicitEntry = '') {
219
+ const absDir = path.resolve(dir);
220
+ if (!fs.existsSync(absDir)) {
221
+ error(`目录不存在: ${dir}`);
222
+ }
223
+
224
+ // 1. 检测入口文件
225
+ const entryFile = detectEntryFile(absDir, explicitEntry);
226
+ if (!entryFile) {
227
+ warn(`未找到入口文件: ${absDir}`);
228
+ }
229
+
230
+ // 2. 提取入口文件的 exports
231
+ let exports = { defaultExport: null, named: [], types: [], reExports: [] };
232
+ const interfaceDefs = [];
233
+ const seenDefNames = new Set();
234
+
235
+ /**
236
+ * 添加接口/类定义 (按名称去重, 先到先得)。
237
+ * @param {string} name 定义名称
238
+ * @param {string} sourceFile 相对源码根目录的文件路径
239
+ * @param {Array} methods 方法签名列表 (为空则不添加)
240
+ */
241
+ const addInterfaceDef = (name, sourceFile, methods) => {
242
+ if (!methods || methods.length === 0 || seenDefNames.has(name)) return;
243
+ interfaceDefs.push({ name, sourceFile, methods });
244
+ seenDefNames.add(name);
245
+ };
246
+
247
+ /**
248
+ * 从文件中提取类与类型定义的方法签名:
249
+ * - default export class (如 export default class JPush {...})
250
+ * - 命名导出的 class
251
+ * - 文件内声明的 interface/type
252
+ * @param {string} filePath 文件绝对路径
253
+ * @param {Object} exportsObj 该文件的 exports 提取结果
254
+ * @param {string} content 文件内容
255
+ */
256
+ const collectClassAndTypeDefs = (filePath, exportsObj, content) => {
257
+ const relSource = path.relative(absDir, filePath);
258
+ const defaultName = exportsObj.defaultExport;
259
+ if (defaultName && /^[A-Za-z_$][\w$]*$/.test(defaultName)) {
260
+ addInterfaceDef(defaultName, relSource, extractClassMethods(content, defaultName));
261
+ }
262
+ for (const name of exportsObj.named) {
263
+ addInterfaceDef(name, relSource, extractClassMethods(content, name));
264
+ }
265
+ for (const typeName of exportsObj.types) {
266
+ addInterfaceDef(typeName, relSource, extractInterfaceMethods(content, typeName));
267
+ }
268
+ };
269
+
270
+ let exportsBaseFile = entryFile;
271
+
272
+ if (entryFile) {
273
+ const content = fs.readFileSync(entryFile, 'utf-8');
274
+ exports = extractExports(content);
275
+
276
+ // 2b. 入口 .d.ts 类型声明 — 类型契约的权威来源
277
+ // 优先检查同名 .d.ts (index.js -> index.d.ts)
278
+ // 其次检查 package.json 的 types/typings 字段
279
+ // 最后回退搜索 types 目录树 (修复打包路径错误, 如 cos-sdk v1.2.1)
280
+ let dtsFile = entryFile.replace(/\.jsx?$/i, '.d.ts');
281
+ if (dtsFile === entryFile || !fs.existsSync(dtsFile)) {
282
+ const pkg = readPackageJson(absDir);
283
+ if (pkg) {
284
+ const typesPath = pkg.types || pkg.typings;
285
+ if (typesPath) {
286
+ const resolved = path.resolve(absDir, typesPath);
287
+ if (fs.existsSync(resolved)) {
288
+ dtsFile = resolved;
289
+ } else {
290
+ // types 路径不存在, 回退搜索 types 目录树
291
+ dtsFile = findDtsFallback(absDir, typesPath);
292
+ }
293
+ } else {
294
+ dtsFile = null;
295
+ }
296
+ } else {
297
+ dtsFile = null;
298
+ }
299
+ }
300
+
301
+ if (dtsFile) {
302
+ const dtsContent = fs.readFileSync(dtsFile, 'utf-8');
303
+ const dtsExports = extractExports(dtsContent);
304
+ // 当入口是编译后的 .js/.jsx (CommonJS) 且 .d.ts 有导出信息时,
305
+ // 使用 .d.ts 的导出作为权威 API 表面 (CommonJS 的 module.exports 无法被正则解析)
306
+ if (/\.(jsx?)$/i.test(entryFile) &&
307
+ (dtsExports.named.length > 0 || dtsExports.types.length > 0 ||
308
+ dtsExports.reExports.length > 0 || dtsExports.defaultExport)) {
309
+ exports = dtsExports;
310
+ exportsBaseFile = dtsFile;
311
+ }
312
+ collectClassAndTypeDefs(dtsFile, dtsExports, dtsContent);
313
+ }
314
+
315
+ // 2d. 无 .d.ts 或 .d.ts 导出为空时, 从 CommonJS 编译代码提取导出名 (最后手段)
316
+ // 防止因缺少 .d.ts 或 .d.ts 使用 export = 等未解析模式 导致一侧导出为空, 对比时产生假阳性
317
+ if (/\.(jsx?)$/i.test(entryFile) &&
318
+ !exports.named.length && !exports.types.length &&
319
+ !exports.reExports.length && !exports.defaultExport) {
320
+ const cjsExports = extractCJSExportNames(content);
321
+ if (cjsExports.named.length > 0 || cjsExports.reExports.length > 0 || cjsExports.defaultExport) {
322
+ exports = cjsExports;
323
+ }
324
+ }
325
+
326
+ // 2c. 入口文件自身定义的类与类型
327
+ // 典型场景: export default class JPush { static getPushStatus() {} }
328
+ // 此前仅跟踪 re-export 的类, 入口自身 class 的方法签名变更
329
+ // (如新增静态方法) 无法被检测, 导致"无接口变更"误报
330
+ collectClassAndTypeDefs(entryFile, exports, content);
331
+ }
332
+
333
+ // 3. 跟踪 re-export 的类型定义
334
+ // 如果入口文件 re-export 了 type/interface, 跟踪到源文件提取方法签名
335
+ // 使用 exportsBaseFile (可能是 .d.ts) 作为相对路径基准
336
+ if (entryFile) {
337
+ for (const re of exports.reExports) {
338
+ // 跟踪 type re-export 和 named re-export (可能是 class/function)
339
+ const sourceFile = resolveRelativePath(exportsBaseFile, re.from);
340
+ if (sourceFile && fs.existsSync(sourceFile)) {
341
+ const sourceContent = fs.readFileSync(sourceFile, 'utf-8');
342
+ const relSource = path.relative(absDir, sourceFile);
343
+
344
+ // 提取 re-export 指定的 interface/type 方法签名
345
+ if (re.name && re.name !== '*') {
346
+ addInterfaceDef(re.name, relSource, extractInterfaceMethods(sourceContent, re.name));
347
+ // 也尝试 class 方法
348
+ addInterfaceDef(re.name, relSource, extractClassMethods(sourceContent, re.name));
349
+ } else if (re.isWildcard) {
350
+ // wildcard: 提取源文件的所有 interface/type/class
351
+ const sourceExports = extractExports(sourceContent);
352
+ for (const typeName of sourceExports.types) {
353
+ addInterfaceDef(typeName, relSource, extractInterfaceMethods(sourceContent, typeName));
354
+ }
355
+ for (const namedExport of sourceExports.named) {
356
+ addInterfaceDef(namedExport, relSource, extractClassMethods(sourceContent, namedExport));
357
+ }
358
+ }
359
+ }
360
+ }
361
+ }
362
+
363
+ // 4. 查找并提取 TurboModule Spec
364
+ const specFiles = findTurboModuleSpecFiles(absDir);
365
+ const turboModuleSpecs = [];
366
+ for (const specFile of specFiles) {
367
+ const content = fs.readFileSync(specFile, 'utf-8');
368
+ const spec = extractTurboModuleSpec(content);
369
+ if (spec.methods.length > 0 || spec.moduleName) {
370
+ turboModuleSpecs.push({
371
+ file: path.relative(absDir, specFile),
372
+ moduleName: spec.moduleName,
373
+ methods: spec.methods.map(m => ({
374
+ name: m.name,
375
+ signature: normalizeSignature(m.signature),
376
+ })),
377
+ });
378
+ }
379
+ }
380
+
381
+ return {
382
+ directory: absDir,
383
+ entryFile: entryFile ? path.relative(absDir, entryFile) : null,
384
+ exports,
385
+ interfaceDefs,
386
+ turboModuleSpecs,
387
+ };
388
+ }
389
+
390
+ // resolveRelativePath, extractInterfaceMethods, extractClassMethods,
391
+ // extractMethodsFromInterfaceBody 已移至 shared-utils.cjs
392
+
393
+ // ── 对比逻辑 ─────────────────────────────────────────────
394
+
395
+ /**
396
+ * 对比两个扫描结果, 生成接口差异报告。
397
+ *
398
+ * @param {Object} scan1 版本1 的扫描结果
399
+ * @param {Object} scan2 版本2 的扫描结果
400
+ * @returns {Object} 差异报告
401
+ */
402
+ function compareScans(scan1, scan2) {
403
+ const result = {
404
+ exportsDiff: {
405
+ defaultChanged: false,
406
+ added: [],
407
+ removed: [],
408
+ typesAdded: [],
409
+ typesRemoved: [],
410
+ reExportsAdded: [],
411
+ reExportsRemoved: [],
412
+ },
413
+ interfaceDefsDiff: [],
414
+ turboModuleDiff: [],
415
+ summary: {
416
+ hasInterfaceChanges: false,
417
+ hasTurboModuleChanges: false,
418
+ needsNativeAdaptation: false,
419
+ },
420
+ };
421
+
422
+ // ── 1. 对比 exports ──
423
+ const e1 = scan1.exports;
424
+ const e2 = scan2.exports;
425
+
426
+ // default export
427
+ if (e1.defaultExport !== e2.defaultExport) {
428
+ result.exportsDiff.defaultChanged = true;
429
+ result.exportsDiff.defaultFrom = e1.defaultExport;
430
+ result.exportsDiff.defaultTo = e2.defaultExport;
431
+ }
432
+
433
+ // named exports
434
+ const named1 = new Set(e1.named);
435
+ const named2 = new Set(e2.named);
436
+ for (const n of named2) {
437
+ if (!named1.has(n)) result.exportsDiff.added.push(n);
438
+ }
439
+ for (const n of named1) {
440
+ if (!named2.has(n)) result.exportsDiff.removed.push(n);
441
+ }
442
+
443
+ // type exports
444
+ const types1 = new Set(e1.types);
445
+ const types2 = new Set(e2.types);
446
+ for (const t of types2) {
447
+ if (!types1.has(t)) result.exportsDiff.typesAdded.push(t);
448
+ }
449
+ for (const t of types1) {
450
+ if (!types2.has(t)) result.exportsDiff.typesRemoved.push(t);
451
+ }
452
+
453
+ // re-exports (export { X } from './...')
454
+ // 以 name + isType 作为唯一标识, 检测新增/移除的再导出
455
+ const reKey = re => `${re.isType ? 'type:' : 'value:'}${re.name}`;
456
+ const reKeys1 = new Set(e1.reExports.map(reKey));
457
+ const reKeys2 = new Set(e2.reExports.map(reKey));
458
+ for (const re of e2.reExports) {
459
+ if (!reKeys1.has(reKey(re))) result.exportsDiff.reExportsAdded.push(re);
460
+ }
461
+ for (const re of e1.reExports) {
462
+ if (!reKeys2.has(reKey(re))) result.exportsDiff.reExportsRemoved.push(re);
463
+ }
464
+
465
+ // ── 2. 对比 interface/type 定义的方法 ──
466
+ const ifaceMap1 = new Map(scan1.interfaceDefs.map(d => [d.name, d]));
467
+ const ifaceMap2 = new Map(scan2.interfaceDefs.map(d => [d.name, d]));
468
+
469
+ const allIfaceNames = new Set([...ifaceMap1.keys(), ...ifaceMap2.keys()]);
470
+ for (const name of allIfaceNames) {
471
+ const def1 = ifaceMap1.get(name);
472
+ const def2 = ifaceMap2.get(name);
473
+
474
+ if (!def1 && def2) {
475
+ // 新增 interface
476
+ result.interfaceDefsDiff.push({
477
+ name,
478
+ change: 'added',
479
+ methods: def2.methods.map(m => m.name),
480
+ });
481
+ } else if (def1 && !def2) {
482
+ // 删除 interface
483
+ result.interfaceDefsDiff.push({
484
+ name,
485
+ change: 'removed',
486
+ methods: def1.methods.map(m => m.name),
487
+ });
488
+ } else if (def1 && def2) {
489
+ // 对比方法
490
+ const methods1 = new Map(def1.methods.map(m => [m.name, m.signature]));
491
+ const methods2 = new Map(def2.methods.map(m => [m.name, m.signature]));
492
+ const added = [];
493
+ const removed = [];
494
+ const modified = [];
495
+
496
+ for (const [mName, mSig] of methods2) {
497
+ if (!methods1.has(mName)) {
498
+ added.push({ name: mName, signature: mSig });
499
+ } else if (methods1.get(mName) !== mSig) {
500
+ modified.push({ name: mName, oldSignature: methods1.get(mName), newSignature: mSig });
501
+ }
502
+ }
503
+ for (const [mName, mSig] of methods1) {
504
+ if (!methods2.has(mName)) {
505
+ removed.push({ name: mName, signature: mSig });
506
+ }
507
+ }
508
+
509
+ if (added.length > 0 || removed.length > 0 || modified.length > 0) {
510
+ result.interfaceDefsDiff.push({
511
+ name,
512
+ change: 'modified',
513
+ added,
514
+ removed,
515
+ modified,
516
+ });
517
+ }
518
+ }
519
+ }
520
+
521
+ // ── 3. 对比 TurboModule Spec ──
522
+ const specMap1 = new Map();
523
+ for (const s of scan1.turboModuleSpecs) {
524
+ if (s.moduleName) specMap1.set(s.moduleName, s);
525
+ }
526
+ const specMap2 = new Map();
527
+ for (const s of scan2.turboModuleSpecs) {
528
+ if (s.moduleName) specMap2.set(s.moduleName, s);
529
+ }
530
+
531
+ const allModuleNames = new Set([...specMap1.keys(), ...specMap2.keys()]);
532
+ for (const modName of allModuleNames) {
533
+ const spec1 = specMap1.get(modName);
534
+ const spec2 = specMap2.get(modName);
535
+
536
+ if (!spec1 && spec2) {
537
+ // 新增 TurboModule
538
+ result.turboModuleDiff.push({
539
+ moduleName: modName,
540
+ change: 'added',
541
+ methods: spec2.methods.map(m => ({ name: m.name, signature: m.signature, change: 'added' })),
542
+ });
543
+ } else if (spec1 && !spec2) {
544
+ // 删除 TurboModule
545
+ result.turboModuleDiff.push({
546
+ moduleName: modName,
547
+ change: 'removed',
548
+ methods: spec1.methods.map(m => ({ name: m.name, signature: m.signature, change: 'removed' })),
549
+ });
550
+ } else if (spec1 && spec2) {
551
+ // 对比方法
552
+ const methods1 = new Map(spec1.methods.map(m => [m.name, m.signature]));
553
+ const methods2 = new Map(spec2.methods.map(m => [m.name, m.signature]));
554
+ const methodChanges = [];
555
+
556
+ for (const [mName, mSig] of methods2) {
557
+ if (!methods1.has(mName)) {
558
+ methodChanges.push({ name: mName, signature: mSig, change: 'added' });
559
+ } else if (methods1.get(mName) !== mSig) {
560
+ methodChanges.push({
561
+ name: mName,
562
+ oldSignature: methods1.get(mName),
563
+ newSignature: mSig,
564
+ change: 'modified',
565
+ });
566
+ }
567
+ }
568
+ for (const [mName, mSig] of methods1) {
569
+ if (!methods2.has(mName)) {
570
+ methodChanges.push({ name: mName, signature: mSig, change: 'removed' });
571
+ }
572
+ }
573
+
574
+ if (methodChanges.length > 0) {
575
+ result.turboModuleDiff.push({
576
+ moduleName: modName,
577
+ change: 'modified',
578
+ methods: methodChanges,
579
+ });
580
+ }
581
+ }
582
+ }
583
+
584
+ // 如果两个版本都没有 moduleName (可能模块名变了), 按 Spec 文件对比
585
+ if (result.turboModuleDiff.length === 0) {
586
+ const unnamedSpecs1 = scan1.turboModuleSpecs.filter(s => !s.moduleName);
587
+ const unnamedSpecs2 = scan2.turboModuleSpecs.filter(s => !s.moduleName);
588
+ if (unnamedSpecs1.length > 0 || unnamedSpecs2.length > 0) {
589
+ // 简单按文件名对比
590
+ const files1 = new Set(unnamedSpecs1.map(s => path.basename(s.file)));
591
+ const files2 = new Set(unnamedSpecs2.map(s => path.basename(s.file)));
592
+ if (files1.size !== files2.size || [...files1].some(f => !files2.has(f))) {
593
+ result.turboModuleDiff.push({
594
+ moduleName: '(unknown - module name may have changed)',
595
+ change: 'modified',
596
+ note: 'Spec 文件结构发生变化, 请手动检查',
597
+ specFiles1: unnamedSpecs1.map(s => s.file),
598
+ specFiles2: unnamedSpecs2.map(s => s.file),
599
+ });
600
+ }
601
+ }
602
+ }
603
+
604
+ // ── 4. 生成摘要判定 ──
605
+ const hasExportChanges = result.exportsDiff.added.length > 0
606
+ || result.exportsDiff.removed.length > 0
607
+ || result.exportsDiff.typesAdded.length > 0
608
+ || result.exportsDiff.typesRemoved.length > 0
609
+ || result.exportsDiff.reExportsAdded.length > 0
610
+ || result.exportsDiff.reExportsRemoved.length > 0
611
+ || result.exportsDiff.defaultChanged;
612
+
613
+ const hasInterfaceChanges = result.interfaceDefsDiff.length > 0;
614
+ const hasTurboModuleChanges = result.turboModuleDiff.length > 0;
615
+
616
+ result.summary.hasExportChanges = hasExportChanges;
617
+ result.summary.hasInterfaceChanges = hasInterfaceChanges;
618
+ result.summary.hasTurboModuleChanges = hasTurboModuleChanges;
619
+ result.summary.needsNativeAdaptation = hasTurboModuleChanges || hasInterfaceChanges;
620
+
621
+ return result;
622
+ }
623
+
624
+ // ── 输出格式化 ───────────────────────────────────────────
625
+
626
+ /**
627
+ * 将扫描结果格式化为人类可读的文本。
628
+ * @param {Object} scan 扫描结果
629
+ * @returns {string} 格式化文本
630
+ */
631
+ function formatScanResult(scan) {
632
+ const lines = [];
633
+ lines.push(`目录: ${scan.directory}`);
634
+ lines.push(`入口: ${scan.entryFile || '(未找到)'}`);
635
+ lines.push('');
636
+
637
+ // exports
638
+ lines.push('导出 (Exports):');
639
+ if (scan.exports.defaultExport) {
640
+ lines.push(` default: ${scan.exports.defaultExport}`);
641
+ }
642
+ if (scan.exports.named.length > 0) {
643
+ lines.push(` named: ${scan.exports.named.join(', ')}`);
644
+ }
645
+ if (scan.exports.types.length > 0) {
646
+ lines.push(` types: ${scan.exports.types.join(', ')}`);
647
+ }
648
+ if (scan.exports.reExports.length > 0) {
649
+ lines.push(` re-exports:`);
650
+ for (const re of scan.exports.reExports) {
651
+ const typeMark = re.isType ? 'type ' : '';
652
+ const wildMark = re.isWildcard ? '* ' : '';
653
+ lines.push(` ${typeMark}${wildMark}${re.name} from '${re.from}'`);
654
+ }
655
+ }
656
+ lines.push('');
657
+
658
+ // interface defs
659
+ if (scan.interfaceDefs.length > 0) {
660
+ lines.push('公共接口/类型定义:');
661
+ for (const def of scan.interfaceDefs) {
662
+ lines.push(` ${def.name} (${def.sourceFile}):`);
663
+ for (const m of def.methods) {
664
+ lines.push(` ${m.name}: ${m.signature}`);
665
+ }
666
+ }
667
+ lines.push('');
668
+ }
669
+
670
+ // turbo module specs
671
+ if (scan.turboModuleSpecs.length > 0) {
672
+ lines.push('TurboModule Spec:');
673
+ for (const spec of scan.turboModuleSpecs) {
674
+ lines.push(` 模块名: ${spec.moduleName || '(未检测到)'} (${spec.file})`);
675
+ lines.push(` 方法 (${spec.methods.length}):`);
676
+ for (const m of spec.methods) {
677
+ lines.push(` ${m.name}: ${m.signature}`);
678
+ }
679
+ }
680
+ lines.push('');
681
+ }
682
+
683
+ return lines.join('\n');
684
+ }
685
+
686
+ /**
687
+ * 将对比结果格式化为人类可读的文本。
688
+ * @param {Object} scan1
689
+ * @param {Object} scan2
690
+ * @param {Object} diff 对比结果
691
+ * @returns {string} 格式化文本
692
+ */
693
+ function formatDiffResult(scan1, scan2, diff) {
694
+ const lines = [];
695
+ lines.push('==========================================');
696
+ lines.push(' 接口差异分析');
697
+ lines.push('==========================================');
698
+ lines.push('');
699
+
700
+ // 摘要判定
701
+ lines.push('## 判定结果');
702
+ lines.push('');
703
+ if (diff.summary.needsNativeAdaptation) {
704
+ lines.push('⚠️ 需要原生适配 — JS 层接口或 TurboModule Spec 发生变更');
705
+ } else {
706
+ lines.push('✅ 无需原生适配 — JS 层接口未变更 (可能仅 bug 修复/内部重构)');
707
+ }
708
+ lines.push('');
709
+ lines.push(`| 检查项 | 结果 |`);
710
+ lines.push(`|--------|------|`);
711
+ lines.push(`| 导出变更 | ${diff.summary.hasExportChanges ? '⚠️ 有变更' : '✅ 无变更'} |`);
712
+ lines.push(`| 接口定义变更 | ${diff.summary.hasInterfaceChanges ? '⚠️ 有变更' : '✅ 无变更'} |`);
713
+ lines.push(`| TurboModule Spec 变更 | ${diff.summary.hasTurboModuleChanges ? '⚠️ 有变更' : '✅ 无变更'} |`);
714
+ lines.push(`| 需要原生适配 | ${diff.summary.needsNativeAdaptation ? '⚠️ 是' : '✅ 否'} |`);
715
+ lines.push('');
716
+
717
+ // exports diff
718
+ const e = diff.exportsDiff;
719
+ if (e.defaultChanged || e.added.length > 0 || e.removed.length > 0 || e.typesAdded.length > 0 || e.typesRemoved.length > 0 || e.reExportsAdded.length > 0 || e.reExportsRemoved.length > 0) {
720
+ lines.push('## JS 导出变更');
721
+ lines.push('');
722
+ if (e.defaultChanged) {
723
+ lines.push(`- default export: ${e.defaultFrom || '(无)'} → ${e.defaultTo || '(无)'}`);
724
+ }
725
+ if (e.added.length > 0) lines.push(`- 新增导出: ${e.added.join(', ')}`);
726
+ if (e.removed.length > 0) lines.push(`- 移除导出: ${e.removed.join(', ')}`);
727
+ if (e.typesAdded.length > 0) lines.push(`- 新增类型导出: ${e.typesAdded.join(', ')}`);
728
+ if (e.typesRemoved.length > 0) lines.push(`- 移除类型导出: ${e.typesRemoved.join(', ')}`);
729
+ if (e.reExportsAdded.length > 0) {
730
+ lines.push(`- 新增再导出: ${e.reExportsAdded.map(re => `\`${re.isType ? 'type ' : ''}${re.name}\` (from '${re.from}')`).join(', ')}`);
731
+ }
732
+ if (e.reExportsRemoved.length > 0) {
733
+ lines.push(`- 移除再导出: ${e.reExportsRemoved.map(re => `\`${re.isType ? 'type ' : ''}${re.name}\` (from '${re.from}')`).join(', ')}`);
734
+ }
735
+ lines.push('');
736
+ }
737
+
738
+ // interface defs diff
739
+ if (diff.interfaceDefsDiff.length > 0) {
740
+ lines.push('## 公共接口/类型定义变更');
741
+ lines.push('');
742
+ for (const d of diff.interfaceDefsDiff) {
743
+ if (d.change === 'added') {
744
+ lines.push(`### ➕ 新增接口: ${d.name}`);
745
+ for (const m of d.methods) lines.push(` - ${m}`);
746
+ } else if (d.change === 'removed') {
747
+ lines.push(`### ➖ 移除接口: ${d.name}`);
748
+ for (const m of d.methods) lines.push(` - ${m}`);
749
+ } else if (d.change === 'modified') {
750
+ lines.push(`### ✏️ 修改接口: ${d.name}`);
751
+ if (d.added.length > 0) {
752
+ lines.push(' 新增方法:');
753
+ for (const m of d.added) lines.push(` + ${m.name}: ${m.signature}`);
754
+ }
755
+ if (d.removed.length > 0) {
756
+ lines.push(' 移除方法:');
757
+ for (const m of d.removed) lines.push(` - ${m.name}: ${m.signature}`);
758
+ }
759
+ if (d.modified.length > 0) {
760
+ lines.push(' 修改方法:');
761
+ for (const m of d.modified) {
762
+ lines.push(` * ${m.name}:`);
763
+ lines.push(` 旧: ${m.oldSignature}`);
764
+ lines.push(` 新: ${m.newSignature}`);
765
+ }
766
+ }
767
+ }
768
+ lines.push('');
769
+ }
770
+ }
771
+
772
+ // turbo module diff
773
+ if (diff.turboModuleDiff.length > 0) {
774
+ lines.push('## TurboModule Spec 变更');
775
+ lines.push('');
776
+ for (const d of diff.turboModuleDiff) {
777
+ if (d.change === 'added') {
778
+ lines.push(`### ➕ 新增模块: ${d.moduleName}`);
779
+ for (const m of d.methods) lines.push(` + ${m.name}: ${m.signature}`);
780
+ } else if (d.change === 'removed') {
781
+ lines.push(`### ➖ 移除模块: ${d.moduleName}`);
782
+ for (const m of d.methods) lines.push(` - ${m.name}: ${m.signature}`);
783
+ } else if (d.change === 'modified') {
784
+ lines.push(`### ✏️ 修改模块: ${d.moduleName}`);
785
+ if (d.note) {
786
+ lines.push(` ⚠️ ${d.note}`);
787
+ if (d.specFiles1) lines.push(` 旧 Spec 文件: ${d.specFiles1.join(', ')}`);
788
+ if (d.specFiles2) lines.push(` 新 Spec 文件: ${d.specFiles2.join(', ')}`);
789
+ } else {
790
+ for (const m of d.methods) {
791
+ if (m.change === 'added') {
792
+ lines.push(` + ${m.name}: ${m.signature}`);
793
+ } else if (m.change === 'removed') {
794
+ lines.push(` - ${m.name}: ${m.signature}`);
795
+ } else if (m.change === 'modified') {
796
+ lines.push(` * ${m.name}:`);
797
+ lines.push(` 旧: ${m.oldSignature}`);
798
+ lines.push(` 新: ${m.newSignature}`);
799
+ }
800
+ }
801
+ }
802
+ }
803
+ lines.push('');
804
+ }
805
+ }
806
+
807
+ return lines.join('\n');
808
+ }
809
+
810
+ // ── Markdown 文档生成 (适配检查清单) ─────────────────────
811
+
812
+ /**
813
+ * 生成适配检查清单 Markdown 文档。
814
+ *
815
+ * 以"适配任务"为单位组织内容, 每个接口变更对应一个检查项 (含 checkbox),
816
+ * 直接指导后续代码适配环节, 避免遗漏。
817
+ *
818
+ * 文档结构:
819
+ * 1. 判定结果 (是否需要原生适配)
820
+ * 2. 适配检查清单 (TurboModule Spec 变更 → 接口定义变更 → JS 导出变更)
821
+ * 3. 无需适配项 (接口未变, 仅计数)
822
+ * 4. 附录: 完整接口快照 (旧版 + 新版)
823
+ *
824
+ * @param {Object} scan1 版本1 的扫描结果
825
+ * @param {Object} scan2 版本2 的扫描结果
826
+ * @param {Object} diff 对比结果
827
+ * @param {Object} meta 元信息 { version1, version2, repoUrl }
828
+ * @returns {string} Markdown 内容
829
+ */
830
+ function generateMarkdownReport(scan1, scan2, diff, meta = {}) {
831
+ const v1 = meta.version1 || '版本1';
832
+ const v2 = meta.version2 || '版本2';
833
+ const lines = [];
834
+
835
+ // ── 标题与元信息 ──
836
+ lines.push(`# 接口差异适配检查清单: ${v1} → ${v2}`);
837
+ lines.push('');
838
+ if (meta.repoUrl) {
839
+ lines.push(`**仓库:** ${meta.repoUrl}`);
840
+ }
841
+ lines.push(`**版本 1:** ${v1} (入口: ${scan1.entryFile || '未检测到'})`);
842
+ lines.push(`**版本 2:** ${v2} (入口: ${scan2.entryFile || '未检测到'})`);
843
+ lines.push(`**生成日期:** ${new Date().toISOString().split('T')[0]}`);
844
+ lines.push('');
845
+
846
+ // ── 1. 判定结果 ──
847
+ lines.push('## 判定结果');
848
+ lines.push('');
849
+ if (diff.summary.needsNativeAdaptation) {
850
+ lines.push('> ⚠️ **需要原生适配** — JS 层接口或 TurboModule Spec 发生变更, 鸿蒙侧需同步修改。');
851
+ } else {
852
+ lines.push('> ✅ **无需原生适配** — JS 层接口未变更, 可能仅 bug 修复/内部重构。');
853
+ lines.push('> 鸿蒙侧只需合并 JS 代码 + 重新 codegen + 验证 RNOH 框架兼容性。');
854
+ }
855
+ lines.push('');
856
+ lines.push('| 检查项 | 结果 | 说明 |');
857
+ lines.push('|--------|------|------|');
858
+ lines.push(`| JS 导出变更 | ${diff.summary.hasExportChanges ? '⚠️ 有' : '✅ 无'} | default/named/type 导出的增删改 |`);
859
+ lines.push(`| 接口定义变更 | ${diff.summary.hasInterfaceChanges ? '⚠️ 有' : '✅ 无'} | 公共 interface/type 的方法增删改 |`);
860
+ lines.push(`| TurboModule Spec 变更 | ${diff.summary.hasTurboModuleChanges ? '⚠️ 有' : '✅ 无'} | 原生模块方法签名变更 |`);
861
+ lines.push(`| **需要原生适配** | ${diff.summary.needsNativeAdaptation ? '⚠️ **是**' : '✅ **否**'} | 接口不变则原生无需动 |`);
862
+ lines.push('');
863
+ lines.push('---');
864
+ lines.push('');
865
+
866
+ // ── 2. 适配检查清单 ──
867
+ lines.push('## 适配检查清单');
868
+ lines.push('');
869
+ lines.push('> 以下每个检查项对应一个需要适配的接口变更。');
870
+ lines.push('> 完成后勾选 `[x]` 标记为已完成。');
871
+ lines.push('');
872
+
873
+ let itemNum = 0;
874
+
875
+ // ── 2a. TurboModule Spec 变更 (最高优先级) ──
876
+ if (diff.turboModuleDiff.length > 0) {
877
+ lines.push('### TurboModule Spec 变更');
878
+ lines.push('');
879
+ lines.push('> 原生模块接口变更, 需同步更新 codegen、ETS TurboModule、C++ Package。');
880
+ lines.push('');
881
+
882
+ for (const d of diff.turboModuleDiff) {
883
+ if (d.change === 'added') {
884
+ itemNum++;
885
+ lines.push(`- [ ] **#${itemNum} 新增 TurboModule: \`${d.moduleName}\`**`);
886
+ lines.push(` - 动作: 在 ETS 中实现新模块, 更新 C++ codegen methodMap, 更新 Package 注册`);
887
+ lines.push(` - 新增方法 (${d.methods.length}):`);
888
+ for (const m of d.methods) {
889
+ lines.push(` - \`${m.name}\`: \`${m.signature}\``);
890
+ }
891
+ lines.push('');
892
+ } else if (d.change === 'removed') {
893
+ itemNum++;
894
+ lines.push(`- [ ] **#${itemNum} 移除 TurboModule: \`${d.moduleName}\`**`);
895
+ lines.push(` - 动作: 删除 ETS TurboModule 实现, 删除 C++ codegen, 移除 Package 注册`);
896
+ lines.push(` - 移除方法 (${d.methods.length}):`);
897
+ for (const m of d.methods) {
898
+ lines.push(` - \`${m.name}\`: \`${m.signature}\``);
899
+ }
900
+ lines.push('');
901
+ } else if (d.change === 'modified') {
902
+ if (d.note) {
903
+ itemNum++;
904
+ lines.push(`- [ ] **#${itemNum} TurboModule 结构变更: \`${d.moduleName}\`**`);
905
+ lines.push(` - ⚠️ ${d.note}`);
906
+ if (d.specFiles1) lines.push(` - 旧 Spec: ${d.specFiles1.join(', ')}`);
907
+ if (d.specFiles2) lines.push(` - 新 Spec: ${d.specFiles2.join(', ')}`);
908
+ lines.push(` - 动作: 手动检查 Spec 文件, 更新 codegen + ETS + C++`);
909
+ } else {
910
+ // 按方法变更类型分组
911
+ const added = d.methods.filter(m => m.change === 'added');
912
+ const removed = d.methods.filter(m => m.change === 'removed');
913
+ const modified = d.methods.filter(m => m.change === 'modified');
914
+
915
+ if (added.length > 0) {
916
+ itemNum++;
917
+ lines.push(`- [ ] **#${itemNum} TurboModule \`${d.moduleName}\` 新增方法**`);
918
+ lines.push(` - 动作: ETS 实现新方法 + C++ codegen 新增方法映射`);
919
+ for (const m of added) {
920
+ lines.push(` - \`${m.name}\`: \`${m.signature}\``);
921
+ }
922
+ lines.push('');
923
+ }
924
+ if (removed.length > 0) {
925
+ itemNum++;
926
+ lines.push(`- [ ] **#${itemNum} TurboModule \`${d.moduleName}\` 移除方法**`);
927
+ lines.push(` - 动作: 删除 ETS 方法 + C++ codegen 移除方法映射`);
928
+ for (const m of removed) {
929
+ lines.push(` - \`${m.name}\`: \`${m.signature}\``);
930
+ }
931
+ lines.push('');
932
+ }
933
+ if (modified.length > 0) {
934
+ itemNum++;
935
+ lines.push(`- [ ] **#${itemNum} TurboModule \`${d.moduleName}\` 修改方法签名**`);
936
+ lines.push(` - 动作: 更新 ETS 方法签名 + C++ codegen 参数数量 + 验证返回值类型兼容`);
937
+ for (const m of modified) {
938
+ lines.push(` - \`${m.name}\`:`);
939
+ lines.push(` - 旧: \`${m.oldSignature}\``);
940
+ lines.push(` - 新: \`${m.newSignature}\``);
941
+ }
942
+ lines.push('');
943
+ }
944
+ }
945
+ }
946
+ }
947
+ }
948
+
949
+ // ── 2b. 公共接口/类型定义变更 ──
950
+ if (diff.interfaceDefsDiff.length > 0) {
951
+ lines.push('### 公共接口/类型定义变更');
952
+ lines.push('');
953
+ lines.push('> JS 层公共 API 接口变更, 需同步鸿蒙化仓库的 src/ 代码。');
954
+ lines.push('');
955
+
956
+ for (const d of diff.interfaceDefsDiff) {
957
+ if (d.change === 'added') {
958
+ itemNum++;
959
+ lines.push(`- [ ] **#${itemNum} 新增接口: \`${d.name}\`**`);
960
+ lines.push(` - 动作: 合并新接口到鸿蒙化仓库 src/`);
961
+ lines.push(` - 方法:`);
962
+ for (const m of d.methods) lines.push(` - \`${m}\``);
963
+ lines.push('');
964
+ } else if (d.change === 'removed') {
965
+ itemNum++;
966
+ lines.push(`- [ ] **#${itemNum} 移除接口: \`${d.name}\`**`);
967
+ lines.push(` - 动作: 从鸿蒙化仓库 src/ 删除旧接口`);
968
+ lines.push(` - 方法:`);
969
+ for (const m of d.methods) lines.push(` - \`${m}\``);
970
+ lines.push('');
971
+ } else if (d.change === 'modified') {
972
+ if (d.added.length > 0) {
973
+ itemNum++;
974
+ lines.push(`- [ ] **#${itemNum} 接口 \`${d.name}\` 新增方法**`);
975
+ lines.push(` - 动作: 在鸿蒙化仓库 src/ 中新增对应方法`);
976
+ for (const m of d.added) {
977
+ lines.push(` - \`${m.name}\`: \`${m.signature}\``);
978
+ }
979
+ lines.push('');
980
+ }
981
+ if (d.removed.length > 0) {
982
+ itemNum++;
983
+ lines.push(`- [ ] **#${itemNum} 接口 \`${d.name}\` 移除方法**`);
984
+ lines.push(` - 动作: 从鸿蒙化仓库 src/ 中删除对应方法`);
985
+ for (const m of d.removed) {
986
+ lines.push(` - \`${m.name}\`: \`${m.signature}\``);
987
+ }
988
+ lines.push('');
989
+ }
990
+ if (d.modified.length > 0) {
991
+ itemNum++;
992
+ lines.push(`- [ ] **#${itemNum} 接口 \`${d.name}\` 修改方法签名**`);
993
+ lines.push(` - 动作: 更新鸿蒙化仓库 src/ 中的方法签名`);
994
+ for (const m of d.modified) {
995
+ lines.push(` - \`${m.name}\`:`);
996
+ lines.push(` - 旧: \`${m.oldSignature}\``);
997
+ lines.push(` - 新: \`${m.newSignature}\``);
998
+ }
999
+ lines.push('');
1000
+ }
1001
+ }
1002
+ }
1003
+ }
1004
+
1005
+ // ── 2c. JS 导出变更 ──
1006
+ const e = diff.exportsDiff;
1007
+ if (e.defaultChanged || e.added.length > 0 || e.removed.length > 0 || e.typesAdded.length > 0 || e.typesRemoved.length > 0 || e.reExportsAdded.length > 0 || e.reExportsRemoved.length > 0) {
1008
+ lines.push('### JS 导出变更');
1009
+ lines.push('');
1010
+ lines.push('> 入口文件的导出变更, 影响包的对外 API。');
1011
+ lines.push('');
1012
+
1013
+ if (e.defaultChanged) {
1014
+ itemNum++;
1015
+ lines.push(`- [ ] **#${itemNum} default export 变更**`);
1016
+ lines.push(` - 旧: \`${e.defaultFrom || '(无)'}\``);
1017
+ lines.push(` - 新: \`${e.defaultTo || '(无)'}\``);
1018
+ lines.push(` - 动作: 更新鸿蒙化仓库入口文件的 default export`);
1019
+ lines.push('');
1020
+ }
1021
+ if (e.added.length > 0) {
1022
+ itemNum++;
1023
+ lines.push(`- [ ] **#${itemNum} 新增 named 导出**`);
1024
+ lines.push(` - 导出: ${e.added.map(n => `\`${n}\``).join(', ')}`);
1025
+ lines.push(` - 动作: 确保新导出在鸿蒙化仓库中可用`);
1026
+ lines.push('');
1027
+ }
1028
+ if (e.removed.length > 0) {
1029
+ itemNum++;
1030
+ lines.push(`- [ ] **#${itemNum} 移除 named 导出**`);
1031
+ lines.push(` - 导出: ${e.removed.map(n => `\`${n}\``).join(', ')}`);
1032
+ lines.push(` - 动作: 从鸿蒙化仓库中移除对应导出`);
1033
+ lines.push('');
1034
+ }
1035
+ if (e.typesAdded.length > 0) {
1036
+ itemNum++;
1037
+ lines.push(`- [ ] **#${itemNum} 新增 type 导出**`);
1038
+ lines.push(` - 类型: ${e.typesAdded.map(n => `\`${n}\``).join(', ')}`);
1039
+ lines.push(` - 动作: 确保新类型在鸿蒙化仓库中可用`);
1040
+ lines.push('');
1041
+ }
1042
+ if (e.typesRemoved.length > 0) {
1043
+ itemNum++;
1044
+ lines.push(`- [ ] **#${itemNum} 移除 type 导出**`);
1045
+ lines.push(` - 类型: ${e.typesRemoved.map(n => `\`${n}\``).join(', ')}`);
1046
+ lines.push(` - 动作: 从鸿蒙化仓库中移除对应类型导出`);
1047
+ lines.push('');
1048
+ }
1049
+ if (e.reExportsAdded.length > 0) {
1050
+ itemNum++;
1051
+ lines.push(`- [ ] **#${itemNum} 新增再导出 (re-export)**`);
1052
+ lines.push(` - 动作: 确保新增的再导出在鸿蒙化仓库入口文件中可用`);
1053
+ for (const re of e.reExportsAdded) {
1054
+ lines.push(` - \`${re.isType ? 'type ' : ''}${re.name}\` (from \`${re.from}\`)`);
1055
+ }
1056
+ lines.push('');
1057
+ }
1058
+ if (e.reExportsRemoved.length > 0) {
1059
+ itemNum++;
1060
+ lines.push(`- [ ] **#${itemNum} 移除再导出 (re-export)**`);
1061
+ lines.push(` - 动作: 从鸿蒙化仓库入口文件中移除对应再导出`);
1062
+ for (const re of e.reExportsRemoved) {
1063
+ lines.push(` - \`${re.isType ? 'type ' : ''}${re.name}\` (was from \`${re.from}\`)`);
1064
+ }
1065
+ lines.push('');
1066
+ }
1067
+ }
1068
+
1069
+ if (itemNum === 0) {
1070
+ lines.push('*无接口变更, 无需适配。*');
1071
+ lines.push('');
1072
+ }
1073
+
1074
+ lines.push('---');
1075
+ lines.push('');
1076
+
1077
+ // ── 3. 无需适配项 ──
1078
+ lines.push('## 无需适配项');
1079
+ lines.push('');
1080
+ if (!diff.summary.needsNativeAdaptation) {
1081
+ lines.push('- ✅ JS 层接口未变更 — 仅 bug 修复/内部重构, 合并 JS 代码即可');
1082
+ lines.push('- ✅ TurboModule Spec 未变更 — 原生模块无需修改');
1083
+ lines.push('');
1084
+ } else {
1085
+ // 即使需要适配, 也标注哪些部分不需要动
1086
+ if (!diff.summary.hasTurboModuleChanges) {
1087
+ lines.push('- ✅ TurboModule Spec 未变更 — 原生模块方法签名不变');
1088
+ }
1089
+ if (!diff.summary.hasInterfaceChanges) {
1090
+ lines.push('- ✅ 公共接口定义未变更 — JS 层 API 不变');
1091
+ }
1092
+ if (!diff.summary.hasExportChanges) {
1093
+ lines.push('- ✅ JS 导出未变更 — 入口文件 API 不变');
1094
+ }
1095
+ lines.push('- ℹ️ 纯 bug 修复/内部重构的文件变更 (不涉及接口) — 合并 JS 代码即可, 无需原生适配');
1096
+ lines.push('');
1097
+ }
1098
+
1099
+ lines.push('---');
1100
+ lines.push('');
1101
+
1102
+ // ── 4. 附录: 完整接口快照 ──
1103
+ lines.push('## 附录: 接口快照');
1104
+ lines.push('');
1105
+
1106
+ // 旧版
1107
+ lines.push(`### ${v1} 接口快照`);
1108
+ lines.push('');
1109
+ lines.push(`**入口:** ${scan1.entryFile || '(未检测到)'}`);
1110
+ lines.push('');
1111
+ if (scan1.exports.defaultExport) {
1112
+ lines.push(`- default: \`${scan1.exports.defaultExport}\``);
1113
+ }
1114
+ if (scan1.exports.named.length > 0) {
1115
+ lines.push(`- named: ${scan1.exports.named.map(n => `\`${n}\``).join(', ')}`);
1116
+ }
1117
+ if (scan1.exports.types.length > 0) {
1118
+ lines.push(`- types: ${scan1.exports.types.map(n => `\`${n}\``).join(', ')}`);
1119
+ }
1120
+ if (scan1.exports.reExports.length > 0) {
1121
+ lines.push(`- re-exports:`);
1122
+ for (const re of scan1.exports.reExports) {
1123
+ const typeMark = re.isType ? 'type ' : '';
1124
+ const wildMark = re.isWildcard ? '* ' : '';
1125
+ lines.push(` - ${typeMark}${wildMark}\`${re.name}\` from \`${re.from}\``);
1126
+ }
1127
+ }
1128
+ for (const def of scan1.interfaceDefs) {
1129
+ lines.push(`- **${def.name}** (${def.sourceFile}):`);
1130
+ for (const m of def.methods) {
1131
+ lines.push(` - \`${m.name}: ${m.signature}\``);
1132
+ }
1133
+ }
1134
+ for (const spec of scan1.turboModuleSpecs) {
1135
+ lines.push(`- **TurboModule: ${spec.moduleName || '(未命名)'}** (${spec.file})`);
1136
+ for (const m of spec.methods) {
1137
+ lines.push(` - \`${m.name}: ${m.signature}\``);
1138
+ }
1139
+ }
1140
+ lines.push('');
1141
+
1142
+ // 新版
1143
+ lines.push(`### ${v2} 接口快照`);
1144
+ lines.push('');
1145
+ lines.push(`**入口:** ${scan2.entryFile || '(未检测到)'}`);
1146
+ lines.push('');
1147
+ if (scan2.exports.defaultExport) {
1148
+ lines.push(`- default: \`${scan2.exports.defaultExport}\``);
1149
+ }
1150
+ if (scan2.exports.named.length > 0) {
1151
+ lines.push(`- named: ${scan2.exports.named.map(n => `\`${n}\``).join(', ')}`);
1152
+ }
1153
+ if (scan2.exports.types.length > 0) {
1154
+ lines.push(`- types: ${scan2.exports.types.map(n => `\`${n}\``).join(', ')}`);
1155
+ }
1156
+ if (scan2.exports.reExports.length > 0) {
1157
+ lines.push(`- re-exports:`);
1158
+ for (const re of scan2.exports.reExports) {
1159
+ const typeMark = re.isType ? 'type ' : '';
1160
+ const wildMark = re.isWildcard ? '* ' : '';
1161
+ lines.push(` - ${typeMark}${wildMark}\`${re.name}\` from \`${re.from}\``);
1162
+ }
1163
+ }
1164
+ for (const def of scan2.interfaceDefs) {
1165
+ lines.push(`- **${def.name}** (${def.sourceFile}):`);
1166
+ for (const m of def.methods) {
1167
+ lines.push(` - \`${m.name}: ${m.signature}\``);
1168
+ }
1169
+ }
1170
+ for (const spec of scan2.turboModuleSpecs) {
1171
+ lines.push(`- **TurboModule: ${spec.moduleName || '(未命名)'}** (${spec.file})`);
1172
+ for (const m of spec.methods) {
1173
+ lines.push(` - \`${m.name}: ${m.signature}\``);
1174
+ }
1175
+ }
1176
+ lines.push('');
1177
+
1178
+ return lines.join('\n');
1179
+ }
1180
+
1181
+ function main() {
1182
+ const opts = parseArgs();
1183
+
1184
+ if (opts.compare) {
1185
+ // 对比模式
1186
+ const [dir1, dir2] = opts.dirs;
1187
+ if (!opts.quiet) info(`扫描目录 1: ${dir1}`);
1188
+ const scan1 = scanDirectory(dir1, opts.entry);
1189
+ if (!opts.quiet) info(`扫描目录 2: ${dir2}`);
1190
+ const scan2 = scanDirectory(dir2, opts.entry);
1191
+
1192
+ const diff = compareScans(scan1, scan2);
1193
+
1194
+ if (opts.output) {
1195
+ // 写入 Markdown 文件
1196
+ const report = generateMarkdownReport(scan1, scan2, diff, {
1197
+ version1: path.basename(dir1),
1198
+ version2: path.basename(dir2),
1199
+ });
1200
+ const outputPath = path.resolve(opts.output);
1201
+ fs.writeFileSync(outputPath, report, 'utf-8');
1202
+ if (!opts.quiet) {
1203
+ ok(`适配检查清单已写入: ${outputPath}`);
1204
+ // 也在终端打印摘要
1205
+ console.log('');
1206
+ console.log(formatDiffResult(scan1, scan2, diff));
1207
+ }
1208
+ } else if (opts.quiet) {
1209
+ // JSON 输出
1210
+ console.log(JSON.stringify({ scan1, scan2, diff }, null, 2));
1211
+ } else {
1212
+ console.log(formatDiffResult(scan1, scan2, diff));
1213
+ }
1214
+ } else {
1215
+ // 单目录扫描模式
1216
+ const [dir] = opts.dirs;
1217
+ if (!opts.quiet) info(`扫描目录: ${dir}`);
1218
+ const scan = scanDirectory(dir, opts.entry);
1219
+
1220
+ if (opts.output) {
1221
+ // 单目录模式也支持写入 Markdown 文件 (接口快照)
1222
+ const report = generateSingleScanMarkdown(scan, {
1223
+ version: path.basename(dir),
1224
+ });
1225
+ const outputPath = path.resolve(opts.output);
1226
+ fs.writeFileSync(outputPath, report, 'utf-8');
1227
+ if (!opts.quiet) ok(`接口快照已写入: ${outputPath}`);
1228
+ } else if (opts.quiet) {
1229
+ console.log(JSON.stringify(scan, null, 2));
1230
+ } else {
1231
+ console.log(formatScanResult(scan));
1232
+ }
1233
+ }
1234
+ }
1235
+
1236
+ /**
1237
+ * 生成单目录扫描的 Markdown 文档 (接口快照)。
1238
+ *
1239
+ * @param {Object} scan 扫描结果
1240
+ * @param {Object} meta 元信息
1241
+ * @returns {string} Markdown 内容
1242
+ */
1243
+ function generateSingleScanMarkdown(scan, meta = {}) {
1244
+ const version = meta.version || '扫描结果';
1245
+ const lines = [];
1246
+
1247
+ lines.push(`# 接口快照: ${version}`);
1248
+ lines.push('');
1249
+ lines.push(`**目录:** ${scan.directory}`);
1250
+ lines.push(`**入口:** ${scan.entryFile || '(未检测到)'}`);
1251
+ lines.push(`**生成日期:** ${new Date().toISOString().split('T')[0]}`);
1252
+ lines.push('');
1253
+ lines.push('---');
1254
+ lines.push('');
1255
+
1256
+ // exports
1257
+ lines.push('## 导出 (Exports)');
1258
+ lines.push('');
1259
+ if (scan.exports.defaultExport) {
1260
+ lines.push(`- **default:** \`${scan.exports.defaultExport}\``);
1261
+ }
1262
+ if (scan.exports.named.length > 0) {
1263
+ lines.push(`- **named:** ${scan.exports.named.map(n => `\`${n}\``).join(', ')}`);
1264
+ }
1265
+ if (scan.exports.types.length > 0) {
1266
+ lines.push(`- **types:** ${scan.exports.types.map(n => `\`${n}\``).join(', ')}`);
1267
+ }
1268
+ if (scan.exports.reExports.length > 0) {
1269
+ lines.push('- **re-exports:**');
1270
+ for (const re of scan.exports.reExports) {
1271
+ const typeMark = re.isType ? 'type ' : '';
1272
+ const wildMark = re.isWildcard ? '* ' : '';
1273
+ lines.push(` - ${typeMark}${wildMark}\`${re.name}\` from \`${re.from}\``);
1274
+ }
1275
+ }
1276
+ if (scan.exports.defaultExport === null && scan.exports.named.length === 0 && scan.exports.types.length === 0 && scan.exports.reExports.length === 0) {
1277
+ lines.push('*(无导出)*');
1278
+ }
1279
+ lines.push('');
1280
+
1281
+ // interface defs
1282
+ if (scan.interfaceDefs.length > 0) {
1283
+ lines.push('## 公共接口/类型定义');
1284
+ lines.push('');
1285
+ for (const def of scan.interfaceDefs) {
1286
+ lines.push(`### \`${def.name}\` (${def.sourceFile})`);
1287
+ lines.push('');
1288
+ lines.push('| 方法 | 签名 |');
1289
+ lines.push('|------|------|');
1290
+ for (const m of def.methods) {
1291
+ lines.push(`| \`${m.name}\` | \`${m.signature}\` |`);
1292
+ }
1293
+ lines.push('');
1294
+ }
1295
+ }
1296
+
1297
+ // turbo module specs
1298
+ if (scan.turboModuleSpecs.length > 0) {
1299
+ lines.push('## TurboModule Spec');
1300
+ lines.push('');
1301
+ for (const spec of scan.turboModuleSpecs) {
1302
+ lines.push(`### 模块: \`${spec.moduleName || '(未检测到)'}\` (${spec.file})`);
1303
+ lines.push('');
1304
+ lines.push('| 方法 | 签名 |');
1305
+ lines.push('|------|------|');
1306
+ for (const m of spec.methods) {
1307
+ lines.push(`| \`${m.name}\` | \`${m.signature}\` |`);
1308
+ }
1309
+ lines.push('');
1310
+ }
1311
+ }
1312
+
1313
+ if (scan.interfaceDefs.length === 0 && scan.turboModuleSpecs.length === 0) {
1314
+ lines.push('## 公共接口/类型定义');
1315
+ lines.push('');
1316
+ lines.push('*(未检测到公共接口或 TurboModule Spec)*');
1317
+ lines.push('');
1318
+ }
1319
+
1320
+ return lines.join('\n');
1321
+ }
1322
+
1323
+ // ── 模块导出 (供 generate-diff.js 调用) ──────────────────
1324
+
1325
+ module.exports = {
1326
+ scanDirectory,
1327
+ compareScans,
1328
+ generateMarkdownReport,
1329
+ formatDiffResult,
1330
+ };
1331
+
1332
+ // ── 直接运行时执行主流程 ──────────────────────────────────
1333
+
1334
+ if (require.main === module) {
1335
+ main();
1336
+ }