android2harmony 0.1.6 → 0.1.8

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 (227) hide show
  1. package/agents/api-analyzer.md +113 -0
  2. package/agents/app-action.md +122 -0
  3. package/agents/code-reviewer.md +191 -295
  4. package/agents/coder.md +377 -0
  5. package/agents/self-tester.md +8 -8
  6. package/agents/spec-designer.md +56 -0
  7. package/package.json +1 -1
  8. package/skills/a2h-app-req-gen/SKILL.md +174 -0
  9. package/skills/a2h-app-req-gen/references/spec-handoff.md +99 -0
  10. package/skills/a2h-app-req-gen/references/subagent-render.md +115 -0
  11. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/build.gradle.kts +41 -0
  12. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradle/wrapper/gradle-wrapper.jar +0 -0
  13. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradle/wrapper/gradle-wrapper.properties +8 -0
  14. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradle.properties +3 -0
  15. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradlew +251 -0
  16. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradlew.bat +94 -0
  17. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/settings.gradle.kts +17 -0
  18. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawAnalysisDetector.java +1911 -0
  19. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawIssueRegistry.java +37 -0
  20. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawJson.java +146 -0
  21. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawLocation.java +96 -0
  22. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawRecordWriter.java +227 -0
  23. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry +1 -0
  24. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/resources/dev/reqgen/analyzer/jvm/raw-record-v1.schema.json +93 -0
  25. package/skills/a2h-app-req-gen/scripts/reqgen/assets/dashboard.html +151 -0
  26. package/skills/a2h-app-req-gen/scripts/reqgen/gradle/reqgen-lint.init.gradle +60 -0
  27. package/skills/a2h-app-req-gen/scripts/reqgen/gradle/reqgen-model.init.gradle +204 -0
  28. package/skills/a2h-app-req-gen/scripts/reqgen/package.json +14 -0
  29. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/backend.js +323 -0
  30. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/cli.js +811 -0
  31. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/gap-audit-link.js +62 -0
  32. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/index.js +66 -0
  33. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/merge.js +605 -0
  34. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/name-trace.js +153 -0
  35. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/orchestrate.js +1324 -0
  36. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/output-parse.js +50 -0
  37. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/overview.js +10 -0
  38. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/pool.js +23 -0
  39. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/post-merge-fix.js +146 -0
  40. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/prompt-loader.js +51 -0
  41. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/prompt.js +967 -0
  42. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/prompts.md +301 -0
  43. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/serve.js +78 -0
  44. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/short-id.js +46 -0
  45. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/snapshot.js +267 -0
  46. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/types.js +1 -0
  47. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/validate.js +122 -0
  48. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/callback-call-sites.js +62 -0
  49. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/gap-audit/build.js +1291 -0
  50. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/gap-audit/index.js +1 -0
  51. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/area-ownership.js +392 -0
  52. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/build.js +1995 -0
  53. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/callback-flow.js +277 -0
  54. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/component-variants.js +725 -0
  55. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/index.js +15 -0
  56. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/names/index.js +12 -0
  57. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/names/policy.js +469 -0
  58. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/names/traceability.js +468 -0
  59. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/req-model/build.js +1066 -0
  60. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/req-model/index.js +1 -0
  61. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/android-interaction-registry.js +174 -0
  62. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/android.js +1062 -0
  63. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/area-titles.js +678 -0
  64. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/builder.js +350 -0
  65. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/compose-api-registry.js +395 -0
  66. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/compose-call-sites.js +2008 -0
  67. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/compose.js +9059 -0
  68. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/dynamic-repeated-call-sites.js +529 -0
  69. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/index.js +1 -0
  70. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/interaction-effects.js +1149 -0
  71. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/jvm.js +6272 -0
  72. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/normalize.js +33 -0
  73. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/pending-intents.js +565 -0
  74. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/resource-id.js +42 -0
  75. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/route-state-variants.js +781 -0
  76. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/state.js +234 -0
  77. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/types.js +1 -0
  78. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/extract.js +40 -0
  79. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/index.js +6 -0
  80. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/manifest.js +513 -0
  81. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/resources.js +398 -0
  82. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/types.js +1 -0
  83. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/views.js +447 -0
  84. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/xml-parser.js +175 -0
  85. package/skills/a2h-app-req-gen/scripts/reqgen/src/cli.js +398 -0
  86. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/area-graph.js +1 -0
  87. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/artifact.js +1 -0
  88. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/common.js +16 -0
  89. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/diagnostic.js +10 -0
  90. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/gap-audit.js +14 -0
  91. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/index.js +11 -0
  92. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/name-traceability.js +26 -0
  93. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/raw-fact.js +40 -0
  94. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/req-model.js +1 -0
  95. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/run.js +19 -0
  96. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/semantic-completeness.js +11 -0
  97. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/source-facts.js +1 -0
  98. package/skills/a2h-app-req-gen/scripts/reqgen/src/core/output-guard.js +41 -0
  99. package/skills/a2h-app-req-gen/scripts/reqgen/src/core/stable-id.js +92 -0
  100. package/skills/a2h-app-req-gen/scripts/reqgen/src/dashboard-cli.js +169 -0
  101. package/skills/a2h-app-req-gen/scripts/reqgen/src/diagnostics.js +104 -0
  102. package/skills/a2h-app-req-gen/scripts/reqgen/src/dump/dump.js +1028 -0
  103. package/skills/a2h-app-req-gen/scripts/reqgen/src/dump/index.js +1 -0
  104. package/skills/a2h-app-req-gen/scripts/reqgen/src/dump-cli.js +251 -0
  105. package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/artifacts.js +49 -0
  106. package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/index.js +18 -0
  107. package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/quality-report.js +125 -0
  108. package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/render.js +353 -0
  109. package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/types.js +2 -0
  110. package/skills/a2h-app-req-gen/scripts/reqgen/src/graph/paths.js +474 -0
  111. package/skills/a2h-app-req-gen/scripts/reqgen/src/jvm/index.js +15 -0
  112. package/skills/a2h-app-req-gen/scripts/reqgen/src/jvm/protocol.js +408 -0
  113. package/skills/a2h-app-req-gen/scripts/reqgen/src/jvm/runner.js +452 -0
  114. package/skills/a2h-app-req-gen/scripts/reqgen/src/pipeline/index.js +8 -0
  115. package/skills/a2h-app-req-gen/scripts/reqgen/src/pipeline/output.js +165 -0
  116. package/skills/a2h-app-req-gen/scripts/reqgen/src/pipeline/run-stage1.js +891 -0
  117. package/skills/a2h-app-req-gen/scripts/reqgen/src/project/discovery.js +299 -0
  118. package/skills/a2h-app-req-gen/scripts/reqgen/src/project/gradle-model.js +358 -0
  119. package/skills/a2h-app-req-gen/scripts/reqgen/src/project/gradle-runner.js +165 -0
  120. package/skills/a2h-app-req-gen/scripts/reqgen/src/project/resource-profile.js +60 -0
  121. package/skills/a2h-app-req-gen/scripts/reqgen/src/project/types.js +1 -0
  122. package/skills/a2h-app-req-gen/scripts/reqgen/src/spec-handoff-cli.js +151 -0
  123. package/skills/a2h-app-req-gen/scripts/reqgen/src/spec-handoff.js +1004 -0
  124. package/skills/a2h-app-req-gen/scripts/reqgen/src/stage2-cli.js +109 -0
  125. package/skills/a2h-app-req-gen/scripts/reqgen/src/stage2-input.js +419 -0
  126. package/skills/a2h-app-req-gen/scripts/reqgen/src/stage2-name-trace-input.js +95 -0
  127. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/area-graph.js +444 -0
  128. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/index.js +7 -0
  129. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/req-model.js +580 -0
  130. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/req-text.js +195 -0
  131. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/semantic-area-graph.js +1431 -0
  132. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/types.js +1 -0
  133. package/skills/a2h-app-req-gen/scripts/reqgen-audit.mjs +298 -0
  134. package/skills/a2h-app-req-gen/scripts/reqgen-selfcheck.mjs +555 -0
  135. package/skills/a2h-code-review/SKILL.md +379 -0
  136. package/skills/{hmos-integration-test → a2h-integration-test}/README.md +6 -6
  137. package/skills/{hmos-integration-test → a2h-integration-test}/SKILL.md +6 -6
  138. package/skills/{hmos-integration-test → a2h-integration-test}/scripts/report-tool.mjs +1 -1
  139. package/skills/a2h-spec-design/SKILL.md +330 -0
  140. package/skills/a2h-spec-design/scripts/inspect_spec_design_inputs.mjs +207 -0
  141. package/skills/a2h-spec-design/scripts/move_spec_technical_reference.mjs +198 -0
  142. package/skills/a2h-spec-design/scripts/validate_spec_design_output.mjs +256 -0
  143. package/skills/a2h-spec-generate/SKILL.md +696 -0
  144. package/skills/a2h-spec-generate/references/trace-template.md +115 -0
  145. package/skills/a2h-spec-generate/scripts/parse_requirements.mjs +537 -0
  146. package/skills/a2h-spec-generate/template/REQ.txt +22 -0
  147. package/skills/a2h-spec-generate/template/REQ.xlsx +0 -0
  148. package/skills/a2h-spec-implement/SKILL.md +548 -0
  149. package/skills/a2h-spec-implement/references/harmony-pitfalls.md +186 -0
  150. package/skills/a2h-spec-implement/references/l1-unit-test.md +185 -0
  151. package/skills/a2h-spec-implement/references/l3-e2e-uitest.md +167 -0
  152. package/skills/a2h-spec-implement/references/test-core.md +132 -0
  153. package/skills/a2h-spec-implement/rules/arkts/arkts-standards.md +184 -0
  154. package/skills/a2h-spec-implement/rules/arkts/conventions/coding-style.md +96 -0
  155. package/skills/a2h-spec-implement/rules/arkts/conventions/security.md +126 -0
  156. package/skills/a2h-spec-implement/rules/arkts/language/arkts-rules.md +650 -0
  157. package/skills/a2h-spec-implement/rules/arkts/language/arkui-structure-rules.md +173 -0
  158. package/skills/a2h-spec-implement/rules/arkts/language/ts-to-arkts-rewrites.md +58 -0
  159. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -0
  160. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2089 -0
  161. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -0
  162. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -0
  163. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -0
  164. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -0
  165. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -0
  166. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +903 -0
  167. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
  168. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -0
  169. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md +911 -0
  170. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +355 -0
  171. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -0
  172. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md +216 -0
  173. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Computed/350/243/205/351/245/260/345/231/250/357/274/232/350/256/241/347/256/227/345/261/236/346/200/247.md +442 -0
  174. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Event/350/243/205/351/245/260/345/231/250/357/274/232/350/247/204/350/214/203/347/273/204/344/273/266/350/276/223/345/207/272.md +169 -0
  175. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Local/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/351/203/250/347/212/266/346/200/201.md +763 -0
  176. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Monitor/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/344/277/256/346/224/271/345/274/202/346/255/245/347/233/221/345/220/254.md +2088 -0
  177. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@ObservedV2/350/243/205/351/245/260/345/231/250/345/222/214@Trace/350/243/205/351/245/260/345/231/250/357/274/232/347/261/273/345/261/236/346/200/247/345/217/230/345/214/226/350/247/202/346/265/213.md +1258 -0
  178. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Once/357/274/232/345/210/235/345/247/213/345/214/226/345/220/214/346/255/245/344/270/200/346/254/241.md +175 -0
  179. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Param/357/274/232/347/273/204/344/273/266/345/244/226/351/203/250/350/276/223/345/205/245.md +850 -0
  180. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Provider/350/243/205/351/245/260/345/231/250/345/222/214@Consumer/350/243/205/351/245/260/345/231/250/357/274/232/350/267/250/347/273/204/344/273/266/345/261/202/347/272/247/345/217/214/345/220/221/345/220/214/346/255/245.md +862 -0
  181. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Type/350/243/205/351/245/260/345/231/250/357/274/232/346/240/207/350/256/260/347/261/273/345/261/236/346/200/247/347/232/204/347/261/273/345/236/213.md +110 -0
  182. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/AppStorageV2/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200UI/347/212/266/346/200/201/345/255/230/345/202/250.md +301 -0
  183. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md +1411 -0
  184. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/PersistenceV2/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +1392 -0
  185. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/getTarget/346/216/245/345/217/243/357/274/232/350/216/267/345/217/226/347/212/266/346/200/201/347/256/241/347/220/206/346/241/206/346/236/266/344/273/243/347/220/206/345/211/215/347/232/204/345/216/237/345/247/213/345/257/271/350/261/241.md +288 -0
  186. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/makeObserved/346/216/245/345/217/243/357/274/232/345/260/206/351/235/236/350/247/202/345/257/237/346/225/260/346/215/256/345/217/230/344/270/272/345/217/257/350/247/202/345/257/237/346/225/260/346/215/256.md +768 -0
  187. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2//347/212/266/346/200/201/347/256/241/347/220/206V1/345/222/214V2/346/267/267/347/224/250/346/214/207/345/257/274/357/274/210API version 19/345/217/212/344/271/213/345/220/216/357/274/211.md" +829 -0
  188. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2//347/212/266/346/200/201/347/256/241/347/220/206/346/246/202/350/277/260.md +184 -0
  189. package/skills/a2h-spec-implement/rules/arkts/ui/component-cookbook.md +431 -0
  190. package/skills/a2h-spec-implement/rules/arkts/ui/state-management.md +152 -0
  191. package/skills/a2h-spec-implement/rules/arkts/ui/ui-quality.md +67 -0
  192. package/skills/a2h-spec-implement/rules/arkts/ui/ui-runtime-diagnosis.md +32 -0
  193. package/skills/a2h-spec-implement/rules/pipeline/build/package-set-collection.md +43 -0
  194. package/skills/a2h-spec-implement/scripts/_lib/common.mjs +97 -0
  195. package/skills/a2h-spec-implement/scripts/_lib/config.mjs +70 -0
  196. package/skills/a2h-spec-implement/scripts/_lib/lessons_cli.mjs +48 -0
  197. package/skills/a2h-spec-implement/scripts/build_lessons.mjs +24 -0
  198. package/skills/a2h-spec-implement/scripts/code_pattern_lessons.mjs +26 -0
  199. package/skills/a2h-spec-implement/scripts/device_ui.mjs +433 -0
  200. package/skills/a2h-spec-implement/scripts/device_ui_core.mjs +895 -0
  201. package/skills/a2h-spec-implement/scripts/ledger/aggregate_run.mjs +207 -0
  202. package/skills/a2h-spec-implement/scripts/manifest/emit_summary_line.mjs +37 -0
  203. package/skills/a2h-spec-implement/scripts/manifest/finalize_run.mjs +115 -0
  204. package/skills/a2h-spec-implement/scripts/manifest/render_final_summary.mjs +78 -0
  205. package/skills/a2h-spec-implement/scripts/manifest/root_mirror.mjs +137 -0
  206. package/skills/a2h-spec-implement/scripts/package.json +12 -0
  207. package/skills/a2h-spec-implement/scripts/state_propagation_check.mjs +676 -0
  208. package/skills/a2h-spec-implement/scripts/test/l2_run.mjs +244 -0
  209. package/skills/a2h-spec-implement/scripts/test/tp_coverage.mjs +231 -0
  210. package/skills/a2h-spec-implement/scripts/ui_memory.mjs +161 -0
  211. package/skills/a2h-spec-implement/scripts/utils/ensure_app.mjs +134 -0
  212. package/skills/a2h-ui-transfer/SKILL.md +20 -9
  213. package/skills/a2h-ui-transfer/scripts/arkts_static_check.js +80 -0
  214. package/skills/hmos-convert-pipeline/SKILL.md +7 -5
  215. package/skills/hmos-incremental-ui-align/README.md +7 -7
  216. package/skills/hmos-incremental-ui-align/SKILL.md +1 -1
  217. package/skills/hmos-incremental-ui-align/page_align.md +1 -1
  218. package/skills/hmos-spec-generate/SKILL.md +2 -2
  219. package/skills/hmos-spec-generate/scripts/parse_requirements.mjs +537 -0
  220. package/skills/hmos-fix-build-errors/SKILL.md +0 -266
  221. package/skills/hmos-fix-build-errors/references/arkts-strict-patterns.md +0 -219
  222. package/skills/hmos-fix-build-errors/references/known-patterns.md +0 -157
  223. package/skills/hmos-fix-build-errors/references/rdb-entity-pattern.md +0 -131
  224. package/skills/hmos-spec-generate/scripts/parse_requirements.ts +0 -515
  225. /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/resolve-metadata-tool.mjs +0 -0
  226. /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/self-test-runner.mjs +0 -0
  227. /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/testcases-tool.mjs +0 -0
@@ -0,0 +1,537 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 需求表/需求文本解析工具 —— 把一个需求文件拆成「多条独立需求」,供 hmos-spec-generate
4
+ * skill 逐条生成 spec。一个输入文件包含多条需求,一条需求 = 一份 spec。
5
+ *
6
+ * 支持两种输入类型,拆分后结构完全一致:
7
+ * - Excel 类型 (.xlsx / .xlsm / .csv):一行 = 一条需求
8
+ * 第 1 列 = UI 路径(如「设置-歌词-悬浮窗状态栏歌词」)
9
+ * 第 2 列 = 功能描述(可含换行与缩进的多行文本)
10
+ * 第 3 列及之后的列会按行追加到功能描述末尾,不会被丢弃
11
+ * - 文本类型 (.txt):空行分隔的一段 = 一条需求
12
+ * 段落首行 = UI 路径,其余行 = 功能描述
13
+ * 参见 template/REQ.xlsx 与 template/REQ.txt,两者内容等价。
14
+ *
15
+ * 零第三方依赖:xlsx 本质是 ZIP + XML,这里用 node:zlib 手工解压 + 正则解析。
16
+ * 运行环境:Node ≥ 22(纯 JS,无需类型剥离,可直接 `node parse_requirements.mjs`)。
17
+ *
18
+ * 用法:
19
+ * node parse_requirements.mjs --input REQ.xlsx --json # 逐条需求(推荐,供 skill 迭代)
20
+ * node parse_requirements.mjs --input REQ.xlsx # 归一化文本 → stdout
21
+ * node parse_requirements.mjs --input REQ.xlsx --out a.txt # 归一化文本 → 文件
22
+ * node parse_requirements.mjs --input REQ.xlsx --split-dir ./out # 每条需求一个 .txt
23
+ * node parse_requirements.mjs --input REQ.xlsx --sheet 需求 # 指定工作表(名称或 1-based 序号)
24
+ * node parse_requirements.mjs --input REQ.xlsx --list-sheets
25
+ * node parse_requirements.mjs --input legacy.txt --single # 整个文件当作单条需求
26
+ */
27
+ import * as fs from "node:fs";
28
+ import * as path from "node:path";
29
+ import * as zlib from "node:zlib";
30
+ // ---------------------------------------------------------------- ZIP 容器
31
+ function findEndOfCentralDirectory(buf) {
32
+ const start = Math.max(0, buf.length - (22 + 0xffff));
33
+ for (let i = buf.length - 22; i >= start; i--) {
34
+ if (buf.readUInt32LE(i) === 0x06054b50) return i;
35
+ }
36
+ return -1;
37
+ }
38
+ /** 读出 zip 中所有条目(名称 → 解压后的内容)。仅支持 stored / deflate,够覆盖 xlsx。 */
39
+ function readZipEntries(buf) {
40
+ const eocd = findEndOfCentralDirectory(buf);
41
+ if (eocd < 0)
42
+ throw new Error("不是合法的 xlsx/zip 文件:找不到 ZIP 中央目录结束记录");
43
+ let entryCount = buf.readUInt16LE(eocd + 10);
44
+ let cdOffset = buf.readUInt32LE(eocd + 16);
45
+ // ZIP64:条目数或中央目录偏移溢出时改读 ZIP64 结束记录
46
+ if (entryCount === 0xffff || cdOffset === 0xffffffff) {
47
+ const locator = eocd - 20;
48
+ if (locator >= 0 && buf.readUInt32LE(locator) === 0x07064b50) {
49
+ const z64 = Number(buf.readBigUInt64LE(locator + 8));
50
+ if (
51
+ z64 >= 0 &&
52
+ z64 + 56 <= buf.length &&
53
+ buf.readUInt32LE(z64) === 0x06064b50
54
+ ) {
55
+ entryCount = Number(buf.readBigUInt64LE(z64 + 32));
56
+ cdOffset = Number(buf.readBigUInt64LE(z64 + 48));
57
+ }
58
+ }
59
+ }
60
+ const files = new Map();
61
+ let p = cdOffset;
62
+ for (let i = 0; i < entryCount; i++) {
63
+ if (p + 46 > buf.length || buf.readUInt32LE(p) !== 0x02014b50) break;
64
+ const method = buf.readUInt16LE(p + 10);
65
+ const compressedSize = buf.readUInt32LE(p + 20);
66
+ const nameLen = buf.readUInt16LE(p + 28);
67
+ const extraLen = buf.readUInt16LE(p + 30);
68
+ const commentLen = buf.readUInt16LE(p + 32);
69
+ const localOffset = buf.readUInt32LE(p + 42);
70
+ const name = buf.toString("utf8", p + 46, p + 46 + nameLen);
71
+ p += 46 + nameLen + extraLen + commentLen;
72
+ if (
73
+ localOffset + 30 > buf.length ||
74
+ buf.readUInt32LE(localOffset) !== 0x04034b50
75
+ )
76
+ continue;
77
+ const lNameLen = buf.readUInt16LE(localOffset + 26);
78
+ const lExtraLen = buf.readUInt16LE(localOffset + 28);
79
+ const dataStart = localOffset + 30 + lNameLen + lExtraLen;
80
+ const raw = buf.subarray(dataStart, dataStart + compressedSize);
81
+ if (method === 0) files.set(name, Buffer.from(raw));
82
+ else if (method === 8) files.set(name, zlib.inflateRawSync(raw));
83
+ else
84
+ throw new Error(`xlsx 内部条目 ${name} 使用了不支持的压缩方式 ${method}`);
85
+ }
86
+ return files;
87
+ }
88
+ // ---------------------------------------------------------------- XML 解析
89
+ function decodeXmlText(s) {
90
+ return s
91
+ .replace(/_x000D_/g, "")
92
+ .replace(/&#x([0-9a-fA-F]+);/g, (_, h) =>
93
+ String.fromCodePoint(parseInt(h, 16)),
94
+ )
95
+ .replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)))
96
+ .replace(/&lt;/g, "<")
97
+ .replace(/&gt;/g, ">")
98
+ .replace(/&quot;/g, '"')
99
+ .replace(/&apos;/g, "'")
100
+ .replace(/&amp;/g, "&");
101
+ }
102
+ function attr(tag, name) {
103
+ const m = new RegExp(
104
+ `\\b${name.replace(/:/g, "\\:")}\\s*=\\s*"([^"]*)"`,
105
+ ).exec(tag);
106
+ return m ? decodeXmlText(m[1]) : undefined;
107
+ }
108
+ /** 取出片段内所有 <t> 文本并拼接(富文本多个 run 会被合并;忽略日文注音 <rPh>)。 */
109
+ function collectTextNodes(xml) {
110
+ const cleaned = xml.replace(/<rPh[\s\S]*?<\/rPh>/g, "");
111
+ let text = "";
112
+ const tRe = /<t\b[^>]*\/>|<t\b[^>]*>([\s\S]*?)<\/t>/g;
113
+ let m;
114
+ while ((m = tRe.exec(cleaned)) !== null) text += decodeXmlText(m[1] ?? "");
115
+ return text;
116
+ }
117
+ function parseSharedStrings(xml) {
118
+ const out = [];
119
+ const siRe = /<si\b[^>]*\/>|<si\b[^>]*>([\s\S]*?)<\/si>/g;
120
+ let m;
121
+ while ((m = siRe.exec(xml)) !== null) out.push(collectTextNodes(m[1] ?? ""));
122
+ return out;
123
+ }
124
+ /** "AB12" → 27(0-based 列号) */
125
+ function columnIndexFromRef(ref) {
126
+ const letters = /^([A-Z]+)/.exec(ref.toUpperCase());
127
+ if (!letters) return -1;
128
+ let n = 0;
129
+ for (const ch of letters[1]) n = n * 26 + (ch.charCodeAt(0) - 64);
130
+ return n - 1;
131
+ }
132
+ function parseSheet(xml, shared) {
133
+ const rows = [];
134
+ const rowRe = /<row\b[^>]*\/>|<row\b[^>]*>([\s\S]*?)<\/row>/g;
135
+ let rm;
136
+ while ((rm = rowRe.exec(xml)) !== null) {
137
+ const inner = rm[1] ?? "";
138
+ const cells = [];
139
+ let auto = 0;
140
+ const cellRe = /<c\b([^>]*)\/>|<c\b([^>]*)>([\s\S]*?)<\/c>/g;
141
+ let cm;
142
+ while ((cm = cellRe.exec(inner)) !== null) {
143
+ const attrs = cm[1] ?? cm[2] ?? "";
144
+ const body = cm[3] ?? "";
145
+ const ref = attr(attrs, "r");
146
+ const col = ref ? columnIndexFromRef(ref) : -1;
147
+ const index = col >= 0 ? col : auto;
148
+ auto = index + 1;
149
+ const type = attr(attrs, "t") ?? "n";
150
+ let value = "";
151
+ if (type === "inlineStr") {
152
+ value = collectTextNodes(body);
153
+ } else {
154
+ const v = /<v\b[^>]*>([\s\S]*?)<\/v>/.exec(body);
155
+ const rawValue = v ? decodeXmlText(v[1]) : "";
156
+ if (type === "s") {
157
+ const i = Number(rawValue);
158
+ value =
159
+ Number.isInteger(i) && i >= 0 && i < shared.length ? shared[i] : "";
160
+ } else if (type === "b") {
161
+ value =
162
+ rawValue === "1" ? "TRUE" : rawValue === "0" ? "FALSE" : rawValue;
163
+ } else {
164
+ value = rawValue;
165
+ }
166
+ }
167
+ while (cells.length < index) cells.push("");
168
+ cells[index] = value;
169
+ }
170
+ rows.push(cells);
171
+ }
172
+ return rows;
173
+ }
174
+ function resolveSheets(zip) {
175
+ const workbook = zip.get("xl/workbook.xml");
176
+ if (!workbook) throw new Error("xlsx 缺少 xl/workbook.xml,文件可能已损坏");
177
+ const relsBuf = zip.get("xl/_rels/workbook.xml.rels");
178
+ const rels = new Map();
179
+ if (relsBuf) {
180
+ const relRe =
181
+ /<Relationship\b[^>]*\/>|<Relationship\b[^>]*>[\s\S]*?<\/Relationship>/g;
182
+ let m;
183
+ const relXml = relsBuf.toString("utf8");
184
+ while ((m = relRe.exec(relXml)) !== null) {
185
+ const id = attr(m[0], "Id");
186
+ const target = attr(m[0], "Target");
187
+ if (id && target) rels.set(id, target);
188
+ }
189
+ }
190
+ const sheets = [];
191
+ const sheetRe = /<sheet\b[^>]*\/>|<sheet\b[^>]*>[\s\S]*?<\/sheet>/g;
192
+ let m;
193
+ const wbXml = workbook.toString("utf8");
194
+ let fallbackIndex = 0;
195
+ while ((m = sheetRe.exec(wbXml)) !== null) {
196
+ fallbackIndex++;
197
+ const name = attr(m[0], "name") ?? `Sheet${fallbackIndex}`;
198
+ const rid = attr(m[0], "r:id") ?? attr(m[0], "relationshipId");
199
+ let target = rid ? rels.get(rid) : undefined;
200
+ if (!target) target = `worksheets/sheet${fallbackIndex}.xml`;
201
+ const entry = target.startsWith("/")
202
+ ? target.slice(1)
203
+ : `xl/${target.replace(/^\.\//, "")}`;
204
+ sheets.push({ name, entry });
205
+ }
206
+ if (sheets.length === 0) throw new Error("xlsx 中没有找到任何工作表");
207
+ return sheets;
208
+ }
209
+ function readXlsx(file, sheetSelector) {
210
+ const zip = readZipEntries(fs.readFileSync(file));
211
+ const sheets = resolveSheets(zip);
212
+ let picked;
213
+ if (sheetSelector) {
214
+ const asIndex = Number(sheetSelector);
215
+ if (Number.isInteger(asIndex) && asIndex >= 1 && asIndex <= sheets.length)
216
+ picked = sheets[asIndex - 1];
217
+ else picked = sheets.find((s) => s.name === sheetSelector);
218
+ if (!picked) {
219
+ throw new Error(
220
+ `找不到工作表 "${sheetSelector}"。可用工作表:${sheets.map((s, i) => `${i + 1}. ${s.name}`).join(",")}`,
221
+ );
222
+ }
223
+ } else {
224
+ picked = sheets[0];
225
+ }
226
+ const sheetBuf = zip.get(picked.entry);
227
+ if (!sheetBuf) throw new Error(`xlsx 内部缺少工作表数据 ${picked.entry}`);
228
+ const sharedBuf = zip.get("xl/sharedStrings.xml");
229
+ const shared = sharedBuf
230
+ ? parseSharedStrings(sharedBuf.toString("utf8"))
231
+ : [];
232
+ return {
233
+ rows: parseSheet(sheetBuf.toString("utf8"), shared),
234
+ sheets,
235
+ picked,
236
+ };
237
+ }
238
+ // ---------------------------------------------------------------- CSV
239
+ function parseCsv(text) {
240
+ const body = text.replace(/^/, "");
241
+ const firstLine = body.split(/\r?\n/, 1)[0] ?? "";
242
+ const delimiter = firstLine.includes("\t")
243
+ ? "\t"
244
+ : firstLine.includes(";") && !firstLine.includes(",")
245
+ ? ";"
246
+ : ",";
247
+ const rows = [];
248
+ let row = [];
249
+ let cell = "";
250
+ let quoted = false;
251
+ for (let i = 0; i < body.length; i++) {
252
+ const ch = body[i];
253
+ if (quoted) {
254
+ if (ch === '"') {
255
+ if (body[i + 1] === '"') {
256
+ cell += '"';
257
+ i++;
258
+ } else quoted = false;
259
+ } else cell += ch;
260
+ continue;
261
+ }
262
+ if (ch === '"') {
263
+ quoted = true;
264
+ continue;
265
+ }
266
+ if (ch === delimiter) {
267
+ row.push(cell);
268
+ cell = "";
269
+ continue;
270
+ }
271
+ if (ch === "\r") continue;
272
+ if (ch === "\n") {
273
+ row.push(cell);
274
+ rows.push(row);
275
+ row = [];
276
+ cell = "";
277
+ continue;
278
+ }
279
+ cell += ch;
280
+ }
281
+ if (cell !== "" || row.length > 0) {
282
+ row.push(cell);
283
+ rows.push(row);
284
+ }
285
+ return rows;
286
+ }
287
+ function isBlankRow(row) {
288
+ return row.every((c) => (c ?? "").trim() === "");
289
+ }
290
+ function looksLikeHeader(row) {
291
+ const first = (row[0] ?? "").trim();
292
+ const second = (row[1] ?? "").trim();
293
+ if (!first) return false;
294
+ if (
295
+ /^(ui\s*路径|ui\s*path|路径|界面路径|菜单路径|设置路径|功能路径)$/i.test(
296
+ first,
297
+ )
298
+ )
299
+ return true;
300
+ return (
301
+ /(路径|path)/i.test(first) && /(描述|说明|功能|需求|desc)/i.test(second)
302
+ );
303
+ }
304
+ /** 表格:一行 = 一条需求。去掉末尾空白行/空白列后逐行映射。 */
305
+ function rowsToRawItems(rows, header) {
306
+ const data = rows.filter((r) => !isBlankRow(r));
307
+ if (data.length === 0) return [];
308
+ // auto 模式下,若跳过表头会把唯一一行数据也吃掉,则宁可不跳——单行需求表比表头更常见
309
+ const autoSkip =
310
+ header === "auto" && looksLikeHeader(data[0]) && data.length > 1;
311
+ const skipFirst = header === "yes" || autoSkip;
312
+ const body = skipFirst ? data.slice(1) : data;
313
+ return body
314
+ .map((row) => {
315
+ const cells = [...row];
316
+ while (cells.length > 0 && (cells[cells.length - 1] ?? "").trim() === "")
317
+ cells.pop();
318
+ const uiPath = (cells[0] ?? "").trim();
319
+ const description = cells
320
+ .slice(1)
321
+ .map((c) => (c ?? "").replace(/\s+$/, ""))
322
+ .filter((c) => c.trim() !== "")
323
+ .join("\n")
324
+ .trim();
325
+ return { uiPath, description };
326
+ })
327
+ .filter((item) => item.uiPath !== "" || item.description !== "");
328
+ }
329
+ /** 文本:空行分隔的一段 = 一条需求,段首行 = UI 路径,其余行 = 功能描述。 */
330
+ function textToRawItems(text, single) {
331
+ const body = text.replace(/^/, "").replace(/\r\n?/g, "\n");
332
+ const blocks = single ? [body] : body.split(/\n[ \t]*\n+/);
333
+ return blocks
334
+ .map((b) => b.replace(/\s+$/, ""))
335
+ .filter((b) => b.trim() !== "")
336
+ .map((block) => {
337
+ const lines = block.split("\n");
338
+ return {
339
+ uiPath: (lines[0] ?? "").trim(),
340
+ description: lines.slice(1).join("\n").replace(/\s+$/, ""),
341
+ };
342
+ });
343
+ }
344
+ function sanitizeFileName(name) {
345
+ const cleaned = name
346
+ .replace(/[\\/:*?"<>|]+/g, "_")
347
+ .replace(/\s+/g, "_")
348
+ .replace(/^_+|_+$/g, "");
349
+ return (cleaned || "requirement").slice(0, 60);
350
+ }
351
+ /**
352
+ * 给每条需求编号并生成确定性的输出名:`<前缀>-<序号>-<UI路径>`。
353
+ * 只有一条需求时退化为 `<前缀>`,保持"一个文件一条需求"的旧命名不变。
354
+ */
355
+ function withNames(items, prefix) {
356
+ const width = String(items.length).length;
357
+ return items.map((item, i) => {
358
+ const index = i + 1;
359
+ const name =
360
+ items.length === 1
361
+ ? sanitizeFileName(prefix)
362
+ : `${sanitizeFileName(prefix)}-${String(index).padStart(width, "0")}-${sanitizeFileName(item.uiPath || `requirement_${index}`)}`;
363
+ return { index, name, uiPath: item.uiPath, description: item.description };
364
+ });
365
+ }
366
+ /** 单条需求归一化为纯文本:UI 路径一行 + 功能描述若干行。 */
367
+ function rawToText(item) {
368
+ return [item.uiPath, item.description].filter((s) => s !== "").join("\n");
369
+ }
370
+ /** 多条需求合并为纯文本:需求之间空一行(与等价的 .txt 逐字节一致)。 */
371
+ function rawListToText(items) {
372
+ return items.map(rawToText).join("\n\n");
373
+ }
374
+ /** --single:把整份表格压成一条需求(UI 路径留空,描述是全表归一化文本)。 */
375
+ function collapseToSingle(items) {
376
+ if (items.length <= 1) return items;
377
+ return [{ uiPath: "", description: rawListToText(items) }];
378
+ }
379
+ /** 整个文件归一化为纯文本,结尾补一个换行。 */
380
+ function toNormalizedText(items) {
381
+ return items.map(rawToText).join("\n\n") + "\n";
382
+ }
383
+ // ---------------------------------------------------------------- CLI
384
+ function parseArgs(argv) {
385
+ const opts = {
386
+ input: "",
387
+ json: false,
388
+ listSheets: false,
389
+ header: "auto",
390
+ single: false,
391
+ };
392
+ for (let i = 0; i < argv.length; i++) {
393
+ const a = argv[i];
394
+ const next = () => {
395
+ const v = argv[++i];
396
+ if (v === undefined) throw new Error(`参数 ${a} 缺少取值`);
397
+ return v;
398
+ };
399
+ switch (a) {
400
+ case "--input":
401
+ case "-i":
402
+ opts.input = next();
403
+ break;
404
+ case "--out":
405
+ case "-o":
406
+ opts.out = next();
407
+ break;
408
+ case "--json":
409
+ opts.json = true;
410
+ break;
411
+ case "--split-dir":
412
+ opts.splitDir = next();
413
+ break;
414
+ case "--sheet":
415
+ opts.sheet = next();
416
+ break;
417
+ case "--list-sheets":
418
+ opts.listSheets = true;
419
+ break;
420
+ case "--single":
421
+ opts.single = true;
422
+ break;
423
+ case "--prefix":
424
+ opts.prefix = next();
425
+ break;
426
+ case "--header": {
427
+ const v = next();
428
+ if (v !== "auto" && v !== "yes" && v !== "no")
429
+ throw new Error("--header 只能是 auto | yes | no");
430
+ opts.header = v;
431
+ break;
432
+ }
433
+ case "--help":
434
+ case "-h":
435
+ console.log(
436
+ [
437
+ "用法: node parse_requirements.mjs --input <REQ.xlsx|REQ.csv|REQ.txt> [选项]",
438
+ "",
439
+ "把一个需求文件拆成多条独立需求(一条需求 = 一份 spec)。",
440
+ "",
441
+ " --input, -i <file> 需求文件(.xlsx / .xlsm / .csv / .txt)",
442
+ " --json 输出 [{index, name, uiPath, description}](推荐)",
443
+ " --out, -o <file> 归一化文本写入文件(默认写 stdout)",
444
+ " --split-dir <dir> 每条需求单独写一个 <name>.txt 到该目录",
445
+ " --prefix <stem> 覆盖 name 前缀(默认取输入文件名)",
446
+ " --sheet <name|index> 指定工作表,名称或 1-based 序号(默认第一个)",
447
+ " --list-sheets 只打印工作表列表",
448
+ " --header auto|yes|no 表格是否跳过表头行(默认 auto,自动识别)",
449
+ " --single 整个文件只算一条需求(不按空行/行拆分)",
450
+ ].join("\n"),
451
+ );
452
+ process.exit(0);
453
+ // eslint-disable-next-line no-fallthrough
454
+ default:
455
+ if (!opts.input && !a.startsWith("-")) opts.input = a;
456
+ else throw new Error(`无法识别的参数:${a}`);
457
+ }
458
+ }
459
+ if (!opts.input) throw new Error("缺少必需参数 --input <需求文件>");
460
+ return opts;
461
+ }
462
+ function main() {
463
+ const opts = parseArgs(process.argv.slice(2));
464
+ const input = path.resolve(opts.input);
465
+ if (!fs.existsSync(input)) throw new Error(`输入文件不存在:${input}`);
466
+ const ext = path.extname(input).toLowerCase();
467
+ if (ext === ".xls") {
468
+ throw new Error(
469
+ "不支持旧版二进制 .xls (BIFF) 格式,请在 Excel/WPS 中另存为 .xlsx 或 .csv 后重试",
470
+ );
471
+ }
472
+ let raw;
473
+ if (ext === ".txt" || ext === ".md") {
474
+ if (opts.listSheets) {
475
+ console.log("1\t(纯文本无工作表)");
476
+ return;
477
+ }
478
+ raw = textToRawItems(fs.readFileSync(input, "utf8"), opts.single);
479
+ } else if (ext === ".csv" || ext === ".tsv") {
480
+ if (opts.listSheets) {
481
+ console.log("1\t(CSV 无工作表)");
482
+ return;
483
+ }
484
+ const rows = rowsToRawItems(
485
+ parseCsv(fs.readFileSync(input, "utf8")),
486
+ opts.header,
487
+ );
488
+ raw = opts.single ? collapseToSingle(rows) : rows;
489
+ } else {
490
+ const parsed = readXlsx(input, opts.sheet);
491
+ if (opts.listSheets) {
492
+ console.log(
493
+ parsed.sheets.map((s, i) => `${i + 1}\t${s.name}`).join("\n"),
494
+ );
495
+ return;
496
+ }
497
+ const rows = rowsToRawItems(parsed.rows, opts.header);
498
+ raw = opts.single ? collapseToSingle(rows) : rows;
499
+ }
500
+ const prefix = opts.prefix ?? path.basename(input, path.extname(input));
501
+ const items = withNames(raw, prefix);
502
+ if (items.length === 0)
503
+ throw new Error(`需求文件 ${input} 中没有解析到任何需求`);
504
+ if (opts.splitDir) {
505
+ const dir = path.resolve(opts.splitDir);
506
+ fs.mkdirSync(dir, { recursive: true });
507
+ const written = [];
508
+ for (const item of items) {
509
+ const file = path.join(dir, `${item.name}.txt`);
510
+ fs.writeFileSync(file, rawToText(item) + "\n", "utf8");
511
+ written.push(file);
512
+ }
513
+ console.log(written.join("\n"));
514
+ return;
515
+ }
516
+ const output = opts.json
517
+ ? JSON.stringify(items, null, 2) + "\n"
518
+ : toNormalizedText(items);
519
+ if (opts.out) {
520
+ const file = path.resolve(opts.out);
521
+ fs.mkdirSync(path.dirname(file), { recursive: true });
522
+ fs.writeFileSync(file, output, "utf8");
523
+ console.error(
524
+ `[parse_requirements] 已解析 ${items.length} 条需求 → ${file}`,
525
+ );
526
+ } else {
527
+ process.stdout.write(output);
528
+ }
529
+ }
530
+ try {
531
+ main();
532
+ } catch (err) {
533
+ console.error(
534
+ `[parse_requirements] 错误:${err instanceof Error ? err.message : String(err)}`,
535
+ );
536
+ process.exit(1);
537
+ }