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,1149 @@
1
+ function object(value) {
2
+ return value !== null && typeof value === "object" && !Array.isArray(value)
3
+ ? value
4
+ : undefined;
5
+ }
6
+ function text(value, key) {
7
+ const candidate = value[key];
8
+ return typeof candidate === "string" && candidate.trim().length > 0
9
+ ? candidate.trim()
10
+ : undefined;
11
+ }
12
+ function sourceKind(record) {
13
+ const language = text(record.data, "language");
14
+ if (language === "kotlin" || record.location?.path.endsWith(".kt")) {
15
+ return "kotlin";
16
+ }
17
+ if (language === "java" || record.location?.path.endsWith(".java")) {
18
+ return "java";
19
+ }
20
+ return "other";
21
+ }
22
+ function recordLocation(record, anchor) {
23
+ if (!record.location) return undefined;
24
+ return {
25
+ sourceKind: sourceKind(record),
26
+ path: record.location.path,
27
+ anchor,
28
+ range: {
29
+ startLine: Math.max(1, record.location.startLine),
30
+ endLine: Math.max(
31
+ Math.max(1, record.location.startLine),
32
+ record.location.endLine,
33
+ ),
34
+ ...(record.location.startColumn >= 1
35
+ ? { startColumn: record.location.startColumn }
36
+ : {}),
37
+ ...(record.location.endColumn >= 1
38
+ ? { endColumn: record.location.endColumn }
39
+ : {}),
40
+ },
41
+ };
42
+ }
43
+ function symbolKey(expression) {
44
+ const symbol = object(expression?.resolvedSymbol);
45
+ if (!symbol) return undefined;
46
+ const kind = text(symbol, "kind") ?? "";
47
+ const name = text(symbol, "name") ?? "";
48
+ const signature = text(symbol, "signature") ?? "";
49
+ const owner = text(symbol, "ownerQualifiedName") ?? "";
50
+ const type = text(symbol, "type") ?? "";
51
+ if (!name && !signature) return undefined;
52
+ const initializer = object(symbol.initializer);
53
+ const declarationIdentity =
54
+ text(symbol, "declarationRef") ??
55
+ text(symbol, "declarationId") ??
56
+ (initializer ? text(initializer, "callRef") : undefined);
57
+ return [
58
+ kind,
59
+ owner,
60
+ signature || name,
61
+ type,
62
+ ...(declarationIdentity ? [declarationIdentity] : []),
63
+ ].join("\u0000");
64
+ }
65
+ function jsonValue(expression) {
66
+ if (!expression || !Object.hasOwn(expression, "constantValue")) {
67
+ return { present: false };
68
+ }
69
+ const value = expression.constantValue;
70
+ if (
71
+ value === null ||
72
+ typeof value === "string" ||
73
+ typeof value === "number" ||
74
+ typeof value === "boolean"
75
+ ) {
76
+ return { present: true, value };
77
+ }
78
+ return { present: false };
79
+ }
80
+ function stringValues(value) {
81
+ return Array.isArray(value)
82
+ ? value.filter(
83
+ (candidate) =>
84
+ typeof candidate === "string" && candidate.trim().length > 0,
85
+ )
86
+ : [];
87
+ }
88
+ function structuredConditionContexts(record) {
89
+ const contexts = record.data.conditionContexts;
90
+ if (!Array.isArray(contexts)) return [];
91
+ return contexts.flatMap((candidate) => {
92
+ const structured = object(candidate);
93
+ if (!structured) return [];
94
+ const conditionRef = text(structured, "conditionRef");
95
+ const branchKind = text(structured, "branchKind");
96
+ const polarity = text(structured, "polarity");
97
+ if (
98
+ !conditionRef ||
99
+ !(
100
+ branchKind === "then" ||
101
+ branchKind === "else" ||
102
+ branchKind === "fallthrough" ||
103
+ branchKind === "case"
104
+ ) ||
105
+ !(polarity === "true" || polarity === "false" || polarity === "case")
106
+ )
107
+ return [];
108
+ return [
109
+ {
110
+ conditionRef,
111
+ branchKind,
112
+ polarity,
113
+ caseValues: stringValues(structured.caseValues),
114
+ },
115
+ ];
116
+ });
117
+ }
118
+ function structuredConditionKeys(
119
+ record,
120
+ callbackRef,
121
+ recordsById,
122
+ state,
123
+ subject,
124
+ ) {
125
+ const result = new Set();
126
+ const covered = new Set();
127
+ const addCondition = (condition, expressionAnchor, plainLanguage, role) => {
128
+ const location = recordLocation(
129
+ condition,
130
+ `${subject}-condition:${expressionAnchor}:${condition.id}`,
131
+ );
132
+ if (!location) return;
133
+ result.add(
134
+ state.builder.addFact("condition", {
135
+ identity: [
136
+ { kind: "callback", value: callbackRef },
137
+ { kind: "semantic-key", value: expressionAnchor },
138
+ ],
139
+ confidence: "high",
140
+ support: [{ location, role }],
141
+ data: {
142
+ ownerKey: callbackRef,
143
+ expressionAnchor,
144
+ plainLanguage,
145
+ valueKind: "runtime",
146
+ },
147
+ relatedFactRefs: [callbackRef],
148
+ }),
149
+ );
150
+ };
151
+ for (const context of structuredConditionContexts(record)) {
152
+ covered.add(context.conditionRef);
153
+ const condition = recordsById.get(context.conditionRef);
154
+ const expression = condition
155
+ ? text(condition.data, "expression")
156
+ : undefined;
157
+ if (!condition || !expression) continue;
158
+ const expressionAnchor =
159
+ context.polarity === "false"
160
+ ? `!(${expression})`
161
+ : context.polarity === "case"
162
+ ? `${expression} in (${context.caseValues.join(", ")})`
163
+ : expression;
164
+ addCondition(
165
+ condition,
166
+ expressionAnchor,
167
+ context.polarity === "false"
168
+ ? `不满足条件:${expression}`
169
+ : context.polarity === "case"
170
+ ? `条件 ${expression} 匹配:${context.caseValues.join("、")}`
171
+ : `满足条件:${expression}`,
172
+ `structured ${context.branchKind} ${subject} branch ` +
173
+ `with ${context.polarity} polarity`,
174
+ );
175
+ }
176
+ for (const conditionRef of stringValues(record.data.conditionRefs)) {
177
+ if (covered.has(conditionRef)) continue;
178
+ const condition = recordsById.get(conditionRef);
179
+ const expression = condition
180
+ ? text(condition.data, "expression")
181
+ : undefined;
182
+ if (!condition || !expression) continue;
183
+ addCondition(
184
+ condition,
185
+ expression,
186
+ expression,
187
+ `structured condition guarding ${subject}`,
188
+ );
189
+ }
190
+ return [...result].sort((left, right) => left.localeCompare(right, "en"));
191
+ }
192
+ function assignmentValueIdentity(assignment, expression, value) {
193
+ if (value.present) return `constant:${JSON.stringify(value.value)}`;
194
+ const source = expression ? text(expression, "source") : undefined;
195
+ return `expression:${source ?? symbolKey(expression) ?? assignment.id}`;
196
+ }
197
+ function sameRawSpan(left, right) {
198
+ const leftLocation = left.location;
199
+ const rightLocation = right.location;
200
+ if (
201
+ !leftLocation ||
202
+ !rightLocation ||
203
+ leftLocation.path !== rightLocation.path
204
+ )
205
+ return false;
206
+ if (
207
+ leftLocation.startOffset >= 0 &&
208
+ leftLocation.endOffset >= 0 &&
209
+ rightLocation.startOffset >= 0 &&
210
+ rightLocation.endOffset >= 0
211
+ ) {
212
+ return (
213
+ leftLocation.startOffset === rightLocation.startOffset &&
214
+ leftLocation.endOffset === rightLocation.endOffset
215
+ );
216
+ }
217
+ return (
218
+ leftLocation.startLine === rightLocation.startLine &&
219
+ leftLocation.startColumn === rightLocation.startColumn &&
220
+ leftLocation.endLine === rightLocation.endLine &&
221
+ leftLocation.endColumn === rightLocation.endColumn
222
+ );
223
+ }
224
+ function sameStrings(left, right) {
225
+ if (left.length !== right.length) return false;
226
+ return left.every((value, index) => value === right[index]);
227
+ }
228
+ /**
229
+ * Kotlin UAST may expose one property write twice: as a binary assignment and
230
+ * as the synthetic setter call used to implement it. The resolved-call pass
231
+ * has the more precise receiver and argument data. Suppress the assignment
232
+ * only when every structural identity field and the already-emitted effect
233
+ * agree; ambiguous matches are deliberately retained.
234
+ */
235
+ function equivalentResolvedSetterEffect(
236
+ jvm,
237
+ assignment,
238
+ callbackRef,
239
+ conditionKeys,
240
+ state,
241
+ ) {
242
+ const left = object(assignment.data.left);
243
+ const symbol = object(left?.resolvedSymbol);
244
+ const signature =
245
+ symbol && text(symbol, "kind") === "method"
246
+ ? text(symbol, "signature")
247
+ : undefined;
248
+ const enclosingCallableRef = text(assignment.data, "enclosingCallableRef");
249
+ if (!signature || !enclosingCallableRef) return undefined;
250
+ const matches = jvm.body.flatMap((call) => {
251
+ if (
252
+ call.kind !== "resolved-call" ||
253
+ text(call.data, "targetSignature") !== signature ||
254
+ text(call.data, "enclosingCallableRef") !== enclosingCallableRef ||
255
+ !sameRawSpan(assignment, call)
256
+ )
257
+ return [];
258
+ const effectRef = state.effectForResolvedCall(call.id);
259
+ const effect = effectRef ? state.builder.fact(effectRef) : undefined;
260
+ if (
261
+ !effect ||
262
+ effect.kind !== "behavior-effect" ||
263
+ effect.data.callbackKey !== callbackRef ||
264
+ effect.data.effectKind !== "update-state" ||
265
+ !sameStrings(
266
+ [...(effect.data.conditionKeys ?? [])].sort(),
267
+ [...conditionKeys].sort(),
268
+ )
269
+ )
270
+ return [];
271
+ return [effectRef];
272
+ });
273
+ return matches.length === 1 ? matches[0] : undefined;
274
+ }
275
+ function callableRecordForCallback(jvm, state, callbackRef) {
276
+ return jvm.body.find(
277
+ (record) =>
278
+ (record.kind === "lambda-declaration" ||
279
+ record.kind === "method-declaration") &&
280
+ state.callbackForRawCallable(record.id) === callbackRef,
281
+ );
282
+ }
283
+ function hasEffect(state, callbackRef, effectKind, targetRef) {
284
+ return state.builder
285
+ .facts("behavior-effect")
286
+ .some(
287
+ (fact) =>
288
+ fact.kind === "behavior-effect" &&
289
+ fact.data.callbackKey === callbackRef &&
290
+ fact.data.effectKind === effectKind &&
291
+ fact.data.targetKey === targetRef,
292
+ );
293
+ }
294
+ function addInteractionIntrinsicEffects(jvm, state) {
295
+ for (const binding of state.builder.facts("interaction-binding")) {
296
+ if (binding.kind !== "interaction-binding") continue;
297
+ const effectKind =
298
+ binding.data.trigger === "text-input"
299
+ ? "input"
300
+ : binding.data.trigger === "toggle"
301
+ ? "update-state"
302
+ : undefined;
303
+ if (!effectKind || hasEffect(state, binding.data.callbackKey, effectKind))
304
+ continue;
305
+ const callable = callableRecordForCallback(
306
+ jvm,
307
+ state,
308
+ binding.data.callbackKey,
309
+ );
310
+ const location = callable
311
+ ? recordLocation(
312
+ callable,
313
+ `interaction-effect:${binding.data.trigger}:${callable.id}`,
314
+ )
315
+ : undefined;
316
+ if (!location) continue;
317
+ state.builder.addFact("behavior-effect", {
318
+ identity: [
319
+ { kind: "callback", value: binding.data.callbackKey },
320
+ { kind: "semantic-key", value: effectKind },
321
+ { kind: "semantic-key", value: binding.id },
322
+ ],
323
+ confidence: binding.confidence,
324
+ support: [
325
+ {
326
+ location,
327
+ role:
328
+ binding.data.trigger === "text-input"
329
+ ? "resolved text-input interaction"
330
+ : "resolved toggle interaction",
331
+ },
332
+ ],
333
+ data: {
334
+ callbackKey: binding.data.callbackKey,
335
+ effectKind,
336
+ },
337
+ relatedFactRefs: [binding.data.callbackKey, binding.id],
338
+ });
339
+ }
340
+ }
341
+ function surfaceConditionOwners(jvm, state) {
342
+ const surfaceTypes = new Set([
343
+ "dialog",
344
+ "drawer",
345
+ "bottom-sheet",
346
+ "overlay",
347
+ "pager",
348
+ "pager-page",
349
+ "inline-detail-surface",
350
+ "popup",
351
+ ]);
352
+ const conditionFacts = state.builder.facts("condition");
353
+ const owners = new Map();
354
+ for (const raw of jvm.body) {
355
+ if (raw.kind !== "condition") continue;
356
+ const expression = text(raw.data, "expression");
357
+ const structure = object(raw.data.expressionStructure);
358
+ const key = symbolKey(structure);
359
+ if (!expression || !key) continue;
360
+ for (const fact of conditionFacts) {
361
+ if (
362
+ fact.kind !== "condition" ||
363
+ fact.data.expressionAnchor !== expression
364
+ )
365
+ continue;
366
+ const owner = state.builder.fact(fact.data.ownerKey);
367
+ if (
368
+ owner?.kind !== "component" ||
369
+ !surfaceTypes.has(owner.data.componentType)
370
+ )
371
+ continue;
372
+ const values = owners.get(key) ?? new Set();
373
+ values.add(owner.id);
374
+ owners.set(key, values);
375
+ }
376
+ }
377
+ return new Map(
378
+ [...owners].map(([key, values]) => [
379
+ key,
380
+ [...values].sort((left, right) => left.localeCompare(right, "en")),
381
+ ]),
382
+ );
383
+ }
384
+ function addAssignmentEffects(jvm, state) {
385
+ const recordsById = new Map(jvm.body.map((record) => [record.id, record]));
386
+ const surfacesByState = surfaceConditionOwners(jvm, state);
387
+ const interactions = state.builder.facts("interaction-binding");
388
+ for (const assignment of jvm.body) {
389
+ if (assignment.kind !== "assignment") continue;
390
+ const rawCallableRef = text(assignment.data, "enclosingCallableRef");
391
+ const callbackRef = rawCallableRef
392
+ ? state.callbackForRawCallable(rawCallableRef)
393
+ : undefined;
394
+ const location = recordLocation(
395
+ assignment,
396
+ `assignment-effect:${assignment.id}`,
397
+ );
398
+ if (!callbackRef || !location) continue;
399
+ const left = object(assignment.data.left);
400
+ const right = object(assignment.data.right);
401
+ const stateSymbol = symbolKey(left);
402
+ const value = jsonValue(right);
403
+ const conditionKeys = structuredConditionKeys(
404
+ assignment,
405
+ callbackRef,
406
+ recordsById,
407
+ state,
408
+ "assignment",
409
+ );
410
+ const valueIdentity = assignmentValueIdentity(assignment, right, value);
411
+ const surfaceRefs = stateSymbol
412
+ ? (surfacesByState.get(stateSymbol) ?? [])
413
+ : [];
414
+ const registeredSurfaces = stateSymbol
415
+ ? state.conditionalSurfacesByStateSymbol.get(stateSymbol)
416
+ : undefined;
417
+ const booleanValue =
418
+ value.present && typeof value.value === "boolean"
419
+ ? value.value
420
+ : undefined;
421
+ const candidates = new Map();
422
+ if (surfaceRefs.length === 1 && booleanValue !== undefined) {
423
+ candidates.set(surfaceRefs[0], {
424
+ effectKind: booleanValue ? "show-surface" : "close-surface",
425
+ confidence: "high",
426
+ });
427
+ }
428
+ if (registeredSurfaces?.size === 1) {
429
+ const [targetRef, activation] = [...registeredSurfaces][0];
430
+ if (activation === "boolean-true" && booleanValue !== undefined) {
431
+ candidates.set(targetRef, {
432
+ effectKind: booleanValue ? "show-surface" : "close-surface",
433
+ confidence: "high",
434
+ });
435
+ } else if (activation === "non-null") {
436
+ if (value.present) {
437
+ candidates.set(targetRef, {
438
+ effectKind: value.value === null ? "close-surface" : "show-surface",
439
+ confidence: "high",
440
+ });
441
+ } else if (
442
+ object(right?.resolvedSymbol) ||
443
+ object(right?.resourceReference) ||
444
+ text(right ?? {}, "callRef")
445
+ ) {
446
+ /*
447
+ * A resolved non-constant expression can still be nullable at
448
+ * runtime. It is valid evidence that the assignment may activate
449
+ * the safe-call surface, but not proof that it always does.
450
+ */
451
+ candidates.set(targetRef, {
452
+ effectKind: "show-surface",
453
+ confidence: "medium",
454
+ });
455
+ }
456
+ }
457
+ }
458
+ if (candidates.size === 1) {
459
+ const [targetRef, candidate] = [...candidates][0];
460
+ const effectKind = candidate.effectKind;
461
+ state.builder.addFact("behavior-effect", {
462
+ identity: [
463
+ { kind: "callback", value: callbackRef },
464
+ { kind: "semantic-key", value: effectKind },
465
+ { kind: "component", value: targetRef },
466
+ { kind: "semantic-key", value: stateSymbol ?? assignment.id },
467
+ { kind: "semantic-key", value: valueIdentity },
468
+ {
469
+ kind: "semantic-key",
470
+ value: `conditions:${conditionKeys.join("|")}`,
471
+ },
472
+ ],
473
+ confidence: candidate.confidence,
474
+ support: [
475
+ {
476
+ location,
477
+ role: "structured assignment to Compose surface activation state",
478
+ },
479
+ ],
480
+ data: {
481
+ callbackKey: callbackRef,
482
+ effectKind,
483
+ targetKey: targetRef,
484
+ ...(stateSymbol ? { stateKey: stateSymbol } : {}),
485
+ ...(value.present ? { value: value.value ?? null } : {}),
486
+ conditionKeys,
487
+ },
488
+ relatedFactRefs: [callbackRef, targetRef, ...conditionKeys],
489
+ });
490
+ continue;
491
+ }
492
+ if (
493
+ equivalentResolvedSetterEffect(
494
+ jvm,
495
+ assignment,
496
+ callbackRef,
497
+ conditionKeys,
498
+ state,
499
+ )
500
+ )
501
+ continue;
502
+ const binding = interactions.find(
503
+ (fact) =>
504
+ fact.kind === "interaction-binding" &&
505
+ fact.data.callbackKey === callbackRef,
506
+ );
507
+ if (
508
+ !binding ||
509
+ binding.kind !== "interaction-binding" ||
510
+ binding.data.trigger === "toggle" ||
511
+ binding.data.trigger === "text-input"
512
+ )
513
+ continue;
514
+ state.builder.addFact("behavior-effect", {
515
+ identity: [
516
+ { kind: "callback", value: callbackRef },
517
+ { kind: "semantic-key", value: "update-state" },
518
+ { kind: "semantic-key", value: stateSymbol ?? assignment.id },
519
+ { kind: "semantic-key", value: valueIdentity },
520
+ {
521
+ kind: "semantic-key",
522
+ value: `conditions:${conditionKeys.join("|")}`,
523
+ },
524
+ ],
525
+ confidence: stateSymbol ? "high" : "medium",
526
+ support: [
527
+ {
528
+ location,
529
+ role: "structured state assignment inside interaction callback",
530
+ },
531
+ ],
532
+ data: {
533
+ callbackKey: callbackRef,
534
+ effectKind: "update-state",
535
+ ...(stateSymbol ? { stateKey: stateSymbol } : {}),
536
+ ...(value.present ? { value: value.value ?? null } : {}),
537
+ conditionKeys,
538
+ },
539
+ relatedFactRefs: [callbackRef, binding.id, ...conditionKeys],
540
+ });
541
+ }
542
+ }
543
+ const composeSurfaceStateOwners = new Set([
544
+ "androidx.compose.material.DrawerState",
545
+ "androidx.compose.material.ModalBottomSheetState",
546
+ "androidx.compose.material3.DrawerState",
547
+ "androidx.compose.material3.SheetState",
548
+ ]);
549
+ function stateApiEffectKind(call) {
550
+ if (call.kind !== "resolved-call") return undefined;
551
+ const owner = text(call.data, "targetOwnerQualifiedName");
552
+ if (!owner || !composeSurfaceStateOwners.has(owner)) return undefined;
553
+ const name = text(call.data, "targetName");
554
+ if (
555
+ name === "open" ||
556
+ name === "show" ||
557
+ name === "expand" ||
558
+ name === "partialExpand"
559
+ ) {
560
+ return "show-surface";
561
+ }
562
+ return name === "close" || name === "hide" ? "close-surface" : undefined;
563
+ }
564
+ function stateApiReceiverSymbol(call) {
565
+ const receiverExpression = object(call.data.receiverExpression);
566
+ const fromExpression = symbolKey(receiverExpression);
567
+ if (fromExpression) return fromExpression;
568
+ const receiverSymbol = object(call.data.receiverResolvedSymbol);
569
+ return receiverSymbol
570
+ ? symbolKey({ resolvedSymbol: receiverSymbol })
571
+ : undefined;
572
+ }
573
+ function effectCallbackForCall(jvm, state, call) {
574
+ const parents = new Map();
575
+ for (const record of jvm.body) {
576
+ if (
577
+ record.kind !== "lambda-declaration" &&
578
+ record.kind !== "method-declaration"
579
+ )
580
+ continue;
581
+ const parent = text(record.data, "enclosingCallableRef");
582
+ if (parent) parents.set(record.id, parent);
583
+ }
584
+ const interactionCallbacks = new Set(
585
+ state.builder
586
+ .facts("interaction-binding")
587
+ .flatMap((fact) =>
588
+ fact.kind === "interaction-binding" ? [fact.data.callbackKey] : [],
589
+ ),
590
+ );
591
+ const visited = new Set();
592
+ let rawCallable = text(call.data, "enclosingCallableRef");
593
+ let nearest;
594
+ while (rawCallable && !visited.has(rawCallable)) {
595
+ visited.add(rawCallable);
596
+ const callbackRef = state.callbackForRawCallable(rawCallable);
597
+ if (callbackRef) {
598
+ nearest ??= callbackRef;
599
+ if (interactionCallbacks.has(callbackRef)) return callbackRef;
600
+ }
601
+ rawCallable = parents.get(rawCallable);
602
+ }
603
+ return nearest;
604
+ }
605
+ function addComposeSurfaceStateEffects(jvm, state) {
606
+ for (const call of jvm.body) {
607
+ const stateSymbol = stateApiReceiverSymbol(call);
608
+ const targetSignature = text(call.data, "targetSignature");
609
+ const registeredEffect =
610
+ stateSymbol && targetSignature
611
+ ? state.statefulSurfaceTransition(stateSymbol, targetSignature)
612
+ : undefined;
613
+ const effectKind = registeredEffect ?? stateApiEffectKind(call);
614
+ const surfaces = stateSymbol
615
+ ? state.statefulSurfacesByStateSymbol.get(stateSymbol)
616
+ : undefined;
617
+ if (!effectKind || !surfaces || surfaces.size !== 1) continue;
618
+ const callbackRef = effectCallbackForCall(jvm, state, call);
619
+ const location = recordLocation(
620
+ call,
621
+ `compose-surface-state:${text(call.data, "targetSignature") ?? call.id}`,
622
+ );
623
+ if (!callbackRef || !location) continue;
624
+ const [surfaceRef] = [...surfaces];
625
+ if (hasEffect(state, callbackRef, effectKind, surfaceRef)) continue;
626
+ state.builder.addFact("behavior-effect", {
627
+ identity: [
628
+ { kind: "callback", value: callbackRef },
629
+ { kind: "semantic-key", value: effectKind },
630
+ { kind: "component", value: surfaceRef },
631
+ ],
632
+ confidence: "high",
633
+ support: [
634
+ {
635
+ location,
636
+ role: "resolved Compose surface-state API",
637
+ },
638
+ ],
639
+ data: {
640
+ callbackKey: callbackRef,
641
+ effectKind,
642
+ targetKey: surfaceRef,
643
+ },
644
+ relatedFactRefs: [callbackRef, surfaceRef],
645
+ });
646
+ }
647
+ }
648
+ const composeSnackbarHostOwners = new Set([
649
+ "androidx.compose.material.SnackbarHostState",
650
+ "androidx.compose.material3.SnackbarHostState",
651
+ ]);
652
+ const composeSnackbarOwners = new Set([
653
+ "androidx.compose.material.SnackbarKt",
654
+ "androidx.compose.material3.SnackbarKt",
655
+ ]);
656
+ const composeTextOwners = new Set([
657
+ "androidx.compose.material.TextKt",
658
+ "androidx.compose.material3.TextKt",
659
+ ]);
660
+ function numeric(value, key) {
661
+ const candidate = value[key];
662
+ return typeof candidate === "number" && Number.isFinite(candidate)
663
+ ? candidate
664
+ : undefined;
665
+ }
666
+ function callBindings(jvm, callRef) {
667
+ return jvm.body.filter(
668
+ (record) =>
669
+ record.kind === "call-argument-binding" &&
670
+ text(record.data, "callRef") === callRef,
671
+ );
672
+ }
673
+ function preferredBinding(jvm, callRef, parameterNames, fallbackIndex) {
674
+ const candidates = callBindings(jvm, callRef).filter(
675
+ (binding) => object(binding.data.expression) !== undefined,
676
+ );
677
+ return (
678
+ candidates.find((binding) =>
679
+ parameterNames.has(text(binding.data, "parameterName") ?? ""),
680
+ ) ??
681
+ candidates.find(
682
+ (binding) => numeric(binding.data, "argumentIndex") === fallbackIndex,
683
+ )
684
+ );
685
+ }
686
+ function selectedStringResource(resourceName, state) {
687
+ const candidates = state.builder
688
+ .facts("resource-value")
689
+ .flatMap((fact) =>
690
+ fact.kind === "resource-value" &&
691
+ fact.data.resourceType === "string" &&
692
+ fact.data.resourceName === resourceName &&
693
+ typeof fact.data.value === "string"
694
+ ? [{ ref: fact.id, value: fact.data.value }]
695
+ : [],
696
+ );
697
+ const values = [...new Set(candidates.map((candidate) => candidate.value))];
698
+ if (values.length !== 1) {
699
+ return {
700
+ present: false,
701
+ expression: `R.string.${resourceName}`,
702
+ resourceFactRefs: [],
703
+ };
704
+ }
705
+ return {
706
+ present: true,
707
+ value: values[0],
708
+ resourceFactRefs: candidates
709
+ .filter((candidate) => candidate.value === values[0])
710
+ .map((candidate) => candidate.ref)
711
+ .sort((left, right) => left.localeCompare(right, "en")),
712
+ };
713
+ }
714
+ function resourceMessage(expression, state) {
715
+ const resource = object(expression.resourceReference);
716
+ const resourceName = resource ? text(resource, "resourceName") : undefined;
717
+ if (resource && text(resource, "resourceType") === "string" && resourceName) {
718
+ return selectedStringResource(resourceName, state);
719
+ }
720
+ return undefined;
721
+ }
722
+ function isResolvedStringResourceCall(call) {
723
+ const owner = text(call.data, "targetOwnerQualifiedName") ?? "";
724
+ const name = text(call.data, "targetName") ?? "";
725
+ return (
726
+ (owner === "androidx.compose.ui.res.StringResources_androidKt" &&
727
+ name === "stringResource") ||
728
+ ((owner === "android.content.Context" ||
729
+ owner === "android.content.res.Resources") &&
730
+ name === "getString")
731
+ );
732
+ }
733
+ function feedbackMessageExpression(
734
+ expression,
735
+ evidenceRecord,
736
+ jvm,
737
+ state,
738
+ visitedCalls,
739
+ ) {
740
+ if (
741
+ Object.hasOwn(expression, "constantValue") &&
742
+ typeof expression.constantValue === "string"
743
+ ) {
744
+ return {
745
+ present: true,
746
+ value: expression.constantValue,
747
+ resourceFactRefs: [],
748
+ evidenceRecord,
749
+ };
750
+ }
751
+ const resource = resourceMessage(expression, state);
752
+ if (resource) return { ...resource, evidenceRecord };
753
+ const resolvedSymbol = object(expression.resolvedSymbol);
754
+ const initializer = object(resolvedSymbol?.initializer);
755
+ if (initializer) {
756
+ const initialized = feedbackMessageExpression(
757
+ initializer,
758
+ evidenceRecord,
759
+ jvm,
760
+ state,
761
+ visitedCalls,
762
+ );
763
+ if (initialized.present) return initialized;
764
+ }
765
+ const directCallRef = text(expression, "callRef");
766
+ const initializerCallRef = initializer
767
+ ? text(initializer, "callRef")
768
+ : undefined;
769
+ const callRef = directCallRef ?? initializerCallRef;
770
+ if (callRef && !visitedCalls.has(callRef)) {
771
+ visitedCalls.add(callRef);
772
+ const call = jvm.body.find((record) => record.id === callRef);
773
+ if (call && isResolvedStringResourceCall(call)) {
774
+ const binding = preferredBinding(
775
+ jvm,
776
+ call.id,
777
+ new Set(["id", "resId"]),
778
+ 0,
779
+ );
780
+ const nested = object(binding?.data.expression);
781
+ if (binding && nested) {
782
+ const resolved = feedbackMessageExpression(
783
+ nested,
784
+ binding,
785
+ jvm,
786
+ state,
787
+ visitedCalls,
788
+ );
789
+ if (resolved.present) return resolved;
790
+ }
791
+ }
792
+ }
793
+ return {
794
+ present: false,
795
+ ...(text(expression, "source")
796
+ ? { expression: text(expression, "source") }
797
+ : {}),
798
+ resourceFactRefs: [],
799
+ evidenceRecord,
800
+ };
801
+ }
802
+ function feedbackMessage(binding, jvm, state, fallbackExpression) {
803
+ const expression = object(binding?.data.expression);
804
+ if (!binding || !expression) {
805
+ return {
806
+ present: false,
807
+ ...(fallbackExpression ? { expression: fallbackExpression } : {}),
808
+ resourceFactRefs: [],
809
+ ...(binding ? { evidenceRecord: binding } : {}),
810
+ };
811
+ }
812
+ const resolution = feedbackMessageExpression(
813
+ expression,
814
+ binding,
815
+ jvm,
816
+ state,
817
+ new Set(),
818
+ );
819
+ return resolution.expression || !fallbackExpression
820
+ ? resolution
821
+ : { ...resolution, expression: fallbackExpression };
822
+ }
823
+ function addComposeFeedbackEffect(
824
+ state,
825
+ callbackRef,
826
+ apiIdentity,
827
+ origin,
828
+ message,
829
+ supports,
830
+ conditionKeys,
831
+ stateKey,
832
+ ) {
833
+ const support = supports.flatMap(({ record, role }) => {
834
+ const location = recordLocation(
835
+ record,
836
+ `compose-feedback:${apiIdentity}:${record.id}`,
837
+ );
838
+ return location ? [{ location, role }] : [];
839
+ });
840
+ if (support.length === 0) return;
841
+ const messageIdentity = message.present
842
+ ? `value:${JSON.stringify(message.value)}`
843
+ : `expression:${message.expression ?? origin.id}`;
844
+ const effectRef = state.builder.addFact("behavior-effect", {
845
+ identity: [
846
+ { kind: "callback", value: callbackRef },
847
+ { kind: "semantic-key", value: `compose-feedback:${apiIdentity}` },
848
+ { kind: "semantic-key", value: stateKey ?? "stateless" },
849
+ { kind: "semantic-key", value: messageIdentity },
850
+ {
851
+ kind: "semantic-key",
852
+ value: `conditions:${conditionKeys.join("|")}`,
853
+ },
854
+ ],
855
+ confidence: "high",
856
+ support: support,
857
+ data: {
858
+ callbackKey: callbackRef,
859
+ effectKind: "show-feedback",
860
+ ...(stateKey ? { stateKey } : {}),
861
+ ...(message.present ? { value: message.value ?? "" } : {}),
862
+ conditionKeys,
863
+ },
864
+ relatedFactRefs: [
865
+ callbackRef,
866
+ ...message.resourceFactRefs,
867
+ ...conditionKeys,
868
+ ],
869
+ });
870
+ if (message.present) return;
871
+ const fact = state.builder.fact(effectRef);
872
+ const evidenceRefs = fact?.support.map((item) => item.evidenceRef) ?? [];
873
+ const candidateSupport = message.evidenceRecord
874
+ ? recordLocation(
875
+ message.evidenceRecord,
876
+ `compose-feedback-expression:${message.evidenceRecord.id}`,
877
+ )
878
+ : undefined;
879
+ const candidateEvidenceRef = candidateSupport
880
+ ? state.builder.addEvidence(candidateSupport)
881
+ : undefined;
882
+ state.builder.addUnresolved({
883
+ ownerRef: effectRef,
884
+ fieldPath: "data.value",
885
+ reasonCode: "INTRINSIC_COMPOSE_FEEDBACK_TEXT_UNKNOWN",
886
+ message:
887
+ "Compose Snackbar feedback is structurally proven, but its runtime " +
888
+ "message text is not a unique static value.",
889
+ evidenceRefs,
890
+ candidates: message.expression
891
+ ? [
892
+ {
893
+ value: message.expression,
894
+ supportRefs: candidateEvidenceRef
895
+ ? [candidateEvidenceRef]
896
+ : evidenceRefs,
897
+ note: "Source expression retained as evidence; not asserted as text.",
898
+ },
899
+ ]
900
+ : [],
901
+ impact: ["req-feature-effect"],
902
+ originRef: origin.id,
903
+ });
904
+ }
905
+ function addSnackbarHostStateEffects(jvm, state) {
906
+ const recordsById = new Map(jvm.body.map((record) => [record.id, record]));
907
+ for (const call of jvm.body) {
908
+ if (
909
+ call.kind !== "resolved-call" ||
910
+ text(call.data, "targetName") !== "showSnackbar" ||
911
+ !composeSnackbarHostOwners.has(
912
+ text(call.data, "targetOwnerQualifiedName") ?? "",
913
+ )
914
+ )
915
+ continue;
916
+ const callbackRef = effectCallbackForCall(jvm, state, call);
917
+ if (!callbackRef) continue;
918
+ const binding = preferredBinding(jvm, call.id, new Set(["message"]), 0);
919
+ const message = feedbackMessage(
920
+ binding,
921
+ jvm,
922
+ state,
923
+ text(call.data, "source"),
924
+ );
925
+ const conditionKeys = structuredConditionKeys(
926
+ call,
927
+ callbackRef,
928
+ recordsById,
929
+ state,
930
+ "feedback",
931
+ );
932
+ addComposeFeedbackEffect(
933
+ state,
934
+ callbackRef,
935
+ text(call.data, "targetSignature") ??
936
+ `${text(call.data, "targetOwnerQualifiedName")}#showSnackbar`,
937
+ call,
938
+ message,
939
+ [{ record: call, role: "resolved SnackbarHostState.showSnackbar call" }],
940
+ conditionKeys,
941
+ stateApiReceiverSymbol(call),
942
+ );
943
+ }
944
+ }
945
+ function lambdaParentCalls(jvm) {
946
+ const result = new Map();
947
+ for (const binding of jvm.body) {
948
+ if (binding.kind !== "call-argument-binding") continue;
949
+ const callRef = text(binding.data, "callRef");
950
+ const expression = object(binding.data.expression);
951
+ const callableRef = expression
952
+ ? text(expression, "callableRef")
953
+ : undefined;
954
+ if (!callRef || !callableRef) continue;
955
+ const calls = result.get(callableRef) ?? new Set();
956
+ calls.add(callRef);
957
+ result.set(callableRef, calls);
958
+ }
959
+ return result;
960
+ }
961
+ function nearestLetCall(call, recordsById, parentCalls) {
962
+ let callableRef = text(call.data, "enclosingCallableRef");
963
+ const visited = new Set();
964
+ while (callableRef && !visited.has(callableRef)) {
965
+ visited.add(callableRef);
966
+ const parentRefs = [...(parentCalls.get(callableRef) ?? [])];
967
+ if (parentRefs.length === 1) {
968
+ const parent = recordsById.get(parentRefs[0]);
969
+ if (
970
+ parent?.kind === "resolved-call" &&
971
+ /^kotlin\.StandardKt(?:__StandardKt)?$/u.test(
972
+ text(parent.data, "targetOwnerQualifiedName") ?? "",
973
+ ) &&
974
+ text(parent.data, "targetName") === "let"
975
+ )
976
+ return parent;
977
+ callableRef = parent
978
+ ? text(parent.data, "enclosingCallableRef")
979
+ : undefined;
980
+ continue;
981
+ }
982
+ callableRef = text(
983
+ recordsById.get(callableRef)?.data ?? {},
984
+ "enclosingCallableRef",
985
+ );
986
+ }
987
+ return undefined;
988
+ }
989
+ function letReceiverStateKey(call, jvm) {
990
+ const direct = object(call.data.receiverResolvedSymbol);
991
+ const directKey = direct ? symbolKey({ resolvedSymbol: direct }) : undefined;
992
+ if (directKey) return directKey;
993
+ const receiver = preferredBinding(jvm, call.id, new Set(["$this$let"]), 1);
994
+ return symbolKey(object(receiver?.data.expression));
995
+ }
996
+ function provablyNonNull(assignment) {
997
+ const right = object(assignment.data.right);
998
+ const value = jsonValue(right);
999
+ if (value.present) return value.value !== null;
1000
+ if (!right || sourceKind(assignment) !== "kotlin") return false;
1001
+ const type = text(right, "expressionType");
1002
+ if (!type || type === "null" || type.endsWith("?")) return false;
1003
+ return Boolean(
1004
+ object(right.resolvedSymbol) ||
1005
+ object(right.resourceReference) ||
1006
+ text(right, "callRef"),
1007
+ );
1008
+ }
1009
+ function callableWithin(callableRef, ancestorRef, recordsById) {
1010
+ const visited = new Set();
1011
+ let current = callableRef;
1012
+ while (current && !visited.has(current)) {
1013
+ if (current === ancestorRef) return true;
1014
+ visited.add(current);
1015
+ current = text(
1016
+ recordsById.get(current)?.data ?? {},
1017
+ "enclosingCallableRef",
1018
+ );
1019
+ }
1020
+ return false;
1021
+ }
1022
+ function composableSnackbarMessage(snackbar, jvm, state, recordsById) {
1023
+ const contentBinding = preferredBinding(
1024
+ jvm,
1025
+ snackbar.id,
1026
+ new Set(["content"]),
1027
+ 0,
1028
+ );
1029
+ const contentExpression = object(contentBinding?.data.expression);
1030
+ const contentCallable = contentExpression
1031
+ ? text(contentExpression, "callableRef")
1032
+ : undefined;
1033
+ if (!contentCallable) {
1034
+ return feedbackMessage(
1035
+ undefined,
1036
+ jvm,
1037
+ state,
1038
+ contentExpression ? text(contentExpression, "source") : undefined,
1039
+ );
1040
+ }
1041
+ const textCalls = jvm.body.filter(
1042
+ (candidate) =>
1043
+ candidate.kind === "resolved-call" &&
1044
+ composeTextOwners.has(
1045
+ text(candidate.data, "targetOwnerQualifiedName") ?? "",
1046
+ ) &&
1047
+ (text(candidate.data, "targetName") ?? "").startsWith("Text") &&
1048
+ callableWithin(
1049
+ text(candidate.data, "enclosingCallableRef"),
1050
+ contentCallable,
1051
+ recordsById,
1052
+ ),
1053
+ );
1054
+ if (textCalls.length !== 1) {
1055
+ return feedbackMessage(
1056
+ contentBinding,
1057
+ jvm,
1058
+ state,
1059
+ contentExpression ? text(contentExpression, "source") : undefined,
1060
+ );
1061
+ }
1062
+ const textBinding = preferredBinding(
1063
+ jvm,
1064
+ textCalls[0].id,
1065
+ new Set(["text"]),
1066
+ 0,
1067
+ );
1068
+ return feedbackMessage(
1069
+ textBinding,
1070
+ jvm,
1071
+ state,
1072
+ text(textCalls[0].data, "source"),
1073
+ );
1074
+ }
1075
+ function addComposableSnackbarEffects(jvm, state) {
1076
+ const recordsById = new Map(jvm.body.map((record) => [record.id, record]));
1077
+ const parentCalls = lambdaParentCalls(jvm);
1078
+ for (const snackbar of jvm.body) {
1079
+ if (
1080
+ snackbar.kind !== "resolved-call" ||
1081
+ text(snackbar.data, "targetName") !== "Snackbar" ||
1082
+ !composeSnackbarOwners.has(
1083
+ text(snackbar.data, "targetOwnerQualifiedName") ?? "",
1084
+ )
1085
+ )
1086
+ continue;
1087
+ const letCall = nearestLetCall(snackbar, recordsById, parentCalls);
1088
+ const stateKey = letCall ? letReceiverStateKey(letCall, jvm) : undefined;
1089
+ if (!letCall || !stateKey) continue;
1090
+ const message = composableSnackbarMessage(
1091
+ snackbar,
1092
+ jvm,
1093
+ state,
1094
+ recordsById,
1095
+ );
1096
+ for (const assignment of jvm.body) {
1097
+ if (
1098
+ assignment.kind !== "assignment" ||
1099
+ symbolKey(object(assignment.data.left)) !== stateKey ||
1100
+ !provablyNonNull(assignment)
1101
+ )
1102
+ continue;
1103
+ const callbackRef = effectCallbackForCall(jvm, state, assignment);
1104
+ if (!callbackRef) continue;
1105
+ const conditionKeys = structuredConditionKeys(
1106
+ assignment,
1107
+ callbackRef,
1108
+ recordsById,
1109
+ state,
1110
+ "feedback",
1111
+ );
1112
+ addComposeFeedbackEffect(
1113
+ state,
1114
+ callbackRef,
1115
+ text(snackbar.data, "targetSignature") ??
1116
+ `${text(snackbar.data, "targetOwnerQualifiedName")}#Snackbar`,
1117
+ snackbar,
1118
+ message,
1119
+ [
1120
+ {
1121
+ record: assignment,
1122
+ role: "structured non-null assignment activates Snackbar state",
1123
+ },
1124
+ {
1125
+ record: snackbar,
1126
+ role: "resolved Compose Snackbar presentation",
1127
+ },
1128
+ ],
1129
+ conditionKeys,
1130
+ stateKey,
1131
+ );
1132
+ }
1133
+ }
1134
+ }
1135
+ function addComposeFeedbackEffects(jvm, state) {
1136
+ addSnackbarHostStateEffects(jvm, state);
1137
+ addComposableSnackbarEffects(jvm, state);
1138
+ }
1139
+ /**
1140
+ * Adds interaction effects that are intrinsic to a resolved UI API and then
1141
+ * classifies structured assignments. It never parses assignment source text.
1142
+ */
1143
+ export function normalizeInteractionEffects(jvm, state) {
1144
+ if (!jvm) return;
1145
+ addInteractionIntrinsicEffects(jvm, state);
1146
+ addAssignmentEffects(jvm, state);
1147
+ addComposeSurfaceStateEffects(jvm, state);
1148
+ addComposeFeedbackEffects(jvm, state);
1149
+ }