create-lumen 2.0.0-alpha.2 → 2.0.0-beta.2

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 (174) hide show
  1. package/README.md +24 -6
  2. package/bin/cli.js +2 -1
  3. package/package.json +2 -2
  4. package/schema/lumen.config.v2.json +29 -0
  5. package/src/agent-context.js +71 -47
  6. package/src/cleanup.js +19 -1
  7. package/src/cli-args.js +131 -5
  8. package/src/configure.js +8 -4
  9. package/src/dependencies.js +46 -13
  10. package/src/engine/capabilities.js +71 -1
  11. package/src/engine/composition.js +334 -17
  12. package/src/engine/providers/base-provider.js +4 -4
  13. package/src/env.js +24 -6
  14. package/src/main.js +38 -15
  15. package/src/manifest/emit.js +14 -1
  16. package/src/manifest/schema.js +30 -0
  17. package/src/prompts.js +227 -23
  18. package/templates/bases/next/app-router/js/.provenance.json +19 -0
  19. package/templates/bases/next/app-router/js/README.md +36 -0
  20. package/templates/bases/next/app-router/js/jsconfig.json +7 -0
  21. package/templates/bases/next/app-router/js/next.config.mjs +6 -0
  22. package/templates/bases/next/app-router/js/package.json +15 -0
  23. package/templates/bases/next/app-router/js/public/file.svg +1 -0
  24. package/templates/bases/next/app-router/js/public/globe.svg +1 -0
  25. package/templates/bases/next/app-router/js/public/next.svg +1 -0
  26. package/templates/bases/next/app-router/js/public/vercel.svg +1 -0
  27. package/templates/bases/next/app-router/js/public/window.svg +1 -0
  28. package/templates/bases/next/app-router/js/src/app/api/hello/route.js +5 -0
  29. package/templates/bases/next/app-router/js/src/app/blog/[slug]/page.js +33 -0
  30. package/templates/bases/next/app-router/js/src/app/favicon.ico +0 -0
  31. package/templates/bases/next/app-router/js/src/app/globals.css +49 -0
  32. package/templates/bases/next/app-router/js/src/app/layout.js +25 -0
  33. package/templates/bases/next/app-router/js/src/app/loading.js +15 -0
  34. package/templates/bases/next/app-router/js/src/app/not-found.js +29 -0
  35. package/templates/bases/next/app-router/js/src/app/page.js +69 -0
  36. package/templates/bases/next/app-router/js/src/app/page.module.css +150 -0
  37. package/templates/bases/next/app-router/ts/.provenance.json +19 -0
  38. package/templates/bases/next/app-router/ts/README.md +36 -0
  39. package/templates/bases/next/app-router/ts/next.config.ts +7 -0
  40. package/templates/bases/next/app-router/ts/package.json +21 -0
  41. package/templates/bases/next/app-router/ts/public/file.svg +1 -0
  42. package/templates/bases/next/app-router/ts/public/globe.svg +1 -0
  43. package/templates/bases/next/app-router/ts/public/next.svg +1 -0
  44. package/templates/bases/next/app-router/ts/public/vercel.svg +1 -0
  45. package/templates/bases/next/app-router/ts/public/window.svg +1 -0
  46. package/templates/bases/next/app-router/ts/src/app/api/hello/route.ts +5 -0
  47. package/templates/bases/next/app-router/ts/src/app/blog/[slug]/page.tsx +38 -0
  48. package/templates/bases/next/app-router/ts/src/app/favicon.ico +0 -0
  49. package/templates/bases/next/app-router/ts/src/app/globals.css +49 -0
  50. package/templates/bases/next/app-router/ts/src/app/layout.tsx +30 -0
  51. package/templates/bases/next/app-router/ts/src/app/loading.tsx +15 -0
  52. package/templates/bases/next/app-router/ts/src/app/not-found.tsx +29 -0
  53. package/templates/bases/next/app-router/ts/src/app/page.module.css +150 -0
  54. package/templates/bases/next/app-router/ts/src/app/page.tsx +69 -0
  55. package/templates/bases/next/app-router/ts/tsconfig.json +34 -0
  56. package/templates/bases/next/pages-router/js/.provenance.json +19 -0
  57. package/templates/bases/next/pages-router/js/README.md +40 -0
  58. package/templates/bases/next/pages-router/js/jsconfig.json +7 -0
  59. package/templates/bases/next/pages-router/js/next.config.mjs +7 -0
  60. package/templates/bases/next/pages-router/js/package.json +15 -0
  61. package/templates/bases/next/pages-router/js/public/favicon.ico +0 -0
  62. package/templates/bases/next/pages-router/js/public/file.svg +1 -0
  63. package/templates/bases/next/pages-router/js/public/globe.svg +1 -0
  64. package/templates/bases/next/pages-router/js/public/next.svg +1 -0
  65. package/templates/bases/next/pages-router/js/public/vercel.svg +1 -0
  66. package/templates/bases/next/pages-router/js/public/window.svg +1 -0
  67. package/templates/bases/next/pages-router/js/src/pages/404.js +35 -0
  68. package/templates/bases/next/pages-router/js/src/pages/500.js +35 -0
  69. package/templates/bases/next/pages-router/js/src/pages/_app.js +20 -0
  70. package/templates/bases/next/pages-router/js/src/pages/_document.js +15 -0
  71. package/templates/bases/next/pages-router/js/src/pages/api/hello.js +5 -0
  72. package/templates/bases/next/pages-router/js/src/pages/blog/[slug].js +41 -0
  73. package/templates/bases/next/pages-router/js/src/pages/index.js +77 -0
  74. package/templates/bases/next/pages-router/js/src/styles/Home.module.css +149 -0
  75. package/templates/bases/next/pages-router/js/src/styles/globals.css +42 -0
  76. package/templates/bases/next/pages-router/ts/.provenance.json +19 -0
  77. package/templates/bases/next/pages-router/ts/README.md +40 -0
  78. package/templates/bases/next/pages-router/ts/next.config.ts +8 -0
  79. package/templates/bases/next/pages-router/ts/package.json +21 -0
  80. package/templates/bases/next/pages-router/ts/public/favicon.ico +0 -0
  81. package/templates/bases/next/pages-router/ts/public/file.svg +1 -0
  82. package/templates/bases/next/pages-router/ts/public/globe.svg +1 -0
  83. package/templates/bases/next/pages-router/ts/public/next.svg +1 -0
  84. package/templates/bases/next/pages-router/ts/public/vercel.svg +1 -0
  85. package/templates/bases/next/pages-router/ts/public/window.svg +1 -0
  86. package/templates/bases/next/pages-router/ts/src/pages/404.tsx +35 -0
  87. package/templates/bases/next/pages-router/ts/src/pages/500.tsx +35 -0
  88. package/templates/bases/next/pages-router/ts/src/pages/_app.tsx +21 -0
  89. package/templates/bases/next/pages-router/ts/src/pages/_document.tsx +15 -0
  90. package/templates/bases/next/pages-router/ts/src/pages/api/hello.ts +13 -0
  91. package/templates/bases/next/pages-router/ts/src/pages/blog/[slug].tsx +46 -0
  92. package/templates/bases/next/pages-router/ts/src/pages/index.tsx +77 -0
  93. package/templates/bases/next/pages-router/ts/src/styles/Home.module.css +149 -0
  94. package/templates/bases/next/pages-router/ts/src/styles/globals.css +42 -0
  95. package/templates/bases/next/pages-router/ts/tsconfig.json +29 -0
  96. package/templates/conditional/database/prisma/db.js +14 -0
  97. package/templates/conditional/database/prisma/db.ts +16 -0
  98. package/templates/conditional/database/prisma/schema.prisma +9 -0
  99. package/templates/conditional/database/prisma/seed.js +16 -0
  100. package/templates/conditional/database/prisma/seed.ts +16 -0
  101. package/templates/conditional/docker/.dockerignore +10 -0
  102. package/templates/conditional/docker/Dockerfile +57 -0
  103. package/templates/conditional/docker/docker-compose.yml +35 -0
  104. package/templates/conditional/linting/biome/biome.json +31 -0
  105. package/templates/conditional/next/config/cloudflare/next.config.mjs +6 -0
  106. package/templates/conditional/next/config/cloudflare/next.config.ts +7 -0
  107. package/templates/conditional/next/config/node/next.config.mjs +6 -0
  108. package/templates/conditional/next/config/node/next.config.ts +7 -0
  109. package/templates/conditional/next/config/static/next.config.mjs +6 -0
  110. package/templates/conditional/next/config/static/next.config.ts +7 -0
  111. package/templates/conditional/next/config/turbopack/next.config.mjs +7 -0
  112. package/templates/conditional/next/config/turbopack/next.config.ts +8 -0
  113. package/templates/conditional/next/config/vercel/next.config.mjs +6 -0
  114. package/templates/conditional/next/config/vercel/next.config.ts +7 -0
  115. package/templates/conditional/next/config/webpack/next.config.mjs +9 -0
  116. package/templates/conditional/next/config/webpack/next.config.ts +10 -0
  117. package/templates/conditional/next/font/BUILD_SIZE_NOTE.md +17 -0
  118. package/templates/conditional/next/font/app-router/src/app/layout.js +25 -0
  119. package/templates/conditional/next/font/app-router/src/app/layout.tsx +30 -0
  120. package/templates/conditional/next/font/pages-router/src/pages/_app.js +20 -0
  121. package/templates/conditional/next/font/pages-router/src/pages/_app.tsx +21 -0
  122. package/templates/conditional/next/font/pages-router/src/pages/_document.js +18 -0
  123. package/templates/conditional/next/font/pages-router/src/pages/_document.tsx +18 -0
  124. package/templates/conditional/next/font/src/app/layout.js +25 -0
  125. package/templates/conditional/next/font/src/app/layout.tsx +30 -0
  126. package/templates/conditional/next/font/src/lib/fonts.js +11 -0
  127. package/templates/conditional/next/font/src/lib/fonts.ts +11 -0
  128. package/templates/conditional/next/font/src/pages/_app.js +20 -0
  129. package/templates/conditional/next/font/src/pages/_app.tsx +21 -0
  130. package/templates/conditional/next/font/src/pages/_document.js +18 -0
  131. package/templates/conditional/next/font/src/pages/_document.tsx +18 -0
  132. package/templates/conditional/next/image/BUILD_SIZE_NOTE.md +15 -0
  133. package/templates/conditional/next/image/app-router/src/components/ImageExample.jsx +34 -0
  134. package/templates/conditional/next/image/app-router/src/components/ImageExample.tsx +44 -0
  135. package/templates/conditional/next/image/pages-router/src/components/ImageExample.jsx +34 -0
  136. package/templates/conditional/next/image/pages-router/src/components/ImageExample.tsx +44 -0
  137. package/templates/conditional/next/image/src/components/ImageExample.jsx +44 -0
  138. package/templates/conditional/next/image/src/components/ImageExample.tsx +54 -0
  139. package/templates/conditional/next/image/src/components/image/index.js +1 -0
  140. package/templates/conditional/next/image/src/components/image/index.ts +2 -0
  141. package/templates/conditional/next/route-groups/js/src/app/(admin)/layout.jsx +23 -0
  142. package/templates/conditional/next/route-groups/js/src/app/(auth)/layout.jsx +17 -0
  143. package/templates/conditional/next/route-groups/js/src/app/(private)/layout.jsx +27 -0
  144. package/templates/conditional/next/route-groups/js/src/app/(public)/layout.jsx +28 -0
  145. package/templates/conditional/next/route-groups/js/src/app/(public)/page.jsx +38 -0
  146. package/templates/conditional/next/route-groups/js/src/middleware.js +48 -0
  147. package/templates/conditional/next/route-groups/ts/src/app/(admin)/layout.tsx +28 -0
  148. package/templates/conditional/next/route-groups/ts/src/app/(auth)/layout.tsx +22 -0
  149. package/templates/conditional/next/route-groups/ts/src/app/(private)/layout.tsx +32 -0
  150. package/templates/conditional/next/route-groups/ts/src/app/(public)/layout.tsx +33 -0
  151. package/templates/conditional/next/route-groups/ts/src/app/(public)/page.tsx +38 -0
  152. package/templates/conditional/next/route-groups/ts/src/middleware.ts +49 -0
  153. package/templates/conditional/next/server-components/README.md +13 -0
  154. package/templates/conditional/next/server-components/eslint.config.mjs +47 -0
  155. package/templates/conditional/next/server-components/rules/no-server-imports-in-client.js +89 -0
  156. package/templates/conditional/next/server-components/rules/no-server-imports-in-client.mjs +88 -0
  157. package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.jsx +48 -0
  158. package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.tsx +53 -0
  159. package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.jsx +41 -0
  160. package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.tsx +46 -0
  161. package/templates/conditional/next/server-components/src/components/boundaries/index.js +2 -0
  162. package/templates/conditional/next/server-components/src/components/boundaries/index.ts +4 -0
  163. package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.jsx +10 -0
  164. package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.tsx +10 -0
  165. package/templates/conditional/testing/jest/next/jest.config.mjs +20 -0
  166. package/templates/conditional/testing/jest/next/jest.config.ts +20 -0
  167. package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.jsx +10 -0
  168. package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.tsx +10 -0
  169. package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.jsx +11 -0
  170. package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.tsx +11 -0
  171. package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.jsx +11 -0
  172. package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.tsx +11 -0
  173. package/templates/conditional/testing/vitest/next/vitest.config.js +16 -0
  174. package/templates/conditional/testing/vitest/next/vitest.config.ts +16 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # create-lumen
2
2
 
3
- A scaffolder that generates production-ready React + Vite projects (feature-based, type-based, hybrid, or none).
3
+ A scaffolder that generates production-ready **React + Vite** or **Next.js** projects (App Router / Pages Router), with a choice of architecture: feature-based, type-based, hybrid, or none.
4
4
 
5
5
  Note: this repository is the CLI scaffolder itself, not a generated app. Generated projects live in the target folder you create.
6
6
 
@@ -14,6 +14,8 @@ cd my-app
14
14
  npm run dev
15
15
  ```
16
16
 
17
+ > The v2 line (React + Vite + Next.js, manifest v2) is available on the `beta` dist-tag while v2 is in prerelease: `npm create lumen@beta my-app`.
18
+
17
19
  ### Non-interactive flags
18
20
 
19
21
  Quick Setup (TypeScript + Tailwind + Feature-based + Router + ESLint + Prettier + Vitest), no prompts:
@@ -26,20 +28,28 @@ npm create lumen -- -y my-app
26
28
  npm create lumen my-app -- --yes
27
29
  ```
28
30
 
29
- Scaffold using a template preset (`react-ts`, `react-js`, `react-type-ts`, `react-type-js`):
31
+ Scaffold using a template preset (`react-ts`, `react-js`, `react-type-ts`, `react-type-js`, `next-ts`, `next-js`, `next-pages-ts`, `next-pages-js`):
30
32
 
31
33
  ```bash
32
34
  npm create lumen my-app -- --template react-ts
33
35
  # or
34
- npm create lumen my-app -- -t react-type-js
36
+ npm create lumen my-app -- -t next-ts
35
37
  ```
36
38
 
37
39
  Choose framework and architecture directly:
38
40
 
39
41
  ```bash
40
42
  npm create lumen my-app -- --framework react --arch none
43
+
44
+ # Next.js — the router is asked interactively (App Router recommended):
45
+ npm create lumen my-app -- -f next
46
+ # ...or pin the variant outright:
47
+ npm create lumen my-app -- -f next:app-router
48
+ npm create lumen my-app -- -f next:pages-router
41
49
  ```
42
50
 
51
+ Interactively, the framework is the first prompt (React + Vite / Next.js), followed by a router step when Next.js is selected; every later option gates on that choice.
52
+
43
53
  Drive scaffolding directly from a manifest file or inline JSON (manifest v2):
44
54
 
45
55
  ```bash
@@ -70,20 +80,25 @@ create-lumen my-app # after npm link
70
80
 
71
81
  ## Features
72
82
 
83
+ - **Two frameworks:** React + Vite (SPA) or Next.js (App Router / Pages Router) — selected interactively or via `-f`
73
84
  - Architecture choice: feature-based, type-based, hybrid, or none (per-framework availability)
74
85
  - TypeScript or JavaScript output
75
86
  - CSS frameworks: Tailwind CSS v4 (CSS-first `@theme`, v3 dropped) or none
87
+ - **UI kit: shadcn/ui** (gated to Tailwind; primitives + `cn` helper land in the mapped `paths.ui`)
76
88
  - Optional state management: Zustand or Redux Toolkit
77
- - Optional router (React Router)
78
- - Optional testing: Vitest or Jest
89
+ - Optional router (React Router — Vite only; Next.js uses filesystem routing)
90
+ - Optional testing: Vitest or Jest (Next.js defaults to none until a Next-native overlay is verified)
91
+ - **Next.js-native overlays:** server/client boundary demos + `no-server-imports-in-client` ESLint rule, `next/font` (Geist), `next/image`, filesystem routing examples, route handlers (`route.ts`) / API routes (`pages/api`)
92
+ - **Next.js options:** `bundler: turbopack|webpack`, `adapter: node|vercel|cloudflare|static` (`next.config` overlays), `reactCompiler`, `linter: biome` — all validated against the capability model
79
93
  - Linting: ESLint (default), Oxlint, or Biome
80
94
  - Code formatter: Prettier or Oxfmt (chosen after the linter; `eslint-config-prettier` is auto-wired for the ESLint + Prettier combo)
81
- - Manifest v2: Emits a deterministic `lumen.config.json` (optional but recommended) validated against JSON Schema Draft 2020-12, including architecture validation modes and harness command configuration.
95
+ - Manifest v2: Emits a deterministic `lumen.config.json` validated against JSON Schema Draft 2020-12, including architecture validation modes and harness command configuration.
82
96
  - Explicit path mapping: Target directories declared for `lumen-cli` code generation
83
97
  - Optional Axios setup and icon libraries
84
98
  - Auto `git init`, generated `README` (with a project description reflecting the chosen tooling) and `LICENSE`
85
99
  - `.env.example` scaffolded, and the generated `.gitignore` excludes env files (`.env`, `.env.*`) while keeping `.env.example`
86
100
  - `@/` path alias configured (`tsconfig`/`jsconfig` + Vite)
101
+ - **Agent docs:** optional `.lumen/` context directory (project, architecture, conventions) via prompt or `--agent-docs`
87
102
 
88
103
  Your selected options are cached and offered as defaults on subsequent runs.
89
104
 
@@ -206,6 +221,9 @@ The manifest now includes:
206
221
  ### Ecosystem Documentation & Contracts
207
222
 
208
223
  - **[Documentation Index](./docs/README.md):** Complete overview of specifications, ADRs, and developer guides.
224
+ - **[Next.js App Router Guide](./docs/next-app-router.md):** App Router scaffold, filesystem routing, route handlers, server/client boundaries, `next/font` and `next/image`.
225
+ - **[Next.js Pages Router Guide](./docs/next-pages-router.md):** Pages Router scaffold, API routes, `_app`/`_document` wiring.
226
+ - **[Deployment Adapters Guide](./docs/next-adapters.md):** Adapter matrix (Node, Vercel, Cloudflare, static), bundler choice and Biome setup for Next.js.
209
227
  - **[Generator Engine & Capabilities Guide (M2)](./docs/engine-capabilities.md):** Capability model, template composition, shadcn/ui on Tailwind v4, architecture presets, and Bootstrap removal.
210
228
  - **[Manifest v2 Overview](./docs/manifest-v2.md):** Specification of the nested schema, validation behavior, and the new harness and architecture validation fields.
211
229
  - **[Shared Contract: `create-lumen` ↔ `lumen-cli`](./docs/contracts/manifest-v2-contract.md):** The agreed contract for path mapping, barrel conventions, version negotiation, and the harness/configuration interface.
package/bin/cli.js CHANGED
@@ -25,7 +25,7 @@ console.log(
25
25
  chalk.bold.cyan("\n ✦ LUMEN") +
26
26
  chalk.gray(` v${version}`) +
27
27
  chalk.gray(
28
- "\n Scaffold a React + Vite project with architecture choice\n"
28
+ "\n Scaffold a React, Vite, or Next.js project with architecture choice\n"
29
29
  )
30
30
  );
31
31
 
@@ -36,6 +36,7 @@ main({
36
36
  projectName: cliArgs.projectName,
37
37
  manifest: cliArgs.manifest,
38
38
  template: cliArgs.template,
39
+ framework: cliArgs.framework,
39
40
  arch: cliArgs.arch,
40
41
  }).catch((e) => {
41
42
  console.error(chalk.red("\nError:"), e.message || e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-lumen",
3
- "version": "2.0.0-alpha.2",
3
+ "version": "2.0.0-beta.2",
4
4
  "description": "Scaffold a React + Vite project with architecture choice: feature-based, type-based, hybrid, or none.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "https://github.com/LUMEN-SH/create-lumen"
11
+ "url": "https://github.com/strophix-software/create-lumen"
12
12
  },
13
13
  "files": [
14
14
  "bin/",
@@ -247,6 +247,35 @@
247
247
  ],
248
248
  "additionalProperties": false
249
249
  },
250
+ "database": {
251
+ "type": "object",
252
+ "properties": {
253
+ "orm": {
254
+ "type": "string",
255
+ "enum": [
256
+ "prisma",
257
+ "none"
258
+ ]
259
+ },
260
+ "provider": {
261
+ "default": "postgresql",
262
+ "type": "string",
263
+ "enum": [
264
+ "postgresql"
265
+ ]
266
+ }
267
+ },
268
+ "required": [
269
+ "orm"
270
+ ],
271
+ "additionalProperties": false
272
+ },
273
+ "docker": {
274
+ "type": "boolean"
275
+ },
276
+ "routeGroups": {
277
+ "type": "boolean"
278
+ },
250
279
  "reactCompiler": {
251
280
  "type": "boolean"
252
281
  },
@@ -19,17 +19,19 @@ function resolveManifest(manifestOrResponses) {
19
19
  }
20
20
 
21
21
  /**
22
- * Render the short agent conventions markdown from a manifest.
23
- * Deterministic: derived only from stable manifest fields.
22
+ * Render the root AGENTS.md document from a manifest.
23
+ * Single Source of Truth (SSOT): references `lumen.config.json` directly.
24
+ * Byte-deterministic: derived only from stable manifest fields (no timestamps).
24
25
  * @param {object} manifest - validated manifest v2
25
26
  * @returns {string}
26
27
  */
27
- export function renderConventions(manifest) {
28
+ export function renderAgentsMd(manifest) {
28
29
  const preset = manifest.architecture?.preset ?? manifest.architecture?.type ?? "feature-based";
29
30
  const validation = manifest.architecture?.validation ?? "strict";
30
31
  const paths = manifest.paths ?? {};
31
32
  const harness = manifest.harness ?? { commands: [] };
32
33
  const barrelFile = manifest.tooling?.language === "js" ? "index.js" : "index.ts";
34
+ const isNext = manifest.framework?.name === "next";
33
35
 
34
36
  const pathLines = Object.entries(paths)
35
37
  .map(([key, value]) => `- ${key}: \`${value}\``)
@@ -41,74 +43,96 @@ export function renderConventions(manifest) {
41
43
 
42
44
  const harnessSummary = (harness.commands ?? []).map((c) => `\`${c.command}\``).join(", ");
43
45
 
46
+ let nextSection = "";
47
+ if (isNext) {
48
+ nextSection =
49
+ `<!-- BEGIN:nextjs-agent-rules -->\n` +
50
+ `## This is NOT the Next.js you know\n` +
51
+ `This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ (resolved from this file's directory...) before writing any code. Heed deprecation notices.\n` +
52
+ `<!-- END:nextjs-agent-rules -->\n` +
53
+ `\n` +
54
+ `### Next.js Component Rules\n` +
55
+ `\n` +
56
+ `- By default, all components in the App Router are React Server Components (RSC).\n` +
57
+ `- Use the \`"use client"\` directive only at the top of files that require browser APIs, React state, or event handlers.\n` +
58
+ `- Never import server-only modules or secrets into client components.\n` +
59
+ `\n`;
60
+ }
61
+
62
+ const uiPath = paths.ui ?? "@/components/ui";
63
+ const componentsPath = paths.components ?? "@/components";
64
+
44
65
  return (
45
- `# Lumen Agent Conventions\n` +
66
+ `# AGENTS.md\n` +
67
+ `\n` +
68
+ `> **Single Source of Truth (SSOT)**: Project configuration, paths, and harness definitions are defined in \`lumen.config.json\`. Consult \`lumen.config.json\` for the authoritative settings.\n` +
46
69
  `\n` +
47
- `Generated from \`lumen.config.json\`. Resolve every file location via \`paths.*\` in \`.lumen/architecture.json\` (preset: \`${preset}\`, validation: \`${validation}\`).\n` +
70
+ `${nextSection}` +
71
+ `## Architecture & Conventions\n` +
48
72
  `\n` +
49
- `## Rules\n` +
73
+ `- **Architecture Preset**: \`${preset}\`\n` +
74
+ `- **Validation Mode**: \`${validation}\` (${VALIDATION_NOTES[validation] ?? VALIDATION_NOTES.strict})\n` +
75
+ `- **Paths Resolution**: Resolve file locations via \`paths.*\` defined in \`lumen.config.json\` — never hardcode arbitrary alternative locations.\n` +
76
+ `- **Feature Barrels**: Every feature directory (under \`${paths.features ?? "src/features"}/<feature>/\`) must re-export its public API via \`${barrelFile}\`.\n` +
77
+ `- **Shared Imports**: Import shared utilities via \`@/shared\` (plus path aliases configured in tsconfig/jsconfig).\n` +
50
78
  `\n` +
51
- `- Resolve all paths via \`paths.*\` — never hardcode alternative \`src/...\` locations.\n` +
52
- `- Feature barrel: every \`src/features/<feature>/\` must re-export its public API via \`${barrelFile}\`.\n` +
53
- `- Shared code: import shared utilities via \`@/shared\` (plus the \`@/...\` aliases from tsconfig/jsconfig).\n` +
54
- `- Harness: before finishing, run each command in \`.lumen/project.json\` \`harness.commands\` in order (${harnessSummary}).\n` +
55
- `- Validation mode \`${validation}\`: ${VALIDATION_NOTES[validation] ?? VALIDATION_NOTES.strict}\n` +
79
+ `## Component Taxonomy\n` +
56
80
  `\n` +
57
- `## Paths\n` +
81
+ `Components must be organized strictly according to \`lumen.config.json\` path mappings:\n` +
82
+ `- **Atomic UI Primitives**: Base atoms and UI primitives (e.g. shadcn components) belong in \`paths.ui\` (\`${uiPath}\`).\n` +
83
+ `- **Compound Components**: Shared composite components belong in \`paths.components\` (\`${componentsPath}\`).\n` +
84
+ `- **Feature Components**: Domain-specific components belong inside their respective feature folder (e.g. \`${paths.features ?? "src/features"}/<feature>/components\`).\n` +
85
+ `\n` +
86
+ `Lumen CLI and AI agents must place primitive UI atoms under \`paths.ui\`, global compound components under \`paths.components\`, and feature-scoped components in their respective feature folders.\n` +
87
+ `\n` +
88
+ `## Path Mappings\n` +
58
89
  `\n` +
59
90
  `${pathLines}\n` +
60
91
  `\n` +
61
- `## Harness\n` +
92
+ `## Validation Harness\n` +
93
+ `\n` +
94
+ `Before finishing work, run each harness command defined in \`lumen.config.json\` \`harness.commands\` in order (${harnessSummary}):\n` +
62
95
  `\n` +
63
96
  `${harnessLines}\n`
64
97
  );
65
98
  }
66
99
 
67
100
  /**
68
- * Build the deterministic agent-context payload from a validated manifest v2.
69
- * No timestamps — output must be byte-stable.
101
+ * Backward compatibility alias for renderAgentsMd.
102
+ * @param {object} manifest - validated manifest v2
103
+ * @returns {string}
104
+ */
105
+ export function renderConventions(manifest) {
106
+ return renderAgentsMd(manifest);
107
+ }
108
+
109
+ /**
110
+ * Build deterministic agent context metadata.
111
+ * Retained for backwards compatibility with tests and callers.
70
112
  * @param {object} manifest - validated manifest v2
71
- * @returns {{ project: object, architecture: object, conventionsMd: string }}
113
+ * @returns {{ agentsMd: string, conventionsMd: string }}
72
114
  */
73
115
  export function buildAgentContext(manifest) {
74
- const project = {
75
- manifestVersion: manifest.manifestVersion,
76
- framework: manifest.framework,
77
- styling: manifest.styling,
78
- tooling: manifest.tooling,
79
- harness: manifest.harness,
80
- docs: manifest.docs,
81
- };
82
- const architecture = {
83
- preset: manifest.architecture?.preset ?? manifest.architecture?.type,
84
- validation: manifest.architecture?.validation ?? "strict",
85
- paths: manifest.paths,
116
+ const agentsMd = renderAgentsMd(manifest);
117
+ return {
118
+ agentsMd,
119
+ conventionsMd: agentsMd,
86
120
  };
87
- const conventionsMd = renderConventions(manifest);
88
- return { project, architecture, conventionsMd };
89
- }
90
-
91
- function writeJson(filePath, value) {
92
- return fsp.writeFile(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
93
121
  }
94
122
 
95
123
  /**
96
- * Emit the .lumen agent context dir into a scaffolded project.
97
- * Accepts either a validated manifest v2 or scaffolder responses
98
- * (uses responses._manifestSource when present, else buildManifest).
99
- * Writes .lumen/project.json, .lumen/architecture.json, .lumen/conventions.md
100
- * with 2-space indent + trailing newline. Byte-deterministic (no timestamps).
124
+ * Emit AGENTS.md directly into project root.
125
+ * Accepts either a validated manifest v2 or scaffolder responses.
126
+ * Writes ${projectPath}/AGENTS.md with trailing newline. Byte-deterministic.
127
+ * Does not emit duplicate JSON files or .lumen/ directory.
101
128
  * @param {string} projectPath - absolute path to scaffolded project
102
129
  * @param {object} manifestOrResponses
103
- * @returns {Promise<string>} absolute path to the .lumen dir
130
+ * @returns {Promise<string>} absolute path to AGENTS.md
104
131
  */
105
132
  export async function emitAgentContext(projectPath, manifestOrResponses) {
106
133
  const manifest = resolveManifest(manifestOrResponses);
107
- const { project, architecture, conventionsMd } = buildAgentContext(manifest);
108
- const dir = path.join(projectPath, ".lumen");
109
- await fsp.mkdir(dir, { recursive: true });
110
- await writeJson(path.join(dir, "project.json"), project);
111
- await writeJson(path.join(dir, "architecture.json"), architecture);
112
- await fsp.writeFile(path.join(dir, "conventions.md"), conventionsMd, "utf8");
113
- return dir;
134
+ const content = renderAgentsMd(manifest);
135
+ const filePath = path.join(projectPath, "AGENTS.md");
136
+ await fsp.writeFile(filePath, content, "utf8");
137
+ return filePath;
114
138
  }
package/src/cleanup.js CHANGED
@@ -1,7 +1,25 @@
1
1
  import { deleteIfExists } from "@/utils/fs.js";
2
+ import { promises as fsp } from "fs";
3
+ import path from "path";
2
4
 
3
- export async function cleanupBoilerplate(projectPath) {
5
+ export async function cleanupBoilerplate(projectPath, responses = {}) {
4
6
  await deleteIfExists(`${projectPath}/public`);
5
7
  await deleteIfExists(`${projectPath}/src/assets`);
6
8
  await deleteIfExists(`${projectPath}/.oxlintrc.json`);
9
+ await deleteIfExists(`${projectPath}/.provenance.json`);
10
+
11
+ const isNext =
12
+ responses?.framework?.name === "next" ||
13
+ responses?.frameworkName === "next" ||
14
+ (await fsp.stat(path.join(projectPath, "next.config.ts")).catch(() => false)) ||
15
+ (await fsp.stat(path.join(projectPath, "next.config.mjs")).catch(() => false)) ||
16
+ (await fsp.stat(path.join(projectPath, "next.config.js")).catch(() => false));
17
+
18
+ if (isNext) {
19
+ await deleteIfExists(`${projectPath}/src/app/App.tsx`);
20
+ await deleteIfExists(`${projectPath}/src/app/App.jsx`);
21
+ await deleteIfExists(`${projectPath}/src/app/hooks`);
22
+ await deleteIfExists(`${projectPath}/src/app/contexts`);
23
+ await deleteIfExists(`${projectPath}/src/app/providers`);
24
+ }
7
25
  }
package/src/cli-args.js CHANGED
@@ -6,7 +6,6 @@ import { parseManifest } from "./manifest/schema.js";
6
6
  import {
7
7
  normalizeFramework,
8
8
  isPromptVisible,
9
- getDefaultResponses,
10
9
  } from "./engine/capabilities.js";
11
10
 
12
11
  export const PRESETS = {
@@ -180,6 +179,78 @@ export const PRESETS = {
180
179
  gitInit: true,
181
180
  readme: true,
182
181
  },
182
+ "next-ts": {
183
+ frameworkName: "next",
184
+ frameworkVariant: "app-router",
185
+ architecture: "feature-based",
186
+ language: "ts",
187
+ cssFramework: "tailwind",
188
+ testing: "vitest",
189
+ router: false,
190
+ bundler: "turbopack",
191
+ stateManagement: "none",
192
+ iconLibrary: "none",
193
+ apiClient: "none",
194
+ linter: "eslint",
195
+ formatter: "prettier",
196
+ docsLanguage: "en",
197
+ gitInit: true,
198
+ readme: true,
199
+ },
200
+ "next-js": {
201
+ frameworkName: "next",
202
+ frameworkVariant: "app-router",
203
+ architecture: "feature-based",
204
+ language: "js",
205
+ cssFramework: "tailwind",
206
+ testing: "vitest",
207
+ router: false,
208
+ bundler: "turbopack",
209
+ stateManagement: "none",
210
+ iconLibrary: "none",
211
+ apiClient: "none",
212
+ linter: "eslint",
213
+ formatter: "prettier",
214
+ docsLanguage: "en",
215
+ gitInit: true,
216
+ readme: true,
217
+ },
218
+ "next-pages-ts": {
219
+ frameworkName: "next",
220
+ frameworkVariant: "pages-router",
221
+ architecture: "feature-based",
222
+ language: "ts",
223
+ cssFramework: "tailwind",
224
+ testing: "vitest",
225
+ router: false,
226
+ bundler: "turbopack",
227
+ stateManagement: "none",
228
+ iconLibrary: "none",
229
+ apiClient: "none",
230
+ linter: "eslint",
231
+ formatter: "prettier",
232
+ docsLanguage: "en",
233
+ gitInit: true,
234
+ readme: true,
235
+ },
236
+ "next-pages-js": {
237
+ frameworkName: "next",
238
+ frameworkVariant: "pages-router",
239
+ architecture: "feature-based",
240
+ language: "js",
241
+ cssFramework: "tailwind",
242
+ testing: "vitest",
243
+ router: false,
244
+ bundler: "turbopack",
245
+ stateManagement: "none",
246
+ iconLibrary: "none",
247
+ apiClient: "none",
248
+ linter: "eslint",
249
+ formatter: "prettier",
250
+ docsLanguage: "en",
251
+ gitInit: true,
252
+ readme: true,
253
+ },
183
254
  };
184
255
 
185
256
  /**
@@ -207,6 +278,18 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
207
278
  framework: { type: "string", short: "f" },
208
279
  arch: { type: "string", short: "a" },
209
280
  architecture: { type: "string" },
281
+ bundler: { type: "string", short: "b" },
282
+ adapter: { type: "string" },
283
+ database: { type: "string" },
284
+ db: { type: "string" },
285
+ "react-compiler": { type: "boolean" },
286
+ reactCompiler: { type: "boolean" },
287
+ "route-groups": { type: "boolean" },
288
+ routeGroups: { type: "boolean" },
289
+ "agent-docs": { type: "boolean" },
290
+ agentDocs: { type: "boolean" },
291
+ docker: { type: "boolean" },
292
+ linter: { type: "string" },
210
293
  help: { type: "boolean", short: "h", default: false },
211
294
  version: { type: "boolean", short: "v", default: false },
212
295
  },
@@ -223,6 +306,18 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
223
306
  else if (arch === "component" || arch === "component-based" || arch === "type") arch = "type-based";
224
307
  }
225
308
 
309
+ const bundler = values.bundler ? values.bundler.trim().toLowerCase() : null;
310
+ const adapter = values.adapter ? values.adapter.trim().toLowerCase() : null;
311
+ let database = values.database || values.db || null;
312
+ if (database) {
313
+ database = database.trim().toLowerCase();
314
+ }
315
+ const reactCompiler = values["react-compiler"] ?? values.reactCompiler ?? null;
316
+ const routeGroups = values["route-groups"] ?? values.routeGroups ?? null;
317
+ const agentDocs = values["agent-docs"] ?? values.agentDocs ?? null;
318
+ const docker = Boolean(values.docker);
319
+ const linter = values.linter ? values.linter.trim().toLowerCase() : null;
320
+
226
321
  return {
227
322
  projectName,
228
323
  quickSetup: Boolean(values.yes),
@@ -230,6 +325,14 @@ export function parseCliArgs(argv = process.argv.slice(2)) {
230
325
  template: values.template || null,
231
326
  framework: values.framework ? values.framework.trim() : null,
232
327
  arch,
328
+ bundler,
329
+ adapter,
330
+ database,
331
+ reactCompiler,
332
+ routeGroups,
333
+ agentDocs,
334
+ docker,
335
+ linter,
233
336
  help: Boolean(values.help),
234
337
  version: Boolean(values.version),
235
338
  positionals,
@@ -278,6 +381,11 @@ export function manifestToResponses(manifest, projectName) {
278
381
  const framework = normalizeFramework({ name: frameworkName, variant: frameworkVariant });
279
382
  const canClientRoute = isPromptVisible(framework, "router");
280
383
  const supportsBundler = isPromptVisible(framework, "bundler");
384
+ const supportsAdapter = isPromptVisible(framework, "adapter");
385
+ const supportsDatabase = isPromptVisible(framework, "database");
386
+ const supportsCompiler = isPromptVisible(framework, "reactCompiler");
387
+ const supportsRouteGroups = isPromptVisible(framework, "routeGroups");
388
+ const supportsDocker = isPromptVisible(framework, "docker");
281
389
 
282
390
  return {
283
391
  projectName,
@@ -287,7 +395,14 @@ export function manifestToResponses(manifest, projectName) {
287
395
  supportsBundler && manifest.framework?.bundler
288
396
  ? manifest.framework.bundler
289
397
  : undefined,
290
- adapter: manifest.framework?.adapter,
398
+ adapter:
399
+ supportsAdapter && manifest.framework?.adapter
400
+ ? manifest.framework.adapter
401
+ : undefined,
402
+ database:
403
+ supportsDatabase && manifest.database?.orm
404
+ ? manifest.database.orm
405
+ : "none",
291
406
  architecture: manifest.architecture?.type || "feature-based",
292
407
  language: manifest.tooling?.language || "ts",
293
408
  cssFramework: manifest.styling?.engine || "tailwind",
@@ -295,10 +410,14 @@ export function manifestToResponses(manifest, projectName) {
295
410
  formatter: manifest.tooling?.formatter || "prettier",
296
411
  uiKit: manifest.ui?.kit || "none",
297
412
  docsLanguage: manifest.docs?.language || "en",
298
- reactCompiler: manifest.reactCompiler,
413
+ reactCompiler: supportsCompiler ? manifest.reactCompiler : undefined,
414
+ routeGroups: supportsRouteGroups ? manifest.routeGroups : undefined,
299
415
  agentDocs: manifest.agentDocs,
300
- // Sensible defaults for scaffolder conditional passes
301
- testing: "vitest",
416
+ docker: supportsDocker ? (manifest.docker ?? false) : false,
417
+ // Sensible defaults for scaffolder conditional passes (Next.js ships no
418
+ // verified test-runner overlay yet, so it defaults to none; explicit
419
+ // vitest/jest selection still applies the standard overlays)
420
+ testing: frameworkName === "next" ? "none" : "vitest",
302
421
  router: canClientRoute,
303
422
  stateManagement: "none",
304
423
  iconLibrary: "none",
@@ -325,6 +444,9 @@ ${chalk.bold("OPTIONS")}
325
444
  ${chalk.yellow("-m, --manifest")} <path> Drive scaffolding from a lumen.config.json or inline JSON
326
445
  ${chalk.yellow("-t, --template")} <name> Scaffold using a preset (${Object.keys(PRESETS).join(", ")})
327
446
  ${chalk.yellow("-f, --framework")} <name> Target framework (react, next, react:vite, next:app-router, etc.)
447
+ ${chalk.yellow("--db, --database")} <orm> Database ORM (prisma, none)
448
+ ${chalk.yellow(" --docker")} Include multi-stage Dockerfile & docker-compose for Next.js standalone
449
+ ${chalk.yellow("--route-groups")} Include Next.js route groups preset (public, auth, private, admin)
328
450
  ${chalk.yellow("-h, --help")} Show this help message
329
451
  ${chalk.yellow("-v, --version")} Show version number
330
452
 
@@ -335,6 +457,10 @@ ${chalk.bold("PRESETS")}
335
457
  ${chalk.cyan("react-type-js")} React + Vite + JavaScript (type-based)
336
458
  ${chalk.cyan("react-none-ts")} React + Vite + TypeScript (none)
337
459
  ${chalk.cyan("react-none-js")} React + Vite + JavaScript (none)
460
+ ${chalk.cyan("next-ts")} Next.js App Router + TypeScript (feature-based)
461
+ ${chalk.cyan("next-js")} Next.js App Router + JavaScript (feature-based)
462
+ ${chalk.cyan("next-pages-ts")} Next.js Pages Router + TypeScript (feature-based)
463
+ ${chalk.cyan("next-pages-js")} Next.js Pages Router + JavaScript (feature-based)
338
464
 
339
465
  ${chalk.bold("EXAMPLES")}
340
466
  ${chalk.gray("# Interactive setup")}
package/src/configure.js CHANGED
@@ -166,12 +166,16 @@ async function configurePackageJson(projectPath, language) {
166
166
 
167
167
  if (!pkg.scripts) pkg.scripts = {};
168
168
 
169
- pkg.scripts.dev = "vite";
170
- pkg.scripts.build = "vite build";
171
- pkg.scripts.preview = "vite preview";
169
+ // Fill defaults only — never overwrite scripts owned by the base template
170
+ // (Next.js bases ship next dev/build/start) or merged overlay fragments
171
+ // (e.g. the bundler-aware dev script). React/Vite bases already carry the
172
+ // vite commands, so this is a no-op there too.
173
+ pkg.scripts.dev ??= "vite";
174
+ pkg.scripts.build ??= "vite build";
175
+ pkg.scripts.preview ??= "vite preview";
172
176
 
173
177
  if (language === "ts") {
174
- pkg.scripts.typecheck = "tsc -b";
178
+ pkg.scripts.typecheck ??= "tsc -b";
175
179
  }
176
180
 
177
181
  await fsp.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");