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,1066 @@
|
|
|
1
|
+
import { createStableId } from "../../core/stable-id.js";
|
|
2
|
+
import { componentIdentifierName } from "../names/policy.js";
|
|
3
|
+
function graphId(namespace, caseId, entityKind, identity) {
|
|
4
|
+
return createStableId({
|
|
5
|
+
namespace,
|
|
6
|
+
caseId,
|
|
7
|
+
entityKind,
|
|
8
|
+
identity: [{ kind: "semantic-key", value: identity }],
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function display(value, fallback) {
|
|
12
|
+
return value.status === "resolved" ? value.value : fallback;
|
|
13
|
+
}
|
|
14
|
+
function uniqueSorted(values) {
|
|
15
|
+
return [...new Set(values)].sort((left, right) =>
|
|
16
|
+
left.localeCompare(right, "en"),
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
function resolved(value, supportRefs, confidence = "high") {
|
|
20
|
+
return {
|
|
21
|
+
status: "resolved",
|
|
22
|
+
value,
|
|
23
|
+
confidence,
|
|
24
|
+
supportRefs: uniqueSorted(supportRefs),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function targetKey(target) {
|
|
28
|
+
return "status" in target
|
|
29
|
+
? `unresolved:${target.unresolvedRef}`
|
|
30
|
+
: `${target.kind}:${target.ref}`;
|
|
31
|
+
}
|
|
32
|
+
function targetBranchConditions(action, branchKey, branchEdges, graph, source) {
|
|
33
|
+
const rawAlternatives = branchEdges.flatMap((edge) =>
|
|
34
|
+
edge.conditionAlternatives && edge.conditionAlternatives.length > 0
|
|
35
|
+
? edge.conditionAlternatives
|
|
36
|
+
: [
|
|
37
|
+
{
|
|
38
|
+
conditionRefs: edge.conditions,
|
|
39
|
+
sourceFactRefs: edge.sourceFactRefs,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
);
|
|
43
|
+
const alternativesByConditions = new Map();
|
|
44
|
+
for (const alternative of rawAlternatives) {
|
|
45
|
+
const conditionRefs = uniqueSorted(alternative.conditionRefs);
|
|
46
|
+
const key = conditionRefs.join("\u0000");
|
|
47
|
+
const existing = alternativesByConditions.get(key);
|
|
48
|
+
alternativesByConditions.set(key, {
|
|
49
|
+
conditionRefs,
|
|
50
|
+
sourceFactRefs: uniqueSorted([
|
|
51
|
+
...(existing?.sourceFactRefs ?? []),
|
|
52
|
+
...alternative.sourceFactRefs,
|
|
53
|
+
]),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const conditionAlternatives = [...alternativesByConditions.values()]
|
|
57
|
+
.sort((left, right) =>
|
|
58
|
+
left.conditionRefs
|
|
59
|
+
.join("\u0000")
|
|
60
|
+
.localeCompare(right.conditionRefs.join("\u0000"), "en"),
|
|
61
|
+
)
|
|
62
|
+
.map((alternative, alternativeIndex) => {
|
|
63
|
+
const conditions = alternative.conditionRefs.map((conditionRef) =>
|
|
64
|
+
textItem(
|
|
65
|
+
graph.caseId,
|
|
66
|
+
"target-branch-condition",
|
|
67
|
+
`${action.id}:${branchKey}:${alternativeIndex}:${conditionRef}`,
|
|
68
|
+
conditionText(conditionRef, source),
|
|
69
|
+
[conditionRef],
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
return {
|
|
73
|
+
conditions,
|
|
74
|
+
supportRefs: uniqueSorted([
|
|
75
|
+
...alternative.sourceFactRefs,
|
|
76
|
+
...conditions.flatMap((condition) => condition.supportRefs),
|
|
77
|
+
]),
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
const conditionsByText = new Map();
|
|
81
|
+
for (const alternative of conditionAlternatives) {
|
|
82
|
+
for (const condition of alternative.conditions) {
|
|
83
|
+
const existing = conditionsByText.get(condition.text);
|
|
84
|
+
conditionsByText.set(
|
|
85
|
+
condition.text,
|
|
86
|
+
existing
|
|
87
|
+
? {
|
|
88
|
+
...existing,
|
|
89
|
+
supportRefs: uniqueSorted([
|
|
90
|
+
...existing.supportRefs,
|
|
91
|
+
...condition.supportRefs,
|
|
92
|
+
]),
|
|
93
|
+
}
|
|
94
|
+
: condition,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
conditions: [...conditionsByText.values()].sort((left, right) =>
|
|
100
|
+
left.id.localeCompare(right.id, "en"),
|
|
101
|
+
),
|
|
102
|
+
conditionAlternatives,
|
|
103
|
+
supportRefs: uniqueSorted(
|
|
104
|
+
branchEdges.flatMap((edge) => edge.sourceFactRefs),
|
|
105
|
+
),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function resolvedReqTarget(action, target, supportRefs, graph) {
|
|
109
|
+
if (target.kind === "area") {
|
|
110
|
+
const area = graph.areas.find((item) => item.id === target.ref);
|
|
111
|
+
if (!area) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Req-model target invariant for Action ${action.id}: ` +
|
|
114
|
+
`missing target Area ${target.ref}.`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
kind: "area",
|
|
119
|
+
ref: area.id,
|
|
120
|
+
displayName: area.name,
|
|
121
|
+
supportRefs: uniqueSorted(supportRefs),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const endpoint = graph.externalEndpoints.find(
|
|
125
|
+
(item) => item.id === target.ref,
|
|
126
|
+
);
|
|
127
|
+
if (!endpoint) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Req-model target invariant for Action ${action.id}: ` +
|
|
130
|
+
`missing external endpoint ${target.ref}.`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
kind: "external",
|
|
135
|
+
ref: endpoint.id,
|
|
136
|
+
displayName: endpoint.name,
|
|
137
|
+
...(endpoint.presentation ? { presentation: endpoint.presentation } : {}),
|
|
138
|
+
...(endpoint.intentAction ? { intentAction: endpoint.intentAction } : {}),
|
|
139
|
+
...(endpoint.mimeType ? { mimeType: endpoint.mimeType } : {}),
|
|
140
|
+
...(endpoint.uri ? { uri: endpoint.uri } : {}),
|
|
141
|
+
supportRefs: uniqueSorted(supportRefs),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function textItem(caseId, kind, identity, text, supportRefs) {
|
|
145
|
+
return {
|
|
146
|
+
id: graphId("text", caseId, kind, identity),
|
|
147
|
+
text,
|
|
148
|
+
supportRefs: [...new Set(supportRefs)].sort(),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function conditionText(ref, source) {
|
|
152
|
+
const fact = source.facts.find(
|
|
153
|
+
(item) => item.id === ref && item.kind === "condition",
|
|
154
|
+
);
|
|
155
|
+
return fact?.kind === "condition"
|
|
156
|
+
? fact.data.plainLanguage
|
|
157
|
+
: `满足条件 ${ref}`;
|
|
158
|
+
}
|
|
159
|
+
function canonicalPath(area, graph) {
|
|
160
|
+
const path = area.canonicalPathRef
|
|
161
|
+
? graph.paths.find((item) => item.id === area.canonicalPathRef)
|
|
162
|
+
: undefined;
|
|
163
|
+
if (!path) {
|
|
164
|
+
const unresolvedRef = area.unresolvedRefs[0];
|
|
165
|
+
if (unresolvedRef) {
|
|
166
|
+
return { status: "unresolved", unresolvedRef };
|
|
167
|
+
}
|
|
168
|
+
throw new Error(`Area ${area.id} has no canonical path`);
|
|
169
|
+
}
|
|
170
|
+
if (path.kind === "unreachable") {
|
|
171
|
+
return {
|
|
172
|
+
status: "unreachable",
|
|
173
|
+
reachability: "unreachable",
|
|
174
|
+
pathRef: path.id,
|
|
175
|
+
alternatePathRefs: graph.paths
|
|
176
|
+
.filter(
|
|
177
|
+
(item) => item.kind === "alternate" && item.targetAreaRef === area.id,
|
|
178
|
+
)
|
|
179
|
+
.map((item) => item.id)
|
|
180
|
+
.sort(),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const entry = path.rootEntryRef
|
|
184
|
+
? graph.entryPoints.find((item) => item.id === path.rootEntryRef)
|
|
185
|
+
: undefined;
|
|
186
|
+
const rootRef =
|
|
187
|
+
path.steps[0]?.sourceAreaRef ??
|
|
188
|
+
(entry?.rootAreaRefs.includes(area.id) === true ? area.id : undefined) ??
|
|
189
|
+
entry?.rootAreaRefs[0] ??
|
|
190
|
+
area.id;
|
|
191
|
+
const root = graph.areas.find((item) => item.id === rootRef);
|
|
192
|
+
const segments = [
|
|
193
|
+
{
|
|
194
|
+
areaRef: rootRef,
|
|
195
|
+
areaName: root ? display(root.name, "Area 名称待确认") : rootRef,
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
for (const step of path.steps) {
|
|
199
|
+
const action = graph.actions.find((item) => item.id === step.actionRef);
|
|
200
|
+
const control = graph.controls.find((item) => item.id === step.controlRef);
|
|
201
|
+
const target = graph.areas.find((item) => item.id === step.targetAreaRef);
|
|
202
|
+
const sourceSegment = segments[segments.length - 1];
|
|
203
|
+
if (!sourceSegment || sourceSegment.areaRef !== step.sourceAreaRef) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`Canonical path ${path.id} is discontinuous at ${step.edgeRef}`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
segments[segments.length - 1] = {
|
|
209
|
+
...sourceSegment,
|
|
210
|
+
...(action ? { action: action.trigger } : {}),
|
|
211
|
+
...(control
|
|
212
|
+
? { controlName: display(control.name, "控件名称待确认") }
|
|
213
|
+
: {}),
|
|
214
|
+
...(target
|
|
215
|
+
? { targetAreaName: display(target.name, "Area 名称待确认") }
|
|
216
|
+
: {}),
|
|
217
|
+
};
|
|
218
|
+
segments.push({
|
|
219
|
+
areaRef: step.targetAreaRef,
|
|
220
|
+
areaName: target
|
|
221
|
+
? display(target.name, "Area 名称待确认")
|
|
222
|
+
: step.targetAreaRef,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
status: "resolved",
|
|
227
|
+
reachability:
|
|
228
|
+
path.reachability === "conditional" ? "conditional" : "unconditional",
|
|
229
|
+
pathRef: path.id,
|
|
230
|
+
segments,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function targetFor(action, control, actionEffects, graph, source) {
|
|
234
|
+
const actionEdges = graph.edges
|
|
235
|
+
.filter((item) => item.actionRef === action.id)
|
|
236
|
+
.sort((left, right) => left.id.localeCompare(right.id, "en"));
|
|
237
|
+
const closeEffects = actionEffects.filter(
|
|
238
|
+
(effect) => effect.kind === "close-surface",
|
|
239
|
+
);
|
|
240
|
+
const returnsToHistory =
|
|
241
|
+
(action.trigger === "back" &&
|
|
242
|
+
!closeEffects.some((effect) => effect.closePolicy === "host-exit")) ||
|
|
243
|
+
closeEffects.some(
|
|
244
|
+
(effect) =>
|
|
245
|
+
effect.kind === "close-surface" && effect.closePolicy !== "host-exit",
|
|
246
|
+
);
|
|
247
|
+
const hasExplicitTransition = actionEffects.some(
|
|
248
|
+
(effect) =>
|
|
249
|
+
effect.kind === "navigate-area" ||
|
|
250
|
+
effect.kind === "show-surface" ||
|
|
251
|
+
effect.kind === "open-external",
|
|
252
|
+
);
|
|
253
|
+
const historyTargets = actionEdges.flatMap((edge) =>
|
|
254
|
+
!("status" in edge.target) && edge.target.kind === "area"
|
|
255
|
+
? [edge.target.ref]
|
|
256
|
+
: [],
|
|
257
|
+
);
|
|
258
|
+
if (
|
|
259
|
+
returnsToHistory &&
|
|
260
|
+
!hasExplicitTransition &&
|
|
261
|
+
actionEdges.length > 0 &&
|
|
262
|
+
historyTargets.length === actionEdges.length
|
|
263
|
+
) {
|
|
264
|
+
const supportRefs = uniqueSorted([
|
|
265
|
+
...actionEdges.flatMap((edge) => edge.sourceFactRefs),
|
|
266
|
+
...action.sourceFactRefs,
|
|
267
|
+
...actionEffects
|
|
268
|
+
.filter((effect) => effect.kind === "close-surface")
|
|
269
|
+
.flatMap((effect) => effect.sourceFactRefs),
|
|
270
|
+
]);
|
|
271
|
+
return {
|
|
272
|
+
kind: "history-back",
|
|
273
|
+
refs: uniqueSorted(historyTargets),
|
|
274
|
+
displayName: resolved("返回进入当前区域前的区域", supportRefs, "medium"),
|
|
275
|
+
supportRefs,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
if (actionEdges.length === 0) {
|
|
279
|
+
if (!returnsToHistory) return undefined;
|
|
280
|
+
/*
|
|
281
|
+
* A close API alone does not prove an in-app history destination:
|
|
282
|
+
* dismissing an externally opened dialog or finishing the root Activity
|
|
283
|
+
* may leave the app. Resolved incoming Area edges are the proof that the
|
|
284
|
+
* current Area was entered from an in-app predecessor.
|
|
285
|
+
*/
|
|
286
|
+
const incoming = graph.edges
|
|
287
|
+
.filter(
|
|
288
|
+
(edge) =>
|
|
289
|
+
!("status" in edge.target) &&
|
|
290
|
+
edge.target.kind === "area" &&
|
|
291
|
+
edge.target.ref === control.areaRef &&
|
|
292
|
+
edge.sourceAreaRef !== control.areaRef,
|
|
293
|
+
)
|
|
294
|
+
.sort((left, right) => left.id.localeCompare(right.id, "en"));
|
|
295
|
+
const refs = uniqueSorted(incoming.map((edge) => edge.sourceAreaRef));
|
|
296
|
+
if (refs.length === 0) return undefined;
|
|
297
|
+
const supportRefs = uniqueSorted([
|
|
298
|
+
...incoming.flatMap((edge) => edge.sourceFactRefs),
|
|
299
|
+
...action.sourceFactRefs,
|
|
300
|
+
...actionEffects
|
|
301
|
+
.filter((effect) => effect.kind === "close-surface")
|
|
302
|
+
.flatMap((effect) => effect.sourceFactRefs),
|
|
303
|
+
]);
|
|
304
|
+
return {
|
|
305
|
+
kind: "history-back",
|
|
306
|
+
refs,
|
|
307
|
+
displayName: resolved("返回进入当前区域前的区域", supportRefs, "medium"),
|
|
308
|
+
supportRefs,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const unresolvedEdges = actionEdges.filter((edge) => "status" in edge.target);
|
|
312
|
+
if (unresolvedEdges.length > 0) {
|
|
313
|
+
const refs = uniqueSorted(
|
|
314
|
+
unresolvedEdges.map((edge) => {
|
|
315
|
+
if (!("status" in edge.target)) {
|
|
316
|
+
throw new Error("Expected an unresolved edge target");
|
|
317
|
+
}
|
|
318
|
+
return edge.target.unresolvedRef;
|
|
319
|
+
}),
|
|
320
|
+
);
|
|
321
|
+
if (unresolvedEdges.length === actionEdges.length && refs.length === 1) {
|
|
322
|
+
return { status: "unresolved", unresolvedRef: refs[0] };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const resolvedEdges = actionEdges.flatMap((edge) =>
|
|
326
|
+
"status" in edge.target ? [] : [{ edge, target: edge.target }],
|
|
327
|
+
);
|
|
328
|
+
const targets = new Map();
|
|
329
|
+
for (const { target } of resolvedEdges) {
|
|
330
|
+
targets.set(`${target.kind}:${target.ref}`, target);
|
|
331
|
+
}
|
|
332
|
+
const uniqueTargets = [...targets.values()].sort(
|
|
333
|
+
(left, right) =>
|
|
334
|
+
left.kind.localeCompare(right.kind, "en") ||
|
|
335
|
+
left.ref.localeCompare(right.ref, "en"),
|
|
336
|
+
);
|
|
337
|
+
const targetKinds = new Set(uniqueTargets.map((target) => target.kind));
|
|
338
|
+
const supportRefs = uniqueSorted(
|
|
339
|
+
actionEdges.flatMap((edge) => edge.sourceFactRefs),
|
|
340
|
+
);
|
|
341
|
+
if (unresolvedEdges.length === 0 && uniqueTargets.length === 1) {
|
|
342
|
+
const target = uniqueTargets[0];
|
|
343
|
+
return resolvedReqTarget(action, target, supportRefs, graph);
|
|
344
|
+
}
|
|
345
|
+
if (
|
|
346
|
+
unresolvedEdges.length === 0 &&
|
|
347
|
+
targetKinds.size === 1 &&
|
|
348
|
+
uniqueTargets[0]?.kind === "area"
|
|
349
|
+
) {
|
|
350
|
+
const refs = uniqueTargets.map((target) => target.ref);
|
|
351
|
+
const branches = refs.map((ref) => {
|
|
352
|
+
const branchEdges = resolvedEdges.filter(
|
|
353
|
+
({ target }) => target.kind === "area" && target.ref === ref,
|
|
354
|
+
);
|
|
355
|
+
return {
|
|
356
|
+
ref,
|
|
357
|
+
...targetBranchConditions(
|
|
358
|
+
action,
|
|
359
|
+
`area:${ref}`,
|
|
360
|
+
branchEdges.map(({ edge }) => edge),
|
|
361
|
+
graph,
|
|
362
|
+
source,
|
|
363
|
+
),
|
|
364
|
+
};
|
|
365
|
+
});
|
|
366
|
+
return {
|
|
367
|
+
kind: "conditional-area-set",
|
|
368
|
+
refs,
|
|
369
|
+
branches,
|
|
370
|
+
displayName: resolved("根据条件进入不同区域", supportRefs),
|
|
371
|
+
supportRefs,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
const edgesByTarget = new Map();
|
|
375
|
+
for (const edge of actionEdges) {
|
|
376
|
+
const key = targetKey(edge.target);
|
|
377
|
+
const grouped = edgesByTarget.get(key) ?? [];
|
|
378
|
+
grouped.push(edge);
|
|
379
|
+
edgesByTarget.set(key, grouped);
|
|
380
|
+
}
|
|
381
|
+
const branches = [...edgesByTarget.entries()]
|
|
382
|
+
.sort(([left], [right]) => left.localeCompare(right, "en"))
|
|
383
|
+
.map(([key, branchEdges]) => {
|
|
384
|
+
const edgeTarget = branchEdges[0]?.target;
|
|
385
|
+
if (!edgeTarget) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
`Req-model target invariant for Action ${action.id}: ` +
|
|
388
|
+
`target branch ${key} has no Edge.`,
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
const branchSupportRefs = uniqueSorted(
|
|
392
|
+
branchEdges.flatMap((edge) => edge.sourceFactRefs),
|
|
393
|
+
);
|
|
394
|
+
const target =
|
|
395
|
+
"status" in edgeTarget
|
|
396
|
+
? {
|
|
397
|
+
status: "unresolved",
|
|
398
|
+
unresolvedRef: edgeTarget.unresolvedRef,
|
|
399
|
+
}
|
|
400
|
+
: resolvedReqTarget(action, edgeTarget, branchSupportRefs, graph);
|
|
401
|
+
return {
|
|
402
|
+
target,
|
|
403
|
+
...targetBranchConditions(action, key, branchEdges, graph, source),
|
|
404
|
+
};
|
|
405
|
+
});
|
|
406
|
+
return {
|
|
407
|
+
kind: "alternative-target-set",
|
|
408
|
+
branches,
|
|
409
|
+
displayName: resolved("根据条件进入不同目标", supportRefs),
|
|
410
|
+
supportRefs,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function normalizedPropertyName(property) {
|
|
414
|
+
const leaf = property.trim().split(/[.:/]/u).at(-1) ?? property;
|
|
415
|
+
return leaf.replace(/[^a-z0-9]/giu, "").toLowerCase();
|
|
416
|
+
}
|
|
417
|
+
function jsonKey(value) {
|
|
418
|
+
if (Array.isArray(value)) {
|
|
419
|
+
return `[${value.map(jsonKey).join(",")}]`;
|
|
420
|
+
}
|
|
421
|
+
if (value !== null && typeof value === "object") {
|
|
422
|
+
return `{${Object.entries(value)
|
|
423
|
+
.sort(([left], [right]) => left.localeCompare(right, "en"))
|
|
424
|
+
.map(([key, child]) => `${JSON.stringify(key)}:${jsonKey(child)}`)
|
|
425
|
+
.join(",")}}`;
|
|
426
|
+
}
|
|
427
|
+
return JSON.stringify(value);
|
|
428
|
+
}
|
|
429
|
+
function weakestConfidence(facts) {
|
|
430
|
+
const rank = { high: 2, medium: 1, low: 0 };
|
|
431
|
+
return facts.reduce(
|
|
432
|
+
(weakest, fact) =>
|
|
433
|
+
rank[fact.confidence] < rank[weakest] ? fact.confidence : weakest,
|
|
434
|
+
"high",
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
function propertiesFor(control, source) {
|
|
438
|
+
const uiElementRefs = new Set(
|
|
439
|
+
control.sourceFactRefs.filter((ref) =>
|
|
440
|
+
source.facts.some(
|
|
441
|
+
(fact) => fact.id === ref && fact.kind === "ui-element",
|
|
442
|
+
),
|
|
443
|
+
),
|
|
444
|
+
);
|
|
445
|
+
if (uiElementRefs.size === 0) {
|
|
446
|
+
return { options: [], supportRefs: [] };
|
|
447
|
+
}
|
|
448
|
+
const properties = source.facts
|
|
449
|
+
.filter(
|
|
450
|
+
(fact) =>
|
|
451
|
+
fact.kind === "ui-property" && uiElementRefs.has(fact.data.ownerKey),
|
|
452
|
+
)
|
|
453
|
+
.sort((left, right) => left.id.localeCompare(right.id, "en"));
|
|
454
|
+
const enabledFacts = properties.filter(
|
|
455
|
+
(fact) =>
|
|
456
|
+
normalizedPropertyName(fact.data.property) === "enabled" &&
|
|
457
|
+
typeof fact.data.value === "boolean",
|
|
458
|
+
);
|
|
459
|
+
let enabled = control.enabled;
|
|
460
|
+
if (enabledFacts.length > 0) {
|
|
461
|
+
const values = new Set(enabledFacts.map((fact) => fact.data.value));
|
|
462
|
+
if (values.size > 1) {
|
|
463
|
+
throw new Error(
|
|
464
|
+
`Req-model property invariant for Control ${control.id}: ` +
|
|
465
|
+
"static enabled properties disagree.",
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
const sourceEnabled = resolved(
|
|
469
|
+
enabledFacts[0].data.value,
|
|
470
|
+
enabledFacts.map((fact) => fact.id),
|
|
471
|
+
weakestConfidence(enabledFacts),
|
|
472
|
+
);
|
|
473
|
+
if (
|
|
474
|
+
enabled?.status === "resolved" &&
|
|
475
|
+
sourceEnabled.status === "resolved" &&
|
|
476
|
+
enabled.value !== sourceEnabled.value
|
|
477
|
+
) {
|
|
478
|
+
throw new Error(
|
|
479
|
+
`Req-model property invariant for Control ${control.id}: ` +
|
|
480
|
+
"Area Graph and Source Facts enabled values disagree.",
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
enabled ??= sourceEnabled;
|
|
484
|
+
}
|
|
485
|
+
const defaultPriority = new Map([
|
|
486
|
+
["defaultvalue", 0],
|
|
487
|
+
["default", 0],
|
|
488
|
+
["value", 1],
|
|
489
|
+
["checked", 2],
|
|
490
|
+
]);
|
|
491
|
+
const defaultCandidates = properties
|
|
492
|
+
.map((fact) => ({
|
|
493
|
+
fact,
|
|
494
|
+
priority: defaultPriority.get(normalizedPropertyName(fact.data.property)),
|
|
495
|
+
}))
|
|
496
|
+
.filter((candidate) => candidate.priority !== undefined);
|
|
497
|
+
const bestPriority = defaultCandidates.reduce(
|
|
498
|
+
(best, candidate) => Math.min(best, candidate.priority),
|
|
499
|
+
Number.POSITIVE_INFINITY,
|
|
500
|
+
);
|
|
501
|
+
const selectedDefaults = defaultCandidates
|
|
502
|
+
.filter((candidate) => candidate.priority === bestPriority)
|
|
503
|
+
.map((candidate) => candidate.fact);
|
|
504
|
+
let defaultValue;
|
|
505
|
+
if (selectedDefaults.length > 0) {
|
|
506
|
+
const values = new Map(
|
|
507
|
+
selectedDefaults.map((fact) => [
|
|
508
|
+
jsonKey(fact.data.value),
|
|
509
|
+
fact.data.value,
|
|
510
|
+
]),
|
|
511
|
+
);
|
|
512
|
+
if (values.size > 1) {
|
|
513
|
+
throw new Error(
|
|
514
|
+
`Req-model property invariant for Control ${control.id}: ` +
|
|
515
|
+
"equally preferred default properties disagree.",
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
defaultValue = resolved(
|
|
519
|
+
values.values().next().value,
|
|
520
|
+
selectedDefaults.map((fact) => fact.id),
|
|
521
|
+
weakestConfidence(selectedDefaults),
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
const optionFacts = properties.filter(
|
|
525
|
+
(fact) =>
|
|
526
|
+
/^(options|choices|items)$/u.test(
|
|
527
|
+
normalizedPropertyName(fact.data.property),
|
|
528
|
+
) &&
|
|
529
|
+
Array.isArray(fact.data.value) &&
|
|
530
|
+
fact.data.value.every(
|
|
531
|
+
(value) =>
|
|
532
|
+
typeof value === "string" ||
|
|
533
|
+
typeof value === "number" ||
|
|
534
|
+
typeof value === "boolean",
|
|
535
|
+
),
|
|
536
|
+
);
|
|
537
|
+
const optionSupportByValue = new Map();
|
|
538
|
+
for (const fact of optionFacts) {
|
|
539
|
+
if (!Array.isArray(fact.data.value)) continue;
|
|
540
|
+
for (const item of fact.data.value) {
|
|
541
|
+
if (
|
|
542
|
+
typeof item !== "string" &&
|
|
543
|
+
typeof item !== "number" &&
|
|
544
|
+
typeof item !== "boolean"
|
|
545
|
+
)
|
|
546
|
+
continue;
|
|
547
|
+
const value = String(item);
|
|
548
|
+
if (value.trim().length === 0) continue;
|
|
549
|
+
optionSupportByValue.set(
|
|
550
|
+
value,
|
|
551
|
+
uniqueSorted([...(optionSupportByValue.get(value) ?? []), fact.id]),
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
const options = [...optionSupportByValue].map(([value, supportRefs]) => ({
|
|
556
|
+
value,
|
|
557
|
+
supportRefs,
|
|
558
|
+
}));
|
|
559
|
+
const supportRefs = uniqueSorted([
|
|
560
|
+
...(enabled?.status === "resolved" ? enabled.supportRefs : []),
|
|
561
|
+
...selectedDefaults.map((fact) => fact.id),
|
|
562
|
+
...optionFacts.flatMap((fact) =>
|
|
563
|
+
Array.isArray(fact.data.value) &&
|
|
564
|
+
fact.data.value.some(
|
|
565
|
+
(value) =>
|
|
566
|
+
(typeof value === "string" && value.trim().length > 0) ||
|
|
567
|
+
typeof value === "number" ||
|
|
568
|
+
typeof value === "boolean",
|
|
569
|
+
)
|
|
570
|
+
? [fact.id]
|
|
571
|
+
: [],
|
|
572
|
+
),
|
|
573
|
+
]);
|
|
574
|
+
return {
|
|
575
|
+
...(enabled ? { enabled } : {}),
|
|
576
|
+
...(defaultValue ? { defaultValue } : {}),
|
|
577
|
+
options,
|
|
578
|
+
supportRefs,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function supportedUnresolvedRefs(value) {
|
|
582
|
+
return value?.status === "unresolved" ? [value.unresolvedRef] : [];
|
|
583
|
+
}
|
|
584
|
+
function supportedSupportRefs(value) {
|
|
585
|
+
return value?.status === "resolved" ? [...value.supportRefs] : [];
|
|
586
|
+
}
|
|
587
|
+
function unresolvedRefsForReqTarget(target) {
|
|
588
|
+
if ("status" in target) return [target.unresolvedRef];
|
|
589
|
+
return uniqueSorted([
|
|
590
|
+
...supportedUnresolvedRefs(target.displayName),
|
|
591
|
+
...(target.kind === "external"
|
|
592
|
+
? [
|
|
593
|
+
...supportedUnresolvedRefs(target.intentAction),
|
|
594
|
+
...supportedUnresolvedRefs(target.mimeType),
|
|
595
|
+
...supportedUnresolvedRefs(target.uri),
|
|
596
|
+
]
|
|
597
|
+
: []),
|
|
598
|
+
...(target.kind === "alternative-target-set"
|
|
599
|
+
? target.branches.flatMap((branch) =>
|
|
600
|
+
unresolvedRefsForReqTarget(branch.target),
|
|
601
|
+
)
|
|
602
|
+
: []),
|
|
603
|
+
]);
|
|
604
|
+
}
|
|
605
|
+
function supportRefsForReqTarget(target) {
|
|
606
|
+
if ("status" in target) return [];
|
|
607
|
+
return uniqueSorted([
|
|
608
|
+
...target.supportRefs,
|
|
609
|
+
...supportedSupportRefs(target.displayName),
|
|
610
|
+
...(target.kind === "external"
|
|
611
|
+
? [
|
|
612
|
+
...supportedSupportRefs(target.intentAction),
|
|
613
|
+
...supportedSupportRefs(target.mimeType),
|
|
614
|
+
...supportedSupportRefs(target.uri),
|
|
615
|
+
]
|
|
616
|
+
: []),
|
|
617
|
+
...(target.kind === "conditional-area-set"
|
|
618
|
+
? (target.branches ?? []).flatMap((branch) => [
|
|
619
|
+
...branch.supportRefs,
|
|
620
|
+
...branch.conditions.flatMap((condition) => condition.supportRefs),
|
|
621
|
+
...branch.conditionAlternatives.flatMap(
|
|
622
|
+
(alternative) => alternative.supportRefs,
|
|
623
|
+
),
|
|
624
|
+
])
|
|
625
|
+
: []),
|
|
626
|
+
...(target.kind === "alternative-target-set"
|
|
627
|
+
? target.branches.flatMap((branch) => [
|
|
628
|
+
...branch.supportRefs,
|
|
629
|
+
...branch.conditions.flatMap((condition) => condition.supportRefs),
|
|
630
|
+
...branch.conditionAlternatives.flatMap(
|
|
631
|
+
(alternative) => alternative.supportRefs,
|
|
632
|
+
),
|
|
633
|
+
...supportRefsForReqTarget(branch.target),
|
|
634
|
+
])
|
|
635
|
+
: []),
|
|
636
|
+
]);
|
|
637
|
+
}
|
|
638
|
+
function interactionFor(control, actionId, graph, source) {
|
|
639
|
+
const action = graph.actions.find((item) => item.id === actionId);
|
|
640
|
+
if (!action) throw new Error(`Missing action ${actionId}`);
|
|
641
|
+
const actionEffects = action.effectRefs.map((effectRef) => {
|
|
642
|
+
const effect = graph.effects.find((item) => item.id === effectRef);
|
|
643
|
+
if (!effect) throw new Error(`Missing effect ${effectRef}`);
|
|
644
|
+
return effect;
|
|
645
|
+
});
|
|
646
|
+
const actionEdges = graph.edges.filter(
|
|
647
|
+
(edge) => edge.actionRef === action.id,
|
|
648
|
+
);
|
|
649
|
+
const distinctResolvedTargets = new Set(
|
|
650
|
+
actionEdges.flatMap((edge) =>
|
|
651
|
+
"status" in edge.target ? [] : [`${edge.target.kind}:${edge.target.ref}`],
|
|
652
|
+
),
|
|
653
|
+
);
|
|
654
|
+
const conditionRefs = uniqueSorted([
|
|
655
|
+
...action.preconditionRefs,
|
|
656
|
+
...actionEdges.flatMap((edge) => edge.preconditions),
|
|
657
|
+
]);
|
|
658
|
+
const baseConditions = conditionRefs.map((conditionRef) =>
|
|
659
|
+
textItem(
|
|
660
|
+
graph.caseId,
|
|
661
|
+
"feature-condition",
|
|
662
|
+
`${action.id}:${conditionRef}`,
|
|
663
|
+
conditionText(conditionRef, source),
|
|
664
|
+
[conditionRef],
|
|
665
|
+
),
|
|
666
|
+
);
|
|
667
|
+
const alternativeConditions = action.preconditionAlternatives ?? [];
|
|
668
|
+
const disjunctiveCondition =
|
|
669
|
+
alternativeConditions.length > 1 &&
|
|
670
|
+
alternativeConditions.every(
|
|
671
|
+
(alternative) => alternative.conditionRefs.length > 0,
|
|
672
|
+
)
|
|
673
|
+
? [
|
|
674
|
+
textItem(
|
|
675
|
+
graph.caseId,
|
|
676
|
+
"feature-condition-alternatives",
|
|
677
|
+
action.id,
|
|
678
|
+
`满足以下任一条件组合:${alternativeConditions
|
|
679
|
+
.map((alternative) =>
|
|
680
|
+
alternative.conditionRefs
|
|
681
|
+
.map((conditionRef) => conditionText(conditionRef, source))
|
|
682
|
+
.join(" 且 "),
|
|
683
|
+
)
|
|
684
|
+
.join(";")}`,
|
|
685
|
+
uniqueSorted(
|
|
686
|
+
alternativeConditions.flatMap((alternative) => [
|
|
687
|
+
...alternative.conditionRefs,
|
|
688
|
+
...alternative.sourceFactRefs,
|
|
689
|
+
]),
|
|
690
|
+
),
|
|
691
|
+
),
|
|
692
|
+
]
|
|
693
|
+
: [];
|
|
694
|
+
const branchConditions =
|
|
695
|
+
distinctResolvedTargets.size > 1
|
|
696
|
+
? actionEdges.flatMap((edge) => {
|
|
697
|
+
if ("status" in edge.target || edge.target.kind !== "area") {
|
|
698
|
+
return [];
|
|
699
|
+
}
|
|
700
|
+
const targetRef = edge.target.ref;
|
|
701
|
+
const targetArea = graph.areas.find((area) => area.id === targetRef);
|
|
702
|
+
const targetName = targetArea
|
|
703
|
+
? display(targetArea.name, targetRef)
|
|
704
|
+
: targetRef;
|
|
705
|
+
return edge.conditions.map((conditionRef) =>
|
|
706
|
+
textItem(
|
|
707
|
+
graph.caseId,
|
|
708
|
+
"feature-target-condition",
|
|
709
|
+
`${action.id}:${targetRef}:${edge.id}:${conditionRef}`,
|
|
710
|
+
`进入“${targetName}”时:${conditionText(conditionRef, source)}`,
|
|
711
|
+
[conditionRef],
|
|
712
|
+
),
|
|
713
|
+
);
|
|
714
|
+
})
|
|
715
|
+
: [];
|
|
716
|
+
branchConditions.sort(
|
|
717
|
+
(left, right) =>
|
|
718
|
+
left.text.localeCompare(right.text, "en") ||
|
|
719
|
+
left.id.localeCompare(right.id, "en"),
|
|
720
|
+
);
|
|
721
|
+
const conditions = [
|
|
722
|
+
...baseConditions,
|
|
723
|
+
...disjunctiveCondition,
|
|
724
|
+
...branchConditions,
|
|
725
|
+
];
|
|
726
|
+
const effects = actionEffects.map((effect) => {
|
|
727
|
+
const item = textItem(
|
|
728
|
+
graph.caseId,
|
|
729
|
+
"feature-effect",
|
|
730
|
+
effect.id,
|
|
731
|
+
effect.description.status === "resolved"
|
|
732
|
+
? effect.description.value
|
|
733
|
+
: "操作结果待确认",
|
|
734
|
+
effect.sourceFactRefs,
|
|
735
|
+
);
|
|
736
|
+
const alternatives = (effect.conditionAlternatives ?? []).map(
|
|
737
|
+
(alternative, alternativeIndex) => {
|
|
738
|
+
const alternativeConditions = alternative.conditionRefs.map(
|
|
739
|
+
(conditionRef) =>
|
|
740
|
+
textItem(
|
|
741
|
+
graph.caseId,
|
|
742
|
+
"effect-condition",
|
|
743
|
+
`${effect.id}:${alternativeIndex}:${conditionRef}`,
|
|
744
|
+
conditionText(conditionRef, source),
|
|
745
|
+
[conditionRef],
|
|
746
|
+
),
|
|
747
|
+
);
|
|
748
|
+
return {
|
|
749
|
+
conditions: alternativeConditions,
|
|
750
|
+
supportRefs: uniqueSorted([
|
|
751
|
+
...alternative.sourceFactRefs,
|
|
752
|
+
...alternativeConditions.flatMap(
|
|
753
|
+
(condition) => condition.supportRefs,
|
|
754
|
+
),
|
|
755
|
+
]),
|
|
756
|
+
};
|
|
757
|
+
},
|
|
758
|
+
);
|
|
759
|
+
return {
|
|
760
|
+
...item,
|
|
761
|
+
kind: effect.kind,
|
|
762
|
+
conditionAlternatives: alternatives,
|
|
763
|
+
...(effect.stateKey ? { stateKey: effect.stateKey } : {}),
|
|
764
|
+
...(effect.value ? { value: effect.value } : {}),
|
|
765
|
+
};
|
|
766
|
+
});
|
|
767
|
+
const target = targetFor(action, control, actionEffects, graph, source);
|
|
768
|
+
const targetUnresolvedRefs = target ? unresolvedRefsForReqTarget(target) : [];
|
|
769
|
+
const targetSupportRefs = target ? supportRefsForReqTarget(target) : [];
|
|
770
|
+
const actionEdgeSupportRefs = graph.edges
|
|
771
|
+
.filter((edge) => edge.actionRef === action.id)
|
|
772
|
+
.flatMap((edge) => edge.sourceFactRefs);
|
|
773
|
+
return {
|
|
774
|
+
id: graphId(
|
|
775
|
+
"interaction",
|
|
776
|
+
graph.caseId,
|
|
777
|
+
action.trigger,
|
|
778
|
+
`${control.id}:${action.id}`,
|
|
779
|
+
),
|
|
780
|
+
actionRef: action.id,
|
|
781
|
+
trigger: action.trigger,
|
|
782
|
+
conditions,
|
|
783
|
+
effects,
|
|
784
|
+
...(target ? { target } : {}),
|
|
785
|
+
unresolvedRefs: [
|
|
786
|
+
...new Set([
|
|
787
|
+
...actionEffects.flatMap((effect) => effect.unresolvedRefs ?? []),
|
|
788
|
+
...targetUnresolvedRefs,
|
|
789
|
+
]),
|
|
790
|
+
].sort(),
|
|
791
|
+
supportRefs: uniqueSorted([
|
|
792
|
+
...action.sourceFactRefs,
|
|
793
|
+
...actionEffects.flatMap((effect) => effect.sourceFactRefs),
|
|
794
|
+
...conditions.flatMap((condition) => condition.supportRefs),
|
|
795
|
+
...effects.flatMap((effect) => effect.supportRefs),
|
|
796
|
+
...actionEdgeSupportRefs,
|
|
797
|
+
...targetSupportRefs,
|
|
798
|
+
]),
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
function featureFor(control, graph, source) {
|
|
802
|
+
if (control.actionRefs.length === 0) {
|
|
803
|
+
throw new Error(
|
|
804
|
+
`Req-model feature invariant for Control ${control.id}: ` +
|
|
805
|
+
"a Feature requires at least one Action.",
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
const interactions = control.actionRefs.map((actionRef) =>
|
|
809
|
+
interactionFor(control, actionRef, graph, source),
|
|
810
|
+
);
|
|
811
|
+
const properties = propertiesFor(control, source);
|
|
812
|
+
const propertyUnresolvedRefs =
|
|
813
|
+
properties.defaultValue?.status === "unresolved"
|
|
814
|
+
? [properties.defaultValue.unresolvedRef]
|
|
815
|
+
: [];
|
|
816
|
+
const onlyInteraction =
|
|
817
|
+
interactions.length === 1 ? interactions[0] : undefined;
|
|
818
|
+
return {
|
|
819
|
+
id: graphId("feature", graph.caseId, "control", control.id),
|
|
820
|
+
controlRef: control.id,
|
|
821
|
+
displayName: control.name,
|
|
822
|
+
interactions,
|
|
823
|
+
/*
|
|
824
|
+
* These summary fields keep pre-interaction in-process consumers source
|
|
825
|
+
* compatible. Draft generation uses interactions exclusively. For a
|
|
826
|
+
* multi-action Control, the sentinel makes it impossible to mistake the
|
|
827
|
+
* first callback for the whole physical Control.
|
|
828
|
+
*/
|
|
829
|
+
trigger: onlyInteraction?.trigger ?? "multiple-interactions",
|
|
830
|
+
...(properties.enabled ? { enabled: properties.enabled } : {}),
|
|
831
|
+
...(properties.defaultValue
|
|
832
|
+
? { defaultValue: properties.defaultValue }
|
|
833
|
+
: {}),
|
|
834
|
+
options: properties.options,
|
|
835
|
+
conditions: interactions.flatMap((interaction) => interaction.conditions),
|
|
836
|
+
effects: interactions.flatMap((interaction) => interaction.effects),
|
|
837
|
+
...(onlyInteraction?.target ? { target: onlyInteraction.target } : {}),
|
|
838
|
+
unresolvedRefs: uniqueSorted([
|
|
839
|
+
...control.unresolvedRefs,
|
|
840
|
+
...(control.name.status === "unresolved"
|
|
841
|
+
? [control.name.unresolvedRef]
|
|
842
|
+
: []),
|
|
843
|
+
...propertyUnresolvedRefs,
|
|
844
|
+
...interactions.flatMap((interaction) => interaction.unresolvedRefs),
|
|
845
|
+
]),
|
|
846
|
+
supportRefs: uniqueSorted([
|
|
847
|
+
...control.sourceFactRefs,
|
|
848
|
+
...properties.supportRefs,
|
|
849
|
+
...interactions.flatMap((interaction) => interaction.supportRefs),
|
|
850
|
+
]),
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
function descriptionItems(area, caseId) {
|
|
854
|
+
if (area.descriptionFacts.length > 0) {
|
|
855
|
+
return area.descriptionFacts.map((fact) =>
|
|
856
|
+
textItem(
|
|
857
|
+
caseId,
|
|
858
|
+
"area-description",
|
|
859
|
+
fact.id,
|
|
860
|
+
fact.value.status === "resolved"
|
|
861
|
+
? String(fact.value.value)
|
|
862
|
+
: "区域说明待确认",
|
|
863
|
+
fact.sourceFactRefs,
|
|
864
|
+
),
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
return [
|
|
868
|
+
textItem(
|
|
869
|
+
caseId,
|
|
870
|
+
"area-description",
|
|
871
|
+
area.id,
|
|
872
|
+
`${display(area.name, "该区域")}是一个 ${area.kind} 类型的交互区域。`,
|
|
873
|
+
area.sourceFactRefs,
|
|
874
|
+
),
|
|
875
|
+
];
|
|
876
|
+
}
|
|
877
|
+
/** Disambiguates Areas that resolve to the exact same displayName (e.g. two
|
|
878
|
+
* unrelated composables/routes sharing one string resource) using their
|
|
879
|
+
* identityAnchors — deterministic, no LLM involved (ADR-0001). Runs against
|
|
880
|
+
* the graph's own Area[] and must run before canonicalPath()/featureFor()
|
|
881
|
+
* read Area.name, so every downstream consumer (path segments, navigation
|
|
882
|
+
* target descriptions, feature titles) sees the same disambiguated name from
|
|
883
|
+
* one source of truth. Winner selection within a collision group is
|
|
884
|
+
* intentionally asymmetric (whichever Area's own identityAnchors happen not
|
|
885
|
+
* to yield a differing candidate keeps the bare name) — not a principled
|
|
886
|
+
* choice, just the first-found one; not worth the complexity to fix. Areas
|
|
887
|
+
* a suffix can't be found for, or whose suffix still collides with a sibling
|
|
888
|
+
* after assignment, are marked unresolved (AMBIGUOUS_AREA_DISPLAY_NAME) and
|
|
889
|
+
* left for the Stage 2 render Agent via the existing
|
|
890
|
+
* hasUnresolvedPathSegmentNames/renderPathNameCompletionLaw machinery —
|
|
891
|
+
* same mechanism already used for any other unresolved Area name. */
|
|
892
|
+
function disambiguateAreaDisplayNames(areas, caseId) {
|
|
893
|
+
const byName = new Map();
|
|
894
|
+
for (let i = 0; i < areas.length; i++) {
|
|
895
|
+
const area = areas[i];
|
|
896
|
+
if (area.name.status !== "resolved") continue;
|
|
897
|
+
const indices = byName.get(area.name.value) ?? [];
|
|
898
|
+
indices.push(i);
|
|
899
|
+
byName.set(area.name.value, indices);
|
|
900
|
+
}
|
|
901
|
+
const collisionGroups = [...byName.values()].filter(
|
|
902
|
+
(indices) => indices.length > 1,
|
|
903
|
+
);
|
|
904
|
+
if (collisionGroups.length === 0) return { areas, newUnresolved: [] };
|
|
905
|
+
const groupNameByIndex = new Map();
|
|
906
|
+
// Only set for an area whose candidate final string (bare groupName if no
|
|
907
|
+
// distinguishing anchor was found, or `groupName (suffix)` otherwise) is
|
|
908
|
+
// unique within its collision group — an area with no distinguishing
|
|
909
|
+
// anchor legitimately keeps the bare name (the asymmetric "winner") as
|
|
910
|
+
// long as no sibling also ends up bare; a collision on the *candidate*
|
|
911
|
+
// final (whether two bare members or two identical suffixes) demotes every
|
|
912
|
+
// colliding member to unresolved instead of silently keeping duplicates
|
|
913
|
+
// under a "resolved" status.
|
|
914
|
+
const finalNameByIndex = new Map();
|
|
915
|
+
for (const indices of collisionGroups) {
|
|
916
|
+
const groupName = display(areas[indices[0]].name, "");
|
|
917
|
+
const suffixByIndex = new Map();
|
|
918
|
+
for (const idx of indices) {
|
|
919
|
+
groupNameByIndex.set(idx, groupName);
|
|
920
|
+
for (const anchor of areas[idx].identityAnchors) {
|
|
921
|
+
const candidate = componentIdentifierName(anchor);
|
|
922
|
+
if (candidate === undefined || candidate.trim().length === 0) continue;
|
|
923
|
+
if (candidate === groupName) continue;
|
|
924
|
+
suffixByIndex.set(idx, candidate);
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
const candidateFinalByIndex = new Map();
|
|
929
|
+
for (const idx of indices) {
|
|
930
|
+
const suffix = suffixByIndex.get(idx);
|
|
931
|
+
candidateFinalByIndex.set(
|
|
932
|
+
idx,
|
|
933
|
+
suffix === undefined ? groupName : `${groupName} (${suffix})`,
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
const finalCounts = new Map();
|
|
937
|
+
for (const value of candidateFinalByIndex.values()) {
|
|
938
|
+
finalCounts.set(value, (finalCounts.get(value) ?? 0) + 1);
|
|
939
|
+
}
|
|
940
|
+
for (const idx of indices) {
|
|
941
|
+
const value = candidateFinalByIndex.get(idx);
|
|
942
|
+
if ((finalCounts.get(value) ?? 0) === 1) finalNameByIndex.set(idx, value);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
const newUnresolved = [];
|
|
946
|
+
const disambiguated = areas.map((area, i) => {
|
|
947
|
+
const groupName = groupNameByIndex.get(i);
|
|
948
|
+
if (groupName === undefined) return area;
|
|
949
|
+
const finalName = finalNameByIndex.get(i);
|
|
950
|
+
const original = area.name;
|
|
951
|
+
if (finalName !== undefined) {
|
|
952
|
+
return {
|
|
953
|
+
...area,
|
|
954
|
+
name: resolved(finalName, original.supportRefs, original.confidence),
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
const unresolvedId = graphId(
|
|
958
|
+
"unresolved",
|
|
959
|
+
caseId,
|
|
960
|
+
"area-display-name",
|
|
961
|
+
area.id,
|
|
962
|
+
);
|
|
963
|
+
newUnresolved.push({
|
|
964
|
+
id: unresolvedId,
|
|
965
|
+
ownerRef: area.id,
|
|
966
|
+
fieldPath: "displayName",
|
|
967
|
+
reasonCode: "AMBIGUOUS_AREA_DISPLAY_NAME",
|
|
968
|
+
message:
|
|
969
|
+
`区域显示名称"${groupName}"与其他区域重复,` +
|
|
970
|
+
"且 identityAnchors 中无可用区分后缀(或候选后缀仍与同组内其他区域冲突)。",
|
|
971
|
+
evidenceRefs: area.identityAnchors,
|
|
972
|
+
candidates: [],
|
|
973
|
+
impact: ["area-display-name"],
|
|
974
|
+
});
|
|
975
|
+
return {
|
|
976
|
+
...area,
|
|
977
|
+
name: { status: "unresolved", unresolvedRef: unresolvedId },
|
|
978
|
+
unresolvedRefs: [...new Set([...area.unresolvedRefs, unresolvedId])],
|
|
979
|
+
};
|
|
980
|
+
});
|
|
981
|
+
return { areas: disambiguated, newUnresolved };
|
|
982
|
+
}
|
|
983
|
+
export function buildReqModel(graph, source) {
|
|
984
|
+
if (
|
|
985
|
+
graph.caseId !== source.caseId ||
|
|
986
|
+
JSON.stringify(graph.analysisContext) !==
|
|
987
|
+
JSON.stringify(source.analysisContext)
|
|
988
|
+
) {
|
|
989
|
+
throw new Error("Area graph and source facts use different contexts");
|
|
990
|
+
}
|
|
991
|
+
// Disambiguate collided Area names before anything below reads Area.name,
|
|
992
|
+
// so canonicalPath()/featureFor() bake the disambiguated (or unresolved)
|
|
993
|
+
// name into every downstream string — see disambiguateAreaDisplayNames.
|
|
994
|
+
const {
|
|
995
|
+
areas: disambiguatedGraphAreas,
|
|
996
|
+
newUnresolved: displayNameUnresolved,
|
|
997
|
+
} = disambiguateAreaDisplayNames(graph.areas, graph.caseId);
|
|
998
|
+
const workingGraph = {
|
|
999
|
+
...graph,
|
|
1000
|
+
areas: disambiguatedGraphAreas,
|
|
1001
|
+
unresolved: [...graph.unresolved, ...displayNameUnresolved],
|
|
1002
|
+
};
|
|
1003
|
+
const areas = workingGraph.areas.map((area) => {
|
|
1004
|
+
const path = area.canonicalPathRef
|
|
1005
|
+
? workingGraph.paths.find((item) => item.id === area.canonicalPathRef)
|
|
1006
|
+
: undefined;
|
|
1007
|
+
const controls = area.controlRefs.map((controlRef) => {
|
|
1008
|
+
const control = workingGraph.controls.find(
|
|
1009
|
+
(item) => item.id === controlRef,
|
|
1010
|
+
);
|
|
1011
|
+
if (!control) throw new Error(`Missing control ${controlRef}`);
|
|
1012
|
+
return control;
|
|
1013
|
+
});
|
|
1014
|
+
return {
|
|
1015
|
+
id: graphId("req-area", workingGraph.caseId, area.kind, area.id),
|
|
1016
|
+
areaRef: area.id,
|
|
1017
|
+
displayName: area.name,
|
|
1018
|
+
canonicalPath: canonicalPath(area, workingGraph),
|
|
1019
|
+
preconditions: [...new Set([...(path?.preconditions ?? [])])]
|
|
1020
|
+
.sort()
|
|
1021
|
+
.map((conditionRef) =>
|
|
1022
|
+
textItem(
|
|
1023
|
+
workingGraph.caseId,
|
|
1024
|
+
"area-precondition",
|
|
1025
|
+
`${area.id}:${conditionRef}`,
|
|
1026
|
+
conditionText(conditionRef, source),
|
|
1027
|
+
[conditionRef],
|
|
1028
|
+
),
|
|
1029
|
+
),
|
|
1030
|
+
descriptionItems: descriptionItems(area, workingGraph.caseId),
|
|
1031
|
+
features: controls.flatMap((control) =>
|
|
1032
|
+
control.actionRefs.length === 0
|
|
1033
|
+
? []
|
|
1034
|
+
: [featureFor(control, workingGraph, source)],
|
|
1035
|
+
),
|
|
1036
|
+
unresolvedRefs: [
|
|
1037
|
+
...new Set([
|
|
1038
|
+
...area.unresolvedRefs,
|
|
1039
|
+
...controls.flatMap((control) => control.unresolvedRefs),
|
|
1040
|
+
]),
|
|
1041
|
+
].sort(),
|
|
1042
|
+
supportRefs: [
|
|
1043
|
+
...new Set([
|
|
1044
|
+
...area.sourceFactRefs,
|
|
1045
|
+
...controls.flatMap((control) => control.sourceFactRefs),
|
|
1046
|
+
]),
|
|
1047
|
+
].sort(),
|
|
1048
|
+
};
|
|
1049
|
+
});
|
|
1050
|
+
return {
|
|
1051
|
+
schemaVersion: "1.0.0",
|
|
1052
|
+
artifactKind: "req-model",
|
|
1053
|
+
caseId: workingGraph.caseId,
|
|
1054
|
+
analysisContext: workingGraph.analysisContext,
|
|
1055
|
+
...(workingGraph.semanticCompleteness
|
|
1056
|
+
? { semanticCompleteness: workingGraph.semanticCompleteness }
|
|
1057
|
+
: source.semanticCompleteness
|
|
1058
|
+
? { semanticCompleteness: source.semanticCompleteness }
|
|
1059
|
+
: {}),
|
|
1060
|
+
...((workingGraph.limitations ?? source.limitations)
|
|
1061
|
+
? { limitations: workingGraph.limitations ?? source.limitations }
|
|
1062
|
+
: {}),
|
|
1063
|
+
areas,
|
|
1064
|
+
unresolved: workingGraph.unresolved,
|
|
1065
|
+
};
|
|
1066
|
+
}
|