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,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.ets"
|
|
4
|
+
- "**/*.ts"
|
|
5
|
+
---
|
|
6
|
+
# ArkUI State Management — V1 / V2
|
|
7
|
+
|
|
8
|
+
The single source of truth for choosing and using a state-management family. Decorator details are NOT restated here — they live in the official docs under `../official/mvvm-v1/` and `../official/mvvm-v2/` (referenced, not duplicated).
|
|
9
|
+
|
|
10
|
+
## Family selection (decide first)
|
|
11
|
+
|
|
12
|
+
Both **V1** and **V2** are valid. **Never mix V1 and V2 decorators within one project** — project-level policy; mixing inside one struct is additionally a build error (`../language/arkui-structure-rules.md` § 5). The official mixusage guide (`../official/mvvm-v2/状态管理V1和V2混用指导(API version 19及之后).md`) documents platform capability for migration reference only — this no-mix policy overrides it.
|
|
13
|
+
|
|
14
|
+
- **New / greenfield project** → use **V2**.
|
|
15
|
+
- **Incremental development on an existing project** → match the family the project already uses. Detect from the existing `.ets`:
|
|
16
|
+
- `@ComponentV2` / `@Local` / `@Param` / `@ObservedV2` ⇒ the project is **V2**.
|
|
17
|
+
- `@Component` / `@State` / `@Link` / `@Observed` ⇒ the project is **V1**.
|
|
18
|
+
|
|
19
|
+
## V2 family (cheat sheet)
|
|
20
|
+
|
|
21
|
+
Full official docs: **`../official/mvvm-v2/`** (`@Local`/`@Param`/`@Once`/`@Event`/`@Provider`/`@Consumer`/`@Monitor`/`@Computed`/`@ObservedV2`/`@Trace`/`@Type`, AppStorageV2/PersistenceV2, makeObserved/getTarget, `!!` two-way binding, V1↔V2 mixusage, `MVVM模式(V2).md`).
|
|
22
|
+
|
|
23
|
+
| Decorator | Purpose |
|
|
24
|
+
|-----------|---------|
|
|
25
|
+
| `@ComponentV2` | Marks a struct as a V2 component |
|
|
26
|
+
| `@Local` | Component-internal state |
|
|
27
|
+
| `@Param` | External input from parent (read-only) |
|
|
28
|
+
| `@Once` | Initialize from parent once, then decouple |
|
|
29
|
+
| `@Event` | Output callback to parent |
|
|
30
|
+
| `@Provider` / `@Consumer` | Cross-level two-way sync |
|
|
31
|
+
| `@Monitor` | State-change listener (replaces V1 `@Watch`) |
|
|
32
|
+
| `@Computed` | Derived/computed value |
|
|
33
|
+
| `@ObservedV2` / `@Trace` | Observable class + observable property |
|
|
34
|
+
| `@Type` | Mark a class-property type (for PersistenceV2) |
|
|
35
|
+
|
|
36
|
+
Quick example (see `../official/mvvm-v2/` for the complete reference):
|
|
37
|
+
|
|
38
|
+
```ets
|
|
39
|
+
@ObservedV2
|
|
40
|
+
class UserModel {
|
|
41
|
+
@Trace name: string = ''
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@ComponentV2
|
|
45
|
+
struct UserCard {
|
|
46
|
+
@Param user: UserModel = new UserModel()
|
|
47
|
+
@Event onDelete: () => void = () => {}
|
|
48
|
+
|
|
49
|
+
build() {
|
|
50
|
+
Column() {
|
|
51
|
+
Text(this.user.name)
|
|
52
|
+
Button($r('app.string.delete')).onClick(() => this.onDelete())
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## V1 family (cheat sheet)
|
|
59
|
+
|
|
60
|
+
Full official docs: **`../official/mvvm-v1/`** (`@State`/`@Prop`/`@Link`/`@Provide`/`@Consume`/`@Observed`+`@ObjectLink`/`@Watch`/`@Track`, AppStorage/LocalStorage/PersistentStorage/Environment, `MVVM模式(V1).md`). Use only when the project is already on V1.
|
|
61
|
+
|
|
62
|
+
| Decorator | Purpose |
|
|
63
|
+
|-----------|---------|
|
|
64
|
+
| `@Component` | Marks a struct as a V1 component |
|
|
65
|
+
| `@State` | Component-internal state |
|
|
66
|
+
| `@Prop` | One-way input from parent (local copy) |
|
|
67
|
+
| `@Link` | Two-way binding with parent (`$var`) |
|
|
68
|
+
| `@Provide` / `@Consume` | Cross-level two-way sync |
|
|
69
|
+
| `@Observed` / `@ObjectLink` | Observable class + nested-object binding |
|
|
70
|
+
| `@Watch` | State-change listener |
|
|
71
|
+
| `@Track` | Class property-level update granularity |
|
|
72
|
+
|
|
73
|
+
Quick example (see `../official/mvvm-v1/` for the complete reference):
|
|
74
|
+
|
|
75
|
+
```ets
|
|
76
|
+
@Observed
|
|
77
|
+
class UserModel {
|
|
78
|
+
name: string = ''
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@Component
|
|
82
|
+
struct UserCard {
|
|
83
|
+
@ObjectLink user: UserModel
|
|
84
|
+
onDelete: () => void = () => {}
|
|
85
|
+
|
|
86
|
+
build() {
|
|
87
|
+
Column() {
|
|
88
|
+
Text(this.user.name)
|
|
89
|
+
Button($r('app.string.delete')).onClick(() => this.onDelete())
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## MVVM layering (both families)
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
feature/
|
|
99
|
+
|-- model/ # data classes (V2: @ObservedV2/@Trace; V1: @Observed)
|
|
100
|
+
|-- viewmodel/ # business logic
|
|
101
|
+
|-- view/ # UI components (V2: @ComponentV2; V1: @Component)
|
|
102
|
+
|-- service/ # network / database / file I/O
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- **View**: rendering only — no business logic in `build()`.
|
|
106
|
+
- **ViewModel**: all business logic.
|
|
107
|
+
- **Model**: pure data classes (observable per the chosen family).
|
|
108
|
+
- **Service**: network requests, database, file I/O.
|
|
109
|
+
|
|
110
|
+
## Async init vs synchronous reads (cold-start race)
|
|
111
|
+
|
|
112
|
+
A fire-and-forget `async` initializer races a synchronous reader. Example: bootstrap calls `cache.refresh()` (async) **without** `await`, and a page's `aboutToAppear` reads the cache synchronously → on cold start the reader sees the empty pre-refresh state (blank page). Fix: keep a synchronous cache **and** expose a `whenReady(): Promise<void>` that resolves when `refresh()` completes; `await whenReady()` before relying on the data, and have a synchronous reader trigger one refresh if it read before ready. Never assume a non-awaited `async` init finished before the first read.
|
|
113
|
+
|
|
114
|
+
## State written but never rendered
|
|
115
|
+
|
|
116
|
+
State that is committed correctly but never reaches the screen. The write path passes review — the store holds the new value — and the screen keeps the old one. All four forms compile clean and pass codelinter; each fails on device as "value stored, screen never changes". Mechanical scan: `scripts/state_propagation_check.mjs` (L0 gate, check 3).
|
|
117
|
+
|
|
118
|
+
**1. Value-passed `@Builder` parameter** (blocking). A `@Builder` receiving a primitive **by value** does not subscribe to the source state, so the builder body never re-renders.
|
|
119
|
+
|
|
120
|
+
```ets
|
|
121
|
+
// ✗ the row summary never updates, however correctly `choose()` persists the value
|
|
122
|
+
@Builder row(title: string, summary: string, key: string) {
|
|
123
|
+
if (summary.length > 0) { Text(summary) }
|
|
124
|
+
}
|
|
125
|
+
this.row('Notebook', this.notebookLocation, 'pref_key__notebook_directory')
|
|
126
|
+
|
|
127
|
+
// ✓ pass by reference
|
|
128
|
+
this.row('Notebook', $$this.notebookLocation, 'pref_key__notebook_directory')
|
|
129
|
+
// ✓ or read the state inside the builder, or split into @Component + @Prop/@Link
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**2. keyGenerator omitting the rendered value** (suspect). `ForEach`'s third argument keyed only on index or a static id means an item is **not rebuilt** when the state it renders changes. Include the rendered value: `(item, i) => \`${item.id}_${item.value}\``.
|
|
133
|
+
|
|
134
|
+
The state does **not** have to be read in the item body to count. It is routinely reached through a helper — `ForEach(this.library, (t) => { this.TagRow(t) }, (t) => t.id)`, where `TagRow` calls `isSelected()` which reads `this.selectedIds`. The key looks complete at the call site while the row still depends on changing state, so the scan follows `this.someMethod(...)` calls out of the item body and says "reached indirectly" when it found the state that way.
|
|
135
|
+
|
|
136
|
+
This one stays **suspect, not blocking**, and the reason is worth knowing: the shape alone does not decide it. "Selection indicator keyed by id" appears verbatim on shipped code that scores 100% (a bottom-tab bar keyed by `tab.id` omitting `currentTab`; a swatch list keyed by `s.id` omitting `accent`) and on code that fails for exactly this reason (a checkmark row keyed by `${dayIndex}` omitting `todayValue`). What separates them is **how the value reaches the child**: passed in as a constructor argument to an `@Prop`, a reused node keeps the stale copy — that is the defect, and the fix is to hold it as `@State` in the child and update locally on the tap, or put it in the key. Read inline in the item body, it usually re-evaluates fine. Judge each finding on that, not on the key.
|
|
137
|
+
|
|
138
|
+
**3. Framework callback overwriting a programmatic set** (blocking). Setting state next to a controller call while the matching `on*Change` writes the same member — the callback fires on the next frame and wins.
|
|
139
|
+
|
|
140
|
+
```ets
|
|
141
|
+
// ✗ caretOffset is reset to the document end before the next action reads it
|
|
142
|
+
this.caretOffset = result.caret;
|
|
143
|
+
this.editorController.caretPosition(result.caret);
|
|
144
|
+
// …meanwhile:
|
|
145
|
+
.onTextSelectionChange((start: number, end: number) => { this.caretOffset = end; })
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Symptom shape worth memorizing: **the forward action passes and the inverse action fails** (apply bold ✓, un-bold ✗). Fix with a suppression flag around the programmatic write, or hold a semantic anchor (line index, item id) instead of a raw offset that drifts with text length.
|
|
149
|
+
|
|
150
|
+
**4. Boolean control state gated only on an engine event** (suspect). `@State isPlaying` flipped solely by `player.on('stateChange', …)`: if the engine never reaches that state the control renders its initial value forever, and every test that must first drive this control dies at step 1. Keep a synchronous write path (optimistic update, roll back on failure) plus a timeout fallback state. A *data* field arriving from the engine (title, elapsed, list) is fine — a *control's* on/off state is not.
|
|
151
|
+
|
|
152
|
+
**Rule of thumb.** For any preference / toggle / theme change, the assertion is the **rendered result**, not the stored value. If a change is only observable in the store, it is not implemented. Trace it end to end: state → token or derived value → the component that paints it.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# ArkUI UI Quality, Animation & Performance
|
|
2
|
+
|
|
3
|
+
The checklist below runs before finalizing ArkUI UI changes (after the build passes — this is about whether the UI meets the requirement, not whether it compiles). Animation and performance practices follow at the end.
|
|
4
|
+
|
|
5
|
+
## Requirement visibility
|
|
6
|
+
|
|
7
|
+
- Required labels, button text, tab labels, card titles, and dialog text appear on the target screen.
|
|
8
|
+
- Text is not hidden by overlays, zero-size containers, off-screen placement, or low contrast.
|
|
9
|
+
- The first screen remains reachable from the app launch path.
|
|
10
|
+
|
|
11
|
+
## Interaction
|
|
12
|
+
|
|
13
|
+
- Required buttons use `Button` when the requirement asks for `Button`.
|
|
14
|
+
- Click handlers update visible state, switch tabs, open dialogs, navigate, or show the requested response.
|
|
15
|
+
- Cancel and close actions do not perform extra business actions.
|
|
16
|
+
- Required click targets remain large enough and visually clear.
|
|
17
|
+
|
|
18
|
+
## Layout quality
|
|
19
|
+
|
|
20
|
+
- New UI follows nearby spacing, color, typography, density, and component style.
|
|
21
|
+
- Layout nesting is only deep enough to express the UI.
|
|
22
|
+
- Dynamic text, lists, and tab content have stable width/height constraints where needed.
|
|
23
|
+
- New elements do not overlap existing banners, cards, bottom bars, or system safe areas.
|
|
24
|
+
|
|
25
|
+
## State and rendering
|
|
26
|
+
|
|
27
|
+
- UI-driving state uses the current component's decorator family.
|
|
28
|
+
- Lists and grids render stable keys.
|
|
29
|
+
- Conditional UI still leaves required content reachable.
|
|
30
|
+
- Dialog, toast, and navigation code follows existing project patterns.
|
|
31
|
+
- A preference / toggle / theme change is confirmed by its **rendered** difference, not by the stored value — a change observable only in the store is not implemented.
|
|
32
|
+
- State reaching the screen is traced end to end: state → token or derived value → the component that paints it (see `state-management.md` § State written but never rendered).
|
|
33
|
+
|
|
34
|
+
## Change scope
|
|
35
|
+
|
|
36
|
+
- Only files required by the UI task are modified.
|
|
37
|
+
- Existing business flow, page registration, resource naming, and navigation architecture are preserved.
|
|
38
|
+
- No state-management migration, navigation rewrite, or broad refactor is introduced without an explicit request.
|
|
39
|
+
|
|
40
|
+
## Animation practices
|
|
41
|
+
|
|
42
|
+
- Drive animations with state variables (declarative), not imperative frame updates; use `animateTo` for explicit control.
|
|
43
|
+
- Prefer `transform` (`translate` / `scale` / `rotate`) and `opacity` — they are cheap to animate.
|
|
44
|
+
- **Never** animate `width` / `height` / `padding` / `margin` frequently — severe performance impact.
|
|
45
|
+
- Set `renderGroup(true)` on complex sub-component animations to reduce render batches.
|
|
46
|
+
- Prefer native HarmonyOS animation APIs and advanced templates.
|
|
47
|
+
|
|
48
|
+
Example (state-driven):
|
|
49
|
+
|
|
50
|
+
```ets
|
|
51
|
+
@ComponentV2
|
|
52
|
+
struct AnimatedCard {
|
|
53
|
+
@Local cardScale: number = 0.8
|
|
54
|
+
build() {
|
|
55
|
+
Column() { /* ... */ }
|
|
56
|
+
.scale({ x: this.cardScale, y: this.cardScale })
|
|
57
|
+
.animation({ duration: 300, curve: Curve.EaseInOut })
|
|
58
|
+
.onClick(() => { this.cardScale = this.cardScale === 1.0 ? 0.8 : 1.0 })
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Performance practices
|
|
64
|
+
|
|
65
|
+
- Large lists/grids use `LazyForEach` with a business-ID key generator (third arg, never the index) — verified form: `component-cookbook.md` § List, Grid, and ForEach.
|
|
66
|
+
- Extract reusable UI into separate components; use `@Builder` for lightweight fragments.
|
|
67
|
+
- Keep the component tree only as deep as the UI requires.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# ArkUI Runtime UI Diagnosis (layout collapse & visual-diff attribution)
|
|
2
|
+
|
|
3
|
+
Method for a **diagnosis round** on a UI-class failure (routed by the orchestrator when a failure signature enters its 2nd fix round): locate the culprit from runtime evidence, then fix once — never another blind rewrite. The two failure shapes below cover the measured field losses (7 rewrite rounds on one collapsed row; 2 rounds fixing gradient stops when the culprit was an overlying card).
|
|
4
|
+
|
|
5
|
+
## Layout collapse (bounds collapse to 0 / children dropped)
|
|
6
|
+
|
|
7
|
+
Symptom: an element renders blank or tiny; `observe --bound` shows its bound collapsed (or the child missing) while the code declares sizes.
|
|
8
|
+
|
|
9
|
+
1. **Find the deciding boundary, bottom-up**: `observe --bound --raw` the subtree; walk up from the collapsed node to the first ancestor whose bound is sane. The collapse is decided at that parent↔child boundary — everything below it is downstream.
|
|
10
|
+
2. **Read that boundary's constraint context**, not the collapsed node's own modifiers: a content-sized parent measured against a full-height sibling, `alignContent`/`offset`/`translate`/animation composites, `.clip` — the measure input is what shrank, the child merely obeyed.
|
|
11
|
+
3. **Bisect in place**: toggle the boundary's suspects one at a time (drop the sibling, remove one modifier), rebuild the minimal pair, re-observe. One boundary, one variable per build — never a whole-page rewrite per round.
|
|
12
|
+
|
|
13
|
+
Measured traps (extend as confirmed):
|
|
14
|
+
|
|
15
|
+
- A full-height sibling inside `Stack({alignContent: Bottom})` composed with offset/margin collapses a content-sized sibling Row to its text height, silently dropping the Image (measured: 7 blind rounds; the row measured 14px).
|
|
16
|
+
- `bindContentCover` shows a full-screen cover that intercepts touch: the host's `PanGesture` never receives events while the visuals look correct — a gesture defect invisible to source-level checks (measured: shipped dead press-drag).
|
|
17
|
+
- A multiplicative opacity/color matrix keeps hue: a white icon ×0.6 over a white backdrop stays white — the spec clause is math-unreachable, fix the spec or the asset, not the matrix (measured).
|
|
18
|
+
- `.scale` `centerY` must be a string percentage (`'100%'`); a number is vp, so a bottom-anchored scale pivots off-anchor (measured on a bottom-anchored card deck).
|
|
19
|
+
|
|
20
|
+
## Visual-diff layer attribution (which layer paints the offending pixels)
|
|
21
|
+
|
|
22
|
+
Symptom: captured page differs from the draft in a region; the tempting fix ("the property that looks closest") is usually the wrong layer.
|
|
23
|
+
|
|
24
|
+
1. **List the z-stack over the diff region**: from `observe --bound` (+`--raw` when overlays hide layers), every element whose bound covers the region, ordered by z (tree order / `zIndex`).
|
|
25
|
+
2. **Attribute by paint, top-down**: compare what the region actually renders (a screenshot region inspected by eye, or the element's resolved style read back from the component tree) against each layer's declared paint (bg color / gradient slice / image). The first layer whose declared paint matches the offending output is the culprit.
|
|
26
|
+
3. **Confirm by minimal toggle**: change only the suspect (hide it, or adjust only its paint), re-capture — the diff must move with it before any real fix lands.
|
|
27
|
+
|
|
28
|
+
Measured: a "gradient shows a hard cut" diff was fixed as gradient stops for 2 rounds with identical symptoms; the culprit was an opaque white rounded-card Rect covering the gradient — step 1's layer list names it immediately.
|
|
29
|
+
|
|
30
|
+
## Deliverable of a diagnosis round
|
|
31
|
+
|
|
32
|
+
Culprit boundary/layer + the minimal repro pair + evidence (targeted observe bounds, probe outputs, hilog at the failure moment) + a one-fix plan. No code delivery in the diagnosis round; the following fix round cites this evidence.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Package-set collection (entry HAP + in-app HSPs)
|
|
2
|
+
|
|
3
|
+
Canonical shared procedure for harvesting the **full package set** — the entry HAP plus any in-app HSPs (and any feature HAP the build freshly produced) — so the installer applies them in one transaction (in-app HSPs cannot install standalone). Referenced by whichever stage performs the build — keep the rule here, not duplicated per stage.
|
|
4
|
+
|
|
5
|
+
**Signing mode follows the project, not this procedure.** A project whose product references a `signingConfig` emits `*-signed.*`; one with `app.signingConfigs: []` emits `*-unsigned.*` and cannot be made to emit anything else without changing the project. Both are collected. Refusing the unsigned variant does not produce a signed package — it produces **no package set at all**, which silently disables every device layer downstream (L1 unit, L2 E2E, integration) and reports the cause as a missing artifact rather than as missing signing config.
|
|
6
|
+
|
|
7
|
+
Two rules keep this safe:
|
|
8
|
+
|
|
9
|
+
- **Signed wins within a directory.** When a module output dir holds both variants, take the signed one. Never prefer an unsigned package over a signed one — the unsigned file may be an older leftover.
|
|
10
|
+
- **The set is uniform.** Every package in `DEST` must share one signing mode. A mixed set fails the single install transaction, so a mix is a collection failure (step 5), not a warning.
|
|
11
|
+
|
|
12
|
+
Whether an unsigned package can actually be installed depends on the device and its developer-mode settings. Collection succeeds regardless; the installer reports the real outcome. Record the mode so a later install failure is read as "unsigned package rejected by device", not as a code defect.
|
|
13
|
+
|
|
14
|
+
**Freshness gate.** The builder runs the pinned `devecocli build --build-mode <mode>` (default product = the `entry` module plus whatever hvigor transitively rebuilds on entry's dependency path), and copies only the entry HAP. Non-entry module output dirs keep whatever a *prior* build left, so a blind glob would pick up **stale** packages and install stale code. The mtime gate below is the only reliable discriminator.
|
|
15
|
+
|
|
16
|
+
## Inputs
|
|
17
|
+
|
|
18
|
+
- `BUILD_ROOT` — the project root the build compiled (= `harmonyos-project-path`).
|
|
19
|
+
- `DEST` — a dedicated `package-set/` directory to populate; **must never be the directory the builder copied the entry HAP into** (step 1 clears `DEST`).
|
|
20
|
+
- `BUILD_START` — a **transient** freshness marker captured **immediately before** the first build: an empty file in the system temp dir, **not** a round artifact under `build/`. Create it with whatever the session shell supports (the shell is PowerShell on Windows, so do not rely on `mktemp`). Used to tell freshly-rebuilt artifacts from stale ones. A touched marker + a newer-than comparison avoids clock-skew between the agent and the build host.
|
|
21
|
+
- `AUTHORITATIVE_ENTRY` — the entry HAP the builder **copied** to the caller's output dir (the `*-signed.hap` or `*-unsigned.hap` it placed — list `entry/build/default/outputs/default/` to get the filename actually produced; do not infer it from a signing flag). Authoritative **by construction**, regardless of mtime.
|
|
22
|
+
- `SIGNING_MODE` — derived from `AUTHORITATIVE_ENTRY`'s basename: `signed` for `*-signed.hap`, `unsigned` for `*-unsigned.hap`. It fixes the mode the rest of the set must match.
|
|
23
|
+
|
|
24
|
+
## Procedure
|
|
25
|
+
|
|
26
|
+
1. **Clear/create `DEST`** (remove prior contents, recreate) so stale packages never leak forward. `DEST` is a dedicated `package-set/` subdir, distinct from where the builder copied the entry.
|
|
27
|
+
2. **Place the authoritative entry:** copy `AUTHORITATIVE_ENTRY` into `DEST`. Included **unconditionally — the mtime gate does NOT apply to the entry** (an incremental build may leave the build-tree entry UP-TO-DATE; the builder's copy still reflects the round's code).
|
|
28
|
+
3. **Discover non-entry outputs** under depth-1 modules `BUILD_ROOT/*/build/default/outputs/default/`, admitting **only files with mtime ≥ `BUILD_START`** (freshly rebuilt this invocation; cross-platform: `find "<dir>" -newer "<marker>"` or compare `stat` mtimes):
|
|
29
|
+
- **HSPs:** `*-<SIGNING_MODE>.hsp` (the fresh in-app HSPs).
|
|
30
|
+
- **Feature HAPs:** `*-<SIGNING_MODE>.hap` from non-entry modules the build freshly rebuilt; skip any whose basename equals `AUTHORITATIVE_ENTRY`'s.
|
|
31
|
+
|
|
32
|
+
Within one module output dir, a fresh `*-signed.*` always beats a fresh `*-unsigned.*` of the same module. If that contradicts `SIGNING_MODE` (a signed module package alongside an unsigned entry, or the reverse), it is a mixed set — see step 5.
|
|
33
|
+
4. **Exclude test artifacts:** skip `*ohosTest*` and `*-test-signed.*` / `*-test-unsigned.*`.
|
|
34
|
+
5. **Uniform-mode rule (all-or-nothing):** if any module dir has a **freshly-rebuilt** `.hsp`/`.hap` whose signing mode differs from `SIGNING_MODE`, or has a fresh package in neither mode's naming form, mark the collection **incomplete** → the caller falls back to the entry-only set and logs the mismatch as a build-config gap. (A *stale* package is ignored — it never passes the mtime gate.) Rationale: packages install in one transaction, so one package the device rejects fails the whole install; a *partial* set is also unsafe.
|
|
35
|
+
6. **Copy** the selected non-entry files into `DEST` (flat). (The entry is already there from step 2.)
|
|
36
|
+
7. **Invariants:**
|
|
37
|
+
- **≥ 1 `.hap`** — guaranteed by step 2. If `AUTHORITATIVE_ENTRY` is missing, collection **fails** → caller fallback.
|
|
38
|
+
- **Exactly one `entry-*.hap`** (the authoritative one). If the fresh harvest still yields a second, differently-named `entry-*.hap`, **fail** the collection and log the conflict (the installer picks `entry-*` first and cannot disambiguate two entries). Non-`entry-*` feature HAPs may be many.
|
|
39
|
+
- On a same-basename collision in `DEST`, abort with a logged error listing sources.
|
|
40
|
+
- Manifest inventory line: `package-set: 1 entry hap + F feature hap(s) + M hsp(s), mode=<SIGNING_MODE> → DEST` (and `WARNING: K package(s) skipped — signing-mode mismatch, build-config gap` if any). When `SIGNING_MODE=unsigned`, add `NOTE: unsigned package set — install depends on device developer mode; an install rejection here is an environment result, not a code defect`.
|
|
41
|
+
8. **Result:** `DEST` is the package set the caller installs from.
|
|
42
|
+
|
|
43
|
+
> **Build-completeness constraint (HSP projects).** Whether a default `devecocli build` emits an `.hsp` at all, and *freshly*, depends on hvigor's transitive-build behavior — on the first run against an HSP project, confirm by capturing a temp marker, running a build (`build_project` — it emits signed artifacts whenever the project is configured for signing, unsigned ones otherwise), then listing artifacts newer than that marker under `*/build/default/outputs/default/` (`*.hsp` / `*.hap`). If nothing fresh is emitted, this procedure collects **entry-only** (safe — equivalent to a single-HAP install). Independent feature modules (not on entry's dependency path) are never rebuilt by the entry-targeted build; collecting their HAPs needs a build change (out of scope).
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Shared helpers for scripts (not an entry point).
|
|
2
|
+
// Contents: emit single-line JSON output contract, the jsonl ledger trio, cross-process append lock.
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
|
|
5
|
+
export function emit(obj) {
|
|
6
|
+
// Compact single-line JSON to stdout (script-to-agent output contract). Node defaults to UTF-8, no reconfigure needed.
|
|
7
|
+
process.stdout.write(JSON.stringify(obj) + "\n");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function loadJsonl(pathStr) {
|
|
11
|
+
// Read jsonl as an ordered entry list (bad lines skipped; one dirty line must not kill the whole query).
|
|
12
|
+
if (!fs.existsSync(pathStr)) return [];
|
|
13
|
+
const out = [];
|
|
14
|
+
for (const raw of fs.readFileSync(pathStr, "utf8").split(/\r?\n/)) {
|
|
15
|
+
const line = raw.trim();
|
|
16
|
+
if (!line) continue;
|
|
17
|
+
try { out.push(JSON.parse(line)); } catch { /* skip bad line */ }
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function latestBy(entries, key) {
|
|
23
|
+
// Key value → latest entry (append-only ledger convention: last write wins on read). Entries missing the key are ignored.
|
|
24
|
+
const by = {};
|
|
25
|
+
for (const e of entries) { const k = e[key]; if (k) by[k] = e; }
|
|
26
|
+
return by;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function lockedAppend(pathStr, fn) {
|
|
30
|
+
// Cross-process mutually exclusive append. Under multi-device parallelism several writers append the same jsonl; unlocked writes can interleave and tear lines.
|
|
31
|
+
// Lock = sidecar `<file>.lock` created exclusively with O_EXCL (cross-platform, dep-free), spin-wait, 30s timeout.
|
|
32
|
+
const lockPath = pathStr + ".lock";
|
|
33
|
+
const deadline = Date.now() + 30000;
|
|
34
|
+
let fd = null;
|
|
35
|
+
for (;;) {
|
|
36
|
+
try { fd = fs.openSync(lockPath, "wx"); break; } // wx = O_CREAT|O_EXCL, throws if it already exists
|
|
37
|
+
catch (e) {
|
|
38
|
+
if (e.code !== "EEXIST") throw e;
|
|
39
|
+
// Stale-lock reclaim: a lock is held for ms — one older than 10s is a dead holder
|
|
40
|
+
// (killed process); remove it and retry (an unlink race with a live holder is benign:
|
|
41
|
+
// both writers still serialize on the fresh lock).
|
|
42
|
+
try {
|
|
43
|
+
if (Date.now() - fs.statSync(lockPath).mtimeMs > 10000) { fs.unlinkSync(lockPath); continue; }
|
|
44
|
+
} catch { /* lock vanished between stat/unlink — retry acquires normally */ }
|
|
45
|
+
if (Date.now() > deadline) throw new Error(`acquire ${lockPath} timed out (30s); stale locks self-reclaim after 10s — a live writer is holding it`);
|
|
46
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return await fn((line) => fs.appendFileSync(pathStr, line, "utf8"));
|
|
51
|
+
} finally {
|
|
52
|
+
try { fs.closeSync(fd); } catch { /* ignore */ }
|
|
53
|
+
try { fs.unlinkSync(lockPath); } catch { /* ignore */ }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function defaultDie(msg) {
|
|
58
|
+
// `<script>: error: <msg>` to stderr, exit 2 (shared CLI arg-parse failure path). Prog name = argv[1] basename.
|
|
59
|
+
const prog = (process.argv[1] || "script").split(/[/\\]/).pop();
|
|
60
|
+
process.stderr.write(`${prog}: error: ${msg}\n`);
|
|
61
|
+
process.exit(2);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function parseFlags(argv, spec, die = defaultDie) {
|
|
65
|
+
// Parse `--flag value` / `--flag=value` into { specValue: value }. `spec` maps the written flag
|
|
66
|
+
// (dashed, no `--`) → its output key; an unrecognized flag or a missing value calls `die` (fail-fast).
|
|
67
|
+
const out = {};
|
|
68
|
+
for (let i = 0; i < argv.length; i++) {
|
|
69
|
+
let tok = argv[i];
|
|
70
|
+
if (!tok.startsWith("--")) die(`unexpected argument: ${tok}`);
|
|
71
|
+
let val;
|
|
72
|
+
const eq = tok.indexOf("=");
|
|
73
|
+
if (eq !== -1) { val = tok.slice(eq + 1); tok = tok.slice(0, eq); }
|
|
74
|
+
const name = tok.slice(2);
|
|
75
|
+
if (!(name in spec)) die(`unrecognized argument: --${name}`);
|
|
76
|
+
if (val === undefined) {
|
|
77
|
+
// next token missing, or itself a flag (else `--a --b` silently swallows --b as --a's value) → fail-fast
|
|
78
|
+
if (i + 1 >= argv.length || argv[i + 1].startsWith("--")) die(`argument --${name}: expected one argument`);
|
|
79
|
+
val = argv[++i];
|
|
80
|
+
}
|
|
81
|
+
out[spec[name]] = val;
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function requireFlags(vals, names, die = defaultDie) {
|
|
87
|
+
// Enforce required flags. `names` = [{ key, flag }] (key = output key, flag = written flag for the message).
|
|
88
|
+
for (const n of names) {
|
|
89
|
+
if (vals[n.key] === undefined) die(`the following arguments are required: --${n.flag}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// HarmonyOS system-UI denylist for view-tree capture: skip these node TYPES / system-app BUNDLES
|
|
94
|
+
// (drop the node itself, keep walking its subtree). Single source consumed by device_ui_core.collectElements —
|
|
95
|
+
// turns a raw dumpLayout tree into a clean app-only tree, stripping OS scaffolding + other-app surfaces.
|
|
96
|
+
export const HMOS_SKIP_TYPES = new Set(["WindowScene", "__Common__", "EffectComponent", "metaballNode", "RootScene"]);
|
|
97
|
+
export const HMOS_SKIP_BUNDLES = new Set(["com.huawei.systemui", "com.ohos.systemui", "com.huawei.android.launcher"]);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Shared config reader for scripts (conf.yaml is its sole responsibility).
|
|
2
|
+
// Single source of truth = repo-root conf.yaml (copied from conf.example.yaml). Locate the repo root
|
|
3
|
+
// upward from this file, parse flat YAML, fall back to same-named OS env vars. On empty values the caller prompts the user.
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
export function repoRoot() {
|
|
11
|
+
// Walk up from this file to the directory containing conf.yaml or conf.example.yaml
|
|
12
|
+
for (let d = HERE; ; ) {
|
|
13
|
+
if (fs.existsSync(path.join(d, "conf.yaml")) || fs.existsSync(path.join(d, "conf.example.yaml"))) return d;
|
|
14
|
+
const up = path.dirname(d);
|
|
15
|
+
if (up === d) return null;
|
|
16
|
+
d = up;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function parseFlatYaml(text) {
|
|
21
|
+
// Parse only flat `KEY: value` lines (this config has no nesting/lists). Comments and blank lines ignored.
|
|
22
|
+
const out = {};
|
|
23
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
24
|
+
const line = raw.trim();
|
|
25
|
+
if (!line || line.startsWith("#") || !line.includes(":")) continue;
|
|
26
|
+
const i = line.indexOf(":");
|
|
27
|
+
const key = line.slice(0, i).trim();
|
|
28
|
+
let val = line.slice(i + 1).trim();
|
|
29
|
+
if (val.startsWith("'") || val.startsWith('"')) {
|
|
30
|
+
const q = val[0], end = val.indexOf(q, 1);
|
|
31
|
+
val = end !== -1 ? val.slice(1, end) : val.slice(1);
|
|
32
|
+
} else {
|
|
33
|
+
val = val.split("#", 1)[0].trim();
|
|
34
|
+
}
|
|
35
|
+
if (key) out[key] = val;
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let _cache = null;
|
|
41
|
+
let _warned = false;
|
|
42
|
+
function load() {
|
|
43
|
+
if (_cache !== null) return _cache;
|
|
44
|
+
_cache = {};
|
|
45
|
+
const root = repoRoot();
|
|
46
|
+
const p = root && path.join(root, "conf.yaml");
|
|
47
|
+
try {
|
|
48
|
+
if (p && fs.existsSync(p)) _cache = parseFlatYaml(fs.readFileSync(p, "utf8"));
|
|
49
|
+
} catch (e) {
|
|
50
|
+
if (!_warned) { _warned = true; process.stderr.write(`警告: 读取 ${p} 失败(${e.message})——按空配置继续\n`); }
|
|
51
|
+
}
|
|
52
|
+
return _cache;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getKey(name, dflt = "") {
|
|
56
|
+
// conf.yaml → OS env var → default
|
|
57
|
+
return (load()[name] || "").trim() || (process.env[name] || "").trim() || dflt;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function requireKey(name, purpose = "") {
|
|
61
|
+
// Fetch a required key; if empty, print a lazy hint and exit non-zero
|
|
62
|
+
const val = getKey(name, "");
|
|
63
|
+
if (!val) {
|
|
64
|
+
const hint = purpose ? `(${purpose})` : "";
|
|
65
|
+
const where = (repoRoot() && path.join(repoRoot(), "conf.yaml")) || "<repo>/conf.yaml(由 conf.example.yaml 复制)";
|
|
66
|
+
process.stderr.write(`错误: 配置项 ${name} 为空${hint}。请在 ${where} 填写后重试(或设置同名环境变量 ${name})。\n`);
|
|
67
|
+
process.exit(2);
|
|
68
|
+
}
|
|
69
|
+
return val;
|
|
70
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Generic keyed-lessons ledger CLI skeleton (not an entry point) — shared by build_lessons.mjs
|
|
2
|
+
// and code_pattern_lessons.mjs, whose command surfaces are identical up to the key field and
|
|
3
|
+
// field sets: append (validated fields, locked) / match (exact-key list, latest per key) / list.
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
import { emit, loadJsonl, latestBy, lockedAppend, parseFlags, requireFlags, defaultDie as die } from "./common.mjs";
|
|
8
|
+
|
|
9
|
+
// cfg: { key: ledger key field, matchFlag: match's list flag name,
|
|
10
|
+
// required: [[flag, field], …], optional: [[flag, field], …] }
|
|
11
|
+
export async function runLessonsCli(argv, cfg) {
|
|
12
|
+
const cmd = argv[0];
|
|
13
|
+
const rest = argv.slice(1);
|
|
14
|
+
if (cmd === "append") await cmdAppend(rest, cfg);
|
|
15
|
+
else if (cmd === "match") cmdMatch(rest, cfg);
|
|
16
|
+
else if (cmd === "list") cmdList(rest, cfg);
|
|
17
|
+
else if (cmd === undefined) die("the following arguments are required: cmd");
|
|
18
|
+
else die(`argument cmd: invalid choice: '${cmd}' (choose from 'append', 'match', 'list')`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const specOf = (pairs) => Object.fromEntries(pairs);
|
|
22
|
+
|
|
23
|
+
async function cmdAppend(argv, cfg) {
|
|
24
|
+
const v = parseFlags(argv, { ledger: "ledger", ...specOf(cfg.required), ...specOf(cfg.optional) });
|
|
25
|
+
requireFlags(v, [{ key: "ledger", flag: "ledger" }, ...cfg.required.map(([flag, key]) => ({ key, flag }))]);
|
|
26
|
+
fs.mkdirSync(path.dirname(path.resolve(v.ledger)), { recursive: true });
|
|
27
|
+
const entry = {};
|
|
28
|
+
for (const [, key] of cfg.required) entry[key] = v[key];
|
|
29
|
+
for (const [, key] of cfg.optional) if (v[key]) entry[key] = v[key];
|
|
30
|
+
await lockedAppend(v.ledger, (append) => { append(JSON.stringify(entry) + "\n"); });
|
|
31
|
+
emit({ ok: true, appended: entry });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function cmdMatch(argv, cfg) {
|
|
35
|
+
const v = parseFlags(argv, { ledger: "ledger", [cfg.matchFlag]: "wants" });
|
|
36
|
+
requireFlags(v, [{ key: "ledger", flag: "ledger" }, { key: "wants", flag: cfg.matchFlag }]);
|
|
37
|
+
const byKey = latestBy(loadJsonl(v.ledger), cfg.key);
|
|
38
|
+
const want = v.wants.split(",").map((s) => s.trim()).filter((s) => s);
|
|
39
|
+
const hits = want.filter((s) => s in byKey).map((s) => byKey[s]);
|
|
40
|
+
emit({ matched: hits, count: hits.length });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function cmdList(argv, cfg) {
|
|
44
|
+
const v = parseFlags(argv, { ledger: "ledger" });
|
|
45
|
+
requireFlags(v, [{ key: "ledger", flag: "ledger" }]);
|
|
46
|
+
const lessons = Object.values(latestBy(loadJsonl(v.ledger), cfg.key));
|
|
47
|
+
emit({ lessons, count: lessons.length });
|
|
48
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// build-fix lessons ledger — build error → fix, reused across features, append-only jsonl.
|
|
2
|
+
//
|
|
3
|
+
// Ledger = <output-dir>/memory/build-lessons.jsonl, one line = root cause + fix for one class of build error, keyed by error_sig.
|
|
4
|
+
// Before fixing, `match` known fixes and apply them first (build capability is inlined; whoever builds uses it); only after the
|
|
5
|
+
// build **truly turns green**, `append` one entry per previously unseen error_sig (unverified guesses do not enter). Run `node build_lessons.mjs <cmd>`.
|
|
6
|
+
//
|
|
7
|
+
// error_sig = compiler error code + normalized message (strip file paths/line numbers/concrete identifiers, keep error class and symbol kind);
|
|
8
|
+
// same error_sig = same class of error. Multiple entries per sig: the **latest** wins (append-only, last write wins on read).
|
|
9
|
+
//
|
|
10
|
+
// Commands:
|
|
11
|
+
// append --ledger P --error-sig SIG --root-cause C --fix F [--scope S] [--symptom Y] [--head H]
|
|
12
|
+
// Append one build lesson.
|
|
13
|
+
// match --ledger P --errors SIG1,SIG2,…
|
|
14
|
+
// Return lessons hit by exact error_sig match, latest entry per sig.
|
|
15
|
+
// list --ledger P
|
|
16
|
+
// Emit all lessons (latest per error_sig).
|
|
17
|
+
import { runLessonsCli } from "./_lib/lessons_cli.mjs";
|
|
18
|
+
|
|
19
|
+
runLessonsCli(process.argv.slice(2), {
|
|
20
|
+
key: "error_sig",
|
|
21
|
+
matchFlag: "errors",
|
|
22
|
+
required: [["error-sig", "error_sig"], ["root-cause", "root_cause"], ["fix", "fix"]],
|
|
23
|
+
optional: [["scope", "scope"], ["symptom", "symptom"], ["head", "head"]],
|
|
24
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// code-pattern lessons ledger —— runtime pitfalls that compile clean but crash on-device / read empty,
|
|
2
|
+
// distilled into avoidance rules, reused across features, append-only jsonl.
|
|
3
|
+
//
|
|
4
|
+
// Ledger = <output-dir>/memory/code-pattern-lessons.jsonl, one line = one class of code-pattern pitfall,
|
|
5
|
+
// keyed by pattern (stable tag). logic-code reads the whole ledger as prior before coding (ledger is small,
|
|
6
|
+
// relevance judged by the model); on a new on-device failure localized to a fresh pitfall, **after on-device
|
|
7
|
+
// confirmation** append one entry (unverified guesses do not enter). Pure stdlib, run `node code_pattern_lessons.mjs <cmd>`.
|
|
8
|
+
//
|
|
9
|
+
// vs build-lessons: build-lessons records **compile-error→fix**, matched precisely by error_sig; this ledger
|
|
10
|
+
// records **compiles-but-runtime** pitfalls, read whole by logic-code as prior. Generic ArkTS pitfalls belong
|
|
11
|
+
// in rules/arkts/ (coding authority); this ledger only collects **project-learned** patterns.
|
|
12
|
+
//
|
|
13
|
+
// Fields: pattern (stable tag, same name keeps latest) / symptom / root_cause / rule / example (optional) / head (optional, calibrated git short hash).
|
|
14
|
+
//
|
|
15
|
+
// Commands:
|
|
16
|
+
// append --ledger P --pattern TAG --symptom Y --root-cause C --rule R [--example E] [--head H]
|
|
17
|
+
// list --ledger P
|
|
18
|
+
// match --ledger P --patterns TAG1,TAG2,…
|
|
19
|
+
import { runLessonsCli } from "./_lib/lessons_cli.mjs";
|
|
20
|
+
|
|
21
|
+
runLessonsCli(process.argv.slice(2), {
|
|
22
|
+
key: "pattern",
|
|
23
|
+
matchFlag: "patterns",
|
|
24
|
+
required: [["pattern", "pattern"], ["symptom", "symptom"], ["root-cause", "root_cause"], ["rule", "rule"]],
|
|
25
|
+
optional: [["example", "example"], ["head", "head"]],
|
|
26
|
+
});
|