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.
- package/agents/api-analyzer.md +113 -0
- package/agents/app-action.md +122 -0
- package/agents/code-reviewer.md +191 -295
- package/agents/coder.md +377 -0
- package/agents/self-tester.md +8 -8
- package/agents/spec-designer.md +56 -0
- package/package.json +1 -1
- package/skills/a2h-app-req-gen/SKILL.md +174 -0
- package/skills/a2h-app-req-gen/references/spec-handoff.md +99 -0
- package/skills/a2h-app-req-gen/references/subagent-render.md +115 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/build.gradle.kts +41 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradle/wrapper/gradle-wrapper.properties +8 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradle.properties +3 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradlew +251 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradlew.bat +94 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/settings.gradle.kts +17 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawAnalysisDetector.java +1911 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawIssueRegistry.java +37 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawJson.java +146 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawLocation.java +96 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawRecordWriter.java +227 -0
- 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
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/resources/dev/reqgen/analyzer/jvm/raw-record-v1.schema.json +93 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/assets/dashboard.html +151 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/gradle/reqgen-lint.init.gradle +60 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/gradle/reqgen-model.init.gradle +204 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/package.json +14 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/backend.js +323 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/cli.js +811 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/gap-audit-link.js +62 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/index.js +66 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/merge.js +605 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/name-trace.js +153 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/orchestrate.js +1324 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/output-parse.js +50 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/overview.js +10 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/pool.js +23 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/post-merge-fix.js +146 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/prompt-loader.js +51 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/prompt.js +967 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/prompts.md +301 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/serve.js +78 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/short-id.js +46 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/snapshot.js +267 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/types.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/validate.js +122 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/callback-call-sites.js +62 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/gap-audit/build.js +1291 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/gap-audit/index.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/area-ownership.js +392 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/build.js +1995 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/callback-flow.js +277 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/component-variants.js +725 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/index.js +15 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/names/index.js +12 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/names/policy.js +469 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/names/traceability.js +468 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/req-model/build.js +1066 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/req-model/index.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/android-interaction-registry.js +174 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/android.js +1062 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/area-titles.js +678 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/builder.js +350 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/compose-api-registry.js +395 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/compose-call-sites.js +2008 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/compose.js +9059 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/dynamic-repeated-call-sites.js +529 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/index.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/interaction-effects.js +1149 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/jvm.js +6272 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/normalize.js +33 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/pending-intents.js +565 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/resource-id.js +42 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/route-state-variants.js +781 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/state.js +234 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/types.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/android/extract.js +40 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/android/index.js +6 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/android/manifest.js +513 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/android/resources.js +398 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/android/types.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/android/views.js +447 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/android/xml-parser.js +175 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/cli.js +398 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/area-graph.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/artifact.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/common.js +16 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/diagnostic.js +10 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/gap-audit.js +14 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/index.js +11 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/name-traceability.js +26 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/raw-fact.js +40 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/req-model.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/run.js +19 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/semantic-completeness.js +11 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/source-facts.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/core/output-guard.js +41 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/core/stable-id.js +92 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/dashboard-cli.js +169 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/diagnostics.js +104 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/dump/dump.js +1028 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/dump/index.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/dump-cli.js +251 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/artifacts.js +49 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/index.js +18 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/quality-report.js +125 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/render.js +353 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/types.js +2 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/graph/paths.js +474 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/jvm/index.js +15 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/jvm/protocol.js +408 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/jvm/runner.js +452 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/pipeline/index.js +8 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/pipeline/output.js +165 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/pipeline/run-stage1.js +891 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/project/discovery.js +299 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/project/gradle-model.js +358 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/project/gradle-runner.js +165 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/project/resource-profile.js +60 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/project/types.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/spec-handoff-cli.js +151 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/spec-handoff.js +1004 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/stage2-cli.js +109 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/stage2-input.js +419 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/stage2-name-trace-input.js +95 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/area-graph.js +444 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/index.js +7 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/req-model.js +580 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/req-text.js +195 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/semantic-area-graph.js +1431 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/types.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen-audit.mjs +298 -0
- package/skills/a2h-app-req-gen/scripts/reqgen-selfcheck.mjs +555 -0
- package/skills/a2h-code-review/SKILL.md +379 -0
- package/skills/{hmos-integration-test → a2h-integration-test}/README.md +6 -6
- package/skills/{hmos-integration-test → a2h-integration-test}/SKILL.md +6 -6
- package/skills/{hmos-integration-test → a2h-integration-test}/scripts/report-tool.mjs +1 -1
- package/skills/a2h-spec-design/SKILL.md +330 -0
- package/skills/a2h-spec-design/scripts/inspect_spec_design_inputs.mjs +207 -0
- package/skills/a2h-spec-design/scripts/move_spec_technical_reference.mjs +198 -0
- package/skills/a2h-spec-design/scripts/validate_spec_design_output.mjs +256 -0
- package/skills/a2h-spec-generate/SKILL.md +696 -0
- package/skills/a2h-spec-generate/references/trace-template.md +115 -0
- package/skills/a2h-spec-generate/scripts/parse_requirements.mjs +537 -0
- package/skills/a2h-spec-generate/template/REQ.txt +22 -0
- package/skills/a2h-spec-generate/template/REQ.xlsx +0 -0
- package/skills/a2h-spec-implement/SKILL.md +548 -0
- package/skills/a2h-spec-implement/references/harmony-pitfalls.md +186 -0
- package/skills/a2h-spec-implement/references/l1-unit-test.md +185 -0
- package/skills/a2h-spec-implement/references/l3-e2e-uitest.md +167 -0
- package/skills/a2h-spec-implement/references/test-core.md +132 -0
- package/skills/a2h-spec-implement/rules/arkts/arkts-standards.md +184 -0
- package/skills/a2h-spec-implement/rules/arkts/conventions/coding-style.md +96 -0
- package/skills/a2h-spec-implement/rules/arkts/conventions/security.md +126 -0
- package/skills/a2h-spec-implement/rules/arkts/language/arkts-rules.md +650 -0
- package/skills/a2h-spec-implement/rules/arkts/language/arkui-structure-rules.md +173 -0
- package/skills/a2h-spec-implement/rules/arkts/language/ts-to-arkts-rewrites.md +58 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/skills/a2h-spec-implement/rules/arkts/ui/component-cookbook.md +431 -0
- package/skills/a2h-spec-implement/rules/arkts/ui/state-management.md +152 -0
- package/skills/a2h-spec-implement/rules/arkts/ui/ui-quality.md +67 -0
- package/skills/a2h-spec-implement/rules/arkts/ui/ui-runtime-diagnosis.md +32 -0
- package/skills/a2h-spec-implement/rules/pipeline/build/package-set-collection.md +43 -0
- package/skills/a2h-spec-implement/scripts/_lib/common.mjs +97 -0
- package/skills/a2h-spec-implement/scripts/_lib/config.mjs +70 -0
- package/skills/a2h-spec-implement/scripts/_lib/lessons_cli.mjs +48 -0
- package/skills/a2h-spec-implement/scripts/build_lessons.mjs +24 -0
- package/skills/a2h-spec-implement/scripts/code_pattern_lessons.mjs +26 -0
- package/skills/a2h-spec-implement/scripts/device_ui.mjs +433 -0
- package/skills/a2h-spec-implement/scripts/device_ui_core.mjs +895 -0
- package/skills/a2h-spec-implement/scripts/ledger/aggregate_run.mjs +207 -0
- package/skills/a2h-spec-implement/scripts/manifest/emit_summary_line.mjs +37 -0
- package/skills/a2h-spec-implement/scripts/manifest/finalize_run.mjs +115 -0
- package/skills/a2h-spec-implement/scripts/manifest/render_final_summary.mjs +78 -0
- package/skills/a2h-spec-implement/scripts/manifest/root_mirror.mjs +137 -0
- package/skills/a2h-spec-implement/scripts/package.json +12 -0
- package/skills/a2h-spec-implement/scripts/state_propagation_check.mjs +676 -0
- package/skills/a2h-spec-implement/scripts/test/l2_run.mjs +244 -0
- package/skills/a2h-spec-implement/scripts/test/tp_coverage.mjs +231 -0
- package/skills/a2h-spec-implement/scripts/ui_memory.mjs +161 -0
- package/skills/a2h-spec-implement/scripts/utils/ensure_app.mjs +134 -0
- package/skills/a2h-ui-transfer/SKILL.md +20 -9
- package/skills/a2h-ui-transfer/scripts/arkts_static_check.js +80 -0
- package/skills/hmos-convert-pipeline/SKILL.md +7 -5
- package/skills/hmos-incremental-ui-align/README.md +7 -7
- package/skills/hmos-incremental-ui-align/SKILL.md +1 -1
- package/skills/hmos-incremental-ui-align/page_align.md +1 -1
- package/skills/hmos-spec-generate/SKILL.md +2 -2
- package/skills/hmos-spec-generate/scripts/parse_requirements.mjs +537 -0
- package/skills/hmos-fix-build-errors/SKILL.md +0 -266
- package/skills/hmos-fix-build-errors/references/arkts-strict-patterns.md +0 -219
- package/skills/hmos-fix-build-errors/references/known-patterns.md +0 -157
- package/skills/hmos-fix-build-errors/references/rdb-entity-pattern.md +0 -131
- package/skills/hmos-spec-generate/scripts/parse_requirements.ts +0 -515
- /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/resolve-metadata-tool.mjs +0 -0
- /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/self-test-runner.mjs +0 -0
- /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/testcases-tool.mjs +0 -0
|
@@ -0,0 +1,1291 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
defineDiagnostic,
|
|
4
|
+
GAP_AUDIT_CONTRACT_VERSION,
|
|
5
|
+
} from "../../contracts/index.js";
|
|
6
|
+
import { resolveAndroidInteractionApi } from "../source-facts/android-interaction-registry.js";
|
|
7
|
+
import {
|
|
8
|
+
COMPOSE_API_SEMANTIC_REGISTRY,
|
|
9
|
+
composeSourceApiName,
|
|
10
|
+
resolveComposeControlApi,
|
|
11
|
+
resolveComposePagerStateFactoryApi,
|
|
12
|
+
resolveComposePagerTransitionApi,
|
|
13
|
+
resolveComposeSurfaceApi,
|
|
14
|
+
} from "../source-facts/compose-api-registry.js";
|
|
15
|
+
const unsupportedComposeSurfaceApis = new Set(["Popup", "DropdownMenu"]);
|
|
16
|
+
const infrastructureCallbackApis = new Map([
|
|
17
|
+
[
|
|
18
|
+
"setOnApplyWindowInsetsListener",
|
|
19
|
+
"Window-inset delivery is layout infrastructure, not a user action.",
|
|
20
|
+
],
|
|
21
|
+
[
|
|
22
|
+
"addOnLayoutChangeListener",
|
|
23
|
+
"Layout-change delivery is rendering infrastructure, not a user action.",
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
"addOnAttachStateChangeListener",
|
|
27
|
+
"View attachment delivery is lifecycle infrastructure, not a user action.",
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
"setOnFocusChangeListener",
|
|
31
|
+
"Focus delivery does not by itself prove a product action.",
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
"setOnScrollChangeListener",
|
|
35
|
+
"Scroll observation does not by itself prove a product action.",
|
|
36
|
+
],
|
|
37
|
+
[
|
|
38
|
+
"addOnScrollListener",
|
|
39
|
+
"Scroll observation does not by itself prove a product action.",
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
"addOnGlobalLayoutListener",
|
|
43
|
+
"Global-layout delivery is rendering infrastructure, not a user action.",
|
|
44
|
+
],
|
|
45
|
+
[
|
|
46
|
+
"addOnPreDrawListener",
|
|
47
|
+
"Pre-draw delivery is rendering infrastructure, not a user action.",
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
"addOnDrawListener",
|
|
51
|
+
"Draw delivery is rendering infrastructure, not a user action.",
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
"addUpdateListener",
|
|
55
|
+
"Animation progress is presentation infrastructure, not a user action.",
|
|
56
|
+
],
|
|
57
|
+
[
|
|
58
|
+
"withEndAction",
|
|
59
|
+
"Animation completion is presentation infrastructure, not a user action.",
|
|
60
|
+
],
|
|
61
|
+
[
|
|
62
|
+
"doOnStart",
|
|
63
|
+
"Animation lifecycle is presentation infrastructure, not a user action.",
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
"doOnEnd",
|
|
67
|
+
"Animation lifecycle is presentation infrastructure, not a user action.",
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
"doOnCancel",
|
|
71
|
+
"Animation lifecycle is presentation infrastructure, not a user action.",
|
|
72
|
+
],
|
|
73
|
+
[
|
|
74
|
+
"doOnRepeat",
|
|
75
|
+
"Animation lifecycle is presentation infrastructure, not a user action.",
|
|
76
|
+
],
|
|
77
|
+
["post", "Posted execution is scheduling infrastructure, not a user action."],
|
|
78
|
+
[
|
|
79
|
+
"postDelayed",
|
|
80
|
+
"Posted execution is scheduling infrastructure, not a user action.",
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
"runOnUiThread",
|
|
84
|
+
"UI-thread dispatch is scheduling infrastructure, not a user action.",
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
"observe",
|
|
88
|
+
"State observation is data/lifecycle infrastructure; visible uses are audited separately.",
|
|
89
|
+
],
|
|
90
|
+
[
|
|
91
|
+
"observeForever",
|
|
92
|
+
"State observation is data/lifecycle infrastructure; visible uses are audited separately.",
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
"addObserver",
|
|
96
|
+
"Lifecycle observation is infrastructure, not a user action.",
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
"registerForActivityResult",
|
|
100
|
+
"Activity-result registration is callback plumbing; its launcher and result effects are normalized separately.",
|
|
101
|
+
],
|
|
102
|
+
[
|
|
103
|
+
"enqueue",
|
|
104
|
+
"Asynchronous completion is data-flow infrastructure, not a user action.",
|
|
105
|
+
],
|
|
106
|
+
]);
|
|
107
|
+
const unsupportedPagerSemanticApis = new Set([
|
|
108
|
+
"Pager",
|
|
109
|
+
"LeadingIconTab",
|
|
110
|
+
"requestScrollToPage",
|
|
111
|
+
]);
|
|
112
|
+
const presentationalTabLayoutApis = new Set([
|
|
113
|
+
"PrimaryTabRow",
|
|
114
|
+
"SecondaryTabRow",
|
|
115
|
+
"TabRow",
|
|
116
|
+
"ScrollableTabRow",
|
|
117
|
+
]);
|
|
118
|
+
const navigationApis = new Set([
|
|
119
|
+
"NavHost",
|
|
120
|
+
"composable",
|
|
121
|
+
"navigation",
|
|
122
|
+
"navigate",
|
|
123
|
+
"navigateUp",
|
|
124
|
+
"popBackStack",
|
|
125
|
+
"findNavController",
|
|
126
|
+
"startActivity",
|
|
127
|
+
"startActivityForResult",
|
|
128
|
+
"finish",
|
|
129
|
+
"finishAffinity",
|
|
130
|
+
"onBackPressed",
|
|
131
|
+
]);
|
|
132
|
+
const asyncApis = new Set([
|
|
133
|
+
"emit",
|
|
134
|
+
"tryEmit",
|
|
135
|
+
"collect",
|
|
136
|
+
"collectLatest",
|
|
137
|
+
"collectAsState",
|
|
138
|
+
"collectAsStateWithLifecycle",
|
|
139
|
+
"onEach",
|
|
140
|
+
"launchIn",
|
|
141
|
+
"send",
|
|
142
|
+
"trySend",
|
|
143
|
+
"receive",
|
|
144
|
+
]);
|
|
145
|
+
const stateObservationApis = new Set([
|
|
146
|
+
"collectAsState",
|
|
147
|
+
"collectAsStateWithLifecycle",
|
|
148
|
+
]);
|
|
149
|
+
const eventProducerApis = new Set(["emit", "tryEmit", "send", "trySend"]);
|
|
150
|
+
const categorySpecs = [
|
|
151
|
+
{
|
|
152
|
+
id: "manifest-components",
|
|
153
|
+
label: "Manifest component declarations",
|
|
154
|
+
expectedFactKinds: ["component"],
|
|
155
|
+
producer: "android",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "manifest-entries",
|
|
159
|
+
label: "Manifest entry declarations",
|
|
160
|
+
expectedFactKinds: ["entry-point"],
|
|
161
|
+
producer: "android",
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: "actionable-xml-controls",
|
|
165
|
+
label: "Actionable XML controls",
|
|
166
|
+
expectedFactKinds: ["ui-element"],
|
|
167
|
+
producer: "android",
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: "resolved-listener-interactions",
|
|
171
|
+
label: "Android listener interactions",
|
|
172
|
+
expectedFactKinds: ["interaction-binding"],
|
|
173
|
+
producer: "jvm",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: "resolved-compose-controls",
|
|
177
|
+
label: "Compose interaction controls",
|
|
178
|
+
expectedFactKinds: ["ui-element", "interaction-binding"],
|
|
179
|
+
producer: "jvm",
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: "resolved-compose-surfaces",
|
|
183
|
+
label: "Compose Area surfaces",
|
|
184
|
+
expectedFactKinds: ["component", "container-binding"],
|
|
185
|
+
producer: "jvm",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: "resolved-compose-ui",
|
|
189
|
+
label: "Other Compose UI calls",
|
|
190
|
+
expectedFactKinds: [
|
|
191
|
+
"component",
|
|
192
|
+
"ui-element",
|
|
193
|
+
"ui-property",
|
|
194
|
+
"container-binding",
|
|
195
|
+
"callback-flow",
|
|
196
|
+
],
|
|
197
|
+
producer: "jvm",
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: "resolved-navigation-calls",
|
|
201
|
+
label: "Navigation and exit calls",
|
|
202
|
+
expectedFactKinds: [
|
|
203
|
+
"component",
|
|
204
|
+
"behavior-effect",
|
|
205
|
+
"navigation-target",
|
|
206
|
+
"container-binding",
|
|
207
|
+
],
|
|
208
|
+
producer: "jvm",
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: "resolved-pager-tab-calls",
|
|
212
|
+
label: "Pager and tab calls",
|
|
213
|
+
expectedFactKinds: [
|
|
214
|
+
"component",
|
|
215
|
+
"ui-element",
|
|
216
|
+
"interaction-binding",
|
|
217
|
+
"container-binding",
|
|
218
|
+
"behavior-effect",
|
|
219
|
+
"navigation-target",
|
|
220
|
+
],
|
|
221
|
+
producer: "jvm",
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
id: "resolved-async-calls",
|
|
225
|
+
label: "Asynchronous event-flow calls",
|
|
226
|
+
expectedFactKinds: ["callback-flow", "behavior-effect"],
|
|
227
|
+
producer: "jvm",
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
id: "other-resolved-calls",
|
|
231
|
+
label: "Calls outside the semantic census",
|
|
232
|
+
expectedFactKinds: [],
|
|
233
|
+
producer: "jvm",
|
|
234
|
+
intentionallyUnassessed: true,
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
function text(record, key) {
|
|
238
|
+
const value = record.data[key];
|
|
239
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
240
|
+
}
|
|
241
|
+
function sourceKind(record) {
|
|
242
|
+
const language = text(record, "language");
|
|
243
|
+
if (language === "kotlin" || record.location?.path.endsWith(".kt")) {
|
|
244
|
+
return "kotlin";
|
|
245
|
+
}
|
|
246
|
+
if (language === "java" || record.location?.path.endsWith(".java")) {
|
|
247
|
+
return "java";
|
|
248
|
+
}
|
|
249
|
+
return "other";
|
|
250
|
+
}
|
|
251
|
+
function rawLocation(record) {
|
|
252
|
+
if (!record.location) return undefined;
|
|
253
|
+
const startLine = Math.max(1, record.location.startLine);
|
|
254
|
+
return {
|
|
255
|
+
sourceKind: sourceKind(record),
|
|
256
|
+
path: record.location.path,
|
|
257
|
+
anchor: `raw-record:${record.id}`,
|
|
258
|
+
range: {
|
|
259
|
+
startLine,
|
|
260
|
+
endLine: Math.max(startLine, record.location.endLine),
|
|
261
|
+
...(record.location.startColumn >= 1
|
|
262
|
+
? { startColumn: record.location.startColumn }
|
|
263
|
+
: {}),
|
|
264
|
+
...(record.location.endColumn >= 1
|
|
265
|
+
? { endColumn: record.location.endColumn }
|
|
266
|
+
: {}),
|
|
267
|
+
},
|
|
268
|
+
...(text(record, "targetSignature")
|
|
269
|
+
? { symbol: text(record, "targetSignature") }
|
|
270
|
+
: {}),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* The broad census and normalizers consume the same raw source locations.
|
|
275
|
+
* Columns are required when present so two calls on one line cannot credit
|
|
276
|
+
* each other with an unrelated fact.
|
|
277
|
+
*/
|
|
278
|
+
function locationKey(location) {
|
|
279
|
+
return JSON.stringify({
|
|
280
|
+
path: location.path
|
|
281
|
+
.trim()
|
|
282
|
+
.replaceAll("\\", "/")
|
|
283
|
+
.replace(/^\.\/+/u, "")
|
|
284
|
+
.replace(/\/{2,}/gu, "/"),
|
|
285
|
+
startLine: location.range.startLine,
|
|
286
|
+
endLine: location.range.endLine,
|
|
287
|
+
startColumn: location.range.startColumn ?? null,
|
|
288
|
+
endColumn: location.range.endColumn ?? null,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
function candidateId(draft) {
|
|
292
|
+
const digest = createHash("sha256")
|
|
293
|
+
.update(
|
|
294
|
+
JSON.stringify({
|
|
295
|
+
category: draft.category,
|
|
296
|
+
source: draft.source,
|
|
297
|
+
semanticKey: draft.semanticKey,
|
|
298
|
+
evidence: draft.evidence
|
|
299
|
+
? {
|
|
300
|
+
path: draft.evidence.path,
|
|
301
|
+
anchor: draft.evidence.anchor,
|
|
302
|
+
range: draft.evidence.range,
|
|
303
|
+
symbol: draft.evidence.symbol ?? null,
|
|
304
|
+
}
|
|
305
|
+
: null,
|
|
306
|
+
rawRecordRef: draft.rawRecordRef ?? null,
|
|
307
|
+
}),
|
|
308
|
+
)
|
|
309
|
+
.digest("hex")
|
|
310
|
+
.slice(0, 20);
|
|
311
|
+
return `candidate:gap-audit/${draft.category}/${digest}`;
|
|
312
|
+
}
|
|
313
|
+
function supportIndex(source) {
|
|
314
|
+
if (!source) return new Map();
|
|
315
|
+
const evidence = new Map(source.evidence.map((item) => [item.id, item]));
|
|
316
|
+
const result = new Map();
|
|
317
|
+
for (const fact of source.facts) {
|
|
318
|
+
const keys = new Set(
|
|
319
|
+
fact.support.flatMap((support) => {
|
|
320
|
+
const location = evidence.get(support.evidenceRef);
|
|
321
|
+
return location ? [locationKey(location)] : [];
|
|
322
|
+
}),
|
|
323
|
+
);
|
|
324
|
+
for (const key of keys) {
|
|
325
|
+
const facts = result.get(key) ?? [];
|
|
326
|
+
facts.push(fact);
|
|
327
|
+
result.set(key, facts);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return result;
|
|
331
|
+
}
|
|
332
|
+
function componentDraft(component) {
|
|
333
|
+
const expectedNames = new Set([
|
|
334
|
+
component.name,
|
|
335
|
+
...(component.targetActivity ? [component.targetActivity] : []),
|
|
336
|
+
]);
|
|
337
|
+
return {
|
|
338
|
+
category: "manifest-components",
|
|
339
|
+
source: "android-xml",
|
|
340
|
+
semanticKey: [
|
|
341
|
+
component.kind,
|
|
342
|
+
component.name,
|
|
343
|
+
component.targetActivity ?? "",
|
|
344
|
+
].join(":"),
|
|
345
|
+
evidence: component.evidence,
|
|
346
|
+
expectedFactKinds: ["component"],
|
|
347
|
+
matches: (fact) =>
|
|
348
|
+
fact.kind === "component" && expectedNames.has(fact.data.qualifiedName),
|
|
349
|
+
critical: true,
|
|
350
|
+
policy: "normalize",
|
|
351
|
+
manifestComponent: component,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function entryDraft(entry) {
|
|
355
|
+
return {
|
|
356
|
+
category: "manifest-entries",
|
|
357
|
+
source: "android-xml",
|
|
358
|
+
semanticKey: [
|
|
359
|
+
entry.kind,
|
|
360
|
+
entry.category,
|
|
361
|
+
entry.componentName,
|
|
362
|
+
entry.action,
|
|
363
|
+
entry.uri ?? "",
|
|
364
|
+
].join(":"),
|
|
365
|
+
evidence: entry.evidence,
|
|
366
|
+
expectedFactKinds: ["entry-point"],
|
|
367
|
+
matches: (fact) =>
|
|
368
|
+
fact.kind === "entry-point" &&
|
|
369
|
+
fact.data.entryKind === entry.kind &&
|
|
370
|
+
fact.data.action === entry.action &&
|
|
371
|
+
fact.data.uri === entry.uri,
|
|
372
|
+
critical: true,
|
|
373
|
+
policy: "normalize",
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function viewDraft(view) {
|
|
377
|
+
return {
|
|
378
|
+
category: "actionable-xml-controls",
|
|
379
|
+
source: "android-xml",
|
|
380
|
+
semanticKey: [
|
|
381
|
+
`${view.resourceType}/${view.resourceName}`,
|
|
382
|
+
view.id ?? view.tag,
|
|
383
|
+
view.onClick ?? "",
|
|
384
|
+
].join(":"),
|
|
385
|
+
evidence: view.evidence,
|
|
386
|
+
expectedFactKinds: ["ui-element"],
|
|
387
|
+
matches: (fact) =>
|
|
388
|
+
fact.kind === "ui-element" &&
|
|
389
|
+
fact.data.actionable &&
|
|
390
|
+
(view.id === undefined || fact.data.resourceId === view.id),
|
|
391
|
+
critical: true,
|
|
392
|
+
policy: "normalize",
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function apiName(bindingApi) {
|
|
396
|
+
const beforeParameters = bindingApi.split("(", 1)[0] ?? bindingApi;
|
|
397
|
+
const hash = beforeParameters.lastIndexOf("#");
|
|
398
|
+
const dot = beforeParameters.lastIndexOf(".");
|
|
399
|
+
return beforeParameters.slice(Math.max(hash, dot) + 1);
|
|
400
|
+
}
|
|
401
|
+
function callDraft(
|
|
402
|
+
record,
|
|
403
|
+
category,
|
|
404
|
+
expectedFactKinds,
|
|
405
|
+
critical,
|
|
406
|
+
policy,
|
|
407
|
+
matches,
|
|
408
|
+
reason,
|
|
409
|
+
) {
|
|
410
|
+
const signature =
|
|
411
|
+
text(record, "targetSignature") ??
|
|
412
|
+
text(record, "targetName") ??
|
|
413
|
+
text(record, "methodName") ??
|
|
414
|
+
record.id;
|
|
415
|
+
return {
|
|
416
|
+
category,
|
|
417
|
+
source: "raw-jvm",
|
|
418
|
+
semanticKey: signature,
|
|
419
|
+
evidence: rawLocation(record),
|
|
420
|
+
rawRecordRef: record.id,
|
|
421
|
+
expectedFactKinds,
|
|
422
|
+
matches,
|
|
423
|
+
critical,
|
|
424
|
+
policy,
|
|
425
|
+
...(reason ? { reason } : {}),
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function isComposeOwner(owner) {
|
|
429
|
+
return (
|
|
430
|
+
owner.startsWith("androidx.compose.") ||
|
|
431
|
+
owner.startsWith("androidx.navigation.compose.")
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
function isAsyncOwner(owner) {
|
|
435
|
+
return /(?:kotlinx\.coroutines|Flow|SharedFlow|StateFlow|Channel)/u.test(
|
|
436
|
+
owner,
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
function isPagerTabOwner(owner) {
|
|
440
|
+
return (
|
|
441
|
+
isComposeOwner(owner) ||
|
|
442
|
+
owner.startsWith("com.google.accompanist.pager.") ||
|
|
443
|
+
/(?:^|\.)(?:Pager|PagerState|TabRow)/u.test(owner)
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
function registryContainsName(entries, name) {
|
|
447
|
+
return entries.some((entry) => entry.selector.apiNames.includes(name));
|
|
448
|
+
}
|
|
449
|
+
function isRegisteredComposeControlName(name) {
|
|
450
|
+
return registryContainsName(COMPOSE_API_SEMANTIC_REGISTRY.controls, name);
|
|
451
|
+
}
|
|
452
|
+
function isRegisteredComposeSurfaceName(name) {
|
|
453
|
+
return registryContainsName(COMPOSE_API_SEMANTIC_REGISTRY.surfaces, name);
|
|
454
|
+
}
|
|
455
|
+
function isRegisteredPagerTransitionName(name) {
|
|
456
|
+
return registryContainsName(
|
|
457
|
+
COMPOSE_API_SEMANTIC_REGISTRY.pagerTransitions,
|
|
458
|
+
name,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
function isRegisteredPagerStateFactoryName(name) {
|
|
462
|
+
return registryContainsName(
|
|
463
|
+
COMPOSE_API_SEMANTIC_REGISTRY.pagerStateFactories,
|
|
464
|
+
name,
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
function object(value) {
|
|
468
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
469
|
+
? value
|
|
470
|
+
: undefined;
|
|
471
|
+
}
|
|
472
|
+
function objectText(value, key) {
|
|
473
|
+
const candidate = value?.[key];
|
|
474
|
+
return typeof candidate === "string" && candidate.trim().length > 0
|
|
475
|
+
? candidate.trim()
|
|
476
|
+
: undefined;
|
|
477
|
+
}
|
|
478
|
+
function eventCarrierTypes(record) {
|
|
479
|
+
const receiver = object(record.data.receiverResolvedSymbol);
|
|
480
|
+
return [
|
|
481
|
+
text(record, "targetOwnerQualifiedName"),
|
|
482
|
+
text(record, "receiverType"),
|
|
483
|
+
objectText(receiver, "type"),
|
|
484
|
+
objectText(receiver, "ownerQualifiedName"),
|
|
485
|
+
objectText(receiver, "qualifiedName"),
|
|
486
|
+
]
|
|
487
|
+
.filter((value) => value !== undefined)
|
|
488
|
+
.join(" ");
|
|
489
|
+
}
|
|
490
|
+
function isExplicitEventProducer(record, name) {
|
|
491
|
+
const types = eventCarrierTypes(record);
|
|
492
|
+
if (name === "tryEmit") {
|
|
493
|
+
return /(?:Mutable)?SharedFlow/u.test(types);
|
|
494
|
+
}
|
|
495
|
+
if (name === "emit") {
|
|
496
|
+
/*
|
|
497
|
+
* An unqualified FlowCollector.emit inside flow { } is a data emission,
|
|
498
|
+
* not independently a product event. An explicitly typed SharedFlow is.
|
|
499
|
+
*/
|
|
500
|
+
return (
|
|
501
|
+
/(?:Mutable)?SharedFlow/u.test(types) &&
|
|
502
|
+
Boolean(text(record, "receiverSource"))
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
return /(?:^|[.< ])(?:Channel|SendChannel)(?:[> ]|$)/u.test(types);
|
|
506
|
+
}
|
|
507
|
+
function isLikelyExternalComposable(owner, signature) {
|
|
508
|
+
return (
|
|
509
|
+
owner.startsWith("com.google.accompanist.") ||
|
|
510
|
+
owner.includes(".compose.") ||
|
|
511
|
+
signature.includes("androidx.compose.runtime.Composer")
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
function localComposableSignatures(jvm) {
|
|
515
|
+
return new Set(
|
|
516
|
+
jvm.body.flatMap((record) => {
|
|
517
|
+
if (record.kind !== "method-declaration") return [];
|
|
518
|
+
const annotations = Array.isArray(record.data.annotations)
|
|
519
|
+
? record.data.annotations
|
|
520
|
+
: [];
|
|
521
|
+
const composable =
|
|
522
|
+
record.data.composable === true ||
|
|
523
|
+
annotations.some(
|
|
524
|
+
(annotation) =>
|
|
525
|
+
typeof annotation === "string" &&
|
|
526
|
+
/(?:^|\.)Composable$/u.test(annotation),
|
|
527
|
+
);
|
|
528
|
+
const signature = text(record, "signature");
|
|
529
|
+
return composable && signature ? [signature] : [];
|
|
530
|
+
}),
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
function isCallbackBinding(record) {
|
|
534
|
+
if (record.kind !== "call-argument-binding") return false;
|
|
535
|
+
const expression = object(record.data.expression);
|
|
536
|
+
const kind = text(record, "argumentKind") ?? "";
|
|
537
|
+
return (
|
|
538
|
+
record.data.isLambda === true ||
|
|
539
|
+
/^(?:lambda|callable-reference|object-literal|anonymous-class)$/u.test(
|
|
540
|
+
kind,
|
|
541
|
+
) ||
|
|
542
|
+
objectText(expression, "callableRef") !== undefined ||
|
|
543
|
+
objectText(expression, "callableReferenceRef") !== undefined ||
|
|
544
|
+
objectText(expression, "classRef") !== undefined
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
function callbackCarrierText(record, bindings) {
|
|
548
|
+
const receiver = object(record.data.receiverResolvedSymbol);
|
|
549
|
+
return [
|
|
550
|
+
text(record, "targetOwnerQualifiedName"),
|
|
551
|
+
text(record, "receiverType"),
|
|
552
|
+
objectText(receiver, "type"),
|
|
553
|
+
objectText(receiver, "ownerQualifiedName"),
|
|
554
|
+
...bindings.map((binding) => text(binding, "parameterType")),
|
|
555
|
+
]
|
|
556
|
+
.filter((value) => value !== undefined)
|
|
557
|
+
.join(" ");
|
|
558
|
+
}
|
|
559
|
+
function callbackInfrastructureReason(record, name, bindings) {
|
|
560
|
+
const exact = infrastructureCallbackApis.get(name);
|
|
561
|
+
if (exact) return exact;
|
|
562
|
+
const carriers = callbackCarrierText(record, bindings);
|
|
563
|
+
if (
|
|
564
|
+
/(?:^|\.)(?:animation|transition)(?:\.|$)/iu.test(carriers) ||
|
|
565
|
+
/(?:Animator|Animation|Transition)(?:Listener|Callback)/u.test(carriers)
|
|
566
|
+
) {
|
|
567
|
+
return "Animation or transition callbacks are presentation infrastructure, not user actions.";
|
|
568
|
+
}
|
|
569
|
+
if (
|
|
570
|
+
/(?:retrofit|okhttp|room|firebase|exoplayer|androidx\.media3|sensor|location)/iu.test(
|
|
571
|
+
carriers,
|
|
572
|
+
)
|
|
573
|
+
) {
|
|
574
|
+
return "This callback belongs to data, media, sensor, or service infrastructure rather than a UI interaction protocol.";
|
|
575
|
+
}
|
|
576
|
+
return undefined;
|
|
577
|
+
}
|
|
578
|
+
function isPotentialInteractionCallback(record, name, bindings) {
|
|
579
|
+
if (!bindings.some(isCallbackBinding)) return false;
|
|
580
|
+
const conventionalName =
|
|
581
|
+
/^(?:set|add)(?:On)?[A-Za-z0-9]*(?:Listener|Callback)$/u.test(name) ||
|
|
582
|
+
/^register(?:On)?[A-Za-z0-9]*Callback$/u.test(name);
|
|
583
|
+
if (!conventionalName) return false;
|
|
584
|
+
const owner = text(record, "targetOwnerQualifiedName") ?? "";
|
|
585
|
+
if (/^(?:java|javax|kotlin|kotlinx)\./u.test(owner)) return false;
|
|
586
|
+
const carriers = callbackCarrierText(record, bindings);
|
|
587
|
+
const uiCarrier =
|
|
588
|
+
/(?:android\.(?:view|widget)|androidx\.(?:appcompat|preference|recyclerview|swiperefreshlayout|viewpager)|com\.google\.android\.material)/u.test(
|
|
589
|
+
carriers,
|
|
590
|
+
) ||
|
|
591
|
+
/(?:View|Button|Menu|Preference|Adapter|Recycler|Pager|Tab|Search|Text|Picker|Slider|Rating|Chip|Toolbar)(?:[.$< ]|$)/u.test(
|
|
592
|
+
carriers,
|
|
593
|
+
);
|
|
594
|
+
const userEventName =
|
|
595
|
+
/(?:Click|Long|Checked|Select|Item|Menu|Query|Search|Refresh|Swipe|Drag|Touch|Submit|Preference|Rating|Tab|Page)/u.test(
|
|
596
|
+
name,
|
|
597
|
+
);
|
|
598
|
+
return uiCarrier || userEventName || owner.length > 0;
|
|
599
|
+
}
|
|
600
|
+
function classifyCall(record, localComposables, bindings) {
|
|
601
|
+
const rawName =
|
|
602
|
+
text(record, "targetName") ?? text(record, "methodName") ?? "";
|
|
603
|
+
const name = composeSourceApiName(rawName) ?? rawName;
|
|
604
|
+
const owner = text(record, "targetOwnerQualifiedName") ?? "";
|
|
605
|
+
const signature = text(record, "targetSignature") ?? "";
|
|
606
|
+
const composeControl = resolveComposeControlApi(owner, rawName);
|
|
607
|
+
const composeSurface = resolveComposeSurfaceApi(owner, rawName);
|
|
608
|
+
const pagerTransition = resolveComposePagerTransitionApi(owner, rawName);
|
|
609
|
+
const pagerStateFactory = resolveComposePagerStateFactoryApi(owner, rawName);
|
|
610
|
+
if (resolveAndroidInteractionApi(owner, name)) {
|
|
611
|
+
return callDraft(
|
|
612
|
+
record,
|
|
613
|
+
"resolved-listener-interactions",
|
|
614
|
+
["interaction-binding"],
|
|
615
|
+
true,
|
|
616
|
+
"normalize",
|
|
617
|
+
(fact) =>
|
|
618
|
+
fact.kind === "interaction-binding" &&
|
|
619
|
+
apiName(fact.data.bindingApi) === name,
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
if (
|
|
623
|
+
composeSurface?.componentType === "pager" ||
|
|
624
|
+
(!owner && (name === "HorizontalPager" || name === "VerticalPager"))
|
|
625
|
+
) {
|
|
626
|
+
return callDraft(
|
|
627
|
+
record,
|
|
628
|
+
"resolved-pager-tab-calls",
|
|
629
|
+
["component", "container-binding"],
|
|
630
|
+
true,
|
|
631
|
+
"normalize",
|
|
632
|
+
(fact) =>
|
|
633
|
+
(fact.kind === "component" &&
|
|
634
|
+
(fact.data.componentType === "pager" ||
|
|
635
|
+
fact.data.componentType === "pager-page")) ||
|
|
636
|
+
(fact.kind === "container-binding" &&
|
|
637
|
+
fact.data.bindingKind === "pager"),
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
if (composeControl?.elementType === "tab" || (!owner && name === "Tab")) {
|
|
641
|
+
return callDraft(
|
|
642
|
+
record,
|
|
643
|
+
"resolved-pager-tab-calls",
|
|
644
|
+
["ui-element", "interaction-binding"],
|
|
645
|
+
true,
|
|
646
|
+
"normalize",
|
|
647
|
+
(fact) =>
|
|
648
|
+
(fact.kind === "ui-element" && fact.data.elementType === "tab") ||
|
|
649
|
+
(fact.kind === "interaction-binding" && fact.data.trigger === "select"),
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
if (pagerTransition || (!owner && isRegisteredPagerTransitionName(name))) {
|
|
653
|
+
return callDraft(
|
|
654
|
+
record,
|
|
655
|
+
"resolved-pager-tab-calls",
|
|
656
|
+
["behavior-effect", "navigation-target"],
|
|
657
|
+
true,
|
|
658
|
+
"normalize",
|
|
659
|
+
(fact) =>
|
|
660
|
+
(fact.kind === "behavior-effect" &&
|
|
661
|
+
fact.data.effectKind === "navigate-area") ||
|
|
662
|
+
fact.kind === "navigation-target",
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
if (
|
|
666
|
+
unsupportedPagerSemanticApis.has(name) &&
|
|
667
|
+
(isPagerTabOwner(owner) || !owner)
|
|
668
|
+
) {
|
|
669
|
+
return callDraft(
|
|
670
|
+
record,
|
|
671
|
+
"resolved-pager-tab-calls",
|
|
672
|
+
categorySpecs.find((item) => item.id === "resolved-pager-tab-calls")
|
|
673
|
+
?.expectedFactKinds ?? [],
|
|
674
|
+
true,
|
|
675
|
+
"normalize",
|
|
676
|
+
() => true,
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
if (
|
|
680
|
+
pagerStateFactory ||
|
|
681
|
+
(!owner && isRegisteredPagerStateFactoryName(name))
|
|
682
|
+
) {
|
|
683
|
+
return callDraft(
|
|
684
|
+
record,
|
|
685
|
+
"resolved-pager-tab-calls",
|
|
686
|
+
[],
|
|
687
|
+
false,
|
|
688
|
+
"exclude",
|
|
689
|
+
() => false,
|
|
690
|
+
"The Pager state factory configures a Pager; it is not independently " +
|
|
691
|
+
"a user-visible Area, control, or transition.",
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
if (
|
|
695
|
+
presentationalTabLayoutApis.has(name) &&
|
|
696
|
+
(isPagerTabOwner(owner) || !owner)
|
|
697
|
+
) {
|
|
698
|
+
return callDraft(
|
|
699
|
+
record,
|
|
700
|
+
"resolved-pager-tab-calls",
|
|
701
|
+
[],
|
|
702
|
+
false,
|
|
703
|
+
"exclude",
|
|
704
|
+
() => false,
|
|
705
|
+
"The tab-row layout is presentational; its child Tab controls are " +
|
|
706
|
+
"audited independently.",
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
if (composeControl || (!owner && isRegisteredComposeControlName(name))) {
|
|
710
|
+
return callDraft(
|
|
711
|
+
record,
|
|
712
|
+
"resolved-compose-controls",
|
|
713
|
+
["ui-element", "interaction-binding"],
|
|
714
|
+
true,
|
|
715
|
+
"normalize",
|
|
716
|
+
(fact) => fact.kind === "ui-element" && fact.data.actionable,
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
if (composeSurface || (!owner && isRegisteredComposeSurfaceName(name))) {
|
|
720
|
+
return callDraft(
|
|
721
|
+
record,
|
|
722
|
+
"resolved-compose-surfaces",
|
|
723
|
+
["component", "container-binding"],
|
|
724
|
+
true,
|
|
725
|
+
"normalize",
|
|
726
|
+
(fact) => fact.kind === "component" || fact.kind === "container-binding",
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
if (
|
|
730
|
+
unsupportedComposeSurfaceApis.has(name) &&
|
|
731
|
+
(isComposeOwner(owner) || !owner)
|
|
732
|
+
) {
|
|
733
|
+
return callDraft(
|
|
734
|
+
record,
|
|
735
|
+
"resolved-compose-surfaces",
|
|
736
|
+
["component", "container-binding"],
|
|
737
|
+
true,
|
|
738
|
+
"normalize",
|
|
739
|
+
() => true,
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
if (
|
|
743
|
+
navigationApis.has(name) &&
|
|
744
|
+
(!owner ||
|
|
745
|
+
/(?:android|androidx\.navigation|NavController|Activity)/u.test(owner))
|
|
746
|
+
) {
|
|
747
|
+
if (name === "findNavController") {
|
|
748
|
+
return callDraft(
|
|
749
|
+
record,
|
|
750
|
+
"resolved-navigation-calls",
|
|
751
|
+
[],
|
|
752
|
+
false,
|
|
753
|
+
"exclude",
|
|
754
|
+
() => false,
|
|
755
|
+
"findNavController resolves navigation infrastructure but does not " +
|
|
756
|
+
"perform a user-visible transition.",
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
const expectedFactKinds =
|
|
760
|
+
name === "composable"
|
|
761
|
+
? ["component"]
|
|
762
|
+
: name === "NavHost" || name === "navigation"
|
|
763
|
+
? ["component", "container-binding"]
|
|
764
|
+
: ["behavior-effect", "navigation-target"];
|
|
765
|
+
return callDraft(
|
|
766
|
+
record,
|
|
767
|
+
"resolved-navigation-calls",
|
|
768
|
+
expectedFactKinds,
|
|
769
|
+
true,
|
|
770
|
+
"normalize",
|
|
771
|
+
(fact) =>
|
|
772
|
+
name === "composable"
|
|
773
|
+
? fact.kind === "component" &&
|
|
774
|
+
fact.data.componentType === "compose-destination"
|
|
775
|
+
: name === "NavHost"
|
|
776
|
+
? fact.kind === "container-binding" &&
|
|
777
|
+
fact.data.bindingKind === "navigation"
|
|
778
|
+
: true,
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
if (asyncApis.has(name) && (!owner || isAsyncOwner(owner))) {
|
|
782
|
+
if (stateObservationApis.has(name)) {
|
|
783
|
+
return callDraft(
|
|
784
|
+
record,
|
|
785
|
+
"resolved-async-calls",
|
|
786
|
+
[],
|
|
787
|
+
false,
|
|
788
|
+
"exclude",
|
|
789
|
+
() => false,
|
|
790
|
+
"State collection bridges a Flow into Compose state; downstream " +
|
|
791
|
+
"conditions, visible UI, and controls are audited at their uses.",
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
if (eventProducerApis.has(name)) {
|
|
795
|
+
if (!isExplicitEventProducer(record, name)) {
|
|
796
|
+
return callDraft(
|
|
797
|
+
record,
|
|
798
|
+
"resolved-async-calls",
|
|
799
|
+
[],
|
|
800
|
+
false,
|
|
801
|
+
"exclude",
|
|
802
|
+
() => false,
|
|
803
|
+
"This is a data-flow emission without an explicit SharedFlow or " +
|
|
804
|
+
"Channel event carrier; downstream product behavior is audited " +
|
|
805
|
+
"at its use site.",
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
return callDraft(
|
|
809
|
+
record,
|
|
810
|
+
"resolved-async-calls",
|
|
811
|
+
["callback-flow", "behavior-effect"],
|
|
812
|
+
true,
|
|
813
|
+
"normalize",
|
|
814
|
+
(fact) =>
|
|
815
|
+
(fact.kind === "callback-flow" &&
|
|
816
|
+
fact.data.flowKind === "event-channel") ||
|
|
817
|
+
fact.kind === "behavior-effect",
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
return callDraft(
|
|
821
|
+
record,
|
|
822
|
+
"resolved-async-calls",
|
|
823
|
+
["callback-flow", "behavior-effect"],
|
|
824
|
+
false,
|
|
825
|
+
"recognize-or-exclude",
|
|
826
|
+
(fact) =>
|
|
827
|
+
(fact.kind === "callback-flow" &&
|
|
828
|
+
fact.data.flowKind === "event-channel") ||
|
|
829
|
+
fact.kind === "behavior-effect",
|
|
830
|
+
"The asynchronous consumer is not independently a product action; " +
|
|
831
|
+
"when it carries an event into product behavior, the event-channel " +
|
|
832
|
+
"fact recognizes it.",
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
if (signature && localComposables.has(signature)) {
|
|
836
|
+
return callDraft(
|
|
837
|
+
record,
|
|
838
|
+
"resolved-compose-ui",
|
|
839
|
+
["component", "container-binding", "callback-flow"],
|
|
840
|
+
true,
|
|
841
|
+
"normalize",
|
|
842
|
+
() => true,
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
if (isComposeOwner(owner)) {
|
|
846
|
+
return callDraft(
|
|
847
|
+
record,
|
|
848
|
+
"resolved-compose-ui",
|
|
849
|
+
[
|
|
850
|
+
"component",
|
|
851
|
+
"ui-element",
|
|
852
|
+
"ui-property",
|
|
853
|
+
"container-binding",
|
|
854
|
+
"callback-flow",
|
|
855
|
+
],
|
|
856
|
+
false,
|
|
857
|
+
"normalize",
|
|
858
|
+
() => true,
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
if (isLikelyExternalComposable(owner, signature)) {
|
|
862
|
+
return callDraft(
|
|
863
|
+
record,
|
|
864
|
+
"resolved-compose-ui",
|
|
865
|
+
[
|
|
866
|
+
"component",
|
|
867
|
+
"ui-element",
|
|
868
|
+
"ui-property",
|
|
869
|
+
"container-binding",
|
|
870
|
+
"callback-flow",
|
|
871
|
+
],
|
|
872
|
+
false,
|
|
873
|
+
"limit",
|
|
874
|
+
() => true,
|
|
875
|
+
"This presentational Compose call is outside the product-critical " +
|
|
876
|
+
"semantic mapping supported by the current analyzer.",
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
if (bindings.some(isCallbackBinding)) {
|
|
880
|
+
const infrastructureReason = callbackInfrastructureReason(
|
|
881
|
+
record,
|
|
882
|
+
name,
|
|
883
|
+
bindings,
|
|
884
|
+
);
|
|
885
|
+
if (infrastructureReason) {
|
|
886
|
+
return callDraft(
|
|
887
|
+
record,
|
|
888
|
+
"resolved-listener-interactions",
|
|
889
|
+
[],
|
|
890
|
+
false,
|
|
891
|
+
"exclude",
|
|
892
|
+
() => false,
|
|
893
|
+
infrastructureReason,
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
if (isPotentialInteractionCallback(record, name, bindings)) {
|
|
897
|
+
return callDraft(
|
|
898
|
+
record,
|
|
899
|
+
"resolved-listener-interactions",
|
|
900
|
+
["interaction-binding"],
|
|
901
|
+
false,
|
|
902
|
+
"normalize",
|
|
903
|
+
(fact) =>
|
|
904
|
+
fact.kind === "interaction-binding" &&
|
|
905
|
+
apiName(fact.data.bindingApi) === name,
|
|
906
|
+
"This callback-shaped UI protocol is not in the public Android " +
|
|
907
|
+
"interaction registry. It remains visible for custom-wrapper review.",
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return callDraft(
|
|
912
|
+
record,
|
|
913
|
+
"other-resolved-calls",
|
|
914
|
+
[],
|
|
915
|
+
false,
|
|
916
|
+
"exclude",
|
|
917
|
+
() => false,
|
|
918
|
+
"The call is not a UI, navigation, pager/tab, listener, or asynchronous " +
|
|
919
|
+
"event-flow candidate.",
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
function collectDrafts(input) {
|
|
923
|
+
const drafts = [];
|
|
924
|
+
if (input.android) {
|
|
925
|
+
drafts.push(
|
|
926
|
+
...input.android.manifest.components.map(componentDraft),
|
|
927
|
+
...input.android.manifest.entries.map(entryDraft),
|
|
928
|
+
...input.android.views.filter((view) => view.actionable).map(viewDraft),
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
if (input.jvm) {
|
|
932
|
+
const localComposables = localComposableSignatures(input.jvm);
|
|
933
|
+
const bindingsByCallRef = new Map();
|
|
934
|
+
for (const record of input.jvm.body) {
|
|
935
|
+
if (record.kind !== "call-argument-binding") continue;
|
|
936
|
+
const callRef = text(record, "callRef");
|
|
937
|
+
if (!callRef) continue;
|
|
938
|
+
const records = bindingsByCallRef.get(callRef) ?? [];
|
|
939
|
+
records.push(record);
|
|
940
|
+
bindingsByCallRef.set(callRef, records);
|
|
941
|
+
}
|
|
942
|
+
for (const record of input.jvm.body) {
|
|
943
|
+
if (record.kind !== "resolved-call" && record.kind !== "unresolved-call")
|
|
944
|
+
continue;
|
|
945
|
+
drafts.push(
|
|
946
|
+
classifyCall(
|
|
947
|
+
record,
|
|
948
|
+
localComposables,
|
|
949
|
+
bindingsByCallRef.get(record.id) ?? [],
|
|
950
|
+
),
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return drafts;
|
|
955
|
+
}
|
|
956
|
+
function unavailableReason(spec, input) {
|
|
957
|
+
if (spec.producer === "android" && !input.android) {
|
|
958
|
+
return "Android XML extraction was unavailable for this run.";
|
|
959
|
+
}
|
|
960
|
+
if (spec.producer === "jvm" && !input.jvm) {
|
|
961
|
+
return "Raw JVM analysis was unavailable for this run.";
|
|
962
|
+
}
|
|
963
|
+
if (spec.intentionallyUnassessed) {
|
|
964
|
+
return "Calls outside the semantic census are deliberately excluded.";
|
|
965
|
+
}
|
|
966
|
+
if (!input.source) {
|
|
967
|
+
return "Source Facts were unavailable, so normalization could not be compared.";
|
|
968
|
+
}
|
|
969
|
+
return undefined;
|
|
970
|
+
}
|
|
971
|
+
function componentHasExplicitAppEntry(draft, source, matchingFacts) {
|
|
972
|
+
if (!source || !draft.manifestComponent) return false;
|
|
973
|
+
const evidence = new Map(source.evidence.map((item) => [item.id, item]));
|
|
974
|
+
const componentRefs = new Set(
|
|
975
|
+
matchingFacts.flatMap((fact) =>
|
|
976
|
+
fact.kind === "component" ? [fact.id] : [],
|
|
977
|
+
),
|
|
978
|
+
);
|
|
979
|
+
if (
|
|
980
|
+
matchingFacts.some(
|
|
981
|
+
(fact) =>
|
|
982
|
+
fact.kind === "component" &&
|
|
983
|
+
fact.support.some((support) => {
|
|
984
|
+
const kind = evidence.get(support.evidenceRef)?.sourceKind;
|
|
985
|
+
return kind === "java" || kind === "kotlin";
|
|
986
|
+
}),
|
|
987
|
+
)
|
|
988
|
+
)
|
|
989
|
+
return true;
|
|
990
|
+
return source.facts.some(
|
|
991
|
+
(fact) =>
|
|
992
|
+
(fact.kind === "navigation-target" &&
|
|
993
|
+
fact.data.targetKind === "component" &&
|
|
994
|
+
fact.data.targetKey !== undefined &&
|
|
995
|
+
componentRefs.has(fact.data.targetKey)) ||
|
|
996
|
+
(fact.kind === "behavior-effect" &&
|
|
997
|
+
fact.data.effectKind === "navigate-area" &&
|
|
998
|
+
fact.data.targetKey !== undefined &&
|
|
999
|
+
componentRefs.has(fact.data.targetKey)),
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
function externalAreaExclusionReason(draft, source, matchingFacts) {
|
|
1003
|
+
const component = draft.manifestComponent;
|
|
1004
|
+
if (
|
|
1005
|
+
!component ||
|
|
1006
|
+
(component.kind !== "activity" && component.kind !== "activity-alias") ||
|
|
1007
|
+
component.provenance?.ownership !== "external-dependency" ||
|
|
1008
|
+
componentHasExplicitAppEntry(draft, source, matchingFacts)
|
|
1009
|
+
)
|
|
1010
|
+
return undefined;
|
|
1011
|
+
return (
|
|
1012
|
+
"The Activity is injected only by an external dependency and no " +
|
|
1013
|
+
"selected app/local source explicitly enters it."
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
function statusFor(draft, source, matchingFacts, hasEvidence) {
|
|
1017
|
+
const externalReason = externalAreaExclusionReason(
|
|
1018
|
+
draft,
|
|
1019
|
+
source,
|
|
1020
|
+
matchingFacts,
|
|
1021
|
+
);
|
|
1022
|
+
if (externalReason) {
|
|
1023
|
+
return { status: "excluded-with-reason", reason: externalReason };
|
|
1024
|
+
}
|
|
1025
|
+
if (draft.policy === "exclude") {
|
|
1026
|
+
return {
|
|
1027
|
+
status: "excluded-with-reason",
|
|
1028
|
+
reason: draft.reason ?? "The candidate is outside the semantic census.",
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
if (draft.policy === "limit") {
|
|
1032
|
+
return {
|
|
1033
|
+
status: "limited",
|
|
1034
|
+
reason: draft.reason ?? "The candidate has limited semantic support.",
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
if (draft.policy === "recognize-or-exclude") {
|
|
1038
|
+
return matchingFacts.length > 0
|
|
1039
|
+
? { status: "recognized" }
|
|
1040
|
+
: {
|
|
1041
|
+
status: "excluded-with-reason",
|
|
1042
|
+
reason:
|
|
1043
|
+
draft.reason ??
|
|
1044
|
+
"The call has no independently proven product behavior.",
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
if (!source) {
|
|
1048
|
+
return {
|
|
1049
|
+
status: draft.critical ? "unresolved" : "limited",
|
|
1050
|
+
reason: "Source Facts were unavailable for semantic comparison.",
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
if (!hasEvidence) {
|
|
1054
|
+
return {
|
|
1055
|
+
status: draft.critical ? "unresolved" : "limited",
|
|
1056
|
+
reason:
|
|
1057
|
+
"The candidate has no shared source span for semantic comparison.",
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
return matchingFacts.length > 0
|
|
1061
|
+
? { status: "recognized" }
|
|
1062
|
+
: draft.critical
|
|
1063
|
+
? {
|
|
1064
|
+
status: "unresolved",
|
|
1065
|
+
reason: "No matching normalized Source Fact was produced.",
|
|
1066
|
+
}
|
|
1067
|
+
: {
|
|
1068
|
+
status: "limited",
|
|
1069
|
+
reason:
|
|
1070
|
+
draft.reason ??
|
|
1071
|
+
"No product-semantic Source Fact is available for this " +
|
|
1072
|
+
"presentational UI call.",
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
function buildCategory(spec, drafts, input, factsByLocation) {
|
|
1076
|
+
const categoryDrafts = drafts.filter((draft) => draft.category === spec.id);
|
|
1077
|
+
const categoryReason = unavailableReason(spec, input);
|
|
1078
|
+
const candidates = categoryDrafts
|
|
1079
|
+
.map((draft) => {
|
|
1080
|
+
const locationFacts = draft.evidence
|
|
1081
|
+
? (factsByLocation.get(locationKey(draft.evidence)) ?? [])
|
|
1082
|
+
: [];
|
|
1083
|
+
const matchingFacts = locationFacts.filter(
|
|
1084
|
+
(fact) =>
|
|
1085
|
+
draft.expectedFactKinds.includes(fact.kind) && draft.matches(fact),
|
|
1086
|
+
);
|
|
1087
|
+
const normalizedFactRefs = [
|
|
1088
|
+
...new Set(matchingFacts.map((fact) => fact.id)),
|
|
1089
|
+
].sort((left, right) => left.localeCompare(right, "en"));
|
|
1090
|
+
const disposition = statusFor(
|
|
1091
|
+
draft,
|
|
1092
|
+
input.source,
|
|
1093
|
+
matchingFacts,
|
|
1094
|
+
draft.evidence !== undefined,
|
|
1095
|
+
);
|
|
1096
|
+
const outcome =
|
|
1097
|
+
normalizedFactRefs.length > 0
|
|
1098
|
+
? "normalized"
|
|
1099
|
+
: disposition.status === "unresolved" &&
|
|
1100
|
+
input.source !== undefined &&
|
|
1101
|
+
draft.evidence !== undefined
|
|
1102
|
+
? "possible-gap"
|
|
1103
|
+
: "unassessed";
|
|
1104
|
+
const unassessedReason =
|
|
1105
|
+
outcome === "unassessed"
|
|
1106
|
+
? (disposition.reason ?? categoryReason)
|
|
1107
|
+
: undefined;
|
|
1108
|
+
return {
|
|
1109
|
+
id: candidateId(draft),
|
|
1110
|
+
category: draft.category,
|
|
1111
|
+
source: draft.source,
|
|
1112
|
+
semanticKey: draft.semanticKey,
|
|
1113
|
+
...(draft.evidence ? { evidence: draft.evidence } : {}),
|
|
1114
|
+
...(draft.rawRecordRef ? { rawRecordRef: draft.rawRecordRef } : {}),
|
|
1115
|
+
expectedFactKinds: draft.expectedFactKinds,
|
|
1116
|
+
normalizedFactRefs,
|
|
1117
|
+
status: disposition.status,
|
|
1118
|
+
critical: draft.critical,
|
|
1119
|
+
...(disposition.reason ? { reason: disposition.reason } : {}),
|
|
1120
|
+
outcome,
|
|
1121
|
+
...(unassessedReason ? { unassessedReason } : {}),
|
|
1122
|
+
};
|
|
1123
|
+
})
|
|
1124
|
+
.sort((left, right) => left.id.localeCompare(right.id, "en"));
|
|
1125
|
+
const countOutcome = (outcome) =>
|
|
1126
|
+
candidates.filter((candidate) => candidate.outcome === outcome).length;
|
|
1127
|
+
const countStatus = (status) =>
|
|
1128
|
+
candidates.filter((candidate) => candidate.status === status).length;
|
|
1129
|
+
const normalized = countOutcome("normalized");
|
|
1130
|
+
const possibleGap = countOutcome("possible-gap");
|
|
1131
|
+
const unassessed = countOutcome("unassessed");
|
|
1132
|
+
const assessed = normalized + possibleGap;
|
|
1133
|
+
const assessment = categoryReason
|
|
1134
|
+
? "unassessed"
|
|
1135
|
+
: unassessed === 0
|
|
1136
|
+
? "assessed"
|
|
1137
|
+
: assessed > 0
|
|
1138
|
+
? "partially-assessed"
|
|
1139
|
+
: "unassessed";
|
|
1140
|
+
return {
|
|
1141
|
+
id: spec.id,
|
|
1142
|
+
label: spec.label,
|
|
1143
|
+
assessment,
|
|
1144
|
+
observed: candidates.length,
|
|
1145
|
+
normalized,
|
|
1146
|
+
possibleGap,
|
|
1147
|
+
unassessed,
|
|
1148
|
+
recognized: countStatus("recognized"),
|
|
1149
|
+
unresolved: countStatus("unresolved"),
|
|
1150
|
+
excludedWithReason: countStatus("excluded-with-reason"),
|
|
1151
|
+
limited: countStatus("limited"),
|
|
1152
|
+
expectedFactKinds: spec.expectedFactKinds,
|
|
1153
|
+
candidates,
|
|
1154
|
+
...(categoryReason ? { unassessedReason: categoryReason } : {}),
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
function semanticLimitation(candidate) {
|
|
1158
|
+
const critical = candidate.status === "unresolved" && candidate.critical;
|
|
1159
|
+
return {
|
|
1160
|
+
id: `limitation:${candidate.id.slice("candidate:".length)}`,
|
|
1161
|
+
code: critical
|
|
1162
|
+
? "SEMANTIC_CANDIDATE_UNRESOLVED"
|
|
1163
|
+
: "SEMANTIC_CANDIDATE_LIMITED",
|
|
1164
|
+
message:
|
|
1165
|
+
candidate.reason ??
|
|
1166
|
+
`Semantic candidate ${candidate.semanticKey} is ${candidate.status}.`,
|
|
1167
|
+
critical,
|
|
1168
|
+
candidateRefs: [candidate.id],
|
|
1169
|
+
supportRefs: candidate.normalizedFactRefs,
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
function semanticStatus(candidates) {
|
|
1173
|
+
if (
|
|
1174
|
+
candidates.some(
|
|
1175
|
+
(candidate) => candidate.status === "unresolved" && candidate.critical,
|
|
1176
|
+
)
|
|
1177
|
+
)
|
|
1178
|
+
return "incomplete-known";
|
|
1179
|
+
if (candidates.some((candidate) => candidate.status === "limited")) {
|
|
1180
|
+
return "limited";
|
|
1181
|
+
}
|
|
1182
|
+
return "complete";
|
|
1183
|
+
}
|
|
1184
|
+
export function buildGapAudit(input) {
|
|
1185
|
+
const drafts = collectDrafts(input);
|
|
1186
|
+
const factsByLocation = supportIndex(input.source);
|
|
1187
|
+
const categories = categorySpecs.map((spec) =>
|
|
1188
|
+
buildCategory(spec, drafts, input, factsByLocation),
|
|
1189
|
+
);
|
|
1190
|
+
const summary = categories.reduce(
|
|
1191
|
+
(result, category) => ({
|
|
1192
|
+
observed: result.observed + category.observed,
|
|
1193
|
+
normalized: result.normalized + category.normalized,
|
|
1194
|
+
possibleGap: result.possibleGap + category.possibleGap,
|
|
1195
|
+
unassessed: result.unassessed + category.unassessed,
|
|
1196
|
+
recognized: result.recognized + category.recognized,
|
|
1197
|
+
unresolved: result.unresolved + category.unresolved,
|
|
1198
|
+
excludedWithReason:
|
|
1199
|
+
result.excludedWithReason + category.excludedWithReason,
|
|
1200
|
+
limited: result.limited + category.limited,
|
|
1201
|
+
}),
|
|
1202
|
+
{
|
|
1203
|
+
observed: 0,
|
|
1204
|
+
normalized: 0,
|
|
1205
|
+
possibleGap: 0,
|
|
1206
|
+
unassessed: 0,
|
|
1207
|
+
recognized: 0,
|
|
1208
|
+
unresolved: 0,
|
|
1209
|
+
excludedWithReason: 0,
|
|
1210
|
+
limited: 0,
|
|
1211
|
+
},
|
|
1212
|
+
);
|
|
1213
|
+
const candidates = categories.flatMap((category) => category.candidates);
|
|
1214
|
+
const semanticLimitations = candidates
|
|
1215
|
+
.filter(
|
|
1216
|
+
(candidate) =>
|
|
1217
|
+
candidate.status === "limited" ||
|
|
1218
|
+
(candidate.status === "unresolved" && candidate.critical),
|
|
1219
|
+
)
|
|
1220
|
+
.map(semanticLimitation)
|
|
1221
|
+
.sort((left, right) => left.id.localeCompare(right.id, "en"));
|
|
1222
|
+
return {
|
|
1223
|
+
contractVersion: GAP_AUDIT_CONTRACT_VERSION,
|
|
1224
|
+
artifactKind: "gap-audit",
|
|
1225
|
+
runId: input.runId,
|
|
1226
|
+
caseId: input.caseId,
|
|
1227
|
+
...(input.target ? { target: input.target } : {}),
|
|
1228
|
+
sourceAvailability: {
|
|
1229
|
+
androidXml: input.android !== undefined,
|
|
1230
|
+
rawJvm: input.jvm !== undefined,
|
|
1231
|
+
sourceFacts: input.source !== undefined,
|
|
1232
|
+
},
|
|
1233
|
+
categories,
|
|
1234
|
+
summary,
|
|
1235
|
+
semanticCompleteness: semanticStatus(candidates),
|
|
1236
|
+
semanticLimitations,
|
|
1237
|
+
limitations: [
|
|
1238
|
+
"The census is static and cannot observe runtime-created behavior.",
|
|
1239
|
+
"excluded-with-reason candidates remain visible but do not reduce completeness.",
|
|
1240
|
+
"limited is a known non-critical analyzer boundary and remains publishable.",
|
|
1241
|
+
],
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
function diagnosticId(category) {
|
|
1245
|
+
const candidateRefs = category.candidates
|
|
1246
|
+
.filter(
|
|
1247
|
+
(candidate) => candidate.status === "unresolved" && candidate.critical,
|
|
1248
|
+
)
|
|
1249
|
+
.map((candidate) => candidate.id);
|
|
1250
|
+
const digest = createHash("sha256")
|
|
1251
|
+
.update(JSON.stringify({ category: category.id, candidateRefs }))
|
|
1252
|
+
.digest("hex")
|
|
1253
|
+
.slice(0, 20);
|
|
1254
|
+
return `diagnostic:gap-audit/${digest}`;
|
|
1255
|
+
}
|
|
1256
|
+
export function diagnosticsForGapAudit(audit) {
|
|
1257
|
+
if (!audit.sourceAvailability.sourceFacts) return [];
|
|
1258
|
+
return audit.categories.flatMap((category) => {
|
|
1259
|
+
const candidates = category.candidates.filter(
|
|
1260
|
+
(candidate) => candidate.status === "unresolved" && candidate.critical,
|
|
1261
|
+
);
|
|
1262
|
+
if (candidates.length === 0) return [];
|
|
1263
|
+
return [
|
|
1264
|
+
defineDiagnostic({
|
|
1265
|
+
id: diagnosticId(category),
|
|
1266
|
+
code: "STAGE1_POSSIBLE_NORMALIZATION_GAP",
|
|
1267
|
+
stage: "normalize",
|
|
1268
|
+
message:
|
|
1269
|
+
`Gap audit found ${candidates.length} unresolved, product-significant ` +
|
|
1270
|
+
`${category.label} candidate(s). Structured artifacts and the ` +
|
|
1271
|
+
"deterministic draft are retained, and the quality gap remains explicit.",
|
|
1272
|
+
remediation:
|
|
1273
|
+
"Review the listed candidates and extend normalization or explicitly " +
|
|
1274
|
+
"classify a proven non-critical boundary.",
|
|
1275
|
+
...(candidates[0]?.evidence
|
|
1276
|
+
? { location: candidates[0].evidence }
|
|
1277
|
+
: {}),
|
|
1278
|
+
details: {
|
|
1279
|
+
category: category.id,
|
|
1280
|
+
observed: category.observed,
|
|
1281
|
+
normalized: category.normalized,
|
|
1282
|
+
possibleGap: category.possibleGap,
|
|
1283
|
+
candidateRefs: candidates.map((candidate) => candidate.id),
|
|
1284
|
+
},
|
|
1285
|
+
disposition: "continue",
|
|
1286
|
+
severity: "warning",
|
|
1287
|
+
category: "source-quality",
|
|
1288
|
+
}),
|
|
1289
|
+
];
|
|
1290
|
+
});
|
|
1291
|
+
}
|