@dewtech/dare-cli 3.0.0 → 3.1.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 (453) hide show
  1. package/README.md +45 -39
  2. package/dist/bin/dare.js +1 -3
  3. package/dist/bin/dare.js.map +1 -1
  4. package/dist/commands/__tests__/init.integration.spec.d.ts +2 -0
  5. package/dist/commands/__tests__/init.integration.spec.d.ts.map +1 -0
  6. package/dist/commands/__tests__/init.integration.spec.js +134 -0
  7. package/dist/commands/__tests__/init.integration.spec.js.map +1 -0
  8. package/dist/commands/init.d.ts.map +1 -1
  9. package/dist/commands/init.js +84 -1
  10. package/dist/commands/init.js.map +1 -1
  11. package/dist/commands/new.d.ts.map +1 -1
  12. package/dist/commands/new.js +2 -1
  13. package/dist/commands/new.js.map +1 -1
  14. package/dist/mcp-server/bin/server.js +0 -0
  15. package/dist/stacks/__tests__/dna-emitter.spec.d.ts +2 -0
  16. package/dist/stacks/__tests__/dna-emitter.spec.d.ts.map +1 -0
  17. package/dist/stacks/__tests__/dna-emitter.spec.js +207 -0
  18. package/dist/stacks/__tests__/dna-emitter.spec.js.map +1 -0
  19. package/dist/stacks/__tests__/dna.spec.d.ts +2 -0
  20. package/dist/stacks/__tests__/dna.spec.d.ts.map +1 -0
  21. package/dist/stacks/__tests__/dna.spec.js +211 -0
  22. package/dist/stacks/__tests__/dna.spec.js.map +1 -0
  23. package/dist/stacks/__tests__/parity-rails.fixture.json +228 -0
  24. package/dist/stacks/__tests__/parity-rails.spec.d.ts +2 -0
  25. package/dist/stacks/__tests__/parity-rails.spec.d.ts.map +1 -0
  26. package/dist/stacks/__tests__/parity-rails.spec.js +99 -0
  27. package/dist/stacks/__tests__/parity-rails.spec.js.map +1 -0
  28. package/dist/stacks/__tests__/registry.spec.d.ts +2 -0
  29. package/dist/stacks/__tests__/registry.spec.d.ts.map +1 -0
  30. package/dist/stacks/__tests__/registry.spec.js +101 -0
  31. package/dist/stacks/__tests__/registry.spec.js.map +1 -0
  32. package/dist/stacks/__tests__/template-engine.spec.d.ts +2 -0
  33. package/dist/stacks/__tests__/template-engine.spec.d.ts.map +1 -0
  34. package/dist/stacks/__tests__/template-engine.spec.js +149 -0
  35. package/dist/stacks/__tests__/template-engine.spec.js.map +1 -0
  36. package/dist/stacks/dna-emitter.d.ts +45 -0
  37. package/dist/stacks/dna-emitter.d.ts.map +1 -0
  38. package/dist/stacks/dna-emitter.js +267 -0
  39. package/dist/stacks/dna-emitter.js.map +1 -0
  40. package/dist/stacks/go-gin/__tests__/scaffold.spec.d.ts +2 -0
  41. package/dist/stacks/go-gin/__tests__/scaffold.spec.d.ts.map +1 -0
  42. package/dist/stacks/go-gin/__tests__/scaffold.spec.js +221 -0
  43. package/dist/stacks/go-gin/__tests__/scaffold.spec.js.map +1 -0
  44. package/dist/stacks/go-gin/scaffold.d.ts +3 -0
  45. package/dist/stacks/go-gin/scaffold.d.ts.map +1 -0
  46. package/dist/stacks/go-gin/scaffold.js +105 -0
  47. package/dist/stacks/go-gin/scaffold.js.map +1 -0
  48. package/dist/stacks/go-stdlib/__tests__/scaffold.spec.d.ts +2 -0
  49. package/dist/stacks/go-stdlib/__tests__/scaffold.spec.d.ts.map +1 -0
  50. package/dist/stacks/go-stdlib/__tests__/scaffold.spec.js +215 -0
  51. package/dist/stacks/go-stdlib/__tests__/scaffold.spec.js.map +1 -0
  52. package/dist/stacks/go-stdlib/scaffold.d.ts +3 -0
  53. package/dist/stacks/go-stdlib/scaffold.d.ts.map +1 -0
  54. package/dist/stacks/go-stdlib/scaffold.js +106 -0
  55. package/dist/stacks/go-stdlib/scaffold.js.map +1 -0
  56. package/dist/stacks/mcp-go/__tests__/scaffold.spec.d.ts +2 -0
  57. package/dist/stacks/mcp-go/__tests__/scaffold.spec.d.ts.map +1 -0
  58. package/dist/stacks/mcp-go/__tests__/scaffold.spec.js +203 -0
  59. package/dist/stacks/mcp-go/__tests__/scaffold.spec.js.map +1 -0
  60. package/dist/stacks/mcp-go/scaffold.d.ts +3 -0
  61. package/dist/stacks/mcp-go/scaffold.d.ts.map +1 -0
  62. package/dist/stacks/mcp-go/scaffold.js +94 -0
  63. package/dist/stacks/mcp-go/scaffold.js.map +1 -0
  64. package/dist/stacks/mcp-node-ts/__tests__/scaffold.spec.d.ts +2 -0
  65. package/dist/stacks/mcp-node-ts/__tests__/scaffold.spec.d.ts.map +1 -0
  66. package/dist/stacks/mcp-node-ts/__tests__/scaffold.spec.js +236 -0
  67. package/dist/stacks/mcp-node-ts/__tests__/scaffold.spec.js.map +1 -0
  68. package/dist/stacks/mcp-node-ts/scaffold.d.ts +3 -0
  69. package/dist/stacks/mcp-node-ts/scaffold.d.ts.map +1 -0
  70. package/dist/stacks/mcp-node-ts/scaffold.js +95 -0
  71. package/dist/stacks/mcp-node-ts/scaffold.js.map +1 -0
  72. package/dist/stacks/mcp-python/__tests__/scaffold.spec.d.ts +2 -0
  73. package/dist/stacks/mcp-python/__tests__/scaffold.spec.d.ts.map +1 -0
  74. package/dist/stacks/mcp-python/__tests__/scaffold.spec.js +228 -0
  75. package/dist/stacks/mcp-python/__tests__/scaffold.spec.js.map +1 -0
  76. package/dist/stacks/mcp-python/scaffold.d.ts +3 -0
  77. package/dist/stacks/mcp-python/scaffold.d.ts.map +1 -0
  78. package/dist/stacks/mcp-python/scaffold.js +98 -0
  79. package/dist/stacks/mcp-python/scaffold.js.map +1 -0
  80. package/dist/stacks/mcp-rust/__tests__/scaffold.spec.d.ts +2 -0
  81. package/dist/stacks/mcp-rust/__tests__/scaffold.spec.d.ts.map +1 -0
  82. package/dist/stacks/mcp-rust/__tests__/scaffold.spec.js +213 -0
  83. package/dist/stacks/mcp-rust/__tests__/scaffold.spec.js.map +1 -0
  84. package/dist/stacks/mcp-rust/scaffold.d.ts +3 -0
  85. package/dist/stacks/mcp-rust/scaffold.d.ts.map +1 -0
  86. package/dist/stacks/mcp-rust/scaffold.js +98 -0
  87. package/dist/stacks/mcp-rust/scaffold.js.map +1 -0
  88. package/dist/stacks/node-nestjs/__tests__/scaffold.spec.d.ts +2 -0
  89. package/dist/stacks/node-nestjs/__tests__/scaffold.spec.d.ts.map +1 -0
  90. package/dist/stacks/node-nestjs/__tests__/scaffold.spec.js +172 -0
  91. package/dist/stacks/node-nestjs/__tests__/scaffold.spec.js.map +1 -0
  92. package/dist/stacks/node-nestjs/scaffold.d.ts +3 -0
  93. package/dist/stacks/node-nestjs/scaffold.d.ts.map +1 -0
  94. package/dist/stacks/node-nestjs/scaffold.js +117 -0
  95. package/dist/stacks/node-nestjs/scaffold.js.map +1 -0
  96. package/dist/stacks/php-laravel/__tests__/scaffold.spec.d.ts +2 -0
  97. package/dist/stacks/php-laravel/__tests__/scaffold.spec.d.ts.map +1 -0
  98. package/dist/stacks/php-laravel/__tests__/scaffold.spec.js +205 -0
  99. package/dist/stacks/php-laravel/__tests__/scaffold.spec.js.map +1 -0
  100. package/dist/stacks/php-laravel/scaffold.d.ts +3 -0
  101. package/dist/stacks/php-laravel/scaffold.d.ts.map +1 -0
  102. package/dist/stacks/php-laravel/scaffold.js +109 -0
  103. package/dist/stacks/php-laravel/scaffold.js.map +1 -0
  104. package/dist/stacks/python-fastapi/__tests__/scaffold.spec.d.ts +2 -0
  105. package/dist/stacks/python-fastapi/__tests__/scaffold.spec.d.ts.map +1 -0
  106. package/dist/stacks/python-fastapi/__tests__/scaffold.spec.js +168 -0
  107. package/dist/stacks/python-fastapi/__tests__/scaffold.spec.js.map +1 -0
  108. package/dist/stacks/python-fastapi/scaffold.d.ts +3 -0
  109. package/dist/stacks/python-fastapi/scaffold.d.ts.map +1 -0
  110. package/dist/stacks/python-fastapi/scaffold.js +108 -0
  111. package/dist/stacks/python-fastapi/scaffold.js.map +1 -0
  112. package/dist/stacks/registry.d.ts +38 -0
  113. package/dist/stacks/registry.d.ts.map +1 -0
  114. package/dist/stacks/registry.js +153 -0
  115. package/dist/stacks/registry.js.map +1 -0
  116. package/dist/stacks/ruby-rails-8/__tests__/scaffold.spec.d.ts +6 -0
  117. package/dist/stacks/ruby-rails-8/__tests__/scaffold.spec.d.ts.map +1 -0
  118. package/dist/stacks/ruby-rails-8/__tests__/scaffold.spec.js +604 -0
  119. package/dist/stacks/ruby-rails-8/__tests__/scaffold.spec.js.map +1 -0
  120. package/dist/stacks/ruby-rails-8/scaffold.d.ts +91 -0
  121. package/dist/stacks/ruby-rails-8/scaffold.d.ts.map +1 -0
  122. package/dist/stacks/ruby-rails-8/scaffold.js +410 -0
  123. package/dist/stacks/ruby-rails-8/scaffold.js.map +1 -0
  124. package/dist/stacks/rust-axum/__tests__/scaffold.spec.d.ts +2 -0
  125. package/dist/stacks/rust-axum/__tests__/scaffold.spec.d.ts.map +1 -0
  126. package/dist/stacks/rust-axum/__tests__/scaffold.spec.js +203 -0
  127. package/dist/stacks/rust-axum/__tests__/scaffold.spec.js.map +1 -0
  128. package/dist/stacks/rust-axum/scaffold.d.ts +3 -0
  129. package/dist/stacks/rust-axum/scaffold.d.ts.map +1 -0
  130. package/dist/stacks/rust-axum/scaffold.js +105 -0
  131. package/dist/stacks/rust-axum/scaffold.js.map +1 -0
  132. package/dist/stacks/template-engine.d.ts +38 -0
  133. package/dist/stacks/template-engine.d.ts.map +1 -0
  134. package/dist/stacks/template-engine.js +134 -0
  135. package/dist/stacks/template-engine.js.map +1 -0
  136. package/dist/stacks/types.d.ts +69 -0
  137. package/dist/stacks/types.d.ts.map +1 -0
  138. package/dist/stacks/types.js +29 -0
  139. package/dist/stacks/types.js.map +1 -0
  140. package/dist/utils/project-generator.d.ts +1 -1
  141. package/dist/utils/project-generator.d.ts.map +1 -1
  142. package/dist/utils/project-generator.js +16 -7
  143. package/dist/utils/project-generator.js.map +1 -1
  144. package/dist/utils/stack-bootstrap.d.ts +3 -2
  145. package/dist/utils/stack-bootstrap.d.ts.map +1 -1
  146. package/dist/utils/stack-bootstrap.js +46 -16
  147. package/dist/utils/stack-bootstrap.js.map +1 -1
  148. package/package.json +91 -87
  149. package/templates/stacks/go-gin/.dare/skills.yml +11 -0
  150. package/templates/stacks/go-gin/.env.example +24 -0
  151. package/templates/stacks/go-gin/.github/workflows/dare-ci.yml +42 -0
  152. package/templates/stacks/go-gin/README.md.tpl +38 -0
  153. package/templates/stacks/go-gin/cmd/server/main.go.tpl +78 -0
  154. package/templates/stacks/go-gin/db/migrations/0001_create_users.down.sql +2 -0
  155. package/templates/stacks/go-gin/db/migrations/0001_create_users.up.sql +12 -0
  156. package/templates/stacks/go-gin/db/queries/users.sql +23 -0
  157. package/templates/stacks/go-gin/gitignore +7 -0
  158. package/templates/stacks/go-gin/go.mod.tpl +17 -0
  159. package/templates/stacks/go-gin/internal/config/config.go +41 -0
  160. package/templates/stacks/go-gin/internal/db/postgres.go.tpl +25 -0
  161. package/templates/stacks/go-gin/internal/handler/auth_handler.go.tpl +72 -0
  162. package/templates/stacks/go-gin/internal/handler/users_handler.go.tpl +72 -0
  163. package/templates/stacks/go-gin/internal/handler/ws_handler.go +37 -0
  164. package/templates/stacks/go-gin/internal/llm/dummy.go +14 -0
  165. package/templates/stacks/go-gin/internal/llm/provider.go +8 -0
  166. package/templates/stacks/go-gin/internal/middleware/jwt.go.tpl +58 -0
  167. package/templates/stacks/go-gin/internal/middleware/rate_limit.go +55 -0
  168. package/templates/stacks/go-gin/internal/model/user.go +17 -0
  169. package/templates/stacks/go-gin/internal/repository/users_repository.go.tpl +79 -0
  170. package/templates/stacks/go-gin/internal/service/auth_service.go.tpl +55 -0
  171. package/templates/stacks/go-gin/internal/service/users_service.go.tpl +53 -0
  172. package/templates/stacks/go-gin/llms.txt.tpl +54 -0
  173. package/templates/stacks/go-gin/openapi.json.tpl +46 -0
  174. package/templates/stacks/go-gin/sqlc.yaml +14 -0
  175. package/templates/stacks/go-gin/tests/smoke_test.go.tpl +22 -0
  176. package/templates/stacks/go-stdlib/.dare/skills.yml +11 -0
  177. package/templates/stacks/go-stdlib/.env.example +24 -0
  178. package/templates/stacks/go-stdlib/.github/workflows/dare-ci.yml +42 -0
  179. package/templates/stacks/go-stdlib/README.md.tpl +41 -0
  180. package/templates/stacks/go-stdlib/cmd/server/main.go.tpl +82 -0
  181. package/templates/stacks/go-stdlib/db/migrations/0001_create_users.down.sql +2 -0
  182. package/templates/stacks/go-stdlib/db/migrations/0001_create_users.up.sql +12 -0
  183. package/templates/stacks/go-stdlib/db/queries/users.sql +23 -0
  184. package/templates/stacks/go-stdlib/gitignore +6 -0
  185. package/templates/stacks/go-stdlib/go.mod.tpl +15 -0
  186. package/templates/stacks/go-stdlib/internal/config/config.go +41 -0
  187. package/templates/stacks/go-stdlib/internal/db/postgres.go.tpl +24 -0
  188. package/templates/stacks/go-stdlib/internal/handler/auth_handler.go.tpl +71 -0
  189. package/templates/stacks/go-stdlib/internal/handler/users_handler.go.tpl +84 -0
  190. package/templates/stacks/go-stdlib/internal/handler/ws_handler.go +36 -0
  191. package/templates/stacks/go-stdlib/internal/httpx/json.go +32 -0
  192. package/templates/stacks/go-stdlib/internal/llm/dummy.go +14 -0
  193. package/templates/stacks/go-stdlib/internal/llm/provider.go +8 -0
  194. package/templates/stacks/go-stdlib/internal/middleware/chain.go +21 -0
  195. package/templates/stacks/go-stdlib/internal/middleware/cors.go +27 -0
  196. package/templates/stacks/go-stdlib/internal/middleware/jwt.go.tpl +51 -0
  197. package/templates/stacks/go-stdlib/internal/middleware/rate_limit.go +81 -0
  198. package/templates/stacks/go-stdlib/internal/model/user.go +17 -0
  199. package/templates/stacks/go-stdlib/internal/repository/users_repository.go.tpl +75 -0
  200. package/templates/stacks/go-stdlib/internal/service/auth_service.go.tpl +55 -0
  201. package/templates/stacks/go-stdlib/internal/service/users_service.go.tpl +53 -0
  202. package/templates/stacks/go-stdlib/llms.txt.tpl +60 -0
  203. package/templates/stacks/go-stdlib/openapi.json.tpl +46 -0
  204. package/templates/stacks/go-stdlib/sqlc.yaml +14 -0
  205. package/templates/stacks/go-stdlib/tests/smoke_test.go.tpl +45 -0
  206. package/templates/stacks/mcp-go/.dare/skills.yml +8 -0
  207. package/templates/stacks/mcp-go/.env.example +14 -0
  208. package/templates/stacks/mcp-go/.github/workflows/dare-ci.yml +42 -0
  209. package/templates/stacks/mcp-go/README.md.tpl +50 -0
  210. package/templates/stacks/mcp-go/cmd/server/main.go.tpl +62 -0
  211. package/templates/stacks/mcp-go/gitignore +6 -0
  212. package/templates/stacks/mcp-go/go.mod.tpl +9 -0
  213. package/templates/stacks/mcp-go/internal/prompts/summarize.go +9 -0
  214. package/templates/stacks/mcp-go/internal/server/server.go.tpl +80 -0
  215. package/templates/stacks/mcp-go/internal/tools/echo.go +15 -0
  216. package/templates/stacks/mcp-go/internal/transports/http.go.tpl +21 -0
  217. package/templates/stacks/mcp-go/internal/transports/sse.go.tpl +17 -0
  218. package/templates/stacks/mcp-go/internal/transports/stdio.go.tpl +14 -0
  219. package/templates/stacks/mcp-go/llms.txt.tpl +60 -0
  220. package/templates/stacks/mcp-go/openapi.json.tpl +31 -0
  221. package/templates/stacks/mcp-go/tests/echo_test.go.tpl +37 -0
  222. package/templates/stacks/mcp-node-ts/.dare/skills.yml +8 -0
  223. package/templates/stacks/mcp-node-ts/.env.example +16 -0
  224. package/templates/stacks/mcp-node-ts/.github/workflows/dare-ci.yml +54 -0
  225. package/templates/stacks/mcp-node-ts/README.md.hbs +49 -0
  226. package/templates/stacks/mcp-node-ts/gitignore +7 -0
  227. package/templates/stacks/mcp-node-ts/llms.txt.hbs +61 -0
  228. package/templates/stacks/mcp-node-ts/openapi.json.hbs +39 -0
  229. package/templates/stacks/mcp-node-ts/package.json.hbs +35 -0
  230. package/templates/stacks/mcp-node-ts/src/cli.ts.hbs +71 -0
  231. package/templates/stacks/mcp-node-ts/src/prompts/index.ts +36 -0
  232. package/templates/stacks/mcp-node-ts/src/server.ts.hbs +45 -0
  233. package/templates/stacks/mcp-node-ts/src/tools/echo.ts +23 -0
  234. package/templates/stacks/mcp-node-ts/src/tools/index.ts +18 -0
  235. package/templates/stacks/mcp-node-ts/src/transports/http.ts +68 -0
  236. package/templates/stacks/mcp-node-ts/src/transports/sse.ts +58 -0
  237. package/templates/stacks/mcp-node-ts/src/transports/stdio.ts +5 -0
  238. package/templates/stacks/mcp-node-ts/tests/echo.test.ts +50 -0
  239. package/templates/stacks/mcp-node-ts/tsconfig.json +17 -0
  240. package/templates/stacks/mcp-python/.dare/skills.yml +8 -0
  241. package/templates/stacks/mcp-python/.env.example +14 -0
  242. package/templates/stacks/mcp-python/.github/workflows/dare-ci.yml +42 -0
  243. package/templates/stacks/mcp-python/README.md.j2 +49 -0
  244. package/templates/stacks/mcp-python/gitignore +12 -0
  245. package/templates/stacks/mcp-python/llms.txt.j2 +56 -0
  246. package/templates/stacks/mcp-python/openapi.json.j2 +33 -0
  247. package/templates/stacks/mcp-python/pyproject.toml.j2 +37 -0
  248. package/templates/stacks/mcp-python/src/__init__.py +0 -0
  249. package/templates/stacks/mcp-python/src/cli.py.j2 +68 -0
  250. package/templates/stacks/mcp-python/src/prompts/__init__.py +0 -0
  251. package/templates/stacks/mcp-python/src/prompts/summarize.py +10 -0
  252. package/templates/stacks/mcp-python/src/server.py.j2 +28 -0
  253. package/templates/stacks/mcp-python/src/tools/__init__.py +0 -0
  254. package/templates/stacks/mcp-python/src/tools/echo.py +12 -0
  255. package/templates/stacks/mcp-python/src/transports/__init__.py +0 -0
  256. package/templates/stacks/mcp-python/src/transports/http.py +12 -0
  257. package/templates/stacks/mcp-python/src/transports/sse.py +13 -0
  258. package/templates/stacks/mcp-python/src/transports/stdio.py +6 -0
  259. package/templates/stacks/mcp-python/tests/__init__.py +0 -0
  260. package/templates/stacks/mcp-python/tests/test_echo.py +28 -0
  261. package/templates/stacks/mcp-rust/.dare/skills.yml +8 -0
  262. package/templates/stacks/mcp-rust/.env.example +14 -0
  263. package/templates/stacks/mcp-rust/.github/workflows/dare-ci.yml +38 -0
  264. package/templates/stacks/mcp-rust/Cargo.toml.tera +35 -0
  265. package/templates/stacks/mcp-rust/README.md.tera +50 -0
  266. package/templates/stacks/mcp-rust/gitignore +5 -0
  267. package/templates/stacks/mcp-rust/llms.txt.tera +60 -0
  268. package/templates/stacks/mcp-rust/openapi.json.tera +31 -0
  269. package/templates/stacks/mcp-rust/src/cli.rs.tera +33 -0
  270. package/templates/stacks/mcp-rust/src/lib.rs +6 -0
  271. package/templates/stacks/mcp-rust/src/main.rs.tera +30 -0
  272. package/templates/stacks/mcp-rust/src/prompts/mod.rs +1 -0
  273. package/templates/stacks/mcp-rust/src/prompts/summarize.rs +5 -0
  274. package/templates/stacks/mcp-rust/src/server.rs.tera +38 -0
  275. package/templates/stacks/mcp-rust/src/tools/echo.rs +18 -0
  276. package/templates/stacks/mcp-rust/src/tools/mod.rs +22 -0
  277. package/templates/stacks/mcp-rust/src/transports/http.rs +27 -0
  278. package/templates/stacks/mcp-rust/src/transports/mod.rs +3 -0
  279. package/templates/stacks/mcp-rust/src/transports/sse.rs +33 -0
  280. package/templates/stacks/mcp-rust/src/transports/stdio.rs +14 -0
  281. package/templates/stacks/mcp-rust/tests/echo_test.rs.tera +27 -0
  282. package/templates/stacks/node-nestjs/.dare/skills.yml +11 -0
  283. package/templates/stacks/node-nestjs/.env.example +21 -0
  284. package/templates/stacks/node-nestjs/.github/workflows/dare-ci.yml +54 -0
  285. package/templates/stacks/node-nestjs/README.md.hbs +35 -0
  286. package/templates/stacks/node-nestjs/gitignore +7 -0
  287. package/templates/stacks/node-nestjs/llms.txt.hbs +47 -0
  288. package/templates/stacks/node-nestjs/nest-cli.json +16 -0
  289. package/templates/stacks/node-nestjs/openapi.json.hbs +75 -0
  290. package/templates/stacks/node-nestjs/package.json.hbs +57 -0
  291. package/templates/stacks/node-nestjs/prisma/schema.prisma +25 -0
  292. package/templates/stacks/node-nestjs/prisma/seed.ts.hbs +25 -0
  293. package/templates/stacks/node-nestjs/src/app.module.ts +39 -0
  294. package/templates/stacks/node-nestjs/src/auth/auth.controller.ts +29 -0
  295. package/templates/stacks/node-nestjs/src/auth/auth.module.ts +25 -0
  296. package/templates/stacks/node-nestjs/src/auth/auth.service.ts +36 -0
  297. package/templates/stacks/node-nestjs/src/auth/dto/login-response.dto.ts +9 -0
  298. package/templates/stacks/node-nestjs/src/auth/dto/login.dto.ts +17 -0
  299. package/templates/stacks/node-nestjs/src/auth/jwt.strategy.ts +25 -0
  300. package/templates/stacks/node-nestjs/src/common/filters/problem-details.filter.ts +38 -0
  301. package/templates/stacks/node-nestjs/src/common/interceptors/json-response.interceptor.ts +13 -0
  302. package/templates/stacks/node-nestjs/src/main.ts.hbs +44 -0
  303. package/templates/stacks/node-nestjs/src/prisma/prisma.module.ts +9 -0
  304. package/templates/stacks/node-nestjs/src/prisma/prisma.service.ts +9 -0
  305. package/templates/stacks/node-nestjs/src/users/dto/create-user.dto.ts +22 -0
  306. package/templates/stacks/node-nestjs/src/users/dto/user.dto.ts +15 -0
  307. package/templates/stacks/node-nestjs/src/users/users.controller.ts +41 -0
  308. package/templates/stacks/node-nestjs/src/users/users.module.ts +11 -0
  309. package/templates/stacks/node-nestjs/src/users/users.repository.ts +38 -0
  310. package/templates/stacks/node-nestjs/src/users/users.service.ts +38 -0
  311. package/templates/stacks/node-nestjs/tsconfig.build.json +4 -0
  312. package/templates/stacks/node-nestjs/tsconfig.json +28 -0
  313. package/templates/stacks/php-laravel/.dare/skills.yml +11 -0
  314. package/templates/stacks/php-laravel/.env.example +41 -0
  315. package/templates/stacks/php-laravel/.github/workflows/dare-ci.yml +43 -0
  316. package/templates/stacks/php-laravel/README.md.hbs +36 -0
  317. package/templates/stacks/php-laravel/app/Http/Controllers/Api/AuthController.php +36 -0
  318. package/templates/stacks/php-laravel/app/Http/Controllers/Api/UsersController.php +33 -0
  319. package/templates/stacks/php-laravel/app/Http/Requests/CreateUserRequest.php +26 -0
  320. package/templates/stacks/php-laravel/app/Http/Requests/LoginRequest.php +34 -0
  321. package/templates/stacks/php-laravel/app/Llm/Contracts/LlmProvider.php +12 -0
  322. package/templates/stacks/php-laravel/app/Llm/Providers/DummyProvider.php +13 -0
  323. package/templates/stacks/php-laravel/app/Llm/Providers/OpenAiProvider.php +33 -0
  324. package/templates/stacks/php-laravel/app/Models/User.php +44 -0
  325. package/templates/stacks/php-laravel/app/Repositories/UsersRepository.php +32 -0
  326. package/templates/stacks/php-laravel/app/Services/AuthService.php +37 -0
  327. package/templates/stacks/php-laravel/app/Services/UsersService.php +57 -0
  328. package/templates/stacks/php-laravel/artisan +12 -0
  329. package/templates/stacks/php-laravel/bootstrap/app.php +29 -0
  330. package/templates/stacks/php-laravel/bootstrap/providers.php +5 -0
  331. package/templates/stacks/php-laravel/composer.json.hbs +58 -0
  332. package/templates/stacks/php-laravel/config/l5-swagger.php +41 -0
  333. package/templates/stacks/php-laravel/config/reverb.php +34 -0
  334. package/templates/stacks/php-laravel/config/sanctum.php +15 -0
  335. package/templates/stacks/php-laravel/database/migrations/2026_06_01_000001_create_users_table.php +27 -0
  336. package/templates/stacks/php-laravel/database/seeders/DatabaseSeeder.php +21 -0
  337. package/templates/stacks/php-laravel/gitignore +23 -0
  338. package/templates/stacks/php-laravel/llms.txt.hbs +53 -0
  339. package/templates/stacks/php-laravel/openapi.json.hbs +43 -0
  340. package/templates/stacks/php-laravel/phpstan.neon +9 -0
  341. package/templates/stacks/php-laravel/routes/api.php +13 -0
  342. package/templates/stacks/php-laravel/routes/channels.php +7 -0
  343. package/templates/stacks/php-laravel/tests/Feature/AuthTest.php +35 -0
  344. package/templates/stacks/php-laravel/tests/Feature/UsersTest.php +30 -0
  345. package/templates/stacks/php-laravel/tests/Pest.php +5 -0
  346. package/templates/stacks/python-fastapi/.dare/skills.yml +11 -0
  347. package/templates/stacks/python-fastapi/.env.example +21 -0
  348. package/templates/stacks/python-fastapi/.github/workflows/dare-ci.yml +43 -0
  349. package/templates/stacks/python-fastapi/README.md.j2 +35 -0
  350. package/templates/stacks/python-fastapi/alembic/env.py +46 -0
  351. package/templates/stacks/python-fastapi/alembic/script.py.mako +26 -0
  352. package/templates/stacks/python-fastapi/alembic/versions/0001_create_users.py.j2 +37 -0
  353. package/templates/stacks/python-fastapi/alembic.ini.j2 +39 -0
  354. package/templates/stacks/python-fastapi/app/__init__.py +0 -0
  355. package/templates/stacks/python-fastapi/app/core/__init__.py +0 -0
  356. package/templates/stacks/python-fastapi/app/core/config.py +24 -0
  357. package/templates/stacks/python-fastapi/app/core/security.py +34 -0
  358. package/templates/stacks/python-fastapi/app/db/__init__.py +0 -0
  359. package/templates/stacks/python-fastapi/app/db/session.py +22 -0
  360. package/templates/stacks/python-fastapi/app/main.py.j2 +36 -0
  361. package/templates/stacks/python-fastapi/app/models/__init__.py +3 -0
  362. package/templates/stacks/python-fastapi/app/models/user.py +30 -0
  363. package/templates/stacks/python-fastapi/app/repositories/__init__.py +0 -0
  364. package/templates/stacks/python-fastapi/app/repositories/user_repository.py +34 -0
  365. package/templates/stacks/python-fastapi/app/routers/__init__.py +0 -0
  366. package/templates/stacks/python-fastapi/app/routers/auth.py +37 -0
  367. package/templates/stacks/python-fastapi/app/routers/users.py +46 -0
  368. package/templates/stacks/python-fastapi/app/schemas/__init__.py +0 -0
  369. package/templates/stacks/python-fastapi/app/schemas/user.py +56 -0
  370. package/templates/stacks/python-fastapi/app/services/__init__.py +0 -0
  371. package/templates/stacks/python-fastapi/app/services/auth_service.py +22 -0
  372. package/templates/stacks/python-fastapi/app/services/user_service.py +31 -0
  373. package/templates/stacks/python-fastapi/gitignore +12 -0
  374. package/templates/stacks/python-fastapi/llms.txt.j2 +53 -0
  375. package/templates/stacks/python-fastapi/openapi.json.j2 +43 -0
  376. package/templates/stacks/python-fastapi/pyproject.toml.j2 +45 -0
  377. package/templates/stacks/python-fastapi/tests/__init__.py +0 -0
  378. package/templates/stacks/python-fastapi/tests/test_auth.py +22 -0
  379. package/templates/stacks/ruby-rails-8/.dare/skills.yml +50 -0
  380. package/templates/stacks/ruby-rails-8/.env.example +20 -0
  381. package/templates/stacks/ruby-rails-8/.github/workflows/dare-ci.yml +112 -0
  382. package/templates/stacks/ruby-rails-8/Gemfile.erb +61 -0
  383. package/templates/stacks/ruby-rails-8/app/channels/application_cable/channel.rb +11 -0
  384. package/templates/stacks/ruby-rails-8/app/channels/application_cable/connection.rb +34 -0
  385. package/templates/stacks/ruby-rails-8/app/channels/dare_updates_channel.rb +18 -0
  386. package/templates/stacks/ruby-rails-8/app/channels/user_updates_channel.rb +23 -0
  387. package/templates/stacks/ruby-rails-8/app/controllers/application_controller.rb +44 -0
  388. package/templates/stacks/ruby-rails-8/app/controllers/concerns/problem_details.rb +93 -0
  389. package/templates/stacks/ruby-rails-8/app/handlers/summarize_handler.rb +33 -0
  390. package/templates/stacks/ruby-rails-8/app/handlers/users_handler.rb +68 -0
  391. package/templates/stacks/ruby-rails-8/app/llm/cache/llm_cache.rb +44 -0
  392. package/templates/stacks/ruby-rails-8/app/llm/prompts/prompt_loader.rb +54 -0
  393. package/templates/stacks/ruby-rails-8/app/llm/prompts/summarize_v1.jinja2 +12 -0
  394. package/templates/stacks/ruby-rails-8/app/llm/providers/dummy_provider.rb +35 -0
  395. package/templates/stacks/ruby-rails-8/app/llm/providers/llm_provider.rb +67 -0
  396. package/templates/stacks/ruby-rails-8/app/llm/providers/openai_provider.rb +62 -0
  397. package/templates/stacks/ruby-rails-8/app/llm/rate_limit/token_bucket.rb +82 -0
  398. package/templates/stacks/ruby-rails-8/app/llm/validators/summarize_output_schema.json +21 -0
  399. package/templates/stacks/ruby-rails-8/app/llm/validators/validator.rb +52 -0
  400. package/templates/stacks/ruby-rails-8/app/models/user.rb +36 -0
  401. package/templates/stacks/ruby-rails-8/app/presenters/user_presenter.rb +48 -0
  402. package/templates/stacks/ruby-rails-8/app/repositories/document_repository.rb +57 -0
  403. package/templates/stacks/ruby-rails-8/app/repositories/user_repository.rb +73 -0
  404. package/templates/stacks/ruby-rails-8/app/services/create_user_service.rb +67 -0
  405. package/templates/stacks/ruby-rails-8/app/services/realtime_service.rb +53 -0
  406. package/templates/stacks/ruby-rails-8/app/services/summarize_document_service.rb +57 -0
  407. package/templates/stacks/ruby-rails-8/config/dare.yml +42 -0
  408. package/templates/stacks/ruby-rails-8/config/initializers/dare.rb +31 -0
  409. package/templates/stacks/ruby-rails-8/config/initializers/rack_attack.rb +64 -0
  410. package/templates/stacks/ruby-rails-8/config/initializers/rswag_api.rb +12 -0
  411. package/templates/stacks/ruby-rails-8/lib/tasks/dare.rake +159 -0
  412. package/templates/stacks/ruby-rails-8/llms.txt.erb +69 -0
  413. package/templates/stacks/ruby-rails-8/spec/api/summarize_spec.rb +56 -0
  414. package/templates/stacks/ruby-rails-8/spec/api/users_spec.rb +72 -0
  415. package/templates/stacks/ruby-rails-8/spec/channels/dare_updates_channel_spec.rb +61 -0
  416. package/templates/stacks/ruby-rails-8/spec/channels/user_updates_channel_spec.rb +56 -0
  417. package/templates/stacks/ruby-rails-8/spec/factories/users.rb +27 -0
  418. package/templates/stacks/ruby-rails-8/spec/handlers/users_handler_spec.rb +88 -0
  419. package/templates/stacks/ruby-rails-8/spec/rails_helper.rb +31 -0
  420. package/templates/stacks/ruby-rails-8/spec/services/create_user_service_spec.rb +88 -0
  421. package/templates/stacks/ruby-rails-8/spec/services/summarize_document_service_spec.rb +142 -0
  422. package/templates/stacks/ruby-rails-8/spec/swagger_helper.rb +73 -0
  423. package/templates/stacks/rust-axum/.dare/skills.yml +11 -0
  424. package/templates/stacks/rust-axum/.env.example +26 -0
  425. package/templates/stacks/rust-axum/.github/workflows/dare-ci.yml +40 -0
  426. package/templates/stacks/rust-axum/Cargo.toml.tera +53 -0
  427. package/templates/stacks/rust-axum/README.md.tera +37 -0
  428. package/templates/stacks/rust-axum/gitignore +5 -0
  429. package/templates/stacks/rust-axum/llms.txt.tera +54 -0
  430. package/templates/stacks/rust-axum/migrations/0001_create_users.sql +13 -0
  431. package/templates/stacks/rust-axum/openapi.json.tera +46 -0
  432. package/templates/stacks/rust-axum/src/config.rs +45 -0
  433. package/templates/stacks/rust-axum/src/errors.rs +48 -0
  434. package/templates/stacks/rust-axum/src/handlers/auth.rs +48 -0
  435. package/templates/stacks/rust-axum/src/handlers/mod.rs +3 -0
  436. package/templates/stacks/rust-axum/src/handlers/users.rs +81 -0
  437. package/templates/stacks/rust-axum/src/handlers/ws.rs +24 -0
  438. package/templates/stacks/rust-axum/src/lib.rs +19 -0
  439. package/templates/stacks/rust-axum/src/llm/mod.rs +1 -0
  440. package/templates/stacks/rust-axum/src/llm/provider.rs +48 -0
  441. package/templates/stacks/rust-axum/src/main.rs.tera +64 -0
  442. package/templates/stacks/rust-axum/src/middleware/auth.rs +20 -0
  443. package/templates/stacks/rust-axum/src/middleware/mod.rs +2 -0
  444. package/templates/stacks/rust-axum/src/middleware/rate_limit.rs +27 -0
  445. package/templates/stacks/rust-axum/src/models/mod.rs +1 -0
  446. package/templates/stacks/rust-axum/src/models/user.rs +13 -0
  447. package/templates/stacks/rust-axum/src/repositories/mod.rs +1 -0
  448. package/templates/stacks/rust-axum/src/repositories/user_repository.rs +62 -0
  449. package/templates/stacks/rust-axum/src/services/auth_service.rs +50 -0
  450. package/templates/stacks/rust-axum/src/services/mod.rs +2 -0
  451. package/templates/stacks/rust-axum/src/services/user_service.rs +53 -0
  452. package/templates/stacks/rust-axum/tests/integration_test.rs.tera +13 -0
  453. package/LICENSE +0 -21
@@ -0,0 +1,604 @@
1
+ /**
2
+ * DARE v3.0 — RailsScaffold unit tests
3
+ * Verifies that the generator produces all required files and directories
4
+ */
5
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
6
+ import fs from 'fs-extra';
7
+ import path from 'path';
8
+ import os from 'os';
9
+ import { RailsScaffold } from '../scaffold.js';
10
+ // ── Helpers ────────────────────────────────────────────────────────────────────
11
+ async function readFile(dir, rel) {
12
+ return fs.readFile(path.join(dir, rel), 'utf-8');
13
+ }
14
+ async function fileExists(dir, rel) {
15
+ return fs.pathExists(path.join(dir, rel));
16
+ }
17
+ // ── Suite: default generation ──────────────────────────────────────────────────
18
+ describe('RailsScaffold — default generation', () => {
19
+ let tmpDir;
20
+ let outputDir;
21
+ let result;
22
+ beforeAll(async () => {
23
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dare-rails-test-'));
24
+ outputDir = path.join(tmpDir, 'myapp');
25
+ const scaffold = new RailsScaffold();
26
+ result = await scaffold.generate('myapp', { outputDir, verbose: false });
27
+ });
28
+ afterAll(async () => {
29
+ await fs.remove(tmpDir);
30
+ });
31
+ // ── M-01: llms.txt ──────────────────────────────────────────────────────────
32
+ it('generates llms.txt (M-01)', async () => {
33
+ expect(await fileExists(outputDir, 'llms.txt')).toBe(true);
34
+ const c = await readFile(outputDir, 'llms.txt');
35
+ expect(c).toContain('myapp');
36
+ expect(c).toContain('Rails 8');
37
+ expect(c).toContain('app/handlers/');
38
+ expect(c).toContain('DARE v3.0');
39
+ });
40
+ // ── M-04: rack-attack initializer ──────────────────────────────────────────
41
+ it('generates rack_attack.rb initializer (M-04)', async () => {
42
+ expect(await fileExists(outputDir, 'config/initializers/rack_attack.rb')).toBe(true);
43
+ const c = await readFile(outputDir, 'config/initializers/rack_attack.rb');
44
+ expect(c).toContain('Rack::Attack');
45
+ expect(c).toContain('throttle');
46
+ expect(c).toContain('application/problem+json');
47
+ });
48
+ // ── D-006: ProblemDetails concern ──────────────────────────────────────────
49
+ it('generates ProblemDetails concern (D-006)', async () => {
50
+ expect(await fileExists(outputDir, 'app/controllers/concerns/problem_details.rb')).toBe(true);
51
+ const c = await readFile(outputDir, 'app/controllers/concerns/problem_details.rb');
52
+ expect(c).toContain('RFC 7807');
53
+ expect(c).toContain('application/problem+json');
54
+ expect(c).toContain('ProblemDetails');
55
+ });
56
+ // ── Layered Design directories (ADR-05) ─────────────────────────────────────
57
+ it.each([
58
+ 'app/handlers',
59
+ 'app/services',
60
+ 'app/repositories',
61
+ 'app/models',
62
+ 'app/presenters',
63
+ 'app/llm/providers',
64
+ 'app/llm/prompts',
65
+ 'app/llm/validators',
66
+ 'app/channels/application_cable',
67
+ 'lib/tasks',
68
+ 'spec/services',
69
+ 'spec/handlers',
70
+ 'spec/channels',
71
+ 'spec/factories',
72
+ 'spec/api',
73
+ ])('creates directory: %s', async (dir) => {
74
+ expect(await fs.pathExists(path.join(outputDir, dir))).toBe(true);
75
+ });
76
+ // ── Gemfile ─────────────────────────────────────────────────────────────────
77
+ it('generates Gemfile with required gems', async () => {
78
+ expect(await fileExists(outputDir, 'Gemfile')).toBe(true);
79
+ const c = await readFile(outputDir, 'Gemfile');
80
+ expect(c).toContain('rails');
81
+ expect(c).toContain('rswag-api');
82
+ expect(c).toContain('rack-attack');
83
+ expect(c).toContain('rspec-rails');
84
+ expect(c).toContain('factory_bot_rails');
85
+ expect(c).toContain('redis');
86
+ expect(c).toContain('solid_cache');
87
+ expect(c).toContain('solid_queue');
88
+ expect(c).toContain('pg');
89
+ });
90
+ // ── rake dare task ───────────────────────────────────────────────────────────
91
+ it('generates dare.rake with M-01 to M-04 collectors', async () => {
92
+ expect(await fileExists(outputDir, 'lib/tasks/dare.rake')).toBe(true);
93
+ const c = await readFile(outputDir, 'lib/tasks/dare.rake');
94
+ expect(c).toContain('M-01');
95
+ expect(c).toContain('M-02');
96
+ expect(c).toContain('M-03');
97
+ expect(c).toContain('M-04');
98
+ expect(c).toContain('dare:metrics');
99
+ expect(c).toContain('dare_metrics.json');
100
+ });
101
+ // ── User example ─────────────────────────────────────────────────────────────
102
+ it('generates User example files', async () => {
103
+ const files = [
104
+ 'app/handlers/users_handler.rb',
105
+ 'app/services/create_user_service.rb',
106
+ 'app/repositories/user_repository.rb',
107
+ 'app/models/user.rb',
108
+ 'app/presenters/user_presenter.rb',
109
+ 'spec/services/create_user_service_spec.rb',
110
+ ];
111
+ for (const f of files) {
112
+ expect(await fileExists(outputDir, f)).toBe(true);
113
+ }
114
+ });
115
+ // ── Action Cable ──────────────────────────────────────────────────────────────
116
+ it('generates Action Cable connection with auth', async () => {
117
+ expect(await fileExists(outputDir, 'app/channels/application_cable/connection.rb')).toBe(true);
118
+ const c = await readFile(outputDir, 'app/channels/application_cable/connection.rb');
119
+ expect(c).toContain('current_user');
120
+ expect(c).toContain('reject_unauthorized_connection');
121
+ expect(c).toContain('cookies.signed');
122
+ });
123
+ // ── RealtimeService ───────────────────────────────────────────────────────────
124
+ it('generates RealtimeService broadcaster', async () => {
125
+ expect(await fileExists(outputDir, 'app/services/realtime_service.rb')).toBe(true);
126
+ const c = await readFile(outputDir, 'app/services/realtime_service.rb');
127
+ expect(c).toContain('Singleton');
128
+ expect(c).toContain('ActionCable.server.broadcast');
129
+ });
130
+ // ── LLM layer ─────────────────────────────────────────────────────────────────
131
+ it('generates LLM provider interface', async () => {
132
+ expect(await fileExists(outputDir, 'app/llm/providers/llm_provider.rb')).toBe(true);
133
+ const c = await readFile(outputDir, 'app/llm/providers/llm_provider.rb');
134
+ expect(c).toContain('LLMProvider');
135
+ expect(c).toContain('complete');
136
+ expect(c).toContain('DummyProvider');
137
+ });
138
+ // ── .dare/skills.yml ──────────────────────────────────────────────────────────
139
+ it('generates .dare/skills.yml manifest', async () => {
140
+ expect(await fileExists(outputDir, '.dare/skills.yml')).toBe(true);
141
+ const c = await readFile(outputDir, '.dare/skills.yml');
142
+ expect(c).toContain('ruby-rails-8');
143
+ expect(c).toContain('dare-ax');
144
+ expect(c).toContain('M-01');
145
+ });
146
+ // ── Result manifest ────────────────────────────────────────────────────────────
147
+ it('returns a valid ScaffoldResult', () => {
148
+ expect(result.appName).toBe('myapp');
149
+ expect(result.outputDir).toBe(outputDir);
150
+ expect(result.filesCreated.length).toBeGreaterThan(10);
151
+ expect(result.directoriesCreated.length).toBeGreaterThan(5);
152
+ });
153
+ });
154
+ // ── Suite: LLM templates content ─────────────────────────────────────────────
155
+ describe('RailsScaffold — LLM templates content', () => {
156
+ let tmpDir;
157
+ let outputDir;
158
+ beforeAll(async () => {
159
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dare-rails-llm-'));
160
+ outputDir = path.join(tmpDir, 'myapp');
161
+ const scaffold = new RailsScaffold();
162
+ await scaffold.generate('myapp', { outputDir, verbose: false });
163
+ });
164
+ afterAll(async () => {
165
+ await fs.remove(tmpDir);
166
+ });
167
+ it('llm_cache.rb exists and contains TTL', async () => {
168
+ expect(await fileExists(outputDir, 'app/llm/cache/llm_cache.rb')).toBe(true);
169
+ const c = await readFile(outputDir, 'app/llm/cache/llm_cache.rb');
170
+ expect(c).toContain('TTL');
171
+ expect(c).toContain('LlmCache');
172
+ });
173
+ it('llm_cache.rb has hit_rate / fetch / invalidate methods', async () => {
174
+ const c = await readFile(outputDir, 'app/llm/cache/llm_cache.rb');
175
+ expect(c).toContain('fetch');
176
+ expect(c).toContain('invalidate');
177
+ });
178
+ it('token_bucket.rb exists and contains acquire / try_acquire semantics', async () => {
179
+ expect(await fileExists(outputDir, 'app/llm/rate_limit/token_bucket.rb')).toBe(true);
180
+ const c = await readFile(outputDir, 'app/llm/rate_limit/token_bucket.rb');
181
+ // The template uses consume! which is the acquire pattern
182
+ expect(c).toContain('consume!');
183
+ expect(c).toContain('TokenBucket');
184
+ expect(c).toContain('retry_after');
185
+ });
186
+ it('validator.rb exists and contains validate_schema', async () => {
187
+ expect(await fileExists(outputDir, 'app/llm/validators/validator.rb')).toBe(true);
188
+ const c = await readFile(outputDir, 'app/llm/validators/validator.rb');
189
+ expect(c).toContain('validate!');
190
+ expect(c).toContain('Validator');
191
+ expect(c).toContain('ValidationError');
192
+ });
193
+ it('prompt_loader.rb exists and contains PromptLoader.load', async () => {
194
+ expect(await fileExists(outputDir, 'app/llm/prompts/prompt_loader.rb')).toBe(true);
195
+ const c = await readFile(outputDir, 'app/llm/prompts/prompt_loader.rb');
196
+ expect(c).toContain('PromptLoader');
197
+ expect(c).toContain('def self.load');
198
+ });
199
+ it('summarize_v1.jinja2 prompt template exists', async () => {
200
+ expect(await fileExists(outputDir, 'app/llm/prompts/summarize_v1.jinja2')).toBe(true);
201
+ });
202
+ it('summarize_output_schema.json exists', async () => {
203
+ expect(await fileExists(outputDir, 'app/llm/validators/summarize_output_schema.json')).toBe(true);
204
+ });
205
+ });
206
+ // ── Suite: Action Cable templates content ─────────────────────────────────────
207
+ describe('RailsScaffold — Action Cable templates content', () => {
208
+ let tmpDir;
209
+ let outputDir;
210
+ beforeAll(async () => {
211
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dare-rails-cable-'));
212
+ outputDir = path.join(tmpDir, 'myapp');
213
+ const scaffold = new RailsScaffold();
214
+ await scaffold.generate('myapp', { outputDir, verbose: false });
215
+ });
216
+ afterAll(async () => {
217
+ await fs.remove(tmpDir);
218
+ });
219
+ it('dare_updates_channel.rb exists and contains subscribed and receive', async () => {
220
+ expect(await fileExists(outputDir, 'app/channels/dare_updates_channel.rb')).toBe(true);
221
+ const c = await readFile(outputDir, 'app/channels/dare_updates_channel.rb');
222
+ expect(c).toContain('subscribed');
223
+ expect(c).toContain('DareUpdatesChannel');
224
+ });
225
+ it('dare_updates_channel.rb streams from user-scoped channel', async () => {
226
+ const c = await readFile(outputDir, 'app/channels/dare_updates_channel.rb');
227
+ expect(c).toContain('stream_from');
228
+ expect(c).toContain('dare_updates');
229
+ });
230
+ it('user_updates_channel.rb exists and contains stream_from', async () => {
231
+ expect(await fileExists(outputDir, 'app/channels/user_updates_channel.rb')).toBe(true);
232
+ const c = await readFile(outputDir, 'app/channels/user_updates_channel.rb');
233
+ expect(c).toContain('stream_for');
234
+ expect(c).toContain('UserUpdatesChannel');
235
+ });
236
+ it('user_updates_channel.rb references current_user', async () => {
237
+ const c = await readFile(outputDir, 'app/channels/user_updates_channel.rb');
238
+ expect(c).toContain('current_user');
239
+ });
240
+ });
241
+ // ── Suite: SummarizeDocument feature ──────────────────────────────────────────
242
+ describe('RailsScaffold — SummarizeDocument feature', () => {
243
+ let tmpDir;
244
+ let outputDir;
245
+ beforeAll(async () => {
246
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dare-rails-summarize-'));
247
+ outputDir = path.join(tmpDir, 'myapp');
248
+ const scaffold = new RailsScaffold();
249
+ await scaffold.generate('myapp', { outputDir, verbose: false });
250
+ });
251
+ afterAll(async () => {
252
+ await fs.remove(tmpDir);
253
+ });
254
+ it('summarize_handler.rb exists', async () => {
255
+ expect(await fileExists(outputDir, 'app/handlers/summarize_handler.rb')).toBe(true);
256
+ });
257
+ it('summarize_handler.rb includes ProblemDetails', async () => {
258
+ const c = await readFile(outputDir, 'app/handlers/summarize_handler.rb');
259
+ expect(c).toContain('ProblemDetails');
260
+ expect(c).toContain('SummarizeHandler');
261
+ });
262
+ it('summarize_handler.rb delegates to SummarizeDocumentService', async () => {
263
+ const c = await readFile(outputDir, 'app/handlers/summarize_handler.rb');
264
+ expect(c).toContain('SummarizeDocumentService');
265
+ });
266
+ it('summarize_handler.rb has RFC 7807 error responses', async () => {
267
+ const c = await readFile(outputDir, 'app/handlers/summarize_handler.rb');
268
+ expect(c).toContain('render_problem');
269
+ expect(c).toContain('DocumentNotFoundError');
270
+ expect(c).toContain('SummarizationError');
271
+ });
272
+ it('summarize_document_service.rb exists', async () => {
273
+ expect(await fileExists(outputDir, 'app/services/summarize_document_service.rb')).toBe(true);
274
+ });
275
+ it('summarize_document_service.rb contains DocumentNotFoundError', async () => {
276
+ const c = await readFile(outputDir, 'app/services/summarize_document_service.rb');
277
+ expect(c).toContain('DocumentNotFoundError');
278
+ });
279
+ it('summarize_document_service.rb contains SummarizationError', async () => {
280
+ const c = await readFile(outputDir, 'app/services/summarize_document_service.rb');
281
+ expect(c).toContain('SummarizationError');
282
+ });
283
+ it('summarize_document_service.rb publishes document.summarized event', async () => {
284
+ const c = await readFile(outputDir, 'app/services/summarize_document_service.rb');
285
+ expect(c).toContain('document.summarized');
286
+ });
287
+ it('summarize_document_service.rb uses Result struct', async () => {
288
+ const c = await readFile(outputDir, 'app/services/summarize_document_service.rb');
289
+ expect(c).toContain('Result');
290
+ expect(c).toContain('summary');
291
+ expect(c).toContain('document_id');
292
+ });
293
+ it('document_repository.rb exists', async () => {
294
+ expect(await fileExists(outputDir, 'app/repositories/document_repository.rb')).toBe(true);
295
+ });
296
+ it('document_repository.rb contains DocumentRepository', async () => {
297
+ const c = await readFile(outputDir, 'app/repositories/document_repository.rb');
298
+ expect(c).toContain('DocumentRepository');
299
+ });
300
+ it('document_repository.rb contains update_summary!', async () => {
301
+ const c = await readFile(outputDir, 'app/repositories/document_repository.rb');
302
+ expect(c).toContain('update_summary!');
303
+ expect(c).toContain('summarized_at');
304
+ });
305
+ it('spec/services/summarize_document_service_spec.rb exists', async () => {
306
+ expect(await fileExists(outputDir, 'spec/services/summarize_document_service_spec.rb')).toBe(true);
307
+ });
308
+ it('summarize_document_service_spec.rb uses instance_double', async () => {
309
+ const c = await readFile(outputDir, 'spec/services/summarize_document_service_spec.rb');
310
+ expect(c).toContain('instance_double');
311
+ expect(c).toContain('document_repository');
312
+ expect(c).toContain('llm_provider');
313
+ expect(c).toContain('event_publisher');
314
+ });
315
+ it('summarize_document_service_spec.rb tests DocumentNotFoundError', async () => {
316
+ const c = await readFile(outputDir, 'spec/services/summarize_document_service_spec.rb');
317
+ expect(c).toContain('DocumentNotFoundError');
318
+ expect(c).toContain('document is not found');
319
+ });
320
+ it('summarize_document_service_spec.rb tests SummarizationError on blank LLM', async () => {
321
+ const c = await readFile(outputDir, 'spec/services/summarize_document_service_spec.rb');
322
+ expect(c).toContain('SummarizationError');
323
+ expect(c).toContain('blank');
324
+ });
325
+ it('spec/api/summarize_spec.rb exists', async () => {
326
+ expect(await fileExists(outputDir, 'spec/api/summarize_spec.rb')).toBe(true);
327
+ });
328
+ it('summarize_spec.rb documents 200 response with summary schema', async () => {
329
+ const c = await readFile(outputDir, 'spec/api/summarize_spec.rb');
330
+ expect(c).toContain('"200"');
331
+ expect(c).toContain('summary');
332
+ expect(c).toContain('document_id');
333
+ });
334
+ it('summarize_spec.rb documents 404 RFC 7807 response', async () => {
335
+ const c = await readFile(outputDir, 'spec/api/summarize_spec.rb');
336
+ expect(c).toContain('"404"');
337
+ expect(c).toContain('ProblemDetails');
338
+ });
339
+ it('summarize_spec.rb documents 422 RFC 7807 response', async () => {
340
+ const c = await readFile(outputDir, 'spec/api/summarize_spec.rb');
341
+ expect(c).toContain('"422"');
342
+ });
343
+ it('summarize_spec.rb documents 401 unauthorized response', async () => {
344
+ const c = await readFile(outputDir, 'spec/api/summarize_spec.rb');
345
+ expect(c).toContain('"401"');
346
+ });
347
+ it('spec/channels/dare_updates_channel_spec.rb exists', async () => {
348
+ expect(await fileExists(outputDir, 'spec/channels/dare_updates_channel_spec.rb')).toBe(true);
349
+ });
350
+ it('dare_updates_channel_spec.rb tests document.summarized event delivery', async () => {
351
+ const c = await readFile(outputDir, 'spec/channels/dare_updates_channel_spec.rb');
352
+ expect(c).toContain('document.summarized');
353
+ });
354
+ });
355
+ // ── Suite: rake dare.rake content ─────────────────────────────────────────────
356
+ describe('RailsScaffold — rake dare.rake content', () => {
357
+ let tmpDir;
358
+ let outputDir;
359
+ beforeAll(async () => {
360
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dare-rails-rake-'));
361
+ outputDir = path.join(tmpDir, 'myapp');
362
+ const scaffold = new RailsScaffold();
363
+ await scaffold.generate('myapp', { outputDir, verbose: false });
364
+ });
365
+ afterAll(async () => {
366
+ await fs.remove(tmpDir);
367
+ });
368
+ it('dare.rake contains dare:openapi task', async () => {
369
+ const c = await readFile(outputDir, 'lib/tasks/dare.rake');
370
+ expect(c).toContain('dare:openapi');
371
+ });
372
+ it('dare.rake contains validate_structure task', async () => {
373
+ const c = await readFile(outputDir, 'lib/tasks/dare.rake');
374
+ expect(c).toContain('validate_structure');
375
+ expect(c).toContain('required_dirs');
376
+ });
377
+ it('dare.rake outputs JSON to dare_metrics.json', async () => {
378
+ const c = await readFile(outputDir, 'lib/tasks/dare.rake');
379
+ expect(c).toContain('dare_metrics.json');
380
+ expect(c).toContain('JSON');
381
+ });
382
+ });
383
+ // ── Suite: GitHub Actions workflow ─────────────────────────────────────────────
384
+ describe('RailsScaffold — GitHub Actions CI workflow', () => {
385
+ let tmpDir;
386
+ let outputDir;
387
+ beforeAll(async () => {
388
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dare-rails-ci-'));
389
+ outputDir = path.join(tmpDir, 'myapp');
390
+ const scaffold = new RailsScaffold();
391
+ await scaffold.generate('myapp', { outputDir, verbose: false });
392
+ });
393
+ afterAll(async () => {
394
+ await fs.remove(tmpDir);
395
+ });
396
+ it('.github/workflows/dare-ci.yml exists', async () => {
397
+ expect(await fileExists(outputDir, '.github/workflows/dare-ci.yml')).toBe(true);
398
+ });
399
+ it('dare-ci.yml runs dare:metrics', async () => {
400
+ const c = await readFile(outputDir, '.github/workflows/dare-ci.yml');
401
+ expect(c).toContain('dare:metrics');
402
+ });
403
+ it('dare-ci.yml includes postgres service', async () => {
404
+ const c = await readFile(outputDir, '.github/workflows/dare-ci.yml');
405
+ expect(c).toContain('postgres');
406
+ expect(c).toContain('pg_isready');
407
+ });
408
+ it('dare-ci.yml includes redis service', async () => {
409
+ const c = await readFile(outputDir, '.github/workflows/dare-ci.yml');
410
+ expect(c).toContain('redis');
411
+ expect(c).toContain('redis-cli ping');
412
+ });
413
+ it('dare-ci.yml uses ruby/setup-ruby@v1', async () => {
414
+ const c = await readFile(outputDir, '.github/workflows/dare-ci.yml');
415
+ expect(c).toContain('ruby/setup-ruby@v1');
416
+ });
417
+ it('dare-ci.yml runs bundle exec rspec', async () => {
418
+ const c = await readFile(outputDir, '.github/workflows/dare-ci.yml');
419
+ expect(c).toContain('bundle exec rspec');
420
+ });
421
+ it('dare-ci.yml runs db:create db:migrate', async () => {
422
+ const c = await readFile(outputDir, '.github/workflows/dare-ci.yml');
423
+ expect(c).toContain('db:create db:migrate');
424
+ });
425
+ it('dare-ci.yml uses actions/checkout@v4', async () => {
426
+ const c = await readFile(outputDir, '.github/workflows/dare-ci.yml');
427
+ expect(c).toContain('actions/checkout@v4');
428
+ });
429
+ });
430
+ // ── Suite: config templates ────────────────────────────────────────────────────
431
+ describe('RailsScaffold — config templates', () => {
432
+ let tmpDir;
433
+ let outputDir;
434
+ beforeAll(async () => {
435
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dare-rails-config-'));
436
+ outputDir = path.join(tmpDir, 'myapp');
437
+ const scaffold = new RailsScaffold();
438
+ await scaffold.generate('myapp', { outputDir, verbose: false });
439
+ });
440
+ afterAll(async () => {
441
+ await fs.remove(tmpDir);
442
+ });
443
+ it('config/dare.yml exists and contains llm_provider settings', async () => {
444
+ expect(await fileExists(outputDir, 'config/dare.yml')).toBe(true);
445
+ const c = await readFile(outputDir, 'config/dare.yml');
446
+ expect(c).toContain('provider');
447
+ expect(c).toContain('dummy');
448
+ });
449
+ it('config/dare.yml contains api_key comment (never in source)', async () => {
450
+ const c = await readFile(outputDir, 'config/dare.yml');
451
+ expect(c).toContain('api_key');
452
+ });
453
+ it('config/initializers/rswag_api.rb contains swagger_root', async () => {
454
+ expect(await fileExists(outputDir, 'config/initializers/rswag_api.rb')).toBe(true);
455
+ const c = await readFile(outputDir, 'config/initializers/rswag_api.rb');
456
+ expect(c).toContain('openapi_root');
457
+ expect(c).toContain('Rswag');
458
+ });
459
+ it('config/initializers/dare.rb exists', async () => {
460
+ expect(await fileExists(outputDir, 'config/initializers/dare.rb')).toBe(true);
461
+ });
462
+ });
463
+ // ── Suite: --skip-llm option ──────────────────────────────────────────────────
464
+ describe('RailsScaffold — --skip-llm option', () => {
465
+ let tmpDir;
466
+ let outputDir;
467
+ beforeAll(async () => {
468
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dare-rails-skipllm-'));
469
+ outputDir = path.join(tmpDir, 'myapp');
470
+ const scaffold = new RailsScaffold();
471
+ await scaffold.generate('myapp', { outputDir, verbose: false, skipLlm: true });
472
+ });
473
+ afterAll(async () => {
474
+ await fs.remove(tmpDir);
475
+ });
476
+ it('does NOT generate app/llm/ files when skipLlm is true', async () => {
477
+ expect(await fileExists(outputDir, 'app/llm/providers/llm_provider.rb')).toBe(false);
478
+ expect(await fileExists(outputDir, 'app/llm/cache/llm_cache.rb')).toBe(false);
479
+ expect(await fileExists(outputDir, 'app/llm/rate_limit/token_bucket.rb')).toBe(false);
480
+ });
481
+ it('still generates Gemfile when skipLlm is true', async () => {
482
+ expect(await fileExists(outputDir, 'Gemfile')).toBe(true);
483
+ });
484
+ it('still generates User handler when skipLlm is true', async () => {
485
+ expect(await fileExists(outputDir, 'app/handlers/users_handler.rb')).toBe(true);
486
+ });
487
+ it('still generates rake dare.rake when skipLlm is true', async () => {
488
+ expect(await fileExists(outputDir, 'lib/tasks/dare.rake')).toBe(true);
489
+ });
490
+ it('still generates GitHub Actions workflow when skipLlm is true', async () => {
491
+ expect(await fileExists(outputDir, '.github/workflows/dare-ci.yml')).toBe(true);
492
+ });
493
+ });
494
+ // ── Suite: --skip-channels option ─────────────────────────────────────────────
495
+ describe('RailsScaffold — --skip-channels option', () => {
496
+ let tmpDir;
497
+ let outputDir;
498
+ beforeAll(async () => {
499
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dare-rails-skipchan-'));
500
+ outputDir = path.join(tmpDir, 'myapp');
501
+ const scaffold = new RailsScaffold();
502
+ await scaffold.generate('myapp', { outputDir, verbose: false, skipChannels: true });
503
+ });
504
+ afterAll(async () => {
505
+ await fs.remove(tmpDir);
506
+ });
507
+ it('does NOT generate dare_updates_channel.rb when skipChannels is true', async () => {
508
+ expect(await fileExists(outputDir, 'app/channels/dare_updates_channel.rb')).toBe(false);
509
+ });
510
+ it('does NOT generate user_updates_channel.rb when skipChannels is true', async () => {
511
+ expect(await fileExists(outputDir, 'app/channels/user_updates_channel.rb')).toBe(false);
512
+ });
513
+ it('does NOT generate realtime_service.rb when skipChannels is true', async () => {
514
+ expect(await fileExists(outputDir, 'app/services/realtime_service.rb')).toBe(false);
515
+ });
516
+ it('still generates Gemfile when skipChannels is true', async () => {
517
+ expect(await fileExists(outputDir, 'Gemfile')).toBe(true);
518
+ });
519
+ it('still generates LLM layer when skipChannels is true', async () => {
520
+ expect(await fileExists(outputDir, 'app/llm/providers/llm_provider.rb')).toBe(true);
521
+ });
522
+ it('still generates SummarizeDocument when skipChannels is true', async () => {
523
+ expect(await fileExists(outputDir, 'app/handlers/summarize_handler.rb')).toBe(true);
524
+ expect(await fileExists(outputDir, 'app/services/summarize_document_service.rb')).toBe(true);
525
+ });
526
+ });
527
+ // ── Suite: --skip-examples option ────────────────────────────────────────────
528
+ describe('RailsScaffold — --skip-examples option', () => {
529
+ let tmpDir;
530
+ let outputDir;
531
+ beforeAll(async () => {
532
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dare-rails-skipex-'));
533
+ outputDir = path.join(tmpDir, 'myapp');
534
+ const scaffold = new RailsScaffold();
535
+ await scaffold.generate('myapp', { outputDir, verbose: false, skipExamples: true });
536
+ });
537
+ afterAll(async () => {
538
+ await fs.remove(tmpDir);
539
+ });
540
+ it('does NOT generate users_handler.rb when skipExamples is true', async () => {
541
+ expect(await fileExists(outputDir, 'app/handlers/users_handler.rb')).toBe(false);
542
+ });
543
+ it('does NOT generate create_user_service.rb when skipExamples is true', async () => {
544
+ expect(await fileExists(outputDir, 'app/services/create_user_service.rb')).toBe(false);
545
+ });
546
+ it('does NOT generate user_repository.rb when skipExamples is true', async () => {
547
+ expect(await fileExists(outputDir, 'app/repositories/user_repository.rb')).toBe(false);
548
+ });
549
+ it('still generates Gemfile when skipExamples is true', async () => {
550
+ expect(await fileExists(outputDir, 'Gemfile')).toBe(true);
551
+ });
552
+ it('still generates LLM layer when skipExamples is true', async () => {
553
+ expect(await fileExists(outputDir, 'app/llm/providers/llm_provider.rb')).toBe(true);
554
+ });
555
+ it('still generates SummarizeDocument when skipExamples is true', async () => {
556
+ expect(await fileExists(outputDir, 'app/handlers/summarize_handler.rb')).toBe(true);
557
+ });
558
+ it('still generates GitHub Actions workflow when skipExamples is true', async () => {
559
+ expect(await fileExists(outputDir, '.github/workflows/dare-ci.yml')).toBe(true);
560
+ });
561
+ });
562
+ // ── Suite: ScaffoldResult manifest ────────────────────────────────────────────
563
+ describe('RailsScaffold — ScaffoldResult manifest includes new Semana 2 files', () => {
564
+ let tmpDir;
565
+ let outputDir;
566
+ let result;
567
+ beforeAll(async () => {
568
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dare-rails-manifest-'));
569
+ outputDir = path.join(tmpDir, 'myapp');
570
+ const scaffold = new RailsScaffold();
571
+ result = await scaffold.generate('myapp', { outputDir, verbose: false });
572
+ });
573
+ afterAll(async () => {
574
+ await fs.remove(tmpDir);
575
+ });
576
+ it('filesCreated includes summarize_handler.rb', () => {
577
+ expect(result.filesCreated).toContain('app/handlers/summarize_handler.rb');
578
+ });
579
+ it('filesCreated includes summarize_document_service.rb', () => {
580
+ expect(result.filesCreated).toContain('app/services/summarize_document_service.rb');
581
+ });
582
+ it('filesCreated includes document_repository.rb', () => {
583
+ expect(result.filesCreated).toContain('app/repositories/document_repository.rb');
584
+ });
585
+ it('filesCreated includes dare-ci.yml', () => {
586
+ expect(result.filesCreated).toContain('.github/workflows/dare-ci.yml');
587
+ });
588
+ it('filesCreated includes summarize_document_service_spec.rb', () => {
589
+ expect(result.filesCreated).toContain('spec/services/summarize_document_service_spec.rb');
590
+ });
591
+ it('filesCreated includes summarize_spec.rb', () => {
592
+ expect(result.filesCreated).toContain('spec/api/summarize_spec.rb');
593
+ });
594
+ it('filesCreated includes dare_updates_channel_spec.rb', () => {
595
+ expect(result.filesCreated).toContain('spec/channels/dare_updates_channel_spec.rb');
596
+ });
597
+ it('total files created is now greater than 25', () => {
598
+ expect(result.filesCreated.length).toBeGreaterThan(25);
599
+ });
600
+ it('directoriesCreated includes .github/workflows', () => {
601
+ expect(result.directoriesCreated).toContain('.github/workflows');
602
+ });
603
+ });
604
+ //# sourceMappingURL=scaffold.spec.js.map