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.
Files changed (227) hide show
  1. package/agents/api-analyzer.md +113 -0
  2. package/agents/app-action.md +122 -0
  3. package/agents/code-reviewer.md +191 -295
  4. package/agents/coder.md +377 -0
  5. package/agents/self-tester.md +8 -8
  6. package/agents/spec-designer.md +56 -0
  7. package/package.json +1 -1
  8. package/skills/a2h-app-req-gen/SKILL.md +174 -0
  9. package/skills/a2h-app-req-gen/references/spec-handoff.md +99 -0
  10. package/skills/a2h-app-req-gen/references/subagent-render.md +115 -0
  11. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/build.gradle.kts +41 -0
  12. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradle/wrapper/gradle-wrapper.jar +0 -0
  13. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradle/wrapper/gradle-wrapper.properties +8 -0
  14. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradle.properties +3 -0
  15. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradlew +251 -0
  16. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradlew.bat +94 -0
  17. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/settings.gradle.kts +17 -0
  18. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawAnalysisDetector.java +1911 -0
  19. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawIssueRegistry.java +37 -0
  20. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawJson.java +146 -0
  21. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawLocation.java +96 -0
  22. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawRecordWriter.java +227 -0
  23. 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
  24. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/resources/dev/reqgen/analyzer/jvm/raw-record-v1.schema.json +93 -0
  25. package/skills/a2h-app-req-gen/scripts/reqgen/assets/dashboard.html +151 -0
  26. package/skills/a2h-app-req-gen/scripts/reqgen/gradle/reqgen-lint.init.gradle +60 -0
  27. package/skills/a2h-app-req-gen/scripts/reqgen/gradle/reqgen-model.init.gradle +204 -0
  28. package/skills/a2h-app-req-gen/scripts/reqgen/package.json +14 -0
  29. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/backend.js +323 -0
  30. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/cli.js +811 -0
  31. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/gap-audit-link.js +62 -0
  32. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/index.js +66 -0
  33. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/merge.js +605 -0
  34. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/name-trace.js +153 -0
  35. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/orchestrate.js +1324 -0
  36. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/output-parse.js +50 -0
  37. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/overview.js +10 -0
  38. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/pool.js +23 -0
  39. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/post-merge-fix.js +146 -0
  40. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/prompt-loader.js +51 -0
  41. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/prompt.js +967 -0
  42. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/prompts.md +301 -0
  43. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/serve.js +78 -0
  44. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/short-id.js +46 -0
  45. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/snapshot.js +267 -0
  46. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/types.js +1 -0
  47. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/validate.js +122 -0
  48. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/callback-call-sites.js +62 -0
  49. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/gap-audit/build.js +1291 -0
  50. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/gap-audit/index.js +1 -0
  51. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/area-ownership.js +392 -0
  52. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/build.js +1995 -0
  53. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/callback-flow.js +277 -0
  54. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/component-variants.js +725 -0
  55. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/index.js +15 -0
  56. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/names/index.js +12 -0
  57. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/names/policy.js +469 -0
  58. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/names/traceability.js +468 -0
  59. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/req-model/build.js +1066 -0
  60. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/req-model/index.js +1 -0
  61. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/android-interaction-registry.js +174 -0
  62. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/android.js +1062 -0
  63. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/area-titles.js +678 -0
  64. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/builder.js +350 -0
  65. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/compose-api-registry.js +395 -0
  66. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/compose-call-sites.js +2008 -0
  67. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/compose.js +9059 -0
  68. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/dynamic-repeated-call-sites.js +529 -0
  69. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/index.js +1 -0
  70. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/interaction-effects.js +1149 -0
  71. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/jvm.js +6272 -0
  72. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/normalize.js +33 -0
  73. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/pending-intents.js +565 -0
  74. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/resource-id.js +42 -0
  75. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/route-state-variants.js +781 -0
  76. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/state.js +234 -0
  77. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/types.js +1 -0
  78. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/extract.js +40 -0
  79. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/index.js +6 -0
  80. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/manifest.js +513 -0
  81. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/resources.js +398 -0
  82. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/types.js +1 -0
  83. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/views.js +447 -0
  84. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/xml-parser.js +175 -0
  85. package/skills/a2h-app-req-gen/scripts/reqgen/src/cli.js +398 -0
  86. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/area-graph.js +1 -0
  87. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/artifact.js +1 -0
  88. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/common.js +16 -0
  89. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/diagnostic.js +10 -0
  90. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/gap-audit.js +14 -0
  91. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/index.js +11 -0
  92. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/name-traceability.js +26 -0
  93. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/raw-fact.js +40 -0
  94. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/req-model.js +1 -0
  95. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/run.js +19 -0
  96. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/semantic-completeness.js +11 -0
  97. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/source-facts.js +1 -0
  98. package/skills/a2h-app-req-gen/scripts/reqgen/src/core/output-guard.js +41 -0
  99. package/skills/a2h-app-req-gen/scripts/reqgen/src/core/stable-id.js +92 -0
  100. package/skills/a2h-app-req-gen/scripts/reqgen/src/dashboard-cli.js +169 -0
  101. package/skills/a2h-app-req-gen/scripts/reqgen/src/diagnostics.js +104 -0
  102. package/skills/a2h-app-req-gen/scripts/reqgen/src/dump/dump.js +1028 -0
  103. package/skills/a2h-app-req-gen/scripts/reqgen/src/dump/index.js +1 -0
  104. package/skills/a2h-app-req-gen/scripts/reqgen/src/dump-cli.js +251 -0
  105. package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/artifacts.js +49 -0
  106. package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/index.js +18 -0
  107. package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/quality-report.js +125 -0
  108. package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/render.js +353 -0
  109. package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/types.js +2 -0
  110. package/skills/a2h-app-req-gen/scripts/reqgen/src/graph/paths.js +474 -0
  111. package/skills/a2h-app-req-gen/scripts/reqgen/src/jvm/index.js +15 -0
  112. package/skills/a2h-app-req-gen/scripts/reqgen/src/jvm/protocol.js +408 -0
  113. package/skills/a2h-app-req-gen/scripts/reqgen/src/jvm/runner.js +452 -0
  114. package/skills/a2h-app-req-gen/scripts/reqgen/src/pipeline/index.js +8 -0
  115. package/skills/a2h-app-req-gen/scripts/reqgen/src/pipeline/output.js +165 -0
  116. package/skills/a2h-app-req-gen/scripts/reqgen/src/pipeline/run-stage1.js +891 -0
  117. package/skills/a2h-app-req-gen/scripts/reqgen/src/project/discovery.js +299 -0
  118. package/skills/a2h-app-req-gen/scripts/reqgen/src/project/gradle-model.js +358 -0
  119. package/skills/a2h-app-req-gen/scripts/reqgen/src/project/gradle-runner.js +165 -0
  120. package/skills/a2h-app-req-gen/scripts/reqgen/src/project/resource-profile.js +60 -0
  121. package/skills/a2h-app-req-gen/scripts/reqgen/src/project/types.js +1 -0
  122. package/skills/a2h-app-req-gen/scripts/reqgen/src/spec-handoff-cli.js +151 -0
  123. package/skills/a2h-app-req-gen/scripts/reqgen/src/spec-handoff.js +1004 -0
  124. package/skills/a2h-app-req-gen/scripts/reqgen/src/stage2-cli.js +109 -0
  125. package/skills/a2h-app-req-gen/scripts/reqgen/src/stage2-input.js +419 -0
  126. package/skills/a2h-app-req-gen/scripts/reqgen/src/stage2-name-trace-input.js +95 -0
  127. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/area-graph.js +444 -0
  128. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/index.js +7 -0
  129. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/req-model.js +580 -0
  130. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/req-text.js +195 -0
  131. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/semantic-area-graph.js +1431 -0
  132. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/types.js +1 -0
  133. package/skills/a2h-app-req-gen/scripts/reqgen-audit.mjs +298 -0
  134. package/skills/a2h-app-req-gen/scripts/reqgen-selfcheck.mjs +555 -0
  135. package/skills/a2h-code-review/SKILL.md +379 -0
  136. package/skills/{hmos-integration-test → a2h-integration-test}/README.md +6 -6
  137. package/skills/{hmos-integration-test → a2h-integration-test}/SKILL.md +6 -6
  138. package/skills/{hmos-integration-test → a2h-integration-test}/scripts/report-tool.mjs +1 -1
  139. package/skills/a2h-spec-design/SKILL.md +330 -0
  140. package/skills/a2h-spec-design/scripts/inspect_spec_design_inputs.mjs +207 -0
  141. package/skills/a2h-spec-design/scripts/move_spec_technical_reference.mjs +198 -0
  142. package/skills/a2h-spec-design/scripts/validate_spec_design_output.mjs +256 -0
  143. package/skills/a2h-spec-generate/SKILL.md +696 -0
  144. package/skills/a2h-spec-generate/references/trace-template.md +115 -0
  145. package/skills/a2h-spec-generate/scripts/parse_requirements.mjs +537 -0
  146. package/skills/a2h-spec-generate/template/REQ.txt +22 -0
  147. package/skills/a2h-spec-generate/template/REQ.xlsx +0 -0
  148. package/skills/a2h-spec-implement/SKILL.md +548 -0
  149. package/skills/a2h-spec-implement/references/harmony-pitfalls.md +186 -0
  150. package/skills/a2h-spec-implement/references/l1-unit-test.md +185 -0
  151. package/skills/a2h-spec-implement/references/l3-e2e-uitest.md +167 -0
  152. package/skills/a2h-spec-implement/references/test-core.md +132 -0
  153. package/skills/a2h-spec-implement/rules/arkts/arkts-standards.md +184 -0
  154. package/skills/a2h-spec-implement/rules/arkts/conventions/coding-style.md +96 -0
  155. package/skills/a2h-spec-implement/rules/arkts/conventions/security.md +126 -0
  156. package/skills/a2h-spec-implement/rules/arkts/language/arkts-rules.md +650 -0
  157. package/skills/a2h-spec-implement/rules/arkts/language/arkui-structure-rules.md +173 -0
  158. package/skills/a2h-spec-implement/rules/arkts/language/ts-to-arkts-rewrites.md +58 -0
  159. 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
  160. 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
  161. 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
  162. 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
  163. 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
  164. 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
  165. 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
  166. 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
  167. 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
  168. 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
  169. 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
  170. 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
  171. 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
  172. 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
  173. 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
  174. 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
  175. 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
  176. 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
  177. 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
  178. 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
  179. 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
  180. 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
  181. 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
  182. 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
  183. 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
  184. 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
  185. 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
  186. 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
  187. 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
  188. 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
  189. package/skills/a2h-spec-implement/rules/arkts/ui/component-cookbook.md +431 -0
  190. package/skills/a2h-spec-implement/rules/arkts/ui/state-management.md +152 -0
  191. package/skills/a2h-spec-implement/rules/arkts/ui/ui-quality.md +67 -0
  192. package/skills/a2h-spec-implement/rules/arkts/ui/ui-runtime-diagnosis.md +32 -0
  193. package/skills/a2h-spec-implement/rules/pipeline/build/package-set-collection.md +43 -0
  194. package/skills/a2h-spec-implement/scripts/_lib/common.mjs +97 -0
  195. package/skills/a2h-spec-implement/scripts/_lib/config.mjs +70 -0
  196. package/skills/a2h-spec-implement/scripts/_lib/lessons_cli.mjs +48 -0
  197. package/skills/a2h-spec-implement/scripts/build_lessons.mjs +24 -0
  198. package/skills/a2h-spec-implement/scripts/code_pattern_lessons.mjs +26 -0
  199. package/skills/a2h-spec-implement/scripts/device_ui.mjs +433 -0
  200. package/skills/a2h-spec-implement/scripts/device_ui_core.mjs +895 -0
  201. package/skills/a2h-spec-implement/scripts/ledger/aggregate_run.mjs +207 -0
  202. package/skills/a2h-spec-implement/scripts/manifest/emit_summary_line.mjs +37 -0
  203. package/skills/a2h-spec-implement/scripts/manifest/finalize_run.mjs +115 -0
  204. package/skills/a2h-spec-implement/scripts/manifest/render_final_summary.mjs +78 -0
  205. package/skills/a2h-spec-implement/scripts/manifest/root_mirror.mjs +137 -0
  206. package/skills/a2h-spec-implement/scripts/package.json +12 -0
  207. package/skills/a2h-spec-implement/scripts/state_propagation_check.mjs +676 -0
  208. package/skills/a2h-spec-implement/scripts/test/l2_run.mjs +244 -0
  209. package/skills/a2h-spec-implement/scripts/test/tp_coverage.mjs +231 -0
  210. package/skills/a2h-spec-implement/scripts/ui_memory.mjs +161 -0
  211. package/skills/a2h-spec-implement/scripts/utils/ensure_app.mjs +134 -0
  212. package/skills/a2h-ui-transfer/SKILL.md +20 -9
  213. package/skills/a2h-ui-transfer/scripts/arkts_static_check.js +80 -0
  214. package/skills/hmos-convert-pipeline/SKILL.md +7 -5
  215. package/skills/hmos-incremental-ui-align/README.md +7 -7
  216. package/skills/hmos-incremental-ui-align/SKILL.md +1 -1
  217. package/skills/hmos-incremental-ui-align/page_align.md +1 -1
  218. package/skills/hmos-spec-generate/SKILL.md +2 -2
  219. package/skills/hmos-spec-generate/scripts/parse_requirements.mjs +537 -0
  220. package/skills/hmos-fix-build-errors/SKILL.md +0 -266
  221. package/skills/hmos-fix-build-errors/references/arkts-strict-patterns.md +0 -219
  222. package/skills/hmos-fix-build-errors/references/known-patterns.md +0 -157
  223. package/skills/hmos-fix-build-errors/references/rdb-entity-pattern.md +0 -131
  224. package/skills/hmos-spec-generate/scripts/parse_requirements.ts +0 -515
  225. /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/resolve-metadata-tool.mjs +0 -0
  226. /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/self-test-runner.mjs +0 -0
  227. /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();