android2harmony 0.1.7 → 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 (226) 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 +2 -2
  213. package/skills/hmos-convert-pipeline/SKILL.md +7 -5
  214. package/skills/hmos-incremental-ui-align/README.md +7 -7
  215. package/skills/hmos-incremental-ui-align/SKILL.md +1 -1
  216. package/skills/hmos-incremental-ui-align/page_align.md +1 -1
  217. package/skills/hmos-spec-generate/SKILL.md +2 -2
  218. package/skills/hmos-spec-generate/scripts/parse_requirements.mjs +537 -0
  219. package/skills/hmos-fix-build-errors/SKILL.md +0 -266
  220. package/skills/hmos-fix-build-errors/references/arkts-strict-patterns.md +0 -219
  221. package/skills/hmos-fix-build-errors/references/known-patterns.md +0 -157
  222. package/skills/hmos-fix-build-errors/references/rdb-entity-pattern.md +0 -131
  223. package/skills/hmos-spec-generate/scripts/parse_requirements.ts +0 -515
  224. /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/resolve-metadata-tool.mjs +0 -0
  225. /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/self-test-runner.mjs +0 -0
  226. /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/testcases-tool.mjs +0 -0
@@ -0,0 +1,678 @@
1
+ import {
2
+ camelToSnakeResourceId,
3
+ resourceIdsEquivalent,
4
+ } from "./resource-id.js";
5
+ const areaComponentTypes = new Set([
6
+ "activity",
7
+ "fragment",
8
+ "composable",
9
+ "compose-destination",
10
+ "dialog",
11
+ "drawer",
12
+ "bottom-sheet",
13
+ "overlay",
14
+ "pager",
15
+ "pager-page",
16
+ "inline-detail-surface",
17
+ "popup",
18
+ "widget",
19
+ ]);
20
+ const identifierNoiseTokens = new Set([
21
+ "label",
22
+ "text",
23
+ "textview",
24
+ "tv",
25
+ "view",
26
+ ]);
27
+ function objectValue(value) {
28
+ return value !== null && typeof value === "object" && !Array.isArray(value)
29
+ ? value
30
+ : undefined;
31
+ }
32
+ function objectText(value, key) {
33
+ const item = value?.[key];
34
+ return typeof item === "string" && item.trim().length > 0
35
+ ? item.trim()
36
+ : undefined;
37
+ }
38
+ function recordText(record, key) {
39
+ return objectText(record.data, key);
40
+ }
41
+ function recordNumber(record, key) {
42
+ const value = record.data[key];
43
+ return typeof value === "number" && Number.isFinite(value)
44
+ ? value
45
+ : undefined;
46
+ }
47
+ function nonBlank(value) {
48
+ return typeof value === "string" && value.trim().length > 0
49
+ ? value.trim()
50
+ : undefined;
51
+ }
52
+ function simpleName(value) {
53
+ const dot = value.lastIndexOf(".");
54
+ const dollar = value.lastIndexOf("$");
55
+ return value.slice(Math.max(dot, dollar) + 1);
56
+ }
57
+ function isTextViewName(value) {
58
+ return simpleName(value).toLowerCase().endsWith("textview");
59
+ }
60
+ function isTextView(view) {
61
+ return isTextViewName(view.tag);
62
+ }
63
+ function identifierTokens(value) {
64
+ const tokens = value
65
+ .replace(/([a-z0-9])([A-Z])/gu, "$1_$2")
66
+ .replace(/([A-Z]+)([A-Z][a-z])/gu, "$1_$2")
67
+ .split(/[^A-Za-z0-9]+/u)
68
+ .map((token) => token.toLowerCase())
69
+ .filter((token) => token.length > 0);
70
+ while (tokens.length > 0 && identifierNoiseTokens.has(tokens[0])) {
71
+ tokens.shift();
72
+ }
73
+ while (
74
+ tokens.length > 0 &&
75
+ identifierNoiseTokens.has(tokens[tokens.length - 1])
76
+ ) {
77
+ tokens.pop();
78
+ }
79
+ return tokens;
80
+ }
81
+ /**
82
+ * Title evidence must come from an identifier whose semantic tail is exactly
83
+ * "title" or "header". This accepts generic forms such as toolbar_title,
84
+ * screenTitle and tv_home_title, while rejecting subtitle, title_icon and
85
+ * status_text.
86
+ */
87
+ function isTitleResourceId(resourceId) {
88
+ const tokens = identifierTokens(resourceId);
89
+ const tail = tokens[tokens.length - 1];
90
+ return tail === "title" || tail === "header";
91
+ }
92
+ /**
93
+ * Android and Material expose semantic title/headline text appearances. A
94
+ * selected static TextView using one of those styles is stronger title
95
+ * evidence than its position in the XML tree, even when it has no id.
96
+ */
97
+ function hasTitleTextAppearance(view) {
98
+ const values = [
99
+ view.textAppearance?.raw,
100
+ view.textAppearance?.resolved,
101
+ ].filter((value) => typeof value === "string" && value.trim().length > 0);
102
+ return values.some((value) =>
103
+ /(?:textappearance(?:large|headline|title)|(?:^|[._/:])(?:display|headline|title)(?:[._/:]|$))/iu.test(
104
+ value,
105
+ ),
106
+ );
107
+ }
108
+ function sourceKind(record) {
109
+ const language = recordText(record, "language");
110
+ if (language === "kotlin" || record.location?.path.endsWith(".kt")) {
111
+ return "kotlin";
112
+ }
113
+ if (language === "java" || record.location?.path.endsWith(".java")) {
114
+ return "java";
115
+ }
116
+ return "other";
117
+ }
118
+ function recordLocation(record, anchor) {
119
+ if (!record.location) return undefined;
120
+ return {
121
+ sourceKind: sourceKind(record),
122
+ path: record.location.path,
123
+ anchor,
124
+ range: {
125
+ startLine: Math.max(1, record.location.startLine),
126
+ endLine: Math.max(
127
+ Math.max(1, record.location.startLine),
128
+ record.location.endLine,
129
+ ),
130
+ ...(record.location.startColumn >= 1
131
+ ? { startColumn: record.location.startColumn }
132
+ : {}),
133
+ ...(record.location.endColumn >= 1
134
+ ? { endColumn: record.location.endColumn }
135
+ : {}),
136
+ },
137
+ ...(recordText(record, "enclosingMethod")
138
+ ? { symbol: recordText(record, "enclosingMethod") }
139
+ : {}),
140
+ };
141
+ }
142
+ function supportKey(support) {
143
+ const location = support.location;
144
+ return [
145
+ support.role,
146
+ location.sourceKind,
147
+ location.path,
148
+ location.anchor,
149
+ location.symbol ?? "",
150
+ location.range.startLine,
151
+ location.range.endLine,
152
+ ].join("\u0000");
153
+ }
154
+ function uniqueSupport(support) {
155
+ const byKey = new Map();
156
+ support.forEach((item) => byKey.set(supportKey(item), item));
157
+ return [...byKey.values()].sort((left, right) =>
158
+ supportKey(left).localeCompare(supportKey(right), "en"),
159
+ );
160
+ }
161
+ function uniqueStrings(values) {
162
+ return [
163
+ ...new Set(
164
+ values.map((value) => value.trim()).filter((value) => value.length > 0),
165
+ ),
166
+ ].sort((left, right) => left.localeCompare(right, "en"));
167
+ }
168
+ function localStringReference(raw, applicationId) {
169
+ const match =
170
+ /^@(?:\+)?(?:(?<package>[^:/]+):)?string\/(?<name>[^/]+)$/u.exec(
171
+ raw.trim(),
172
+ );
173
+ const packageName = match?.groups?.package;
174
+ if (
175
+ packageName === "android" ||
176
+ (packageName !== undefined && packageName !== applicationId)
177
+ )
178
+ return undefined;
179
+ return match?.groups?.name;
180
+ }
181
+ function selectedStringRecords(android, name) {
182
+ return android.values.filter(
183
+ (record) => record.resourceType === "string" && record.name === name,
184
+ );
185
+ }
186
+ function resolveSelectedString(android, name, seen = new Set()) {
187
+ if (seen.has(name)) return undefined;
188
+ const records = selectedStringRecords(android, name);
189
+ if (records.length === 0) return undefined;
190
+ const nextSeen = new Set([...seen, name]);
191
+ const resolutions = records.flatMap((record) => {
192
+ const raw = nonBlank(record.value);
193
+ if (!raw) return [];
194
+ const alias = localStringReference(raw, android.manifest.packageName);
195
+ const nested = alias
196
+ ? resolveSelectedString(android, alias, nextSeen)
197
+ : undefined;
198
+ if (raw.startsWith("@") && !nested) return [];
199
+ return [
200
+ {
201
+ value: nested?.value ?? raw,
202
+ support: [
203
+ {
204
+ location: record.evidence,
205
+ role: "selected string resource for Area title",
206
+ },
207
+ ...(nested?.support ?? []),
208
+ ],
209
+ resourceNames: [name, ...(nested?.resourceNames ?? [])],
210
+ },
211
+ ];
212
+ });
213
+ const values = uniqueStrings(resolutions.map((item) => item.value));
214
+ if (values.length !== 1) return undefined;
215
+ const matching = resolutions.filter((item) => item.value === values[0]);
216
+ return {
217
+ value: values[0],
218
+ support: uniqueSupport(matching.flatMap((item) => item.support)),
219
+ resourceNames: uniqueStrings(
220
+ matching.flatMap((item) => item.resourceNames),
221
+ ),
222
+ };
223
+ }
224
+ function resourceFactRefs(state, resourceNames) {
225
+ const names = new Set(resourceNames);
226
+ return state.builder
227
+ .facts("resource-value")
228
+ .flatMap((fact) =>
229
+ fact.kind === "resource-value" &&
230
+ fact.data.resourceType === "string" &&
231
+ names.has(fact.data.resourceName)
232
+ ? [fact.id]
233
+ : [],
234
+ );
235
+ }
236
+ function ownerIsArea(state, ownerRef) {
237
+ const owner = state.builder.fact(ownerRef);
238
+ return (
239
+ owner?.kind === "component" &&
240
+ areaComponentTypes.has(owner.data.componentType) &&
241
+ !owner.data.qualifiedName.startsWith("@layout/")
242
+ );
243
+ }
244
+ function ownerUiTextViews(state, ownerRef, resourceId) {
245
+ return [...(state.uiByResourceId.get(resourceId) ?? [])]
246
+ .filter((factRef) => {
247
+ const fact = state.builder.fact(factRef);
248
+ return (
249
+ fact?.kind === "ui-element" &&
250
+ fact.data.ownerKey === ownerRef &&
251
+ fact.data.resourceId !== undefined &&
252
+ resourceIdsEquivalent(fact.data.resourceId, resourceId) &&
253
+ isTextViewName(fact.data.elementType)
254
+ );
255
+ })
256
+ .sort((left, right) => left.localeCompare(right, "en"));
257
+ }
258
+ function xmlStaticString(android, view) {
259
+ const resolved = nonBlank(view.text?.resolved);
260
+ const raw = nonBlank(view.text?.raw);
261
+ const resourceName = raw
262
+ ? localStringReference(raw, android.manifest.packageName)
263
+ : undefined;
264
+ const selected = resourceName
265
+ ? resolveSelectedString(android, resourceName)
266
+ : undefined;
267
+ if (resolved) {
268
+ return {
269
+ value: resolved,
270
+ support: selected?.value === resolved ? selected.support : [],
271
+ resourceNames: selected?.value === resolved ? selected.resourceNames : [],
272
+ };
273
+ }
274
+ if (!raw) return undefined;
275
+ if (!raw.startsWith("@")) {
276
+ return { value: raw, support: [], resourceNames: [] };
277
+ }
278
+ return selected;
279
+ }
280
+ function xmlTitleCandidates(android, state) {
281
+ const candidates = [];
282
+ for (const view of android.views) {
283
+ if (
284
+ view.resourceType !== "layout" ||
285
+ !(
286
+ (view.id && isTitleResourceId(view.id)) ||
287
+ hasTitleTextAppearance(view)
288
+ ) ||
289
+ !isTextView(view)
290
+ )
291
+ continue;
292
+ const title = xmlStaticString(android, view);
293
+ if (!title) continue;
294
+ const owners = [
295
+ ...(state.layoutOwners.get(view.resourceName) ?? []),
296
+ ].sort();
297
+ for (const ownerRef of owners) {
298
+ if (!ownerIsArea(state, ownerRef)) continue;
299
+ candidates.push({
300
+ ownerRef,
301
+ value: title.value,
302
+ support: uniqueSupport([
303
+ {
304
+ location: view.evidence,
305
+ role: "selected XML title TextView",
306
+ },
307
+ ...title.support,
308
+ ]),
309
+ relatedFactRefs: uniqueStrings([
310
+ ownerRef,
311
+ ...(view.id ? ownerUiTextViews(state, ownerRef, view.id) : []),
312
+ ...resourceFactRefs(state, title.resourceNames),
313
+ ]),
314
+ });
315
+ }
316
+ }
317
+ return candidates;
318
+ }
319
+ function navigationLabelCandidates(android, state) {
320
+ return android.fragments.flatMap((fragment) => {
321
+ const label = fragment.label;
322
+ if (!label) return [];
323
+ const value =
324
+ nonBlank(label.resolved) ??
325
+ (!label.raw.startsWith("@") ? nonBlank(label.raw) : undefined);
326
+ if (!value) return [];
327
+ const className =
328
+ fragment.className.startsWith(".") && android.manifest.packageName
329
+ ? `${android.manifest.packageName}${fragment.className}`
330
+ : fragment.className.includes(".") || !android.manifest.packageName
331
+ ? fragment.className
332
+ : `${android.manifest.packageName}.${fragment.className}`;
333
+ const ownerRef = state.componentByQualifiedName.get(className);
334
+ if (!ownerRef || !ownerIsArea(state, ownerRef)) return [];
335
+ const resourceName = localStringReference(
336
+ label.raw,
337
+ android.manifest.packageName,
338
+ );
339
+ return [
340
+ {
341
+ ownerRef,
342
+ value,
343
+ support: [
344
+ {
345
+ location: fragment.evidence,
346
+ role: "selected navigation destination label",
347
+ },
348
+ ],
349
+ relatedFactRefs: uniqueStrings([
350
+ ownerRef,
351
+ ...(resourceName ? resourceFactRefs(state, [resourceName]) : []),
352
+ ]),
353
+ },
354
+ ];
355
+ });
356
+ }
357
+ function isBindingClass(value) {
358
+ if (!value) return false;
359
+ const name = simpleName(value);
360
+ return /^[A-Z][A-Za-z0-9]*Binding$/u.test(name) && name !== "ViewDataBinding";
361
+ }
362
+ function bindingPropertyResourceIdFromSymbol(symbol) {
363
+ if (!isBindingClass(objectText(symbol, "ownerQualifiedName")))
364
+ return undefined;
365
+ const rawName = objectText(symbol, "name");
366
+ if (!rawName) return undefined;
367
+ const propertyName = /^get[A-Z]/u.test(rawName)
368
+ ? `${rawName[3]?.toLowerCase() ?? ""}${rawName.slice(4)}`
369
+ : rawName;
370
+ if (propertyName === "root") return undefined;
371
+ return camelToSnakeResourceId(propertyName);
372
+ }
373
+ function bindingPropertyResourceId(expression) {
374
+ return bindingPropertyResourceIdFromSymbol(
375
+ objectValue(expression.resolvedSymbol),
376
+ );
377
+ }
378
+ function expressionOf(binding) {
379
+ return objectValue(binding.data.expression);
380
+ }
381
+ function isResolvedBinding(binding) {
382
+ return recordText(binding, "bindingStatus") === "resolved";
383
+ }
384
+ function resourceReferenceOf(expression) {
385
+ return objectValue(expression.resourceReference);
386
+ }
387
+ function isFrameworkFindViewById(call) {
388
+ if (recordText(call, "targetName") !== "findViewById") return false;
389
+ const owner = recordText(call, "targetOwnerQualifiedName") ?? "";
390
+ return /^(?:android\.(?:app\.(?:Activity|Dialog)|view\.(?:View|Window))|androidx\.appcompat\.app\.AppCompat(?:Activity|Dialog))$/u.test(
391
+ owner,
392
+ );
393
+ }
394
+ function structuredResourceId(expression, index) {
395
+ const resource = resourceReferenceOf(expression);
396
+ if (objectText(resource, "resourceType") === "id") {
397
+ return objectText(resource, "resourceName");
398
+ }
399
+ const property = bindingPropertyResourceId(expression);
400
+ if (property) return property;
401
+ const callRef = objectText(expression, "callRef");
402
+ if (!callRef) return undefined;
403
+ const call = index.resolvedCallsById.get(callRef);
404
+ if (!call || !isFrameworkFindViewById(call)) return undefined;
405
+ const ids = uniqueStrings(
406
+ (index.bindingsByCallRef.get(callRef) ?? [])
407
+ .filter(
408
+ (binding) =>
409
+ isResolvedBinding(binding) &&
410
+ recordNumber(binding, "argumentIndex") === 0,
411
+ )
412
+ .flatMap((binding) => {
413
+ const nested = expressionOf(binding);
414
+ if (!nested) return [];
415
+ const nestedResource = resourceReferenceOf(nested);
416
+ return objectText(nestedResource, "resourceType") === "id"
417
+ ? [objectText(nestedResource, "resourceName") ?? ""]
418
+ : [];
419
+ }),
420
+ );
421
+ return ids.length === 1 ? ids[0] : undefined;
422
+ }
423
+ function receiverResourceId(call, index) {
424
+ const receiver = objectValue(call.data.receiverResolvedSymbol);
425
+ const receiverExpression = objectValue(call.data.receiverExpression);
426
+ const candidates = uniqueStrings([
427
+ receiver ? (bindingPropertyResourceIdFromSymbol(receiver) ?? "") : "",
428
+ receiverExpression
429
+ ? (structuredResourceId(receiverExpression, index) ?? "")
430
+ : "",
431
+ ...(receiver && objectValue(receiver.initializer)
432
+ ? [structuredResourceId(objectValue(receiver.initializer), index) ?? ""]
433
+ : []),
434
+ ]);
435
+ return candidates.length === 1 ? candidates[0] : undefined;
436
+ }
437
+ function hasBranchContext(record) {
438
+ const refs = record.data.conditionRefs;
439
+ const contexts = record.data.conditionContexts;
440
+ return (
441
+ (Array.isArray(refs) && refs.length > 0) ||
442
+ (Array.isArray(contexts) && contexts.length > 0)
443
+ );
444
+ }
445
+ function isStaticStringAccessor(call) {
446
+ const name = recordText(call, "targetName");
447
+ if (name !== "getString" && name !== "getText") return false;
448
+ const owner = recordText(call, "targetOwnerQualifiedName") ?? "";
449
+ return /^(?:android\.(?:app\.(?:Activity|Fragment)|content\.(?:Context|ContextWrapper)|content\.res\.Resources)|androidx\.fragment\.app\.Fragment)$/u.test(
450
+ owner,
451
+ );
452
+ }
453
+ function resolveStaticExpression(expression, android, index, seen = new Set()) {
454
+ const constant = nonBlank(expression.constantValue);
455
+ if (constant) {
456
+ return { value: constant, support: [], resourceNames: [] };
457
+ }
458
+ const resource = resourceReferenceOf(expression);
459
+ if (objectText(resource, "resourceType") === "string") {
460
+ if (objectText(resource, "rClassQualifiedName") === "android.R") {
461
+ return undefined;
462
+ }
463
+ const resourceName = objectText(resource, "resourceName");
464
+ return resourceName
465
+ ? resolveSelectedString(android, resourceName)
466
+ : undefined;
467
+ }
468
+ const callRef = objectText(expression, "callRef");
469
+ if (!callRef || seen.has(callRef)) return undefined;
470
+ const call = index.resolvedCallsById.get(callRef);
471
+ if (!call || !isStaticStringAccessor(call) || hasBranchContext(call)) {
472
+ return undefined;
473
+ }
474
+ const bindings = (index.bindingsByCallRef.get(callRef) ?? []).filter(
475
+ (binding) =>
476
+ isResolvedBinding(binding) &&
477
+ recordNumber(binding, "argumentIndex") !== undefined,
478
+ );
479
+ if (bindings.length !== 1 || recordNumber(bindings[0], "argumentIndex") !== 0)
480
+ return undefined;
481
+ const nestedExpression = expressionOf(bindings[0]);
482
+ if (!nestedExpression) return undefined;
483
+ const nested = resolveStaticExpression(
484
+ nestedExpression,
485
+ android,
486
+ index,
487
+ new Set([...seen, callRef]),
488
+ );
489
+ if (!nested) return undefined;
490
+ const callLocation = recordLocation(
491
+ call,
492
+ `area-title:string-accessor:${
493
+ recordText(call, "targetSignature") ??
494
+ recordText(call, "targetName") ??
495
+ call.id
496
+ }`,
497
+ );
498
+ const bindingLocation = recordLocation(
499
+ bindings[0],
500
+ `area-title:string-argument:${call.id}`,
501
+ );
502
+ return {
503
+ ...nested,
504
+ support: uniqueSupport([
505
+ ...nested.support,
506
+ ...(callLocation
507
+ ? [
508
+ {
509
+ location: callLocation,
510
+ role: "structured static string accessor",
511
+ },
512
+ ]
513
+ : []),
514
+ ...(bindingLocation
515
+ ? [
516
+ {
517
+ location: bindingLocation,
518
+ role: "structured static string argument",
519
+ },
520
+ ]
521
+ : []),
522
+ ]),
523
+ };
524
+ }
525
+ function setTextStaticString(call, android, index) {
526
+ const resolutions = (index.bindingsByCallRef.get(call.id) ?? [])
527
+ .filter(
528
+ (binding) =>
529
+ isResolvedBinding(binding) &&
530
+ recordNumber(binding, "argumentIndex") === 0,
531
+ )
532
+ .flatMap((binding) => {
533
+ const expression = expressionOf(binding);
534
+ const resolved = expression
535
+ ? resolveStaticExpression(expression, android, index)
536
+ : undefined;
537
+ if (!resolved) return [];
538
+ const location = recordLocation(
539
+ binding,
540
+ `area-title:setText-argument:${call.id}`,
541
+ );
542
+ return [
543
+ {
544
+ ...resolved,
545
+ support: uniqueSupport([
546
+ ...resolved.support,
547
+ ...(location
548
+ ? [
549
+ {
550
+ location,
551
+ role: "structured static TextView.setText argument",
552
+ },
553
+ ]
554
+ : []),
555
+ ]),
556
+ },
557
+ ];
558
+ });
559
+ const values = uniqueStrings(resolutions.map((item) => item.value));
560
+ if (values.length !== 1) return undefined;
561
+ const matching = resolutions.filter((item) => item.value === values[0]);
562
+ return {
563
+ value: values[0],
564
+ support: uniqueSupport(matching.flatMap((item) => item.support)),
565
+ resourceNames: uniqueStrings(
566
+ matching.flatMap((item) => item.resourceNames),
567
+ ),
568
+ };
569
+ }
570
+ function isTextViewSetText(call) {
571
+ if (recordText(call, "targetName") !== "setText") return false;
572
+ return [
573
+ recordText(call, "targetOwnerQualifiedName"),
574
+ recordText(call, "receiverType"),
575
+ ].some((type) => type !== undefined && isTextViewName(type));
576
+ }
577
+ function jvmTitleCandidates(android, state, index) {
578
+ const candidates = [];
579
+ for (const call of index.resolvedCallsById.values()) {
580
+ if (!isTextViewSetText(call) || hasBranchContext(call)) continue;
581
+ const enclosingClass = recordText(call, "enclosingClass");
582
+ const ownerRef = enclosingClass
583
+ ? state.componentByQualifiedName.get(enclosingClass)
584
+ : undefined;
585
+ if (!ownerRef || !ownerIsArea(state, ownerRef)) continue;
586
+ const resourceId = receiverResourceId(call, index);
587
+ if (!resourceId || !isTitleResourceId(resourceId)) continue;
588
+ const uiFacts = ownerUiTextViews(state, ownerRef, resourceId);
589
+ if (uiFacts.length !== 1) continue;
590
+ const title = setTextStaticString(call, android, index);
591
+ const location = recordLocation(
592
+ call,
593
+ `area-title:setText:${
594
+ recordText(call, "targetSignature") ??
595
+ recordText(call, "source") ??
596
+ call.id
597
+ }`,
598
+ );
599
+ if (!title || !location) continue;
600
+ candidates.push({
601
+ ownerRef,
602
+ value: title.value,
603
+ support: uniqueSupport([
604
+ {
605
+ location,
606
+ role: "structured TextView.setText Area title",
607
+ },
608
+ ...title.support,
609
+ ]),
610
+ relatedFactRefs: uniqueStrings([
611
+ ownerRef,
612
+ uiFacts[0],
613
+ ...resourceFactRefs(state, title.resourceNames),
614
+ ]),
615
+ });
616
+ }
617
+ return candidates;
618
+ }
619
+ function ownersWithExistingTitle(state) {
620
+ return new Set(
621
+ state.builder
622
+ .facts("ui-property")
623
+ .flatMap((fact) =>
624
+ fact.kind === "ui-property" && fact.data.property === "title"
625
+ ? [fact.data.ownerKey]
626
+ : [],
627
+ ),
628
+ );
629
+ }
630
+ /**
631
+ * Adds a title only when all conservative, owner-specific candidates agree.
632
+ * Repeated corroborating evidence for the same static value is safe; two
633
+ * distinct values are ambiguous and intentionally produce no title fact.
634
+ */
635
+ export function normalizeAreaTitleFacts(android, state, index) {
636
+ const candidates = [
637
+ ...navigationLabelCandidates(android, state),
638
+ ...xmlTitleCandidates(android, state),
639
+ ...jvmTitleCandidates(android, state, index),
640
+ ];
641
+ const existing = ownersWithExistingTitle(state);
642
+ const byOwner = new Map();
643
+ for (const candidate of candidates) {
644
+ const list = byOwner.get(candidate.ownerRef) ?? [];
645
+ list.push(candidate);
646
+ byOwner.set(candidate.ownerRef, list);
647
+ }
648
+ for (const [ownerRef, ownerCandidates] of byOwner) {
649
+ if (existing.has(ownerRef)) continue;
650
+ const values = uniqueStrings(
651
+ ownerCandidates.map((candidate) => candidate.value),
652
+ );
653
+ if (values.length !== 1) continue;
654
+ const matching = ownerCandidates.filter(
655
+ (candidate) => candidate.value === values[0],
656
+ );
657
+ const support = uniqueSupport(
658
+ matching.flatMap((candidate) => candidate.support),
659
+ );
660
+ if (support.length === 0) continue;
661
+ state.builder.addFact("ui-property", {
662
+ identity: [
663
+ { kind: "component", value: ownerRef },
664
+ { kind: "semantic-key", value: "title" },
665
+ ],
666
+ confidence: "high",
667
+ support: support,
668
+ data: {
669
+ ownerKey: ownerRef,
670
+ property: "title",
671
+ value: values[0],
672
+ },
673
+ relatedFactRefs: uniqueStrings(
674
+ matching.flatMap((candidate) => candidate.relatedFactRefs),
675
+ ),
676
+ });
677
+ }
678
+ }