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,1066 @@
1
+ import { createStableId } from "../../core/stable-id.js";
2
+ import { componentIdentifierName } from "../names/policy.js";
3
+ function graphId(namespace, caseId, entityKind, identity) {
4
+ return createStableId({
5
+ namespace,
6
+ caseId,
7
+ entityKind,
8
+ identity: [{ kind: "semantic-key", value: identity }],
9
+ });
10
+ }
11
+ function display(value, fallback) {
12
+ return value.status === "resolved" ? value.value : fallback;
13
+ }
14
+ function uniqueSorted(values) {
15
+ return [...new Set(values)].sort((left, right) =>
16
+ left.localeCompare(right, "en"),
17
+ );
18
+ }
19
+ function resolved(value, supportRefs, confidence = "high") {
20
+ return {
21
+ status: "resolved",
22
+ value,
23
+ confidence,
24
+ supportRefs: uniqueSorted(supportRefs),
25
+ };
26
+ }
27
+ function targetKey(target) {
28
+ return "status" in target
29
+ ? `unresolved:${target.unresolvedRef}`
30
+ : `${target.kind}:${target.ref}`;
31
+ }
32
+ function targetBranchConditions(action, branchKey, branchEdges, graph, source) {
33
+ const rawAlternatives = branchEdges.flatMap((edge) =>
34
+ edge.conditionAlternatives && edge.conditionAlternatives.length > 0
35
+ ? edge.conditionAlternatives
36
+ : [
37
+ {
38
+ conditionRefs: edge.conditions,
39
+ sourceFactRefs: edge.sourceFactRefs,
40
+ },
41
+ ],
42
+ );
43
+ const alternativesByConditions = new Map();
44
+ for (const alternative of rawAlternatives) {
45
+ const conditionRefs = uniqueSorted(alternative.conditionRefs);
46
+ const key = conditionRefs.join("\u0000");
47
+ const existing = alternativesByConditions.get(key);
48
+ alternativesByConditions.set(key, {
49
+ conditionRefs,
50
+ sourceFactRefs: uniqueSorted([
51
+ ...(existing?.sourceFactRefs ?? []),
52
+ ...alternative.sourceFactRefs,
53
+ ]),
54
+ });
55
+ }
56
+ const conditionAlternatives = [...alternativesByConditions.values()]
57
+ .sort((left, right) =>
58
+ left.conditionRefs
59
+ .join("\u0000")
60
+ .localeCompare(right.conditionRefs.join("\u0000"), "en"),
61
+ )
62
+ .map((alternative, alternativeIndex) => {
63
+ const conditions = alternative.conditionRefs.map((conditionRef) =>
64
+ textItem(
65
+ graph.caseId,
66
+ "target-branch-condition",
67
+ `${action.id}:${branchKey}:${alternativeIndex}:${conditionRef}`,
68
+ conditionText(conditionRef, source),
69
+ [conditionRef],
70
+ ),
71
+ );
72
+ return {
73
+ conditions,
74
+ supportRefs: uniqueSorted([
75
+ ...alternative.sourceFactRefs,
76
+ ...conditions.flatMap((condition) => condition.supportRefs),
77
+ ]),
78
+ };
79
+ });
80
+ const conditionsByText = new Map();
81
+ for (const alternative of conditionAlternatives) {
82
+ for (const condition of alternative.conditions) {
83
+ const existing = conditionsByText.get(condition.text);
84
+ conditionsByText.set(
85
+ condition.text,
86
+ existing
87
+ ? {
88
+ ...existing,
89
+ supportRefs: uniqueSorted([
90
+ ...existing.supportRefs,
91
+ ...condition.supportRefs,
92
+ ]),
93
+ }
94
+ : condition,
95
+ );
96
+ }
97
+ }
98
+ return {
99
+ conditions: [...conditionsByText.values()].sort((left, right) =>
100
+ left.id.localeCompare(right.id, "en"),
101
+ ),
102
+ conditionAlternatives,
103
+ supportRefs: uniqueSorted(
104
+ branchEdges.flatMap((edge) => edge.sourceFactRefs),
105
+ ),
106
+ };
107
+ }
108
+ function resolvedReqTarget(action, target, supportRefs, graph) {
109
+ if (target.kind === "area") {
110
+ const area = graph.areas.find((item) => item.id === target.ref);
111
+ if (!area) {
112
+ throw new Error(
113
+ `Req-model target invariant for Action ${action.id}: ` +
114
+ `missing target Area ${target.ref}.`,
115
+ );
116
+ }
117
+ return {
118
+ kind: "area",
119
+ ref: area.id,
120
+ displayName: area.name,
121
+ supportRefs: uniqueSorted(supportRefs),
122
+ };
123
+ }
124
+ const endpoint = graph.externalEndpoints.find(
125
+ (item) => item.id === target.ref,
126
+ );
127
+ if (!endpoint) {
128
+ throw new Error(
129
+ `Req-model target invariant for Action ${action.id}: ` +
130
+ `missing external endpoint ${target.ref}.`,
131
+ );
132
+ }
133
+ return {
134
+ kind: "external",
135
+ ref: endpoint.id,
136
+ displayName: endpoint.name,
137
+ ...(endpoint.presentation ? { presentation: endpoint.presentation } : {}),
138
+ ...(endpoint.intentAction ? { intentAction: endpoint.intentAction } : {}),
139
+ ...(endpoint.mimeType ? { mimeType: endpoint.mimeType } : {}),
140
+ ...(endpoint.uri ? { uri: endpoint.uri } : {}),
141
+ supportRefs: uniqueSorted(supportRefs),
142
+ };
143
+ }
144
+ function textItem(caseId, kind, identity, text, supportRefs) {
145
+ return {
146
+ id: graphId("text", caseId, kind, identity),
147
+ text,
148
+ supportRefs: [...new Set(supportRefs)].sort(),
149
+ };
150
+ }
151
+ function conditionText(ref, source) {
152
+ const fact = source.facts.find(
153
+ (item) => item.id === ref && item.kind === "condition",
154
+ );
155
+ return fact?.kind === "condition"
156
+ ? fact.data.plainLanguage
157
+ : `满足条件 ${ref}`;
158
+ }
159
+ function canonicalPath(area, graph) {
160
+ const path = area.canonicalPathRef
161
+ ? graph.paths.find((item) => item.id === area.canonicalPathRef)
162
+ : undefined;
163
+ if (!path) {
164
+ const unresolvedRef = area.unresolvedRefs[0];
165
+ if (unresolvedRef) {
166
+ return { status: "unresolved", unresolvedRef };
167
+ }
168
+ throw new Error(`Area ${area.id} has no canonical path`);
169
+ }
170
+ if (path.kind === "unreachable") {
171
+ return {
172
+ status: "unreachable",
173
+ reachability: "unreachable",
174
+ pathRef: path.id,
175
+ alternatePathRefs: graph.paths
176
+ .filter(
177
+ (item) => item.kind === "alternate" && item.targetAreaRef === area.id,
178
+ )
179
+ .map((item) => item.id)
180
+ .sort(),
181
+ };
182
+ }
183
+ const entry = path.rootEntryRef
184
+ ? graph.entryPoints.find((item) => item.id === path.rootEntryRef)
185
+ : undefined;
186
+ const rootRef =
187
+ path.steps[0]?.sourceAreaRef ??
188
+ (entry?.rootAreaRefs.includes(area.id) === true ? area.id : undefined) ??
189
+ entry?.rootAreaRefs[0] ??
190
+ area.id;
191
+ const root = graph.areas.find((item) => item.id === rootRef);
192
+ const segments = [
193
+ {
194
+ areaRef: rootRef,
195
+ areaName: root ? display(root.name, "Area 名称待确认") : rootRef,
196
+ },
197
+ ];
198
+ for (const step of path.steps) {
199
+ const action = graph.actions.find((item) => item.id === step.actionRef);
200
+ const control = graph.controls.find((item) => item.id === step.controlRef);
201
+ const target = graph.areas.find((item) => item.id === step.targetAreaRef);
202
+ const sourceSegment = segments[segments.length - 1];
203
+ if (!sourceSegment || sourceSegment.areaRef !== step.sourceAreaRef) {
204
+ throw new Error(
205
+ `Canonical path ${path.id} is discontinuous at ${step.edgeRef}`,
206
+ );
207
+ }
208
+ segments[segments.length - 1] = {
209
+ ...sourceSegment,
210
+ ...(action ? { action: action.trigger } : {}),
211
+ ...(control
212
+ ? { controlName: display(control.name, "控件名称待确认") }
213
+ : {}),
214
+ ...(target
215
+ ? { targetAreaName: display(target.name, "Area 名称待确认") }
216
+ : {}),
217
+ };
218
+ segments.push({
219
+ areaRef: step.targetAreaRef,
220
+ areaName: target
221
+ ? display(target.name, "Area 名称待确认")
222
+ : step.targetAreaRef,
223
+ });
224
+ }
225
+ return {
226
+ status: "resolved",
227
+ reachability:
228
+ path.reachability === "conditional" ? "conditional" : "unconditional",
229
+ pathRef: path.id,
230
+ segments,
231
+ };
232
+ }
233
+ function targetFor(action, control, actionEffects, graph, source) {
234
+ const actionEdges = graph.edges
235
+ .filter((item) => item.actionRef === action.id)
236
+ .sort((left, right) => left.id.localeCompare(right.id, "en"));
237
+ const closeEffects = actionEffects.filter(
238
+ (effect) => effect.kind === "close-surface",
239
+ );
240
+ const returnsToHistory =
241
+ (action.trigger === "back" &&
242
+ !closeEffects.some((effect) => effect.closePolicy === "host-exit")) ||
243
+ closeEffects.some(
244
+ (effect) =>
245
+ effect.kind === "close-surface" && effect.closePolicy !== "host-exit",
246
+ );
247
+ const hasExplicitTransition = actionEffects.some(
248
+ (effect) =>
249
+ effect.kind === "navigate-area" ||
250
+ effect.kind === "show-surface" ||
251
+ effect.kind === "open-external",
252
+ );
253
+ const historyTargets = actionEdges.flatMap((edge) =>
254
+ !("status" in edge.target) && edge.target.kind === "area"
255
+ ? [edge.target.ref]
256
+ : [],
257
+ );
258
+ if (
259
+ returnsToHistory &&
260
+ !hasExplicitTransition &&
261
+ actionEdges.length > 0 &&
262
+ historyTargets.length === actionEdges.length
263
+ ) {
264
+ const supportRefs = uniqueSorted([
265
+ ...actionEdges.flatMap((edge) => edge.sourceFactRefs),
266
+ ...action.sourceFactRefs,
267
+ ...actionEffects
268
+ .filter((effect) => effect.kind === "close-surface")
269
+ .flatMap((effect) => effect.sourceFactRefs),
270
+ ]);
271
+ return {
272
+ kind: "history-back",
273
+ refs: uniqueSorted(historyTargets),
274
+ displayName: resolved("返回进入当前区域前的区域", supportRefs, "medium"),
275
+ supportRefs,
276
+ };
277
+ }
278
+ if (actionEdges.length === 0) {
279
+ if (!returnsToHistory) return undefined;
280
+ /*
281
+ * A close API alone does not prove an in-app history destination:
282
+ * dismissing an externally opened dialog or finishing the root Activity
283
+ * may leave the app. Resolved incoming Area edges are the proof that the
284
+ * current Area was entered from an in-app predecessor.
285
+ */
286
+ const incoming = graph.edges
287
+ .filter(
288
+ (edge) =>
289
+ !("status" in edge.target) &&
290
+ edge.target.kind === "area" &&
291
+ edge.target.ref === control.areaRef &&
292
+ edge.sourceAreaRef !== control.areaRef,
293
+ )
294
+ .sort((left, right) => left.id.localeCompare(right.id, "en"));
295
+ const refs = uniqueSorted(incoming.map((edge) => edge.sourceAreaRef));
296
+ if (refs.length === 0) return undefined;
297
+ const supportRefs = uniqueSorted([
298
+ ...incoming.flatMap((edge) => edge.sourceFactRefs),
299
+ ...action.sourceFactRefs,
300
+ ...actionEffects
301
+ .filter((effect) => effect.kind === "close-surface")
302
+ .flatMap((effect) => effect.sourceFactRefs),
303
+ ]);
304
+ return {
305
+ kind: "history-back",
306
+ refs,
307
+ displayName: resolved("返回进入当前区域前的区域", supportRefs, "medium"),
308
+ supportRefs,
309
+ };
310
+ }
311
+ const unresolvedEdges = actionEdges.filter((edge) => "status" in edge.target);
312
+ if (unresolvedEdges.length > 0) {
313
+ const refs = uniqueSorted(
314
+ unresolvedEdges.map((edge) => {
315
+ if (!("status" in edge.target)) {
316
+ throw new Error("Expected an unresolved edge target");
317
+ }
318
+ return edge.target.unresolvedRef;
319
+ }),
320
+ );
321
+ if (unresolvedEdges.length === actionEdges.length && refs.length === 1) {
322
+ return { status: "unresolved", unresolvedRef: refs[0] };
323
+ }
324
+ }
325
+ const resolvedEdges = actionEdges.flatMap((edge) =>
326
+ "status" in edge.target ? [] : [{ edge, target: edge.target }],
327
+ );
328
+ const targets = new Map();
329
+ for (const { target } of resolvedEdges) {
330
+ targets.set(`${target.kind}:${target.ref}`, target);
331
+ }
332
+ const uniqueTargets = [...targets.values()].sort(
333
+ (left, right) =>
334
+ left.kind.localeCompare(right.kind, "en") ||
335
+ left.ref.localeCompare(right.ref, "en"),
336
+ );
337
+ const targetKinds = new Set(uniqueTargets.map((target) => target.kind));
338
+ const supportRefs = uniqueSorted(
339
+ actionEdges.flatMap((edge) => edge.sourceFactRefs),
340
+ );
341
+ if (unresolvedEdges.length === 0 && uniqueTargets.length === 1) {
342
+ const target = uniqueTargets[0];
343
+ return resolvedReqTarget(action, target, supportRefs, graph);
344
+ }
345
+ if (
346
+ unresolvedEdges.length === 0 &&
347
+ targetKinds.size === 1 &&
348
+ uniqueTargets[0]?.kind === "area"
349
+ ) {
350
+ const refs = uniqueTargets.map((target) => target.ref);
351
+ const branches = refs.map((ref) => {
352
+ const branchEdges = resolvedEdges.filter(
353
+ ({ target }) => target.kind === "area" && target.ref === ref,
354
+ );
355
+ return {
356
+ ref,
357
+ ...targetBranchConditions(
358
+ action,
359
+ `area:${ref}`,
360
+ branchEdges.map(({ edge }) => edge),
361
+ graph,
362
+ source,
363
+ ),
364
+ };
365
+ });
366
+ return {
367
+ kind: "conditional-area-set",
368
+ refs,
369
+ branches,
370
+ displayName: resolved("根据条件进入不同区域", supportRefs),
371
+ supportRefs,
372
+ };
373
+ }
374
+ const edgesByTarget = new Map();
375
+ for (const edge of actionEdges) {
376
+ const key = targetKey(edge.target);
377
+ const grouped = edgesByTarget.get(key) ?? [];
378
+ grouped.push(edge);
379
+ edgesByTarget.set(key, grouped);
380
+ }
381
+ const branches = [...edgesByTarget.entries()]
382
+ .sort(([left], [right]) => left.localeCompare(right, "en"))
383
+ .map(([key, branchEdges]) => {
384
+ const edgeTarget = branchEdges[0]?.target;
385
+ if (!edgeTarget) {
386
+ throw new Error(
387
+ `Req-model target invariant for Action ${action.id}: ` +
388
+ `target branch ${key} has no Edge.`,
389
+ );
390
+ }
391
+ const branchSupportRefs = uniqueSorted(
392
+ branchEdges.flatMap((edge) => edge.sourceFactRefs),
393
+ );
394
+ const target =
395
+ "status" in edgeTarget
396
+ ? {
397
+ status: "unresolved",
398
+ unresolvedRef: edgeTarget.unresolvedRef,
399
+ }
400
+ : resolvedReqTarget(action, edgeTarget, branchSupportRefs, graph);
401
+ return {
402
+ target,
403
+ ...targetBranchConditions(action, key, branchEdges, graph, source),
404
+ };
405
+ });
406
+ return {
407
+ kind: "alternative-target-set",
408
+ branches,
409
+ displayName: resolved("根据条件进入不同目标", supportRefs),
410
+ supportRefs,
411
+ };
412
+ }
413
+ function normalizedPropertyName(property) {
414
+ const leaf = property.trim().split(/[.:/]/u).at(-1) ?? property;
415
+ return leaf.replace(/[^a-z0-9]/giu, "").toLowerCase();
416
+ }
417
+ function jsonKey(value) {
418
+ if (Array.isArray(value)) {
419
+ return `[${value.map(jsonKey).join(",")}]`;
420
+ }
421
+ if (value !== null && typeof value === "object") {
422
+ return `{${Object.entries(value)
423
+ .sort(([left], [right]) => left.localeCompare(right, "en"))
424
+ .map(([key, child]) => `${JSON.stringify(key)}:${jsonKey(child)}`)
425
+ .join(",")}}`;
426
+ }
427
+ return JSON.stringify(value);
428
+ }
429
+ function weakestConfidence(facts) {
430
+ const rank = { high: 2, medium: 1, low: 0 };
431
+ return facts.reduce(
432
+ (weakest, fact) =>
433
+ rank[fact.confidence] < rank[weakest] ? fact.confidence : weakest,
434
+ "high",
435
+ );
436
+ }
437
+ function propertiesFor(control, source) {
438
+ const uiElementRefs = new Set(
439
+ control.sourceFactRefs.filter((ref) =>
440
+ source.facts.some(
441
+ (fact) => fact.id === ref && fact.kind === "ui-element",
442
+ ),
443
+ ),
444
+ );
445
+ if (uiElementRefs.size === 0) {
446
+ return { options: [], supportRefs: [] };
447
+ }
448
+ const properties = source.facts
449
+ .filter(
450
+ (fact) =>
451
+ fact.kind === "ui-property" && uiElementRefs.has(fact.data.ownerKey),
452
+ )
453
+ .sort((left, right) => left.id.localeCompare(right.id, "en"));
454
+ const enabledFacts = properties.filter(
455
+ (fact) =>
456
+ normalizedPropertyName(fact.data.property) === "enabled" &&
457
+ typeof fact.data.value === "boolean",
458
+ );
459
+ let enabled = control.enabled;
460
+ if (enabledFacts.length > 0) {
461
+ const values = new Set(enabledFacts.map((fact) => fact.data.value));
462
+ if (values.size > 1) {
463
+ throw new Error(
464
+ `Req-model property invariant for Control ${control.id}: ` +
465
+ "static enabled properties disagree.",
466
+ );
467
+ }
468
+ const sourceEnabled = resolved(
469
+ enabledFacts[0].data.value,
470
+ enabledFacts.map((fact) => fact.id),
471
+ weakestConfidence(enabledFacts),
472
+ );
473
+ if (
474
+ enabled?.status === "resolved" &&
475
+ sourceEnabled.status === "resolved" &&
476
+ enabled.value !== sourceEnabled.value
477
+ ) {
478
+ throw new Error(
479
+ `Req-model property invariant for Control ${control.id}: ` +
480
+ "Area Graph and Source Facts enabled values disagree.",
481
+ );
482
+ }
483
+ enabled ??= sourceEnabled;
484
+ }
485
+ const defaultPriority = new Map([
486
+ ["defaultvalue", 0],
487
+ ["default", 0],
488
+ ["value", 1],
489
+ ["checked", 2],
490
+ ]);
491
+ const defaultCandidates = properties
492
+ .map((fact) => ({
493
+ fact,
494
+ priority: defaultPriority.get(normalizedPropertyName(fact.data.property)),
495
+ }))
496
+ .filter((candidate) => candidate.priority !== undefined);
497
+ const bestPriority = defaultCandidates.reduce(
498
+ (best, candidate) => Math.min(best, candidate.priority),
499
+ Number.POSITIVE_INFINITY,
500
+ );
501
+ const selectedDefaults = defaultCandidates
502
+ .filter((candidate) => candidate.priority === bestPriority)
503
+ .map((candidate) => candidate.fact);
504
+ let defaultValue;
505
+ if (selectedDefaults.length > 0) {
506
+ const values = new Map(
507
+ selectedDefaults.map((fact) => [
508
+ jsonKey(fact.data.value),
509
+ fact.data.value,
510
+ ]),
511
+ );
512
+ if (values.size > 1) {
513
+ throw new Error(
514
+ `Req-model property invariant for Control ${control.id}: ` +
515
+ "equally preferred default properties disagree.",
516
+ );
517
+ }
518
+ defaultValue = resolved(
519
+ values.values().next().value,
520
+ selectedDefaults.map((fact) => fact.id),
521
+ weakestConfidence(selectedDefaults),
522
+ );
523
+ }
524
+ const optionFacts = properties.filter(
525
+ (fact) =>
526
+ /^(options|choices|items)$/u.test(
527
+ normalizedPropertyName(fact.data.property),
528
+ ) &&
529
+ Array.isArray(fact.data.value) &&
530
+ fact.data.value.every(
531
+ (value) =>
532
+ typeof value === "string" ||
533
+ typeof value === "number" ||
534
+ typeof value === "boolean",
535
+ ),
536
+ );
537
+ const optionSupportByValue = new Map();
538
+ for (const fact of optionFacts) {
539
+ if (!Array.isArray(fact.data.value)) continue;
540
+ for (const item of fact.data.value) {
541
+ if (
542
+ typeof item !== "string" &&
543
+ typeof item !== "number" &&
544
+ typeof item !== "boolean"
545
+ )
546
+ continue;
547
+ const value = String(item);
548
+ if (value.trim().length === 0) continue;
549
+ optionSupportByValue.set(
550
+ value,
551
+ uniqueSorted([...(optionSupportByValue.get(value) ?? []), fact.id]),
552
+ );
553
+ }
554
+ }
555
+ const options = [...optionSupportByValue].map(([value, supportRefs]) => ({
556
+ value,
557
+ supportRefs,
558
+ }));
559
+ const supportRefs = uniqueSorted([
560
+ ...(enabled?.status === "resolved" ? enabled.supportRefs : []),
561
+ ...selectedDefaults.map((fact) => fact.id),
562
+ ...optionFacts.flatMap((fact) =>
563
+ Array.isArray(fact.data.value) &&
564
+ fact.data.value.some(
565
+ (value) =>
566
+ (typeof value === "string" && value.trim().length > 0) ||
567
+ typeof value === "number" ||
568
+ typeof value === "boolean",
569
+ )
570
+ ? [fact.id]
571
+ : [],
572
+ ),
573
+ ]);
574
+ return {
575
+ ...(enabled ? { enabled } : {}),
576
+ ...(defaultValue ? { defaultValue } : {}),
577
+ options,
578
+ supportRefs,
579
+ };
580
+ }
581
+ function supportedUnresolvedRefs(value) {
582
+ return value?.status === "unresolved" ? [value.unresolvedRef] : [];
583
+ }
584
+ function supportedSupportRefs(value) {
585
+ return value?.status === "resolved" ? [...value.supportRefs] : [];
586
+ }
587
+ function unresolvedRefsForReqTarget(target) {
588
+ if ("status" in target) return [target.unresolvedRef];
589
+ return uniqueSorted([
590
+ ...supportedUnresolvedRefs(target.displayName),
591
+ ...(target.kind === "external"
592
+ ? [
593
+ ...supportedUnresolvedRefs(target.intentAction),
594
+ ...supportedUnresolvedRefs(target.mimeType),
595
+ ...supportedUnresolvedRefs(target.uri),
596
+ ]
597
+ : []),
598
+ ...(target.kind === "alternative-target-set"
599
+ ? target.branches.flatMap((branch) =>
600
+ unresolvedRefsForReqTarget(branch.target),
601
+ )
602
+ : []),
603
+ ]);
604
+ }
605
+ function supportRefsForReqTarget(target) {
606
+ if ("status" in target) return [];
607
+ return uniqueSorted([
608
+ ...target.supportRefs,
609
+ ...supportedSupportRefs(target.displayName),
610
+ ...(target.kind === "external"
611
+ ? [
612
+ ...supportedSupportRefs(target.intentAction),
613
+ ...supportedSupportRefs(target.mimeType),
614
+ ...supportedSupportRefs(target.uri),
615
+ ]
616
+ : []),
617
+ ...(target.kind === "conditional-area-set"
618
+ ? (target.branches ?? []).flatMap((branch) => [
619
+ ...branch.supportRefs,
620
+ ...branch.conditions.flatMap((condition) => condition.supportRefs),
621
+ ...branch.conditionAlternatives.flatMap(
622
+ (alternative) => alternative.supportRefs,
623
+ ),
624
+ ])
625
+ : []),
626
+ ...(target.kind === "alternative-target-set"
627
+ ? target.branches.flatMap((branch) => [
628
+ ...branch.supportRefs,
629
+ ...branch.conditions.flatMap((condition) => condition.supportRefs),
630
+ ...branch.conditionAlternatives.flatMap(
631
+ (alternative) => alternative.supportRefs,
632
+ ),
633
+ ...supportRefsForReqTarget(branch.target),
634
+ ])
635
+ : []),
636
+ ]);
637
+ }
638
+ function interactionFor(control, actionId, graph, source) {
639
+ const action = graph.actions.find((item) => item.id === actionId);
640
+ if (!action) throw new Error(`Missing action ${actionId}`);
641
+ const actionEffects = action.effectRefs.map((effectRef) => {
642
+ const effect = graph.effects.find((item) => item.id === effectRef);
643
+ if (!effect) throw new Error(`Missing effect ${effectRef}`);
644
+ return effect;
645
+ });
646
+ const actionEdges = graph.edges.filter(
647
+ (edge) => edge.actionRef === action.id,
648
+ );
649
+ const distinctResolvedTargets = new Set(
650
+ actionEdges.flatMap((edge) =>
651
+ "status" in edge.target ? [] : [`${edge.target.kind}:${edge.target.ref}`],
652
+ ),
653
+ );
654
+ const conditionRefs = uniqueSorted([
655
+ ...action.preconditionRefs,
656
+ ...actionEdges.flatMap((edge) => edge.preconditions),
657
+ ]);
658
+ const baseConditions = conditionRefs.map((conditionRef) =>
659
+ textItem(
660
+ graph.caseId,
661
+ "feature-condition",
662
+ `${action.id}:${conditionRef}`,
663
+ conditionText(conditionRef, source),
664
+ [conditionRef],
665
+ ),
666
+ );
667
+ const alternativeConditions = action.preconditionAlternatives ?? [];
668
+ const disjunctiveCondition =
669
+ alternativeConditions.length > 1 &&
670
+ alternativeConditions.every(
671
+ (alternative) => alternative.conditionRefs.length > 0,
672
+ )
673
+ ? [
674
+ textItem(
675
+ graph.caseId,
676
+ "feature-condition-alternatives",
677
+ action.id,
678
+ `满足以下任一条件组合:${alternativeConditions
679
+ .map((alternative) =>
680
+ alternative.conditionRefs
681
+ .map((conditionRef) => conditionText(conditionRef, source))
682
+ .join(" 且 "),
683
+ )
684
+ .join(";")}`,
685
+ uniqueSorted(
686
+ alternativeConditions.flatMap((alternative) => [
687
+ ...alternative.conditionRefs,
688
+ ...alternative.sourceFactRefs,
689
+ ]),
690
+ ),
691
+ ),
692
+ ]
693
+ : [];
694
+ const branchConditions =
695
+ distinctResolvedTargets.size > 1
696
+ ? actionEdges.flatMap((edge) => {
697
+ if ("status" in edge.target || edge.target.kind !== "area") {
698
+ return [];
699
+ }
700
+ const targetRef = edge.target.ref;
701
+ const targetArea = graph.areas.find((area) => area.id === targetRef);
702
+ const targetName = targetArea
703
+ ? display(targetArea.name, targetRef)
704
+ : targetRef;
705
+ return edge.conditions.map((conditionRef) =>
706
+ textItem(
707
+ graph.caseId,
708
+ "feature-target-condition",
709
+ `${action.id}:${targetRef}:${edge.id}:${conditionRef}`,
710
+ `进入“${targetName}”时:${conditionText(conditionRef, source)}`,
711
+ [conditionRef],
712
+ ),
713
+ );
714
+ })
715
+ : [];
716
+ branchConditions.sort(
717
+ (left, right) =>
718
+ left.text.localeCompare(right.text, "en") ||
719
+ left.id.localeCompare(right.id, "en"),
720
+ );
721
+ const conditions = [
722
+ ...baseConditions,
723
+ ...disjunctiveCondition,
724
+ ...branchConditions,
725
+ ];
726
+ const effects = actionEffects.map((effect) => {
727
+ const item = textItem(
728
+ graph.caseId,
729
+ "feature-effect",
730
+ effect.id,
731
+ effect.description.status === "resolved"
732
+ ? effect.description.value
733
+ : "操作结果待确认",
734
+ effect.sourceFactRefs,
735
+ );
736
+ const alternatives = (effect.conditionAlternatives ?? []).map(
737
+ (alternative, alternativeIndex) => {
738
+ const alternativeConditions = alternative.conditionRefs.map(
739
+ (conditionRef) =>
740
+ textItem(
741
+ graph.caseId,
742
+ "effect-condition",
743
+ `${effect.id}:${alternativeIndex}:${conditionRef}`,
744
+ conditionText(conditionRef, source),
745
+ [conditionRef],
746
+ ),
747
+ );
748
+ return {
749
+ conditions: alternativeConditions,
750
+ supportRefs: uniqueSorted([
751
+ ...alternative.sourceFactRefs,
752
+ ...alternativeConditions.flatMap(
753
+ (condition) => condition.supportRefs,
754
+ ),
755
+ ]),
756
+ };
757
+ },
758
+ );
759
+ return {
760
+ ...item,
761
+ kind: effect.kind,
762
+ conditionAlternatives: alternatives,
763
+ ...(effect.stateKey ? { stateKey: effect.stateKey } : {}),
764
+ ...(effect.value ? { value: effect.value } : {}),
765
+ };
766
+ });
767
+ const target = targetFor(action, control, actionEffects, graph, source);
768
+ const targetUnresolvedRefs = target ? unresolvedRefsForReqTarget(target) : [];
769
+ const targetSupportRefs = target ? supportRefsForReqTarget(target) : [];
770
+ const actionEdgeSupportRefs = graph.edges
771
+ .filter((edge) => edge.actionRef === action.id)
772
+ .flatMap((edge) => edge.sourceFactRefs);
773
+ return {
774
+ id: graphId(
775
+ "interaction",
776
+ graph.caseId,
777
+ action.trigger,
778
+ `${control.id}:${action.id}`,
779
+ ),
780
+ actionRef: action.id,
781
+ trigger: action.trigger,
782
+ conditions,
783
+ effects,
784
+ ...(target ? { target } : {}),
785
+ unresolvedRefs: [
786
+ ...new Set([
787
+ ...actionEffects.flatMap((effect) => effect.unresolvedRefs ?? []),
788
+ ...targetUnresolvedRefs,
789
+ ]),
790
+ ].sort(),
791
+ supportRefs: uniqueSorted([
792
+ ...action.sourceFactRefs,
793
+ ...actionEffects.flatMap((effect) => effect.sourceFactRefs),
794
+ ...conditions.flatMap((condition) => condition.supportRefs),
795
+ ...effects.flatMap((effect) => effect.supportRefs),
796
+ ...actionEdgeSupportRefs,
797
+ ...targetSupportRefs,
798
+ ]),
799
+ };
800
+ }
801
+ function featureFor(control, graph, source) {
802
+ if (control.actionRefs.length === 0) {
803
+ throw new Error(
804
+ `Req-model feature invariant for Control ${control.id}: ` +
805
+ "a Feature requires at least one Action.",
806
+ );
807
+ }
808
+ const interactions = control.actionRefs.map((actionRef) =>
809
+ interactionFor(control, actionRef, graph, source),
810
+ );
811
+ const properties = propertiesFor(control, source);
812
+ const propertyUnresolvedRefs =
813
+ properties.defaultValue?.status === "unresolved"
814
+ ? [properties.defaultValue.unresolvedRef]
815
+ : [];
816
+ const onlyInteraction =
817
+ interactions.length === 1 ? interactions[0] : undefined;
818
+ return {
819
+ id: graphId("feature", graph.caseId, "control", control.id),
820
+ controlRef: control.id,
821
+ displayName: control.name,
822
+ interactions,
823
+ /*
824
+ * These summary fields keep pre-interaction in-process consumers source
825
+ * compatible. Draft generation uses interactions exclusively. For a
826
+ * multi-action Control, the sentinel makes it impossible to mistake the
827
+ * first callback for the whole physical Control.
828
+ */
829
+ trigger: onlyInteraction?.trigger ?? "multiple-interactions",
830
+ ...(properties.enabled ? { enabled: properties.enabled } : {}),
831
+ ...(properties.defaultValue
832
+ ? { defaultValue: properties.defaultValue }
833
+ : {}),
834
+ options: properties.options,
835
+ conditions: interactions.flatMap((interaction) => interaction.conditions),
836
+ effects: interactions.flatMap((interaction) => interaction.effects),
837
+ ...(onlyInteraction?.target ? { target: onlyInteraction.target } : {}),
838
+ unresolvedRefs: uniqueSorted([
839
+ ...control.unresolvedRefs,
840
+ ...(control.name.status === "unresolved"
841
+ ? [control.name.unresolvedRef]
842
+ : []),
843
+ ...propertyUnresolvedRefs,
844
+ ...interactions.flatMap((interaction) => interaction.unresolvedRefs),
845
+ ]),
846
+ supportRefs: uniqueSorted([
847
+ ...control.sourceFactRefs,
848
+ ...properties.supportRefs,
849
+ ...interactions.flatMap((interaction) => interaction.supportRefs),
850
+ ]),
851
+ };
852
+ }
853
+ function descriptionItems(area, caseId) {
854
+ if (area.descriptionFacts.length > 0) {
855
+ return area.descriptionFacts.map((fact) =>
856
+ textItem(
857
+ caseId,
858
+ "area-description",
859
+ fact.id,
860
+ fact.value.status === "resolved"
861
+ ? String(fact.value.value)
862
+ : "区域说明待确认",
863
+ fact.sourceFactRefs,
864
+ ),
865
+ );
866
+ }
867
+ return [
868
+ textItem(
869
+ caseId,
870
+ "area-description",
871
+ area.id,
872
+ `${display(area.name, "该区域")}是一个 ${area.kind} 类型的交互区域。`,
873
+ area.sourceFactRefs,
874
+ ),
875
+ ];
876
+ }
877
+ /** Disambiguates Areas that resolve to the exact same displayName (e.g. two
878
+ * unrelated composables/routes sharing one string resource) using their
879
+ * identityAnchors — deterministic, no LLM involved (ADR-0001). Runs against
880
+ * the graph's own Area[] and must run before canonicalPath()/featureFor()
881
+ * read Area.name, so every downstream consumer (path segments, navigation
882
+ * target descriptions, feature titles) sees the same disambiguated name from
883
+ * one source of truth. Winner selection within a collision group is
884
+ * intentionally asymmetric (whichever Area's own identityAnchors happen not
885
+ * to yield a differing candidate keeps the bare name) — not a principled
886
+ * choice, just the first-found one; not worth the complexity to fix. Areas
887
+ * a suffix can't be found for, or whose suffix still collides with a sibling
888
+ * after assignment, are marked unresolved (AMBIGUOUS_AREA_DISPLAY_NAME) and
889
+ * left for the Stage 2 render Agent via the existing
890
+ * hasUnresolvedPathSegmentNames/renderPathNameCompletionLaw machinery —
891
+ * same mechanism already used for any other unresolved Area name. */
892
+ function disambiguateAreaDisplayNames(areas, caseId) {
893
+ const byName = new Map();
894
+ for (let i = 0; i < areas.length; i++) {
895
+ const area = areas[i];
896
+ if (area.name.status !== "resolved") continue;
897
+ const indices = byName.get(area.name.value) ?? [];
898
+ indices.push(i);
899
+ byName.set(area.name.value, indices);
900
+ }
901
+ const collisionGroups = [...byName.values()].filter(
902
+ (indices) => indices.length > 1,
903
+ );
904
+ if (collisionGroups.length === 0) return { areas, newUnresolved: [] };
905
+ const groupNameByIndex = new Map();
906
+ // Only set for an area whose candidate final string (bare groupName if no
907
+ // distinguishing anchor was found, or `groupName (suffix)` otherwise) is
908
+ // unique within its collision group — an area with no distinguishing
909
+ // anchor legitimately keeps the bare name (the asymmetric "winner") as
910
+ // long as no sibling also ends up bare; a collision on the *candidate*
911
+ // final (whether two bare members or two identical suffixes) demotes every
912
+ // colliding member to unresolved instead of silently keeping duplicates
913
+ // under a "resolved" status.
914
+ const finalNameByIndex = new Map();
915
+ for (const indices of collisionGroups) {
916
+ const groupName = display(areas[indices[0]].name, "");
917
+ const suffixByIndex = new Map();
918
+ for (const idx of indices) {
919
+ groupNameByIndex.set(idx, groupName);
920
+ for (const anchor of areas[idx].identityAnchors) {
921
+ const candidate = componentIdentifierName(anchor);
922
+ if (candidate === undefined || candidate.trim().length === 0) continue;
923
+ if (candidate === groupName) continue;
924
+ suffixByIndex.set(idx, candidate);
925
+ break;
926
+ }
927
+ }
928
+ const candidateFinalByIndex = new Map();
929
+ for (const idx of indices) {
930
+ const suffix = suffixByIndex.get(idx);
931
+ candidateFinalByIndex.set(
932
+ idx,
933
+ suffix === undefined ? groupName : `${groupName} (${suffix})`,
934
+ );
935
+ }
936
+ const finalCounts = new Map();
937
+ for (const value of candidateFinalByIndex.values()) {
938
+ finalCounts.set(value, (finalCounts.get(value) ?? 0) + 1);
939
+ }
940
+ for (const idx of indices) {
941
+ const value = candidateFinalByIndex.get(idx);
942
+ if ((finalCounts.get(value) ?? 0) === 1) finalNameByIndex.set(idx, value);
943
+ }
944
+ }
945
+ const newUnresolved = [];
946
+ const disambiguated = areas.map((area, i) => {
947
+ const groupName = groupNameByIndex.get(i);
948
+ if (groupName === undefined) return area;
949
+ const finalName = finalNameByIndex.get(i);
950
+ const original = area.name;
951
+ if (finalName !== undefined) {
952
+ return {
953
+ ...area,
954
+ name: resolved(finalName, original.supportRefs, original.confidence),
955
+ };
956
+ }
957
+ const unresolvedId = graphId(
958
+ "unresolved",
959
+ caseId,
960
+ "area-display-name",
961
+ area.id,
962
+ );
963
+ newUnresolved.push({
964
+ id: unresolvedId,
965
+ ownerRef: area.id,
966
+ fieldPath: "displayName",
967
+ reasonCode: "AMBIGUOUS_AREA_DISPLAY_NAME",
968
+ message:
969
+ `区域显示名称"${groupName}"与其他区域重复,` +
970
+ "且 identityAnchors 中无可用区分后缀(或候选后缀仍与同组内其他区域冲突)。",
971
+ evidenceRefs: area.identityAnchors,
972
+ candidates: [],
973
+ impact: ["area-display-name"],
974
+ });
975
+ return {
976
+ ...area,
977
+ name: { status: "unresolved", unresolvedRef: unresolvedId },
978
+ unresolvedRefs: [...new Set([...area.unresolvedRefs, unresolvedId])],
979
+ };
980
+ });
981
+ return { areas: disambiguated, newUnresolved };
982
+ }
983
+ export function buildReqModel(graph, source) {
984
+ if (
985
+ graph.caseId !== source.caseId ||
986
+ JSON.stringify(graph.analysisContext) !==
987
+ JSON.stringify(source.analysisContext)
988
+ ) {
989
+ throw new Error("Area graph and source facts use different contexts");
990
+ }
991
+ // Disambiguate collided Area names before anything below reads Area.name,
992
+ // so canonicalPath()/featureFor() bake the disambiguated (or unresolved)
993
+ // name into every downstream string — see disambiguateAreaDisplayNames.
994
+ const {
995
+ areas: disambiguatedGraphAreas,
996
+ newUnresolved: displayNameUnresolved,
997
+ } = disambiguateAreaDisplayNames(graph.areas, graph.caseId);
998
+ const workingGraph = {
999
+ ...graph,
1000
+ areas: disambiguatedGraphAreas,
1001
+ unresolved: [...graph.unresolved, ...displayNameUnresolved],
1002
+ };
1003
+ const areas = workingGraph.areas.map((area) => {
1004
+ const path = area.canonicalPathRef
1005
+ ? workingGraph.paths.find((item) => item.id === area.canonicalPathRef)
1006
+ : undefined;
1007
+ const controls = area.controlRefs.map((controlRef) => {
1008
+ const control = workingGraph.controls.find(
1009
+ (item) => item.id === controlRef,
1010
+ );
1011
+ if (!control) throw new Error(`Missing control ${controlRef}`);
1012
+ return control;
1013
+ });
1014
+ return {
1015
+ id: graphId("req-area", workingGraph.caseId, area.kind, area.id),
1016
+ areaRef: area.id,
1017
+ displayName: area.name,
1018
+ canonicalPath: canonicalPath(area, workingGraph),
1019
+ preconditions: [...new Set([...(path?.preconditions ?? [])])]
1020
+ .sort()
1021
+ .map((conditionRef) =>
1022
+ textItem(
1023
+ workingGraph.caseId,
1024
+ "area-precondition",
1025
+ `${area.id}:${conditionRef}`,
1026
+ conditionText(conditionRef, source),
1027
+ [conditionRef],
1028
+ ),
1029
+ ),
1030
+ descriptionItems: descriptionItems(area, workingGraph.caseId),
1031
+ features: controls.flatMap((control) =>
1032
+ control.actionRefs.length === 0
1033
+ ? []
1034
+ : [featureFor(control, workingGraph, source)],
1035
+ ),
1036
+ unresolvedRefs: [
1037
+ ...new Set([
1038
+ ...area.unresolvedRefs,
1039
+ ...controls.flatMap((control) => control.unresolvedRefs),
1040
+ ]),
1041
+ ].sort(),
1042
+ supportRefs: [
1043
+ ...new Set([
1044
+ ...area.sourceFactRefs,
1045
+ ...controls.flatMap((control) => control.sourceFactRefs),
1046
+ ]),
1047
+ ].sort(),
1048
+ };
1049
+ });
1050
+ return {
1051
+ schemaVersion: "1.0.0",
1052
+ artifactKind: "req-model",
1053
+ caseId: workingGraph.caseId,
1054
+ analysisContext: workingGraph.analysisContext,
1055
+ ...(workingGraph.semanticCompleteness
1056
+ ? { semanticCompleteness: workingGraph.semanticCompleteness }
1057
+ : source.semanticCompleteness
1058
+ ? { semanticCompleteness: source.semanticCompleteness }
1059
+ : {}),
1060
+ ...((workingGraph.limitations ?? source.limitations)
1061
+ ? { limitations: workingGraph.limitations ?? source.limitations }
1062
+ : {}),
1063
+ areas,
1064
+ unresolved: workingGraph.unresolved,
1065
+ };
1066
+ }