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,109 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { runAgentStage2 } from "./agent-render/index.js";
|
|
4
|
+
export { loadStage2Input } from "./stage2-input.js";
|
|
5
|
+
export { loadStage2NameTraceabilityInput } from "./stage2-name-trace-input.js";
|
|
6
|
+
export { parseAgentArguments, runAgentStage2 } from "./agent-render/index.js";
|
|
7
|
+
export {
|
|
8
|
+
buildSpecInputBatches,
|
|
9
|
+
exportSpecHandoff,
|
|
10
|
+
parseStage2ReqText,
|
|
11
|
+
SPEC_HANDOFF_CONTRACT_VERSION,
|
|
12
|
+
SPEC_INPUT_FORMAT,
|
|
13
|
+
validateSpecInputText,
|
|
14
|
+
} from "./spec-handoff.js";
|
|
15
|
+
export const STAGE2_USAGE = `Usage (default, --mode subagent, ADR-0005):
|
|
16
|
+
pnpm stage2 -- --input <successful-stage1-run-directory> \\
|
|
17
|
+
--project-root <android-project> [--output <directory>] \\
|
|
18
|
+
[--per-feature] [--with-unreachable-area] [--spec-output <directory>]
|
|
19
|
+
|
|
20
|
+
Writes rendering/<unit>/{task.md,expected.json} for every renderable Area
|
|
21
|
+
(or Feature, with --per-feature) and prints a manifest — it does NOT spawn
|
|
22
|
+
any backend. Turning each manifest entry into a rendering/<unit>/output.json
|
|
23
|
+
is done by dispatching the Claude Code Agent tool outside this process; see
|
|
24
|
+
../references/subagent-render.md for the full protocol
|
|
25
|
+
(self-check via scripts/reqgen-selfcheck.mjs, retry/skip/abort policy).
|
|
26
|
+
|
|
27
|
+
Usage (--mode shell):
|
|
28
|
+
pnpm stage2 -- --mode shell --input <successful-stage1-run-directory> \\
|
|
29
|
+
--project-root <android-project> \\
|
|
30
|
+
[--backend claude|opencode] [--agent-name reqgen-renderer] \\
|
|
31
|
+
[--ai-command <executable>] [--concurrency 8] \\
|
|
32
|
+
[--max-retries 5] [--backoff-ms 5000] [--output <directory> | --resume <directory>] \\
|
|
33
|
+
[--per-feature] [--with-unreachable-area] [--spec-output <directory>]
|
|
34
|
+
|
|
35
|
+
Renders REQ prose by spawning a backend (claude, opencode, or --ai-command)
|
|
36
|
+
per unit, then writes BOTH req.md (markdown) and per-area req-{idx}.txt
|
|
37
|
+
(plain text, no markdown) into the output directory. --backend defaults to
|
|
38
|
+
"claude" (the \`claude\` CLI's own -p/--print headless mode, dispatched with
|
|
39
|
+
--agent <agent-name> so it picks up the same .claude/agents/<agent-name>.md
|
|
40
|
+
config the default subagent mode uses). The default output directory is
|
|
41
|
+
<input>/generation.
|
|
42
|
+
|
|
43
|
+
The default Claude backend uses the unified protocol (task.md +
|
|
44
|
+
self-check/finalize via reqgen-selfcheck.mjs); custom --ai-command and
|
|
45
|
+
opencode use the inline-prompt path. Use --mode shell-legacy to force the
|
|
46
|
+
old inline-prompt path for Claude as well.
|
|
47
|
+
|
|
48
|
+
--per-feature render one session per Feature instead of the
|
|
49
|
+
default one session per Area.
|
|
50
|
+
--with-unreachable-area keep Areas judged unreachable in output (sunk
|
|
51
|
+
after every reachable Area) instead of dropping
|
|
52
|
+
them. In per-feature mode the judgment falls back
|
|
53
|
+
to static analysis (canonicalPath.status).
|
|
54
|
+
--resume <directory> resume --mode shell from an existing output directory;
|
|
55
|
+
finalized units are reused and unfinished units rerun.
|
|
56
|
+
|
|
57
|
+
Usage (--mode shell-legacy):
|
|
58
|
+
pnpm stage2 -- --mode shell-legacy --input <successful-stage1-run-directory> \\
|
|
59
|
+
--project-root <android-project> \\
|
|
60
|
+
[--backend claude|opencode] [--agent-name reqgen-renderer] \\
|
|
61
|
+
[--ai-command <executable>] [--concurrency 8] \\
|
|
62
|
+
[--max-retries 5] [--backoff-ms 5000] [--output <directory>] \\
|
|
63
|
+
[--per-feature] [--with-unreachable-area] [--spec-output <directory>]
|
|
64
|
+
|
|
65
|
+
Pre-normalize shell interaction: ALL backends (including claude) use the
|
|
66
|
+
inline-prompt path — the full prompt is passed on stdin, JSON is parsed
|
|
67
|
+
from stdout, and validation/retry is done by this process. Unlike --mode
|
|
68
|
+
shell (which uses task.md + self-check/finalize for the claude backend),
|
|
69
|
+
this mode does NOT delegate validation to the subprocess. Same output as
|
|
70
|
+
unified self-check protocol is not available in the target environment.
|
|
71
|
+
|
|
72
|
+
Usage (either mode):
|
|
73
|
+
pnpm stage2 -- --input <run-directory> --snapshot \\
|
|
74
|
+
[--output <directory>] [--per-feature] [--with-unreachable-area] \\
|
|
75
|
+
[--spec-output <directory>]
|
|
76
|
+
|
|
77
|
+
--snapshot merges already-persisted rendering results offline into both
|
|
78
|
+
a timestamped req-<timestamp>.md and per-area req-{idx}.txt files, without
|
|
79
|
+
contacting any backend.
|
|
80
|
+
|
|
81
|
+
--spec-output <directory> additionally writes page-local
|
|
82
|
+
<batch>/REQ.txt files and manifest.json for $a2h-spec-generate after the
|
|
83
|
+
Stage 2 merge. Each batch contains one Area/page boundary; the option may
|
|
84
|
+
be used with shell rendering or --snapshot. It is rejected during the
|
|
85
|
+
subagent preparation-only phase because no finalized output exists yet.
|
|
86
|
+
|
|
87
|
+
When --output is explicitly set (all modes except --snapshot), the target
|
|
88
|
+
directory must be empty or non-existent. Use --resume <directory> with
|
|
89
|
+
--mode shell to continue an interrupted run; finalized output.json files
|
|
90
|
+
are reused and the remaining units are rendered. --snapshot is exempt
|
|
91
|
+
because it must read existing output.json. Default paths are never guarded.
|
|
92
|
+
`;
|
|
93
|
+
const defaultIo = {
|
|
94
|
+
stdout: (text) => process.stdout.write(text),
|
|
95
|
+
stderr: (text) => process.stderr.write(text),
|
|
96
|
+
};
|
|
97
|
+
export async function runStage2Cli(argv, io = defaultIo) {
|
|
98
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
99
|
+
io.stdout(STAGE2_USAGE);
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
return runAgentStage2(argv, io);
|
|
103
|
+
}
|
|
104
|
+
const isDirectExecution =
|
|
105
|
+
process.argv[1] !== undefined &&
|
|
106
|
+
resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
|
|
107
|
+
if (isDirectExecution) {
|
|
108
|
+
process.exitCode = await runStage2Cli(process.argv.slice(2));
|
|
109
|
+
}
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
GAP_AUDIT_CONTRACT_VERSION,
|
|
5
|
+
semanticCompletenessValues,
|
|
6
|
+
} from "./contracts/index.js";
|
|
7
|
+
import { sha256Text } from "./pipeline/output.js";
|
|
8
|
+
export class GenerationError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
status;
|
|
11
|
+
details;
|
|
12
|
+
constructor(code, message, options = {}) {
|
|
13
|
+
super(message, { cause: options.cause });
|
|
14
|
+
this.name = "GenerationError";
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.status = options.status;
|
|
17
|
+
this.details = options.details;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function isRecord(value) {
|
|
21
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
22
|
+
}
|
|
23
|
+
export function parseJson(text, filename) {
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(text);
|
|
26
|
+
} catch {
|
|
27
|
+
throw new GenerationError(
|
|
28
|
+
"STAGE2_INPUT_JSON_INVALID",
|
|
29
|
+
`${filename} is not valid JSON`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function readInputFiles(inputDirectory) {
|
|
34
|
+
try {
|
|
35
|
+
const [reportText, reqModelText] = await Promise.all([
|
|
36
|
+
readFile(join(inputDirectory, "run-report.json"), "utf8"),
|
|
37
|
+
readFile(join(inputDirectory, "req-model.json"), "utf8"),
|
|
38
|
+
]);
|
|
39
|
+
return { reportText, reqModelText };
|
|
40
|
+
} catch (error) {
|
|
41
|
+
throw new GenerationError(
|
|
42
|
+
"STAGE2_INPUT_MISSING",
|
|
43
|
+
"Stage 2 requires run-report.json and req-model.json from one Stage 1 run",
|
|
44
|
+
{ cause: error },
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function completenessOf(value) {
|
|
49
|
+
if (!isRecord(value) || !Object.hasOwn(value, "semanticCompleteness")) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return typeof value.semanticCompleteness === "string"
|
|
53
|
+
? value.semanticCompleteness
|
|
54
|
+
: undefined;
|
|
55
|
+
}
|
|
56
|
+
function assertReportCompleteness(reportValue) {
|
|
57
|
+
const reportCompleteness = completenessOf(reportValue);
|
|
58
|
+
const invalidReport =
|
|
59
|
+
Object.hasOwn(reportValue, "semanticCompleteness") &&
|
|
60
|
+
(reportCompleteness === undefined ||
|
|
61
|
+
!semanticCompletenessValues.includes(reportCompleteness));
|
|
62
|
+
const inconsistentReport =
|
|
63
|
+
(reportValue.publishable === false &&
|
|
64
|
+
reportCompleteness !== "incomplete-known") ||
|
|
65
|
+
(reportValue.publishable === true &&
|
|
66
|
+
reportCompleteness === "incomplete-known");
|
|
67
|
+
if (invalidReport || inconsistentReport) {
|
|
68
|
+
throw new GenerationError(
|
|
69
|
+
"STAGE2_INPUT_QUALITY_STATE_INVALID",
|
|
70
|
+
"run-report.json has an inconsistent publishable/semanticCompleteness state",
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function assertReqModelCompleteness(reportValue, reqModelValue) {
|
|
75
|
+
const reqModelCompleteness = completenessOf(reqModelValue);
|
|
76
|
+
const invalidReqModel =
|
|
77
|
+
Object.hasOwn(reqModelValue, "semanticCompleteness") &&
|
|
78
|
+
(reqModelCompleteness === undefined ||
|
|
79
|
+
!semanticCompletenessValues.includes(reqModelCompleteness));
|
|
80
|
+
if (
|
|
81
|
+
invalidReqModel ||
|
|
82
|
+
(reportValue.publishable === true &&
|
|
83
|
+
reqModelCompleteness === "incomplete-known")
|
|
84
|
+
) {
|
|
85
|
+
throw new GenerationError(
|
|
86
|
+
"STAGE2_INPUT_QUALITY_STATE_INVALID",
|
|
87
|
+
"req-model.json has an inconsistent semanticCompleteness state",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function assertReportReady(reportValue) {
|
|
92
|
+
if (
|
|
93
|
+
!isRecord(reportValue) ||
|
|
94
|
+
reportValue.status !== "succeeded" ||
|
|
95
|
+
(reportValue.publishable !== true && reportValue.publishable !== false) ||
|
|
96
|
+
reportValue.completedStage !== "validate" ||
|
|
97
|
+
!Array.isArray(reportValue.artifacts)
|
|
98
|
+
) {
|
|
99
|
+
throw new GenerationError(
|
|
100
|
+
"STAGE2_INPUT_NOT_READY",
|
|
101
|
+
"run-report.json does not describe a successful, validated Stage 1 run",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function assertReqModelEnvelope(reqModelValue) {
|
|
106
|
+
if (
|
|
107
|
+
!isRecord(reqModelValue) ||
|
|
108
|
+
reqModelValue.schemaVersion !== "1.0.0" ||
|
|
109
|
+
reqModelValue.artifactKind !== "req-model" ||
|
|
110
|
+
typeof reqModelValue.caseId !== "string" ||
|
|
111
|
+
!isRecord(reqModelValue.analysisContext) ||
|
|
112
|
+
!Array.isArray(reqModelValue.areas) ||
|
|
113
|
+
!Array.isArray(reqModelValue.unresolved)
|
|
114
|
+
) {
|
|
115
|
+
throw new GenerationError(
|
|
116
|
+
"STAGE2_REQ_MODEL_INVALID",
|
|
117
|
+
"req-model.json has an unsupported envelope",
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function assertReqModelHash(reportValue, reqModelText) {
|
|
122
|
+
const reqArtifact = reportValue.artifacts.find(
|
|
123
|
+
(artifact) =>
|
|
124
|
+
isRecord(artifact) &&
|
|
125
|
+
artifact.kind === "req-model" &&
|
|
126
|
+
artifact.relativePath === "req-model.json",
|
|
127
|
+
);
|
|
128
|
+
if (
|
|
129
|
+
!isRecord(reqArtifact) ||
|
|
130
|
+
typeof reqArtifact.sha256 !== "string" ||
|
|
131
|
+
reqArtifact.sha256 !== sha256Text(reqModelText)
|
|
132
|
+
) {
|
|
133
|
+
throw new GenerationError(
|
|
134
|
+
"STAGE2_REQ_MODEL_HASH_MISMATCH",
|
|
135
|
+
"req-model.json does not match the artifact hash in run-report.json",
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function assertSameContext(report, reqModel) {
|
|
140
|
+
if (
|
|
141
|
+
report.caseId !== reqModel.caseId ||
|
|
142
|
+
report.target === undefined ||
|
|
143
|
+
report.target.applicationModule !==
|
|
144
|
+
reqModel.analysisContext.applicationModule ||
|
|
145
|
+
report.target.buildVariant !== reqModel.analysisContext.buildVariant ||
|
|
146
|
+
JSON.stringify(report.target.resourceConfiguration) !==
|
|
147
|
+
JSON.stringify(reqModel.analysisContext.resourceConfiguration)
|
|
148
|
+
) {
|
|
149
|
+
throw new GenerationError(
|
|
150
|
+
"STAGE2_INPUT_CONTEXT_MISMATCH",
|
|
151
|
+
"run-report.json and req-model.json do not describe the same analysis context",
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function assertAreaGraphEnvelope(graphValue) {
|
|
156
|
+
if (
|
|
157
|
+
!isRecord(graphValue) ||
|
|
158
|
+
graphValue.schemaVersion !== "1.0.0" ||
|
|
159
|
+
graphValue.artifactKind !== "area-graph" ||
|
|
160
|
+
typeof graphValue.caseId !== "string" ||
|
|
161
|
+
!isRecord(graphValue.analysisContext) ||
|
|
162
|
+
!Array.isArray(graphValue.areas) ||
|
|
163
|
+
!Array.isArray(graphValue.controls) ||
|
|
164
|
+
!Array.isArray(graphValue.unresolved)
|
|
165
|
+
) {
|
|
166
|
+
throw new GenerationError(
|
|
167
|
+
"STAGE2_AREA_GRAPH_INVALID",
|
|
168
|
+
"area-graph.json has an unsupported envelope",
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function assertAreaGraphHash(report, graphText) {
|
|
173
|
+
const graphArtifact = report.artifacts.find(
|
|
174
|
+
(artifact) =>
|
|
175
|
+
artifact.kind === "area-graph" &&
|
|
176
|
+
artifact.relativePath === "area-graph.json",
|
|
177
|
+
);
|
|
178
|
+
if (
|
|
179
|
+
graphArtifact === undefined ||
|
|
180
|
+
graphArtifact.sha256 !== sha256Text(graphText)
|
|
181
|
+
) {
|
|
182
|
+
throw new GenerationError(
|
|
183
|
+
"STAGE2_AREA_GRAPH_HASH_MISMATCH",
|
|
184
|
+
"area-graph.json does not match the artifact hash in run-report.json",
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function assertAreaGraphContext(graph, reqModel) {
|
|
189
|
+
if (
|
|
190
|
+
graph.caseId !== reqModel.caseId ||
|
|
191
|
+
JSON.stringify(graph.analysisContext) !==
|
|
192
|
+
JSON.stringify(reqModel.analysisContext)
|
|
193
|
+
) {
|
|
194
|
+
throw new GenerationError(
|
|
195
|
+
"STAGE2_AREA_GRAPH_CONTEXT_MISMATCH",
|
|
196
|
+
"area-graph.json does not match req-model.json",
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/** Required alongside req-model.json to recompute the Stage 1 deterministic
|
|
201
|
+
* draft (renderReqDraft) as the merge template — recomputing instead of
|
|
202
|
+
* reading generation/req.md keeps the template hash-verified against
|
|
203
|
+
* the same req-model instead of trusting a plain text file on disk. */
|
|
204
|
+
export async function loadStage2AreaGraph(inputDirectory, report, reqModel) {
|
|
205
|
+
let graphText;
|
|
206
|
+
try {
|
|
207
|
+
graphText = await readFile(join(inputDirectory, "area-graph.json"), "utf8");
|
|
208
|
+
} catch (error) {
|
|
209
|
+
throw new GenerationError(
|
|
210
|
+
"STAGE2_AREA_GRAPH_MISSING",
|
|
211
|
+
"Stage 2 requires area-graph.json from the same Stage 1 run",
|
|
212
|
+
{ cause: error },
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
const graphValue = parseJson(graphText, "area-graph.json");
|
|
216
|
+
assertAreaGraphEnvelope(graphValue);
|
|
217
|
+
assertAreaGraphHash(report, graphText);
|
|
218
|
+
const graph = graphValue;
|
|
219
|
+
assertAreaGraphContext(graph, reqModel);
|
|
220
|
+
return graph;
|
|
221
|
+
}
|
|
222
|
+
function assertGapAuditEnvelope(gapAuditValue) {
|
|
223
|
+
if (
|
|
224
|
+
!isRecord(gapAuditValue) ||
|
|
225
|
+
gapAuditValue.contractVersion !== GAP_AUDIT_CONTRACT_VERSION ||
|
|
226
|
+
gapAuditValue.artifactKind !== "gap-audit" ||
|
|
227
|
+
typeof gapAuditValue.caseId !== "string" ||
|
|
228
|
+
!Array.isArray(gapAuditValue.categories) ||
|
|
229
|
+
!isRecord(gapAuditValue.summary) ||
|
|
230
|
+
!semanticCompletenessValues.includes(gapAuditValue.semanticCompleteness) ||
|
|
231
|
+
!Array.isArray(gapAuditValue.semanticLimitations)
|
|
232
|
+
) {
|
|
233
|
+
throw new GenerationError(
|
|
234
|
+
"STAGE2_GAP_AUDIT_INVALID",
|
|
235
|
+
"gap-audit.json has an unsupported envelope",
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function assertGapAuditHash(report, gapAuditText) {
|
|
240
|
+
const gapAuditArtifact = report.artifacts.find(
|
|
241
|
+
(artifact) =>
|
|
242
|
+
artifact.kind === "gap-audit" &&
|
|
243
|
+
artifact.relativePath === "gap-audit.json",
|
|
244
|
+
);
|
|
245
|
+
if (
|
|
246
|
+
gapAuditArtifact === undefined ||
|
|
247
|
+
gapAuditArtifact.sha256 !== sha256Text(gapAuditText)
|
|
248
|
+
) {
|
|
249
|
+
throw new GenerationError(
|
|
250
|
+
"STAGE2_GAP_AUDIT_HASH_MISMATCH",
|
|
251
|
+
"gap-audit.json does not match the artifact hash in run-report.json",
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function assertGapAuditContext(gapAudit, reqModel) {
|
|
256
|
+
if (gapAudit.caseId !== reqModel.caseId) {
|
|
257
|
+
throw new GenerationError(
|
|
258
|
+
"STAGE2_GAP_AUDIT_CONTEXT_MISMATCH",
|
|
259
|
+
"gap-audit.json does not match req-model.json",
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/** Required alongside req-model.json so the AI Agent rendering route can
|
|
264
|
+
* surface gap-audit's unresolved/limited candidates in `## unresolved` —
|
|
265
|
+
* a purely additive Stage 2 signal (ADR-0004, PRD-0007). Mandatory like
|
|
266
|
+
* loadStage2AreaGraph: no silent-skip path when the file is missing. */
|
|
267
|
+
export async function loadStage2GapAudit(inputDirectory, report, reqModel) {
|
|
268
|
+
let gapAuditText;
|
|
269
|
+
try {
|
|
270
|
+
gapAuditText = await readFile(
|
|
271
|
+
join(inputDirectory, "gap-audit.json"),
|
|
272
|
+
"utf8",
|
|
273
|
+
);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
throw new GenerationError(
|
|
276
|
+
"STAGE2_GAP_AUDIT_MISSING",
|
|
277
|
+
"Stage 2 requires gap-audit.json from the same Stage 1 run",
|
|
278
|
+
{ cause: error },
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
const gapAuditValue = parseJson(gapAuditText, "gap-audit.json");
|
|
282
|
+
assertGapAuditEnvelope(gapAuditValue);
|
|
283
|
+
assertGapAuditHash(report, gapAuditText);
|
|
284
|
+
const gapAudit = gapAuditValue;
|
|
285
|
+
assertGapAuditContext(gapAudit, reqModel);
|
|
286
|
+
return gapAudit;
|
|
287
|
+
}
|
|
288
|
+
/** True only when every branch a target could resolve to is a "kind: area"
|
|
289
|
+
* ReqFeatureAreaTarget — i.e. the destination is always one of the Areas
|
|
290
|
+
* already rendered elsewhere in this req-model. "history-back" and
|
|
291
|
+
* "conditional-area-set" are built (analysis/req-model/build.ts) only from
|
|
292
|
+
* resolved Area edges, so they qualify unconditionally; "external" and an
|
|
293
|
+
* unresolved target/branch never do, since no Area entry describes them.
|
|
294
|
+
* "alternative-target-set" is the one shape that can legitimately mix an
|
|
295
|
+
* Area branch with an external or unresolved branch (built as the fallback
|
|
296
|
+
* whenever an Action's edges resolve to more than one target kind, or include
|
|
297
|
+
* an unresolved edge) — such a Feature must not be skipped, since part of its
|
|
298
|
+
* behavior (the non-Area branch) has no Area entry to fall back on. */
|
|
299
|
+
function targetResolvesOnlyToAreas(target) {
|
|
300
|
+
if ("status" in target) return false;
|
|
301
|
+
switch (target.kind) {
|
|
302
|
+
case "area":
|
|
303
|
+
case "history-back":
|
|
304
|
+
case "conditional-area-set":
|
|
305
|
+
return true;
|
|
306
|
+
case "external":
|
|
307
|
+
return false;
|
|
308
|
+
case "alternative-target-set":
|
|
309
|
+
return target.branches.every((branch) =>
|
|
310
|
+
isAreaBranchTarget(branch.target),
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function isAreaBranchTarget(target) {
|
|
315
|
+
return !("status" in target) && target.kind === "area";
|
|
316
|
+
}
|
|
317
|
+
function isNavigationOnlyInteraction(interaction) {
|
|
318
|
+
return (
|
|
319
|
+
interaction.target !== undefined &&
|
|
320
|
+
interaction.effects.length === 0 &&
|
|
321
|
+
targetResolvesOnlyToAreas(interaction.target)
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
/** A Feature whose only observable behavior, across every way it can be
|
|
325
|
+
* triggered, is navigating to Area(s) that are themselves already rendered
|
|
326
|
+
* carries no independent requirement content — the destination Area already
|
|
327
|
+
* describes itself. Areas with no remaining Features are still rendered
|
|
328
|
+
* (static description + navigation), so this only ever removes Features,
|
|
329
|
+
* never their owning Area. */
|
|
330
|
+
function isNavigationOnlyFeature(feature) {
|
|
331
|
+
const interactions = feature.interactions ?? [
|
|
332
|
+
{ effects: feature.effects, target: feature.target },
|
|
333
|
+
];
|
|
334
|
+
return (
|
|
335
|
+
interactions.length > 0 && interactions.every(isNavigationOnlyInteraction)
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Filters what should not be rendered, unconditionally:
|
|
340
|
+
* - Features whose only behavior, on every trigger, is navigating to Area(s)
|
|
341
|
+
* already rendered elsewhere (no independent req content). A Feature that
|
|
342
|
+
* can also navigate to something that is not an Area in this req-model
|
|
343
|
+
* (an external endpoint, or an unresolved target/branch) is kept, since
|
|
344
|
+
* that part of its behavior has nowhere else to be described.
|
|
345
|
+
*
|
|
346
|
+
* Areas with unreachable canonicalPath are always kept — the Agent is asked
|
|
347
|
+
* to determine the actual access path (or confirm unreachability with
|
|
348
|
+
* evidence) itself rather than being skipped in favor of the deterministic
|
|
349
|
+
* "无法到达" placeholder.
|
|
350
|
+
*
|
|
351
|
+
* Areas are never dropped for having zero remaining Features — an Area with
|
|
352
|
+
* no dynamic Features still gets rendered for its static description and
|
|
353
|
+
* navigation.
|
|
354
|
+
*/
|
|
355
|
+
export function filterRenderableAreas(reqModel) {
|
|
356
|
+
return {
|
|
357
|
+
...reqModel,
|
|
358
|
+
areas: reqModel.areas.map((area) => ({
|
|
359
|
+
...area,
|
|
360
|
+
features: area.features.filter(
|
|
361
|
+
(feature) => !isNavigationOnlyFeature(feature),
|
|
362
|
+
),
|
|
363
|
+
})),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Stable partition: Areas with a resolved/unresolved canonicalPath first (in
|
|
368
|
+
* their original relative order), followed by unreachable Areas (in their
|
|
369
|
+
* original relative order). Must run once, before both the deterministic
|
|
370
|
+
* template (renderReqDraft) and the Agent-render walk (filterRenderableAreas,
|
|
371
|
+
* merge*) see the req-model — they all read `reqModel.areas` positionally,
|
|
372
|
+
* so reordering later or only in one of them would desync the merge walk
|
|
373
|
+
* from the template it is walking.
|
|
374
|
+
*/
|
|
375
|
+
export function sortAreasReachableFirst(reqModel) {
|
|
376
|
+
const reachable = reqModel.areas.filter(
|
|
377
|
+
(area) => area.canonicalPath.status !== "unreachable",
|
|
378
|
+
);
|
|
379
|
+
const unreachable = reqModel.areas.filter(
|
|
380
|
+
(area) => area.canonicalPath.status === "unreachable",
|
|
381
|
+
);
|
|
382
|
+
return { ...reqModel, areas: [...reachable, ...unreachable] };
|
|
383
|
+
}
|
|
384
|
+
export async function loadStage2Input(inputDirectory) {
|
|
385
|
+
return (await loadStage2InputWithReport(inputDirectory)).reqModel;
|
|
386
|
+
}
|
|
387
|
+
export async function loadStage2InputWithReport(inputDirectory) {
|
|
388
|
+
const { reportText, reqModelText } = await readInputFiles(inputDirectory);
|
|
389
|
+
const reportValue = parseJson(reportText, "run-report.json");
|
|
390
|
+
const reqModelValue = parseJson(reqModelText, "req-model.json");
|
|
391
|
+
assertReportReady(reportValue);
|
|
392
|
+
assertReportCompleteness(reportValue);
|
|
393
|
+
assertReqModelHash(reportValue, reqModelText);
|
|
394
|
+
assertReqModelEnvelope(reqModelValue);
|
|
395
|
+
assertReqModelCompleteness(reportValue, reqModelValue);
|
|
396
|
+
const report = reportValue;
|
|
397
|
+
const reqModel = reqModelValue;
|
|
398
|
+
if (
|
|
399
|
+
report.publishable === false &&
|
|
400
|
+
reqModel.semanticCompleteness !== "incomplete-known"
|
|
401
|
+
) {
|
|
402
|
+
throw new GenerationError(
|
|
403
|
+
"STAGE2_INPUT_QUALITY_STATE_INVALID",
|
|
404
|
+
"req-model.json does not match the incomplete Stage 1 quality state",
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
if (
|
|
408
|
+
report.semanticCompleteness !== undefined &&
|
|
409
|
+
reqModel.semanticCompleteness !== undefined &&
|
|
410
|
+
report.semanticCompleteness !== reqModel.semanticCompleteness
|
|
411
|
+
) {
|
|
412
|
+
throw new GenerationError(
|
|
413
|
+
"STAGE2_SEMANTIC_COMPLETENESS_MISMATCH",
|
|
414
|
+
"run-report.json and req-model.json disagree about semantic completeness",
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
assertSameContext(report, reqModel);
|
|
418
|
+
return { reqModel, report };
|
|
419
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { sha256Text } from "./pipeline/output.js";
|
|
4
|
+
import { GenerationError, isRecord, parseJson } from "./stage2-input.js";
|
|
5
|
+
async function readOptionalText(path) {
|
|
6
|
+
try {
|
|
7
|
+
return await readFile(path, "utf8");
|
|
8
|
+
} catch (error) {
|
|
9
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
10
|
+
return undefined;
|
|
11
|
+
throw error;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Source locations are intentionally not copied into req-model.json. Read the
|
|
16
|
+
* two sibling Stage 1 artifacts only for the optional name-traceability
|
|
17
|
+
* output (human audit in openai-compatible mode, title/citation grounding in
|
|
18
|
+
* agent mode).
|
|
19
|
+
*/
|
|
20
|
+
export async function loadStage2NameTraceabilityInput(
|
|
21
|
+
inputDirectory,
|
|
22
|
+
reqModel,
|
|
23
|
+
) {
|
|
24
|
+
const [sourceText, graphText] = await Promise.all([
|
|
25
|
+
readOptionalText(join(inputDirectory, "source-facts.json")),
|
|
26
|
+
readOptionalText(join(inputDirectory, "area-graph.json")),
|
|
27
|
+
]);
|
|
28
|
+
if (sourceText === undefined && graphText === undefined) return undefined;
|
|
29
|
+
if (sourceText === undefined || graphText === undefined) {
|
|
30
|
+
throw new GenerationError(
|
|
31
|
+
"STAGE2_NAME_TRACE_INPUT_INCOMPLETE",
|
|
32
|
+
"Name traceability requires both source-facts.json and area-graph.json",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const sourceValue = parseJson(sourceText, "source-facts.json");
|
|
36
|
+
const graphValue = parseJson(graphText, "area-graph.json");
|
|
37
|
+
const reportValue = parseJson(
|
|
38
|
+
await readFile(join(inputDirectory, "run-report.json"), "utf8"),
|
|
39
|
+
"run-report.json",
|
|
40
|
+
);
|
|
41
|
+
if (
|
|
42
|
+
!isRecord(sourceValue) ||
|
|
43
|
+
sourceValue.schemaVersion !== "1.0.0" ||
|
|
44
|
+
sourceValue.artifactKind !== "source-facts" ||
|
|
45
|
+
!Array.isArray(sourceValue.evidence) ||
|
|
46
|
+
!Array.isArray(sourceValue.facts) ||
|
|
47
|
+
!Array.isArray(sourceValue.unresolved) ||
|
|
48
|
+
!isRecord(graphValue) ||
|
|
49
|
+
graphValue.schemaVersion !== "1.0.0" ||
|
|
50
|
+
graphValue.artifactKind !== "area-graph" ||
|
|
51
|
+
!Array.isArray(graphValue.areas) ||
|
|
52
|
+
!Array.isArray(graphValue.controls) ||
|
|
53
|
+
!Array.isArray(graphValue.unresolved)
|
|
54
|
+
) {
|
|
55
|
+
throw new GenerationError(
|
|
56
|
+
"STAGE2_NAME_TRACE_INPUT_INVALID",
|
|
57
|
+
"source-facts.json or area-graph.json has an unsupported envelope",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const artifactHashMatches = (kind, relativePath, text) =>
|
|
61
|
+
isRecord(reportValue) &&
|
|
62
|
+
Array.isArray(reportValue.artifacts) &&
|
|
63
|
+
reportValue.artifacts.some(
|
|
64
|
+
(artifact) =>
|
|
65
|
+
isRecord(artifact) &&
|
|
66
|
+
artifact.kind === kind &&
|
|
67
|
+
artifact.relativePath === relativePath &&
|
|
68
|
+
artifact.sha256 === sha256Text(text),
|
|
69
|
+
);
|
|
70
|
+
if (
|
|
71
|
+
!artifactHashMatches("source-facts", "source-facts.json", sourceText) ||
|
|
72
|
+
!artifactHashMatches("area-graph", "area-graph.json", graphText)
|
|
73
|
+
) {
|
|
74
|
+
throw new GenerationError(
|
|
75
|
+
"STAGE2_NAME_TRACE_HASH_MISMATCH",
|
|
76
|
+
"Name traceability inputs do not match the hashes in run-report.json",
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const source = sourceValue;
|
|
80
|
+
const graph = graphValue;
|
|
81
|
+
if (
|
|
82
|
+
source.caseId !== reqModel.caseId ||
|
|
83
|
+
graph.caseId !== reqModel.caseId ||
|
|
84
|
+
JSON.stringify(source.analysisContext) !==
|
|
85
|
+
JSON.stringify(reqModel.analysisContext) ||
|
|
86
|
+
JSON.stringify(graph.analysisContext) !==
|
|
87
|
+
JSON.stringify(reqModel.analysisContext)
|
|
88
|
+
) {
|
|
89
|
+
throw new GenerationError(
|
|
90
|
+
"STAGE2_NAME_TRACE_CONTEXT_MISMATCH",
|
|
91
|
+
"Name traceability inputs do not match req-model.json",
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return { source, graph };
|
|
95
|
+
}
|