create-win-project 1.3.0 → 1.4.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 (232) hide show
  1. package/README.md +19 -28
  2. package/{scripts/compatibility-matrix.mjs → checks/check-compatibility.js} +24 -6
  3. package/{scripts/verify-generated.mjs → checks/check-generated-project.js} +2 -2
  4. package/{scripts/validate-content.mjs → checks/check-library.js} +5 -5
  5. package/checks/classify-changes.js +39 -0
  6. package/docs/ARCHITECTURE.md +83 -40
  7. package/docs/CONTENT_MODEL.md +3 -3
  8. package/docs/CONTRIBUTING.md +154 -0
  9. package/docs/DEPENDENCY_MAINTENANCE.md +1 -1
  10. package/index.js +2 -429
  11. package/{playbooks → library}/INDEX.md +4 -4
  12. package/{playbooks/capabilities/ci/github-actions.manifest.json → library/development-tools/ci/definition.json} +1 -0
  13. package/{playbooks/devops/makefile.manifest.json → library/development-tools/devops/makefile/definition.json} +1 -0
  14. package/{playbooks/devops/pr-template.manifest.json → library/development-tools/devops/pr-template/definition.json} +1 -0
  15. package/{playbooks/platform/mobile.manifest.json → library/platforms/mobile/definition.json} +1 -0
  16. package/{playbooks/platform/web.manifest.json → library/platforms/web/definition.json} +1 -0
  17. package/{compatibility/profiles.json → library/tested-versions.json} +4 -0
  18. package/{playbooks/universal/accessibility.manifest.json → library/universal/accessibility/definition.json} +1 -1
  19. package/{playbooks/universal/coding-rules.manifest.json → library/universal/coding-rules/definition.json} +1 -0
  20. package/{playbooks/universal/error-handling.manifest.json → library/universal/error-handling/definition.json} +1 -0
  21. package/{playbooks/universal/git-conventions.manifest.json → library/universal/git-conventions/definition.json} +1 -0
  22. package/{playbooks/universal/observability.manifest.json → library/universal/observability/definition.json} +1 -0
  23. package/{playbooks/universal/security.manifest.json → library/universal/security/definition.json} +1 -0
  24. package/{playbooks/universal/typescript.manifest.json → library/universal/typescript/definition.json} +1 -0
  25. package/package.json +12 -11
  26. package/public/logo.svg +19 -0
  27. package/src/cli/arguments.js +52 -0
  28. package/src/cli/display.js +220 -0
  29. package/src/cli/main.js +185 -0
  30. package/src/cli/navigation.js +156 -0
  31. package/src/cli/questions.js +187 -0
  32. package/src/cli/system-check.js +138 -0
  33. package/src/engine/create-project.js +10 -0
  34. package/src/engine/install-dependencies.js +13 -0
  35. package/{lib/catalog.js → src/engine/load-library.js} +5 -6
  36. package/{lib/playbooks.js → src/engine/project-guidance.js} +25 -3
  37. package/{lib/template.js → src/engine/render-templates.js} +8 -0
  38. package/src/engine/write-files.js +34 -0
  39. package/{lib/stacks/registry.js → src/stacks/available-stacks.js} +23 -1
  40. package/src/stacks/backends/README.md +5 -0
  41. package/{lib/stacks → src/stacks/backends}/laravel/architecture.js +1 -0
  42. package/{lib/stacks → src/stacks/backends}/laravel/auth/oidc.js +1 -0
  43. package/{lib/stacks → src/stacks/backends}/laravel/auth/public.js +1 -0
  44. package/{lib/stacks → src/stacks/backends}/laravel/auth/sanctum.js +1 -0
  45. package/{lib/stacks → src/stacks/backends}/laravel/auth/session.js +1 -0
  46. package/src/stacks/backends/laravel/ci.js +3 -0
  47. package/{lib/stacks → src/stacks/backends}/laravel/composer.js +1 -1
  48. package/{lib/stacks/laravel/generate.js → src/stacks/backends/laravel/create-files.js} +1 -0
  49. package/src/stacks/backends/laravel/docker.js +14 -0
  50. package/src/stacks/backends/laravel/environment.js +5 -0
  51. package/{lib/stacks → src/stacks/backends}/laravel/index.js +11 -3
  52. package/{lib/stacks → src/stacks/backends}/laravel/ui/blade.js +1 -0
  53. package/{lib/stacks → src/stacks/backends}/laravel/ui/index.js +1 -0
  54. package/{lib/stacks → src/stacks/backends}/laravel/ui/inertia-react.js +4 -2
  55. package/{lib/stacks → src/stacks/backends}/laravel/ui/livewire.js +1 -0
  56. package/{lib/stacks → src/stacks/backends}/laravel/ui/shared.js +1 -0
  57. package/src/stacks/backends/none/index.js +23 -0
  58. package/src/stacks/backends/postgres/ci.js +3 -0
  59. package/src/stacks/backends/postgres/create-files.js +21 -0
  60. package/src/stacks/backends/postgres/docker.js +3 -0
  61. package/src/stacks/backends/postgres/environment.js +3 -0
  62. package/src/stacks/backends/postgres/index.js +30 -0
  63. package/src/stacks/backends/springboot/ci.js +3 -0
  64. package/src/stacks/backends/springboot/create-files.js +91 -0
  65. package/src/stacks/backends/springboot/docker.js +3 -0
  66. package/src/stacks/backends/springboot/environment.js +3 -0
  67. package/src/stacks/backends/springboot/index.js +31 -0
  68. package/src/stacks/backends/supabase/ci.js +3 -0
  69. package/src/stacks/backends/supabase/create-files.js +52 -0
  70. package/src/stacks/backends/supabase/docker.js +3 -0
  71. package/src/stacks/backends/supabase/environment.js +3 -0
  72. package/src/stacks/backends/supabase/index.js +31 -0
  73. package/src/stacks/backends/supabase/native.js +14 -0
  74. package/src/stacks/compose-files.js +241 -0
  75. package/{lib/generator.js → src/stacks/create-project.js} +78 -50
  76. package/src/stacks/frontends/README.md +4 -0
  77. package/src/stacks/frontends/nextjs/ci.js +3 -0
  78. package/src/stacks/frontends/nextjs/create-files.js +46 -0
  79. package/src/stacks/frontends/nextjs/dependencies.js +5 -0
  80. package/src/stacks/frontends/nextjs/docker.js +3 -0
  81. package/src/stacks/frontends/nextjs/environment.js +7 -0
  82. package/src/stacks/frontends/nextjs/index.js +35 -0
  83. package/src/stacks/frontends/react-native/ci.js +3 -0
  84. package/src/stacks/frontends/react-native/create-files.js +25 -0
  85. package/src/stacks/frontends/react-native/dependencies.js +5 -0
  86. package/src/stacks/frontends/react-native/docker.js +5 -0
  87. package/src/stacks/frontends/react-native/environment.js +7 -0
  88. package/src/stacks/frontends/react-native/index.js +30 -0
  89. package/src/stacks/frontends/react-vite/ci.js +3 -0
  90. package/src/stacks/frontends/react-vite/create-files.js +27 -0
  91. package/src/stacks/frontends/react-vite/dependencies.js +5 -0
  92. package/src/stacks/frontends/react-vite/docker.js +3 -0
  93. package/src/stacks/frontends/react-vite/environment.js +8 -0
  94. package/src/stacks/frontends/react-vite/index.js +32 -0
  95. package/{lib → src}/stacks/shared/contributions.js +1 -1
  96. package/src/stacks/shared/environment.js +30 -0
  97. package/src/stacks/shared/javascript-package.js +111 -0
  98. package/src/stacks/shared/testing-files.js +16 -0
  99. package/templates/docker/compose/postgres.yml +1 -1
  100. package/templates/docker/compose/springboot.yml +3 -3
  101. package/templates/docker/compose/supabase.yml +1 -1
  102. package/templates/docker/compose-prod/springboot.yml +1 -1
  103. package/lib/banner.js +0 -45
  104. package/lib/constants.js +0 -3
  105. package/lib/doctor.js +0 -45
  106. package/lib/interview.js +0 -77
  107. package/lib/laravel-scaffold.js +0 -3
  108. package/lib/scaffold.js +0 -467
  109. package/lib/stacks/index.js +0 -8
  110. /package/{playbooks/capabilities → library/development-tools}/ci/github-actions.md +0 -0
  111. /package/{playbooks/devops → library/development-tools/devops/makefile}/makefile.md +0 -0
  112. /package/{playbooks/devops → library/development-tools/devops/pr-template}/pr-template.md +0 -0
  113. /package/{playbooks/capabilities/docker/docker.manifest.json → library/development-tools/docker/definition.json} +0 -0
  114. /package/{playbooks/capabilities → library/development-tools}/docker/overview.md +0 -0
  115. /package/{playbooks/capabilities → library/features}/auth/oidc-resource-server.md +0 -0
  116. /package/{playbooks/capabilities → library/features}/auth/spring-session.md +0 -0
  117. /package/{playbooks/capabilities/flyway/flyway.manifest.json → library/features/flyway/definition.json} +0 -0
  118. /package/{playbooks/capabilities → library/features}/flyway/environments.md +0 -0
  119. /package/{playbooks/capabilities → library/features}/flyway/migrations.md +0 -0
  120. /package/{playbooks/capabilities → library/features}/flyway/testing.md +0 -0
  121. /package/{playbooks/capabilities → library/features}/laravel/database.md +0 -0
  122. /package/{playbooks/capabilities → library/features}/laravel/migrations.md +0 -0
  123. /package/{playbooks/capabilities → library/features}/laravel/observability.md +0 -0
  124. /package/{playbooks/capabilities → library/features}/laravel/oidc-resource-server.md +0 -0
  125. /package/{playbooks/capabilities → library/features}/laravel/queues.md +0 -0
  126. /package/{playbooks/capabilities → library/features}/laravel/sanctum-spa.md +0 -0
  127. /package/{playbooks/capabilities → library/features}/laravel/scheduler.md +0 -0
  128. /package/{playbooks/capabilities → library/features}/laravel/session-auth.md +0 -0
  129. /package/{playbooks/capabilities → library/features}/laravel/storage-uploads.md +0 -0
  130. /package/{playbooks/capabilities → library/features}/postgresql/architecture.md +0 -0
  131. /package/{playbooks/capabilities/postgresql/postgresql.manifest.json → library/features/postgresql/definition.json} +0 -0
  132. /package/{playbooks/capabilities → library/features}/postgresql/migrations.md +0 -0
  133. /package/{playbooks/capabilities → library/features}/postgresql/schema-design.md +0 -0
  134. /package/{playbooks/capabilities → library/features}/postgresql/security.md +0 -0
  135. /package/{playbooks/capabilities → library/features}/postgresql/testing.md +0 -0
  136. /package/{playbooks/capabilities → library/features}/prisma/architecture.md +0 -0
  137. /package/{playbooks/capabilities/prisma/prisma.manifest.json → library/features/prisma/definition.json} +0 -0
  138. /package/{playbooks/capabilities → library/features}/prisma/migrations.md +0 -0
  139. /package/{playbooks/capabilities → library/features}/prisma/runtime.md +0 -0
  140. /package/{playbooks/capabilities → library/features}/prisma/schema.md +0 -0
  141. /package/{playbooks/capabilities → library/features}/prisma/testing.md +0 -0
  142. /package/{playbooks/capabilities → library/features}/supabase/architecture.md +0 -0
  143. /package/{playbooks/capabilities → library/features}/supabase/authentication.md +0 -0
  144. /package/{playbooks/capabilities/supabase/supabase.manifest.json → library/features/supabase/definition.json} +0 -0
  145. /package/{playbooks/capabilities → library/features}/supabase/expo.md +0 -0
  146. /package/{playbooks/capabilities → library/features}/supabase/migrations.md +0 -0
  147. /package/{playbooks/capabilities → library/features}/supabase/nextjs.md +0 -0
  148. /package/{playbooks/capabilities → library/features}/supabase/rls.md +0 -0
  149. /package/{playbooks/capabilities → library/features}/supabase/testing.md +0 -0
  150. /package/{playbooks/capabilities → library/features}/supabase/vite.md +0 -0
  151. /package/{playbooks → library/optional-features}/concerns/axios.md +0 -0
  152. /package/{playbooks → library/optional-features}/concerns/next-safe-action.md +0 -0
  153. /package/{playbooks → library/optional-features}/concerns/next-themes.md +0 -0
  154. /package/{playbooks → library/optional-features}/concerns/nuqs.md +0 -0
  155. /package/{playbooks → library/optional-features}/concerns/t3-env.md +0 -0
  156. /package/{playbooks → library/optional-features}/concerns/tanstack-query.md +0 -0
  157. /package/{playbooks → library/optional-features}/concerns/zod.md +0 -0
  158. /package/{playbooks → library/optional-features}/concerns/zustand.md +0 -0
  159. /package/{playbooks/styling → library/optional-features/styling/css-modules}/css-modules-extensions.md +0 -0
  160. /package/{playbooks/styling/css-modules.manifest.json → library/optional-features/styling/css-modules/definition.json} +0 -0
  161. /package/{playbooks/styling/native-styles.manifest.json → library/optional-features/styling/native-styles/definition.json} +0 -0
  162. /package/{playbooks/styling → library/optional-features/styling/native-styles}/native-styles.md +0 -0
  163. /package/{playbooks/styling/tailwind.manifest.json → library/optional-features/styling/tailwind/definition.json} +0 -0
  164. /package/{playbooks/styling → library/optional-features/styling/tailwind}/tailwind-extensions.md +0 -0
  165. /package/{playbooks/platform → library/platforms}/laravel-ui/blade/architecture.md +0 -0
  166. /package/{playbooks/platform → library/platforms}/laravel-ui/blade/runtime.md +0 -0
  167. /package/{playbooks/platform → library/platforms}/laravel-ui/blade/security.md +0 -0
  168. /package/{playbooks/platform → library/platforms}/laravel-ui/blade/structure.md +0 -0
  169. /package/{playbooks/platform → library/platforms}/laravel-ui/blade/testing.md +0 -0
  170. /package/{playbooks/stack/laravel-ui.manifest.json → library/platforms/laravel-ui/definition.json} +0 -0
  171. /package/{playbooks/platform → library/platforms}/laravel-ui/inertia-react/architecture.md +0 -0
  172. /package/{playbooks/platform → library/platforms}/laravel-ui/inertia-react/runtime.md +0 -0
  173. /package/{playbooks/platform → library/platforms}/laravel-ui/inertia-react/security.md +0 -0
  174. /package/{playbooks/platform → library/platforms}/laravel-ui/inertia-react/structure.md +0 -0
  175. /package/{playbooks/platform → library/platforms}/laravel-ui/inertia-react/testing.md +0 -0
  176. /package/{playbooks/platform → library/platforms}/laravel-ui/livewire/architecture.md +0 -0
  177. /package/{playbooks/platform → library/platforms}/laravel-ui/livewire/runtime.md +0 -0
  178. /package/{playbooks/platform → library/platforms}/laravel-ui/livewire/security.md +0 -0
  179. /package/{playbooks/platform → library/platforms}/laravel-ui/livewire/structure.md +0 -0
  180. /package/{playbooks/platform → library/platforms}/laravel-ui/livewire/testing.md +0 -0
  181. /package/{playbooks/platform → library/platforms/mobile}/mobile.md +0 -0
  182. /package/{playbooks/platform → library/platforms/web}/web.md +0 -0
  183. /package/{playbooks/stack → library/stacks}/expo/architecture.md +0 -0
  184. /package/{playbooks/stack/react-native.manifest.json → library/stacks/expo/definition.json} +0 -0
  185. /package/{playbooks/stack → library/stacks}/expo/runtime.md +0 -0
  186. /package/{playbooks/stack → library/stacks}/expo/security.md +0 -0
  187. /package/{playbooks/stack → library/stacks}/expo/structure.md +0 -0
  188. /package/{playbooks/stack → library/stacks}/expo/testing.md +0 -0
  189. /package/{playbooks/stack → library/stacks}/laravel/architecture.md +0 -0
  190. /package/{playbooks/stack/laravel.manifest.json → library/stacks/laravel/definition.json} +0 -0
  191. /package/{playbooks/stack → library/stacks}/laravel/runtime.md +0 -0
  192. /package/{playbooks/stack → library/stacks}/laravel/security.md +0 -0
  193. /package/{playbooks/stack → library/stacks}/laravel/structure.md +0 -0
  194. /package/{playbooks/stack → library/stacks}/laravel/testing.md +0 -0
  195. /package/{playbooks/stack → library/stacks}/nextjs/architecture.md +0 -0
  196. /package/{playbooks/stack/nextjs.manifest.json → library/stacks/nextjs/definition.json} +0 -0
  197. /package/{playbooks/stack → library/stacks}/nextjs/runtime.md +0 -0
  198. /package/{playbooks/stack → library/stacks}/nextjs/security.md +0 -0
  199. /package/{playbooks/stack → library/stacks}/nextjs/structure.md +0 -0
  200. /package/{playbooks/stack → library/stacks}/nextjs/testing.md +0 -0
  201. /package/{playbooks/stack/no-frontend.manifest.json → library/stacks/no-frontend/definition.json} +0 -0
  202. /package/{playbooks/stack/none.manifest.json → library/stacks/none/definition.json} +0 -0
  203. /package/{playbooks/stack → library/stacks}/react-vite/architecture.md +0 -0
  204. /package/{playbooks/stack/react-vite.manifest.json → library/stacks/react-vite/definition.json} +0 -0
  205. /package/{playbooks/stack → library/stacks}/react-vite/runtime.md +0 -0
  206. /package/{playbooks/stack → library/stacks}/react-vite/security.md +0 -0
  207. /package/{playbooks/stack → library/stacks}/react-vite/structure.md +0 -0
  208. /package/{playbooks/stack → library/stacks}/react-vite/testing.md +0 -0
  209. /package/{playbooks/stack → library/stacks}/springboot/architecture.md +0 -0
  210. /package/{playbooks/stack/springboot.manifest.json → library/stacks/springboot/definition.json} +0 -0
  211. /package/{playbooks/stack → library/stacks}/springboot/runtime.md +0 -0
  212. /package/{playbooks/stack → library/stacks}/springboot/security.md +0 -0
  213. /package/{playbooks/stack → library/stacks}/springboot/structure.md +0 -0
  214. /package/{playbooks/stack → library/stacks}/springboot/testing.md +0 -0
  215. /package/{playbooks/universal → library/universal/accessibility}/accessibility.md +0 -0
  216. /package/{playbooks/universal → library/universal/coding-rules}/coding-rules.md +0 -0
  217. /package/{playbooks/universal → library/universal/error-handling}/error-handling.md +0 -0
  218. /package/{playbooks/universal → library/universal/git-conventions}/git-conventions.md +0 -0
  219. /package/{playbooks/universal → library/universal/observability}/observability.md +0 -0
  220. /package/{playbooks/universal → library/universal/security}/security.md +0 -0
  221. /package/{playbooks/universal → library/universal/typescript}/typescript.md +0 -0
  222. /package/{lib/files.js → src/engine/project-files.js} +0 -0
  223. /package/{lib → src/engine}/project-location.js +0 -0
  224. /package/{lib/application-shapes.js → src/engine/project-shapes.js} +0 -0
  225. /package/{lib/compatibility.js → src/engine/tested-versions.js} +0 -0
  226. /package/{lib → src}/stacks/context.js +0 -0
  227. /package/{lib/stacks/contract.js → src/stacks/rules.js} +0 -0
  228. /package/{ci → templates/ci}/expo.yml +0 -0
  229. /package/{ci → templates/ci}/laravel.yml +0 -0
  230. /package/{ci → templates/ci}/nextjs.yml +0 -0
  231. /package/{ci → templates/ci}/springboot.yml +0 -0
  232. /package/{ci → templates/ci}/vite.yml +0 -0
@@ -0,0 +1,154 @@
1
+ # Contributing
2
+
3
+ See `docs/ARCHITECTURE.md` for completed source ownership and `docs/DEPENDENCY_MAINTENANCE.md` for profile promotion. This file is the workflow, not the design.
4
+
5
+ ## Quick start
6
+
7
+ 1. Fork and create a feature branch from `dev`.
8
+ 2. `npm ci` — Node 22.14+ with npm 11.19+ required (tested on Node 24).
9
+ 3. `npm test` must stay green. No global `prettier`/`eslint`/`typescript` needed — they are generated inside your project.
10
+
11
+ ## Adding a stack — plan first, then declare, then build
12
+
13
+ A stack is **planned content first, then a declaration in `library/`, then executable code in `src/stacks/`**. `library/` never contains business entities — it declares *what* the stack supports; `src/stacks/<id>/create-files.js` implements *how* those files are generated. Do not start with code; start with the content model.
14
+
15
+ ### 0. Plan your stack's content (before touching `library/`)
16
+
17
+ Answer these 5 questions and write them down (a short RFC or issue is enough). If you can't answer them, you can't write a `definition.json` yet.
18
+
19
+ **a. Identity & compatibility:** What `id` (`django`, `nuxt`), `kind` (`frontend`|`backend`|`data`), `label` (`Django (Python)`), and which existing stacks can it pair with? Example: `nextjs` pairs with `none,postgres,supabase,springboot,laravel`; `react-native` pairs with `none,supabase,springboot,laravel`; `laravel` pairs with `nextjs,react,react-native,no-frontend,laravel-ui`.
20
+
21
+ **b. Shapes:** Which **applicationShapes** does it enable? Shapes are the *deployment layout*, not the framework. They determine valid `frontend + backend` combos via `lib/application-shapes.js` → `src/engine/project-shapes.js`:
22
+
23
+ | Shape | Meaning | Example combos |
24
+ |-------|---------|---------------|
25
+ | `fullstack` | One project owns website + server | `nextjs+none`, `nextjs+postgres`, `laravel-ui+laravel` |
26
+ | `separate` | Two apps via API: frontend + backend API | `nextjs+springboot`, `react+supabase`, `react+laravel` |
27
+ | `api` | Backend API only, no generated website | `no-frontend+springboot`, `no-frontend+laravel` |
28
+ | `mobile` | Expo app + API/managed data | `react-native+supabase`, `react-native+springboot` |
29
+ | `frontend` | Browser-only, no generated server | `react+none` |
30
+
31
+ You don't invent a new shape — you pick from these 5. If `nextjs+django` should be fullstack, add `django` to `fullstack`'s `VALID_COMBINATIONS`. `inferApplicationShape({frontend,backend})` will then infer it; `validateApplicationShape` rejects invalid pairings.
32
+
33
+ **c. Architecture & auth:** Which `architectureProfiles` (`small,medium,large` — all 3 required for frontends/backends) and which `authenticationModels` (`public,undecided,supabase,session,oidc,laravel-session,sanctum-spa,laravel-oidc`)? Example: `supabase` → `supabase`, `springboot` website → `session`, `springboot` multi-client → `oidc`.
34
+
35
+ **d. Playbooks & concerns:** Which 5 facets does the stack need? Every stack needs `architecture.md, structure.md, runtime.md, security.md, testing.md` under `playbooks/stack/<id>/`. Which optional concerns (validation `zod`, state `zustand`, etc.) will it surface? Each concern has one home — don't duplicate prose in `AGENTS.md`.
36
+
37
+ **e. Dependencies & env:** Which npm/composer package *names* and *scripts* will this stack generate, and which env var *names* (semantic, no prefix)? Example: `deps: ["next","react"]`, `env: ["DATABASE_URL"]`, `clientEnv: ["API_URL"]` — the engine will prefix `API_URL` once (`NEXT_PUBLIC_API_URL`). Never put a version here.
38
+
39
+ If a new stack needs a runtime (`node`, `java`, `php`), add its version to `library/tested-versions.json` (current profile), not to the definition.
40
+
41
+ ### 1. Declare capability in `library/` (no executable logic yet)
42
+
43
+ - Add `library/stacks/<id>/definition.json`:
44
+
45
+ ```json
46
+ {
47
+ "id": "django",
48
+ "kind": "backend",
49
+ "label": "Django (Python)",
50
+ "appliesTo": { "frontend": ["nextjs","react","no-frontend"] },
51
+ "architectureProfiles": ["small","medium","large"],
52
+ "playbooks": ["stack/django/architecture.md","stack/django/structure.md","stack/django/runtime.md","stack/django/security.md","stack/django/testing.md"],
53
+ "deps": ["django"],
54
+ "env": ["DATABASE_URL"],
55
+ "clientEnv": [],
56
+ "concerns": [{ "id": "validation", "required": false, "when": "runtime validation needed", "sections": ["Zod for Runtime Validation"] }]
57
+ }
58
+ ```
59
+
60
+ - `deps`/`devDeps` are **names only** — never versions or ranges. Exact versions live only in `library/tested-versions.json`.
61
+ - `env` are semantic names (`DATABASE_URL`); `clientEnv` subset gets the frontend prefix (`NEXT_PUBLIC_`, `VITE_`, `EXPO_PUBLIC_`).
62
+
63
+ - Add `playbooks/stack/<id>/{architecture,structure,runtime,security,testing}.md` — the five facets. Keep each file focused; route concerns via definition `concerns[]`, not duplication. Check `docs/CONTENT_MODEL.md` — every concern section must match a Markdown heading (numbered headings normalized).
64
+
65
+ - Run: `node checks/check-library.js` — catches duplicate ids, unknown dep names, `clientEnv` not in `env`, missing labels, heading mismatches.
66
+
67
+ ### 2. Create stack directory (one place, owns everything specific to that stack)
68
+
69
+ ```
70
+ src/stacks/<frontends|backends>/<id>/
71
+ index.js # defineStackAdapter({ id, kind, label, compatibleWith, capabilities, contributes })
72
+ create-files.js # (answers, context) -> FileMap — ALL files this stack generates (pure function)
73
+ dependencies.js # optional: { deps: [], devDeps: [], scripts: {} } — names/scripts only
74
+ environment.js # optional: ["API_URL","DATABASE_URL"] — semantic env names
75
+ auth/ # for Laravel-style stacks: session.js, sanctum.js, oidc.js, public.js
76
+ ui/ # for Laravel-style stacks: blade.js, livewire.js, inertia-react.js, shared.js
77
+ ```
78
+
79
+ Use `src/stacks/backends/laravel/` as the reference (13 files, 363-line `create-files.js` + `auth/` + `ui/` + `composer.js` + `architecture.js`). `src/stacks/frontends/nextjs/` is the minimal vertical.
80
+
81
+ `create-files.js` receives a read-only context from `src/stacks/available-stacks.js` + `src/engine/load-library.js` — it returns contributions, it never writes files or installs tools directly.
82
+
83
+ ```js
84
+ // src/stacks/backends/django/index.js
85
+ import { defineStackAdapter } from '../../rules.js'
86
+ export const djangoAdapter = defineStackAdapter({
87
+ id: 'django', kind: 'backend', label: 'Django (Python)',
88
+ compatibleWith: { frontend: ['nextjs','react','no-frontend'] },
89
+ capabilities: { applicationShapes: ['fullstack','api'], architectureProfiles: ['small','medium','large'], authenticationModels: ['public','undecided','session'] },
90
+ contributes: {
91
+ environment: () => ['DATABASE_URL'],
92
+ install: () => [{ cwd: 'backend', command: 'pip', args: ['install','-r','requirements.txt'] }],
93
+ docker: () => [{ template: 'django', developmentPath: 'Dockerfile.dev' }],
94
+ ci: () => [{ template: 'django', path: '.github/workflows/ci-backend.yml' }],
95
+ }
96
+ })
97
+ ```
98
+
99
+ ### 3. Register once (explicit, no scanning)
100
+
101
+ `src/stacks/available-stacks.js` — add one import and one array entry:
102
+
103
+ ```js
104
+ import { djangoAdapter } from './backends/django/index.js'
105
+ export const stackRegistry = createStackRegistry([..., djangoAdapter])
106
+ ```
107
+
108
+ This is the **only** list of available adapters. Adding a file never silently activates a stack.
109
+
110
+ ### 4. Add tests and matrix entries
111
+
112
+ - `tests/stacks/<id>/` — at least: files exist, env prefix, large-arch boundary, auth metadata.
113
+ - `checks/check-compatibility.js` — add `'<frontend>-<backend>'` to `cases[]` and to `smokeSelections[]`.
114
+ - `templates/` — add `docker/dockerfile/django.dev.dockerfile`, `templates/ci/django.yml` etc. as static templates.
115
+
116
+ ### 5. Verify (focused, not full matrix every time)
117
+
118
+ ```bash
119
+ npm test
120
+ node checks/check-library.js
121
+ node checks/check-compatibility.js --scope=stack --stack=<id> | head
122
+ npm run verify:generated -- --profile=$(jq -r .defaultProfile library/tested-versions.json) --case=<frontend>-<backend> --architecture=medium --authentication=yes
123
+ ```
124
+
125
+ - `verify:generated` compares byte-identical output via `generateProject` vs legacy shim for your new stack.
126
+ - Run `npm run matrix:smoke` (10 smoke projects) before any `feature -> dev` PR.
127
+ - Full matrix `npm run matrix:full` (every profile x stack x arch x auth, with install/build/Maven/Expo/Compose checks) is only required on `dev -> main` promotion per `docs/CI_STRATEGY.md`.
128
+
129
+ ### 6. Docs (only if ownership changed)
130
+
131
+ Update `docs/ARCHITECTURE.md` only if source ownership or the generation pipeline changed — not for every stack. If you added concerns/playbooks, verify `docs/CONTENT_MODEL.md` checklist (concern has one home, `RULES.md` sections resolve).
132
+
133
+ ## Testing and PRs
134
+
135
+ - Required checks per `docs/CI_STRATEGY.md`:
136
+ - `feature -> dev`: `quality` + `compatibility-gate` (repository tests + 10 smoke projects)
137
+ - `dev -> main`: `quality` + `compatibility-gate` (every profile x stack x arch x applicable auth)
138
+
139
+ - Contribution history: use merge commits for feature branches so history remains visible. Do not require individual matrix job names; require the stable `compatibility-gate` aggregator.
140
+
141
+ - `tests/architecture-boundaries.test.js` enforces `src/engine` not importing `cli`/`stacks/frontends|backends` and `src/stacks` not importing `cli`/`engine` (except `shared/scaffold` allowlist). `tests/architecture/no-lib-legacy.test.js` bans `src/** -> lib/**` imports and one-line wrapper regressions (no `lib/` exists).
142
+
143
+ ## Version ownership
144
+
145
+ Exact versions belong only to `library/tested-versions.json`. Never add a version to a `definition.json` or a `create-files.js`. `packageVersion(profile, name, capability)` and `composerPackageVersion(profile, name)` are the only version sources; they reject ranges and unknown package requests. See `docs/DEPENDENCY_MAINTENANCE.md` for current/previous profile promotion and Renovate flow.
146
+
147
+ ## Docs workflow
148
+
149
+ - `AGENTS.md` — tiny always-on contract; keep short.
150
+ - `RULES.md` — lazy index `concern -> playbook §`; generated per project, read only what you touch.
151
+ - `playbooks/` — reusable standards; add the five facets per stack (`architecture.md`, `structure.md`, `runtime.md`, `security.md`, `testing.md`).
152
+ - `docs/guides/` inside generated projects — architecture, API, setup, deployment for *this* product.
153
+
154
+ Do not advertise a capability solely because a playbook mentions it. A generated capability must have executable configuration and contract coverage. For questions, read `docs/ARCHITECTURE.md` first, then this file.
@@ -1,6 +1,6 @@
1
1
  # Dependency maintenance
2
2
 
3
- `compatibility/profiles.json` is the only owner of versions emitted into generated applications. Stack and concern manifests declare package names and capabilities, never ranges or versions. Direct npm dependencies are generated as exact versions, and consumers commit the lockfile created by their first install.
3
+ `library/tested-versions.json` is the only owner of versions emitted into generated applications. Stack and concern definitions declare package names and capabilities, never ranges or versions. Direct npm dependencies are generated as exact versions, and consumers commit the lockfile created by their first install.
4
4
 
5
5
  ## Profile policy
6
6
 
package/index.js CHANGED
@@ -1,431 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import inquirer from 'inquirer'
3
- import chalk from 'chalk'
4
- import ora from 'ora'
5
- import path from 'path'
6
- import { fileURLToPath } from 'url'
7
- import { spawnSync } from 'node:child_process'
8
- import { generateProject } from './lib/generator.js'
9
- import { loadCompatibility } from './lib/compatibility.js'
10
- import { printDoctor } from './lib/doctor.js'
11
- import { configurationDecisionChoices, promptWithBack } from './lib/interview.js'
12
- import { projectLocationNotice } from './lib/project-location.js'
13
- import { w1nBanner } from './lib/banner.js'
14
- import {
15
- loadCatalog, resolveStack,
16
- stylingChoicesFor, architectureChoicesFor,
17
- } from './lib/catalog.js'
18
- import {
19
- APPLICATION_SHAPES,
20
- applicationShapeChoices,
21
- backendChoicesForShape,
22
- frontendChoicesForShape,
23
- } from './lib/application-shapes.js'
24
- import { laravelUiPromptContribution, laravelUis } from './lib/stacks/laravel/ui/index.js'
2
+ import { runCli } from './src/cli/main.js'
25
3
 
26
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
27
- const cliArgs = process.argv.slice(2)
28
- const profileArg = cliArgs.find((arg) => arg.startsWith('--profile='))?.split('=')[1]
29
- const shapeArg = cliArgs.find((arg) => arg.startsWith('--shape='))?.split('=')[1]
30
- const frontendValue = cliArgs.find((arg) => arg.startsWith('--frontend='))?.split('=')[1]
31
- const backendArg = cliArgs.find((arg) => arg.startsWith('--backend='))?.split('=')[1]
32
- const frontendAliases = { vite: 'react', expo: 'react-native', none: 'no-frontend' }
33
- const frontendArg = frontendAliases[frontendValue] || frontendValue
34
- const architectureArg = cliArgs.find((arg) => arg.startsWith('--architecture='))?.split('=')[1]
35
- const authenticationArg = cliArgs.find((arg) => arg.startsWith('--authentication='))?.split('=')[1]
36
- const authAudienceArg = cliArgs.find((arg) => arg.startsWith('--auth-audience='))?.split('=')[1]
37
- const laravelUiArg = cliArgs.find((arg) => arg.startsWith('--laravel-ui='))?.split('=')[1]
38
- if (shapeArg && !APPLICATION_SHAPES[shapeArg]) {
39
- throw new Error('--shape must be fullstack, separate, api, mobile, or frontend')
40
- }
41
- if (architectureArg && !['small', 'medium', 'large'].includes(architectureArg)) {
42
- throw new Error('--architecture must be small, medium, or large')
43
- }
44
- if (authenticationArg && !['yes', 'not-yet', 'none'].includes(authenticationArg)) {
45
- throw new Error('--authentication must be yes, not-yet, or none')
46
- }
47
- if (authAudienceArg && !['website', 'multi-client'].includes(authAudienceArg)) {
48
- throw new Error('--auth-audience must be website or multi-client')
49
- }
50
- if (laravelUiArg && !laravelUis.some((ui) => ui.id === laravelUiArg)) {
51
- throw new Error('--laravel-ui must be blade, livewire, or inertia-react')
52
- }
53
- const wantsInstall = cliArgs.includes('--install')
54
- const skipsInstall = cliArgs.includes('--no-install')
55
- if (wantsInstall && skipsInstall) throw new Error('Use either --install or --no-install, not both')
56
- const { profile } = await loadCompatibility(
57
- path.join(__dirname, 'compatibility/profiles.json'),
58
- profileArg,
59
- )
60
- if (cliArgs[0] === 'doctor' || cliArgs.includes('--doctor')) {
61
- printDoctor(profile)
62
- process.exit(0)
63
- }
64
- const catalog = await loadCatalog(path.join(__dirname, 'playbooks'), profile)
65
-
66
- // ─── Banner ──────────────────────────────────────────────────────────────────
67
-
68
- console.log('')
69
- console.log(w1nBanner())
70
- console.log('')
71
- console.log(chalk.gray(' Production-ready project scaffolding'))
72
- console.log(chalk.gray(` Compatibility profile: ${profile.id} (${profile.status})`))
73
- console.log('')
74
-
75
- // ─── Interview ───────────────────────────────────────────────────────────────
76
-
77
- const questions = [
78
- {
79
- type: 'list',
80
- name: 'applicationShape',
81
- message: 'What kind of application are you building?',
82
- choices: applicationShapeChoices(),
83
- default: 'fullstack',
84
- when: () => !shapeArg,
85
- },
86
- // ── Always-on ──────────────────────────────────────────────────────────
87
- {
88
- type: 'input',
89
- name: 'projectName',
90
- message: 'Project name?',
91
- default: 'my-project',
92
- validate: (v) => {
93
- if (!v.trim()) return 'Project name is required'
94
- if (!/^[a-z0-9-]+$/.test(v)) return 'Use lowercase letters, numbers, and hyphens only'
95
- return true
96
- },
97
- },
98
- {
99
- type: 'input',
100
- name: 'projectDescription',
101
- message: 'One-line description?',
102
- default: 'A new application',
103
- },
104
-
105
- // ── Stack selection (catalog-driven) ───────────────────────────────────
106
- {
107
- type: 'list',
108
- name: 'frontend',
109
- message: 'Which application framework or frontend?',
110
- choices: (a) => frontendChoicesForShape(shapeArg || a.applicationShape, catalog),
111
- when: () => !frontendArg,
112
- },
113
- {
114
- type: 'list',
115
- name: 'backend',
116
- message: 'Which backend or data service?',
117
- choices: (a) => backendChoicesForShape(shapeArg || a.applicationShape, a.frontend, catalog),
118
- when: () => !backendArg,
119
- },
120
- ...laravelUiPromptContribution(laravelUiArg).questions,
121
-
122
- // ── Styling: only shown when frontend has >1 option (catalog-driven) ───
123
- {
124
- type: 'list',
125
- name: 'styling',
126
- message: 'Styling approach?',
127
- choices: (a) => stylingChoicesFor(catalog, a.frontend),
128
- when: (a) => stylingChoicesFor(catalog, a.frontend).length > 1,
129
- },
130
-
131
- // ── One architecture profile, interpreted natively by every stack ──────
132
- {
133
- type: 'list',
134
- name: 'architecture',
135
- message: 'Architecture? (Medium is recommended for most long-term applications)',
136
- choices: (a) => {
137
- const supported = architectureChoicesFor(catalog, a.frontend, a.backend)
138
- return [
139
- { name: 'Medium (Recommended) — clear feature, service, and data boundaries', value: 'medium' },
140
- { name: 'Small — fewer layers for prototypes and simple applications', value: 'small' },
141
- { name: 'Large — enforced boundaries for complex domains and larger teams', value: 'large' },
142
- ].filter((choice) => supported.includes(choice.value))
143
- },
144
- default: 'medium',
145
- when: () => !architectureArg,
146
- },
147
-
148
- // ── Authentication intent, expressed without protocol jargon ───────────
149
- {
150
- type: 'list',
151
- name: 'authentication',
152
- message: 'Does your application need user login?',
153
- choices: (a) => {
154
- const choices = []
155
- if (a.backend === 'supabase' || a.backend === 'springboot' || a.backend === 'laravel') {
156
- choices.push({ name: 'Yes — generate authentication appropriate for this stack', value: 'yes' })
157
- }
158
- choices.push(
159
- { name: 'Not yet (Recommended) — add guidance without pretending login exists', value: 'not-yet' },
160
- { name: 'No — this application is intentionally public and has no user accounts', value: 'none' },
161
- )
162
- return choices
163
- },
164
- default: 'not-yet',
165
- when: () => !authenticationArg,
166
- },
167
- {
168
- type: 'list',
169
- name: 'authAudience',
170
- message: 'Where will users access the application?',
171
- choices: [
172
- { name: 'Website only — use a secure server-managed browser session', value: 'website' },
173
- { name: 'Website and mobile — use a trusted identity provider for every client', value: 'multi-client' },
174
- ],
175
- default: 'website',
176
- when: (a) => ['springboot', 'laravel'].includes(a.backend) && a.frontend !== 'laravel-ui' && (authenticationArg || a.authentication) === 'yes' && !authAudienceArg,
177
- },
178
-
179
- // ── Testing ────────────────────────────────────────────────────────────
180
- {
181
- type: 'list',
182
- name: 'testing',
183
- message: 'Testing setup?',
184
- choices: (a) => {
185
- const fe = catalog.byId[a.frontend]
186
- if (fe?.platform === 'mobile') {
187
- return [
188
- { name: 'Basic (Jest + React Native Testing Library)', value: 'basic' },
189
- { name: 'None', value: 'none' },
190
- ]
191
- }
192
- return [
193
- { name: 'Full (Vitest + React Testing Library + Playwright)', value: 'full' },
194
- { name: 'Basic (Vitest + React Testing Library)', value: 'basic' },
195
- { name: 'None', value: 'none' },
196
- ]
197
- },
198
- default: (a) => {
199
- const fe = catalog.byId[a.frontend]
200
- return fe?.platform === 'mobile' ? 'basic' : 'full'
201
- },
202
- },
203
-
204
- // ── DevOps extras ──────────────────────────────────────────────────────
205
- {
206
- type: 'confirm',
207
- name: 'docker',
208
- message: 'Include Docker?',
209
- default: true,
210
- when: (a) => {
211
- const fe = catalog.byId[a.frontend]
212
- const be = catalog.byId[a.backend]
213
- return (be?.needsDocker ?? fe?.needsDocker ?? false)
214
- },
215
- },
216
- {
217
- type: 'confirm',
218
- name: 'makefile',
219
- message: 'Include Makefile?',
220
- default: true,
221
- when: (a) => {
222
- // Makefile is useful for web stacks; less relevant for bare mobile
223
- const fe = catalog.byId[a.frontend]
224
- return fe?.platform !== 'mobile'
225
- },
226
- },
227
- {
228
- type: 'confirm',
229
- name: 'githubActions',
230
- message: 'Include GitHub Actions CI?',
231
- default: true,
232
- },
233
- {
234
- type: 'confirm',
235
- name: 'installDependencies',
236
- message: 'Install project dependencies and create the lockfile now?',
237
- default: true,
238
- when: () => !wantsInstall && !skipsInstall,
239
- },
240
-
241
- // ── Spring Boot specific ───────────────────────────────────────────────
242
- {
243
- type: 'input',
244
- name: 'packageName',
245
- message: 'Java package name? (e.g. com.yourname)',
246
- default: 'com.app',
247
- when: (a) => a.backend === 'springboot',
248
- validate: (v) => {
249
- if (!v.trim()) return 'Package name is required'
250
- if (!/^[a-z]+(\.[a-z]+)+$/.test(v)) return 'Use format: com.yourname'
251
- return true
252
- },
253
- },
254
-
255
- // ── Optional concerns (filtered to current stack) ──────────────────────
256
- {
257
- type: 'checkbox',
258
- name: 'expectedConcerns',
259
- message: 'Expected optional concerns? (advisory only — all stay available)',
260
- choices: (a) => {
261
- const stack = resolveStack({ ...a, styling: a.styling || catalog.byId[a.frontend]?.stylingOptions?.[0] }, catalog)
262
- const opts = new Set()
263
- for (const c of stack.concerns) if (!c.required) opts.add(c.id)
264
- return [...opts].map((id) => ({ name: id, value: id }))
265
- },
266
- },
267
- ]
268
- let answers = {
269
- applicationShape: shapeArg,
270
- frontend: frontendArg,
271
- backend: backendArg,
272
- }
273
- let stack
274
- while (true) {
275
- answers = await promptWithBack(inquirer, questions, answers)
276
- answers.compatibilityProfile = profile.id
277
- answers.applicationShape = shapeArg || answers.applicationShape
278
- answers.architecture = architectureArg || answers.architecture || 'medium'
279
- answers.authentication = authenticationArg || answers.authentication || 'not-yet'
280
- answers.authAudience = authAudienceArg || answers.authAudience || (catalog.byId[answers.frontend]?.platform === 'mobile' ? 'multi-client' : 'website')
281
- answers.laravelUi = laravelUiArg || answers.laravelUi || (answers.frontend === 'laravel-ui' ? 'blade' : undefined)
282
- if (wantsInstall) answers.installDependencies = true
283
- if (skipsInstall) answers.installDependencies = false
284
-
285
- // ── Auto-resolve docker for stacks that need it ───────────────────────────────
286
- if (resolveStack({ ...answers, styling: answers.styling || catalog.byId[answers.frontend]?.stylingOptions?.[0] || 'tailwind' }, catalog).needsDocker) {
287
- answers.docker = answers.docker ?? true
288
- }
289
-
290
- stack = resolveStack({ ...answers, styling: answers.styling || catalog.byId[answers.frontend]?.stylingOptions?.[0] || 'tailwind' }, catalog)
291
-
292
- // ─── Confirm ─────────────────────────────────────────────────────────────────
293
-
294
- console.log('')
295
- console.log(chalk.bold(' Summary'))
296
- console.log(chalk.gray(' ───────────────────────────'))
297
- console.log(` ${chalk.cyan('Name:')} ${answers.projectName}`)
298
- console.log(` ${chalk.cyan('Stack:')} ${stack.label}`)
299
- console.log(` ${chalk.cyan('Shape:')} ${APPLICATION_SHAPES[stack.applicationShape].label}`)
300
- console.log(` ${chalk.cyan('Platform:')} ${stack.platform}`)
301
- if (stack.styleId) {
302
- console.log(` ${chalk.cyan('Styling:')} ${catalog.byId[stack.styleId]?.label || stack.styleId}`)
303
- }
304
- if (answers.laravelUi) console.log(` ${chalk.cyan('Laravel UI:')} ${answers.laravelUi}`)
305
- console.log(` ${chalk.cyan('Architecture:')} ${stack.architecture[0].toUpperCase()}${stack.architecture.slice(1)}`)
306
- console.log(` ${chalk.cyan('Authentication:')} ${stack.authentication}`)
307
- console.log(` ${chalk.cyan('Testing:')} ${answers.testing}`)
308
- if (stack.platform !== 'mobile') {
309
- console.log(` ${chalk.cyan('Docker:')} ${answers.docker ? 'yes' : 'no'}`)
310
- console.log(` ${chalk.cyan('Makefile:')} ${answers.makefile ? 'yes' : 'no'}`)
311
- }
312
- console.log(` ${chalk.cyan('CI/CD:')} ${answers.githubActions ? 'yes' : 'no'}`)
313
- console.log(` ${chalk.cyan('Install deps:')} ${answers.installDependencies ? 'yes' : 'no'}`)
314
- if (answers.packageName) {
315
- console.log(` ${chalk.cyan('Package:')} ${answers.packageName}`)
316
- }
317
- if (stack.constraints.length) {
318
- console.log('')
319
- console.log(chalk.gray(' Key constraints:'))
320
- for (const rule of stack.constraints.slice(0, 3)) {
321
- console.log(chalk.gray(` • ${rule}`))
322
- }
323
- }
324
- console.log('')
325
-
326
- const { decision } = await inquirer.prompt([{
327
- type: 'list', name: 'decision', message: 'Ready?', choices: configurationDecisionChoices(),
328
- }])
329
-
330
- if (decision === 'back') {
331
- continue
332
- }
333
- if (decision === 'cancel') {
334
- console.log(chalk.yellow('\n Cancelled.\n'))
335
- process.exit(0)
336
- }
337
- break
338
- }
339
-
340
- // ─── Generate ────────────────────────────────────────────────────────────────
341
-
342
- console.log('')
343
- const spinner = ora('Scaffolding project...').start()
344
-
345
- try {
346
- await generateProject(answers, __dirname)
347
- spinner.succeed(chalk.green('Project created!'))
348
- const locationNotice = projectLocationNotice({ cwd: process.cwd(), cliRoot: __dirname, projectName: answers.projectName })
349
- if (locationNotice) {
350
- console.log('')
351
- console.log(chalk.yellow.bold(' Project location note'))
352
- console.log(chalk.yellow(` ${locationNotice.message}`))
353
- console.log(chalk.gray(` Created at: ${locationNotice.generatedPath}`))
354
- console.log(chalk.gray(` Suggested destination: ${locationNotice.suggestedPath}`))
355
- console.log(chalk.gray(` Linux/macOS: mv "${locationNotice.generatedPath}" "${locationNotice.suggestedPath}"`))
356
- console.log(chalk.gray(` Windows: cut the generated folder in File Explorer and paste it into your projects folder.`))
357
- }
358
- if (answers.installDependencies) {
359
- const projectRoot = path.join(process.cwd(), answers.projectName)
360
- const steps = []
361
- if (stack.backendKey === 'laravel') {
362
- const laravelRoot = ['laravel-ui', 'no-frontend'].includes(stack.frontendKey) ? projectRoot : path.join(projectRoot, 'backend')
363
- steps.push({ command: process.platform === 'win32' ? 'composer.bat' : 'composer', args: ['install'], cwd: laravelRoot, retry: `cd ${path.relative(process.cwd(), laravelRoot)} && composer install` })
364
- }
365
- const needsNpm = stack.frontendKey !== 'no-frontend' && (stack.frontendKey !== 'laravel-ui' || answers.laravelUi === 'inertia-react')
366
- if (needsNpm) {
367
- const npmRoot = stack.frontendKey === 'react' ? path.join(projectRoot, 'frontend') : projectRoot
368
- steps.push({ command: process.platform === 'win32' ? 'npm.cmd' : 'npm', args: ['install'], cwd: npmRoot, retry: `cd ${path.relative(process.cwd(), npmRoot)} && npm install` })
369
- }
370
- const installSpinner = ora('Installing exact dependencies and creating lockfiles...').start()
371
- const failed = steps.find((step) => spawnSync(step.command, step.args, { cwd: step.cwd, stdio: 'inherit', shell: false }).status !== 0)
372
- if (failed) {
373
- installSpinner.warn(chalk.yellow('Project created, but dependency installation did not finish.'))
374
- console.log(chalk.yellow(` Retry with: ${failed.retry}`))
375
- } else {
376
- installSpinner.succeed(chalk.green('Dependencies installed and lockfiles created.'))
377
- }
378
- }
379
- console.log('')
380
- console.log(chalk.bold(` Next steps:`))
381
- console.log(chalk.gray(` cd ${answers.projectName}`))
382
-
383
- if (stack.backendKey === 'laravel') {
384
- const laravelDir = ['laravel-ui', 'no-frontend'].includes(stack.frontendKey) ? '' : 'backend/'
385
- console.log(chalk.gray(` cp ${laravelDir}.env.example ${laravelDir}.env`))
386
- if (answers.makefile) {
387
- console.log(chalk.gray(` make setup # first run only`))
388
- console.log(chalk.gray(` make run # later runs; never rebuilds`))
389
- } else {
390
- console.log(chalk.gray(` docker compose build`))
391
- console.log(chalk.gray(` docker compose up -d`))
392
- }
393
- } else if (stack.frontendKey === 'react') {
394
- if (stack.backendKey === 'springboot') {
395
- console.log(chalk.gray(` cp .env.example .env # Docker/backend values`))
396
- }
397
- console.log(chalk.gray(` cd frontend`))
398
- console.log(chalk.gray(` cp .env.example .env`))
399
- if (!answers.installDependencies) console.log(chalk.gray(` npm install`))
400
- } else {
401
- console.log(chalk.gray(` cp .env.example ${stack.isMobile ? '.env' : '.env.local'}`))
402
- if (!answers.installDependencies) console.log(chalk.gray(` npm install`))
403
- }
404
-
405
- if (stack.backendKey === 'laravel') {
406
- // Laravel commands were printed above because its root differs by application shape.
407
- } else if (stack.isMobile) {
408
- console.log(chalk.gray(` npx expo start`))
409
- } else if (answers.makefile) {
410
- if (stack.frontendKey === 'react') {
411
- console.log(chalk.gray(` cd ..`))
412
- }
413
- console.log(chalk.gray(` make dev`))
414
- } else if (stack.backendKey === 'supabase') {
415
- console.log(chalk.gray(` npm run supabase:start`))
416
- console.log(chalk.gray(` npm run dev`))
417
- } else if (stack.frontendKey === 'react') {
418
- console.log(chalk.gray(` npm run dev`))
419
- } else {
420
- console.log(chalk.gray(` docker compose up -d db`))
421
- console.log(chalk.gray(` npm run dev`))
422
- }
423
-
424
- console.log('')
425
- console.log(chalk.cyan(` Read RULES.md before starting — it maps every playbook for this stack.`))
426
- console.log('')
427
- } catch (err) {
428
- spinner.fail(chalk.red('Failed to generate project'))
429
- console.error(err)
430
- process.exit(1)
431
- }
4
+ await runCli()
@@ -1,6 +1,6 @@
1
1
  # Playbook Index
2
2
 
3
- Playbooks are task-routed standards used by generated projects. Their co-located manifests decide when a playbook applies and which headings appear in generated `RULES.md`.
3
+ Guidance files are task-routed standards used by generated projects. Their co-located definitions decide when guidance applies and which headings appear in generated `RULES.md`.
4
4
 
5
5
  ## Content groups
6
6
 
@@ -17,7 +17,7 @@ Playbooks are task-routed standards used by generated projects. Their co-located
17
17
  ## Composition
18
18
 
19
19
  ```text
20
- required universal + selected platform manifests
20
+ required universal + selected platform definitions
21
21
  + selected frontend
22
22
  + selected backend/database/migration
23
23
  + selected styling
@@ -37,10 +37,10 @@ Expo uses React Native `StyleSheet` by default. Web projects can select Tailwind
37
37
 
38
38
  ## Manifest contract
39
39
 
40
- A stack manifest must declare all three architecture profiles. Any manifest may declare:
40
+ A stack definition must declare all three architecture profiles. Any definition may declare:
41
41
 
42
42
  - `id`, `kind`, `label`, and compatibility;
43
- - dependency names, scripts, templates, and conditional playbooks (versions live only in `compatibility/profiles.json`);
43
+ - dependency names, scripts, templates, and conditional playbooks (versions live only in `library/tested-versions.json`);
44
44
  - semantic environment names and client-visible names;
45
45
  - stack constraints;
46
46
  - required and optional concerns, target playbook, headings, and applicability.
@@ -2,6 +2,7 @@
2
2
  "id": "github-actions",
3
3
  "kind": "devops",
4
4
  "label": "GitHub Actions CI",
5
+ "file": "capabilities/ci/github-actions.md",
5
6
  "required": false,
6
7
  "folders": [".github/workflows"],
7
8
  "concerns": [
@@ -2,6 +2,7 @@
2
2
  "id": "makefile",
3
3
  "kind": "devops",
4
4
  "label": "Makefile",
5
+ "file": "devops/makefile.md",
5
6
  "required": false,
6
7
  "folders": [],
7
8
  "concerns": [
@@ -2,6 +2,7 @@
2
2
  "id": "pr-template",
3
3
  "kind": "devops",
4
4
  "label": "PR Template",
5
+ "file": "devops/pr-template.md",
5
6
  "required": false,
6
7
  "folders": [],
7
8
  "concerns": [
@@ -2,6 +2,7 @@
2
2
  "id": "mobile-platform",
3
3
  "kind": "platform",
4
4
  "label": "Mobile Platform",
5
+ "file": "platform/mobile.md",
5
6
  "appliesTo": { "platform": ["mobile"] },
6
7
  "deps": [],
7
8
  "devDeps": [],