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,433 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// device_ui.mjs — HarmonyOS device UI driver facade (CLI; core logic in device_ui_core.mjs).
|
|
3
|
+
//
|
|
4
|
+
// Contract: argument shapes, single-line JSON on stdout, daemon protocol
|
|
5
|
+
// (<work-dir>/.hypium/daemon.json + {"argv":[...]} wire protocol).
|
|
6
|
+
// Run: node <plugin-path>/scripts/device_ui.mjs [--device SN] [--work-dir DIR] <command> [args]
|
|
7
|
+
// Deps: hypium-driver (Node >=20.11.1), resolved lazily by device_ui_core.loadDriverPkg:
|
|
8
|
+
// scripts/node_modules (after `npm install` in a repo checkout) → global npm root →
|
|
9
|
+
// auto `npm install -g hypium-driver@<pinned>`. Pure-hdc commands need none of it.
|
|
10
|
+
//
|
|
11
|
+
// Commands (basic): devices launch observe tap input swipe key density serve shutdown
|
|
12
|
+
// Commands (actions): double-tap long-press clear-text drag fling pinch scroll-until gesture rotate;
|
|
13
|
+
// swipe supports two-point --from/--to; selectors support --regex/--starts/--ends;
|
|
14
|
+
// observe elements sparsely expose checked/selected/focused/enabled.
|
|
15
|
+
// clear-text/scroll-until use device-side By addressing (sole component-handle exemption; uniqueness guard kept).
|
|
16
|
+
// Commands (extended, hypium-driver capability surface):
|
|
17
|
+
// driver-based: wait-idle unlock wake info window record launch-uri app
|
|
18
|
+
// pure hdc (no driver connection): shell hdc hilog toast file
|
|
19
|
+
// Addressing: tap/input/long-press/double-tap take the native findComponents fast path (unique hit closes device-side,
|
|
20
|
+
// ~4-30x faster than full-tree dump; 0/multi hits, --desc, or needing bound fall back to host active-layer
|
|
21
|
+
// disambiguation; ghost filtering unchanged). observe still uses the host full tree.
|
|
22
|
+
// No-console rule: daemon spawns with a hidden console (windowsHide), children inherit; never a cmd popup.
|
|
23
|
+
import fs from "node:fs";
|
|
24
|
+
import net from "node:net";
|
|
25
|
+
import os from "node:os";
|
|
26
|
+
import path from "node:path";
|
|
27
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
28
|
+
import { fileURLToPath } from "node:url";
|
|
29
|
+
import * as core from "./device_ui_core.mjs";
|
|
30
|
+
|
|
31
|
+
core.captureStdout(); // must run before hypium-driver loads: library logs go to stderr, stdout emits JSON only
|
|
32
|
+
|
|
33
|
+
const SELF = fileURLToPath(import.meta.url);
|
|
34
|
+
const DAEMON_CONNECT_TIMEOUT = 3000;
|
|
35
|
+
const REQUEST_TIMEOUT = 300000;
|
|
36
|
+
const SERVE_READY_TIMEOUT = 15000;
|
|
37
|
+
const IDLE_TIMEOUT = 1800000;
|
|
38
|
+
|
|
39
|
+
// --------------------------------------------------------------------------
|
|
40
|
+
// Output + lightweight call tracing (<work-dir>/.hypium/calls.jsonl)
|
|
41
|
+
// --------------------------------------------------------------------------
|
|
42
|
+
let T0 = null, CMD = null, LOG_DIR = null;
|
|
43
|
+
function trace(obj, payload) {
|
|
44
|
+
if (!LOG_DIR) return;
|
|
45
|
+
try {
|
|
46
|
+
const rec = { cmd: CMD, elapsed_ms: T0 == null ? null : Math.round(performance.now() - T0),
|
|
47
|
+
bytes: Buffer.byteLength(payload, "utf8"), chars: payload.length,
|
|
48
|
+
count: obj.count, ok: obj.ok };
|
|
49
|
+
const d = path.join(LOG_DIR, ".hypium");
|
|
50
|
+
fs.mkdirSync(d, { recursive: true });
|
|
51
|
+
fs.appendFileSync(path.join(d, "calls.jsonl"), JSON.stringify(rec) + "\n");
|
|
52
|
+
} catch { /* tracing failure must not affect the main flow */ }
|
|
53
|
+
}
|
|
54
|
+
function emit(obj) {
|
|
55
|
+
const s = JSON.stringify(obj);
|
|
56
|
+
core.writeStdoutLine(s);
|
|
57
|
+
trace(obj, s);
|
|
58
|
+
}
|
|
59
|
+
function fail(reason, extra = {}, code = 1) {
|
|
60
|
+
emit({ ok: false, error: reason, ...extra });
|
|
61
|
+
process.exit(code);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// --------------------------------------------------------------------------
|
|
65
|
+
// Argument parsing (hand-written, zero dependencies)
|
|
66
|
+
// Kept independent of _lib/common.mjs parseFlags: this drives per-command flag tables with
|
|
67
|
+
// typed kinds (xy/xyxy/codes/track/rest switches), which the shared flat parser does not model.
|
|
68
|
+
// --------------------------------------------------------------------------
|
|
69
|
+
const SEL_FLAGS = { "--text": "text", "--contains": "contains", "--regex": "regex",
|
|
70
|
+
"--starts": "starts", "--ends": "ends", "--key": "key", "--desc": "desc", "--type": "type" };
|
|
71
|
+
|
|
72
|
+
// Per-command flag table: flag -> [target key, kind] kinds: s=string i=int f=float b=switch xy=two ints xyxy=four ints codes=comma-separated ints
|
|
73
|
+
const CMD_FLAGS = {
|
|
74
|
+
devices: {},
|
|
75
|
+
launch: { "--bundle": ["bundle", "s"], "--ability": ["ability", "s"], "--restart": ["restart", "b"] },
|
|
76
|
+
observe: { "--out": ["out", "s"], "-o": ["out", "s"], "--all": ["all", "b"], "--raw": ["raw", "b"],
|
|
77
|
+
"--screenshot": ["screenshot", "b"], "--bound": ["bound", "b"], "--limit": ["limit", "i"], ...selFlags() },
|
|
78
|
+
tap: { ...selFlags(), "--xy": ["xy", "xy"], "--nth": ["nth", "i"] },
|
|
79
|
+
"double-tap": { ...selFlags(), "--xy": ["xy", "xy"], "--nth": ["nth", "i"] },
|
|
80
|
+
"long-press": { ...selFlags(), "--xy": ["xy", "xy"], "--nth": ["nth", "i"], "--press-ms": ["pressMs", "i"] },
|
|
81
|
+
input: { ...selFlags(), "--xy": ["xy", "xy"], "--nth": ["nth", "i"], "--value": ["value", "s"] },
|
|
82
|
+
swipe: { "--dir": ["dir", "s"], "--distance": ["distance", "i"], "--speed": ["speed", "i"],
|
|
83
|
+
"--from": ["from", "xy"], "--to": ["to", "xy"] },
|
|
84
|
+
drag: { "--from": ["from", "xy"], "--to": ["to", "xy"], "--speed": ["speed", "i"] },
|
|
85
|
+
fling: { "--dir": ["dir", "s"], "--speed": ["speed", "i"] },
|
|
86
|
+
pinch: { "--mode": ["mode", "s"], "--area": ["area", "xyxy"], "--scale": ["scale", "f"],
|
|
87
|
+
"--speed": ["speed", "i"], ...selFlags(), "--nth": ["nth", "i"] },
|
|
88
|
+
"clear-text": { ...selFlags(), "--nth": ["nth", "i"] },
|
|
89
|
+
"scroll-until": { ...selFlags(), "--nth": ["nth", "i"], "--container-key": ["containerKey", "s"],
|
|
90
|
+
"--container-type": ["containerType", "s"], "--container-nth": ["containerNth", "i"] },
|
|
91
|
+
gesture: { "--track": ["tracks", "track"], "--speed": ["speed", "i"], "--sampling-ms": ["samplingMs", "i"] },
|
|
92
|
+
key: { "--back": ["back", "b"], "--home": ["home", "b"], "--power": ["power", "b"],
|
|
93
|
+
"--code": ["code", "i"], "--combo": ["combo", "codes"] },
|
|
94
|
+
rotate: { "--deg": ["deg", "i"] },
|
|
95
|
+
density: {},
|
|
96
|
+
// Extended: driver-based
|
|
97
|
+
"wait-idle": { "--idle-ms": ["idleMs", "i"], "--timeout-ms": ["timeoutMs", "i"] },
|
|
98
|
+
unlock: {},
|
|
99
|
+
wake: {},
|
|
100
|
+
info: {},
|
|
101
|
+
window: { "--wbundle": ["wbundle", "s"], "--title": ["title", "s"], "--focused": ["focused", "b"],
|
|
102
|
+
"--op": ["wop", "s"], "--w": ["w", "i"], "--h": ["h", "i"], "--x": ["x", "i"], "--y": ["y", "i"], "--dir": ["dir", "s"] },
|
|
103
|
+
record: { "--op": ["rop", "s"], "--out": ["out", "s"], "-o": ["out", "s"] },
|
|
104
|
+
"launch-uri": { "--action": ["uaction", "s"], "--uri": ["uri", "s"], "--extra": ["extra", "s"] },
|
|
105
|
+
app: { "--op": ["aop", "s"], "--bundle": ["bundle", "s"], "--path": ["path", "s"] },
|
|
106
|
+
// Extended: pure hdc (no driver connection)
|
|
107
|
+
shell: { "--cmd": ["cmd", "s"], "--timeout-ms": ["timeoutMs", "i"] },
|
|
108
|
+
hdc: { "--args": ["args", "rest"] },
|
|
109
|
+
hilog: { "--grep": ["grep", "s"], "--lines": ["lines", "i"], "--timeout-ms": ["timeoutMs", "i"] },
|
|
110
|
+
toast: { "--timeout-ms": ["timeoutMs", "i"] },
|
|
111
|
+
file: { "--op": ["fop", "s"], "--local": ["local", "s"], "--remote": ["remote", "s"] },
|
|
112
|
+
serve: { "--foreground": ["foreground", "b"] },
|
|
113
|
+
shutdown: {},
|
|
114
|
+
};
|
|
115
|
+
function selFlags() {
|
|
116
|
+
const o = {};
|
|
117
|
+
for (const [f, k] of Object.entries(SEL_FLAGS)) o[f] = [k, "s"];
|
|
118
|
+
return o;
|
|
119
|
+
}
|
|
120
|
+
const REQUIRED = { launch: ["bundle"], input: ["value"], swipe: [], drag: ["from", "to"],
|
|
121
|
+
fling: ["dir"], pinch: ["mode"], rotate: ["deg"], shell: ["cmd"] };
|
|
122
|
+
const DIRS = new Set(["up", "down", "left", "right"]);
|
|
123
|
+
|
|
124
|
+
function parseInt_(v, flag) {
|
|
125
|
+
const n = Number(v);
|
|
126
|
+
if (!Number.isInteger(n)) throw new core.DeviceUIError(`${flag} 需要整数,得到: ${v}`);
|
|
127
|
+
return n;
|
|
128
|
+
}
|
|
129
|
+
export function parseArgv(argv) {
|
|
130
|
+
const opts = { device: null, workDir: null };
|
|
131
|
+
let i = 0;
|
|
132
|
+
for (; i < argv.length; i++) {
|
|
133
|
+
const t = argv[i];
|
|
134
|
+
if (t === "--device" || t === "-d") opts.device = argv[++i];
|
|
135
|
+
else if (t === "--work-dir") opts.workDir = argv[++i];
|
|
136
|
+
else if (t.startsWith("--device=")) opts.device = t.slice(9);
|
|
137
|
+
else if (t.startsWith("--work-dir=")) opts.workDir = t.slice(11);
|
|
138
|
+
else break;
|
|
139
|
+
}
|
|
140
|
+
const cmd = argv[i++];
|
|
141
|
+
if (!cmd || !(cmd in CMD_FLAGS)) {
|
|
142
|
+
throw new core.DeviceUIError(`未知或缺失命令: ${cmd || "(空)"};可用: ${Object.keys(CMD_FLAGS).join(" ")}`);
|
|
143
|
+
}
|
|
144
|
+
const table = CMD_FLAGS[cmd];
|
|
145
|
+
for (; i < argv.length; i++) {
|
|
146
|
+
let t = argv[i], inline = null;
|
|
147
|
+
const eq = t.indexOf("=");
|
|
148
|
+
if (t.startsWith("--") && eq > 0) { inline = t.slice(eq + 1); t = t.slice(0, eq); }
|
|
149
|
+
const spec = table[t];
|
|
150
|
+
if (!spec) throw new core.DeviceUIError(`命令 ${cmd} 不认识参数: ${t}`);
|
|
151
|
+
const [key, kind] = spec;
|
|
152
|
+
const next = () => { if (inline != null) return inline; const v = argv[++i]; if (v == null) throw new core.DeviceUIError(`${t} 缺值`); return v; };
|
|
153
|
+
if (kind === "b") opts[key] = true;
|
|
154
|
+
else if (kind === "s") opts[key] = next();
|
|
155
|
+
else if (kind === "i") opts[key] = parseInt_(next(), t);
|
|
156
|
+
else if (kind === "f") { opts[key] = Number(next()); if (!Number.isFinite(opts[key])) throw new core.DeviceUIError(`${t} 需要数值,得到非法值`); }
|
|
157
|
+
else if (kind === "xy") opts[key] = [parseInt_(next(), t), parseInt_(argv[++i], t)];
|
|
158
|
+
else if (kind === "xyxy") opts[key] = [parseInt_(next(), t), parseInt_(argv[++i], t), parseInt_(argv[++i], t), parseInt_(argv[++i], t)];
|
|
159
|
+
else if (kind === "codes") opts[key] = next().split(",").map((c) => parseInt_(c.trim(), t));
|
|
160
|
+
else if (kind === "rest") { opts[key] = argv.slice(i + 1); i = argv.length; } // consume everything remaining (hdc passthrough)
|
|
161
|
+
else if (kind === "track") {
|
|
162
|
+
// each --track is one finger trajectory: "x,y[,ms];x,y[,ms];..."
|
|
163
|
+
const pts = next().split(";").map((seg) => seg.split(",").map((n) => parseInt_(n.trim(), t)));
|
|
164
|
+
(opts[key] ||= []).push(pts);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const k of REQUIRED[cmd] || []) {
|
|
168
|
+
if (opts[k] == null) throw new core.DeviceUIError(`${cmd} 缺少必填参数 --${k === "pressMs" ? "press-ms" : k}`);
|
|
169
|
+
}
|
|
170
|
+
if ((cmd === "swipe" && !opts.from) || cmd === "fling") {
|
|
171
|
+
if (!opts.dir || !DIRS.has(opts.dir)) throw new core.DeviceUIError(`${cmd} 需要 --dir up|down|left|right(或 swipe 用 --from/--to 两点式)`);
|
|
172
|
+
}
|
|
173
|
+
if (cmd === "pinch" && !["in", "out"].includes(opts.mode)) throw new core.DeviceUIError("pinch 需要 --mode in|out");
|
|
174
|
+
if (cmd === "gesture" && !opts.tracks) throw new core.DeviceUIError('gesture 需要 ≥1 个 --track "x,y[,ms];x,y[,ms];…"');
|
|
175
|
+
return { cmd, opts };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// --------------------------------------------------------------------------
|
|
179
|
+
// daemon(serve/client)
|
|
180
|
+
// --------------------------------------------------------------------------
|
|
181
|
+
const daemonFile = (wd) => path.join(wd || ".", ".hypium", "daemon.json");
|
|
182
|
+
function readDaemon(wd) {
|
|
183
|
+
try { return JSON.parse(fs.readFileSync(daemonFile(wd), "utf8")); } catch { return null; }
|
|
184
|
+
}
|
|
185
|
+
function removeDaemonFile(wd) {
|
|
186
|
+
try { fs.rmSync(daemonFile(wd), { force: true }); } catch { /* ignore */ }
|
|
187
|
+
}
|
|
188
|
+
function tryPort(port, timeout) {
|
|
189
|
+
return new Promise((resolve) => {
|
|
190
|
+
const s = net.connect({ host: "127.0.0.1", port });
|
|
191
|
+
const done = (v) => { s.destroy(); resolve(v); };
|
|
192
|
+
s.setTimeout(timeout, () => done(false));
|
|
193
|
+
s.once("connect", () => done(true));
|
|
194
|
+
s.once("error", () => done(false));
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
async function daemonAlive(opts) {
|
|
198
|
+
const info = readDaemon(opts.workDir);
|
|
199
|
+
if (!info) return null;
|
|
200
|
+
if (opts.device && info.device && info.device !== opts.device) return null;
|
|
201
|
+
if (await tryPort(info.port, DAEMON_CONNECT_TIMEOUT)) return info;
|
|
202
|
+
removeDaemonFile(opts.workDir); // unreachable port = daemon dead; remove stale daemon.json
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
function requestDaemon(port, payload, timeout) {
|
|
206
|
+
return new Promise((resolve) => {
|
|
207
|
+
const s = net.connect({ host: "127.0.0.1", port });
|
|
208
|
+
let buf = "";
|
|
209
|
+
const done = (v) => { s.destroy(); resolve(v); };
|
|
210
|
+
s.setTimeout(timeout, () => done(null));
|
|
211
|
+
s.once("error", () => done(null));
|
|
212
|
+
s.once("connect", () => s.write(JSON.stringify(payload) + "\n"));
|
|
213
|
+
s.on("data", (c) => {
|
|
214
|
+
buf += c.toString("utf8");
|
|
215
|
+
const i = buf.indexOf("\n");
|
|
216
|
+
if (i >= 0) { try { done(JSON.parse(buf.slice(0, i))); } catch { done(null); } }
|
|
217
|
+
});
|
|
218
|
+
s.once("end", () => { if (buf && !buf.includes("\n")) { try { done(JSON.parse(buf)); } catch { done(null); } } });
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
function forwardArgv(opts) {
|
|
222
|
+
// rewrite --work-dir to its absolutized value (daemon cwd != caller cwd); everything else as-is
|
|
223
|
+
const argv = process.argv.slice(2);
|
|
224
|
+
if (opts.workDir) {
|
|
225
|
+
for (let i = 0; i < argv.length; i++) {
|
|
226
|
+
if (argv[i] === "--work-dir" && i + 1 < argv.length) { argv[i + 1] = opts.workDir; break; }
|
|
227
|
+
if (argv[i].startsWith("--work-dir=")) { argv[i] = "--work-dir=" + opts.workDir; break; }
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return argv;
|
|
231
|
+
}
|
|
232
|
+
async function clientTry(cmd, opts) {
|
|
233
|
+
if (cmd === "serve") return null;
|
|
234
|
+
const info = readDaemon(opts.workDir);
|
|
235
|
+
if (!info) return null;
|
|
236
|
+
if (opts.device && info.device && info.device !== opts.device) return null;
|
|
237
|
+
if (!(await tryPort(info.port, DAEMON_CONNECT_TIMEOUT))) {
|
|
238
|
+
if (cmd !== "shutdown") removeDaemonFile(opts.workDir); // shutdown left for cmdShutdown to clean up and report truthfully
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const payload = cmd === "shutdown" ? { shutdown: true } : { argv: forwardArgv(opts) };
|
|
242
|
+
return requestDaemon(info.port, payload, REQUEST_TIMEOUT);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function serveLoop(opts) {
|
|
246
|
+
// resident listener: connect the device once, reuse one driver per command; always disconnect on exit (release device singleton/forwarding)
|
|
247
|
+
core.selfHealDevice(opts.device);
|
|
248
|
+
let d;
|
|
249
|
+
try { d = await core.connectWithReclaim(opts.device, opts.workDir); }
|
|
250
|
+
catch (e) { fail(e.reason || String(e.message || e), e.extra || {}); }
|
|
251
|
+
const srv = net.createServer();
|
|
252
|
+
let idleTimer = null, closing = false;
|
|
253
|
+
const shutdownSelf = async () => {
|
|
254
|
+
if (closing) return; closing = true;
|
|
255
|
+
try { srv.close(); } catch { /* ignore */ }
|
|
256
|
+
await core.disconnect(d);
|
|
257
|
+
core.restoreScreenTimeout(opts.device); // undo selfHealDevice's screen-off override
|
|
258
|
+
removeDaemonFile(opts.workDir);
|
|
259
|
+
process.exit(0);
|
|
260
|
+
};
|
|
261
|
+
const resetIdle = () => { clearTimeout(idleTimer); idleTimer = setTimeout(shutdownSelf, IDLE_TIMEOUT); };
|
|
262
|
+
process.on("SIGTERM", shutdownSelf);
|
|
263
|
+
process.on("SIGINT", shutdownSelf);
|
|
264
|
+
srv.on("connection", (conn) => {
|
|
265
|
+
resetIdle();
|
|
266
|
+
let buf = "";
|
|
267
|
+
conn.setTimeout(REQUEST_TIMEOUT, () => conn.destroy());
|
|
268
|
+
conn.on("data", async (c) => {
|
|
269
|
+
buf += c.toString("utf8");
|
|
270
|
+
const i = buf.indexOf("\n");
|
|
271
|
+
if (i < 0) return;
|
|
272
|
+
const line = buf.slice(0, i); buf = "";
|
|
273
|
+
let result;
|
|
274
|
+
let wantShutdown = false;
|
|
275
|
+
try {
|
|
276
|
+
const req = JSON.parse(line || "{}");
|
|
277
|
+
if (req.shutdown) { wantShutdown = true; result = { ok: true, shutdown: true }; }
|
|
278
|
+
else result = await dispatch(d, req.argv || []);
|
|
279
|
+
} catch (e) {
|
|
280
|
+
result = { ok: false, error: String(e.message || e).slice(0, 300) };
|
|
281
|
+
}
|
|
282
|
+
try { conn.end(JSON.stringify(result) + "\n"); } catch { /* ignore */ }
|
|
283
|
+
if (wantShutdown) await shutdownSelf();
|
|
284
|
+
});
|
|
285
|
+
conn.on("error", () => { /* client disconnects etc., ignore */ });
|
|
286
|
+
});
|
|
287
|
+
await new Promise((res) => srv.listen(0, "127.0.0.1", res));
|
|
288
|
+
const port = srv.address().port;
|
|
289
|
+
const df = daemonFile(opts.workDir);
|
|
290
|
+
fs.mkdirSync(path.dirname(df), { recursive: true });
|
|
291
|
+
fs.writeFileSync(df, JSON.stringify({ port, device: opts.device, pid: process.pid }));
|
|
292
|
+
emit({ ok: true, action: "serve", port, device: opts.device, daemon_file: df });
|
|
293
|
+
resetIdle();
|
|
294
|
+
await new Promise(() => {}); // stay resident: only shutdown/idle/signal may exit; main's exit must not reap us
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function dispatch(d, argv) {
|
|
298
|
+
// daemon side: parse the forwarded argv -> execute (same action table as one-shot)
|
|
299
|
+
let parsed;
|
|
300
|
+
try { parsed = parseArgv(argv); } catch { return { ok: false, error: `daemon 无法解析命令: ${JSON.stringify(argv)}` }; }
|
|
301
|
+
const { cmd, opts } = parsed;
|
|
302
|
+
try {
|
|
303
|
+
if (cmd === "devices") return core.doDevices();
|
|
304
|
+
if (cmd === "density") {
|
|
305
|
+
const dd = core.probeDensity(opts.device);
|
|
306
|
+
return { ok: dd != null, density: dd, ...(dd == null ? { error: "密度探测失败(hidumper 无 Density 行)" } : {}) };
|
|
307
|
+
}
|
|
308
|
+
if (cmd in core.HDC_ACTIONS) return core.HDC_ACTIONS[cmd](opts); // pure hdc, no driver needed
|
|
309
|
+
if (cmd in core.ACTIONS) return await core.runAction(d, cmd, opts);
|
|
310
|
+
return { ok: false, error: `daemon 不支持命令: ${cmd}` };
|
|
311
|
+
} catch (e) {
|
|
312
|
+
if (e instanceof core.DeviceUIError) return { ok: false, error: e.reason, ...e.extra };
|
|
313
|
+
return { ok: false, error: String(e.message || e).slice(0, 300) };
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function cmdServe(opts) {
|
|
318
|
+
if (opts.foreground) return serveLoop(opts);
|
|
319
|
+
const info = await daemonAlive(opts);
|
|
320
|
+
if (info) {
|
|
321
|
+
emit({ ok: true, action: "serve", port: info.port, device: info.device, daemon_file: daemonFile(opts.workDir), reused: true });
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const raw = readDaemon(opts.workDir);
|
|
325
|
+
if (raw && opts.device && raw.device && raw.device !== opts.device) {
|
|
326
|
+
// same work-dir already holds a daemon for another device -> if alive, fail loudly (per-device work-dir contract); if dead, clean up
|
|
327
|
+
if (raw.port && (await tryPort(raw.port, DAEMON_CONNECT_TIMEOUT))) {
|
|
328
|
+
fail(`work-dir 已被 device ${raw.device} 的存活 daemon 占用(本次 --device ${opts.device})——每设备用独立 work-dir,或先对原设备 shutdown`,
|
|
329
|
+
{ daemon_file: daemonFile(opts.workDir) });
|
|
330
|
+
}
|
|
331
|
+
removeDaemonFile(opts.workDir);
|
|
332
|
+
}
|
|
333
|
+
const args = [SELF];
|
|
334
|
+
if (opts.device) args.push("--device", opts.device);
|
|
335
|
+
if (opts.workDir) args.push("--work-dir", opts.workDir);
|
|
336
|
+
args.push("serve", "--foreground");
|
|
337
|
+
// No cmd popups + survival across calls, both required (measured combo: CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW):
|
|
338
|
+
// hidden console — hypium-driver spawns hdc internally without windowsHide; if the daemon has no
|
|
339
|
+
// console (DETACHED), every hdc child pops a new cmd window (measured root cause);
|
|
340
|
+
// new process group — otherwise group signals kill the daemon when the launcher's process tree is
|
|
341
|
+
// reaped (measured: a direct spawn with only windowsHide dies).
|
|
342
|
+
// node's detached on Windows bundles DETACHED_PROCESS|CREATE_NEW_PROCESS_GROUP, inseparable ->
|
|
343
|
+
// relay via cmd: cmd gets a hidden console with CREATE_NO_WINDOW, then `start "" /b` gives the daemon
|
|
344
|
+
// a new process group while sharing that hidden console — both properties hold. POSIX has no such issue; detached suffices.
|
|
345
|
+
let child;
|
|
346
|
+
if (os.platform() === "win32") {
|
|
347
|
+
const q = (s) => `"${s}"`;
|
|
348
|
+
const line = `start "" /b ${q(process.execPath)} ${args.map(q).join(" ")}`;
|
|
349
|
+
child = spawn("cmd.exe", ["/d", "/s", "/c", line], {
|
|
350
|
+
stdio: "ignore", windowsHide: true, windowsVerbatimArguments: true,
|
|
351
|
+
});
|
|
352
|
+
} else {
|
|
353
|
+
child = spawn(process.execPath, args, { stdio: "ignore", detached: true });
|
|
354
|
+
}
|
|
355
|
+
child.unref();
|
|
356
|
+
const deadline = Date.now() + SERVE_READY_TIMEOUT;
|
|
357
|
+
while (Date.now() < deadline) {
|
|
358
|
+
const alive = await daemonAlive(opts);
|
|
359
|
+
if (alive) {
|
|
360
|
+
emit({ ok: true, action: "serve", port: alive.port, device: alive.device, daemon_file: daemonFile(opts.workDir), detached: true });
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
364
|
+
}
|
|
365
|
+
fail("daemon 未在 15s 内就绪(detached serve 启动失败,检查设备/hypium-driver)", { daemon_file: daemonFile(opts.workDir) });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function killDaemonByPid(pid) {
|
|
369
|
+
// kill a leftover daemon by the pid in daemon.json; verify cmdline contains device_ui first, to avoid killing a reused pid
|
|
370
|
+
if (!pid) return;
|
|
371
|
+
try {
|
|
372
|
+
if (os.platform() === "win32") {
|
|
373
|
+
const ps = `$p = Get-CimInstance Win32_Process -Filter 'ProcessId=${Number(pid)}' -ErrorAction SilentlyContinue; if ($p -and $p.CommandLine -match 'device_ui') { Stop-Process -Id $p.ProcessId -Force }`;
|
|
374
|
+
spawnSync("powershell.exe", ["-NoProfile", "-Command", ps], { timeout: 15000, windowsHide: true });
|
|
375
|
+
} else {
|
|
376
|
+
let cmdline = "device_ui";
|
|
377
|
+
try { cmdline = fs.readFileSync(`/proc/${Number(pid)}/cmdline`, "utf8"); } catch { /* macOS etc.: fall back to killing by pid */ }
|
|
378
|
+
if (cmdline.includes("device_ui")) process.kill(Number(pid), "SIGTERM");
|
|
379
|
+
}
|
|
380
|
+
} catch { /* best-effort */ }
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function cmdShutdown(opts) {
|
|
384
|
+
// graceful shutdown of a live daemon is handled by clientTry({"shutdown":true}); this runs only when there is no/dead daemon
|
|
385
|
+
const info = readDaemon(opts.workDir);
|
|
386
|
+
if (!info) { emit({ ok: true, action: "shutdown", note: "no daemon running" }); return; }
|
|
387
|
+
killDaemonByPid(info.pid);
|
|
388
|
+
removeDaemonFile(opts.workDir);
|
|
389
|
+
emit({ ok: true, action: "shutdown", note: "cleaned stale daemon", killed_pid: info.pid });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// --------------------------------------------------------------------------
|
|
393
|
+
// main
|
|
394
|
+
// --------------------------------------------------------------------------
|
|
395
|
+
async function main() {
|
|
396
|
+
let parsed;
|
|
397
|
+
try { parsed = parseArgv(process.argv.slice(2)); }
|
|
398
|
+
catch (e) { fail(e.reason || String(e.message || e), e.extra || {}, 2); }
|
|
399
|
+
const { cmd, opts } = parsed;
|
|
400
|
+
if (opts.workDir) opts.workDir = path.resolve(opts.workDir);
|
|
401
|
+
T0 = performance.now(); CMD = cmd; LOG_DIR = opts.workDir;
|
|
402
|
+
core.ensureHdcOnPath();
|
|
403
|
+
|
|
404
|
+
if (cmd === "serve") {
|
|
405
|
+
await cmdServe(opts);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const routed = await clientTry(cmd, opts); // route transparently if a daemon is up, else fall back to one-shot
|
|
409
|
+
if (routed != null) { emit(routed); return; }
|
|
410
|
+
if (cmd === "shutdown") { await cmdShutdown(opts); return; }
|
|
411
|
+
if (cmd === "devices") { emit(core.doDevices()); return; }
|
|
412
|
+
if (cmd === "density") {
|
|
413
|
+
const dd = core.probeDensity(opts.device);
|
|
414
|
+
emit({ ok: dd != null, density: dd, ...(dd == null ? { error: "密度探测失败(hidumper 无 Density 行)" } : {}) });
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (cmd in core.HDC_ACTIONS) { emit(core.HDC_ACTIONS[cmd](opts)); return; } // pure hdc, no driver connection
|
|
418
|
+
let d = null, failure = null;
|
|
419
|
+
try {
|
|
420
|
+
d = await core.connect(opts.device, opts.workDir);
|
|
421
|
+
emit(await core.runAction(d, cmd, opts));
|
|
422
|
+
} catch (e) {
|
|
423
|
+
// Defer fail(): process.exit inside the catch would skip the finally disconnect and leak the fport/driver session.
|
|
424
|
+
failure = e instanceof core.DeviceUIError
|
|
425
|
+
? [e.reason, e.extra]
|
|
426
|
+
: [String(e.message || e).slice(0, 300), { tb: String(e.stack || "").slice(-500) }];
|
|
427
|
+
} finally {
|
|
428
|
+
if (d) await core.disconnect(d); // clean up per invocation; never accumulate fports at the source
|
|
429
|
+
}
|
|
430
|
+
if (failure) fail(failure[0], failure[1]);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
main().then(() => process.exit(0)).catch((e) => fail(String(e?.message || e).slice(0, 300)));
|