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,299 @@
|
|
|
1
|
+
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { hardFailure } from "../diagnostics.js";
|
|
4
|
+
const SETTINGS_NAMES = ["settings.gradle.kts", "settings.gradle"];
|
|
5
|
+
const BUILD_NAMES = ["build.gradle.kts", "build.gradle"];
|
|
6
|
+
function firstExisting(root, names) {
|
|
7
|
+
return names
|
|
8
|
+
.map((name) => join(root, name))
|
|
9
|
+
.find((path) => existsSync(path) && statSync(path).isFile());
|
|
10
|
+
}
|
|
11
|
+
function moduleDirectory(projectRoot, gradlePath) {
|
|
12
|
+
if (gradlePath === ":") {
|
|
13
|
+
return projectRoot;
|
|
14
|
+
}
|
|
15
|
+
return join(projectRoot, ...gradlePath.slice(1).split(":"));
|
|
16
|
+
}
|
|
17
|
+
function uniqueSorted(values) {
|
|
18
|
+
return [...new Set(values)].sort();
|
|
19
|
+
}
|
|
20
|
+
export function parseIncludedModules(settingsText) {
|
|
21
|
+
const modules = [];
|
|
22
|
+
const callPattern = /\binclude\s*\(([\s\S]*?)\)/gu;
|
|
23
|
+
for (const match of settingsText.matchAll(callPattern)) {
|
|
24
|
+
for (const value of match[1].matchAll(/["'](:[^"']+)["']/gu)) {
|
|
25
|
+
modules.push(value[1]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const groovyPattern = /^\s*include\s+(.+)$/gmu;
|
|
29
|
+
for (const match of settingsText.matchAll(groovyPattern)) {
|
|
30
|
+
for (const value of match[1].matchAll(/["'](:[^"']+)["']/gu)) {
|
|
31
|
+
modules.push(value[1]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return uniqueSorted(modules);
|
|
35
|
+
}
|
|
36
|
+
function block(text, name) {
|
|
37
|
+
const start = new RegExp(`\\b${name}\\s*\\{`, "u").exec(text);
|
|
38
|
+
if (!start) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const open = text.indexOf("{", start.index);
|
|
42
|
+
let depth = 0;
|
|
43
|
+
let quote;
|
|
44
|
+
for (let index = open; index < text.length; index += 1) {
|
|
45
|
+
const character = text[index];
|
|
46
|
+
const previous = text[index - 1];
|
|
47
|
+
if (quote) {
|
|
48
|
+
if (character === quote && previous !== "\\") {
|
|
49
|
+
quote = undefined;
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (character === '"' || character === "'") {
|
|
54
|
+
quote = character;
|
|
55
|
+
} else if (character === "{") {
|
|
56
|
+
depth += 1;
|
|
57
|
+
} else if (character === "}") {
|
|
58
|
+
depth -= 1;
|
|
59
|
+
if (depth === 0) {
|
|
60
|
+
return text.slice(open + 1, index);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
function assignedString(text, name) {
|
|
67
|
+
const patterns = [
|
|
68
|
+
new RegExp(`\\b${name}\\s*=\\s*["']([^"']+)["']`, "u"),
|
|
69
|
+
new RegExp(`\\b${name}\\s+["']([^"']+)["']`, "u"),
|
|
70
|
+
];
|
|
71
|
+
for (const pattern of patterns) {
|
|
72
|
+
const value = pattern.exec(text)?.[1];
|
|
73
|
+
if (value) {
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
function parseNamedContainers(text) {
|
|
80
|
+
const names = [
|
|
81
|
+
...text.matchAll(
|
|
82
|
+
/\b(?:create|maybeCreate|named|register)\s*\(\s*["']([^"']+)["']\s*\)/gu,
|
|
83
|
+
),
|
|
84
|
+
].map((match) => match[1]);
|
|
85
|
+
return uniqueSorted(names);
|
|
86
|
+
}
|
|
87
|
+
function upperCamel(value) {
|
|
88
|
+
return value.length === 0
|
|
89
|
+
? value
|
|
90
|
+
: `${value[0].toUpperCase()}${value.slice(1)}`;
|
|
91
|
+
}
|
|
92
|
+
function buildVariants(buildTypes, productFlavors) {
|
|
93
|
+
if (productFlavors.length === 0) {
|
|
94
|
+
return buildTypes.map((buildType) => ({
|
|
95
|
+
name: buildType,
|
|
96
|
+
buildType,
|
|
97
|
+
productFlavors: [],
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
// The frozen baseline uses at most one flavor dimension. A Gradle-derived
|
|
101
|
+
// model replaces this fallback before wider toolchain support is claimed.
|
|
102
|
+
return productFlavors.flatMap((flavor) =>
|
|
103
|
+
buildTypes.map((buildType) => ({
|
|
104
|
+
name: `${flavor.name}${upperCamel(buildType)}`,
|
|
105
|
+
buildType,
|
|
106
|
+
productFlavors: [flavor],
|
|
107
|
+
})),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
function parseModule(projectRoot, gradlePath) {
|
|
111
|
+
const directory = moduleDirectory(projectRoot, gradlePath);
|
|
112
|
+
const buildFile = firstExisting(directory, BUILD_NAMES);
|
|
113
|
+
if (!buildFile) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
const text = readFileSync(buildFile, "utf8");
|
|
117
|
+
const hasAppliedPlugin = (pluginId) =>
|
|
118
|
+
text
|
|
119
|
+
.split(/\r?\n/u)
|
|
120
|
+
.some(
|
|
121
|
+
(line) =>
|
|
122
|
+
line.includes(pluginId) &&
|
|
123
|
+
!/\bapply\s+false\b/u.test(line) &&
|
|
124
|
+
(/\bid\s*\(/u.test(line) ||
|
|
125
|
+
/\bapply\s+plugin\s*:/u.test(line) ||
|
|
126
|
+
/\bplugins\.apply\s*\(/u.test(line)),
|
|
127
|
+
);
|
|
128
|
+
const kind = hasAppliedPlugin("com.android.application")
|
|
129
|
+
? "application"
|
|
130
|
+
: hasAppliedPlugin("com.android.library")
|
|
131
|
+
? "library"
|
|
132
|
+
: undefined;
|
|
133
|
+
if (!kind) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
const android = block(text, "android") ?? text;
|
|
137
|
+
const buildTypesBlock = block(android, "buildTypes") ?? "";
|
|
138
|
+
const parsedBuildTypes = parseNamedContainers(buildTypesBlock);
|
|
139
|
+
const buildTypes =
|
|
140
|
+
parsedBuildTypes.length > 0 ? parsedBuildTypes : ["debug", "release"];
|
|
141
|
+
const flavorsBlock = block(android, "productFlavors") ?? "";
|
|
142
|
+
const dimension = assignedString(android, "flavorDimensions");
|
|
143
|
+
const productFlavors = parseNamedContainers(flavorsBlock).map((name) => ({
|
|
144
|
+
name,
|
|
145
|
+
...(dimension ? { dimension } : {}),
|
|
146
|
+
}));
|
|
147
|
+
const localProjectDependencies = uniqueSorted(
|
|
148
|
+
[...text.matchAll(/\bproject\s*\(\s*["'](:[^"']+)["']\s*\)/gu)].map(
|
|
149
|
+
(match) => match[1],
|
|
150
|
+
),
|
|
151
|
+
);
|
|
152
|
+
return {
|
|
153
|
+
gradlePath,
|
|
154
|
+
directory,
|
|
155
|
+
buildFile,
|
|
156
|
+
kind,
|
|
157
|
+
namespace: assignedString(android, "namespace"),
|
|
158
|
+
applicationId: assignedString(
|
|
159
|
+
block(android, "defaultConfig") ?? android,
|
|
160
|
+
"applicationId",
|
|
161
|
+
),
|
|
162
|
+
buildTypes,
|
|
163
|
+
productFlavors,
|
|
164
|
+
variants: buildVariants(buildTypes, productFlavors),
|
|
165
|
+
localProjectDependencies,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function ensureInside(root, candidate) {
|
|
169
|
+
const child = relative(root, candidate);
|
|
170
|
+
if (
|
|
171
|
+
child === ".." ||
|
|
172
|
+
child.startsWith(`..${sep}`) ||
|
|
173
|
+
child.startsWith("/") ||
|
|
174
|
+
/^[A-Za-z]:/u.test(child)
|
|
175
|
+
) {
|
|
176
|
+
throw hardFailure(
|
|
177
|
+
"PROJECT_ROOT_ESCAPE",
|
|
178
|
+
"input",
|
|
179
|
+
`Resolved project path escapes the selected root: ${candidate}`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export function discoverAndroidProject(projectRoot) {
|
|
184
|
+
const absoluteRoot = realpathSync(resolve(projectRoot));
|
|
185
|
+
const settingsFile = firstExisting(absoluteRoot, SETTINGS_NAMES);
|
|
186
|
+
if (!settingsFile) {
|
|
187
|
+
throw hardFailure(
|
|
188
|
+
"GRADLE_SETTINGS_MISSING",
|
|
189
|
+
"gradle",
|
|
190
|
+
`No settings.gradle(.kts) exists at ${absoluteRoot}`,
|
|
191
|
+
"Pass the Android Gradle root rather than an individual source folder.",
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
const included = parseIncludedModules(readFileSync(settingsFile, "utf8"));
|
|
195
|
+
const rootBuild = firstExisting(absoluteRoot, BUILD_NAMES);
|
|
196
|
+
const candidates = [...(rootBuild ? [":"] : []), ...included];
|
|
197
|
+
const modules = candidates
|
|
198
|
+
.map((path) => {
|
|
199
|
+
const directory = moduleDirectory(absoluteRoot, path);
|
|
200
|
+
ensureInside(absoluteRoot, resolve(directory));
|
|
201
|
+
return parseModule(absoluteRoot, path);
|
|
202
|
+
})
|
|
203
|
+
.filter((module) => module !== undefined)
|
|
204
|
+
.sort((left, right) =>
|
|
205
|
+
left.gradlePath.localeCompare(right.gradlePath, "en"),
|
|
206
|
+
);
|
|
207
|
+
if (modules.every((module) => module.kind !== "application")) {
|
|
208
|
+
throw hardFailure(
|
|
209
|
+
"APPLICATION_MODULE_MISSING",
|
|
210
|
+
"gradle",
|
|
211
|
+
"No Android application module was discovered.",
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
projectRoot: absoluteRoot,
|
|
216
|
+
settingsFile,
|
|
217
|
+
modules,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function reachableModules(inventory, root) {
|
|
221
|
+
const byPath = new Map(
|
|
222
|
+
inventory.modules.map((module) => [module.gradlePath, module]),
|
|
223
|
+
);
|
|
224
|
+
const visited = new Set();
|
|
225
|
+
const queue = [root.gradlePath];
|
|
226
|
+
while (queue.length > 0) {
|
|
227
|
+
const path = queue.shift();
|
|
228
|
+
if (!path || visited.has(path)) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
visited.add(path);
|
|
232
|
+
const module = byPath.get(path);
|
|
233
|
+
module?.localProjectDependencies.forEach((dependency) =>
|
|
234
|
+
queue.push(dependency),
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return [...visited]
|
|
238
|
+
.map((path) => byPath.get(path))
|
|
239
|
+
.filter((module) => module !== undefined)
|
|
240
|
+
.sort((left, right) =>
|
|
241
|
+
left.gradlePath.localeCompare(right.gradlePath, "en"),
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
export function selectProjectModel(inventory, options) {
|
|
245
|
+
const applications = inventory.modules.filter(
|
|
246
|
+
(module) => module.kind === "application",
|
|
247
|
+
);
|
|
248
|
+
const applicationModule = options.applicationModule
|
|
249
|
+
? applications.find(
|
|
250
|
+
(module) => module.gradlePath === options.applicationModule,
|
|
251
|
+
)
|
|
252
|
+
: applications.length === 1
|
|
253
|
+
? applications[0]
|
|
254
|
+
: undefined;
|
|
255
|
+
if (!applicationModule) {
|
|
256
|
+
throw hardFailure(
|
|
257
|
+
options.applicationModule
|
|
258
|
+
? "APPLICATION_MODULE_UNKNOWN"
|
|
259
|
+
: "APPLICATION_MODULE_AMBIGUOUS",
|
|
260
|
+
"variant",
|
|
261
|
+
options.applicationModule
|
|
262
|
+
? `Application module ${options.applicationModule} was not found.`
|
|
263
|
+
: `More than one application module exists: ${applications.map((module) => module.gradlePath).join(", ")}`,
|
|
264
|
+
"Select exactly one application module.",
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
const buildVariant = options.buildVariant
|
|
268
|
+
? applicationModule.variants.find(
|
|
269
|
+
(variant) => variant.name === options.buildVariant,
|
|
270
|
+
)
|
|
271
|
+
: applicationModule.variants.length === 1
|
|
272
|
+
? applicationModule.variants[0]
|
|
273
|
+
: undefined;
|
|
274
|
+
if (!buildVariant) {
|
|
275
|
+
throw hardFailure(
|
|
276
|
+
options.buildVariant
|
|
277
|
+
? "BUILD_VARIANT_UNKNOWN"
|
|
278
|
+
: "BUILD_VARIANT_AMBIGUOUS",
|
|
279
|
+
"variant",
|
|
280
|
+
options.buildVariant
|
|
281
|
+
? `Variant ${options.buildVariant} does not exist in ${applicationModule.gradlePath}.`
|
|
282
|
+
: `More than one variant exists in ${applicationModule.gradlePath}: ${applicationModule.variants.map((variant) => variant.name).join(", ")}`,
|
|
283
|
+
"Select exactly one build variant.",
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
projectRoot: inventory.projectRoot,
|
|
288
|
+
applicationModule,
|
|
289
|
+
buildVariant,
|
|
290
|
+
reachableModules: reachableModules(inventory, applicationModule),
|
|
291
|
+
resourceConfiguration: options.resourceConfiguration,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
export function projectRelativePath(projectRoot, path) {
|
|
295
|
+
return relative(projectRoot, resolve(path)).split(sep).join("/");
|
|
296
|
+
}
|
|
297
|
+
export function projectDirectoryFromSettings(settingsFile) {
|
|
298
|
+
return dirname(settingsFile);
|
|
299
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { hardFailure } from "../diagnostics.js";
|
|
4
|
+
import { runGradle, writeGradleProjectModel } from "./gradle-runner.js";
|
|
5
|
+
/**
|
|
6
|
+
* Convert every model filesystem field through an explicitly supplied mapper.
|
|
7
|
+
* Native execution uses the paths produced by Gradle unchanged; non-native
|
|
8
|
+
* executors must normalize their model before returning it or inject a mapper.
|
|
9
|
+
*/
|
|
10
|
+
export function normalizeGradleProjectModelPaths(
|
|
11
|
+
model,
|
|
12
|
+
mapPath = (path) => path,
|
|
13
|
+
) {
|
|
14
|
+
return {
|
|
15
|
+
...model,
|
|
16
|
+
projectRoot: mapPath(model.projectRoot),
|
|
17
|
+
modules: model.modules.map((module) => ({
|
|
18
|
+
...module,
|
|
19
|
+
directory: mapPath(module.directory),
|
|
20
|
+
buildFile: mapPath(module.buildFile),
|
|
21
|
+
sourceSets: module.sourceSets.map((sourceSet) => ({
|
|
22
|
+
...sourceSet,
|
|
23
|
+
java: sourceSet.java.map(mapPath),
|
|
24
|
+
res: sourceSet.res.map(mapPath),
|
|
25
|
+
manifest: mapPath(sourceSet.manifest),
|
|
26
|
+
})),
|
|
27
|
+
})),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function isRecord(value) {
|
|
31
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
32
|
+
}
|
|
33
|
+
function stringArray(value) {
|
|
34
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string")
|
|
35
|
+
? value
|
|
36
|
+
: undefined;
|
|
37
|
+
}
|
|
38
|
+
function parseSourceSet(value, path) {
|
|
39
|
+
if (!isRecord(value)) {
|
|
40
|
+
throw new Error(`${path} must be an object`);
|
|
41
|
+
}
|
|
42
|
+
const java = stringArray(value.java);
|
|
43
|
+
const res = stringArray(value.res);
|
|
44
|
+
if (
|
|
45
|
+
typeof value.name !== "string" ||
|
|
46
|
+
!java ||
|
|
47
|
+
!res ||
|
|
48
|
+
typeof value.manifest !== "string"
|
|
49
|
+
) {
|
|
50
|
+
throw new Error(`${path} has an invalid source-set shape`);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
name: value.name,
|
|
54
|
+
java,
|
|
55
|
+
res,
|
|
56
|
+
manifest: value.manifest,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function parseVariant(value, path) {
|
|
60
|
+
if (
|
|
61
|
+
!isRecord(value) ||
|
|
62
|
+
typeof value.name !== "string" ||
|
|
63
|
+
typeof value.buildType !== "string" ||
|
|
64
|
+
!Array.isArray(value.productFlavors)
|
|
65
|
+
) {
|
|
66
|
+
throw new Error(`${path} has an invalid variant shape`);
|
|
67
|
+
}
|
|
68
|
+
const productFlavors = value.productFlavors.map((flavor, index) => {
|
|
69
|
+
if (!isRecord(flavor) || typeof flavor.name !== "string") {
|
|
70
|
+
throw new Error(`${path}.productFlavors[${index}] is invalid`);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
name: flavor.name,
|
|
74
|
+
...(typeof flavor.dimension === "string"
|
|
75
|
+
? { dimension: flavor.dimension }
|
|
76
|
+
: {}),
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
name: value.name,
|
|
81
|
+
buildType: value.buildType,
|
|
82
|
+
productFlavors,
|
|
83
|
+
...(typeof value.applicationId === "string"
|
|
84
|
+
? { applicationId: value.applicationId }
|
|
85
|
+
: {}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function parseModule(value, path) {
|
|
89
|
+
if (
|
|
90
|
+
!isRecord(value) ||
|
|
91
|
+
typeof value.gradlePath !== "string" ||
|
|
92
|
+
typeof value.directory !== "string" ||
|
|
93
|
+
typeof value.buildFile !== "string" ||
|
|
94
|
+
(value.kind !== "application" &&
|
|
95
|
+
value.kind !== "library" &&
|
|
96
|
+
value.kind !== "non-android-library") ||
|
|
97
|
+
!Array.isArray(value.variants) ||
|
|
98
|
+
!Array.isArray(value.sourceSets)
|
|
99
|
+
) {
|
|
100
|
+
throw new Error(`${path} has an invalid Android module shape`);
|
|
101
|
+
}
|
|
102
|
+
const dependencies = stringArray(value.localProjectDependencies) ?? [];
|
|
103
|
+
return {
|
|
104
|
+
gradlePath: value.gradlePath,
|
|
105
|
+
directory: value.directory,
|
|
106
|
+
buildFile: value.buildFile,
|
|
107
|
+
kind: value.kind,
|
|
108
|
+
...(typeof value.namespace === "string"
|
|
109
|
+
? { namespace: value.namespace }
|
|
110
|
+
: {}),
|
|
111
|
+
variants: value.variants.map((variant, index) =>
|
|
112
|
+
parseVariant(variant, `${path}.variants[${index}]`),
|
|
113
|
+
),
|
|
114
|
+
sourceSets: value.sourceSets.map((sourceSet, index) =>
|
|
115
|
+
parseSourceSet(sourceSet, `${path}.sourceSets[${index}]`),
|
|
116
|
+
),
|
|
117
|
+
localProjectDependencies: dependencies,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export function parseGradleProjectModel(value) {
|
|
121
|
+
if (
|
|
122
|
+
!isRecord(value) ||
|
|
123
|
+
value.schemaVersion !== "1.0.0" ||
|
|
124
|
+
typeof value.projectRoot !== "string" ||
|
|
125
|
+
!Array.isArray(value.modules)
|
|
126
|
+
) {
|
|
127
|
+
throw new Error("Gradle project model has an unsupported envelope");
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
schemaVersion: "1.0.0",
|
|
131
|
+
projectRoot: value.projectRoot,
|
|
132
|
+
modules: value.modules.map((module, index) =>
|
|
133
|
+
parseModule(module, `modules[${index}]`),
|
|
134
|
+
),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export async function loadGradleProjectModel(projectRoot, options) {
|
|
138
|
+
await writeGradleProjectModel(
|
|
139
|
+
projectRoot,
|
|
140
|
+
options.initScript,
|
|
141
|
+
options.outputPath,
|
|
142
|
+
options.runner,
|
|
143
|
+
);
|
|
144
|
+
try {
|
|
145
|
+
return normalizeGradleProjectModelPaths(
|
|
146
|
+
parseGradleProjectModel(
|
|
147
|
+
JSON.parse(readFileSync(options.outputPath, "utf8")),
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
throw hardFailure(
|
|
152
|
+
"GRADLE_MODEL_INVALID",
|
|
153
|
+
"gradle",
|
|
154
|
+
`Gradle produced an invalid project model: ${error instanceof Error ? error.message : String(error)}`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Android names a source set that combines multiple flavor dimensions in
|
|
160
|
+
* lower camel case (for example, `free` + `demo` becomes `freeDemo`).
|
|
161
|
+
*/
|
|
162
|
+
export function combinedProductFlavorSourceSetName(variant) {
|
|
163
|
+
const flavors = variant.productFlavors.map((flavor) => flavor.name);
|
|
164
|
+
if (flavors.length < 2) return undefined;
|
|
165
|
+
return flavors
|
|
166
|
+
.map((flavor, index) =>
|
|
167
|
+
index === 0
|
|
168
|
+
? flavor
|
|
169
|
+
: `${flavor[0]?.toUpperCase() ?? ""}${flavor.slice(1)}`,
|
|
170
|
+
)
|
|
171
|
+
.join("");
|
|
172
|
+
}
|
|
173
|
+
function activeNames(variant) {
|
|
174
|
+
const flavors = variant.productFlavors.map((flavor) => flavor.name);
|
|
175
|
+
const combinedFlavor = combinedProductFlavorSourceSetName(variant);
|
|
176
|
+
return [
|
|
177
|
+
"main",
|
|
178
|
+
...flavors,
|
|
179
|
+
...(combinedFlavor ? [combinedFlavor] : []),
|
|
180
|
+
variant.buildType,
|
|
181
|
+
variant.name,
|
|
182
|
+
];
|
|
183
|
+
}
|
|
184
|
+
export function selectCompatibleModuleVariant(module, selected) {
|
|
185
|
+
const variant =
|
|
186
|
+
module.variants.find((candidate) => candidate.name === selected.name) ??
|
|
187
|
+
module.variants.find(
|
|
188
|
+
(candidate) =>
|
|
189
|
+
candidate.buildType === selected.buildType &&
|
|
190
|
+
candidate.productFlavors.length === 0,
|
|
191
|
+
);
|
|
192
|
+
if (!variant) {
|
|
193
|
+
throw hardFailure(
|
|
194
|
+
"DEPENDENCY_VARIANT_MISSING",
|
|
195
|
+
"variant",
|
|
196
|
+
`No compatible ${selected.name} source variant exists in ${module.gradlePath}.`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return variant;
|
|
200
|
+
}
|
|
201
|
+
function selectModuleSourceSets(module, selected) {
|
|
202
|
+
// A non-android-library module has no variant concept and no res/manifest
|
|
203
|
+
// to merge — it contributes no source sets to resource/manifest merging.
|
|
204
|
+
if (module.kind === "non-android-library") return [];
|
|
205
|
+
const variant = selectCompatibleModuleVariant(module, selected);
|
|
206
|
+
const names = new Set(activeNames(variant));
|
|
207
|
+
return module.sourceSets.filter((sourceSet) => names.has(sourceSet.name));
|
|
208
|
+
}
|
|
209
|
+
function moduleClosure(model, root) {
|
|
210
|
+
const byPath = new Map(
|
|
211
|
+
model.modules.map((module) => [module.gradlePath, module]),
|
|
212
|
+
);
|
|
213
|
+
const visited = new Set();
|
|
214
|
+
const queue = [root.gradlePath];
|
|
215
|
+
while (queue.length > 0) {
|
|
216
|
+
const path = queue.shift();
|
|
217
|
+
if (!path || visited.has(path)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const module = byPath.get(path);
|
|
221
|
+
if (!module) {
|
|
222
|
+
throw hardFailure(
|
|
223
|
+
"LOCAL_MODULE_MISSING",
|
|
224
|
+
"gradle",
|
|
225
|
+
`Local dependency ${path} is absent from the Gradle model.`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
visited.add(path);
|
|
229
|
+
module.localProjectDependencies.forEach((dependency) =>
|
|
230
|
+
queue.push(dependency),
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
return [...visited]
|
|
234
|
+
.map((path) => byPath.get(path))
|
|
235
|
+
.filter((module) => module !== undefined)
|
|
236
|
+
.sort((left, right) =>
|
|
237
|
+
left.gradlePath.localeCompare(right.gradlePath, "en"),
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
export function selectGradleVariant(model, applicationModulePath, variantName) {
|
|
241
|
+
const applications = model.modules.filter(
|
|
242
|
+
(module) => module.kind === "application",
|
|
243
|
+
);
|
|
244
|
+
const applicationModule = applicationModulePath
|
|
245
|
+
? applications.find((module) => module.gradlePath === applicationModulePath)
|
|
246
|
+
: applications.length === 1
|
|
247
|
+
? applications[0]
|
|
248
|
+
: undefined;
|
|
249
|
+
if (!applicationModule) {
|
|
250
|
+
throw hardFailure(
|
|
251
|
+
applicationModulePath
|
|
252
|
+
? "APPLICATION_MODULE_UNKNOWN"
|
|
253
|
+
: "APPLICATION_MODULE_AMBIGUOUS",
|
|
254
|
+
"variant",
|
|
255
|
+
applicationModulePath
|
|
256
|
+
? `Unknown application module: ${applicationModulePath}`
|
|
257
|
+
: `Select one application module: ${applications.map((module) => module.gradlePath).join(", ")}`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
const variant = variantName
|
|
261
|
+
? applicationModule.variants.find(
|
|
262
|
+
(candidate) => candidate.name === variantName,
|
|
263
|
+
)
|
|
264
|
+
: applicationModule.variants.length === 1
|
|
265
|
+
? applicationModule.variants[0]
|
|
266
|
+
: undefined;
|
|
267
|
+
if (!variant) {
|
|
268
|
+
throw hardFailure(
|
|
269
|
+
variantName ? "BUILD_VARIANT_UNKNOWN" : "BUILD_VARIANT_AMBIGUOUS",
|
|
270
|
+
"variant",
|
|
271
|
+
variantName
|
|
272
|
+
? `Unknown variant ${variantName} in ${applicationModule.gradlePath}.`
|
|
273
|
+
: `Select one variant in ${applicationModule.gradlePath}: ${applicationModule.variants.map((item) => item.name).join(", ")}`,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
const reachableModules = moduleClosure(model, applicationModule);
|
|
277
|
+
const activeSourceSets = new Map(
|
|
278
|
+
reachableModules.map((module) => [
|
|
279
|
+
module.gradlePath,
|
|
280
|
+
selectModuleSourceSets(module, variant),
|
|
281
|
+
]),
|
|
282
|
+
);
|
|
283
|
+
return {
|
|
284
|
+
model,
|
|
285
|
+
applicationModule,
|
|
286
|
+
variant,
|
|
287
|
+
reachableModules,
|
|
288
|
+
activeSourceSets,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function upperCamel(value) {
|
|
292
|
+
return value.length === 0
|
|
293
|
+
? value
|
|
294
|
+
: `${value[0].toUpperCase()}${value.slice(1)}`;
|
|
295
|
+
}
|
|
296
|
+
function walkFiles(root) {
|
|
297
|
+
if (!existsSync(root) || !statSync(root).isDirectory()) {
|
|
298
|
+
return [];
|
|
299
|
+
}
|
|
300
|
+
const results = [];
|
|
301
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
302
|
+
const path = join(root, entry.name);
|
|
303
|
+
if (entry.isDirectory()) {
|
|
304
|
+
results.push(...walkFiles(path));
|
|
305
|
+
} else if (entry.isFile()) {
|
|
306
|
+
results.push(path);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return results;
|
|
310
|
+
}
|
|
311
|
+
export async function prepareMergedManifest(selected, runner) {
|
|
312
|
+
const moduleTask =
|
|
313
|
+
selected.applicationModule.gradlePath === ":"
|
|
314
|
+
? ""
|
|
315
|
+
: selected.applicationModule.gradlePath;
|
|
316
|
+
const variant = upperCamel(selected.variant.name);
|
|
317
|
+
const task = `${moduleTask}:process${variant}MainManifest`;
|
|
318
|
+
const result = await runGradle(
|
|
319
|
+
selected.model.projectRoot,
|
|
320
|
+
["--no-daemon", "--console=plain", task],
|
|
321
|
+
runner,
|
|
322
|
+
);
|
|
323
|
+
if (result.exitCode !== 0) {
|
|
324
|
+
throw hardFailure(
|
|
325
|
+
"MERGED_MANIFEST_FAILED",
|
|
326
|
+
"manifest",
|
|
327
|
+
`Gradle task ${task} failed.`,
|
|
328
|
+
undefined,
|
|
329
|
+
{
|
|
330
|
+
stdoutTail: result.stdout.slice(-4_000),
|
|
331
|
+
stderrTail: result.stderr.slice(-4_000),
|
|
332
|
+
},
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
const candidates = walkFiles(
|
|
336
|
+
join(
|
|
337
|
+
selected.applicationModule.directory,
|
|
338
|
+
"build",
|
|
339
|
+
"intermediates",
|
|
340
|
+
"merged_manifest",
|
|
341
|
+
selected.variant.name,
|
|
342
|
+
),
|
|
343
|
+
).filter((path) => /AndroidManifest\.xml$/u.test(path));
|
|
344
|
+
if (candidates.length !== 1) {
|
|
345
|
+
throw hardFailure(
|
|
346
|
+
"MERGED_MANIFEST_AMBIGUOUS",
|
|
347
|
+
"manifest",
|
|
348
|
+
`Expected one merged main Manifest for ${selected.applicationModule.gradlePath}/${selected.variant.name}, found ${candidates.length}.`,
|
|
349
|
+
undefined,
|
|
350
|
+
{
|
|
351
|
+
candidates: candidates.map((path) =>
|
|
352
|
+
relative(selected.model.projectRoot, path).split(sep).join("/"),
|
|
353
|
+
),
|
|
354
|
+
},
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
return resolve(candidates[0]);
|
|
358
|
+
}
|