create-yss-spec 2.1.3 → 2.1.5

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 (296) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/template/.agents/skills/code-review/SKILL.md +38 -10
  4. package/template/.agents/skills/high-fidelity-html-prototype/SKILL.md +16 -12
  5. package/template/.agents/skills/product-design-prototype/SKILL.md +6 -2
  6. package/template/.agents/skills/prototype-review/SKILL.md +6 -2
  7. package/template/.agents/skills/wireframe-prototype/SKILL.md +5 -1
  8. package/template/.agents/skills/yss-api-integration/SKILL.md +25 -7
  9. package/template/.agents/skills/yss-ddd-scaffold-generator/SKILL.md +3 -17
  10. package/template/.agents/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +0 -30
  11. package/template/.agents/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +1 -1
  12. package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.py +1 -21
  13. package/template/.agents/skills/yss-design-system/SKILL.md +6 -5
  14. package/template/.agents/skills/yss-frontend-scaffold-generator/SKILL.md +12 -6
  15. package/template/.agents/skills/yss-openapi-draft-review/SKILL.md +13 -17
  16. package/template/.agents/skills/yss-openapi-governance/SKILL.md +89 -114
  17. package/template/.agents/skills/yss-openapi-governance/agents/openai.yaml +2 -2
  18. package/template/.agents/skills/yss-product-lifecycle/SKILL.md +13 -7
  19. package/template/.agents/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
  20. package/template/.agents/skills/yss-product-lifecycle/references/matt-yss-adapter.md +8 -2
  21. package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +77 -30
  22. package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +7 -2
  23. package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +8 -0
  24. package/template/.agents/skills/yss-prototype-stage/SKILL.md +40 -0
  25. package/template/.agents/skills/yss-router/SKILL.md +2 -2
  26. package/template/.agents/skills/yss-router/references/router-contract.yaml +9 -8
  27. package/template/.agents/skills/yss-router/references/slice-implementation-contract.md +4 -1
  28. package/template/.agents/skills/yss-router/references/yss-skill-execution-result.md +2 -1
  29. package/template/.claude/skills/code-review/SKILL.md +38 -10
  30. package/template/.claude/skills/high-fidelity-html-prototype/SKILL.md +16 -12
  31. package/template/.claude/skills/product-design-prototype/SKILL.md +6 -2
  32. package/template/.claude/skills/prototype-review/SKILL.md +6 -2
  33. package/template/.claude/skills/wireframe-prototype/SKILL.md +5 -1
  34. package/template/.claude/skills/yss-api-integration/SKILL.md +25 -7
  35. package/template/.claude/skills/yss-ddd-scaffold-generator/SKILL.md +3 -17
  36. package/template/.claude/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +0 -30
  37. package/template/.claude/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +1 -1
  38. package/template/.claude/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.py +1 -21
  39. package/template/.claude/skills/yss-design-system/SKILL.md +6 -5
  40. package/template/.claude/skills/yss-frontend-scaffold-generator/SKILL.md +12 -6
  41. package/template/.claude/skills/yss-openapi-draft-review/SKILL.md +13 -17
  42. package/template/.claude/skills/yss-openapi-governance/SKILL.md +89 -114
  43. package/template/.claude/skills/yss-openapi-governance/agents/openai.yaml +2 -2
  44. package/template/.claude/skills/yss-product-lifecycle/SKILL.md +13 -7
  45. package/template/.claude/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
  46. package/template/.claude/skills/yss-product-lifecycle/references/matt-yss-adapter.md +8 -2
  47. package/template/.claude/skills/yss-product-lifecycle/references/orchestration-contract.yaml +77 -30
  48. package/template/.claude/skills/yss-product-lifecycle/references/orchestration.md +7 -2
  49. package/template/.claude/skills/yss-product-lifecycle/references/state-model.md +8 -0
  50. package/template/.claude/skills/yss-prototype-stage/SKILL.md +40 -0
  51. package/template/.claude/skills/yss-router/SKILL.md +2 -2
  52. package/template/.claude/skills/yss-router/references/router-contract.yaml +9 -8
  53. package/template/.claude/skills/yss-router/references/slice-implementation-contract.md +4 -1
  54. package/template/.claude/skills/yss-router/references/yss-skill-execution-result.md +2 -1
  55. package/template/.codex/skills/code-review/SKILL.md +38 -10
  56. package/template/.codex/skills/high-fidelity-html-prototype/SKILL.md +16 -12
  57. package/template/.codex/skills/product-design-prototype/SKILL.md +6 -2
  58. package/template/.codex/skills/prototype-review/SKILL.md +6 -2
  59. package/template/.codex/skills/wireframe-prototype/SKILL.md +5 -1
  60. package/template/.codex/skills/yss-api-integration/SKILL.md +25 -7
  61. package/template/.codex/skills/yss-ddd-scaffold-generator/SKILL.md +3 -17
  62. package/template/.codex/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +0 -30
  63. package/template/.codex/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +1 -1
  64. package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.py +1 -21
  65. package/template/.codex/skills/yss-design-system/SKILL.md +6 -5
  66. package/template/.codex/skills/yss-frontend-scaffold-generator/SKILL.md +12 -6
  67. package/template/.codex/skills/yss-openapi-draft-review/SKILL.md +13 -17
  68. package/template/.codex/skills/yss-openapi-governance/SKILL.md +89 -114
  69. package/template/.codex/skills/yss-openapi-governance/agents/openai.yaml +2 -2
  70. package/template/.codex/skills/yss-product-lifecycle/SKILL.md +13 -7
  71. package/template/.codex/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
  72. package/template/.codex/skills/yss-product-lifecycle/references/matt-yss-adapter.md +8 -2
  73. package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +77 -30
  74. package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +7 -2
  75. package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +8 -0
  76. package/template/.codex/skills/yss-prototype-stage/SKILL.md +40 -0
  77. package/template/.codex/skills/yss-router/SKILL.md +2 -2
  78. package/template/.codex/skills/yss-router/references/router-contract.yaml +9 -8
  79. package/template/.codex/skills/yss-router/references/slice-implementation-contract.md +4 -1
  80. package/template/.codex/skills/yss-router/references/yss-skill-execution-result.md +2 -1
  81. package/template/.hermes/skills/code-review/SKILL.md +38 -10
  82. package/template/.hermes/skills/high-fidelity-html-prototype/SKILL.md +16 -12
  83. package/template/.hermes/skills/product-design-prototype/SKILL.md +6 -2
  84. package/template/.hermes/skills/prototype-review/SKILL.md +6 -2
  85. package/template/.hermes/skills/wireframe-prototype/SKILL.md +5 -1
  86. package/template/.hermes/skills/yss-api-integration/SKILL.md +25 -7
  87. package/template/.hermes/skills/yss-ddd-scaffold-generator/SKILL.md +3 -17
  88. package/template/.hermes/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +0 -30
  89. package/template/.hermes/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +1 -1
  90. package/template/.hermes/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.py +1 -21
  91. package/template/.hermes/skills/yss-design-system/SKILL.md +6 -5
  92. package/template/.hermes/skills/yss-frontend-scaffold-generator/SKILL.md +12 -6
  93. package/template/.hermes/skills/yss-openapi-draft-review/SKILL.md +13 -17
  94. package/template/.hermes/skills/yss-openapi-governance/SKILL.md +89 -114
  95. package/template/.hermes/skills/yss-openapi-governance/agents/openai.yaml +2 -2
  96. package/template/.hermes/skills/yss-product-lifecycle/SKILL.md +13 -7
  97. package/template/.hermes/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
  98. package/template/.hermes/skills/yss-product-lifecycle/references/matt-yss-adapter.md +8 -2
  99. package/template/.hermes/skills/yss-product-lifecycle/references/orchestration-contract.yaml +77 -30
  100. package/template/.hermes/skills/yss-product-lifecycle/references/orchestration.md +7 -2
  101. package/template/.hermes/skills/yss-product-lifecycle/references/state-model.md +8 -0
  102. package/template/.hermes/skills/yss-prototype-stage/SKILL.md +40 -0
  103. package/template/.hermes/skills/yss-router/SKILL.md +2 -2
  104. package/template/.hermes/skills/yss-router/references/router-contract.yaml +9 -8
  105. package/template/.hermes/skills/yss-router/references/slice-implementation-contract.md +4 -1
  106. package/template/.hermes/skills/yss-router/references/yss-skill-execution-result.md +2 -1
  107. package/template/.pi/skills/code-review/SKILL.md +38 -10
  108. package/template/.pi/skills/high-fidelity-html-prototype/SKILL.md +16 -12
  109. package/template/.pi/skills/product-design-prototype/SKILL.md +6 -2
  110. package/template/.pi/skills/prototype-review/SKILL.md +6 -2
  111. package/template/.pi/skills/wireframe-prototype/SKILL.md +5 -1
  112. package/template/.pi/skills/yss-api-integration/SKILL.md +25 -7
  113. package/template/.pi/skills/yss-ddd-scaffold-generator/SKILL.md +3 -17
  114. package/template/.pi/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +0 -30
  115. package/template/.pi/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +1 -1
  116. package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.py +1 -21
  117. package/template/.pi/skills/yss-design-system/SKILL.md +6 -5
  118. package/template/.pi/skills/yss-frontend-scaffold-generator/SKILL.md +12 -6
  119. package/template/.pi/skills/yss-openapi-draft-review/SKILL.md +13 -17
  120. package/template/.pi/skills/yss-openapi-governance/SKILL.md +89 -114
  121. package/template/.pi/skills/yss-openapi-governance/agents/openai.yaml +2 -2
  122. package/template/.pi/skills/yss-product-lifecycle/SKILL.md +13 -7
  123. package/template/.pi/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
  124. package/template/.pi/skills/yss-product-lifecycle/references/matt-yss-adapter.md +8 -2
  125. package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +77 -30
  126. package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +7 -2
  127. package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +8 -0
  128. package/template/.pi/skills/yss-prototype-stage/SKILL.md +40 -0
  129. package/template/.pi/skills/yss-router/SKILL.md +2 -2
  130. package/template/.pi/skills/yss-router/references/router-contract.yaml +9 -8
  131. package/template/.pi/skills/yss-router/references/slice-implementation-contract.md +4 -1
  132. package/template/.pi/skills/yss-router/references/yss-skill-execution-result.md +2 -1
  133. package/template/.qoder/skills/code-review/SKILL.md +38 -10
  134. package/template/.qoder/skills/high-fidelity-html-prototype/SKILL.md +16 -12
  135. package/template/.qoder/skills/product-design-prototype/SKILL.md +6 -2
  136. package/template/.qoder/skills/prototype-review/SKILL.md +6 -2
  137. package/template/.qoder/skills/wireframe-prototype/SKILL.md +5 -1
  138. package/template/.qoder/skills/yss-api-integration/SKILL.md +25 -7
  139. package/template/.qoder/skills/yss-ddd-scaffold-generator/SKILL.md +3 -17
  140. package/template/.qoder/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +0 -30
  141. package/template/.qoder/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +1 -1
  142. package/template/.qoder/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.py +1 -21
  143. package/template/.qoder/skills/yss-design-system/SKILL.md +6 -5
  144. package/template/.qoder/skills/yss-frontend-scaffold-generator/SKILL.md +12 -6
  145. package/template/.qoder/skills/yss-openapi-draft-review/SKILL.md +13 -17
  146. package/template/.qoder/skills/yss-openapi-governance/SKILL.md +89 -114
  147. package/template/.qoder/skills/yss-openapi-governance/agents/openai.yaml +2 -2
  148. package/template/.qoder/skills/yss-product-lifecycle/SKILL.md +13 -7
  149. package/template/.qoder/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
  150. package/template/.qoder/skills/yss-product-lifecycle/references/matt-yss-adapter.md +8 -2
  151. package/template/.qoder/skills/yss-product-lifecycle/references/orchestration-contract.yaml +77 -30
  152. package/template/.qoder/skills/yss-product-lifecycle/references/orchestration.md +7 -2
  153. package/template/.qoder/skills/yss-product-lifecycle/references/state-model.md +8 -0
  154. package/template/.qoder/skills/yss-prototype-stage/SKILL.md +40 -0
  155. package/template/.qoder/skills/yss-router/SKILL.md +2 -2
  156. package/template/.qoder/skills/yss-router/references/router-contract.yaml +9 -8
  157. package/template/.qoder/skills/yss-router/references/slice-implementation-contract.md +4 -1
  158. package/template/.qoder/skills/yss-router/references/yss-skill-execution-result.md +2 -1
  159. package/template/.trae/skills/code-review/SKILL.md +38 -10
  160. package/template/.trae/skills/high-fidelity-html-prototype/SKILL.md +16 -12
  161. package/template/.trae/skills/product-design-prototype/SKILL.md +6 -2
  162. package/template/.trae/skills/prototype-review/SKILL.md +6 -2
  163. package/template/.trae/skills/wireframe-prototype/SKILL.md +5 -1
  164. package/template/.trae/skills/yss-api-integration/SKILL.md +25 -7
  165. package/template/.trae/skills/yss-ddd-scaffold-generator/SKILL.md +3 -17
  166. package/template/.trae/skills/yss-ddd-scaffold-generator/assets/templates/pom/bootstrap-pom.xml.template +0 -30
  167. package/template/.trae/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +1 -1
  168. package/template/.trae/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.py +1 -21
  169. package/template/.trae/skills/yss-design-system/SKILL.md +6 -5
  170. package/template/.trae/skills/yss-frontend-scaffold-generator/SKILL.md +12 -6
  171. package/template/.trae/skills/yss-openapi-draft-review/SKILL.md +13 -17
  172. package/template/.trae/skills/yss-openapi-governance/SKILL.md +89 -114
  173. package/template/.trae/skills/yss-openapi-governance/agents/openai.yaml +2 -2
  174. package/template/.trae/skills/yss-product-lifecycle/SKILL.md +13 -7
  175. package/template/.trae/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
  176. package/template/.trae/skills/yss-product-lifecycle/references/matt-yss-adapter.md +8 -2
  177. package/template/.trae/skills/yss-product-lifecycle/references/orchestration-contract.yaml +77 -30
  178. package/template/.trae/skills/yss-product-lifecycle/references/orchestration.md +7 -2
  179. package/template/.trae/skills/yss-product-lifecycle/references/state-model.md +8 -0
  180. package/template/.trae/skills/yss-prototype-stage/SKILL.md +40 -0
  181. package/template/.trae/skills/yss-router/SKILL.md +2 -2
  182. package/template/.trae/skills/yss-router/references/router-contract.yaml +9 -8
  183. package/template/.trae/skills/yss-router/references/slice-implementation-contract.md +4 -1
  184. package/template/.trae/skills/yss-router/references/yss-skill-execution-result.md +2 -1
  185. package/template/AGENTS.md +7 -5
  186. package/template/CONTEXT.md +15 -1
  187. package/template/README.md +2 -1
  188. package/template/__yss_dotfile__.gitignore +4 -0
  189. package/template/docs/adr/0001-product-design-prototype-entrypoint.md +1 -1
  190. package/template/docs/adr/0003-machine-readable-lifecycle-registry.md +5 -0
  191. package/template/docs/adr/0004-split-lifecycle-and-router-registry-domains.md +5 -0
  192. package/template/docs/adr/0005-cross-repository-ecosystem-release-manifest.md +5 -0
  193. package/template/docs/adr/0006-stable-lifecycle-ids-and-generated-structure.md +5 -0
  194. package/template/docs/adr/0007-separate-skill-routing-registry-from-integrity-lock.md +5 -0
  195. package/template/docs/agents/issue-tracker.md +1 -0
  196. package/template/docs/api/templates/openapi-draft-review-checklist.md +8 -6
  197. package/template/docs/api/templates/openapi-freeze-record-template.md +8 -2
  198. package/template/docs/api/templates/openapi-json-export-record-template.md +60 -0
  199. package/template/docs/architecture/templates/architecture-review-checklist.md +6 -19
  200. package/template/docs/architecture/templates/functional-architecture-template.md +4 -4
  201. package/template/docs/architecture/templates/system-overview-design-template.md +10 -5
  202. package/template/docs/architecture/templates/tech-design-template.md +4 -11
  203. package/template/docs/design/README.md +4 -2
  204. package/template/docs/design/design.md +4 -0
  205. package/template/docs/design/templates/interaction-spec-template.md +3 -3
  206. package/template/docs/design/templates/prototype-confirmation-template.md +9 -6
  207. package/template/docs/design/templates/prototype-evidence-template.yaml +46 -0
  208. package/template/docs/design/templates/prototype-review-checklist.md +4 -4
  209. package/template/docs/discovery/templates/discovery-template.md +0 -1
  210. package/template/docs/implementation/create-yss-spec-repository-mode-contract.md +2 -2
  211. package/template/docs/process/harness-process-tailoring.md +2 -0
  212. package/template/docs/process/harness-work-unit-map.md +14 -10
  213. package/template/docs/process/lifecycle-artifact-map.md +84 -39
  214. package/template/docs/process/lifecycle-registry-baseline.json +70 -0
  215. package/template/docs/process/lifecycle-registry.yaml +289 -0
  216. package/template/docs/process/schemas/lifecycle-registry.schema.json +35 -0
  217. package/template/docs/reviews/lifecycle-registry-phase0-1-red-green-2026-08-17.md +61 -0
  218. package/template/docs/reviews/matt-yss-p0-ticket-review-git-red-green-2026-08-17.md +91 -0
  219. package/template/docs/reviews/openapi-skill-primary-source-research-2026-08-16.md +76 -0
  220. package/template/docs/reviews/openapi-yaml-first-independent-review-2026-08-16.md +121 -0
  221. package/template/docs/reviews/openapi-yaml-first-pressure-scenarios-2026-08-16.md +191 -0
  222. package/template/docs/reviews/openapi-yaml-first-red-green-2026-08-16.md +76 -0
  223. package/template/docs/reviews/openapi-yaml-json-output-independent-review-2026-08-16.md +30 -0
  224. package/template/docs/reviews/openapi-yaml-json-output-scope-red-green-2026-08-16.md +154 -0
  225. package/template/docs/reviews/research-create-yss-spec-refresh-2026-08-16.md +164 -0
  226. package/template/docs/reviews/security-permission-lifecycle-simplification-red-green-2026-08-16.md +43 -0
  227. package/template/docs/reviews/template-release-review-remediation-red-green-2026-08-17.md +41 -0
  228. package/template/docs/templates/agent-brief-template.md +2 -2
  229. package/template/docs/templates/build-architecture-checklist-template.md +3 -4
  230. package/template/docs/templates/implementation-repo-registry-template.md +0 -2
  231. package/template/docs/templates/implementation-routing-template.md +6 -6
  232. package/template/docs/templates/openapi-spec-template.yaml +0 -6
  233. package/template/docs/templates/review-report-template.md +2 -1
  234. package/template/docs/templates/spec-delta-template.md +1 -1
  235. package/template/docs/templates/spec-template.md +0 -1
  236. package/template/docs/templates/vertical-slice-ticket-template.md +2 -2
  237. package/template/docs/user-guide//344/272/247/345/223/201/347/224/237/345/221/275/345/221/250/346/234/237/345/267/245/344/275/234/346/265/201.md +6 -6
  238. package/template/docs/user-guide//344/272/247/345/223/201/347/240/224/345/217/221/345/205/250/347/224/237/345/221/275/345/221/250/346/234/237/346/234/200/344/275/263/345/256/236/350/267/265.md +3 -3
  239. package/template/docs/user-guide//345/244/226/351/203/250/345/221/275/344/273/244/350/241/214/345/267/245/345/205/267/345/256/236/350/267/265/346/214/207/345/215/227.md +1 -1
  240. package/template/docs/user-guide//347/224/237/345/221/275/345/221/250/346/234/237/346/234/200/344/275/263/345/256/236/350/267/265.md +1 -1
  241. package/template/scripts/generate-lifecycle-artifacts +26 -0
  242. package/template/scripts/lifecycle-registry.rb +194 -0
  243. package/template/scripts/sync-skills +1 -0
  244. package/template/scripts/test-export-yss-skills.rb +10 -6
  245. package/template/scripts/test-lifecycle-registry.rb +74 -0
  246. package/template/scripts/update-skill-lock +1 -0
  247. package/template/scripts/verify-governance-release +39 -0
  248. package/template/scripts/verify-lifecycle-registry +67 -0
  249. package/template/scripts/verify-lifecycle-scenarios +193 -23
  250. package/template/scripts/verify-matt-yss-integration-scenarios +297 -1
  251. package/template/scripts/verify-openapi-json-handoff-scenarios +270 -0
  252. package/template/scripts/verify-openapi-yaml-first-scenarios +120 -0
  253. package/template/scripts/verify-prototype-backend-scaffold-scenarios +26 -4
  254. package/template/scripts/verify-template +27 -3
  255. package/template/scripts/verify-yss-router-scenarios +51 -14
  256. package/template/skills-lock.json +25 -25
  257. package/template/wiki/raw/skills-lock.json +0 -15
  258. package/template/wiki/wiki/OpenAPI/345/245/221/347/272/246.md +1 -1
  259. package/template/wiki/wiki/YSS/345/267/245/347/250/213/346/212/200/350/203/275/344/275/223/347/263/273.md +1 -1
  260. package/template/yss-public-skills.json +5 -5
  261. package/template.snapshot.json +4 -4
  262. package/template/.agents/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template +0 -17
  263. package/template/.agents/skills/yss-openapi/SKILL.md +0 -116
  264. package/template/.agents/skills/yss-openapi/agents/openai.yaml +0 -4
  265. package/template/.agents/skills/yss-openapi/img.png +0 -0
  266. package/template/.agents/skills/yss-openapi/img_1.png +0 -0
  267. package/template/.claude/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template +0 -17
  268. package/template/.claude/skills/yss-openapi/SKILL.md +0 -116
  269. package/template/.claude/skills/yss-openapi/agents/openai.yaml +0 -4
  270. package/template/.claude/skills/yss-openapi/img.png +0 -0
  271. package/template/.claude/skills/yss-openapi/img_1.png +0 -0
  272. package/template/.codex/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template +0 -17
  273. package/template/.codex/skills/yss-openapi/SKILL.md +0 -116
  274. package/template/.codex/skills/yss-openapi/agents/openai.yaml +0 -4
  275. package/template/.codex/skills/yss-openapi/img.png +0 -0
  276. package/template/.codex/skills/yss-openapi/img_1.png +0 -0
  277. package/template/.hermes/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template +0 -17
  278. package/template/.hermes/skills/yss-openapi/SKILL.md +0 -116
  279. package/template/.hermes/skills/yss-openapi/agents/openai.yaml +0 -4
  280. package/template/.hermes/skills/yss-openapi/img.png +0 -0
  281. package/template/.hermes/skills/yss-openapi/img_1.png +0 -0
  282. package/template/.pi/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template +0 -17
  283. package/template/.pi/skills/yss-openapi/SKILL.md +0 -116
  284. package/template/.pi/skills/yss-openapi/agents/openai.yaml +0 -4
  285. package/template/.pi/skills/yss-openapi/img.png +0 -0
  286. package/template/.pi/skills/yss-openapi/img_1.png +0 -0
  287. package/template/.qoder/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template +0 -17
  288. package/template/.qoder/skills/yss-openapi/SKILL.md +0 -116
  289. package/template/.qoder/skills/yss-openapi/agents/openai.yaml +0 -4
  290. package/template/.qoder/skills/yss-openapi/img.png +0 -0
  291. package/template/.qoder/skills/yss-openapi/img_1.png +0 -0
  292. package/template/.trae/skills/yss-ddd-scaffold-generator/assets/templates/config/smart-doc.json.template +0 -17
  293. package/template/.trae/skills/yss-openapi/SKILL.md +0 -116
  294. package/template/.trae/skills/yss-openapi/agents/openai.yaml +0 -4
  295. package/template/.trae/skills/yss-openapi/img.png +0 -0
  296. package/template/.trae/skills/yss-openapi/img_1.png +0 -0
@@ -3,6 +3,7 @@
3
3
 
4
4
  require "pathname"
5
5
  require "yaml"
6
+ require "digest"
6
7
 
7
8
  ROOT = Pathname.new(__dir__).join("..").realpath
8
9
 
@@ -76,6 +77,46 @@ def flow_decision(definition, repository_mode:, flow:)
76
77
  {"result" => "allowed", "reason" => nil}
77
78
  end
78
79
 
80
+ def git_operation_authorized?(definition, facts)
81
+ return false unless definition["requires_explicit_user_authorization"] == true
82
+ return false unless definition["authorized_value"] == true
83
+
84
+ required = definition.fetch("required", [])
85
+ return false unless required.all? { |field| facts.key?(field) }
86
+
87
+ authorization_fields = required.grep(/_authorized\z/)
88
+ return false unless authorization_fields.all? { |field| facts[field] == definition["authorized_value"] }
89
+
90
+ definition.fetch("non_empty", []).all? { |field| !facts.fetch(field, "").to_s.empty? }
91
+ end
92
+
93
+ def uint64be(value)
94
+ [value].pack("Q>")
95
+ end
96
+
97
+ def uint32be(value)
98
+ [value].pack("N")
99
+ end
100
+
101
+ def canonical_worktree_candidate_stream(snapshot, tracked_diff:, untracked:)
102
+ header = snapshot.fetch("header_ascii").b + [Integer(snapshot.fetch("terminator_byte"))].pack("C")
103
+ tracked = snapshot.fetch("tracked_record")
104
+ stream = header + [Integer(tracked.fetch("type_byte"))].pack("C") + uint64be(tracked_diff.bytesize) + tracked_diff
105
+ kinds = snapshot.fetch("untracked_record").fetch("entry_kinds")
106
+
107
+ untracked.sort_by { |entry| entry.fetch(:path).b }.each do |entry|
108
+ path = entry.fetch(:path).b
109
+ content = entry.fetch(:content).b
110
+ kind = entry.fetch(:kind).to_s
111
+ stream << [Integer(snapshot.fetch("untracked_record").fetch("type_byte"))].pack("C")
112
+ stream << uint64be(path.bytesize) << path
113
+ stream << uint32be(entry.fetch(:mode))
114
+ stream << [Integer(kinds.fetch(kind))].pack("C")
115
+ stream << uint64be(content.bytesize) << content
116
+ end
117
+ stream
118
+ end
119
+
79
120
  entry = contract.fetch("entry_routing", {})
80
121
  require_equal(failures, "entry manifest", entry["manifest"], "yss-project.yaml")
81
122
  require_equal(failures, "entry valid modes", entry["valid_modes"], %w[template-source project-instance])
@@ -196,6 +237,222 @@ ready_required = contract.dig("ready_for_agent", "required") || []
196
237
  (preconditions.dig("implement", "required") || []).reject { |field| field == "ready_for_agent" }.each do |field|
197
238
  require_include(failures, "ready-for-agent 与 implement 前置条件", ready_required, field)
198
239
  end
240
+
241
+ ticket_role_transition = contract.fetch("ticket_role_transition", {})
242
+ require_equal(
243
+ failures,
244
+ "YSS to-tickets initial role",
245
+ ticket_role_transition.dig("yss_active", "to_tickets_initial_role"),
246
+ "ready-for-human"
247
+ )
248
+ require_equal(
249
+ failures,
250
+ "YSS ticket promotion owner",
251
+ ticket_role_transition.dig("yss_active", "promotion_owner"),
252
+ "yss-product-lifecycle"
253
+ )
254
+ require_equal(
255
+ failures,
256
+ "YSS ticket promoted role",
257
+ ticket_role_transition.dig("yss_active", "promoted_role"),
258
+ "ready-for-agent"
259
+ )
260
+ require_equal(
261
+ failures,
262
+ "YSS ticket promotion condition",
263
+ ticket_role_transition.dig("yss_active", "promotion_requires"),
264
+ "ready_for_agent"
265
+ )
266
+
267
+ review_input = contract.fetch("review_input", {})
268
+ require_equal(failures, "review input schema", review_input["schema_version"], 1)
269
+ %w[
270
+ review_mode
271
+ review_base_ref
272
+ implementation_candidate_ref
273
+ candidate_snapshot_ref
274
+ candidate_digest
275
+ spec_ref
276
+ ticket_ref
277
+ slice_contract_ref
278
+ build_architecture_checklist_ref
279
+ yss_execution_result_refs
280
+ ].each do |field|
281
+ require_include(failures, "review input required fields", review_input["required"] || [], field)
282
+ end
283
+ %w[committed worktree].each do |mode|
284
+ require_include(failures, "review modes", review_input["modes"] || [], mode)
285
+ end
286
+ %w[committed staged unstaged untracked].each do |candidate_part|
287
+ require_include(
288
+ failures,
289
+ "worktree review coverage",
290
+ review_input.dig("worktree", "required_coverage") || [],
291
+ candidate_part
292
+ )
293
+ end
294
+ require_equal(
295
+ failures,
296
+ "worktree untracked inventory",
297
+ review_input.dig("worktree", "untracked_inventory_command"),
298
+ "git ls-files -z --others --exclude-standard"
299
+ )
300
+ require_equal(
301
+ failures,
302
+ "worktree tracked binary diff",
303
+ review_input.dig("worktree", "tracked_diff_command"),
304
+ "git diff --no-ext-diff --binary --full-index <merge-base>"
305
+ )
306
+ require_equal(
307
+ failures,
308
+ "worktree untracked binary diff",
309
+ review_input.dig("worktree", "untracked_diff_command"),
310
+ "git diff --no-index --no-ext-diff --binary --full-index -- /dev/null <path>"
311
+ )
312
+ require_equal(failures, "worktree capture once", review_input.dig("worktree", "capture_once"), true)
313
+ require_equal(failures, "worktree verify unchanged", review_input.dig("worktree", "verify_unchanged_after_review"), true)
314
+ require_equal(failures, "review snapshot immutable", review_input.dig("snapshot", "immutable"), true)
315
+ require_equal(failures, "review digest algorithm", review_input.dig("snapshot", "digest_algorithm"), "sha256")
316
+ require_equal(
317
+ failures,
318
+ "review digest framing",
319
+ review_input.dig("snapshot", "digest_framing"),
320
+ "length-prefixed-path-and-bytes"
321
+ )
322
+ require_equal(failures, "review digest path order", review_input.dig("snapshot", "path_order"), "bytewise")
323
+ require_equal(failures, "review digest change action", review_input.dig("snapshot", "on_digest_change"), "blocked")
324
+ require_equal(failures, "review completion digest guard", review_input.dig("snapshot", "completion_requires_current_digest"), true)
325
+ require_equal(failures, "review snapshot protocol", review_input.dig("snapshot", "protocol"), "yss-worktree-candidate-v1")
326
+ require_equal(failures, "review snapshot path encoding", review_input.dig("snapshot", "path_encoding"), "raw-bytes")
327
+ require_equal(failures, "review snapshot length encoding", review_input.dig("snapshot", "length_encoding"), "uint64-big-endian")
328
+ require_equal(failures, "review snapshot header", review_input.dig("snapshot", "header_ascii"), "YSS-WORKTREE-CANDIDATE-V1")
329
+ require_equal(failures, "review snapshot terminator", review_input.dig("snapshot", "terminator_byte"), "0x00")
330
+ require_equal(failures, "review snapshot tracked record", review_input.dig("snapshot", "tracked_record", "type_byte"), "0x54")
331
+ require_equal(failures, "review snapshot untracked record", review_input.dig("snapshot", "untracked_record", "type_byte"), "0x55")
332
+ require_equal(failures, "review snapshot unsupported entry action", review_input.dig("snapshot", "unsupported_entry_action"), "blocked")
333
+ require_equal(
334
+ failures,
335
+ "review snapshot tracked fields",
336
+ review_input.dig("snapshot", "tracked_record", "fields"),
337
+ %w[type_byte uint64be_content_length binary_diff_bytes]
338
+ )
339
+ require_equal(
340
+ failures,
341
+ "review snapshot untracked fields",
342
+ review_input.dig("snapshot", "untracked_record", "fields"),
343
+ %w[type_byte uint64be_path_length raw_path_bytes uint32be_file_mode entry_kind_byte uint64be_content_length content_bytes]
344
+ )
345
+ require_equal(
346
+ failures,
347
+ "review snapshot entry kinds",
348
+ review_input.dig("snapshot", "untracked_record", "entry_kinds"),
349
+ {"regular" => "0x52", "symlink" => "0x4c"}
350
+ )
351
+ fixture_tracked_diff = "COMMITTED\\nSTAGED\\nUNSTAGED\\nDELETE\\nMODE-CHANGE\\n".b
352
+ fixture_untracked = [
353
+ {path: "z\\nname".b, mode: 0o100644, kind: :regular, content: "untracked-bytes\\n".b},
354
+ {path: "a-link".b, mode: 0o120777, kind: :symlink, content: "target\\nname".b}
355
+ ]
356
+ fixture_stream = canonical_worktree_candidate_stream(
357
+ review_input.fetch("snapshot"),
358
+ tracked_diff: fixture_tracked_diff,
359
+ untracked: fixture_untracked
360
+ )
361
+ require_equal(
362
+ failures,
363
+ "review snapshot canonical digest",
364
+ Digest::SHA256.hexdigest(fixture_stream),
365
+ "cc1b30990cc0d2a11721140cd94cb92a275f9dd24c6663e21ad7e5ea47898b7d"
366
+ )
367
+ reordered_stream = canonical_worktree_candidate_stream(
368
+ review_input.fetch("snapshot"),
369
+ tracked_diff: fixture_tracked_diff,
370
+ untracked: fixture_untracked.reverse
371
+ )
372
+ require_equal(failures, "review snapshot bytewise ordering", reordered_stream, fixture_stream)
373
+ review_return_digest = Digest::SHA256.hexdigest(fixture_stream)
374
+ completion_digest = Digest::SHA256.hexdigest(fixture_stream)
375
+ require_equal(failures, "review return digest guard", review_return_digest, Digest::SHA256.hexdigest(fixture_stream))
376
+ require_equal(failures, "review completion digest guard behavior", completion_digest, Digest::SHA256.hexdigest(fixture_stream))
377
+ drift_stream = canonical_worktree_candidate_stream(
378
+ review_input.fetch("snapshot"),
379
+ tracked_diff: fixture_tracked_diff,
380
+ untracked: fixture_untracked.map { |entry| entry.merge(content: entry[:content] + "changed".b) }
381
+ )
382
+ failures << "review return digest drift 未阻断" if Digest::SHA256.hexdigest(drift_stream) == review_return_digest
383
+ failures << "review completion digest drift 未阻断" if Digest::SHA256.hexdigest(drift_stream) == completion_digest
384
+ %w[merge_base tracked_diff_command untracked_inventory_command untracked_diff_command untracked_files commit_list_command].each do |field|
385
+ require_include(
386
+ failures,
387
+ "worktree manifest required fields",
388
+ review_input.dig("manifest_required_by_mode", "worktree") || [],
389
+ field
390
+ )
391
+ end
392
+ %w[merge_base tracked_diff_command commit_list_command].each do |field|
393
+ require_include(
394
+ failures,
395
+ "committed manifest required fields",
396
+ review_input.dig("manifest_required_by_mode", "committed") || [],
397
+ field
398
+ )
399
+ end
400
+
401
+ git_authorization = contract.fetch("git_authorization", {})
402
+ {
403
+ "commit" => %w[commit_authorized commit_scope commit_authorization_ref],
404
+ "push" => %w[push_authorized push_scope push_authorization_ref]
405
+ }.each do |operation, required_fields|
406
+ require_equal(
407
+ failures,
408
+ "#{operation} explicit authorization",
409
+ git_authorization.dig(operation, "requires_explicit_user_authorization"),
410
+ true
411
+ )
412
+ required_fields.each do |field|
413
+ require_include(failures, "#{operation} authorization fields", git_authorization.dig(operation, "required") || [], field)
414
+ end
415
+ require_equal(
416
+ failures,
417
+ "#{operation} authorization value",
418
+ git_authorization.dig(operation, "authorized_value"),
419
+ true
420
+ )
421
+ required_fields.reject { |field| field.end_with?("_authorized") }.each do |field|
422
+ require_include(
423
+ failures,
424
+ "#{operation} non-empty authorization fields",
425
+ git_authorization.dig(operation, "non_empty") || [],
426
+ field
427
+ )
428
+ end
429
+ require_equal(
430
+ failures,
431
+ "#{operation} unauthorized action",
432
+ git_authorization.dig(operation, "unauthorized_action"),
433
+ "checkpoint-only"
434
+ )
435
+
436
+ definition = git_authorization.fetch(operation, {})
437
+ valid_facts = required_fields.to_h do |field|
438
+ [field, field.end_with?("_authorized") ? true : "explicit-value"]
439
+ end
440
+ allowed = git_operation_authorized?(definition, valid_facts)
441
+ failures << "#{operation} 完整显式授权被错误阻断" unless allowed
442
+
443
+ false_authorization = valid_facts.merge("#{operation}_authorized" => false)
444
+ false_allowed = git_operation_authorized?(definition, false_authorization)
445
+ failures << "#{operation} authorized=false 未阻断" if false_allowed
446
+
447
+ required_fields.reject { |field| field.end_with?("_authorized") }.each do |field|
448
+ blank = valid_facts.merge(field => "")
449
+ blank_allowed = git_operation_authorized?(definition, blank)
450
+ failures << "#{operation} #{field} 为空时未阻断" if blank_allowed
451
+ end
452
+
453
+ false_contract = definition.merge("authorized_value" => false)
454
+ failures << "#{operation} authorized_value=false 未阻断" if git_operation_authorized?(false_contract, valid_facts)
455
+ end
199
456
  {
200
457
  "to-spec" => %w[user_problem mvp_and_non_goals success_criteria test_seam terminology_review]
201
458
  }.each do |flow, required|
@@ -447,6 +704,7 @@ end
447
704
 
448
705
  adapter = read(ROOT, ".agents/skills/yss-product-lifecycle/references/matt-yss-adapter.md")
449
706
  to_spec = read(ROOT, ".agents/skills/to-spec/SKILL.md")
707
+ code_review = read(ROOT, ".agents/skills/code-review/SKILL.md")
450
708
  require_markers(
451
709
  failures,
452
710
  "Matt to-spec core contract",
@@ -466,17 +724,55 @@ require_markers(
466
724
  "发布提示由本适配层覆盖为 `ready-for-human`",
467
725
  "to-spec",
468
726
  "to-tickets",
727
+ "YSS active 的 `to-tickets`",
728
+ "`review_mode`",
729
+ "`commit_authorized`",
730
+ "`push_authorized`",
469
731
  "Release",
470
732
  "Retrospective"
471
733
  ]
472
734
  )
735
+ require_markers(
736
+ failures,
737
+ "Matt code-review candidate modes",
738
+ code_review,
739
+ [
740
+ "Committed candidate",
741
+ "Worktree candidate",
742
+ "git diff --no-ext-diff --binary --full-index <merge-base>",
743
+ "git diff --no-index --no-ext-diff --binary --full-index -- /dev/null <path>",
744
+ "git ls-files -z --others --exclude-standard",
745
+ "staged",
746
+ "unstaged",
747
+ "untracked",
748
+ "candidate manifest",
749
+ "captured candidate",
750
+ "yss-worktree-candidate-v1",
751
+ "unsigned 64-bit big-endian",
752
+ "candidate_digest",
753
+ "stale candidate",
754
+ "immutable",
755
+ "length-prefixed",
756
+ "bytewise path order",
757
+ "return `blocked`"
758
+ ]
759
+ )
473
760
 
474
761
  lifecycle_skill = read(ROOT, ".agents/skills/yss-product-lifecycle/SKILL.md")
475
762
  require_markers(
476
763
  failures,
477
764
  "lifecycle template-source guard",
478
765
  lifecycle_skill,
479
- ["template-source", "必须返回 `blocked`", "template-source-product-artifact-forbidden", "YSS active 调用 `to-spec` 时", "ready-for-agent` 发布提示归一化为 Spec `ready-for-human`"]
766
+ [
767
+ "template-source",
768
+ "必须返回 `blocked`",
769
+ "template-source-product-artifact-forbidden",
770
+ "YSS active 调用 `to-spec` 时",
771
+ "ready-for-agent` 发布提示归一化为 Spec `ready-for-human`",
772
+ "YSS active 调用 `to-tickets` 时",
773
+ "`commit_authorized`",
774
+ "`push_authorized`"
775
+ ]
480
776
  )
481
777
  orchestration = read(ROOT, ".agents/skills/yss-product-lifecycle/references/orchestration.md")
482
778
  require_markers(
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "digest"
5
+ require "json"
6
+ require "tmpdir"
7
+ require "yaml"
8
+
9
+ # This validates the YAML-to-JSON handoff contract rather than invoking
10
+ # Redocly or any frontend generator. These fixtures prove that a JSON
11
+ # derivative preserves the frozen OpenAPI semantics and can be handed to an
12
+ # implementation repository as unchanged bytes.
13
+ module OpenapiJsonHandoff
14
+ module_function
15
+
16
+ def validate!(source_yaml:, source_json:, materialized_json:, handoff:, **_unused)
17
+ validate_source_paths!(handoff)
18
+ assert!(handoff.fetch("freeze_status") == "approved", "OpenAPI Freeze 未批准")
19
+ assert!(handoff.fetch("export_status") == "exported", "JSON 导出未完成")
20
+ assert!(!handoff.fetch("freeze_record").to_s.empty?, "缺少 OpenAPI Freeze 记录")
21
+ assert!(!handoff.fetch("export_record").to_s.empty?, "缺少 OpenAPI JSON 派生记录")
22
+
23
+ yaml_documents = YAML.load_stream(File.binread(source_yaml).force_encoding("UTF-8"))
24
+ assert!(yaml_documents.length == 1, "YAML 必须是单一 document")
25
+ assert!(yaml_documents.first.is_a?(Hash) && yaml_documents.first["openapi"] == "3.1.0", "YAML 必须是 OAS 3.1")
26
+
27
+ json_document = JSON.parse(File.binread(source_json))
28
+ assert!(json_document["openapi"] == "3.1.0", "JSON 必须是 OAS 3.1")
29
+ validate_operation_contract!(yaml_documents.first, json_document)
30
+ validate_json_refs!(json_document)
31
+ assert!(Digest::SHA256.file(source_yaml).hexdigest == handoff.fetch("yaml_sha256"), "YAML SHA-256 不匹配")
32
+ assert!(Digest::SHA256.file(source_json).hexdigest == handoff.fetch("json_sha256"), "JSON SHA-256 不匹配")
33
+ assert!(Digest::SHA256.file(materialized_json).hexdigest == handoff.fetch("json_sha256"), "前端物化 JSON 与导出记录不一致")
34
+
35
+ bundle_command = handoff.fetch("bundle_command")
36
+ %w[pnpm\ exec\ redocly\ bundle --ext\ json --component-renaming-conflicts-severity=error].each do |marker|
37
+ assert!(bundle_command.include?(marker), "bundle 命令缺少 #{marker}")
38
+ end
39
+ assert!(bundle_command.include?(handoff.fetch("source_yaml")), "bundle 命令未使用冻结 YAML")
40
+ assert!(bundle_command.include?(handoff.fetch("source_json")), "bundle 命令未写入记录的 JSON")
41
+ assert!(!bundle_command.match?(/(?:\bmvn\b|smart[ _-]?doc)/i), "bundle 命令包含已移除的 Maven OpenAPI 链路")
42
+
43
+ end
44
+
45
+ def validate_operation_contract!(yaml_document, json_document)
46
+ yaml_operations = operation_contracts(yaml_document)
47
+ json_operations = operation_contracts(json_document)
48
+ assert!(!yaml_operations.empty?, "YAML 缺少可生成客户端的操作")
49
+ assert!(yaml_operations == json_operations, "JSON 未保留 YAML 的路径、方法、operationId、请求或响应契约")
50
+ ids = yaml_operations.values.map { |contract| contract.fetch("operationId") }
51
+ assert!(ids.all? { |operation_id| operation_id.is_a?(String) && !operation_id.empty? }, "operationId 必须为非空字符串")
52
+ assert!(ids.uniq.length == ids.length, "operationId 必须唯一")
53
+ end
54
+
55
+ def operation_contracts(document)
56
+ methods = %w[get put post delete options head patch trace]
57
+ paths = document.fetch("paths", {})
58
+ paths.each_with_object({}) do |(path, path_item), operations|
59
+ next unless path_item.is_a?(Hash)
60
+
61
+ path_item.each do |method, operation|
62
+ next unless methods.include?(method.to_s.downcase) && operation.is_a?(Hash)
63
+
64
+ operations["#{method.to_s.upcase} #{path}"] = {
65
+ "operationId" => operation["operationId"],
66
+ "parameters" => operation.fetch("parameters", []),
67
+ "requestBody" => operation["requestBody"],
68
+ "responses" => operation.fetch("responses", {})
69
+ }
70
+ end
71
+ end
72
+ end
73
+
74
+ def validate_json_refs!(document)
75
+ refs = []
76
+ collect_refs(document, refs)
77
+ refs.each do |ref|
78
+ assert!(ref.start_with?("#/"), "JSON 不得包含外部或远程 $ref: #{ref}")
79
+ resolve_json_pointer!(document, ref)
80
+ end
81
+ end
82
+
83
+ def collect_refs(value, refs)
84
+ case value
85
+ when Hash
86
+ refs << value["$ref"] if value["$ref"].is_a?(String)
87
+ value.each_value { |child| collect_refs(child, refs) }
88
+ when Array
89
+ value.each { |child| collect_refs(child, refs) }
90
+ end
91
+ end
92
+
93
+ def resolve_json_pointer!(document, ref)
94
+ ref.delete_prefix("#/").split("/").reduce(document) do |node, segment|
95
+ key = segment.gsub("~1", "/").gsub("~0", "~")
96
+ assert!(node.is_a?(Hash) && node.key?(key), "JSON $ref 无法解析: #{ref}")
97
+ node.fetch(key)
98
+ end
99
+ end
100
+
101
+ def validate_source_paths!(handoff)
102
+ yaml_path = handoff.fetch("source_yaml")
103
+ json_path = handoff.fetch("source_json")
104
+ match = %r{\Adocs/\.scratch/([^/]+)/api/([^/]+)\.yaml\z}.match(yaml_path)
105
+ assert!(!match.nil? && match[1] == match[2], "冻结 YAML 路径不符合 feature 约定")
106
+ assert!(json_path == "docs/.scratch/#{match[1]}/api/#{match[2]}.json", "JSON 必须与冻结 YAML 同 feature 同目录")
107
+ end
108
+
109
+ def assert!(condition, message)
110
+ raise ArgumentError, message unless condition
111
+ end
112
+ end
113
+
114
+ def build_context(root)
115
+ feature = "order"
116
+ api_dir = File.join(root, "docs", ".scratch", feature, "api")
117
+ frontend_dir = File.join(root, "frontend", "openapi")
118
+ Dir.mkdir(File.join(root, "docs"))
119
+ Dir.mkdir(File.join(root, "docs", ".scratch"))
120
+ Dir.mkdir(File.join(root, "docs", ".scratch", feature))
121
+ Dir.mkdir(api_dir)
122
+ Dir.mkdir(File.join(root, "frontend"))
123
+ Dir.mkdir(frontend_dir)
124
+
125
+ source_yaml = File.join(api_dir, "#{feature}.yaml")
126
+ source_json = File.join(api_dir, "#{feature}.json")
127
+ materialized_json = File.join(frontend_dir, "openapi.json")
128
+ File.write(source_yaml, <<~YAML)
129
+ openapi: 3.1.0
130
+ info:
131
+ title: Order API
132
+ version: 1.0.0
133
+ paths:
134
+ /api/v1/orders/{orderId}:
135
+ get:
136
+ operationId: getOrder
137
+ parameters:
138
+ - name: orderId
139
+ in: path
140
+ required: true
141
+ schema:
142
+ type: string
143
+ responses:
144
+ '200':
145
+ description: success
146
+ content:
147
+ application/json:
148
+ schema:
149
+ $ref: '#/components/schemas/Order'
150
+ components:
151
+ schemas:
152
+ Order:
153
+ type: object
154
+ required: [id]
155
+ properties:
156
+ id:
157
+ type: string
158
+ YAML
159
+ json_document = {
160
+ "openapi" => "3.1.0",
161
+ "info" => { "title" => "Order API", "version" => "1.0.0" },
162
+ "paths" => {
163
+ "/api/v1/orders/{orderId}" => {
164
+ "get" => {
165
+ "operationId" => "getOrder",
166
+ "parameters" => [{ "name" => "orderId", "in" => "path", "required" => true, "schema" => { "type" => "string" } }],
167
+ "responses" => {
168
+ "200" => {
169
+ "description" => "success",
170
+ "content" => { "application/json" => { "schema" => { "$ref" => "#/components/schemas/Order" } } }
171
+ }
172
+ }
173
+ }
174
+ }
175
+ },
176
+ "components" => {
177
+ "schemas" => {
178
+ "Order" => {
179
+ "type" => "object",
180
+ "required" => ["id"],
181
+ "properties" => { "id" => { "type" => "string" } }
182
+ }
183
+ }
184
+ }
185
+ }
186
+ File.write(source_json, JSON.generate(json_document))
187
+ File.binwrite(materialized_json, File.binread(source_json))
188
+
189
+ source_yaml_path = "docs/.scratch/#{feature}/api/#{feature}.yaml"
190
+ source_json_path = "docs/.scratch/#{feature}/api/#{feature}.json"
191
+ handoff = {
192
+ "freeze_status" => "approved",
193
+ "export_status" => "exported",
194
+ "freeze_record" => "docs/.scratch/#{feature}/api/#{feature}-freeze.md",
195
+ "export_record" => "docs/.scratch/#{feature}/api/#{feature}-json-export.md",
196
+ "source_yaml" => source_yaml_path,
197
+ "source_json" => source_json_path,
198
+ "yaml_sha256" => Digest::SHA256.file(source_yaml).hexdigest,
199
+ "json_sha256" => Digest::SHA256.file(source_json).hexdigest,
200
+ "bundle_command" => "pnpm exec redocly bundle #{source_yaml_path} --output #{source_json_path} --ext json --component-renaming-conflicts-severity=error"
201
+ }
202
+ {
203
+ source_yaml: source_yaml,
204
+ source_json: source_json,
205
+ materialized_json: materialized_json,
206
+ handoff: handoff
207
+ }
208
+ end
209
+
210
+ def expect_rejected(label)
211
+ yield
212
+ raise "负向场景未被阻断: #{label}"
213
+ rescue ArgumentError
214
+ true
215
+ end
216
+
217
+ Dir.mktmpdir("openapi-handoff-") do |root|
218
+ context = build_context(root)
219
+ OpenapiJsonHandoff.validate!(**context)
220
+
221
+ expect_rejected("Draft 不能导出") do
222
+ OpenapiJsonHandoff.validate!(**context.merge(handoff: context.fetch(:handoff).merge("freeze_status" => "draft")))
223
+ end
224
+
225
+ expect_rejected("Maven 链路不能作为导出命令") do
226
+ OpenapiJsonHandoff.validate!(**context.merge(handoff: context.fetch(:handoff).merge("bundle_command" => "mvn package")))
227
+ end
228
+
229
+ expect_rejected("运行时 URL 不能替代治理 JSON") do
230
+ OpenapiJsonHandoff.validate!(**context.merge(handoff: context.fetch(:handoff).merge("source_json" => "https://example.invalid/openapi.json")))
231
+ end
232
+
233
+ original_json = File.binread(context.fetch(:source_json))
234
+
235
+ changed_operation = JSON.parse(original_json)
236
+ changed_operation.fetch("paths").fetch("/api/v1/orders/{orderId}").fetch("get")["operationId"] = "getOtherOrder"
237
+ File.write(context.fetch(:source_json), JSON.generate(changed_operation))
238
+ expect_rejected("JSON 必须保留冻结 YAML 的 operationId") do
239
+ OpenapiJsonHandoff.validate!(**context.merge(handoff: context.fetch(:handoff).merge("json_sha256" => Digest::SHA256.file(context.fetch(:source_json)).hexdigest)))
240
+ end
241
+
242
+ invalid_ref = JSON.parse(original_json)
243
+ invalid_ref.fetch("paths").fetch("/api/v1/orders/{orderId}").fetch("get").fetch("responses").fetch("200").fetch("content").fetch("application/json").fetch("schema")["$ref"] = "#/components/schemas/Missing"
244
+ File.write(context.fetch(:source_json), JSON.generate(invalid_ref))
245
+ expect_rejected("JSON $ref 必须可解析") do
246
+ OpenapiJsonHandoff.validate!(**context.merge(handoff: context.fetch(:handoff).merge("json_sha256" => Digest::SHA256.file(context.fetch(:source_json)).hexdigest)))
247
+ end
248
+
249
+ changed_response = JSON.parse(original_json)
250
+ changed_response.fetch("paths").fetch("/api/v1/orders/{orderId}").fetch("get").fetch("responses").fetch("200").fetch("content").fetch("application/json").fetch("schema")["$ref"] = "#/components/schemas/ChangedOrder"
251
+ changed_response.fetch("components").fetch("schemas")["ChangedOrder"] = { "type" => "string" }
252
+ File.write(context.fetch(:source_json), JSON.generate(changed_response))
253
+ expect_rejected("JSON 必须保留冻结 YAML 的请求和响应契约") do
254
+ OpenapiJsonHandoff.validate!(**context.merge(handoff: context.fetch(:handoff).merge("json_sha256" => Digest::SHA256.file(context.fetch(:source_json)).hexdigest)))
255
+ end
256
+
257
+ File.binwrite(context.fetch(:source_json), original_json)
258
+ File.write(context.fetch(:materialized_json), "{\"openapi\":\"3.1.0\",\"paths\":{\"/tampered\":{}}}")
259
+ expect_rejected("前端 JSON 被手改") do
260
+ OpenapiJsonHandoff.validate!(**context)
261
+ end
262
+
263
+ File.binwrite(context.fetch(:materialized_json), original_json)
264
+ File.write(context.fetch(:source_json), "{\"openapi\":\"3.1.0\",\"paths\":{\"/tampered-source\":{}}}")
265
+ expect_rejected("治理 JSON 被手改") do
266
+ OpenapiJsonHandoff.validate!(**context)
267
+ end
268
+ end
269
+
270
+ puts "OpenAPI YAML-first JSON handoff scenarios passed"