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,2008 @@
1
+ import {
2
+ advanceCallSiteStack,
3
+ executionFlowsForward,
4
+ isCallSiteArgumentStep,
5
+ } from "../callback-call-sites.js";
6
+ import { dynamicRepeatedCallSiteContexts } from "./dynamic-repeated-call-sites.js";
7
+ function object(value) {
8
+ return value !== null && typeof value === "object" && !Array.isArray(value)
9
+ ? value
10
+ : undefined;
11
+ }
12
+ function text(record, key) {
13
+ const value = record.data[key];
14
+ return typeof value === "string" && value.trim().length > 0
15
+ ? value.trim()
16
+ : undefined;
17
+ }
18
+ function expression(binding) {
19
+ return object(binding.data.expression) ?? {};
20
+ }
21
+ function expressionText(binding, key) {
22
+ const value = expression(binding)[key];
23
+ return typeof value === "string" && value.trim().length > 0
24
+ ? value.trim()
25
+ : undefined;
26
+ }
27
+ function structuredSymbolKey(summary) {
28
+ const symbol = object(summary?.resolvedSymbol);
29
+ if (!symbol) return undefined;
30
+ const part = (key) =>
31
+ typeof symbol[key] === "string" ? symbol[key].trim() : "";
32
+ const name = part("name");
33
+ const signature = part("signature");
34
+ if (!name && !signature) return undefined;
35
+ const initializer = object(symbol.initializer);
36
+ const declarationIdentity =
37
+ part("declarationRef") ||
38
+ part("declarationId") ||
39
+ (typeof initializer?.callRef === "string"
40
+ ? initializer.callRef.trim()
41
+ : "");
42
+ return [
43
+ part("kind"),
44
+ part("ownerQualifiedName"),
45
+ signature || name,
46
+ part("type"),
47
+ ...(declarationIdentity ? [declarationIdentity] : []),
48
+ ].join("\u0000");
49
+ }
50
+ function stateRecordSymbol(record) {
51
+ if (record.kind === "condition") {
52
+ return structuredSymbolKey(object(record.data.expressionStructure));
53
+ }
54
+ if (record.kind === "assignment") {
55
+ return structuredSymbolKey(object(record.data.left));
56
+ }
57
+ return undefined;
58
+ }
59
+ function stateRecordScope(index, record) {
60
+ const enclosingMethod = text(record, "enclosingMethod");
61
+ if (enclosingMethod) return enclosingMethod;
62
+ const visited = new Set();
63
+ let callableRef = text(record, "enclosingCallableRef");
64
+ while (callableRef && !visited.has(callableRef)) {
65
+ visited.add(callableRef);
66
+ const callable = index.recordsById.get(callableRef);
67
+ if (callable?.kind === "method-declaration") {
68
+ return text(callable, "signature") ?? callable.id;
69
+ }
70
+ callableRef = index.callableParent.get(callableRef);
71
+ }
72
+ return undefined;
73
+ }
74
+ function directBooleanCallGuard(index, call) {
75
+ const rawContexts = call.data.conditionContexts;
76
+ if (!Array.isArray(rawContexts) || rawContexts.length !== 1) {
77
+ return undefined;
78
+ }
79
+ const context = object(rawContexts[0]);
80
+ const conditionRef =
81
+ typeof context?.conditionRef === "string"
82
+ ? context.conditionRef.trim()
83
+ : "";
84
+ if (
85
+ !conditionRef ||
86
+ context?.branchKind !== "then" ||
87
+ context.polarity !== "true" ||
88
+ (Array.isArray(context.caseValues) && context.caseValues.length > 0)
89
+ )
90
+ return undefined;
91
+ const condition = index.recordsById.get(conditionRef);
92
+ const structure = object(condition?.data.expressionStructure);
93
+ const symbol = object(structure?.resolvedSymbol);
94
+ const expressionValue = condition ? text(condition, "expression") : undefined;
95
+ const booleanTypes = new Set([
96
+ "boolean",
97
+ "java.lang.Boolean",
98
+ "kotlin.Boolean",
99
+ ]);
100
+ const isBoolean = [
101
+ condition?.data.expressionType,
102
+ structure?.expressionType,
103
+ symbol?.type,
104
+ ].some(
105
+ (value) => typeof value === "string" && booleanTypes.has(value.trim()),
106
+ );
107
+ const stateSymbol = structuredSymbolKey(structure);
108
+ if (
109
+ !condition ||
110
+ !structure ||
111
+ !symbol ||
112
+ !expressionValue ||
113
+ !isBoolean ||
114
+ !stateSymbol
115
+ )
116
+ return undefined;
117
+ const matches = [...index.recordsById.values()].filter(
118
+ (record) =>
119
+ (record.kind === "condition" || record.kind === "assignment") &&
120
+ stateRecordSymbol(record) === stateSymbol,
121
+ );
122
+ const scopes = new Set(
123
+ matches.map((record) => stateRecordScope(index, record)),
124
+ );
125
+ if (matches.length === 0 || scopes.has(undefined) || scopes.size !== 1) {
126
+ return undefined;
127
+ }
128
+ return { condition, stateSymbol, expression: expressionValue };
129
+ }
130
+ /**
131
+ * Returns the complete structured branch context of one exact local
132
+ * Composable invocation. Unlike surface-state inference this does not try to
133
+ * identify a mutable state slot: the narrower claim is simply that controls
134
+ * materialized for this call site exist only while every enclosing branch
135
+ * guard holds.
136
+ */
137
+ function callSiteVisibilityGuards(index, call, inlineGuardsByCall = new Map()) {
138
+ const inline = inlineGuardsByCall.get(call.id) ?? [];
139
+ const rawContexts = call.data.conditionContexts;
140
+ if (!Array.isArray(rawContexts) || rawContexts.length === 0) return inline;
141
+ const guards = rawContexts.flatMap((rawContext) => {
142
+ const context = object(rawContext);
143
+ const conditionRef =
144
+ typeof context?.conditionRef === "string"
145
+ ? context.conditionRef.trim()
146
+ : "";
147
+ const polarity = context?.polarity;
148
+ const branchKind = context?.branchKind;
149
+ const caseValues = Array.isArray(context?.caseValues)
150
+ ? context.caseValues.filter((value) => typeof value === "string")
151
+ : [];
152
+ if (
153
+ !conditionRef ||
154
+ !["then", "else", "fallthrough", "case"].includes(
155
+ typeof branchKind === "string" ? branchKind : "",
156
+ ) ||
157
+ !["true", "false", "case"].includes(
158
+ typeof polarity === "string" ? polarity : "",
159
+ )
160
+ )
161
+ return [];
162
+ const condition = index.recordsById.get(conditionRef);
163
+ const expressionValue = condition
164
+ ? text(condition, "expression")
165
+ : undefined;
166
+ if (
167
+ !condition ||
168
+ !expressionValue ||
169
+ !object(condition.data.expressionStructure)
170
+ )
171
+ return [];
172
+ const expressionAnchor =
173
+ polarity === "false"
174
+ ? `!(${expressionValue})`
175
+ : polarity === "case"
176
+ ? `${expressionValue} in (${caseValues.join(", ")})`
177
+ : expressionValue;
178
+ const plainLanguage =
179
+ polarity === "false"
180
+ ? `不满足条件:${expressionValue}`
181
+ : polarity === "case"
182
+ ? `条件 ${expressionValue} 匹配:${caseValues.join("、")}`
183
+ : `满足条件:${expressionValue}`;
184
+ return [{ condition, expressionAnchor, plainLanguage }];
185
+ });
186
+ const structured = guards.length === rawContexts.length ? guards : [];
187
+ return [
188
+ ...new Map(
189
+ [...structured, ...inline].map((guard) => [
190
+ `${guard.expressionAnchor}\u0000${guard.condition.id}`,
191
+ guard,
192
+ ]),
193
+ ).values(),
194
+ ];
195
+ }
196
+ function bindingCallableRef(binding) {
197
+ return (
198
+ expressionText(binding, "callableRef") ??
199
+ expressionText(binding, "callableReferenceRef")
200
+ );
201
+ }
202
+ function booleanExpressionBinding(binding) {
203
+ const summary = expression(binding);
204
+ const symbol = object(summary.resolvedSymbol);
205
+ return [summary.expressionType, symbol?.type].some(
206
+ (value) =>
207
+ typeof value === "string" &&
208
+ /(?:^|\.)(?:Boolean|boolean)$/u.test(value.trim()),
209
+ );
210
+ }
211
+ function normalizedExpressionSource(binding) {
212
+ const summary = expression(binding);
213
+ const source =
214
+ typeof summary.source === "string"
215
+ ? summary.source.trim()
216
+ : text(binding, "argumentSource");
217
+ return source && source.length > 0 ? source : undefined;
218
+ }
219
+ function animatedVisibilityBoundary(index, call) {
220
+ if (
221
+ call.kind !== "resolved-call" ||
222
+ sourceApiName(call) !== "AnimatedVisibility" ||
223
+ !(text(call, "targetOwnerQualifiedName") ?? "").startsWith(
224
+ "androidx.compose.animation.",
225
+ )
226
+ )
227
+ return undefined;
228
+ const bindings = bindingsFor(index, call.id);
229
+ const visible = bindings.filter(
230
+ (binding) =>
231
+ !bindingCallableRef(binding) && booleanExpressionBinding(binding),
232
+ );
233
+ const content = bindings.filter(
234
+ (binding) => bindingCallableRef(binding) !== undefined,
235
+ );
236
+ if (visible.length !== 1 || content.length !== 1) return undefined;
237
+ const scopeRef = bindingCallableRef(content[0]);
238
+ const expressionAnchor = normalizedExpressionSource(visible[0]);
239
+ if (!scopeRef || !expressionAnchor) return undefined;
240
+ const symbol = structuredSymbolKey(expression(visible[0]));
241
+ return {
242
+ scopeRef,
243
+ canonicalKey: `${symbol ?? expressionAnchor}\u0000${expressionAnchor}`,
244
+ guard: {
245
+ condition: visible[0],
246
+ expressionAnchor,
247
+ plainLanguage: `仅在 ${expressionAnchor} 为 true 时显示`,
248
+ },
249
+ };
250
+ }
251
+ function enclosingComposable(index, callableRef) {
252
+ const visited = new Set();
253
+ let current = callableRef;
254
+ while (current && !visited.has(current)) {
255
+ visited.add(current);
256
+ const method = index.methodsByCallable.get(current);
257
+ if (method) return method;
258
+ current = index.callableParent.get(current);
259
+ }
260
+ return undefined;
261
+ }
262
+ function sameVisibilityGuards(left, right) {
263
+ return (
264
+ left.length === right.length &&
265
+ left.every(
266
+ (guard, index) => guard.canonicalKey === right[index].canonicalKey,
267
+ )
268
+ );
269
+ }
270
+ /**
271
+ * Propagates an AnimatedVisibility boolean guard through exact local
272
+ * Composable calls only when every runtime invocation of the declaration has
273
+ * the same complete guard set. AnimatedVisibility remains inline UI; this map
274
+ * annotates controls and never creates a Surface/Area.
275
+ */
276
+ function animatedVisibilityGuardsByCall(index) {
277
+ const boundaries = index.calls.flatMap((call) => {
278
+ const boundary = animatedVisibilityBoundary(index, call);
279
+ return boundary ? [boundary] : [];
280
+ });
281
+ const lexicalGuards = (call) =>
282
+ boundaries
283
+ .filter((boundary) =>
284
+ scopeIsWithin(
285
+ index,
286
+ text(call, "enclosingCallableRef"),
287
+ boundary.scopeRef,
288
+ ),
289
+ )
290
+ .sort((left, right) =>
291
+ left.canonicalKey.localeCompare(right.canonicalKey, "en"),
292
+ );
293
+ const inheritedByMethod = new Map();
294
+ const effective = (call) => {
295
+ const method = enclosingComposable(
296
+ index,
297
+ text(call, "enclosingCallableRef"),
298
+ );
299
+ const inherited = method
300
+ ? (inheritedByMethod.get(method.record.id) ?? [])
301
+ : [];
302
+ return [
303
+ ...new Map(
304
+ [...lexicalGuards(call), ...inherited].map((guard) => [
305
+ guard.canonicalKey,
306
+ guard,
307
+ ]),
308
+ ).values(),
309
+ ].sort((left, right) =>
310
+ left.canonicalKey.localeCompare(right.canonicalKey, "en"),
311
+ );
312
+ };
313
+ for (
314
+ let iteration = 0;
315
+ iteration < index.methodsByCallable.size;
316
+ iteration += 1
317
+ ) {
318
+ let changed = false;
319
+ for (const target of index.methodsByCallable.values()) {
320
+ const invocations = index.calls.filter(
321
+ (call) =>
322
+ targetForCall(index, call)?.record.id === target.record.id &&
323
+ !scopeIsWithinPreview(index, text(call, "enclosingCallableRef")),
324
+ );
325
+ if (invocations.length === 0) continue;
326
+ const guards = invocations.map(effective);
327
+ if (
328
+ guards[0].length === 0 ||
329
+ !guards.every((candidate) => sameVisibilityGuards(candidate, guards[0]))
330
+ )
331
+ continue;
332
+ const previous = inheritedByMethod.get(target.record.id) ?? [];
333
+ if (sameVisibilityGuards(previous, guards[0])) continue;
334
+ inheritedByMethod.set(target.record.id, guards[0]);
335
+ changed = true;
336
+ }
337
+ if (!changed) break;
338
+ }
339
+ return new Map(
340
+ index.calls.flatMap((call) => {
341
+ const guards = effective(call);
342
+ return guards.length > 0
343
+ ? [[call.id, guards.map((item) => item.guard)]]
344
+ : [];
345
+ }),
346
+ );
347
+ }
348
+ function sourceApiName(record) {
349
+ return text(record, "targetName")?.replace(/-.*$/u, "");
350
+ }
351
+ function composableQualifiedName(record) {
352
+ const owner = text(record, "ownerQualifiedName");
353
+ const name = text(record, "name");
354
+ return name ? (owner ? `${owner}#${name}` : name) : undefined;
355
+ }
356
+ function isComposableDeclaration(record) {
357
+ if (record.kind !== "method-declaration") return false;
358
+ if (record.data.composable === true) return true;
359
+ return (
360
+ Array.isArray(record.data.annotations) &&
361
+ record.data.annotations.includes("androidx.compose.runtime.Composable")
362
+ );
363
+ }
364
+ function append(map, key, value) {
365
+ const values = map.get(key) ?? [];
366
+ values.push(value);
367
+ map.set(key, values);
368
+ }
369
+ function buildIndex(android, jvm, state) {
370
+ const recordsById = new Map();
371
+ const calls = [];
372
+ const mutableBindings = new Map();
373
+ const callableParent = new Map();
374
+ const methodsBySignature = new Map();
375
+ const methodsByCallable = new Map();
376
+ const mutableByName = new Map();
377
+ for (const record of jvm.body) {
378
+ recordsById.set(record.id, record);
379
+ if (record.kind === "resolved-call") calls.push(record);
380
+ if (record.kind === "call-argument-binding") {
381
+ const callRef = text(record, "callRef");
382
+ if (callRef) append(mutableBindings, callRef, record);
383
+ }
384
+ if (
385
+ record.kind === "method-declaration" ||
386
+ record.kind === "lambda-declaration"
387
+ ) {
388
+ const parent = text(record, "enclosingCallableRef");
389
+ if (parent) callableParent.set(record.id, parent);
390
+ }
391
+ }
392
+ for (const record of jvm.body) {
393
+ if (!isComposableDeclaration(record)) continue;
394
+ const qualifiedName = composableQualifiedName(record);
395
+ const signature = text(record, "signature") ?? qualifiedName;
396
+ const componentRef = qualifiedName
397
+ ? state.componentByQualifiedName.get(qualifiedName)
398
+ : undefined;
399
+ if (!qualifiedName || !signature || !componentRef) continue;
400
+ const method = { record, componentRef, qualifiedName, signature };
401
+ methodsByCallable.set(record.id, method);
402
+ methodsBySignature.set(signature, method);
403
+ const candidates = mutableByName.get(qualifiedName) ?? [];
404
+ candidates.push(method);
405
+ mutableByName.set(qualifiedName, candidates);
406
+ }
407
+ const resourceValues = new Map();
408
+ for (const value of android.values) {
409
+ if (typeof value.value !== "string" || value.value.trim().length === 0) {
410
+ continue;
411
+ }
412
+ const key = `${value.resourceType}/${value.name}`;
413
+ const values = resourceValues.get(key) ?? new Set();
414
+ values.add(value.value);
415
+ resourceValues.set(key, values);
416
+ }
417
+ return {
418
+ recordsById,
419
+ calls: calls.sort((left, right) => left.id.localeCompare(right.id, "en")),
420
+ bindingsByCall: new Map(
421
+ [...mutableBindings].map(([key, values]) => [
422
+ key,
423
+ values.sort((left, right) => left.id.localeCompare(right.id, "en")),
424
+ ]),
425
+ ),
426
+ callableParent,
427
+ methodsByCallable,
428
+ methodsBySignature,
429
+ methodsByQualifiedName: new Map(
430
+ [...mutableByName].map(([key, values]) => [
431
+ key,
432
+ values.sort((left, right) =>
433
+ left.signature.localeCompare(right.signature, "en"),
434
+ ),
435
+ ]),
436
+ ),
437
+ resourceValues: new Map(
438
+ [...resourceValues].map(([key, values]) => [
439
+ key,
440
+ [...values].sort((left, right) => left.localeCompare(right, "en")),
441
+ ]),
442
+ ),
443
+ };
444
+ }
445
+ function targetForCall(index, call) {
446
+ const signature = text(call, "targetSignature");
447
+ const exact = signature ? index.methodsBySignature.get(signature) : undefined;
448
+ if (exact) return exact;
449
+ if (!signature?.includes("androidx.compose.runtime.Composer")) {
450
+ return undefined;
451
+ }
452
+ const owner = text(call, "targetOwnerQualifiedName");
453
+ const name = sourceApiName(call);
454
+ if (!owner || !name) return undefined;
455
+ const candidates = index.methodsByQualifiedName.get(`${owner}#${name}`) ?? [];
456
+ return candidates.length === 1 ? candidates[0] : undefined;
457
+ }
458
+ function bindingsFor(index, callRef) {
459
+ return index.bindingsByCall.get(callRef) ?? [];
460
+ }
461
+ function parameterName(binding) {
462
+ return (
463
+ text(binding, "parameterName") ?? expressionText(binding, "namedArgument")
464
+ );
465
+ }
466
+ function scopeIsWithin(index, candidate, ancestor) {
467
+ const visited = new Set();
468
+ let current = candidate;
469
+ while (current && !visited.has(current)) {
470
+ if (current === ancestor) return true;
471
+ visited.add(current);
472
+ current = index.callableParent.get(current);
473
+ }
474
+ return false;
475
+ }
476
+ const composePreviewAnnotations = new Set([
477
+ "androidx.compose.ui.tooling.preview.Preview",
478
+ "org.jetbrains.compose.ui.tooling.preview.Preview",
479
+ ]);
480
+ function scopeIsWithinPreview(index, candidate) {
481
+ const visited = new Set();
482
+ let current = candidate;
483
+ while (current && !visited.has(current)) {
484
+ visited.add(current);
485
+ const callable = index.recordsById.get(current);
486
+ const annotations = Array.isArray(callable?.data.annotations)
487
+ ? callable.data.annotations
488
+ : [];
489
+ if (
490
+ annotations.some(
491
+ (annotation) =>
492
+ typeof annotation === "string" &&
493
+ composePreviewAnnotations.has(annotation),
494
+ )
495
+ )
496
+ return true;
497
+ current = index.callableParent.get(current);
498
+ }
499
+ return false;
500
+ }
501
+ function reachableComponents(index, root) {
502
+ const methods = new Map([[root.record.id, root]]);
503
+ const pending = [root];
504
+ while (pending.length > 0) {
505
+ const current = pending.shift();
506
+ for (const call of index.calls) {
507
+ if (
508
+ !scopeIsWithin(
509
+ index,
510
+ text(call, "enclosingCallableRef"),
511
+ current.record.id,
512
+ )
513
+ )
514
+ continue;
515
+ const target = targetForCall(index, call);
516
+ if (!target || methods.has(target.record.id)) continue;
517
+ methods.set(target.record.id, target);
518
+ pending.push(target);
519
+ }
520
+ }
521
+ return new Set([...methods.values()].map((method) => method.componentRef));
522
+ }
523
+ function sourceKind(record) {
524
+ const language = text(record, "language");
525
+ if (language === "kotlin" || record.location?.path.endsWith(".kt")) {
526
+ return "kotlin";
527
+ }
528
+ if (language === "java" || record.location?.path.endsWith(".java")) {
529
+ return "java";
530
+ }
531
+ return "other";
532
+ }
533
+ function recordLocation(record, anchor, symbol) {
534
+ if (!record.location) return undefined;
535
+ return {
536
+ sourceKind: sourceKind(record),
537
+ path: record.location.path,
538
+ anchor,
539
+ range: {
540
+ startLine: Math.max(1, record.location.startLine),
541
+ endLine: Math.max(record.location.startLine, record.location.endLine),
542
+ ...(record.location.startColumn >= 1
543
+ ? { startColumn: record.location.startColumn }
544
+ : {}),
545
+ ...(record.location.endColumn >= 1
546
+ ? { endColumn: record.location.endColumn }
547
+ : {}),
548
+ },
549
+ ...(symbol ? { symbol } : {}),
550
+ };
551
+ }
552
+ function resourceValue(index, summary) {
553
+ const reference = object(summary.resourceReference);
554
+ const type = reference?.resourceType;
555
+ const name = reference?.resourceName;
556
+ if (typeof type !== "string" || typeof name !== "string") return undefined;
557
+ const values = index.resourceValues.get(`${type}/${name}`) ?? [];
558
+ return values.length === 1 ? values[0] : undefined;
559
+ }
560
+ function staticLabel(index, binding, visited = new Set()) {
561
+ const summary = expression(binding);
562
+ const constant = summary.constantValue;
563
+ if (typeof constant === "string" && constant.trim().length > 0) {
564
+ return { value: constant, support: [binding] };
565
+ }
566
+ const selected = resourceValue(index, summary);
567
+ if (selected) return { value: selected, support: [binding] };
568
+ const initializer = object(object(summary.resolvedSymbol)?.initializer);
569
+ if (initializer) {
570
+ const value = initializer.constantValue;
571
+ if (typeof value === "string" && value.trim().length > 0) {
572
+ return { value, support: [binding] };
573
+ }
574
+ const initializedResource = resourceValue(index, initializer);
575
+ if (initializedResource) {
576
+ return { value: initializedResource, support: [binding] };
577
+ }
578
+ }
579
+ const nestedRef = expressionText(binding, "callRef");
580
+ if (!nestedRef || visited.has(nestedRef)) return { support: [] };
581
+ const nested = index.recordsById.get(nestedRef);
582
+ if (!nested) return { support: [] };
583
+ const nextVisited = new Set(visited).add(nestedRef);
584
+ const candidates = bindingsFor(index, nestedRef)
585
+ .map((candidate) => staticLabel(index, candidate, nextVisited))
586
+ .filter((candidate) => candidate.value !== undefined);
587
+ const values = [...new Set(candidates.map((candidate) => candidate.value))];
588
+ return values.length === 1
589
+ ? {
590
+ value: values[0],
591
+ support: [
592
+ binding,
593
+ nested,
594
+ ...candidates.flatMap((item) => item.support),
595
+ ],
596
+ }
597
+ : { support: [] };
598
+ }
599
+ function namingRole(name) {
600
+ const normalized = name.replace(/[^A-Za-z0-9]/gu, "").toLowerCase();
601
+ if (["text", "title", "label"].includes(normalized)) return "visibleText";
602
+ if (
603
+ ["contentdescription", "accessibilitylabel", "semanticslabel"].includes(
604
+ normalized,
605
+ )
606
+ )
607
+ return "contentDescription";
608
+ if (["hint", "placeholder"].includes(normalized)) return "associatedLabel";
609
+ return undefined;
610
+ }
611
+ function normalizedSemanticParameterName(name) {
612
+ return name.replace(/[^A-Za-z0-9]/gu, "").toLowerCase();
613
+ }
614
+ function callSiteNaming(
615
+ index,
616
+ call,
617
+ callbackParameterName,
618
+ allowOrdinaryLabel,
619
+ ) {
620
+ const grouped = new Map();
621
+ const callbackSpecific = [];
622
+ const callbackLabel = `${normalizedSemanticParameterName(callbackParameterName)}label`;
623
+ for (const binding of bindingsFor(index, call.id)) {
624
+ const name = parameterName(binding);
625
+ if (!name) continue;
626
+ const normalized = normalizedSemanticParameterName(name);
627
+ if (normalized === callbackLabel) {
628
+ callbackSpecific.push(binding);
629
+ continue;
630
+ }
631
+ const role = allowOrdinaryLabel ? namingRole(name) : undefined;
632
+ if (!role) continue;
633
+ const values = grouped.get(role) ?? [];
634
+ values.push(binding);
635
+ grouped.set(role, values);
636
+ }
637
+ const resolveBindings = (bindings) => {
638
+ const candidates = bindings.map((binding) => ({
639
+ binding,
640
+ resolution: staticLabel(index, binding),
641
+ }));
642
+ const values = [
643
+ ...new Set(
644
+ candidates.flatMap(({ resolution }) =>
645
+ resolution.value ? [resolution.value] : [],
646
+ ),
647
+ ),
648
+ ];
649
+ if (values.length !== 1) return { support: [] };
650
+ return {
651
+ value: values[0],
652
+ support: candidates
653
+ .filter(({ resolution }) => resolution.value === values[0])
654
+ .flatMap(({ binding, resolution }) => [binding, ...resolution.support]),
655
+ };
656
+ };
657
+ const specific = resolveBindings(callbackSpecific);
658
+ if (callbackSpecific.length > 0) {
659
+ return specific.value
660
+ ? {
661
+ contentDescription: specific.value,
662
+ support: [
663
+ ...new Map(
664
+ specific.support.map((record) => [record.id, record]),
665
+ ).values(),
666
+ ],
667
+ }
668
+ : { support: [] };
669
+ }
670
+ const visible = resolveBindings(grouped.get("visibleText") ?? []);
671
+ const description = resolveBindings(grouped.get("contentDescription") ?? []);
672
+ const associated = resolveBindings(grouped.get("associatedLabel") ?? []);
673
+ return {
674
+ ...(visible.value ? { visibleText: visible.value } : {}),
675
+ ...(description.value ? { contentDescription: description.value } : {}),
676
+ ...(associated.value ? { associatedLabel: associated.value } : {}),
677
+ support: [
678
+ ...new Map(
679
+ [...visible.support, ...description.support, ...associated.support].map(
680
+ (record) => [record.id, record],
681
+ ),
682
+ ).values(),
683
+ ],
684
+ };
685
+ }
686
+ function exactParameterReference(summary, parameterName) {
687
+ const symbol = object(summary?.resolvedSymbol);
688
+ return symbol?.kind === "parameter" && symbol.name === parameterName;
689
+ }
690
+ function exactLambdaParameter(record, ordinal) {
691
+ const parameters = Array.isArray(record?.data.parameters)
692
+ ? record.data.parameters
693
+ : [];
694
+ const parameter = object(parameters[ordinal]);
695
+ const name = typeof parameter?.name === "string" ? parameter.name.trim() : "";
696
+ const type = typeof parameter?.type === "string" ? parameter.type.trim() : "";
697
+ return name && type ? { name, type } : undefined;
698
+ }
699
+ function enumEntriesType(summary) {
700
+ const receiver = object(summary?.receiverExpression);
701
+ const receiverSymbol = object(receiver?.resolvedSymbol);
702
+ const qualifiedName = receiverSymbol?.qualifiedName;
703
+ if (
704
+ receiverSymbol?.kind !== "class" ||
705
+ typeof qualifiedName !== "string" ||
706
+ qualifiedName.trim().length === 0
707
+ )
708
+ return undefined;
709
+ const enumType = qualifiedName.trim();
710
+ const selector = object(summary?.selectorExpression);
711
+ const selectorSymbol =
712
+ object(selector?.resolvedSymbol) ?? object(summary?.resolvedSymbol);
713
+ if (
714
+ selectorSymbol?.name === "getEntries" ||
715
+ selectorSymbol?.name === "entries"
716
+ )
717
+ return enumType;
718
+ /*
719
+ * Kotlin UAST can resolve the synthetic `entries` selector back to the enum
720
+ * class instead of its generated getter. Accept that malformed symbol only
721
+ * when the remaining structured facts prove the same operation: the exact
722
+ * class receiver, the single selector token, and EnumEntries of that class.
723
+ * The conjunction keeps this a language-level compatibility path rather
724
+ * than a source-text or product-specific guess.
725
+ */
726
+ const selectorSource =
727
+ typeof selector?.source === "string" ? selector.source.trim() : "";
728
+ const expressionType =
729
+ typeof summary?.expressionType === "string"
730
+ ? summary.expressionType.replace(/\s+/gu, "").trim()
731
+ : "";
732
+ return selectorSource === "entries" &&
733
+ expressionType === `kotlin.enums.EnumEntries<${enumType}>` &&
734
+ selectorSymbol?.kind === "class" &&
735
+ selectorSymbol.qualifiedName === enumType
736
+ ? enumType
737
+ : undefined;
738
+ }
739
+ function locationContains(container, nested) {
740
+ if (
741
+ !container.location ||
742
+ !nested.location ||
743
+ container.location.path !== nested.location.path
744
+ )
745
+ return false;
746
+ return (
747
+ nested.location.startOffset >= container.location.startOffset &&
748
+ nested.location.endOffset <= container.location.endOffset
749
+ );
750
+ }
751
+ function finiteEnumLoopForCall(index, child) {
752
+ const childScope = text(child, "enclosingCallableRef");
753
+ const candidates = index.calls.flatMap((call) => {
754
+ if (
755
+ call.kind !== "resolved-call" ||
756
+ sourceApiName(call) !== "forEach" ||
757
+ !(text(call, "targetOwnerQualifiedName") ?? "").startsWith(
758
+ "kotlin.collections.",
759
+ )
760
+ )
761
+ return [];
762
+ const actionBindings = bindingsFor(index, call.id).filter(
763
+ (binding) =>
764
+ parameterName(binding) === "action" &&
765
+ bindingCallableRef(binding) !== undefined,
766
+ );
767
+ if (actionBindings.length !== 1) return [];
768
+ const actionRef = bindingCallableRef(actionBindings[0]);
769
+ const action = index.recordsById.get(actionRef);
770
+ const parameter = exactLambdaParameter(action, 0);
771
+ const enumType = enumEntriesType(object(call.data.receiverExpression));
772
+ return action &&
773
+ parameter &&
774
+ enumType &&
775
+ parameter.type === enumType &&
776
+ scopeIsWithin(index, childScope, actionRef)
777
+ ? [
778
+ {
779
+ call,
780
+ actionBinding: actionBindings[0],
781
+ action,
782
+ parameter,
783
+ enumType,
784
+ },
785
+ ]
786
+ : [];
787
+ });
788
+ return candidates.length === 1 ? candidates[0] : undefined;
789
+ }
790
+ function caseContextsForCondition(record, conditionRef) {
791
+ const contexts = Array.isArray(record.data.conditionContexts)
792
+ ? record.data.conditionContexts
793
+ : [];
794
+ return contexts.flatMap((value) => {
795
+ const context = object(value);
796
+ return context?.conditionRef === conditionRef ? [context] : [];
797
+ });
798
+ }
799
+ /**
800
+ * Expands one exact enum.entries/forEach invocation only when a source-level
801
+ * `when(loopParameter)` provides a complete set of static, uniquely named
802
+ * case branches for the call-site label. Dynamic/else/multiple-loop shapes
803
+ * remain one unresolved control instead of being guessed.
804
+ */
805
+ function finiteCallSiteNamingVariants(index, call) {
806
+ const loop = finiteEnumLoopForCall(index, call);
807
+ if (!loop) return undefined;
808
+ const labelBindings = bindingsFor(index, call.id).filter((binding) => {
809
+ const name = parameterName(binding);
810
+ return (
811
+ name !== undefined &&
812
+ namingRole(name) === "visibleText" &&
813
+ bindingCallableRef(binding) === undefined
814
+ );
815
+ });
816
+ if (labelBindings.length !== 1) return undefined;
817
+ const labelBinding = labelBindings[0];
818
+ const conditions = [...index.recordsById.values()].filter(
819
+ (record) =>
820
+ record.kind === "condition" &&
821
+ record.data.conditionKind === "switch-or-when" &&
822
+ text(record, "enclosingCallableRef") === loop.action.id &&
823
+ exactParameterReference(
824
+ object(record.data.expressionStructure),
825
+ loop.parameter.name,
826
+ ) &&
827
+ locationContains(labelBinding, record),
828
+ );
829
+ if (conditions.length !== 1) return undefined;
830
+ const condition = conditions[0];
831
+ const caseCalls = index.calls.filter(
832
+ (candidate) =>
833
+ locationContains(labelBinding, candidate) &&
834
+ caseContextsForCondition(candidate, condition.id).length > 0,
835
+ );
836
+ if (caseCalls.length < 2) return undefined;
837
+ const labelsByField = new Map();
838
+ for (const caseCall of caseCalls) {
839
+ const contexts = caseContextsForCondition(caseCall, condition.id);
840
+ if (
841
+ contexts.length !== 1 ||
842
+ contexts[0].branchKind !== "case" ||
843
+ contexts[0].polarity !== "case"
844
+ )
845
+ return undefined;
846
+ const structures = Array.isArray(contexts[0].caseValueStructures)
847
+ ? contexts[0].caseValueStructures.flatMap((value) => {
848
+ const structure = object(value);
849
+ return structure ? [structure] : [];
850
+ })
851
+ : [];
852
+ if (structures.length === 0) return undefined;
853
+ const candidates = bindingsFor(index, caseCall.id)
854
+ .map((binding) => ({ binding, label: staticLabel(index, binding) }))
855
+ .filter((candidate) => candidate.label.value !== undefined);
856
+ const values = [
857
+ ...new Set(candidates.map((candidate) => candidate.label.value)),
858
+ ];
859
+ if (values.length !== 1) return undefined;
860
+ for (const structure of structures) {
861
+ const symbol = object(structure.resolvedSymbol);
862
+ const field = typeof symbol?.name === "string" ? symbol.name.trim() : "";
863
+ if (
864
+ symbol?.kind !== "field" ||
865
+ symbol.ownerQualifiedName !== loop.enumType ||
866
+ !field ||
867
+ labelsByField.has(field)
868
+ )
869
+ return undefined;
870
+ labelsByField.set(field, {
871
+ visibleText: values[0],
872
+ support: [
873
+ loop.call,
874
+ loop.actionBinding,
875
+ condition,
876
+ caseCall,
877
+ labelBinding,
878
+ ...candidates.flatMap((candidate) => [
879
+ candidate.binding,
880
+ ...candidate.label.support,
881
+ ]),
882
+ ],
883
+ });
884
+ }
885
+ }
886
+ const variants = [...labelsByField.values()];
887
+ if (
888
+ variants.length < 2 ||
889
+ variants.length > 64 ||
890
+ new Set(variants.map((variant) => variant.visibleText)).size !==
891
+ variants.length
892
+ )
893
+ return undefined;
894
+ return variants;
895
+ }
896
+ function hasMoreSpecificFiniteInvocation(index, target, templateOwnerRef) {
897
+ return index.calls.some((candidate) => {
898
+ if (
899
+ !scopeIsWithin(
900
+ index,
901
+ text(candidate, "enclosingCallableRef"),
902
+ target.record.id,
903
+ )
904
+ )
905
+ return false;
906
+ const nestedTarget = targetForCall(index, candidate);
907
+ return (
908
+ nestedTarget !== undefined &&
909
+ reachableComponents(index, nestedTarget).has(templateOwnerRef) &&
910
+ finiteCallSiteNamingVariants(index, candidate) !== undefined
911
+ );
912
+ });
913
+ }
914
+ function declaredParameterNames(record) {
915
+ const parameters = Array.isArray(record.data.parameters)
916
+ ? record.data.parameters
917
+ : [];
918
+ return new Set(
919
+ parameters.flatMap((value) => {
920
+ const parameter = object(value);
921
+ const name = parameter?.name;
922
+ return typeof name === "string" && name.trim().length > 0
923
+ ? [name.trim()]
924
+ : [];
925
+ }),
926
+ );
927
+ }
928
+ /**
929
+ * Finds the lexical declaration that owns a referenced parameter. Merely
930
+ * knowing the enclosing method is insufficient: a nested lambda may declare
931
+ * a same-named parameter and must shadow the outer slot.
932
+ */
933
+ function nearestParameterDeclaration(index, callableRef, parameterName) {
934
+ const visited = new Set();
935
+ let current = callableRef;
936
+ while (current && !visited.has(current)) {
937
+ visited.add(current);
938
+ const declaration = index.recordsById.get(current);
939
+ if (!declaration) return undefined;
940
+ if (declaredParameterNames(declaration).has(parameterName)) {
941
+ return declaration;
942
+ }
943
+ current = index.callableParent.get(current);
944
+ }
945
+ return undefined;
946
+ }
947
+ function concreteCallback(state, index, binding) {
948
+ const callable = expressionText(binding, "callableRef");
949
+ if (callable) return state.callbackForRawCallable(callable);
950
+ const referenceRef = expressionText(binding, "callableReferenceRef");
951
+ const reference = referenceRef
952
+ ? index.recordsById.get(referenceRef)
953
+ : undefined;
954
+ const targetCallable = reference
955
+ ? text(reference, "targetCallableRef")
956
+ : undefined;
957
+ if (targetCallable) return state.callbackForRawCallable(targetCallable);
958
+ const signature = reference ? text(reference, "targetSignature") : undefined;
959
+ if (!signature) return undefined;
960
+ const matches = state.builder
961
+ .facts("callback")
962
+ .filter(
963
+ (fact) =>
964
+ fact.kind === "callback" && fact.data.callableSymbol === signature,
965
+ );
966
+ return matches.length === 1 ? matches[0].id : undefined;
967
+ }
968
+ function executionAdjacency(facts, index, callbacks, state) {
969
+ const mutable = new Map();
970
+ const add = (from, step) => {
971
+ const targets = mutable.get(from) ?? new Map();
972
+ const key = [
973
+ step.callbackKey,
974
+ step.flowKind,
975
+ step.parameterName ?? "",
976
+ step.callSiteKey ?? "",
977
+ ].join("\u0000");
978
+ targets.set(key, step);
979
+ mutable.set(from, targets);
980
+ };
981
+ for (const fact of facts) {
982
+ const forward = executionFlowsForward(fact.data.flowKind);
983
+ const from = forward ? fact.data.fromCallbackKey : fact.data.toCallbackKey;
984
+ const to = forward ? fact.data.toCallbackKey : fact.data.fromCallbackKey;
985
+ add(from, {
986
+ callbackKey: to,
987
+ flowKind: fact.data.flowKind,
988
+ ...(fact.data.parameterName
989
+ ? { parameterName: fact.data.parameterName }
990
+ : {}),
991
+ ...(fact.data.callSiteKey ? { callSiteKey: fact.data.callSiteKey } : {}),
992
+ });
993
+ }
994
+ /*
995
+ * Callback facts deliberately share source callable identities, while the
996
+ * JVM invocation normalizer can conservatively reject some `invoke()`
997
+ * records. This compensation is still lexical: it connects only the nearest
998
+ * declaration that actually owns the receiver parameter. A lambda-owned
999
+ * parameter therefore shadows, rather than aliases, an outer formal slot.
1000
+ */
1001
+ for (const call of index.calls) {
1002
+ if (
1003
+ sourceApiName(call) !== "invoke" ||
1004
+ !/(?:^|\.)Function\d+$/u.test(
1005
+ text(call, "targetOwnerQualifiedName") ?? "",
1006
+ )
1007
+ )
1008
+ continue;
1009
+ const receiver = object(call.data.receiverResolvedSymbol);
1010
+ const name = typeof receiver?.name === "string" ? receiver.name.trim() : "";
1011
+ if (receiver?.kind !== "parameter" || name.length === 0) continue;
1012
+ const rawCaller = text(call, "enclosingCallableRef");
1013
+ const caller = rawCaller
1014
+ ? state.callbackForRawCallable(rawCaller)
1015
+ : undefined;
1016
+ if (!caller || !rawCaller) continue;
1017
+ const declaration = nearestParameterDeclaration(index, rawCaller, name);
1018
+ if (!declaration || declaration.kind !== "method-declaration") continue;
1019
+ const signature = text(declaration, "signature");
1020
+ const qualified = composableQualifiedName(declaration);
1021
+ if (!signature && !qualified) continue;
1022
+ const symbols = new Set([
1023
+ ...(signature ? [`${signature}#parameter:${name}`] : []),
1024
+ ...(qualified ? [`${qualified}#parameter:${name}`] : []),
1025
+ ]);
1026
+ for (const formal of callbacks) {
1027
+ if (symbols.has(formal.data.callableSymbol)) {
1028
+ add(caller, {
1029
+ callbackKey: formal.id,
1030
+ flowKind: "listener-invocation",
1031
+ parameterName: name,
1032
+ });
1033
+ }
1034
+ }
1035
+ }
1036
+ return new Map(
1037
+ [...mutable].map(([key, values]) => [
1038
+ key,
1039
+ [...values.values()].sort(
1040
+ (left, right) =>
1041
+ left.callbackKey.localeCompare(right.callbackKey, "en") ||
1042
+ left.flowKind.localeCompare(right.flowKind, "en") ||
1043
+ (left.callSiteKey ?? "").localeCompare(right.callSiteKey ?? "", "en"),
1044
+ ),
1045
+ ]),
1046
+ );
1047
+ }
1048
+ function copyTemplateSemantics(
1049
+ state,
1050
+ templateRef,
1051
+ instanceRef,
1052
+ properties,
1053
+ conditions,
1054
+ location,
1055
+ ) {
1056
+ for (const property of properties) {
1057
+ if (property.data.ownerKey !== templateRef) continue;
1058
+ state.builder.addFact("ui-property", {
1059
+ identity: [
1060
+ { kind: "control", value: instanceRef },
1061
+ { kind: "semantic-key", value: property.data.property },
1062
+ ],
1063
+ confidence: property.confidence,
1064
+ support: [
1065
+ {
1066
+ location,
1067
+ role: "call-site-specialized Compose control property",
1068
+ },
1069
+ ],
1070
+ data: {
1071
+ ...property.data,
1072
+ ownerKey: instanceRef,
1073
+ },
1074
+ relatedFactRefs: [instanceRef],
1075
+ });
1076
+ }
1077
+ for (const condition of conditions) {
1078
+ if (condition.data.ownerKey !== templateRef) continue;
1079
+ state.builder.addFact("condition", {
1080
+ identity: [
1081
+ { kind: "control", value: instanceRef },
1082
+ {
1083
+ kind: "semantic-key",
1084
+ value: condition.data.conditionRole ?? "enabled",
1085
+ },
1086
+ { kind: "semantic-key", value: condition.data.expressionAnchor },
1087
+ ],
1088
+ confidence: condition.confidence,
1089
+ support: [
1090
+ {
1091
+ location,
1092
+ role: "call-site-specialized Compose control condition",
1093
+ },
1094
+ ],
1095
+ data: {
1096
+ ...condition.data,
1097
+ ownerKey: instanceRef,
1098
+ },
1099
+ relatedFactRefs: [instanceRef],
1100
+ });
1101
+ }
1102
+ }
1103
+ function specializeCallSiteSurfaces(
1104
+ state,
1105
+ index,
1106
+ call,
1107
+ target,
1108
+ actions,
1109
+ directSurfaceCount,
1110
+ componentById,
1111
+ properties,
1112
+ conditions,
1113
+ location,
1114
+ ) {
1115
+ const instances = new Map();
1116
+ const templateRefs = [
1117
+ ...new Set(
1118
+ actions.flatMap((action) =>
1119
+ action.surfaceTemplateRef ? [action.surfaceTemplateRef] : [],
1120
+ ),
1121
+ ),
1122
+ ].sort((left, right) => left.localeCompare(right, "en"));
1123
+ // One guard controlling multiple distinct surfaces does not identify which
1124
+ // surface the state activates. Preserve that ambiguity instead of adding
1125
+ // the same activation to every target.
1126
+ const directGuard =
1127
+ templateRefs.length === 1 && directSurfaceCount === 1
1128
+ ? directBooleanCallGuard(index, call)
1129
+ : undefined;
1130
+ for (const templateRef of templateRefs) {
1131
+ const template = componentById.get(templateRef);
1132
+ if (!template) continue;
1133
+ const hostRefs = [
1134
+ ...new Set(
1135
+ actions
1136
+ .filter((action) => action.surfaceTemplateRef === templateRef)
1137
+ .map((action) => action.actualFact.data.ownerKey),
1138
+ ),
1139
+ ];
1140
+ if (hostRefs.length !== 1) continue;
1141
+ const [hostRef] = hostRefs;
1142
+ const host = componentById.get(hostRef);
1143
+ const anchor = state.builder
1144
+ .stableId("fact", "compose-call-site-surface-anchor", [
1145
+ { kind: "component", value: hostRef },
1146
+ { kind: "component", value: templateRef },
1147
+ { kind: "symbol", value: target.signature },
1148
+ // Source text is not a call-site identity: two invocations can be
1149
+ // byte-for-byte identical. The JVM extractor record id includes the
1150
+ // deterministic source path/range and therefore keeps those physical
1151
+ // invocations as distinct surface instances.
1152
+ { kind: "semantic-key", value: call.id },
1153
+ ])
1154
+ .split("/")
1155
+ .at(-1);
1156
+ const qualifiedName =
1157
+ `${host?.data.qualifiedName ?? hostRef}` +
1158
+ `#surface-call:${template.data.componentType}:${anchor}`;
1159
+ const instanceRef = state.ensureComponent(
1160
+ qualifiedName,
1161
+ template.data.componentType,
1162
+ location,
1163
+ "exact invocation of a reusable Compose surface",
1164
+ "high",
1165
+ {
1166
+ uiTechnology: template.data.uiTechnology ?? "compose",
1167
+ analysisRole: "call-site-instance",
1168
+ },
1169
+ );
1170
+ state.builder.mergeFactData("component", templateRef, {
1171
+ analysisRole: "declaration-template",
1172
+ });
1173
+ state.builder.addFact("container-binding", {
1174
+ identity: [
1175
+ { kind: "component", value: hostRef },
1176
+ { kind: "container", value: "surface" },
1177
+ { kind: "component", value: instanceRef },
1178
+ ],
1179
+ confidence: "high",
1180
+ support: [
1181
+ {
1182
+ location,
1183
+ role: "call-site-specialized Compose surface ownership",
1184
+ },
1185
+ ],
1186
+ data: {
1187
+ bindingKind: "surface",
1188
+ hostKey: hostRef,
1189
+ contentKeys: [instanceRef],
1190
+ },
1191
+ relatedFactRefs: [hostRef, instanceRef],
1192
+ });
1193
+ const naming = callSiteNaming(index, call, "__surface", true);
1194
+ if (naming.visibleText) {
1195
+ state.builder.addFact("ui-property", {
1196
+ identity: [
1197
+ { kind: "component", value: instanceRef },
1198
+ { kind: "semantic-key", value: "title" },
1199
+ ],
1200
+ confidence: "high",
1201
+ support: [
1202
+ {
1203
+ location,
1204
+ role: "statically resolved call-site surface title",
1205
+ },
1206
+ ],
1207
+ data: {
1208
+ ownerKey: instanceRef,
1209
+ property: "title",
1210
+ value: naming.visibleText,
1211
+ },
1212
+ relatedFactRefs: [instanceRef],
1213
+ });
1214
+ } else {
1215
+ for (const property of properties) {
1216
+ if (property.data.ownerKey !== templateRef) continue;
1217
+ state.builder.addFact("ui-property", {
1218
+ identity: [
1219
+ { kind: "component", value: instanceRef },
1220
+ { kind: "semantic-key", value: property.data.property },
1221
+ ],
1222
+ confidence: property.confidence,
1223
+ support: [
1224
+ {
1225
+ location,
1226
+ role: "call-site-specialized Compose surface property",
1227
+ },
1228
+ ],
1229
+ data: { ...property.data, ownerKey: instanceRef },
1230
+ relatedFactRefs: [instanceRef],
1231
+ });
1232
+ }
1233
+ }
1234
+ for (const condition of conditions) {
1235
+ if (condition.data.ownerKey !== templateRef) continue;
1236
+ state.builder.addFact("condition", {
1237
+ identity: [
1238
+ { kind: "component", value: instanceRef },
1239
+ { kind: "semantic-key", value: condition.data.expressionAnchor },
1240
+ ],
1241
+ confidence: condition.confidence,
1242
+ support: [
1243
+ {
1244
+ location,
1245
+ role: "structured condition guarding exact local @Composable surface invocation",
1246
+ },
1247
+ ],
1248
+ data: { ...condition.data, ownerKey: instanceRef },
1249
+ relatedFactRefs: [instanceRef],
1250
+ });
1251
+ }
1252
+ if (directGuard) {
1253
+ const guardLocation =
1254
+ recordLocation(
1255
+ directGuard.condition,
1256
+ `compose-call-site-surface-condition:${call.id}`,
1257
+ target.signature,
1258
+ ) ?? location;
1259
+ state.builder.addFact("condition", {
1260
+ identity: [
1261
+ { kind: "component", value: instanceRef },
1262
+ { kind: "semantic-key", value: directGuard.expression },
1263
+ ],
1264
+ confidence: "high",
1265
+ support: [
1266
+ {
1267
+ location: guardLocation,
1268
+ role: "direct positive boolean guard at exact surface invocation",
1269
+ },
1270
+ ],
1271
+ data: {
1272
+ ownerKey: instanceRef,
1273
+ expressionAnchor: directGuard.expression,
1274
+ plainLanguage: `满足条件:${directGuard.expression}`,
1275
+ valueKind: "runtime",
1276
+ },
1277
+ relatedFactRefs: [instanceRef],
1278
+ });
1279
+ state.registerConditionalSurfaceState(
1280
+ directGuard.stateSymbol,
1281
+ instanceRef,
1282
+ "boolean-true",
1283
+ );
1284
+ }
1285
+ for (const [
1286
+ stateSymbol,
1287
+ surfaces,
1288
+ ] of state.conditionalSurfacesByStateSymbol) {
1289
+ const activation = surfaces.get(templateRef);
1290
+ if (activation) {
1291
+ state.registerConditionalSurfaceState(
1292
+ stateSymbol,
1293
+ instanceRef,
1294
+ activation,
1295
+ );
1296
+ }
1297
+ }
1298
+ instances.set(templateRef, instanceRef);
1299
+ }
1300
+ return instances;
1301
+ }
1302
+ function reachesAny(adjacency, root, targets) {
1303
+ const stateKey = (state) =>
1304
+ [state.callbackKey, state.callSiteStack.join("\u0000")].join("\u0001");
1305
+ const initial = {
1306
+ callbackKey: root,
1307
+ callbackPath: [root],
1308
+ callSiteStack: [],
1309
+ };
1310
+ const visited = new Set([stateKey(initial)]);
1311
+ const pending = [initial];
1312
+ while (pending.length > 0) {
1313
+ const current = pending.shift();
1314
+ if (targets.has(current.callbackKey)) return true;
1315
+ const steps = adjacency.get(current.callbackKey) ?? [];
1316
+ const reverseArgumentCandidateCount = new Set(
1317
+ steps.filter(isCallSiteArgumentStep).map((step) => step.callbackKey),
1318
+ ).size;
1319
+ for (const step of steps) {
1320
+ if (current.callbackPath.includes(step.callbackKey)) continue;
1321
+ const transition = advanceCallSiteStack(
1322
+ current.callSiteStack,
1323
+ step,
1324
+ reverseArgumentCandidateCount,
1325
+ );
1326
+ if (!transition.accepted) continue;
1327
+ const next = {
1328
+ callbackKey: step.callbackKey,
1329
+ callbackPath: [...current.callbackPath, step.callbackKey],
1330
+ callSiteStack: transition.callSiteStack,
1331
+ };
1332
+ const key = stateKey(next);
1333
+ if (visited.has(key)) continue;
1334
+ visited.add(key);
1335
+ pending.push(next);
1336
+ }
1337
+ }
1338
+ return false;
1339
+ }
1340
+ function formalCallbacks(callbacks, target, parameter) {
1341
+ const symbols = new Set([
1342
+ `${target.qualifiedName}#parameter:${parameter}`,
1343
+ `${target.signature}#parameter:${parameter}`,
1344
+ ]);
1345
+ return new Set(
1346
+ callbacks.flatMap((callback) =>
1347
+ callback.data.parameterName === parameter &&
1348
+ symbols.has(callback.data.callableSymbol)
1349
+ ? [callback.id]
1350
+ : [],
1351
+ ),
1352
+ );
1353
+ }
1354
+ function hasFunctionalFormalParameter(target) {
1355
+ const parameters = Array.isArray(target.record.data.parameters)
1356
+ ? target.record.data.parameters
1357
+ : [];
1358
+ return parameters.some((value) => {
1359
+ const parameter = object(value);
1360
+ const type = typeof parameter?.type === "string" ? parameter.type : "";
1361
+ return /(?:kotlin\.jvm\.functions\.Function|->|java\.util\.function\.)/u.test(
1362
+ type,
1363
+ );
1364
+ });
1365
+ }
1366
+ function enclosingComposableForCall(index, call) {
1367
+ const visited = new Set();
1368
+ let current = text(call, "enclosingCallableRef");
1369
+ while (current && !visited.has(current)) {
1370
+ visited.add(current);
1371
+ const method = index.methodsByCallable.get(current);
1372
+ if (method) return method;
1373
+ current = index.callableParent.get(current);
1374
+ }
1375
+ return undefined;
1376
+ }
1377
+ /**
1378
+ * A self-contained reusable Composable can own an internal action without an
1379
+ * `onX` formal parameter (for example, an icon that reads shared state and
1380
+ * opens a drawer). When that declaration is invoked from several pages, its
1381
+ * declaration component has several parents and cannot itself be assigned to
1382
+ * one Area. Clone only statically named, directly-owned action controls onto
1383
+ * each exact caller. Reusing the internal callback is correct because its
1384
+ * implementation and effect are declaration-invariant; the call record is
1385
+ * used solely to prove the visual owner for each instance.
1386
+ */
1387
+ function specializeSelfContainedReusableControls(
1388
+ state,
1389
+ index,
1390
+ inlineVisibilityGuards,
1391
+ ) {
1392
+ const elements = state.builder
1393
+ .facts("ui-element")
1394
+ .filter((fact) => fact.kind === "ui-element");
1395
+ const interactions = state.builder
1396
+ .facts("interaction-binding")
1397
+ .filter((fact) => fact.kind === "interaction-binding");
1398
+ const properties = state.builder
1399
+ .facts("ui-property")
1400
+ .filter((fact) => fact.kind === "ui-property");
1401
+ const conditions = state.builder
1402
+ .facts("condition")
1403
+ .filter((fact) => fact.kind === "condition");
1404
+ const callsByTarget = new Map();
1405
+ for (const call of index.calls) {
1406
+ const target = targetForCall(index, call);
1407
+ if (!target || hasFunctionalFormalParameter(target)) continue;
1408
+ const entry = callsByTarget.get(target.componentRef) ?? {
1409
+ target,
1410
+ calls: [],
1411
+ };
1412
+ entry.calls.push(call);
1413
+ callsByTarget.set(target.componentRef, entry);
1414
+ }
1415
+ for (const { target, calls } of callsByTarget.values()) {
1416
+ if (calls.length < 2) continue;
1417
+ const templates = elements.filter(
1418
+ (element) =>
1419
+ element.data.ownerKey === target.componentRef &&
1420
+ element.data.actionable &&
1421
+ element.data.analysisRole !== "call-site-instance" &&
1422
+ Boolean(
1423
+ element.data.visibleText ??
1424
+ element.data.contentDescription ??
1425
+ element.data.associatedLabel,
1426
+ ) &&
1427
+ interactions.some(
1428
+ (interaction) => interaction.data.elementKey === element.id,
1429
+ ),
1430
+ );
1431
+ for (const template of templates) {
1432
+ const templateInteractions = interactions.filter(
1433
+ (interaction) => interaction.data.elementKey === template.id,
1434
+ );
1435
+ for (const call of calls) {
1436
+ const caller = enclosingComposableForCall(index, call);
1437
+ const location = recordLocation(
1438
+ call,
1439
+ `compose-self-contained-call-site:${target.signature}:${call.id}`,
1440
+ target.signature,
1441
+ );
1442
+ if (
1443
+ !caller ||
1444
+ caller.componentRef === target.componentRef ||
1445
+ !location
1446
+ ) {
1447
+ continue;
1448
+ }
1449
+ const elementRef = state.builder.addFact("ui-element", {
1450
+ identity: [
1451
+ { kind: "component", value: caller.componentRef },
1452
+ { kind: "symbol", value: target.signature },
1453
+ {
1454
+ kind: "control",
1455
+ value: `self-contained-call-site:${call.id}:${template.id}`,
1456
+ },
1457
+ ],
1458
+ confidence: template.confidence,
1459
+ support: [
1460
+ {
1461
+ location,
1462
+ role: "exact invocation of a self-contained reusable Composable",
1463
+ },
1464
+ ],
1465
+ data: {
1466
+ ...template.data,
1467
+ ownerKey: caller.componentRef,
1468
+ analysisRole: "call-site-instance",
1469
+ },
1470
+ relatedFactRefs: [
1471
+ caller.componentRef,
1472
+ ...templateInteractions.map(
1473
+ (interaction) => interaction.data.callbackKey,
1474
+ ),
1475
+ ],
1476
+ });
1477
+ state.uiOwnerById.set(elementRef, caller.componentRef);
1478
+ copyTemplateSemantics(
1479
+ state,
1480
+ template.id,
1481
+ elementRef,
1482
+ properties,
1483
+ conditions,
1484
+ location,
1485
+ );
1486
+ for (const guard of callSiteVisibilityGuards(
1487
+ index,
1488
+ call,
1489
+ inlineVisibilityGuards,
1490
+ )) {
1491
+ const guardLocation =
1492
+ recordLocation(
1493
+ guard.condition,
1494
+ `compose-self-contained-visibility:${guard.expressionAnchor}`,
1495
+ target.signature,
1496
+ ) ?? location;
1497
+ state.builder.addFact("condition", {
1498
+ identity: [
1499
+ { kind: "control", value: elementRef },
1500
+ { kind: "semantic-key", value: "visibility" },
1501
+ { kind: "semantic-key", value: guard.expressionAnchor },
1502
+ ],
1503
+ confidence: "high",
1504
+ support: [
1505
+ {
1506
+ location: guardLocation,
1507
+ role: "structured call-site visibility guard",
1508
+ },
1509
+ ],
1510
+ data: {
1511
+ ownerKey: elementRef,
1512
+ expressionAnchor: guard.expressionAnchor,
1513
+ plainLanguage: guard.plainLanguage,
1514
+ valueKind: "runtime",
1515
+ conditionRole: "visibility",
1516
+ pathRequirement: "runtime-availability",
1517
+ },
1518
+ relatedFactRefs: [elementRef],
1519
+ });
1520
+ }
1521
+ for (const interaction of templateInteractions) {
1522
+ state.builder.addFact("interaction-binding", {
1523
+ identity: [
1524
+ { kind: "control", value: elementRef },
1525
+ { kind: "callback", value: interaction.data.callbackKey },
1526
+ { kind: "semantic-key", value: interaction.data.trigger },
1527
+ ],
1528
+ confidence: interaction.confidence,
1529
+ support: [
1530
+ {
1531
+ location,
1532
+ role: "self-contained call-site Compose interaction",
1533
+ },
1534
+ ],
1535
+ data: {
1536
+ ...interaction.data,
1537
+ elementKey: elementRef,
1538
+ },
1539
+ relatedFactRefs: [elementRef, interaction.data.callbackKey],
1540
+ });
1541
+ }
1542
+ }
1543
+ state.builder.mergeFactData("ui-element", template.id, {
1544
+ analysisRole: "declaration-template",
1545
+ });
1546
+ }
1547
+ }
1548
+ }
1549
+ /**
1550
+ * Materializes one product control per exact invocation of a reusable local
1551
+ * Composable. A declaration-level control is converted to a provenance-only
1552
+ * template only after an actual callback and a proven implementation path to
1553
+ * the matching formal callback both exist.
1554
+ */
1555
+ export function normalizeComposeCallSiteFacts(android, jvm, state) {
1556
+ if (!jvm) return;
1557
+ const index = buildIndex(android, jvm, state);
1558
+ const repeatedCallSites = dynamicRepeatedCallSiteContexts(jvm);
1559
+ const inlineVisibilityGuards = animatedVisibilityGuardsByCall(index);
1560
+ specializeSelfContainedReusableControls(state, index, inlineVisibilityGuards);
1561
+ const callbacks = state.builder
1562
+ .facts("callback")
1563
+ .filter((fact) => fact.kind === "callback");
1564
+ const flows = state.builder
1565
+ .facts("callback-flow")
1566
+ .filter((fact) => fact.kind === "callback-flow");
1567
+ const surfaceTypes = new Set([
1568
+ "dialog",
1569
+ "drawer",
1570
+ "bottom-sheet",
1571
+ "overlay",
1572
+ "popup",
1573
+ "inline-detail-surface",
1574
+ ]);
1575
+ const specializedSurfaceTemplates = new Set();
1576
+ const adjacency = executionAdjacency(flows, index, callbacks, state);
1577
+ // Pass 1 materializes reusable child controls inside declaration-level
1578
+ // surfaces. Pass 2 can then clone that exact child set when the reusable
1579
+ // surface itself is invoked. Stable fact identities make the ordinary
1580
+ // controls observed in both passes idempotent.
1581
+ for (let pass = 0; pass < 2; pass += 1) {
1582
+ const uiElements = state.builder
1583
+ .facts("ui-element")
1584
+ .filter((fact) => fact.kind === "ui-element");
1585
+ const interactions = state.builder
1586
+ .facts("interaction-binding")
1587
+ .filter((fact) => fact.kind === "interaction-binding");
1588
+ const properties = state.builder
1589
+ .facts("ui-property")
1590
+ .filter((fact) => fact.kind === "ui-property");
1591
+ const conditions = state.builder
1592
+ .facts("condition")
1593
+ .filter((fact) => fact.kind === "condition");
1594
+ const uiById = new Map(uiElements.map((fact) => [fact.id, fact]));
1595
+ const componentById = new Map(
1596
+ state.builder
1597
+ .facts("component")
1598
+ .filter((fact) => fact.kind === "component")
1599
+ .map((fact) => [fact.id, fact]),
1600
+ );
1601
+ const surfaceHostByComponent = new Map();
1602
+ for (const binding of state.builder
1603
+ .facts("container-binding")
1604
+ .filter((fact) => fact.kind === "container-binding")) {
1605
+ if (binding.data.bindingKind !== "surface") continue;
1606
+ for (const contentRef of binding.data.contentKeys) {
1607
+ surfaceHostByComponent.set(contentRef, binding.data.hostKey);
1608
+ }
1609
+ }
1610
+ for (const call of index.calls) {
1611
+ if (scopeIsWithinPreview(index, text(call, "enclosingCallableRef")))
1612
+ continue;
1613
+ const target = targetForCall(index, call);
1614
+ if (!target) continue;
1615
+ const repeatedCallSite = repeatedCallSites.get(call.id);
1616
+ if (repeatedCallSite?.population === "impossible") continue;
1617
+ const location = recordLocation(
1618
+ call,
1619
+ `compose-call-site-control:${target.signature}:${call.id}`,
1620
+ target.signature,
1621
+ );
1622
+ if (!location) continue;
1623
+ const components = reachableComponents(index, target);
1624
+ const actions = [];
1625
+ for (const actualBinding of bindingsFor(index, call.id)) {
1626
+ const name = parameterName(actualBinding);
1627
+ if (!name || !/^on[A-Z][A-Za-z0-9]*$/u.test(name)) continue;
1628
+ const actualCallback = concreteCallback(state, index, actualBinding);
1629
+ if (!actualCallback) continue;
1630
+ const actualFact = state.builder.fact(actualCallback);
1631
+ if (!actualFact || actualFact.kind !== "callback") continue;
1632
+ const formals = formalCallbacks(callbacks, target, name);
1633
+ if (formals.size === 0) continue;
1634
+ const matchingTemplates = interactions.filter((interaction) => {
1635
+ const element = uiById.get(interaction.data.elementKey);
1636
+ const surfaceHost = element
1637
+ ? surfaceHostByComponent.get(element.data.ownerKey)
1638
+ : undefined;
1639
+ const elementOwner = element
1640
+ ? componentById.get(element.data.ownerKey)
1641
+ : undefined;
1642
+ const elementIsSurfaceOwned =
1643
+ elementOwner !== undefined &&
1644
+ surfaceTypes.has(elementOwner.data.componentType);
1645
+ const directlyOwnedSurfaceControl =
1646
+ elementIsSurfaceOwned && surfaceHost === target.componentRef;
1647
+ const eligibleTemplate =
1648
+ element !== undefined &&
1649
+ (element.data.analysisRole === "call-site-instance"
1650
+ ? // Pass 2 may use a child control materialized inside this exact
1651
+ // reusable surface, but never a final instance from its caller.
1652
+ directlyOwnedSurfaceControl
1653
+ : directlyOwnedSurfaceControl ||
1654
+ (!elementIsSurfaceOwned &&
1655
+ components.has(element.data.ownerKey)));
1656
+ return (
1657
+ eligibleTemplate &&
1658
+ reachesAny(adjacency, interaction.data.callbackKey, formals)
1659
+ );
1660
+ });
1661
+ const directSurfaceTemplates = matchingTemplates.filter(
1662
+ (interaction) => {
1663
+ const element = uiById.get(interaction.data.elementKey);
1664
+ return (
1665
+ element !== undefined &&
1666
+ surfaceTypes.has(
1667
+ componentById.get(element.data.ownerKey)?.data.componentType ??
1668
+ "other",
1669
+ ) &&
1670
+ surfaceHostByComponent.get(element.data.ownerKey) ===
1671
+ target.componentRef
1672
+ );
1673
+ },
1674
+ );
1675
+ // Once exact children have been materialized within the surface, the
1676
+ // lower-level declaration controls are only implementation templates.
1677
+ const templates =
1678
+ directSurfaceTemplates.length > 0
1679
+ ? directSurfaceTemplates
1680
+ : matchingTemplates;
1681
+ actions.push(
1682
+ ...templates.map((templateBinding) => {
1683
+ const template = uiById.get(templateBinding.data.elementKey);
1684
+ const templateOwner = componentById.get(template.data.ownerKey);
1685
+ const surfaceTemplateRef =
1686
+ templateOwner &&
1687
+ surfaceTypes.has(templateOwner.data.componentType)
1688
+ ? template.data.ownerKey
1689
+ : undefined;
1690
+ const instanceOwnerRef =
1691
+ surfaceTemplateRef ?? actualFact.data.ownerKey;
1692
+ return {
1693
+ parameterName: name,
1694
+ actualCallback,
1695
+ actualFact,
1696
+ instanceOwnerRef,
1697
+ ...(surfaceTemplateRef ? { surfaceTemplateRef } : {}),
1698
+ templateBinding,
1699
+ template,
1700
+ };
1701
+ }),
1702
+ );
1703
+ }
1704
+ const mostSpecificActions = actions.filter(
1705
+ (action) =>
1706
+ action.surfaceTemplateRef !== undefined ||
1707
+ !hasMoreSpecificFiniteInvocation(
1708
+ index,
1709
+ target,
1710
+ action.template.data.ownerKey,
1711
+ ),
1712
+ );
1713
+ /*
1714
+ * A callback parameter is an event slot, not a control identity. Group
1715
+ * every event that reaches the same declaration-level UI element before
1716
+ * materialising the call-site instance. Distinct template elements stay
1717
+ * distinct even when they share one callback; one combinedClickable or
1718
+ * slider stays one element even when it exposes multiple event slots.
1719
+ */
1720
+ const surfaceInstances = specializeCallSiteSurfaces(
1721
+ state,
1722
+ index,
1723
+ call,
1724
+ target,
1725
+ mostSpecificActions,
1726
+ new Set(
1727
+ [...surfaceHostByComponent]
1728
+ .filter(([, hostRef]) => hostRef === target.componentRef)
1729
+ .map(([surfaceRef]) => surfaceRef),
1730
+ ).size,
1731
+ componentById,
1732
+ properties,
1733
+ conditions,
1734
+ location,
1735
+ );
1736
+ const materializedActions = mostSpecificActions.map((action) => ({
1737
+ ...action,
1738
+ instanceOwnerRef: action.surfaceTemplateRef
1739
+ ? (surfaceInstances.get(action.surfaceTemplateRef) ??
1740
+ action.instanceOwnerRef)
1741
+ : action.instanceOwnerRef,
1742
+ }));
1743
+ for (const templateRef of surfaceInstances.keys()) {
1744
+ specializedSurfaceTemplates.add(templateRef);
1745
+ }
1746
+ const groupedActions = new Map();
1747
+ for (const action of materializedActions) {
1748
+ const key = `${action.instanceOwnerRef}\u0000${action.template.id}`;
1749
+ const grouped = groupedActions.get(key) ?? [];
1750
+ grouped.push(action);
1751
+ groupedActions.set(key, grouped);
1752
+ }
1753
+ const groups = [...groupedActions.values()].sort(
1754
+ (left, right) =>
1755
+ left[0].template.id.localeCompare(right[0].template.id, "en") ||
1756
+ left[0].instanceOwnerRef.localeCompare(
1757
+ right[0].instanceOwnerRef,
1758
+ "en",
1759
+ ),
1760
+ );
1761
+ const ordinaryLabelIsSafe = groups.length === 1;
1762
+ for (const group of groups) {
1763
+ const { actualFact, instanceOwnerRef, template } = group[0];
1764
+ const fallbackNaming =
1765
+ group.length === 1
1766
+ ? callSiteNaming(
1767
+ index,
1768
+ call,
1769
+ group[0].parameterName,
1770
+ ordinaryLabelIsSafe,
1771
+ )
1772
+ : ordinaryLabelIsSafe
1773
+ ? callSiteNaming(index, call, "__sharedControl", true)
1774
+ : { support: [] };
1775
+ const finiteVariants =
1776
+ group.length === 1
1777
+ ? finiteCallSiteNamingVariants(index, call)
1778
+ : undefined;
1779
+ const namingVariants = finiteVariants ?? [fallbackNaming];
1780
+ for (const [variantIndex, naming] of namingVariants.entries()) {
1781
+ const instanceVisibleText =
1782
+ naming.visibleText ?? template.data.visibleText;
1783
+ const instanceContentDescription =
1784
+ naming.contentDescription ?? template.data.contentDescription;
1785
+ const instanceAssociatedLabel =
1786
+ naming.associatedLabel ?? template.data.associatedLabel;
1787
+ const semanticName =
1788
+ instanceVisibleText ??
1789
+ instanceContentDescription ??
1790
+ instanceAssociatedLabel;
1791
+ const elementRef = state.builder.addFact("ui-element", {
1792
+ identity: [
1793
+ { kind: "component", value: instanceOwnerRef },
1794
+ { kind: "symbol", value: target.signature },
1795
+ {
1796
+ kind: "control",
1797
+ value:
1798
+ `call-site:${call.id}:${template.id}` +
1799
+ (namingVariants.length > 1
1800
+ ? `:finite:${variantIndex}:${instanceVisibleText ?? ""}`
1801
+ : ""),
1802
+ },
1803
+ ],
1804
+ confidence: semanticName ? "high" : template.confidence,
1805
+ support: [
1806
+ {
1807
+ location,
1808
+ role: "exact local @Composable control invocation",
1809
+ },
1810
+ ...naming.support.flatMap((record) => {
1811
+ const supportLocation = recordLocation(
1812
+ record,
1813
+ `compose-call-site-label:${record.id}`,
1814
+ target.signature,
1815
+ );
1816
+ return supportLocation
1817
+ ? [
1818
+ {
1819
+ location: supportLocation,
1820
+ role: "statically resolved call-site naming argument",
1821
+ },
1822
+ ]
1823
+ : [];
1824
+ }),
1825
+ ...(repeatedCallSite?.support ?? []).flatMap((record) => {
1826
+ const repeatedLocation = recordLocation(
1827
+ record,
1828
+ `compose-call-site-repeated:${record.id}`,
1829
+ target.signature,
1830
+ );
1831
+ return repeatedLocation
1832
+ ? [
1833
+ {
1834
+ location: repeatedLocation,
1835
+ role: "resolved Kotlin collection iteration provenance",
1836
+ },
1837
+ ]
1838
+ : [];
1839
+ }),
1840
+ ],
1841
+ data: {
1842
+ ownerKey: instanceOwnerRef,
1843
+ elementType: repeatedCallSite
1844
+ ? "list-item"
1845
+ : template.data.elementType,
1846
+ actionable: template.data.actionable,
1847
+ analysisRole: "call-site-instance",
1848
+ ...(template.data.resourceId
1849
+ ? { resourceId: template.data.resourceId }
1850
+ : {}),
1851
+ ...(template.data.symbol ? { symbol: template.data.symbol } : {}),
1852
+ ...(instanceVisibleText
1853
+ ? { visibleText: instanceVisibleText }
1854
+ : {}),
1855
+ ...(instanceContentDescription
1856
+ ? { contentDescription: instanceContentDescription }
1857
+ : {}),
1858
+ ...(instanceAssociatedLabel
1859
+ ? { associatedLabel: instanceAssociatedLabel }
1860
+ : {}),
1861
+ ...(template.data.iconResource
1862
+ ? { iconResource: template.data.iconResource }
1863
+ : {}),
1864
+ ...(!semanticName && template.data.dynamicIdentity
1865
+ ? { dynamicIdentity: template.data.dynamicIdentity }
1866
+ : {}),
1867
+ },
1868
+ relatedFactRefs: [
1869
+ instanceOwnerRef,
1870
+ ...new Set(group.map((action) => action.actualCallback)),
1871
+ ],
1872
+ });
1873
+ state.uiOwnerById.set(elementRef, instanceOwnerRef);
1874
+ copyTemplateSemantics(
1875
+ state,
1876
+ template.id,
1877
+ elementRef,
1878
+ properties,
1879
+ conditions,
1880
+ location,
1881
+ );
1882
+ for (const guard of callSiteVisibilityGuards(
1883
+ index,
1884
+ call,
1885
+ inlineVisibilityGuards,
1886
+ )) {
1887
+ if (
1888
+ state.builder
1889
+ .facts("condition")
1890
+ .some(
1891
+ (condition) =>
1892
+ condition.kind === "condition" &&
1893
+ condition.data.ownerKey === elementRef &&
1894
+ condition.data.conditionRole === "visibility" &&
1895
+ condition.data.expressionAnchor === guard.expressionAnchor,
1896
+ )
1897
+ )
1898
+ continue;
1899
+ const guardLocation =
1900
+ recordLocation(
1901
+ guard.condition,
1902
+ `compose-call-site-control-visibility:${guard.expressionAnchor}`,
1903
+ target.signature,
1904
+ ) ?? location;
1905
+ state.builder.addFact("condition", {
1906
+ identity: [
1907
+ { kind: "control", value: elementRef },
1908
+ { kind: "semantic-key", value: "visibility" },
1909
+ { kind: "semantic-key", value: guard.expressionAnchor },
1910
+ ],
1911
+ confidence: "high",
1912
+ support: [
1913
+ {
1914
+ location: guardLocation,
1915
+ role: "structured branch guarding exact local @Composable control invocation",
1916
+ },
1917
+ ],
1918
+ data: {
1919
+ ownerKey: elementRef,
1920
+ expressionAnchor: guard.expressionAnchor,
1921
+ plainLanguage: guard.plainLanguage,
1922
+ valueKind: "runtime",
1923
+ conditionRole: "visibility",
1924
+ },
1925
+ relatedFactRefs: [elementRef],
1926
+ });
1927
+ }
1928
+ for (const availability of repeatedCallSite?.availabilities ?? []) {
1929
+ const availabilityLocation =
1930
+ recordLocation(
1931
+ availability.record,
1932
+ `compose-call-site-runtime-availability:` +
1933
+ availability.expressionAnchor,
1934
+ target.signature,
1935
+ ) ?? location;
1936
+ state.builder.addFact("condition", {
1937
+ identity: [
1938
+ { kind: "control", value: elementRef },
1939
+ { kind: "semantic-key", value: "visibility" },
1940
+ {
1941
+ kind: "semantic-key",
1942
+ value: availability.expressionAnchor,
1943
+ },
1944
+ ],
1945
+ confidence: "high",
1946
+ support: [
1947
+ {
1948
+ location: availabilityLocation,
1949
+ role: "resolved runtime collection-item availability",
1950
+ },
1951
+ ],
1952
+ data: {
1953
+ ownerKey: elementRef,
1954
+ expressionAnchor: availability.expressionAnchor,
1955
+ plainLanguage: availability.plainLanguage,
1956
+ valueKind: "runtime",
1957
+ conditionRole: "visibility",
1958
+ pathRequirement: "runtime-availability",
1959
+ },
1960
+ relatedFactRefs: [elementRef],
1961
+ });
1962
+ }
1963
+ for (const action of group) {
1964
+ state.builder.addFact("interaction-binding", {
1965
+ identity: [
1966
+ { kind: "control", value: elementRef },
1967
+ { kind: "callback", value: action.actualCallback },
1968
+ {
1969
+ kind: "semantic-key",
1970
+ value: action.templateBinding.data.trigger,
1971
+ },
1972
+ ],
1973
+ confidence: action.templateBinding.confidence,
1974
+ support: [
1975
+ {
1976
+ location,
1977
+ role: "call-site-specialized Compose interaction",
1978
+ },
1979
+ ],
1980
+ data: {
1981
+ elementKey: elementRef,
1982
+ callbackKey: action.actualCallback,
1983
+ trigger: action.templateBinding.data.trigger,
1984
+ bindingApi: action.templateBinding.data.bindingApi,
1985
+ },
1986
+ relatedFactRefs: [elementRef, action.actualCallback],
1987
+ });
1988
+ }
1989
+ state.builder.mergeFactData("ui-element", template.id, {
1990
+ analysisRole: "declaration-template",
1991
+ });
1992
+ state.builder.removeUnresolvedOwnedBy(
1993
+ template.id,
1994
+ new Set([
1995
+ "INTRINSIC_CONTROL_NAME_UNKNOWN",
1996
+ "INTRINSIC_COMPOSE_CONTROL_NAME_UNKNOWN",
1997
+ ]),
1998
+ );
1999
+ }
2000
+ }
2001
+ }
2002
+ }
2003
+ for (const surfaces of state.conditionalSurfacesByStateSymbol.values()) {
2004
+ for (const templateRef of specializedSurfaceTemplates) {
2005
+ surfaces.delete(templateRef);
2006
+ }
2007
+ }
2008
+ }