create-yss-spec 2.2.10 → 3.0.0

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 (381) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/template/.agents/skills/ask-matt/SKILL.md +1 -1
  4. package/template/.agents/skills/wayfinder/SKILL.md +2 -2
  5. package/template/.agents/skills/yss-ddd-scaffold-generator/SKILL.md +14 -3
  6. package/template/.agents/skills/yss-ddd-scaffold-generator/references/ARCHITECTURE.md +3 -3
  7. package/template/.agents/skills/yss-ddd-scaffold-generator/references/USAGE_EXAMPLES.md +12 -1
  8. package/template/.agents/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +2 -1
  9. package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +45 -11
  10. package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +164 -0
  11. package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +18 -4
  12. package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +172 -0
  13. package/template/.agents/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +14 -1
  14. package/template/.agents/skills/yss-domain/SKILL.md +2 -1
  15. package/template/.agents/skills/yss-domain/agents/openai.yaml +2 -2
  16. package/template/.agents/skills/yss-domain/references/domain-layer-guide.md +2 -0
  17. package/template/.agents/skills/yss-dto/SKILL.md +6 -0
  18. package/template/.agents/skills/yss-dto/references/openapi-wire-profile.yaml +6 -0
  19. package/template/.agents/skills/yss-product-lifecycle/SKILL.md +1 -1
  20. package/template/.agents/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
  21. package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -10
  22. package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  23. package/template/.agents/skills/yss-repository/SKILL.md +15 -13
  24. package/template/.agents/skills/yss-repository/agents/openai.yaml +2 -2
  25. package/template/.agents/skills/yss-repository/references/infrastructure-layer-guide.md +1 -1
  26. package/template/.agents/skills/yss-research/SKILL.md +73 -0
  27. package/template/.agents/skills/yss-research/agents/openai.yaml +3 -0
  28. package/template/.agents/skills/yss-research/assets/evidence-template.yaml +68 -0
  29. package/template/.agents/skills/yss-research/assets/research-brief-template.md +36 -0
  30. package/template/.agents/skills/yss-research/references/evidence-contract.md +42 -0
  31. package/template/.agents/skills/yss-research/scripts/validate-research-package.mjs +128 -0
  32. package/template/.agents/skills/yss-research/tests/run-scenarios.mjs +81 -0
  33. package/template/.agents/skills/yss-router/references/router-contract.yaml +7 -3
  34. package/template/.agents/skills/yss-stage-decision/SKILL.md +2 -1
  35. package/template/.agents/skills/yss-web-controller/SKILL.md +16 -10
  36. package/template/.agents/skills/yss-web-controller/agents/openai.yaml +2 -2
  37. package/template/.agents/skills/yss-web-controller/assets/templates/Controller.java.template +11 -11
  38. package/template/.agents/skills/yss-web-controller/assets/templates/WebConvertor.java.template +4 -7
  39. package/template/.agents/skills/yss-web-controller/assets/templates/web/dto/CreateRequest.java.template +2 -2
  40. package/template/.agents/skills/yss-web-controller/assets/templates/web/dto/PageRequest.java.template +4 -7
  41. package/template/.agents/skills/yss-web-controller/assets/templates/web/dto/UpdateRequest.java.template +2 -2
  42. package/template/.agents/skills/yss-web-controller/references/web-adapter-layer-guide.md +6 -4
  43. package/template/.agents/skills/yss-web-controller/references/web-generation-contract.schema.json +97 -0
  44. package/template/.agents/skills/yss-web-controller/scripts/generate_controller.mjs +141 -20
  45. package/template/.agents/skills/yss-web-controller/scripts/generate_controller.test.mjs +131 -35
  46. package/template/.claude/skills/ask-matt/SKILL.md +1 -1
  47. package/template/.claude/skills/wayfinder/SKILL.md +2 -2
  48. package/template/.claude/skills/yss-ddd-scaffold-generator/SKILL.md +14 -3
  49. package/template/.claude/skills/yss-ddd-scaffold-generator/references/ARCHITECTURE.md +3 -3
  50. package/template/.claude/skills/yss-ddd-scaffold-generator/references/USAGE_EXAMPLES.md +12 -1
  51. package/template/.claude/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +2 -1
  52. package/template/.claude/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +45 -11
  53. package/template/.claude/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +164 -0
  54. package/template/.claude/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +18 -4
  55. package/template/.claude/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +172 -0
  56. package/template/.claude/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +14 -1
  57. package/template/.claude/skills/yss-domain/SKILL.md +2 -1
  58. package/template/.claude/skills/yss-domain/agents/openai.yaml +2 -2
  59. package/template/.claude/skills/yss-domain/references/domain-layer-guide.md +2 -0
  60. package/template/.claude/skills/yss-dto/SKILL.md +6 -0
  61. package/template/.claude/skills/yss-dto/references/openapi-wire-profile.yaml +6 -0
  62. package/template/.claude/skills/yss-product-lifecycle/SKILL.md +1 -1
  63. package/template/.claude/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
  64. package/template/.claude/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -10
  65. package/template/.claude/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  66. package/template/.claude/skills/yss-repository/SKILL.md +15 -13
  67. package/template/.claude/skills/yss-repository/agents/openai.yaml +2 -2
  68. package/template/.claude/skills/yss-repository/references/infrastructure-layer-guide.md +1 -1
  69. package/template/.claude/skills/yss-research/SKILL.md +73 -0
  70. package/template/.claude/skills/yss-research/agents/openai.yaml +3 -0
  71. package/template/.claude/skills/yss-research/assets/evidence-template.yaml +68 -0
  72. package/template/.claude/skills/yss-research/assets/research-brief-template.md +36 -0
  73. package/template/.claude/skills/yss-research/references/evidence-contract.md +42 -0
  74. package/template/.claude/skills/yss-research/scripts/validate-research-package.mjs +128 -0
  75. package/template/.claude/skills/yss-research/tests/run-scenarios.mjs +81 -0
  76. package/template/.claude/skills/yss-router/references/router-contract.yaml +7 -3
  77. package/template/.claude/skills/yss-stage-decision/SKILL.md +2 -1
  78. package/template/.claude/skills/yss-web-controller/SKILL.md +16 -10
  79. package/template/.claude/skills/yss-web-controller/agents/openai.yaml +2 -2
  80. package/template/.claude/skills/yss-web-controller/assets/templates/Controller.java.template +11 -11
  81. package/template/.claude/skills/yss-web-controller/assets/templates/WebConvertor.java.template +4 -7
  82. package/template/.claude/skills/yss-web-controller/assets/templates/web/dto/CreateRequest.java.template +2 -2
  83. package/template/.claude/skills/yss-web-controller/assets/templates/web/dto/PageRequest.java.template +4 -7
  84. package/template/.claude/skills/yss-web-controller/assets/templates/web/dto/UpdateRequest.java.template +2 -2
  85. package/template/.claude/skills/yss-web-controller/references/web-adapter-layer-guide.md +6 -4
  86. package/template/.claude/skills/yss-web-controller/references/web-generation-contract.schema.json +97 -0
  87. package/template/.claude/skills/yss-web-controller/scripts/generate_controller.mjs +141 -20
  88. package/template/.claude/skills/yss-web-controller/scripts/generate_controller.test.mjs +131 -35
  89. package/template/.codex/skills/ask-matt/SKILL.md +1 -1
  90. package/template/.codex/skills/product-design/package.json +4 -2
  91. package/template/.codex/skills/product-design/skills/research/SKILL.md +22 -0
  92. package/template/.codex/skills/product-design/skills/research/assets/evidence-template.yaml +65 -0
  93. package/template/.codex/skills/product-design/skills/research/assets/research-brief-template.md +33 -0
  94. package/template/.codex/skills/product-design/skills/research/references/evidence-audited.md +80 -0
  95. package/template/.codex/skills/product-design/skills/research/scripts/validate-research-package.mjs +149 -0
  96. package/template/.codex/skills/product-design/skills/research/tests/run-scenarios.mjs +72 -0
  97. package/template/.codex/skills/wayfinder/SKILL.md +2 -2
  98. package/template/.codex/skills/yss-ddd-scaffold-generator/SKILL.md +14 -3
  99. package/template/.codex/skills/yss-ddd-scaffold-generator/references/ARCHITECTURE.md +3 -3
  100. package/template/.codex/skills/yss-ddd-scaffold-generator/references/USAGE_EXAMPLES.md +12 -1
  101. package/template/.codex/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +2 -1
  102. package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +45 -11
  103. package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +164 -0
  104. package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +18 -4
  105. package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +172 -0
  106. package/template/.codex/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +14 -1
  107. package/template/.codex/skills/yss-domain/SKILL.md +2 -1
  108. package/template/.codex/skills/yss-domain/agents/openai.yaml +2 -2
  109. package/template/.codex/skills/yss-domain/references/domain-layer-guide.md +2 -0
  110. package/template/.codex/skills/yss-dto/SKILL.md +6 -0
  111. package/template/.codex/skills/yss-dto/references/openapi-wire-profile.yaml +6 -0
  112. package/template/.codex/skills/yss-product-lifecycle/SKILL.md +1 -1
  113. package/template/.codex/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
  114. package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -10
  115. package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  116. package/template/.codex/skills/yss-repository/SKILL.md +15 -13
  117. package/template/.codex/skills/yss-repository/agents/openai.yaml +2 -2
  118. package/template/.codex/skills/yss-repository/references/infrastructure-layer-guide.md +1 -1
  119. package/template/.codex/skills/yss-research/SKILL.md +73 -0
  120. package/template/.codex/skills/yss-research/agents/openai.yaml +3 -0
  121. package/template/.codex/skills/yss-research/assets/evidence-template.yaml +68 -0
  122. package/template/.codex/skills/yss-research/assets/research-brief-template.md +36 -0
  123. package/template/.codex/skills/yss-research/references/evidence-contract.md +42 -0
  124. package/template/.codex/skills/yss-research/scripts/validate-research-package.mjs +128 -0
  125. package/template/.codex/skills/yss-research/tests/run-scenarios.mjs +81 -0
  126. package/template/.codex/skills/yss-router/references/router-contract.yaml +7 -3
  127. package/template/.codex/skills/yss-stage-decision/SKILL.md +2 -1
  128. package/template/.codex/skills/yss-web-controller/SKILL.md +16 -10
  129. package/template/.codex/skills/yss-web-controller/agents/openai.yaml +2 -2
  130. package/template/.codex/skills/yss-web-controller/assets/templates/Controller.java.template +11 -11
  131. package/template/.codex/skills/yss-web-controller/assets/templates/WebConvertor.java.template +4 -7
  132. package/template/.codex/skills/yss-web-controller/assets/templates/web/dto/CreateRequest.java.template +2 -2
  133. package/template/.codex/skills/yss-web-controller/assets/templates/web/dto/PageRequest.java.template +4 -7
  134. package/template/.codex/skills/yss-web-controller/assets/templates/web/dto/UpdateRequest.java.template +2 -2
  135. package/template/.codex/skills/yss-web-controller/references/web-adapter-layer-guide.md +6 -4
  136. package/template/.codex/skills/yss-web-controller/references/web-generation-contract.schema.json +97 -0
  137. package/template/.codex/skills/yss-web-controller/scripts/generate_controller.mjs +141 -20
  138. package/template/.codex/skills/yss-web-controller/scripts/generate_controller.test.mjs +131 -35
  139. package/template/.cursor/skills/ask-matt/SKILL.md +1 -1
  140. package/template/.cursor/skills/wayfinder/SKILL.md +2 -2
  141. package/template/.cursor/skills/yss-ddd-scaffold-generator/SKILL.md +14 -3
  142. package/template/.cursor/skills/yss-ddd-scaffold-generator/references/ARCHITECTURE.md +3 -3
  143. package/template/.cursor/skills/yss-ddd-scaffold-generator/references/USAGE_EXAMPLES.md +12 -1
  144. package/template/.cursor/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +2 -1
  145. package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +45 -11
  146. package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +164 -0
  147. package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +18 -4
  148. package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +172 -0
  149. package/template/.cursor/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +14 -1
  150. package/template/.cursor/skills/yss-domain/SKILL.md +2 -1
  151. package/template/.cursor/skills/yss-domain/agents/openai.yaml +2 -2
  152. package/template/.cursor/skills/yss-domain/references/domain-layer-guide.md +2 -0
  153. package/template/.cursor/skills/yss-dto/SKILL.md +6 -0
  154. package/template/.cursor/skills/yss-dto/references/openapi-wire-profile.yaml +6 -0
  155. package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +1 -1
  156. package/template/.cursor/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
  157. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -10
  158. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  159. package/template/.cursor/skills/yss-repository/SKILL.md +15 -13
  160. package/template/.cursor/skills/yss-repository/agents/openai.yaml +2 -2
  161. package/template/.cursor/skills/yss-repository/references/infrastructure-layer-guide.md +1 -1
  162. package/template/.cursor/skills/yss-research/SKILL.md +73 -0
  163. package/template/.cursor/skills/yss-research/agents/openai.yaml +3 -0
  164. package/template/.cursor/skills/yss-research/assets/evidence-template.yaml +68 -0
  165. package/template/.cursor/skills/yss-research/assets/research-brief-template.md +36 -0
  166. package/template/.cursor/skills/yss-research/references/evidence-contract.md +42 -0
  167. package/template/.cursor/skills/yss-research/scripts/validate-research-package.mjs +128 -0
  168. package/template/.cursor/skills/yss-research/tests/run-scenarios.mjs +81 -0
  169. package/template/.cursor/skills/yss-router/references/router-contract.yaml +7 -3
  170. package/template/.cursor/skills/yss-stage-decision/SKILL.md +2 -1
  171. package/template/.cursor/skills/yss-web-controller/SKILL.md +16 -10
  172. package/template/.cursor/skills/yss-web-controller/agents/openai.yaml +2 -2
  173. package/template/.cursor/skills/yss-web-controller/assets/templates/Controller.java.template +11 -11
  174. package/template/.cursor/skills/yss-web-controller/assets/templates/WebConvertor.java.template +4 -7
  175. package/template/.cursor/skills/yss-web-controller/assets/templates/web/dto/CreateRequest.java.template +2 -2
  176. package/template/.cursor/skills/yss-web-controller/assets/templates/web/dto/PageRequest.java.template +4 -7
  177. package/template/.cursor/skills/yss-web-controller/assets/templates/web/dto/UpdateRequest.java.template +2 -2
  178. package/template/.cursor/skills/yss-web-controller/references/web-adapter-layer-guide.md +6 -4
  179. package/template/.cursor/skills/yss-web-controller/references/web-generation-contract.schema.json +97 -0
  180. package/template/.cursor/skills/yss-web-controller/scripts/generate_controller.mjs +141 -20
  181. package/template/.cursor/skills/yss-web-controller/scripts/generate_controller.test.mjs +131 -35
  182. package/template/.hermes/skills/ask-matt/SKILL.md +1 -1
  183. package/template/.hermes/skills/wayfinder/SKILL.md +2 -2
  184. package/template/.hermes/skills/yss-ddd-scaffold-generator/SKILL.md +14 -3
  185. package/template/.hermes/skills/yss-ddd-scaffold-generator/references/ARCHITECTURE.md +3 -3
  186. package/template/.hermes/skills/yss-ddd-scaffold-generator/references/USAGE_EXAMPLES.md +12 -1
  187. package/template/.hermes/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +2 -1
  188. package/template/.hermes/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +45 -11
  189. package/template/.hermes/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +164 -0
  190. package/template/.hermes/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +18 -4
  191. package/template/.hermes/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +172 -0
  192. package/template/.hermes/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +14 -1
  193. package/template/.hermes/skills/yss-domain/SKILL.md +2 -1
  194. package/template/.hermes/skills/yss-domain/agents/openai.yaml +2 -2
  195. package/template/.hermes/skills/yss-domain/references/domain-layer-guide.md +2 -0
  196. package/template/.hermes/skills/yss-dto/SKILL.md +6 -0
  197. package/template/.hermes/skills/yss-dto/references/openapi-wire-profile.yaml +6 -0
  198. package/template/.hermes/skills/yss-product-lifecycle/SKILL.md +1 -1
  199. package/template/.hermes/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
  200. package/template/.hermes/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -10
  201. package/template/.hermes/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  202. package/template/.hermes/skills/yss-repository/SKILL.md +15 -13
  203. package/template/.hermes/skills/yss-repository/agents/openai.yaml +2 -2
  204. package/template/.hermes/skills/yss-repository/references/infrastructure-layer-guide.md +1 -1
  205. package/template/.hermes/skills/yss-research/SKILL.md +73 -0
  206. package/template/.hermes/skills/yss-research/agents/openai.yaml +3 -0
  207. package/template/.hermes/skills/yss-research/assets/evidence-template.yaml +68 -0
  208. package/template/.hermes/skills/yss-research/assets/research-brief-template.md +36 -0
  209. package/template/.hermes/skills/yss-research/references/evidence-contract.md +42 -0
  210. package/template/.hermes/skills/yss-research/scripts/validate-research-package.mjs +128 -0
  211. package/template/.hermes/skills/yss-research/tests/run-scenarios.mjs +81 -0
  212. package/template/.hermes/skills/yss-router/references/router-contract.yaml +7 -3
  213. package/template/.hermes/skills/yss-stage-decision/SKILL.md +2 -1
  214. package/template/.hermes/skills/yss-web-controller/SKILL.md +16 -10
  215. package/template/.hermes/skills/yss-web-controller/agents/openai.yaml +2 -2
  216. package/template/.hermes/skills/yss-web-controller/assets/templates/Controller.java.template +11 -11
  217. package/template/.hermes/skills/yss-web-controller/assets/templates/WebConvertor.java.template +4 -7
  218. package/template/.hermes/skills/yss-web-controller/assets/templates/web/dto/CreateRequest.java.template +2 -2
  219. package/template/.hermes/skills/yss-web-controller/assets/templates/web/dto/PageRequest.java.template +4 -7
  220. package/template/.hermes/skills/yss-web-controller/assets/templates/web/dto/UpdateRequest.java.template +2 -2
  221. package/template/.hermes/skills/yss-web-controller/references/web-adapter-layer-guide.md +6 -4
  222. package/template/.hermes/skills/yss-web-controller/references/web-generation-contract.schema.json +97 -0
  223. package/template/.hermes/skills/yss-web-controller/scripts/generate_controller.mjs +141 -20
  224. package/template/.hermes/skills/yss-web-controller/scripts/generate_controller.test.mjs +131 -35
  225. package/template/.pi/skills/ask-matt/SKILL.md +1 -1
  226. package/template/.pi/skills/wayfinder/SKILL.md +2 -2
  227. package/template/.pi/skills/yss-ddd-scaffold-generator/SKILL.md +14 -3
  228. package/template/.pi/skills/yss-ddd-scaffold-generator/references/ARCHITECTURE.md +3 -3
  229. package/template/.pi/skills/yss-ddd-scaffold-generator/references/USAGE_EXAMPLES.md +12 -1
  230. package/template/.pi/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +2 -1
  231. package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +45 -11
  232. package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +164 -0
  233. package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +18 -4
  234. package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +172 -0
  235. package/template/.pi/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +14 -1
  236. package/template/.pi/skills/yss-domain/SKILL.md +2 -1
  237. package/template/.pi/skills/yss-domain/agents/openai.yaml +2 -2
  238. package/template/.pi/skills/yss-domain/references/domain-layer-guide.md +2 -0
  239. package/template/.pi/skills/yss-dto/SKILL.md +6 -0
  240. package/template/.pi/skills/yss-dto/references/openapi-wire-profile.yaml +6 -0
  241. package/template/.pi/skills/yss-product-lifecycle/SKILL.md +1 -1
  242. package/template/.pi/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
  243. package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -10
  244. package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  245. package/template/.pi/skills/yss-repository/SKILL.md +15 -13
  246. package/template/.pi/skills/yss-repository/agents/openai.yaml +2 -2
  247. package/template/.pi/skills/yss-repository/references/infrastructure-layer-guide.md +1 -1
  248. package/template/.pi/skills/yss-research/SKILL.md +73 -0
  249. package/template/.pi/skills/yss-research/agents/openai.yaml +3 -0
  250. package/template/.pi/skills/yss-research/assets/evidence-template.yaml +68 -0
  251. package/template/.pi/skills/yss-research/assets/research-brief-template.md +36 -0
  252. package/template/.pi/skills/yss-research/references/evidence-contract.md +42 -0
  253. package/template/.pi/skills/yss-research/scripts/validate-research-package.mjs +128 -0
  254. package/template/.pi/skills/yss-research/tests/run-scenarios.mjs +81 -0
  255. package/template/.pi/skills/yss-router/references/router-contract.yaml +7 -3
  256. package/template/.pi/skills/yss-stage-decision/SKILL.md +2 -1
  257. package/template/.pi/skills/yss-web-controller/SKILL.md +16 -10
  258. package/template/.pi/skills/yss-web-controller/agents/openai.yaml +2 -2
  259. package/template/.pi/skills/yss-web-controller/assets/templates/Controller.java.template +11 -11
  260. package/template/.pi/skills/yss-web-controller/assets/templates/WebConvertor.java.template +4 -7
  261. package/template/.pi/skills/yss-web-controller/assets/templates/web/dto/CreateRequest.java.template +2 -2
  262. package/template/.pi/skills/yss-web-controller/assets/templates/web/dto/PageRequest.java.template +4 -7
  263. package/template/.pi/skills/yss-web-controller/assets/templates/web/dto/UpdateRequest.java.template +2 -2
  264. package/template/.pi/skills/yss-web-controller/references/web-adapter-layer-guide.md +6 -4
  265. package/template/.pi/skills/yss-web-controller/references/web-generation-contract.schema.json +97 -0
  266. package/template/.pi/skills/yss-web-controller/scripts/generate_controller.mjs +141 -20
  267. package/template/.pi/skills/yss-web-controller/scripts/generate_controller.test.mjs +131 -35
  268. package/template/.qoder/skills/ask-matt/SKILL.md +1 -1
  269. package/template/.qoder/skills/wayfinder/SKILL.md +2 -2
  270. package/template/.qoder/skills/yss-ddd-scaffold-generator/SKILL.md +14 -3
  271. package/template/.qoder/skills/yss-ddd-scaffold-generator/references/ARCHITECTURE.md +3 -3
  272. package/template/.qoder/skills/yss-ddd-scaffold-generator/references/USAGE_EXAMPLES.md +12 -1
  273. package/template/.qoder/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +2 -1
  274. package/template/.qoder/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +45 -11
  275. package/template/.qoder/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +164 -0
  276. package/template/.qoder/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +18 -4
  277. package/template/.qoder/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +172 -0
  278. package/template/.qoder/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +14 -1
  279. package/template/.qoder/skills/yss-domain/SKILL.md +2 -1
  280. package/template/.qoder/skills/yss-domain/agents/openai.yaml +2 -2
  281. package/template/.qoder/skills/yss-domain/references/domain-layer-guide.md +2 -0
  282. package/template/.qoder/skills/yss-dto/SKILL.md +6 -0
  283. package/template/.qoder/skills/yss-dto/references/openapi-wire-profile.yaml +6 -0
  284. package/template/.qoder/skills/yss-product-lifecycle/SKILL.md +1 -1
  285. package/template/.qoder/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
  286. package/template/.qoder/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -10
  287. package/template/.qoder/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  288. package/template/.qoder/skills/yss-repository/SKILL.md +15 -13
  289. package/template/.qoder/skills/yss-repository/agents/openai.yaml +2 -2
  290. package/template/.qoder/skills/yss-repository/references/infrastructure-layer-guide.md +1 -1
  291. package/template/.qoder/skills/yss-research/SKILL.md +73 -0
  292. package/template/.qoder/skills/yss-research/agents/openai.yaml +3 -0
  293. package/template/.qoder/skills/yss-research/assets/evidence-template.yaml +68 -0
  294. package/template/.qoder/skills/yss-research/assets/research-brief-template.md +36 -0
  295. package/template/.qoder/skills/yss-research/references/evidence-contract.md +42 -0
  296. package/template/.qoder/skills/yss-research/scripts/validate-research-package.mjs +128 -0
  297. package/template/.qoder/skills/yss-research/tests/run-scenarios.mjs +81 -0
  298. package/template/.qoder/skills/yss-router/references/router-contract.yaml +7 -3
  299. package/template/.qoder/skills/yss-stage-decision/SKILL.md +2 -1
  300. package/template/.qoder/skills/yss-web-controller/SKILL.md +16 -10
  301. package/template/.qoder/skills/yss-web-controller/agents/openai.yaml +2 -2
  302. package/template/.qoder/skills/yss-web-controller/assets/templates/Controller.java.template +11 -11
  303. package/template/.qoder/skills/yss-web-controller/assets/templates/WebConvertor.java.template +4 -7
  304. package/template/.qoder/skills/yss-web-controller/assets/templates/web/dto/CreateRequest.java.template +2 -2
  305. package/template/.qoder/skills/yss-web-controller/assets/templates/web/dto/PageRequest.java.template +4 -7
  306. package/template/.qoder/skills/yss-web-controller/assets/templates/web/dto/UpdateRequest.java.template +2 -2
  307. package/template/.qoder/skills/yss-web-controller/references/web-adapter-layer-guide.md +6 -4
  308. package/template/.qoder/skills/yss-web-controller/references/web-generation-contract.schema.json +97 -0
  309. package/template/.qoder/skills/yss-web-controller/scripts/generate_controller.mjs +141 -20
  310. package/template/.qoder/skills/yss-web-controller/scripts/generate_controller.test.mjs +131 -35
  311. package/template/.trae/skills/ask-matt/SKILL.md +1 -1
  312. package/template/.trae/skills/wayfinder/SKILL.md +2 -2
  313. package/template/.trae/skills/yss-ddd-scaffold-generator/SKILL.md +14 -3
  314. package/template/.trae/skills/yss-ddd-scaffold-generator/references/ARCHITECTURE.md +3 -3
  315. package/template/.trae/skills/yss-ddd-scaffold-generator/references/USAGE_EXAMPLES.md +12 -1
  316. package/template/.trae/skills/yss-ddd-scaffold-generator/references/yss-backend-scaffold-parent/SKILL.md +2 -1
  317. package/template/.trae/skills/yss-ddd-scaffold-generator/scripts/first-slice-compatibility.test.mjs +45 -11
  318. package/template/.trae/skills/yss-ddd-scaffold-generator/scripts/first-slice-verification.test.mjs +164 -0
  319. package/template/.trae/skills/yss-ddd-scaffold-generator/scripts/generate_scaffold.mjs +18 -4
  320. package/template/.trae/skills/yss-ddd-scaffold-generator/scripts/run_first_slice_verification.mjs +172 -0
  321. package/template/.trae/skills/yss-ddd-scaffold-generator/scripts/scaffold-generator.test.mjs +14 -1
  322. package/template/.trae/skills/yss-domain/SKILL.md +2 -1
  323. package/template/.trae/skills/yss-domain/agents/openai.yaml +2 -2
  324. package/template/.trae/skills/yss-domain/references/domain-layer-guide.md +2 -0
  325. package/template/.trae/skills/yss-dto/SKILL.md +6 -0
  326. package/template/.trae/skills/yss-dto/references/openapi-wire-profile.yaml +6 -0
  327. package/template/.trae/skills/yss-product-lifecycle/SKILL.md +1 -1
  328. package/template/.trae/skills/yss-product-lifecycle/references/matt-yss-adapter.md +2 -2
  329. package/template/.trae/skills/yss-product-lifecycle/references/orchestration-contract.yaml +12 -10
  330. package/template/.trae/skills/yss-product-lifecycle/references/orchestration.md +2 -2
  331. package/template/.trae/skills/yss-repository/SKILL.md +15 -13
  332. package/template/.trae/skills/yss-repository/agents/openai.yaml +2 -2
  333. package/template/.trae/skills/yss-repository/references/infrastructure-layer-guide.md +1 -1
  334. package/template/.trae/skills/yss-research/SKILL.md +73 -0
  335. package/template/.trae/skills/yss-research/agents/openai.yaml +3 -0
  336. package/template/.trae/skills/yss-research/assets/evidence-template.yaml +68 -0
  337. package/template/.trae/skills/yss-research/assets/research-brief-template.md +36 -0
  338. package/template/.trae/skills/yss-research/references/evidence-contract.md +42 -0
  339. package/template/.trae/skills/yss-research/scripts/validate-research-package.mjs +128 -0
  340. package/template/.trae/skills/yss-research/tests/run-scenarios.mjs +81 -0
  341. package/template/.trae/skills/yss-router/references/router-contract.yaml +7 -3
  342. package/template/.trae/skills/yss-stage-decision/SKILL.md +2 -1
  343. package/template/.trae/skills/yss-web-controller/SKILL.md +16 -10
  344. package/template/.trae/skills/yss-web-controller/agents/openai.yaml +2 -2
  345. package/template/.trae/skills/yss-web-controller/assets/templates/Controller.java.template +11 -11
  346. package/template/.trae/skills/yss-web-controller/assets/templates/WebConvertor.java.template +4 -7
  347. package/template/.trae/skills/yss-web-controller/assets/templates/web/dto/CreateRequest.java.template +2 -2
  348. package/template/.trae/skills/yss-web-controller/assets/templates/web/dto/PageRequest.java.template +4 -7
  349. package/template/.trae/skills/yss-web-controller/assets/templates/web/dto/UpdateRequest.java.template +2 -2
  350. package/template/.trae/skills/yss-web-controller/references/web-adapter-layer-guide.md +6 -4
  351. package/template/.trae/skills/yss-web-controller/references/web-generation-contract.schema.json +97 -0
  352. package/template/.trae/skills/yss-web-controller/scripts/generate_controller.mjs +141 -20
  353. package/template/.trae/skills/yss-web-controller/scripts/generate_controller.test.mjs +131 -35
  354. package/template/AGENTS.md +2 -2
  355. package/template/CONTEXT.md +1 -1
  356. package/template/docs/agents/digital-human-roles.yaml +2 -2
  357. package/template/docs/agents/yss-skill-registry.yaml +18 -3
  358. package/template/docs/process/MATT-POCOCK-ENGINEERING-SKILLS.md +1 -0
  359. package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214.md +1 -1
  360. package/template/scripts/lib/scenario-checks.mjs +8 -4
  361. package/template/scripts/lib/skill-registry.mjs +49 -5
  362. package/template/scripts/lib/skill-supply-chain.mjs +3 -2
  363. package/template/scripts/verify-yss-dto-openapi-profile +8 -0
  364. package/template/skills-lock.json +30 -30
  365. package/template.snapshot.json +4 -4
  366. package/template/.agents/skills/research/SKILL.md +0 -12
  367. package/template/.agents/skills/research/agents/openai.yaml +0 -3
  368. package/template/.claude/skills/research/SKILL.md +0 -12
  369. package/template/.claude/skills/research/agents/openai.yaml +0 -3
  370. package/template/.codex/skills/research/SKILL.md +0 -12
  371. package/template/.codex/skills/research/agents/openai.yaml +0 -3
  372. package/template/.cursor/skills/research/SKILL.md +0 -12
  373. package/template/.cursor/skills/research/agents/openai.yaml +0 -3
  374. package/template/.hermes/skills/research/SKILL.md +0 -12
  375. package/template/.hermes/skills/research/agents/openai.yaml +0 -3
  376. package/template/.pi/skills/research/SKILL.md +0 -12
  377. package/template/.pi/skills/research/agents/openai.yaml +0 -3
  378. package/template/.qoder/skills/research/SKILL.md +0 -12
  379. package/template/.qoder/skills/research/agents/openai.yaml +0 -3
  380. package/template/.trae/skills/research/SKILL.md +0 -12
  381. package/template/.trae/skills/research/agents/openai.yaml +0 -3
@@ -20,7 +20,7 @@ DTO 固定属于 Web module。独立 client module 与把 client DTO 放进 Doma
20
20
 
21
21
  - Request/Response 字段必须来自冻结 OpenAPI 和批准 allowlist。
22
22
  - 数据库 metadata 只补充 Java 类型候选,不能自行扩大公开字段。
23
- - Web Page Request 不继承含内部协作字段的 `PageQuery`;只暴露批准的 `pageIndex/pageSize/orderBy/orderDirection/groupBy` 及业务查询字段。
23
+ - Web Page Request 不继承含内部协作字段的 `PageQuery`;分页字段、默认值、枚举和禁用字段全部来自合同绑定且 digest 匹配的 `yss-dto` wire profile,再与 `fields.<table>.pagination` allowlist 取交集。Web Adapter 不维护第二份分页协议。
24
24
  - `orderBy` / `groupBy` 必须在 Application/Infrastructure 映射到数据库列白名单。
25
25
  - `offset`、`needTotalCount`、`tempTotalCount` 禁止进入 HTTP 绑定面。
26
26
 
@@ -70,9 +70,11 @@ public interface QualityRuleWebConvertor {
70
70
 
71
71
  ## 6. 生成门禁
72
72
 
73
- - `generate_controller.mjs` 必须消费 `status=approved` 的 Web generation contract。
74
- - 合同必须声明 `base_package`、`module_name`、`domain_segment`、`target-domain-model`、平台/validation Profile、`dto_placement=web`、OpenAPI Freeze 引用和每张表的 create/update/query/response allowlist;CLI 身份必须逐项匹配。
75
- - 生成器是 initialize-only,必须在写入前规划全部目标;任一文件已存在或传入 `--force` 时整体阻断,不允许“跳过已有文件后继续生成”的部分成功。
73
+ - `generate_controller.mjs` 只消费 `schema_version=2`、`status=approved` 且 `current_version=contract_version` 的 Web generation contract;schema v1 为 `unsupported`。
74
+ - `references/web-generation-contract.schema.json` 是 Web generation contract v2 的机器可读结构合同;生成器仍执行跨字段、文件系统与 Manifest 语义校验。
75
+ - 合同必须声明 Slice `contract_id/contract_version/slice_id`、`integration_mode`、`implementation_project_root`、`allowed_write_paths`、预期证据、验证命令、`base_package`、`module_name`、`domain_segment`、Application Service package、`target-domain-model`、平台/validation Profile、`dto_placement=web`、`dto_wire_profile_ref/digest`、OpenAPI Freeze 引用和每张表的 create/update/query/pagination/response allowlist;CLI 身份必须逐项匹配。
76
+ - `scaffold-v2` 模式必须绑定已达 `empty-scaffold-verified` 或 `first-slice-verified` 的 Manifest,并校验标准 Web module 路径和所有 Profile;`existing-project` 模式不得伪造 scaffold manifest。
77
+ - 生成器是 initialize-only,必须在写入前规划全部目标并校验每个目标位于批准写路径;任一文件已存在或传入 `--force` 时整体阻断。落盘使用排他创建,任一步失败都回滚本次已创建文件,不允许部分成功。
76
78
  - 权限、校验语义、异常行为和复杂查询仍使用 `behavior-tdd`,不能由 metadata 猜测。
77
79
 
78
80
  ## 7. 验证
@@ -0,0 +1,97 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://yss.local/schemas/web-generation-contract.schema.json",
4
+ "title": "YSS Web Generation Contract",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "schema_version",
9
+ "contract_id",
10
+ "contract_version",
11
+ "current_version",
12
+ "slice_id",
13
+ "status",
14
+ "integration_mode",
15
+ "implementation_project_root",
16
+ "base_package",
17
+ "module_name",
18
+ "domain_segment",
19
+ "application_service_package",
20
+ "architecture_profile",
21
+ "platform_profile",
22
+ "validation_namespace",
23
+ "dto_placement",
24
+ "dto_wire_profile_ref",
25
+ "dto_wire_profile_digest",
26
+ "openapi_freeze_ref",
27
+ "allowed_write_paths",
28
+ "expected_evidence_files",
29
+ "verification_commands",
30
+ "fields"
31
+ ],
32
+ "properties": {
33
+ "schema_version": { "const": 2 },
34
+ "contract_id": { "$ref": "#/$defs/nonEmptyString" },
35
+ "contract_version": { "type": "integer", "minimum": 1 },
36
+ "current_version": { "type": "integer", "minimum": 1 },
37
+ "slice_id": { "$ref": "#/$defs/nonEmptyString" },
38
+ "status": { "const": "approved" },
39
+ "integration_mode": { "enum": ["existing-project", "scaffold-v2"] },
40
+ "implementation_project_root": { "$ref": "#/$defs/nonEmptyString" },
41
+ "scaffold_manifest_ref": { "$ref": "#/$defs/nonEmptyString" },
42
+ "base_package": { "$ref": "#/$defs/nonEmptyString" },
43
+ "module_name": { "$ref": "#/$defs/nonEmptyString" },
44
+ "domain_segment": { "$ref": "#/$defs/nonEmptyString" },
45
+ "application_service_package": { "$ref": "#/$defs/nonEmptyString" },
46
+ "architecture_profile": { "const": "target-domain-model" },
47
+ "platform_profile": { "enum": ["spring-boot-2.7-jdk8", "spring-boot-3-jdk17"] },
48
+ "validation_namespace": { "enum": ["javax", "jakarta"] },
49
+ "dto_placement": { "const": "web" },
50
+ "dto_wire_profile_ref": { "$ref": "#/$defs/nonEmptyString" },
51
+ "dto_wire_profile_digest": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
52
+ "openapi_freeze_ref": { "$ref": "#/$defs/nonEmptyString" },
53
+ "allowed_write_paths": { "$ref": "#/$defs/nonEmptyStringArray" },
54
+ "expected_evidence_files": { "$ref": "#/$defs/nonEmptyStringArray" },
55
+ "verification_commands": { "$ref": "#/$defs/nonEmptyStringArray" },
56
+ "fields": {
57
+ "type": "object",
58
+ "minProperties": 1,
59
+ "additionalProperties": {
60
+ "type": "object",
61
+ "additionalProperties": false,
62
+ "required": ["create", "update", "query", "pagination", "response"],
63
+ "properties": {
64
+ "create": { "$ref": "#/$defs/stringArray" },
65
+ "update": { "$ref": "#/$defs/stringArray" },
66
+ "query": { "$ref": "#/$defs/stringArray" },
67
+ "pagination": {
68
+ "type": "array",
69
+ "uniqueItems": true,
70
+ "items": { "$ref": "#/$defs/nonEmptyString" }
71
+ },
72
+ "response": { "$ref": "#/$defs/stringArray" }
73
+ }
74
+ }
75
+ }
76
+ },
77
+ "allOf": [
78
+ {
79
+ "if": { "properties": { "integration_mode": { "const": "scaffold-v2" } } },
80
+ "then": { "required": ["scaffold_manifest_ref"] },
81
+ "else": { "not": { "required": ["scaffold_manifest_ref"] } }
82
+ },
83
+ {
84
+ "if": { "properties": { "platform_profile": { "const": "spring-boot-2.7-jdk8" } } },
85
+ "then": { "properties": { "validation_namespace": { "const": "javax" } } }
86
+ },
87
+ {
88
+ "if": { "properties": { "platform_profile": { "const": "spring-boot-3-jdk17" } } },
89
+ "then": { "properties": { "validation_namespace": { "const": "jakarta" } } }
90
+ }
91
+ ],
92
+ "$defs": {
93
+ "nonEmptyString": { "type": "string", "minLength": 1 },
94
+ "stringArray": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/nonEmptyString" } },
95
+ "nonEmptyStringArray": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "$ref": "#/$defs/nonEmptyString" } }
96
+ }
97
+ }
@@ -1,18 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import { access, mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { createHash } from "node:crypto";
3
+ import { constants, access, copyFile, mkdir, mkdtemp, readFile, rm, unlink, writeFile } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
3
5
  import path from "node:path";
4
6
  import { fileURLToPath } from "node:url";
5
7
 
8
+ import { parseDocument } from "../../../../scripts/vendor/yaml.mjs";
9
+
6
10
  const TYPE_MAPPING = { bigint: "Long", int: "Integer", integer: "Integer", smallint: "Integer", tinyint: "Integer", number: "Long", numeric: "BigDecimal", decimal: "BigDecimal", float: "Float", double: "Double", real: "Float", varchar: "String", varchar2: "String", nvarchar2: "String", char: "String", nchar: "String", text: "String", clob: "String", nclob: "String", date: "LocalDateTime", datetime: "LocalDateTime", timestamp: "LocalDateTime", "timestamp with time zone": "LocalDateTime", boolean: "Boolean", bool: "Boolean", bit: "Boolean", json: "String", jsonb: "String", uuid: "String" };
7
11
  const PLATFORM_VALIDATION_NAMESPACES = {
8
12
  "spring-boot-2.7-jdk8": "javax",
9
13
  "spring-boot-3-jdk17": "jakarta"
10
14
  };
11
- const required = ["metadata-file", "contract-file", "base-package", "module-name", "domain-segment"];
12
- const valued = new Set([...required, "output-dir", "web-project-dir", "web-output-dir", "author", "application-service-package", "validation-namespace"]);
15
+ const required = ["metadata-file", "contract-file", "dto-wire-profile-file", "base-package", "module-name", "domain-segment"];
16
+ const valued = new Set([...required, "output-dir", "web-project-dir", "web-output-dir", "author", "application-service-package", "validation-namespace", "scaffold-manifest-file"]);
13
17
 
14
18
  function usage() {
15
- return "Usage: node generate_controller.mjs --metadata-file FILE --contract-file FILE --base-package PACKAGE --module-name NAME --domain-segment SEGMENT [--output-dir DIR] [--web-project-dir DIR] [--web-output-dir DIR] [--author NAME] [--application-service-package PACKAGE] [--validation-namespace javax|jakarta]";
19
+ return "Usage: node generate_controller.mjs --metadata-file FILE --contract-file FILE --dto-wire-profile-file FILE --base-package PACKAGE --module-name NAME --domain-segment SEGMENT [--output-dir DIR] [--web-project-dir DIR] [--web-output-dir DIR] [--scaffold-manifest-file FILE] [--author NAME] [--application-service-package PACKAGE] [--validation-namespace javax|jakarta]";
16
20
  }
17
21
  export function parseArgs(argv) {
18
22
  const args = { "output-dir": "./output", author: "System", "validation-namespace": "javax", force: false };
@@ -51,6 +55,114 @@ function fields(columns, selected, { command = false } = {}) {
51
55
  }
52
56
  function render(template, context) { return template.replace(/\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g, (_, braced, plain) => { const key = braced || plain; if (!(key in context)) throw new Error(`missing template field: ${key}`); return context[key]; }); }
53
57
  async function exists(target) { try { await access(target); return true; } catch { return false; } }
58
+ const present = (value) => value !== undefined && value !== null && value !== "";
59
+ function within(root, target) {
60
+ const relative = path.relative(path.resolve(root), path.resolve(target));
61
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
62
+ }
63
+ function simpleJavaName(javaType) {
64
+ return javaType.replace(/<.*$/, "").split(".").at(-1);
65
+ }
66
+ function paginationFields(selected, dtoProfile) {
67
+ const definitions = dtoProfile.page_query.fields;
68
+ const unique = [...new Set(selected)];
69
+ const unsupported = unique.filter((name) => !(name in definitions) || dtoProfile.page_query.forbidden_client_fields.includes(name));
70
+ if (unsupported.length) throw new Error(`unsupported pagination fields: ${unsupported.join(", ")}`);
71
+ return unique.map((name) => {
72
+ const definition = definitions[name];
73
+ const wireType = definition.wire_types.find((item) => item !== "null");
74
+ const javaType = wireType === "integer" ? (definition.nullable ? "Integer" : "int") : wireType === "string" ? "String" : null;
75
+ if (!javaType) throw new Error(`unsupported yss-dto pagination wire type for ${name}: ${definition.wire_types.join(", ")}`);
76
+ const annotation = definition.enum?.length
77
+ ? ` @Pattern(regexp = "${definition.enum.join("|")}", message = "${name} must be one of ${definition.enum.join(", ")}")\n`
78
+ : "";
79
+ const initializer = definition.default === undefined ? "" : ` = ${typeof definition.default === "string" ? JSON.stringify(definition.default) : definition.default}`;
80
+ return `${annotation} private ${javaType} ${name}${initializer};`;
81
+ }).join("\n");
82
+ }
83
+
84
+ async function loadDtoWireProfile(contract, args) {
85
+ const profilePath = path.resolve(args["dto-wire-profile-file"]);
86
+ if (profilePath !== path.resolve(contract.dto_wire_profile_ref ?? "")) throw new Error("--dto-wire-profile-file does not match the approved Web generation contract");
87
+ if (!await exists(profilePath)) throw new Error(`yss-dto wire profile not found: ${profilePath}`);
88
+ const source = await readFile(profilePath);
89
+ const digest = createHash("sha256").update(source).digest("hex");
90
+ if (digest !== contract.dto_wire_profile_digest) throw new Error("yss-dto wire profile digest does not match the approved Web generation contract");
91
+ const document = parseDocument(source.toString("utf8"), { maxAliasCount: 0, uniqueKeys: true });
92
+ if (document.errors.length) throw new Error(`invalid yss-dto wire profile: ${document.errors[0].message}`);
93
+ const profile = document.toJS({ maxAliasCount: 0 });
94
+ if (profile?.schema_version !== 1 || profile?.kind !== "yss-dto-openapi-wire-profile") throw new Error("unsupported yss-dto wire profile");
95
+ const resultPackage = profile.canonical?.java_package;
96
+ const pageResult = profile.wrappers?.PageResult;
97
+ const singleResult = profile.wrappers?.SingleResult;
98
+ const commandDtoType = profile.request_bases?.command?.java_type;
99
+ const queryDtoType = profile.request_bases?.query?.java_type;
100
+ if (![resultPackage, pageResult?.java_type, singleResult?.java_type, commandDtoType, queryDtoType].every(present)) throw new Error("incomplete yss-dto wire profile type mapping");
101
+ if (![pageResult.java_type, singleResult.java_type].every((type) => type.startsWith(`${resultPackage}.`))) throw new Error("yss-dto result wrapper package is inconsistent");
102
+ if (!profile.page_query?.fields || !Array.isArray(profile.page_query?.forbidden_client_fields)) throw new Error("incomplete yss-dto page_query mapping");
103
+ const factory = pageResult.java_factory;
104
+ const singleFactory = singleResult.java_factory;
105
+ const javaIdentifier = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
106
+ if (!javaIdentifier.test(factory?.method ?? "") || !Array.isArray(factory.arguments) || !factory.arguments.length) throw new Error("incomplete yss-dto PageResult factory mapping");
107
+ if (!javaIdentifier.test(singleFactory?.method ?? "") || JSON.stringify(singleFactory.arguments) !== JSON.stringify(["data"])) throw new Error("incomplete yss-dto SingleResult factory mapping");
108
+ const expressionByArgument = { data: "toResponses(source.getData())", totalCount: "source.getTotalCount()", pageSize: "source.getPageSize()", pageIndex: "source.getPageIndex()" };
109
+ const unsupportedArguments = factory.arguments.filter((name) => !(name in expressionByArgument));
110
+ if (unsupportedArguments.length) throw new Error(`unsupported yss-dto PageResult factory arguments: ${unsupportedArguments.join(", ")}`);
111
+ return {
112
+ profile,
113
+ resultPackage,
114
+ pageResultType: pageResult.java_type.replace(/<.*$/, ""),
115
+ singleResultType: singleResult.java_type.replace(/<.*$/, ""),
116
+ pageResultFactoryMethod: factory.method,
117
+ singleResultFactoryMethod: singleFactory.method,
118
+ commandDtoType,
119
+ queryDtoType,
120
+ pageResultFactoryArguments: factory.arguments.map((name, index) => ` ${expressionByArgument[name]}${index < factory.arguments.length - 1 ? "," : ""}`).join("\n")
121
+ };
122
+ }
123
+
124
+ async function validateContract(contract, args) {
125
+ if (contract.schema_version !== 2) throw new Error(`web generation contract schema_version=${contract.schema_version} is unsupported; only schema_version=2 is accepted`);
126
+ if (contract.status !== "approved") throw new Error("web generation contract must be approved");
127
+ const requiredFields = ["contract_id", "contract_version", "current_version", "slice_id", "integration_mode", "implementation_project_root", "base_package", "module_name", "domain_segment", "application_service_package", "architecture_profile", "platform_profile", "validation_namespace", "dto_placement", "dto_wire_profile_ref", "dto_wire_profile_digest", "openapi_freeze_ref", "fields", "allowed_write_paths", "expected_evidence_files", "verification_commands"];
128
+ const missing = requiredFields.filter((name) => !present(contract[name]));
129
+ if (missing.length) throw new Error(`approved web generation contract is incomplete: ${missing.join(", ")}`);
130
+ if (contract.current_version !== contract.contract_version) throw new Error("web generation contract is not the current approved version");
131
+ if (!Number.isInteger(contract.contract_version) || contract.contract_version < 1) throw new Error("web generation contract_version must be a positive integer");
132
+ if (!["existing-project", "scaffold-v2"].includes(contract.integration_mode)) throw new Error("unsupported web generation integration_mode");
133
+ if (!["allowed_write_paths", "expected_evidence_files", "verification_commands"].every((name) => Array.isArray(contract[name]) && contract[name].length)) throw new Error("web generation contract requires non-empty allowed_write_paths, expected_evidence_files, and verification_commands");
134
+ if (contract.architecture_profile !== "target-domain-model" || contract.dto_placement !== "web") throw new Error("web generation only supports target-domain-model with dto_placement=web");
135
+ if (contract.base_package !== args["base-package"] || contract.module_name !== args["module-name"] || contract.domain_segment !== args["domain-segment"]) throw new Error("CLI generation identity does not match the approved web generation contract");
136
+ const applicationPackage = args["application-service-package"] || `${args["base-package"]}.application.service`;
137
+ if (contract.application_service_package !== applicationPackage) throw new Error("--application-service-package does not match the approved web generation contract");
138
+ if (!(contract.platform_profile in PLATFORM_VALIDATION_NAMESPACES)) throw new Error("unsupported platform_profile in approved web generation contract");
139
+ if (PLATFORM_VALIDATION_NAMESPACES[contract.platform_profile] !== contract.validation_namespace) throw new Error("platform_profile and validation_namespace are inconsistent");
140
+ if (contract.validation_namespace !== args["validation-namespace"]) throw new Error("--validation-namespace must match the approved web generation contract");
141
+
142
+ const implementationRoot = path.resolve(contract.implementation_project_root);
143
+ if (contract.integration_mode === "existing-project") {
144
+ if (args["scaffold-manifest-file"] || present(contract.scaffold_manifest_ref)) throw new Error("existing-project Web generation must not claim a scaffold manifest");
145
+ return { applicationPackage, implementationRoot };
146
+ }
147
+
148
+ if (!args["scaffold-manifest-file"] || !present(contract.scaffold_manifest_ref)) throw new Error("scaffold-v2 Web generation requires --scaffold-manifest-file and scaffold_manifest_ref");
149
+ const manifestPath = path.resolve(args["scaffold-manifest-file"]);
150
+ if (manifestPath !== path.resolve(contract.scaffold_manifest_ref)) throw new Error("--scaffold-manifest-file does not match the approved web generation contract");
151
+ if (!await exists(manifestPath)) throw new Error(`scaffold manifest not found: ${manifestPath}`);
152
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
153
+ if (manifest.schema_version !== 2) throw new Error("scaffold manifest must use schema_version=2");
154
+ if (!["empty-scaffold-verified", "first-slice-verified"].includes(manifest.completion_level)) throw new Error("scaffold manifest must be empty-scaffold-verified before Web generation");
155
+ const scaffoldRoot = path.dirname(path.dirname(manifestPath));
156
+ if (implementationRoot !== scaffoldRoot) throw new Error("implementation_project_root does not match the scaffold manifest project root");
157
+ const expectedWebProject = path.join(scaffoldRoot, `${manifest.project_name}-adapter`, `${manifest.project_name}-web`);
158
+ if (!args["web-project-dir"] || path.resolve(args["web-project-dir"]) !== expectedWebProject) throw new Error("--web-project-dir does not match the scaffold manifest project layout");
159
+ const profilePairs = [["architecture", "architecture_profile"], ["platform", "platform_profile"], ["validation_namespace", "validation_namespace"], ["dto_placement", "dto_placement"]];
160
+ for (const [manifestField, contractField] of profilePairs) {
161
+ if (manifest.profiles?.[manifestField] !== contract[contractField]) throw new Error(`scaffold manifest ${manifestField} profile does not match the approved Web contract`);
162
+ }
163
+ if (manifest.base_package !== contract.base_package) throw new Error("scaffold manifest base_package does not match the approved Web contract");
164
+ return { applicationPackage, implementationRoot };
165
+ }
54
166
 
55
167
  export async function generate(args, logger = console) {
56
168
  if (!await exists(args["metadata-file"])) throw new Error(`Metadata file not found: ${args["metadata-file"]}`);
@@ -59,14 +171,8 @@ export async function generate(args, logger = console) {
59
171
  try { metadata = JSON.parse(await readFile(args["metadata-file"], "utf8")); } catch (error) { throw new Error(`Error reading metadata file: ${error.message}`); }
60
172
  let contract;
61
173
  try { contract = JSON.parse(await readFile(args["contract-file"], "utf8")); } catch (error) { throw new Error(`Error reading contract file: ${error.message}`); }
62
- if (contract.schema_version !== 1 || contract.status !== "approved") throw new Error("web generation contract must be schema_version=1 and approved");
63
- if (contract.architecture_profile !== "target-domain-model" || contract.dto_placement !== "web") throw new Error("web generation only supports target-domain-model with dto_placement=web");
64
- if (!contract.openapi_freeze_ref || !contract.fields || typeof contract.fields !== "object") throw new Error("approved web generation contract requires openapi_freeze_ref and fields");
65
- if (!contract.base_package || !contract.module_name || !contract.domain_segment) throw new Error("approved web generation contract requires base_package, module_name, and domain_segment");
66
- if (contract.base_package !== args["base-package"] || contract.module_name !== args["module-name"] || contract.domain_segment !== args["domain-segment"]) throw new Error("CLI generation identity does not match the approved web generation contract");
67
- if (!(contract.platform_profile in PLATFORM_VALIDATION_NAMESPACES)) throw new Error("unsupported platform_profile in approved web generation contract");
68
- if (PLATFORM_VALIDATION_NAMESPACES[contract.platform_profile] !== contract.validation_namespace) throw new Error("platform_profile and validation_namespace are inconsistent");
69
- if (contract.validation_namespace !== args["validation-namespace"]) throw new Error("--validation-namespace must match the approved web generation contract");
174
+ const { applicationPackage, implementationRoot } = await validateContract(contract, args);
175
+ const dto = await loadDtoWireProfile(contract, args);
70
176
  const tables = metadata.tables || [];
71
177
  if (!tables.length) { logger.warn("Warning: No tables found in metadata."); return []; }
72
178
  const scriptDir = path.dirname(fileURLToPath(import.meta.url));
@@ -82,22 +188,37 @@ export async function generate(args, logger = console) {
82
188
  for (const table of tables) {
83
189
  const domainClass = pascal(table.table_name); const domainVar = lowerCamel(table.table_name); const domainDesc = String(table.table_comment || domainClass).trim().replaceAll("\n", " ");
84
190
  const approvedFields = contract.fields[table.table_name];
85
- if (!approvedFields || ["create", "update", "query", "response"].some((name) => !Array.isArray(approvedFields[name]))) throw new Error(`approved web field contract is missing for table: ${table.table_name}`);
86
- const applicationPackage = args["application-service-package"] || `${args["base-package"]}.application.service`;
87
- const context = { base_package: args["base-package"], module_name: args["module-name"], domain_class: domainClass, domain_var: domainVar, domain_desc: domainDesc, domain_url_path: kebab(table.table_name), domain_pkg_name: args["domain-segment"], author: args.author, dto_imports: [`import ${args["base-package"]}.rest.dto.request.${domainClass}CreateRequest;`, `import ${args["base-package"]}.rest.dto.request.${domainClass}UpdateRequest;`, `import ${args["base-package"]}.rest.dto.request.${domainClass}PageRequest;`, `import ${args["base-package"]}.rest.dto.response.${domainClass}Response;`].join("\n"), application_type_imports: [`import ${args["base-package"]}.application.command.${domainClass}CreateCommand;`, `import ${args["base-package"]}.application.command.${domainClass}UpdateCommand;`, `import ${args["base-package"]}.application.query.${domainClass}PageQuery;`, `import ${args["base-package"]}.application.result.${domainClass}Result;`].join("\n"), application_service_import: `import ${applicationPackage}.${domainClass}Service;`, web_convertor_import: `import ${args["base-package"]}.rest.convertor.${domainClass}WebConvertor;`, web_convertor_class: `${domainClass}WebConvertor`, application_service_field: `private final ${domainClass}Service ${domainVar}Service;`, query_call: `${domainVar}Service.page(webConvertor.toPageQuery(request))`, detail_call: `${domainVar}Service.detail(id)`, add_call: `${domainVar}Service.add(webConvertor.toCreateCommand(request))`, update_call: `${domainVar}Service.update(webConvertor.toUpdateCommand(request))`, delete_call: `${domainVar}Service.delete(id)`, response_class: `${domainClass}Response`, create_request_class: `${domainClass}CreateRequest`, update_request_class: `${domainClass}UpdateRequest`, page_request_class: `${domainClass}PageRequest`, validation_namespace: args["validation-namespace"] };
191
+ if (!approvedFields || ["create", "update", "query", "pagination", "response"].some((name) => !Array.isArray(approvedFields[name]))) throw new Error(`approved web field contract is missing create/update/query/pagination/response allowlists for table: ${table.table_name}`);
192
+ const context = { base_package: args["base-package"], module_name: args["module-name"], domain_class: domainClass, domain_var: domainVar, domain_desc: domainDesc, domain_url_path: kebab(table.table_name), domain_pkg_name: args["domain-segment"], author: args.author, dto_imports: [`import ${args["base-package"]}.rest.dto.request.${domainClass}CreateRequest;`, `import ${args["base-package"]}.rest.dto.request.${domainClass}UpdateRequest;`, `import ${args["base-package"]}.rest.dto.request.${domainClass}PageRequest;`, `import ${args["base-package"]}.rest.dto.response.${domainClass}Response;`].join("\n"), application_type_imports: [`import ${args["base-package"]}.application.command.${domainClass}CreateCommand;`, `import ${args["base-package"]}.application.command.${domainClass}UpdateCommand;`, `import ${args["base-package"]}.application.query.${domainClass}PageQuery;`, `import ${args["base-package"]}.application.result.${domainClass}Result;`].join("\n"), application_service_import: `import ${applicationPackage}.${domainClass}Service;`, web_convertor_import: `import ${args["base-package"]}.rest.convertor.${domainClass}WebConvertor;`, web_convertor_class: `${domainClass}WebConvertor`, application_service_field: `private final ${domainClass}Service ${domainVar}Service;`, query_call: `${domainVar}Service.page(webConvertor.toPageQuery(request))`, detail_call: `${domainVar}Service.detail(id)`, add_call: `${domainVar}Service.add(webConvertor.toCreateCommand(request))`, update_call: `${domainVar}Service.update(webConvertor.toUpdateCommand(request))`, delete_call: `${domainVar}Service.delete(id)`, response_class: `${domainClass}Response`, create_request_class: `${domainClass}CreateRequest`, update_request_class: `${domainClass}UpdateRequest`, page_request_class: `${domainClass}PageRequest`, validation_namespace: args["validation-namespace"], result_package: dto.resultPackage, page_result_type: dto.pageResultType, page_result_simple_name: simpleJavaName(dto.pageResultType), page_result_factory_method: dto.pageResultFactoryMethod, single_result_type: dto.singleResultType, single_result_simple_name: simpleJavaName(dto.singleResultType), single_result_factory_method: dto.singleResultFactoryMethod, command_dto_type: dto.commandDtoType, command_dto_simple_name: simpleJavaName(dto.commandDtoType), query_dto_type: dto.queryDtoType, query_dto_simple_name: simpleJavaName(dto.queryDtoType), page_result_factory_arguments: dto.pageResultFactoryArguments };
88
193
  plan(path.join(webRoot, `${domainClass}Controller.java`), render(templates["Controller.java.template"], context));
89
194
  plan(path.join(webRoot, "convertor", `${domainClass}WebConvertor.java`), render(templates["WebConvertor.java.template"], context));
90
- for (const [templateName, target, declaration] of [["web/dto/Response.java.template", path.join(webRoot, "dto", "response", `${domainClass}Response.java`), fields(table.columns || [], approvedFields.response)], ["web/dto/CreateRequest.java.template", path.join(webRoot, "dto", "request", `${domainClass}CreateRequest.java`), fields(table.columns || [], approvedFields.create, { command: true })], ["web/dto/UpdateRequest.java.template", path.join(webRoot, "dto", "request", `${domainClass}UpdateRequest.java`), fields(table.columns || [], approvedFields.update, { command: true })], ["web/dto/PageRequest.java.template", path.join(webRoot, "dto", "request", `${domainClass}PageRequest.java`), fields(table.columns || [], approvedFields.query)]]) plan(target, render(templates[templateName], { ...context, field_declarations: declaration }));
195
+ for (const [templateName, target, declaration] of [["web/dto/Response.java.template", path.join(webRoot, "dto", "response", `${domainClass}Response.java`), fields(table.columns || [], approvedFields.response)], ["web/dto/CreateRequest.java.template", path.join(webRoot, "dto", "request", `${domainClass}CreateRequest.java`), fields(table.columns || [], approvedFields.create, { command: true })], ["web/dto/UpdateRequest.java.template", path.join(webRoot, "dto", "request", `${domainClass}UpdateRequest.java`), fields(table.columns || [], approvedFields.update, { command: true })], ["web/dto/PageRequest.java.template", path.join(webRoot, "dto", "request", `${domainClass}PageRequest.java`), fields(table.columns || [], approvedFields.query)]]) plan(target, render(templates[templateName], { ...context, field_declarations: declaration, pagination_field_declarations: paginationFields(approvedFields.pagination, dto.profile) }));
91
196
  }
92
197
  const targets = planned.map(({ target }) => target);
93
198
  if (new Set(targets).size !== targets.length) throw new Error("approved metadata produces duplicate target files");
94
199
  const existing = [];
95
200
  for (const target of targets) if (await exists(target)) existing.push(target);
96
201
  if (existing.length) throw new Error(`initialize-only Web generation refuses existing targets: ${existing.join(", ")}`);
97
- for (const { target, contents } of planned) {
98
- await mkdir(path.dirname(target), { recursive: true });
99
- await writeFile(target, contents, "utf8");
100
- logger.log(`Generated: ${target}`);
202
+ const allowedRoots = contract.allowed_write_paths.map((item) => path.resolve(implementationRoot, String(item)));
203
+ const outside = targets.filter((target) => !allowedRoots.some((root) => within(root, target)));
204
+ if (outside.length) throw new Error(`planned Web targets are outside allowed_write_paths: ${outside.join(", ")}`);
205
+ const staging = await mkdtemp(path.join(tmpdir(), "yss-web-generation-"));
206
+ const created = [];
207
+ try {
208
+ for (const [index, { contents }] of planned.entries()) await writeFile(path.join(staging, String(index)), contents, "utf8");
209
+ for (const [index, { target }] of planned.entries()) {
210
+ await mkdir(path.dirname(target), { recursive: true });
211
+ await copyFile(path.join(staging, String(index)), target, constants.COPYFILE_EXCL);
212
+ created.push(target);
213
+ logger.log(`Generated: ${target}`);
214
+ }
215
+ } catch (error) {
216
+ for (const target of created.reverse()) {
217
+ try { await unlink(target); } catch (cleanupError) { if (cleanupError.code !== "ENOENT") error.cleanupError = cleanupError.message; }
218
+ }
219
+ throw error;
220
+ } finally {
221
+ await rm(staging, { recursive: true, force: true });
101
222
  }
102
223
  return targets;
103
224
  }
@@ -1,60 +1,156 @@
1
1
  import assert from "node:assert/strict";
2
+ import { createHash } from "node:crypto";
2
3
  import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
3
4
  import { tmpdir } from "node:os";
4
5
  import path from "node:path";
5
6
  import test from "node:test";
7
+ import { fileURLToPath } from "node:url";
6
8
  import { generate, parseArgs } from "./generate_controller.mjs";
7
9
 
8
- test("generates application-service controller with requested validation profile", async () => {
10
+ const scripts = path.dirname(fileURLToPath(import.meta.url));
11
+ const dtoWireProfileFile = path.resolve(scripts, "../../yss-dto/references/openapi-wire-profile.yaml");
12
+ const dtoWireProfileDigest = createHash("sha256").update(await readFile(dtoWireProfileFile)).digest("hex");
13
+
14
+ const metadataValue = {
15
+ tables: [{
16
+ table_name: "quality_rule",
17
+ table_comment: "质量规则",
18
+ columns: [
19
+ { name: "id", sql_type: "bigint", primary: true, nullable: false },
20
+ { name: "rule_name", sql_type: "varchar(64)", nullable: false }
21
+ ]
22
+ }]
23
+ };
24
+
25
+ function webContract(root, overrides = {}) {
26
+ return {
27
+ schema_version: 2,
28
+ contract_id: "slice-quality-web-1",
29
+ contract_version: 1,
30
+ current_version: 1,
31
+ slice_id: "quality-rule-create",
32
+ status: "approved",
33
+ integration_mode: "existing-project",
34
+ implementation_project_root: root,
35
+ base_package: "com.yss.demo",
36
+ module_name: "demo",
37
+ domain_segment: "quality",
38
+ application_service_package: "com.yss.demo.application.service",
39
+ architecture_profile: "target-domain-model",
40
+ platform_profile: "spring-boot-3-jdk17",
41
+ validation_namespace: "jakarta",
42
+ dto_placement: "web",
43
+ dto_wire_profile_ref: dtoWireProfileFile,
44
+ dto_wire_profile_digest: dtoWireProfileDigest,
45
+ openapi_freeze_ref: "openapi://quality-rule@1",
46
+ allowed_write_paths: [root],
47
+ expected_evidence_files: ["evidence/web-generation.json"],
48
+ verification_commands: ["./mvnw test", "./mvnw package"],
49
+ fields: {
50
+ quality_rule: {
51
+ create: ["rule_name"],
52
+ update: ["id", "rule_name"],
53
+ query: ["rule_name"],
54
+ pagination: ["pageIndex", "pageSize"],
55
+ response: ["id", "rule_name"]
56
+ }
57
+ },
58
+ ...overrides
59
+ };
60
+ }
61
+
62
+ async function fixture(overrides = {}) {
9
63
  const root = await mkdtemp(path.join(tmpdir(), "yss-controller-"));
10
64
  const metadata = path.join(root, "metadata.json");
11
65
  const contract = path.join(root, "web-contract.json");
12
- await writeFile(metadata, JSON.stringify({ tables: [{ table_name: "quality_rule", table_comment: "质量规则", columns: [{ name: "id", sql_type: "bigint", primary: true, nullable: false }, { name: "rule_name", sql_type: "varchar(64)", nullable: false }] }] }));
13
- await writeFile(contract, JSON.stringify({ schema_version: 1, status: "approved", base_package: "com.yss.demo", module_name: "demo", domain_segment: "quality", architecture_profile: "target-domain-model", platform_profile: "spring-boot-3-jdk17", validation_namespace: "jakarta", dto_placement: "web", openapi_freeze_ref: "openapi://quality-rule@1", fields: { quality_rule: { create: ["rule_name"], update: ["id", "rule_name"], query: ["rule_name"], response: ["id", "rule_name"] } } }));
14
- await generate(parseArgs(["--metadata-file", metadata, "--contract-file", contract, "--base-package", "com.yss.demo", "--module-name", "demo", "--domain-segment", "quality", "--output-dir", path.join(root, "out"), "--application-service-package", "com.yss.demo.application.service", "--validation-namespace", "jakarta"]), { log() {}, warn() {} });
15
- const controller = await readFile(path.join(root, "out", "com", "yss", "demo", "rest", "QualityRuleController.java"), "utf8");
16
- const convertor = await readFile(path.join(root, "out", "com", "yss", "demo", "rest", "convertor", "QualityRuleWebConvertor.java"), "utf8");
17
- const createRequest = await readFile(path.join(root, "out", "com", "yss", "demo", "rest", "dto", "request", "QualityRuleCreateRequest.java"), "utf8");
66
+ const output = path.join(root, "out");
67
+ await writeFile(metadata, JSON.stringify(metadataValue));
68
+ await writeFile(contract, JSON.stringify(webContract(root, overrides)));
69
+ const args = ["--metadata-file", metadata, "--contract-file", contract, "--dto-wire-profile-file", dtoWireProfileFile, "--base-package", "com.yss.demo", "--module-name", "demo", "--domain-segment", "quality", "--output-dir", output, "--application-service-package", "com.yss.demo.application.service", "--validation-namespace", overrides.validation_namespace ?? "jakarta"];
70
+ return { root, metadata, contract, output, args };
71
+ }
72
+
73
+ test("generates approved standalone Web code and exposes only approved pagination fields", async () => {
74
+ const data = await fixture();
75
+ await generate(parseArgs(data.args), { log() {}, warn() {} });
76
+ const rest = path.join(data.output, "com", "yss", "demo", "rest");
77
+ const controller = await readFile(path.join(rest, "QualityRuleController.java"), "utf8");
78
+ const convertor = await readFile(path.join(rest, "convertor", "QualityRuleWebConvertor.java"), "utf8");
79
+ const createRequest = await readFile(path.join(rest, "dto", "request", "QualityRuleCreateRequest.java"), "utf8");
80
+ const pageRequest = await readFile(path.join(rest, "dto", "request", "QualityRulePageRequest.java"), "utf8");
18
81
  assert.match(controller, /import com\.yss\.demo\.application\.service\.QualityRuleService;/);
82
+ assert.match(controller, /SingleResult\.of\(/);
19
83
  assert.match(controller, /import jakarta\.validation\.Valid;/);
20
- assert.match(controller, /webConvertor\.toCreateCommand\(request\)/);
21
84
  assert.match(convertor, /@Mapper\(componentModel = "spring"\)/);
22
- assert.match(convertor, /application\.command\.QualityRuleCreateCommand/);
23
- assert.match(convertor, /application\.result\.QualityRuleResult/);
85
+ assert.match(convertor, /PageResult\.of\(\s*toResponses\(source\.getData\(\)\),\s*source\.getTotalCount\(\),\s*source\.getPageSize\(\),\s*source\.getPageIndex\(\)/s);
24
86
  assert.doesNotMatch(`${controller}\n${convertor}`, /Gateway|\.domain\.|Mappers\.getMapper|INSTANCE/);
25
87
  assert.match(createRequest, /private String ruleName;/);
26
88
  assert.doesNotMatch(createRequest, /private Long id;|@Data/);
89
+ assert.match(pageRequest, /private int pageIndex = 1;/);
90
+ assert.match(pageRequest, /private int pageSize = 10;/);
91
+ assert.doesNotMatch(pageRequest, /orderBy|orderDirection|groupBy|offset|needTotalCount|tempTotalCount|extends PageQuery/);
27
92
  });
28
93
 
29
- test("rejects missing required CLI arguments", () => assert.throws(() => parseArgs(["--module-name", "demo"]), /required/));
94
+ test("rejects a yss-dto wire profile whose digest differs from the approved contract", async () => {
95
+ const data = await fixture({ dto_wire_profile_digest: "0".repeat(64) });
96
+ await assert.rejects(() => generate(parseArgs(data.args), { log() {}, warn() {} }), /yss-dto wire profile digest/);
97
+ });
30
98
 
31
- test("requires an approved target-profile field contract and refuses force overwrite", async () => {
32
- const root = await mkdtemp(path.join(tmpdir(), "yss-controller-contract-"));
33
- const metadata = path.join(root, "metadata.json");
34
- const contract = path.join(root, "web-contract.json");
35
- await writeFile(metadata, JSON.stringify({ tables: [{ table_name: "quality_rule", columns: [{ name: "id", sql_type: "bigint", primary: true, nullable: false }] }] }));
36
- await writeFile(contract, JSON.stringify({ schema_version: 1, status: "draft", base_package: "com.yss.demo", module_name: "demo", domain_segment: "quality", architecture_profile: "target-domain-model", platform_profile: "spring-boot-2.7-jdk8", validation_namespace: "javax", dto_placement: "web", openapi_freeze_ref: "openapi://quality-rule@1", fields: { quality_rule: { create: [], update: ["id"], query: [], response: ["id"] } } }));
37
- const base = ["--metadata-file", metadata, "--contract-file", contract, "--base-package", "com.yss.demo", "--module-name", "demo", "--domain-segment", "quality", "--output-dir", path.join(root, "out")];
38
- await assert.rejects(() => generate(parseArgs(base), { log() {}, warn() {} }), /approved/);
39
- await writeFile(contract, JSON.stringify({ schema_version: 1, status: "approved", base_package: "com.yss.demo", module_name: "demo", domain_segment: "quality", architecture_profile: "target-domain-model", platform_profile: "spring-boot-2.7-jdk8", validation_namespace: "javax", dto_placement: "web", openapi_freeze_ref: "openapi://quality-rule@1", fields: { quality_rule: { create: [], update: ["id"], query: [], response: ["id"] } } }));
40
- await writeFile(contract, JSON.stringify({ schema_version: 1, status: "approved", base_package: "com.yss.demo", module_name: "demo", domain_segment: "quality", architecture_profile: "target-domain-model", platform_profile: "spring-boot-3-jdk17", validation_namespace: "javax", dto_placement: "web", openapi_freeze_ref: "openapi://quality-rule@1", fields: { quality_rule: { create: [], update: ["id"], query: [], response: ["id"] } } }));
41
- await assert.rejects(() => generate(parseArgs(base), { log() {}, warn() {} }), /platform_profile and validation_namespace/);
42
- assert.throws(() => parseArgs([...base, "--force"]), /initialize-only/);
99
+ test("rejects missing CLI arguments, schema v1, draft contracts, and inconsistent platform profiles", async () => {
100
+ assert.throws(() => parseArgs(["--module-name", "demo"]), /required/);
101
+ const v1 = await fixture({ schema_version: 1 });
102
+ await assert.rejects(() => generate(parseArgs(v1.args), { log() {}, warn() {} }), /schema_version=1.*unsupported/);
103
+ const draft = await fixture({ status: "draft" });
104
+ await assert.rejects(() => generate(parseArgs(draft.args), { log() {}, warn() {} }), /approved/);
105
+ const mismatch = await fixture({ platform_profile: "spring-boot-3-jdk17", validation_namespace: "javax" });
106
+ await assert.rejects(() => generate(parseArgs(mismatch.args), { log() {}, warn() {} }), /platform_profile and validation_namespace/);
107
+ assert.throws(() => parseArgs([...draft.args, "--force"]), /initialize-only/);
43
108
  });
44
109
 
45
- test("binds generation identity to the approved contract and fails before writing when any target exists", async () => {
46
- const root = await mkdtemp(path.join(tmpdir(), "yss-controller-initialize-only-"));
47
- const metadata = path.join(root, "metadata.json");
48
- const contract = path.join(root, "web-contract.json");
49
- const output = path.join(root, "out");
50
- await writeFile(metadata, JSON.stringify({ tables: [{ table_name: "quality_rule", columns: [{ name: "id", sql_type: "bigint", nullable: false }] }] }));
51
- await writeFile(contract, JSON.stringify({ schema_version: 1, status: "approved", base_package: "com.yss.demo", module_name: "demo", domain_segment: "quality", architecture_profile: "target-domain-model", platform_profile: "spring-boot-2.7-jdk8", validation_namespace: "javax", dto_placement: "web", openapi_freeze_ref: "openapi://quality-rule@1", fields: { quality_rule: { create: [], update: ["id"], query: [], response: ["id"] } } }));
52
- const args = ["--metadata-file", metadata, "--contract-file", contract, "--base-package", "com.yss.demo", "--module-name", "demo", "--domain-segment", "quality", "--output-dir", output];
53
- await assert.rejects(() => generate(parseArgs(args.map((value) => value === "demo" ? "other" : value)), { log() {}, warn() {} }), /approved web generation contract/);
54
- const existing = path.join(output, "com/yss/demo/rest/QualityRuleController.java");
110
+ test("binds identity and every target to the approved write scope before writing", async () => {
111
+ const data = await fixture();
112
+ await assert.rejects(() => generate(parseArgs(data.args.map((value) => value === "demo" ? "other" : value)), { log() {}, warn() {} }), /approved web generation contract/);
113
+ await writeFile(data.contract, JSON.stringify(webContract(data.root, { allowed_write_paths: [path.join(data.root, "different-root")] })));
114
+ await assert.rejects(() => generate(parseArgs(data.args), { log() {}, warn() {} }), /allowed_write_paths/);
115
+ await writeFile(data.contract, JSON.stringify(webContract(data.root)));
116
+ const existing = path.join(data.output, "com/yss/demo/rest/QualityRuleController.java");
55
117
  await mkdir(path.dirname(existing), { recursive: true });
56
118
  await writeFile(existing, "user-owned\n");
57
- await assert.rejects(() => generate(parseArgs(args), { log() {}, warn() {} }), /initialize-only/);
119
+ await assert.rejects(() => generate(parseArgs(data.args), { log() {}, warn() {} }), /initialize-only/);
58
120
  assert.equal(await readFile(existing, "utf8"), "user-owned\n");
59
- await assert.rejects(() => readFile(path.join(output, "com/yss/demo/rest/convertor/QualityRuleWebConvertor.java"), "utf8"), /ENOENT/);
121
+ await assert.rejects(() => readFile(path.join(data.output, "com/yss/demo/rest/convertor/QualityRuleWebConvertor.java"), "utf8"), /ENOENT/);
122
+ });
123
+
124
+ test("scaffold-v2 mode requires an empty-scaffold-verified manifest with matching profile and project layout", async () => {
125
+ const data = await fixture({ platform_profile: "spring-boot-2.7-jdk8", validation_namespace: "javax" });
126
+ const projectRoot = path.join(data.root, "demo-service");
127
+ const webProject = path.join(projectRoot, "demo-service-adapter", "demo-service-web");
128
+ const manifest = path.join(projectRoot, ".yss", "scaffold-generation.json");
129
+ await mkdir(path.dirname(manifest), { recursive: true });
130
+ await writeFile(manifest, JSON.stringify({ schema_version: 2, project_name: "demo-service", base_package: "com.yss.demo", completion_level: "empty-scaffold-verified", profiles: { architecture: "target-domain-model", platform: "spring-boot-2.7-jdk8", validation_namespace: "javax", dto_placement: "web" } }));
131
+ const contractValue = webContract(projectRoot, {
132
+ integration_mode: "scaffold-v2",
133
+ implementation_project_root: projectRoot,
134
+ scaffold_manifest_ref: manifest,
135
+ platform_profile: "spring-boot-3-jdk17",
136
+ validation_namespace: "jakarta",
137
+ allowed_write_paths: [path.join(webProject, "src/main/java/com/yss/demo/rest")]
138
+ });
139
+ await writeFile(data.contract, JSON.stringify(contractValue));
140
+ const args = ["--metadata-file", data.metadata, "--contract-file", data.contract, "--dto-wire-profile-file", dtoWireProfileFile, "--scaffold-manifest-file", manifest, "--base-package", "com.yss.demo", "--module-name", "demo", "--domain-segment", "quality", "--web-project-dir", webProject, "--application-service-package", "com.yss.demo.application.service", "--validation-namespace", "jakarta"];
141
+ await assert.rejects(() => generate(parseArgs(args), { log() {}, warn() {} }), /scaffold manifest.*platform/i);
142
+ contractValue.platform_profile = "spring-boot-2.7-jdk8";
143
+ contractValue.validation_namespace = "javax";
144
+ await writeFile(data.contract, JSON.stringify(contractValue));
145
+ await generate(parseArgs(args.map((value) => value === "jakarta" ? "javax" : value)), { log() {}, warn() {} });
146
+ });
147
+
148
+ test("rolls back files written before a filesystem failure", async () => {
149
+ const data = await fixture();
150
+ const rest = path.join(data.output, "com/yss/demo/rest");
151
+ await mkdir(rest, { recursive: true });
152
+ await writeFile(path.join(rest, "convertor"), "blocking-file\n");
153
+ await assert.rejects(() => generate(parseArgs(data.args), { log() {}, warn() {} }));
154
+ await assert.rejects(() => readFile(path.join(rest, "QualityRuleController.java"), "utf8"), /ENOENT/);
155
+ assert.equal(await readFile(path.join(rest, "convertor"), "utf8"), "blocking-file\n");
60
156
  });
@@ -84,7 +84,7 @@ Off the main flow entirely.
84
84
  - **`/grilling`** — the interview primitive itself: rounds, the frontier, facts are the agent's job and decisions are yours. `/grill-me` and `/grill-with-docs` are the two named ways in, and `/triage`, `/wayfinder` and `/improve-codebase-architecture` all run it internally. Reach for it directly only when you want the interview with no wrapper around it.
85
85
  - **`/resolving-merge-conflicts`** — work an in-progress merge or rebase conflict hunk by hunk, resolving by **intent** traced to each side's primary source rather than by picking lines, then finish the operation. It never runs `--abort`. Standalone and off every flow: reach for it when you are already mid-conflict.
86
86
  - **`/prototype`** — a small, throwaway program that answers one design question: does this state model feel right, or what should this UI look like. Throwaway is a constraint on how the code is written, not a promise to destroy it: the answer folds into the real code, and the prototype itself is kept as a **primary source** on a `prototype/<name>` branch out of main, pointed at from the implementation issue. It's the detour in step 2 of the main flow, but reach for it any time a design question is hard to settle on paper.
87
- - **`/research`** — delegate reading legwork to a **background agent**: it investigates a question against **primary sources**, then leaves a cited Markdown file in the repo. Keep working while it reads. The file it produces is something to take *into* the main flow at `/grill-with-docs` — research feeds the thinking, it doesn't replace it.
87
+ - **`/yss-research`** — investigate technical or strategy evidence using `technical-evidence` / `strategy-evidence` and `quick` / `evidence-audited` modes. Use a background agent when the runtime supports it and parallel reading is useful; delegation is not a trust guarantee. The old `/research` name is a deprecated alias. Research feeds `/grill-with-docs` and lifecycle decisions; it does not approve them.
88
88
  - **`/to-questionnaire`** — when the thing blocking you isn't in your head or the codebase but in **someone else's**, this writes them a questionnaire to fill in. It's the inverse of `/grill-me`: instead of interviewing you about the subject, it interviews you about the **send** — who it's going to, what you need back — and aims the questions at the gap. What comes back is material for `/grill-with-docs` or `/to-spec`.
89
89
  - **`/wait-what`** — the corrective for a message that didn't land. Use it mid-conversation, inside any other skill, and the agent re-pitches what it just said with the context you were missing, in plain English, using the `CONTEXT.md` vocabulary. It works after the fact; `/grill-with-docs` is the upfront cure, because a shared language agreed early is what stops the jargon arriving at all.
90
90
  - **`/teach`** — learn a concept over multiple sessions, using the current directory as a stateful workspace.
@@ -74,7 +74,7 @@ The answer isn't part of the body — it's recorded on resolution (see [Work thr
74
74
 
75
75
  Every ticket is either **HITL** — human in the loop, worked _with_ a human who speaks for themselves — or **AFK**, driven by the agent alone. A HITL ticket only resolves through that live exchange; the agent never stands in for the human's side of it (a grilling agent that answers its own questions has broken this).
76
76
 
77
- - **Research** (AFK): Reading documentation, third-party APIs, or local resources like knowledge bases to surface a fact a decision waits on. Resolved by a `/research` **subagent**. Use when knowledge outside the current working directory is required.
77
+ - **Research** (AFK): Reading documentation, third-party APIs, or local resources like knowledge bases to surface a fact a decision waits on. Resolved by a `/yss-research` **subagent** (`/research` is a deprecated alias). Use when knowledge outside the current working directory is required.
78
78
  - **Prototype** (HITL): Raise the fidelity of the discussion by making a cheap, rough, concrete artifact to react to — an outline, a rough take, a stub, or UI/logic code via the /prototype skill. Links the prototype as an asset. Use when "how should it look" or "how should it behave" is the key question.
79
79
  - **Grilling** (HITL): Conversation. The default case. Always invoke the /grilling and /domain-modeling skills.
80
80
  - **Task** (HITL or AFK): Manual work that must happen before a _decision_ can be made — nothing to decide, prototype, or research, but the discussion is blocked until it's done. Signing up for a service so its API can be judged, provisioning access, moving data so its shape can be seen. This is the one type that _does_ rather than decides — and it earns its place by unblocking a decision, not by delivering the destination. The agent drives it alone where it can (AFK); otherwise it hands the human a precise checklist (HITL). Resolved when the work is done; the answer records what was done and any resulting facts (credentials location, new URLs, row counts) later tickets depend on.
@@ -112,7 +112,7 @@ User invokes with a loose idea.
112
112
  2. **Map the frontier.** Grill again, **breadth-first** this time: fan out across the whole space rather than deep on any one thread, surfacing the open decisions and the first steps takeable now. **If this surfaces no fog** — the way to the destination is already clear, the whole journey small enough for one session — you don't need a map. Stop and ask the user how they'd like to proceed.
113
113
  3. **Create the map** (label `wayfinder:map`): Destination and Notes filled in, Decisions-so-far empty, the fog sketched into **Not yet specified**.
114
114
  4. **Create the tickets you can specify now** as child issues of the map — then wire blocking edges in a **second pass** (issues need ids before they can reference each other). Wiring sorts them into the frontier and the blocked; everything you can't yet specify stays in the fog — the **Not yet specified** section.
115
- 5. **Fire the research subagents.** For each `research` ticket you just created, spin up a `/research` subagent to resolve it in parallel, capturing its findings on a throwaway `research/<name>` branch with a context pointer from the ticket.
115
+ 5. **Fire the research subagents.** For each `research` ticket you just created, spin up a `/yss-research` subagent to resolve it in parallel, capturing its findings on a throwaway `research/<name>` branch with a context pointer from the ticket. `/research` remains only as a deprecated compatibility alias.
116
116
  6. Stop — charting is one session's work; it hand-resolves nothing.
117
117
 
118
118
  ### Work through the map