android2harmony 0.1.7 → 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 +2 -2
- 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,555 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Standalone self-check/finalize script for stage2 subagent-mode rendering
|
|
3
|
+
// (ADR-0005: docs/ADR/ADR-0005-subagent-launch-mode-for-stage2.md).
|
|
4
|
+
//
|
|
5
|
+
// Deliberately independent of this repository's TypeScript build: a subagent
|
|
6
|
+
// rendering a page invokes this with plain `node`, with its cwd inside the
|
|
7
|
+
// *target* Android project, not this repo. Zero npm dependencies, Node
|
|
8
|
+
// built-ins only. Bundled inside the skill (not the parent TS project's
|
|
9
|
+
// scripts/) so it travels with the skill wherever it's installed.
|
|
10
|
+
//
|
|
11
|
+
// This file re-implements (does not import) the validation rules in
|
|
12
|
+
// src/agent-render/validate.ts and the stripLeadingTitle helper in
|
|
13
|
+
// src/agent-render/merge.ts. Keep the two in sync by hand when either
|
|
14
|
+
// changes — see ADR-0005 follow-ups.
|
|
15
|
+
//
|
|
16
|
+
// Usage:
|
|
17
|
+
// node reqgen-selfcheck.mjs check <outputPath> <expectedPath> <projectRoot>
|
|
18
|
+
// node reqgen-selfcheck.mjs finalize <outputPath> <expectedPath> <projectRoot>
|
|
19
|
+
//
|
|
20
|
+
// There is one file, `outputPath`, throughout: the subagent's first write is
|
|
21
|
+
// its own raw, not-yet-validated judgment; `finalize` reads that same path
|
|
22
|
+
// and overwrites it in place with either a validated success (`finalized:
|
|
23
|
+
// true`) or a diagnostic validation failure (`finalized: false`) — there is
|
|
24
|
+
// no separate draft file and nothing gets copied between two files.
|
|
25
|
+
//
|
|
26
|
+
// Exit codes:
|
|
27
|
+
// 0 - the script itself ran to completion. For `check`, this is true
|
|
28
|
+
// whether or not the draft passed (see the printed report's `ok`
|
|
29
|
+
// field) — a failed check is a normal, expected outcome, not a script
|
|
30
|
+
// error. For `finalize`, 0 means outputPath was overwritten in place;
|
|
31
|
+
// inspect the printed `ok` field to learn whether it passed validation.
|
|
32
|
+
// 2 - the script could not run at all (bad arguments, unreadable/unparsable
|
|
33
|
+
// outputPath or expectedPath, missing projectRoot) — this is a data/
|
|
34
|
+
// environment problem the caller must fix before retrying, not a
|
|
35
|
+
// "content failed validation" result.
|
|
36
|
+
|
|
37
|
+
import { readFile, writeFile, stat } from "node:fs/promises";
|
|
38
|
+
import path from "node:path";
|
|
39
|
+
|
|
40
|
+
function fail(message) {
|
|
41
|
+
process.stderr.write(`${message}\n`);
|
|
42
|
+
process.exit(2);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function readJson(filePath, label) {
|
|
46
|
+
let text;
|
|
47
|
+
try {
|
|
48
|
+
text = await readFile(filePath, "utf8");
|
|
49
|
+
} catch (error) {
|
|
50
|
+
fail(`Cannot read ${label} at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(text);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
fail(`${label} at ${filePath} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function requireDirectory(dirPath, label) {
|
|
60
|
+
let info;
|
|
61
|
+
try {
|
|
62
|
+
info = await stat(dirPath);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
fail(`Cannot stat ${label} at ${dirPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
65
|
+
}
|
|
66
|
+
if (!info.isDirectory()) fail(`${label} at ${dirPath} is not a directory`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Ported from src/agent-render/validate.ts (verbatim logic, plain JS).
|
|
71
|
+
// Keep the two in sync by hand — see ADR-0005 follow-ups.
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
async function checkCitation(citation, resolvedRoot) {
|
|
75
|
+
const resolvedFile = path.isAbsolute(citation.file)
|
|
76
|
+
? path.resolve(citation.file)
|
|
77
|
+
: path.resolve(resolvedRoot, citation.file);
|
|
78
|
+
if (
|
|
79
|
+
resolvedFile !== resolvedRoot &&
|
|
80
|
+
!resolvedFile.startsWith(resolvedRoot + path.sep)
|
|
81
|
+
) {
|
|
82
|
+
return { citation, reason: "outside-project-root" };
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
await stat(resolvedFile);
|
|
86
|
+
} catch {
|
|
87
|
+
return { citation, reason: "file-not-found" };
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function validateCitations(citations, projectRoot) {
|
|
93
|
+
const resolvedRoot = path.resolve(projectRoot);
|
|
94
|
+
const invalid = [];
|
|
95
|
+
for (const citation of citations) {
|
|
96
|
+
const problem = await checkCitation(citation, resolvedRoot);
|
|
97
|
+
if (problem !== undefined) invalid.push(problem);
|
|
98
|
+
}
|
|
99
|
+
return { valid: invalid.length === 0, invalid };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Deep equality that handles arrays ([] !== [] in JS).
|
|
103
|
+
function deepEqual(a, b) {
|
|
104
|
+
if (a === b) return true;
|
|
105
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
106
|
+
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
107
|
+
}
|
|
108
|
+
if (typeof a === "object" && typeof b === "object" && a !== null && b !== null) {
|
|
109
|
+
const ka = Object.keys(a), kb = Object.keys(b);
|
|
110
|
+
if (ka.length !== kb.length) return false;
|
|
111
|
+
return ka.every((k) => deepEqual(a[k], b[k]));
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// expectedRestatedFacts: { default: {status:"resolved", value} | {status:"unresolved"}, options: string[] }
|
|
117
|
+
function validateRestatedFacts(restatedFacts, expectedRestatedFacts) {
|
|
118
|
+
const mismatches = [];
|
|
119
|
+
const rf = restatedFacts ?? {};
|
|
120
|
+
const expectedDefault = expectedRestatedFacts.default;
|
|
121
|
+
if (expectedDefault !== undefined && expectedDefault.status === "resolved") {
|
|
122
|
+
if (!deepEqual(rf.default, expectedDefault.value)) mismatches.push("default");
|
|
123
|
+
}
|
|
124
|
+
const expectedOptions = new Set(expectedRestatedFacts.options ?? []);
|
|
125
|
+
const actualOptions = new Set(rf.options ?? []);
|
|
126
|
+
const optionsMatch = expectedOptions.size === actualOptions.size &&
|
|
127
|
+
[...expectedOptions].every((label) => actualOptions.has(label));
|
|
128
|
+
if (!optionsMatch) mismatches.push("options");
|
|
129
|
+
return { valid: mismatches.length === 0, mismatches };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// expectedTitle: { areaName, featureName, areaResolved, featureResolved, areaTranslatable, featureTranslatable, template }
|
|
133
|
+
// Only checks that UI-literal names survive and placeholders are replaced.
|
|
134
|
+
// Quote consistency is NOT checked here — it's a prompt-level concern.
|
|
135
|
+
function validateTitle(agentTitle, expectedTitle) {
|
|
136
|
+
const problems = [];
|
|
137
|
+
// Still-pending: placeholder must be replaced
|
|
138
|
+
if (!expectedTitle.areaResolved && agentTitle.includes("Area 名称待确认")) {
|
|
139
|
+
problems.push("area-segment-still-pending");
|
|
140
|
+
}
|
|
141
|
+
if (!expectedTitle.featureResolved && agentTitle.includes("控件名称待确认")) {
|
|
142
|
+
problems.push("feature-segment-still-pending");
|
|
143
|
+
}
|
|
144
|
+
// Resolved UI-literal name must appear verbatim
|
|
145
|
+
if (
|
|
146
|
+
expectedTitle.areaResolved && !expectedTitle.areaTranslatable &&
|
|
147
|
+
!agentTitle.includes(expectedTitle.areaName)
|
|
148
|
+
) {
|
|
149
|
+
problems.push("resolved-area-segment-rewritten");
|
|
150
|
+
}
|
|
151
|
+
if (
|
|
152
|
+
expectedTitle.featureResolved && !expectedTitle.featureTranslatable &&
|
|
153
|
+
!agentTitle.includes(expectedTitle.featureName)
|
|
154
|
+
) {
|
|
155
|
+
problems.push("resolved-feature-segment-rewritten");
|
|
156
|
+
}
|
|
157
|
+
return { valid: problems.length === 0, problems };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Ported from src/agent-render/merge.ts.
|
|
161
|
+
function stripLeadingTitle(body, title) {
|
|
162
|
+
const trimmed = body.replace(/^\s+/, "");
|
|
163
|
+
if (!trimmed.startsWith(title)) return body;
|
|
164
|
+
return trimmed.slice(title.length).replace(/^\s*\n+/, "");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Per-entry evaluation shared by `check` and `finalize`.
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
// Check that body has properly formatted enumeration lines when options is non-empty.
|
|
172
|
+
// Format: 4 spaces + "N,选项名:说明" — one line per option, count must match.
|
|
173
|
+
// enum-missing is blocking; enum-count-mismatch is a non-blocking warning.
|
|
174
|
+
function validateOptionEnumeration(body, options) {
|
|
175
|
+
if (!options || options.length === 0) return { valid: true, problems: [], warnings: [] };
|
|
176
|
+
if (typeof body !== "string") return { valid: false, problems: ["body-missing"], warnings: [] };
|
|
177
|
+
const lines = body.split("\n");
|
|
178
|
+
const enumLines = lines.filter((l) => /^ \d+,/.test(l));
|
|
179
|
+
const problems = [];
|
|
180
|
+
const warnings = [];
|
|
181
|
+
if (enumLines.length === 0) {
|
|
182
|
+
problems.push(
|
|
183
|
+
"enum-missing: options 非空但 body 中没有枚举行。" +
|
|
184
|
+
"正确格式:正文后紧跟枚举行,每行 4 个空格缩进 + 数字序号逗号 + 选项名 + 全角冒号 + 说明。" +
|
|
185
|
+
"例:\n" +
|
|
186
|
+
" 1,米:采用公制单位\n" +
|
|
187
|
+
" 2,英里:采用英制单位"
|
|
188
|
+
);
|
|
189
|
+
} else if (enumLines.length !== options.length) {
|
|
190
|
+
warnings.push(
|
|
191
|
+
`enum-count-mismatch: options 有 ${options.length} 项,枚举行有 ${enumLines.length} 行。` +
|
|
192
|
+
"每项 option 都需要单独一行枚举行(4 空格 + N,选项名:说明),不能漏写或多写。"
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
return { valid: problems.length === 0, problems, warnings };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function evaluateEntry(entry, expected, projectRoot) {
|
|
199
|
+
const citationsResult = await validateCitations(entry?.citations ?? [], projectRoot);
|
|
200
|
+
const restatedResult = validateRestatedFacts(entry?.restatedFacts ?? {}, expected.restatedFacts);
|
|
201
|
+
const titleResult = entry?.title === undefined
|
|
202
|
+
? { valid: false, problems: ["title-missing"] }
|
|
203
|
+
: validateTitle(entry.title, expected.title);
|
|
204
|
+
const optionEnumResult = validateOptionEnumeration(
|
|
205
|
+
entry?.body, expected.restatedFacts.options
|
|
206
|
+
);
|
|
207
|
+
const warnings = [
|
|
208
|
+
...citationsResult.invalid.map((inv) => ({ source: "citation", ...inv })),
|
|
209
|
+
...restatedResult.mismatches.map((field) => ({ source: "restatedFacts", field })),
|
|
210
|
+
...titleResult.problems.map((reason) => ({ source: "title", reason })),
|
|
211
|
+
...optionEnumResult.problems.map((reason) => ({ source: "optionEnum", reason })),
|
|
212
|
+
...optionEnumResult.warnings.map((reason) => ({ source: "optionEnum", reason }))
|
|
213
|
+
];
|
|
214
|
+
return { citationsResult, restatedResult, titleResult, optionEnumResult, warnings };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function problemsForEntry(requirementId, evaluation) {
|
|
218
|
+
const problems = [];
|
|
219
|
+
for (const inv of evaluation.citationsResult.invalid) {
|
|
220
|
+
problems.push({
|
|
221
|
+
requirementId,
|
|
222
|
+
check: "citation",
|
|
223
|
+
reason: inv.reason,
|
|
224
|
+
file: inv.citation.file,
|
|
225
|
+
startLine: inv.citation.startLine,
|
|
226
|
+
endLine: inv.citation.endLine
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
for (const field of evaluation.restatedResult.mismatches) {
|
|
230
|
+
problems.push({ requirementId, check: "restatedFacts", field });
|
|
231
|
+
}
|
|
232
|
+
for (const reason of evaluation.titleResult.problems) {
|
|
233
|
+
problems.push({ requirementId, check: "title", reason });
|
|
234
|
+
}
|
|
235
|
+
for (const reason of evaluation.optionEnumResult?.problems ?? []) {
|
|
236
|
+
problems.push({ requirementId, check: "optionEnum", reason });
|
|
237
|
+
}
|
|
238
|
+
return problems;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function finalizeEntryFields(entry, expected, evaluation, stripTitleOverride) {
|
|
242
|
+
const stripTitle = stripTitleOverride ?? expected.title.template;
|
|
243
|
+
return {
|
|
244
|
+
status: "ok",
|
|
245
|
+
title: entry?.title,
|
|
246
|
+
titleValid: evaluation.titleResult.valid,
|
|
247
|
+
body: entry?.body === undefined
|
|
248
|
+
? undefined
|
|
249
|
+
: stripLeadingTitle(entry.body, stripTitle),
|
|
250
|
+
restatedFacts: entry?.restatedFacts,
|
|
251
|
+
citations: entry?.citations,
|
|
252
|
+
confidence: entry?.confidence,
|
|
253
|
+
citationsValid: evaluation.citationsResult.valid,
|
|
254
|
+
invalidCitations: evaluation.citationsResult.invalid,
|
|
255
|
+
restatedFactsValid: evaluation.restatedResult.valid,
|
|
256
|
+
restatedFactsMismatches: evaluation.restatedResult.mismatches,
|
|
257
|
+
optionEnumValid: evaluation.optionEnumResult.valid,
|
|
258
|
+
optionEnumProblems: evaluation.optionEnumResult.problems,
|
|
259
|
+
warnings: evaluation.warnings
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
// Area-mode grouping: mirrors validateAreaOutput in orchestrate.ts — group by
|
|
265
|
+
// every requirementId an entry covers (not a plain Map) so a repeated id is
|
|
266
|
+
// reported as a collision instead of silently overwritten. An entry covers
|
|
267
|
+
// just its own `requirementId` normally, or every id listed in
|
|
268
|
+
// `mergedRequirementIds` (including its own) when the subagent merged
|
|
269
|
+
// several Controls into one req item (see prompt.ts's "合并规则").
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
function coveredRequirementIds(entry) {
|
|
273
|
+
return Array.isArray(entry.mergedRequirementIds) && entry.mergedRequirementIds.length > 0
|
|
274
|
+
? entry.mergedRequirementIds
|
|
275
|
+
: [entry.requirementId ?? ""];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function groupByRequirementId(entries) {
|
|
279
|
+
const byRequirementId = new Map();
|
|
280
|
+
for (const entry of entries ?? []) {
|
|
281
|
+
for (const key of coveredRequirementIds(entry)) {
|
|
282
|
+
const group = byRequirementId.get(key);
|
|
283
|
+
if (group === undefined) byRequirementId.set(key, [entry]);
|
|
284
|
+
else group.push(entry);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return byRequirementId;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Mirrors validateTitleForMembers in validate.ts: the one shared title of a
|
|
291
|
+
// merged req item must satisfy every covered member's own name-inclusion
|
|
292
|
+
// requirement. Degenerates to validateTitle's result for a single-member list.
|
|
293
|
+
function validateTitleForMembers(agentTitle, expectedTitleList) {
|
|
294
|
+
const problems = new Set();
|
|
295
|
+
for (const expectedTitle of expectedTitleList) {
|
|
296
|
+
for (const problem of validateTitle(agentTitle, expectedTitle).problems) problems.add(problem);
|
|
297
|
+
}
|
|
298
|
+
return { valid: problems.size === 0, problems: [...problems] };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Mirrors validateFeatureOutput's merged branch in orchestrate.ts:
|
|
302
|
+
// restatedFacts checked against `memberExps[0]` (the earliest member in
|
|
303
|
+
// Area order), title checked against every member's own parts, and the
|
|
304
|
+
// leading-title-line strip target is the entry's own title (there is no
|
|
305
|
+
// single per-member template that represents the whole group).
|
|
306
|
+
async function evaluateMergedEntry(entry, memberExps, projectRoot) {
|
|
307
|
+
const citationsResult = await validateCitations(entry?.citations ?? [], projectRoot);
|
|
308
|
+
const restatedResult = validateRestatedFacts(entry?.restatedFacts ?? {}, memberExps[0].restatedFacts);
|
|
309
|
+
const titleResult = entry?.title === undefined
|
|
310
|
+
? { valid: false, problems: ["title-missing"] }
|
|
311
|
+
: validateTitleForMembers(entry.title, memberExps.map((exp) => exp.title));
|
|
312
|
+
const optionEnumResult = validateOptionEnumeration(
|
|
313
|
+
entry?.body, memberExps[0].restatedFacts.options
|
|
314
|
+
);
|
|
315
|
+
const warnings = [
|
|
316
|
+
...citationsResult.invalid.map((inv) => ({ source: "citation", ...inv })),
|
|
317
|
+
...restatedResult.mismatches.map((field) => ({ source: "restatedFacts", field })),
|
|
318
|
+
...titleResult.problems.map((reason) => ({ source: "title", reason })),
|
|
319
|
+
...optionEnumResult.problems.map((reason) => ({ source: "optionEnum", reason })),
|
|
320
|
+
...optionEnumResult.warnings.map((reason) => ({ source: "optionEnum", reason }))
|
|
321
|
+
];
|
|
322
|
+
return { citationsResult, restatedResult, titleResult, optionEnumResult, warnings };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function isAgentAddedFeature(entry) {
|
|
326
|
+
return entry?.kind === "agent-added";
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function evaluateAgentAddedFeature(entry, projectRoot) {
|
|
330
|
+
const citations = entry?.citations ?? [];
|
|
331
|
+
const citationsResult = await validateCitations(citations, projectRoot);
|
|
332
|
+
const title = typeof entry?.title === "string" ? entry.title.trim() : "";
|
|
333
|
+
const body = typeof entry?.body === "string" ? entry.body.trim() : "";
|
|
334
|
+
const reason = title.length === 0
|
|
335
|
+
? "agent-added-feature-title-missing"
|
|
336
|
+
: body.length === 0
|
|
337
|
+
? "agent-added-feature-body-missing"
|
|
338
|
+
: citations.length === 0
|
|
339
|
+
? "agent-added-feature-citations-missing"
|
|
340
|
+
: citationsResult.valid ? undefined : "agent-added-feature-citations-invalid";
|
|
341
|
+
return { entry, citationsResult, reason };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function agentAddedProblems(evaluation) {
|
|
345
|
+
const problems = [];
|
|
346
|
+
if (evaluation.reason !== undefined) {
|
|
347
|
+
problems.push({ check: "agent-added-feature", reason: evaluation.reason });
|
|
348
|
+
}
|
|
349
|
+
return problems;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function finalizeAgentAddedFeature(evaluation) {
|
|
353
|
+
const { entry, citationsResult, reason } = evaluation;
|
|
354
|
+
return {
|
|
355
|
+
status: reason === undefined ? "ok" : "error",
|
|
356
|
+
...(reason === undefined ? {} : { error: reason }),
|
|
357
|
+
...(entry?.title === undefined ? {} : { title: entry.title }),
|
|
358
|
+
...(entry?.body === undefined ? {} : { body: entry.body }),
|
|
359
|
+
...(entry?.citations === undefined ? {} : { citations: entry.citations }),
|
|
360
|
+
...(entry?.confidence === undefined ? {} : { confidence: entry.confidence }),
|
|
361
|
+
citationsValid: citationsResult.valid,
|
|
362
|
+
...(citationsResult.invalid.length === 0
|
|
363
|
+
? {}
|
|
364
|
+
: { invalidCitations: citationsResult.invalid })
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function evaluateArea(draft, expected, projectRoot) {
|
|
369
|
+
const rawFeatures = draft.features ?? [];
|
|
370
|
+
const byRequirementId = groupByRequirementId(rawFeatures.filter((entry) => !isAgentAddedFeature(entry)));
|
|
371
|
+
const expByRequirementId = new Map(expected.features.map((exp) => [exp.requirementId, exp]));
|
|
372
|
+
const problems = [];
|
|
373
|
+
const addedFeatures = await Promise.all(
|
|
374
|
+
rawFeatures.filter(isAgentAddedFeature).map((entry) => evaluateAgentAddedFeature(entry, projectRoot))
|
|
375
|
+
);
|
|
376
|
+
for (const added of addedFeatures) problems.push(...agentAddedProblems(added));
|
|
377
|
+
if (expected.features.length === 0 && rawFeatures.length === 0 && draft.hasFeature !== false) {
|
|
378
|
+
problems.push({ check: "presence", reason: "has-feature-marker-missing-in-draft" });
|
|
379
|
+
}
|
|
380
|
+
if (draft.hasFeature === false && (expected.features.length > 0 || rawFeatures.length > 0)) {
|
|
381
|
+
problems.push({ check: "presence", reason: "has-feature-marker-conflicts-with-features" });
|
|
382
|
+
}
|
|
383
|
+
const perFeature = [];
|
|
384
|
+
// A merge group's single entry is validated once and the evaluation
|
|
385
|
+
// shared by every member it covers, instead of re-running citation/title
|
|
386
|
+
// checks once per member.
|
|
387
|
+
const evaluatedEntries = new Map();
|
|
388
|
+
expected.features.forEach((exp, index) => {
|
|
389
|
+
const group = byRequirementId.get(exp.requirementId);
|
|
390
|
+
if (group === undefined) {
|
|
391
|
+
problems.push({ requirementId: exp.requirementId, check: "presence", reason: "feature-missing-in-draft" });
|
|
392
|
+
perFeature.push({ exp, error: "feature-missing-in-agent-output" });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (group.length > 1) {
|
|
396
|
+
problems.push({ requirementId: exp.requirementId, check: "presence", reason: "feature-id-collision-in-draft" });
|
|
397
|
+
perFeature.push({ exp, error: "feature-id-collision-in-agent-output" });
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const entry = group[0];
|
|
401
|
+
const coveredIds = coveredRequirementIds(entry);
|
|
402
|
+
const merged = coveredIds.length > 1;
|
|
403
|
+
// Only checked for a non-merged entry — see validateAreaOutput in
|
|
404
|
+
// orchestrate.ts for why this cross-check is skipped for a merged entry.
|
|
405
|
+
if (!merged && entry.controlIndex !== undefined && entry.controlIndex !== index + 1) {
|
|
406
|
+
problems.push({ requirementId: exp.requirementId, check: "presence", reason: "feature-index-mismatch-in-draft" });
|
|
407
|
+
perFeature.push({ exp, error: "feature-index-mismatch-in-agent-output" });
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
perFeature.push({ exp, entry, merged, coveredIds });
|
|
411
|
+
});
|
|
412
|
+
for (const item of perFeature) {
|
|
413
|
+
if (item.error !== undefined) continue;
|
|
414
|
+
let evaluation = evaluatedEntries.get(item.entry);
|
|
415
|
+
if (evaluation === undefined) {
|
|
416
|
+
if (item.merged) {
|
|
417
|
+
const memberExps = item.coveredIds
|
|
418
|
+
.map((id) => expByRequirementId.get(id))
|
|
419
|
+
.filter((exp) => exp !== undefined);
|
|
420
|
+
evaluation = await evaluateMergedEntry(
|
|
421
|
+
item.entry, memberExps.length > 0 ? memberExps : [item.exp], projectRoot
|
|
422
|
+
);
|
|
423
|
+
} else {
|
|
424
|
+
evaluation = await evaluateEntry(item.entry, item.exp, projectRoot);
|
|
425
|
+
}
|
|
426
|
+
evaluatedEntries.set(item.entry, evaluation);
|
|
427
|
+
}
|
|
428
|
+
item.evaluation = evaluation;
|
|
429
|
+
problems.push(...problemsForEntry(item.exp.requirementId, evaluation));
|
|
430
|
+
}
|
|
431
|
+
return { perFeature, addedFeatures, problems };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function finalizeAreaFields(draft, expected, evaluated) {
|
|
435
|
+
const featuresObj = {};
|
|
436
|
+
const expByRequirementId = new Map(expected.features.map((exp) => [exp.requirementId, exp]));
|
|
437
|
+
for (const item of evaluated.perFeature) {
|
|
438
|
+
if (item.error !== undefined) {
|
|
439
|
+
featuresObj[item.exp.featureId] = {
|
|
440
|
+
status: "error",
|
|
441
|
+
error: item.error,
|
|
442
|
+
citationsValid: false,
|
|
443
|
+
restatedFactsValid: false
|
|
444
|
+
};
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (item.merged) {
|
|
448
|
+
// The merged entry's own title (not any single member's template) is
|
|
449
|
+
// the correct leading-title-line strip target — see evaluateMergedEntry.
|
|
450
|
+
const fields = finalizeEntryFields(item.entry, item.exp, item.evaluation, item.entry?.title);
|
|
451
|
+
const mergedFeatureIds = item.coveredIds
|
|
452
|
+
.map((id) => expByRequirementId.get(id)?.featureId)
|
|
453
|
+
.filter((id) => id !== undefined);
|
|
454
|
+
featuresObj[item.exp.featureId] = { ...fields, mergedFeatureIds };
|
|
455
|
+
} else {
|
|
456
|
+
featuresObj[item.exp.featureId] = finalizeEntryFields(item.entry, item.exp, item.evaluation);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const addedFeatures = evaluated.addedFeatures.map(finalizeAgentAddedFeature);
|
|
460
|
+
return {
|
|
461
|
+
status: "ok",
|
|
462
|
+
areaId: expected.areaId,
|
|
463
|
+
...(draft.areaDescription === undefined ? {} : { areaDescription: draft.areaDescription }),
|
|
464
|
+
...(draft.accessPath === undefined ? {} : { accessPath: draft.accessPath }),
|
|
465
|
+
...(draft.reachable === undefined ? {} : { reachable: draft.reachable }),
|
|
466
|
+
...(Object.keys(featuresObj).length === 0 ? {} : { features: featuresObj }),
|
|
467
|
+
...(addedFeatures.length === 0 ? {} : { addedFeatures }),
|
|
468
|
+
...(expected.features.length === 0 && addedFeatures.length === 0
|
|
469
|
+
? { hasFeature: false }
|
|
470
|
+
: {}),
|
|
471
|
+
attempt: 1
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// ---------------------------------------------------------------------------
|
|
476
|
+
// CLI
|
|
477
|
+
// ---------------------------------------------------------------------------
|
|
478
|
+
|
|
479
|
+
async function loadInputs(path_, expectedPath, projectRoot) {
|
|
480
|
+
const draft = await readJson(path_, "outputPath");
|
|
481
|
+
const expected = await readJson(expectedPath, "expectedPath");
|
|
482
|
+
await requireDirectory(path.resolve(projectRoot), "projectRoot");
|
|
483
|
+
if (expected.kind !== "feature" && expected.kind !== "area") {
|
|
484
|
+
fail(`expectedPath at ${expectedPath} has unknown "kind": ${String(expected.kind)}`);
|
|
485
|
+
}
|
|
486
|
+
return { draft, expected };
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async function runCheck(path_, expectedPath, projectRoot) {
|
|
490
|
+
const { draft, expected } = await loadInputs(path_, expectedPath, projectRoot);
|
|
491
|
+
let problems;
|
|
492
|
+
if (expected.kind === "feature") {
|
|
493
|
+
const evaluation = await evaluateEntry(draft, expected, projectRoot);
|
|
494
|
+
problems = problemsForEntry(expected.requirementId, evaluation);
|
|
495
|
+
} else {
|
|
496
|
+
const evaluated = await evaluateArea(draft, expected, projectRoot);
|
|
497
|
+
problems = evaluated.problems;
|
|
498
|
+
}
|
|
499
|
+
process.stdout.write(`${JSON.stringify({ ok: problems.length === 0, problems }, null, 2)}\n`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// In-place: reads `path_` (the subagent's raw, not-yet-validated write) and
|
|
503
|
+
// overwrites that same path with the finalized result — no separate
|
|
504
|
+
// draft/output pair, nothing to copy between two files. `finalized: true`
|
|
505
|
+
// is the marker that distinguishes "actually went through finalize" from a
|
|
506
|
+
// raw draft that merely happens to have status "ok" (its own self-report,
|
|
507
|
+
// not a validation verdict). It is written only when validation has no
|
|
508
|
+
// problems, so callers (isUnitDone, snapshot.ts) must check it together with
|
|
509
|
+
// status instead of trusting a raw draft.
|
|
510
|
+
async function runFinalize(path_, expectedPath, projectRoot) {
|
|
511
|
+
const { draft, expected } = await loadInputs(path_, expectedPath, projectRoot);
|
|
512
|
+
let result;
|
|
513
|
+
let problems;
|
|
514
|
+
if (expected.kind === "feature") {
|
|
515
|
+
const evaluation = await evaluateEntry(draft, expected, projectRoot);
|
|
516
|
+
problems = problemsForEntry(expected.requirementId, evaluation);
|
|
517
|
+
result = {
|
|
518
|
+
...draft,
|
|
519
|
+
...finalizeEntryFields(draft, expected, evaluation),
|
|
520
|
+
attempt: 1
|
|
521
|
+
};
|
|
522
|
+
} else {
|
|
523
|
+
const evaluated = await evaluateArea(draft, expected, projectRoot);
|
|
524
|
+
problems = evaluated.problems;
|
|
525
|
+
result = { ...finalizeAreaFields(draft, expected, evaluated) };
|
|
526
|
+
}
|
|
527
|
+
const ok = problems.length === 0;
|
|
528
|
+
result = {
|
|
529
|
+
...result,
|
|
530
|
+
status: "ok",
|
|
531
|
+
...(ok ? {} : { problems }),
|
|
532
|
+
finalized: true
|
|
533
|
+
};
|
|
534
|
+
await writeFile(path_, JSON.stringify(result, null, 2), "utf8");
|
|
535
|
+
process.stdout.write(`${JSON.stringify({ ok, outputPath: path_, ...(ok ? {} : { problems }) }, null, 2)}\n`);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async function main() {
|
|
539
|
+
const [, , mode, ...rest] = process.argv;
|
|
540
|
+
if (mode === "check" && rest.length === 3) {
|
|
541
|
+
await runCheck(rest[0], rest[1], rest[2]);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (mode === "finalize" && rest.length === 3) {
|
|
545
|
+
await runFinalize(rest[0], rest[1], rest[2]);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
fail(
|
|
549
|
+
"Usage:\n" +
|
|
550
|
+
" node reqgen-selfcheck.mjs check <outputPath> <expectedPath> <projectRoot>\n" +
|
|
551
|
+
" node reqgen-selfcheck.mjs finalize <outputPath> <expectedPath> <projectRoot>"
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
await main();
|