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,115 @@
1
+ # Trace file template
2
+
3
+ The trace is the machine-readable contract between code exploration (2.3) and spec synthesis (2.5). It is written to `<spec_output_dir>/.trace/<feature>.md`, using the same `<feature>` stem as the spec, and it is the **only** input 2.5 may synthesize from.
4
+
5
+ The trace is an **evidence** document, not a deliverable. It keeps Android symbol names, package names, resource ids, `file:line` anchors, and original English strings verbatim — `## 平台中立化规则` and `## No-Vagueness Rules` govern the spec, never this file. De-platforming happens at 2.5.
6
+
7
+ Every H2 section below is mandatory. A section with nothing to record is written with an explicit `None`; a missing section fails the 2.4 review.
8
+
9
+ ---
10
+
11
+ ## Field schema
12
+
13
+ | Field | Meaning |
14
+ |---|---|
15
+ | `claim` | One sentence, the user-observable behavior this entry carries |
16
+ | `layers` | Three fixed sub-fields — `code`, `resource`, `manifest`. Each is a `file:line` hit or `N/A: <reason>`. Omission = not checked, never = not present |
17
+ | `interaction` | State writes, persistence keys (verbatim), in-memory mutations |
18
+ | `data_flow` | trigger → state/coordination layer → repository/service → data source, **each hop carrying `file:line`** |
19
+
20
+ `code` MUST anchor the **listener / callback registration line**, not only the handler body. Handler-only anchoring fails review.
21
+
22
+ Every `file:line` is project-root-relative and resolves to exactly one file (see the anchor write-time resolution gate in 2.3).
23
+
24
+ ---
25
+
26
+ ## Template
27
+
28
+ ```markdown
29
+ # Code trace · <feature>
30
+
31
+ ## Status
32
+ status: ok | no_recall | error
33
+ project-path: <android_project_dir>
34
+ backend: homegraph-cli
35
+ req-title: <原始需求标题行,逐字照抄>
36
+ ui-sequence: <该需求的 UI 路径,与 spec 的 `## UI序列` 一字不差>
37
+ terse-req: true | false
38
+ candidate-hosts: [<仅在召回出现 ≥3 个候选宿主时列出>]
39
+ owning-page: <规范化页面归属;同一批次所有 trace 必须一致;无页面归属时用 __shared_capability__>
40
+ page-path: <自应用入口到 owning-page 的页面导航路径,只按页面边界拆分;__shared_capability__ 用 []>
41
+ page-order: pending(Step 4 替换为页内唯一连续正整数,从 1 起)
42
+ page-order-acceptance: pending(Step 4 替换为非空 UI 可见验收动作,需点名桩数据/被替换行为及观察方式)
43
+
44
+ ## Recalled entry points (Path 1 + Path 2 union, deduplicated)
45
+ - Entry 1: <UI 路径 / 页面名> — <file:line> — recalled by: path 1 | path 2 | both
46
+ - ...
47
+
48
+ ## Entry · <name>
49
+ - claim: <一句话描述用户可观察行为>
50
+ - layers:
51
+ - code: <file:line> # 必填;命中或 `N/A: <reason>`
52
+ - resource: <file:line> | N/A: <reason> # res/layout、res/menu、res/values、res/xml …
53
+ - manifest: <file:line> | N/A: <reason> # AndroidManifest.xml / Gradle / BuildConfig
54
+ - interaction: <状态写入、持久化 key(逐字)、内存态变更>
55
+ - data_flow: <trigger → state 层 → repository/service → data source,每跳带 file:line>
56
+
57
+ ## Entry · <name 2> … Entry · <name N>
58
+ (每个入口重复上面的 claim / layers / interaction / data_flow 字段块)
59
+
60
+ ## Implicit triggers (non-UI state changes that activate this feature)
61
+ - Trigger: <切歌 / 权限变化 / 前后台切换 / 网络变化 / 当前歌曲为空 / …> — <file:line> — behavior: <一句话>
62
+ - (无则显式写 None)
63
+
64
+ ## User-visible literals (concrete values the spec must state)
65
+ | 用途 | 值(逐字) | file:line | 备注 |
66
+ |---|---|---|---|
67
+ | toast / dialog / label 文案 | <resource 中的原文> | <res/values/strings.xml:NN> | 有无 values-zh 译文 |
68
+ | 长度 / 数量 / 时长上限 | <100 字符 / 20 条 / 500 毫秒> | <file:line> | |
69
+ | 默认值 / 开关默认态 | <关闭 / 150dp / 14.0dp> | <file:line> | 声明默认 vs 读侧生效默认 |
70
+ | 排序规则与方向 | <按添加时间倒序> | <file:line> | |
71
+ | 位置 / 尺寸 / 颜色 | <…> | <file:line> | 需布局属性坐实 |
72
+ (这一节是 2.5 满足 `## No-Vagueness Rules` 的取值来源;无则写 None)
73
+
74
+ ## Core business entities (data model / persistence key / state machine)
75
+ - Entity X: <file:line + 字段列表 + 语义>
76
+ - 依赖型需求:<依赖的 key 名> 来自 feature `<被依赖 feature>`,见 <file:line>
77
+ - ...
78
+
79
+ ## Cross-entry shared declarations
80
+ - <多个入口共用的 AndroidManifest / Gradle 声明,带 file:line>
81
+ - (无则显式写 None;单入口的 manifest 事实写在该入口的 `manifest` 层字段里)
82
+
83
+ ## Deviations from REQ_DESC
84
+ 1. <需求写 A,但 file:line 处代码行为是 B>
85
+ 2. ...
86
+ (无则显式写 None,以确认这项检查执行过)
87
+
88
+ ## Scope / Boundary — Exhaustive entry enumeration
89
+ ### Touched entries (所有触达该功能/状态的 UI 路径,不限于需求原文提到的)
90
+ (每条沿用与 `## Entry` 相同的 claim / layers / interaction / data_flow 字段块)
91
+
92
+ ### Consumers (谁读取该状态 / 数据)
93
+ - Consumer: <组件名> — <file:line>
94
+
95
+ ### Non-consumers (带证据的边界反例)
96
+ - claim: <例:"桌面小组件不读取该 key">
97
+ closure_layers: [code, resource, manifest]
98
+ tools: [Grep "<pattern>", npx --yes homegraph callers "<symbol>", Read "<path>"]
99
+ zero_hits: <逐字零命中证据:搜索的文件范围、使用的正则、命中数>
100
+
101
+ ## Claim evidence ledger (fabrication-prone classes)
102
+ | claim(与 trace/spec 中措辞一致) | class(guard / retention / position / order / default / string-visibility / global-attribution / displayed-value / quantifier) | file:line | verbatim source quote |
103
+ |---|---|---|---|
104
+ (上述所有小节与 spec 草稿中,落入易伪造类别的每条声明各占一行;引文必须能推出该声明;无则显式写 None)
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Notes
110
+
111
+ - **`ui-sequence`** mirrors the spec's `## UI序列` body exactly — the requirement title copied verbatim. It is not re-derived here.
112
+ - **`terse-req: true`** is set when the requirement body is under ~20 substantive characters or carries only topical keywords. It tells 2.4 to additionally verify that every settings screen discovered during the broad enumeration passes has at least one Entry or Touched-entry.
113
+ - **`status: no_recall`** is written when both recall paths return zero and the user declines to name an entry point. 2.3's remaining sections, 2.4, and 2.5 are then skipped and the requirement is flagged in the Step 3 report. Never fabricate entry points to avoid this state.
114
+ - **Library-source anchors**: facts read from a dependency's sources are replayable evidence, but the `path:line` colon format is reserved for files under the project root. Write library evidence as `«artifactId:version» path/inside/jar.kt line N` — the word `line`, never a colon.
115
+ - **Ownership & order metadata**: `owning-page` and `page-path` are derived in Step 0 and never rewritten; `page-path: []` is the canonical empty spelling for `__shared_capability__`. `page-order` and `page-order-acceptance` start as `pending` and are replaced by Step 4 only after intra-page ordering. `a2h-spec-order` requires all four fields (non-pending) before it can include this trace in the global order. A trace that still carries `pending` means its page batch has not finished Step 4 — except for `no_recall` / `error` traces, which carry no Spec, are excluded from Step 4 and from `a2h-spec-order`, and legitimately keep `pending` after a completed run.
@@ -0,0 +1,537 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 需求表/需求文本解析工具 —— 把一个需求文件拆成「多条独立需求」,供 a2h-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
+ }
@@ -0,0 +1,22 @@
1
+ 设置-歌词-歌词界面-卡拉OK歌词动画兼容策略(播放页歌词设置同步实现)
2
+ 逐字歌词效果,默认是当前行
3
+ 1,当前行:只为前歌曲的歌词中有单字时间戳的歌词行显示逐字特效
4
+ 2,扩展全部:当前歌曲的歌词中只要有一行歌词带单字时间戳,所有歌词行都显示逐字特效
5
+ 3,总是:当前歌曲所有歌词行都显示逐字特效
6
+
7
+ 设置-用户界面-圆形播放封面(圆形支持旋转)
8
+ 开关,默认关闭,打开后,在播放页以圆形展示封面,并且自动旋转
9
+
10
+ 设置-用户界面-允许不规则封面(支持长方形封面,参考无归)
11
+ 开关,默认打开,播放页封面以大小一致的正方形展示,打开后,在播放页封面以原始样式展示
12
+
13
+ 设置-歌词-悬浮窗状态栏歌词
14
+ 开关 ,默认关闭,打开前需要申请悬浮窗权限,打开后在悬浮窗滚动展示歌词
15
+ 1,左右位置:默认0%
16
+ 2,上下位置:默认0px
17
+ 3,宽度:默认150dp
18
+ 4,大小:默认14.0dp
19
+ 5,选择颜色:默认蓝色
20
+ 6,歌词文本居左对齐:开关 默认关闭,歌词在设置的宽度内中对齐
21
+ 7,状态栏歌词不显示翻译:开关 默认关闭
22
+ 8,在播放界面隐藏:开关 默认关闭 打开后如果切换到播放页,则不在悬浮窗区域显示歌词