create-simone-app 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/README.md +70 -0
  2. package/bin/create-simone-app.ts +6 -0
  3. package/package.json +61 -0
  4. package/src/cli.ts +97 -0
  5. package/src/matrix.ts +39 -0
  6. package/src/options.ts +137 -0
  7. package/src/prompts.ts +108 -0
  8. package/src/scaffold.ts +304 -0
  9. package/src/templates.ts +77 -0
  10. package/templates/api/base/.agents/skills/ai-rules/SKILL.md +12 -0
  11. package/templates/api/base/.agents/skills/plan-feature/SKILL.md +13 -0
  12. package/templates/api/base/.agents/skills/review-diff/SKILL.md +12 -0
  13. package/templates/api/base/.agents/skills/tdd-step/SKILL.md +14 -0
  14. package/templates/api/base/.claude/agents/reviewer.md +20 -0
  15. package/templates/api/base/.claude/hooks/format.sh +22 -0
  16. package/templates/api/base/.claude/settings.json +38 -0
  17. package/templates/api/base/.codex/agents/reviewer.toml +7 -0
  18. package/templates/api/base/.codex/config.toml +10 -0
  19. package/templates/api/base/.codex/hooks.json +12 -0
  20. package/templates/api/base/.env.example +2 -0
  21. package/templates/api/base/.github/workflows/ci.yml +18 -0
  22. package/templates/api/base/.prettierignore +10 -0
  23. package/templates/api/base/.prettierrc +20 -0
  24. package/templates/api/base/CLAUDE.md +1 -0
  25. package/templates/api/base/README.md +43 -0
  26. package/templates/api/base/TASK.md.template +30 -0
  27. package/templates/api/base/_gitignore +8 -0
  28. package/templates/api/base/docs/agents/base.md +54 -0
  29. package/templates/api/base/eslint.config.js +21 -0
  30. package/templates/api/base/openapi.yaml +233 -0
  31. package/templates/api/base/package.json +41 -0
  32. package/templates/api/base/scripts/agents-build.ts +26 -0
  33. package/templates/api/base/scripts/rules-check.ts +48 -0
  34. package/templates/api/base/scripts/rules-sync.ts +35 -0
  35. package/templates/api/base/scripts/rules.config.ts +28 -0
  36. package/templates/api/base/scripts/seed-regen.ts +64 -0
  37. package/templates/api/base/src/data/orders.json +187 -0
  38. package/templates/api/base/src/data/products.json +142 -0
  39. package/templates/api/base/src/data/repository.ts +55 -0
  40. package/templates/api/base/src/data/schemas.ts +44 -0
  41. package/templates/api/base/src/data/users.json +352 -0
  42. package/templates/api/base/src/lib/appName.ts +2 -0
  43. package/templates/api/base/src/lib/http-error.ts +14 -0
  44. package/templates/api/base/src/lib/pagination.ts +55 -0
  45. package/templates/api/base/src/modules/orders/orders.repository.ts +26 -0
  46. package/templates/api/base/src/modules/orders/orders.schema.ts +11 -0
  47. package/templates/api/base/src/modules/products/products.repository.ts +17 -0
  48. package/templates/api/base/src/modules/products/products.schema.ts +21 -0
  49. package/templates/api/base/src/modules/stats/stats.repository.ts +37 -0
  50. package/templates/api/base/src/modules/users/users.repository.ts +26 -0
  51. package/templates/api/base/src/modules/users/users.schema.ts +15 -0
  52. package/templates/api/base/test/docs.test.ts +23 -0
  53. package/templates/api/base/test/health.test.ts +19 -0
  54. package/templates/api/base/test/orders.test.ts +82 -0
  55. package/templates/api/base/test/products.test.ts +54 -0
  56. package/templates/api/base/test/repository.test.ts +45 -0
  57. package/templates/api/base/test/smoke.test.ts +7 -0
  58. package/templates/api/base/test/stats.test.ts +40 -0
  59. package/templates/api/base/test/users.test.ts +84 -0
  60. package/templates/api/base/tsconfig.json +22 -0
  61. package/templates/api/base/vitest.config.ts +16 -0
  62. package/templates/api/variants/features/no-github-ci/variant.json +1 -0
  63. package/templates/api/variants/framework/express/docs/agents/framework-express.md +9 -0
  64. package/templates/api/variants/framework/express/src/app.ts +37 -0
  65. package/templates/api/variants/framework/express/src/middleware/errorHandler.ts +13 -0
  66. package/templates/api/variants/framework/express/src/middleware/validate.ts +59 -0
  67. package/templates/api/variants/framework/express/src/modules/docs/docs.router.ts +15 -0
  68. package/templates/api/variants/framework/express/src/modules/health/health.router.ts +7 -0
  69. package/templates/api/variants/framework/express/src/modules/orders/orders.router.ts +14 -0
  70. package/templates/api/variants/framework/express/src/modules/products/products.router.ts +24 -0
  71. package/templates/api/variants/framework/express/src/modules/stats/stats.router.ts +9 -0
  72. package/templates/api/variants/framework/express/src/modules/users/users.router.ts +24 -0
  73. package/templates/api/variants/framework/express/src/server.ts +8 -0
  74. package/templates/api/variants/framework/express/test/errorHandler.test.ts +89 -0
  75. package/templates/api/variants/framework/express/variant.json +15 -0
  76. package/templates/fe/base/.agents/skills/ai-rules/SKILL.md +12 -0
  77. package/templates/fe/base/.agents/skills/plan-feature/SKILL.md +13 -0
  78. package/templates/fe/base/.agents/skills/review-diff/SKILL.md +12 -0
  79. package/templates/fe/base/.agents/skills/tdd-step/SKILL.md +14 -0
  80. package/templates/fe/base/.claude/agents/reviewer.md +21 -0
  81. package/templates/fe/base/.claude/hooks/format.sh +22 -0
  82. package/templates/fe/base/.claude/settings.json +41 -0
  83. package/templates/fe/base/.codex/agents/reviewer.toml +7 -0
  84. package/templates/fe/base/.codex/config.toml +10 -0
  85. package/templates/fe/base/.codex/hooks.json +12 -0
  86. package/templates/fe/base/.env.example +2 -0
  87. package/templates/fe/base/.github/workflows/ci.yml +37 -0
  88. package/templates/fe/base/.prettierignore +14 -0
  89. package/templates/fe/base/.prettierrc +20 -0
  90. package/templates/fe/base/CLAUDE.md +1 -0
  91. package/templates/fe/base/README.md +37 -0
  92. package/templates/fe/base/TASK.md.template +31 -0
  93. package/templates/fe/base/_gitignore +11 -0
  94. package/templates/fe/base/docs/agents/base.md +69 -0
  95. package/templates/fe/base/e2e/users.spec.ts +82 -0
  96. package/templates/fe/base/eslint.config.js +169 -0
  97. package/templates/fe/base/index.html +13 -0
  98. package/templates/fe/base/package.json +63 -0
  99. package/templates/fe/base/playwright.config.ts +17 -0
  100. package/templates/fe/base/public/favicon.svg +1 -0
  101. package/templates/fe/base/public/icons.svg +24 -0
  102. package/templates/fe/base/scripts/agents-build.ts +26 -0
  103. package/templates/fe/base/scripts/rules-check.ts +48 -0
  104. package/templates/fe/base/scripts/rules-sync.ts +35 -0
  105. package/templates/fe/base/scripts/rules.config.ts +35 -0
  106. package/templates/fe/base/src/components/shared/EmptyState.tsx +23 -0
  107. package/templates/fe/base/src/components/shared/ErrorState.tsx +37 -0
  108. package/templates/fe/base/src/components/shared/PageHeader.tsx +21 -0
  109. package/templates/fe/base/src/features/users/UserSearch.tsx +48 -0
  110. package/templates/fe/base/src/features/users/UsersPage.tsx +58 -0
  111. package/templates/fe/base/src/features/users/UsersTable.tsx +245 -0
  112. package/templates/fe/base/src/features/users/UsersTableSkeleton.tsx +16 -0
  113. package/templates/fe/base/src/features/users/__tests__/UserSearch.test.tsx +30 -0
  114. package/templates/fe/base/src/features/users/__tests__/UsersPage.test.tsx +69 -0
  115. package/templates/fe/base/src/features/users/__tests__/UsersTable.test.tsx +120 -0
  116. package/templates/fe/base/src/features/users/__tests__/useUsers.test.tsx +62 -0
  117. package/templates/fe/base/src/features/users/useUsers.ts +22 -0
  118. package/templates/fe/base/src/features/users/users.api.ts +22 -0
  119. package/templates/fe/base/src/features/users/users.schema.ts +43 -0
  120. package/templates/fe/base/src/lib/apiClient.test.ts +56 -0
  121. package/templates/fe/base/src/lib/apiClient.ts +42 -0
  122. package/templates/fe/base/src/lib/appName.ts +2 -0
  123. package/templates/fe/base/src/lib/env.test.ts +21 -0
  124. package/templates/fe/base/src/lib/env.ts +26 -0
  125. package/templates/fe/base/src/lib/queryClient.ts +15 -0
  126. package/templates/fe/base/src/lib/router.ts +11 -0
  127. package/templates/fe/base/src/main.tsx +21 -0
  128. package/templates/fe/base/src/routeTree.gen.ts +77 -0
  129. package/templates/fe/base/src/routes/__root.tsx +83 -0
  130. package/templates/fe/base/src/routes/index.tsx +26 -0
  131. package/templates/fe/base/src/routes/users/index.tsx +24 -0
  132. package/templates/fe/base/src/stores/seam.test.ts +21 -0
  133. package/templates/fe/base/src/test/api-mock.ts +22 -0
  134. package/templates/fe/base/src/test/fixtures/users.ts +39 -0
  135. package/templates/fe/base/src/test/msw/handlers.ts +9 -0
  136. package/templates/fe/base/src/test/msw/server.ts +5 -0
  137. package/templates/fe/base/src/test/render.tsx +45 -0
  138. package/templates/fe/base/src/test/setup.ts +15 -0
  139. package/templates/fe/base/tsconfig.json +25 -0
  140. package/templates/fe/base/vite.config.ts +22 -0
  141. package/templates/fe/base/vitest.config.ts +21 -0
  142. package/templates/fe/variants/features/no-github-ci/variant.json +1 -0
  143. package/templates/fe/variants/features/no-msw/src/test/api-mock.ts +52 -0
  144. package/templates/fe/variants/features/no-msw/src/test/setup.ts +13 -0
  145. package/templates/fe/variants/features/no-msw/variant.json +4 -0
  146. package/templates/fe/variants/features/no-playwright/.github/workflows/ci.yml +19 -0
  147. package/templates/fe/variants/features/no-playwright/variant.json +5 -0
  148. package/templates/fe/variants/state/jotai/docs/agents/state-jotai.md +5 -0
  149. package/templates/fe/variants/state/jotai/src/stores/index.ts +15 -0
  150. package/templates/fe/variants/state/jotai/src/stores/ui.atoms.test.tsx +29 -0
  151. package/templates/fe/variants/state/jotai/src/stores/ui.atoms.ts +39 -0
  152. package/templates/fe/variants/state/jotai/variant.json +12 -0
  153. package/templates/fe/variants/state/zustand/docs/agents/state-zustand.md +5 -0
  154. package/templates/fe/variants/state/zustand/src/stores/index.ts +12 -0
  155. package/templates/fe/variants/state/zustand/src/stores/ui.store.test.ts +34 -0
  156. package/templates/fe/variants/state/zustand/src/stores/ui.store.ts +62 -0
  157. package/templates/fe/variants/state/zustand/variant.json +5 -0
  158. package/templates/fe/variants/ui/base-ui/components.json +25 -0
  159. package/templates/fe/variants/ui/base-ui/docs/agents/ui-base-ui.md +7 -0
  160. package/templates/fe/variants/ui/base-ui/src/components/ui/alert.tsx +75 -0
  161. package/templates/fe/variants/ui/base-ui/src/components/ui/button.tsx +57 -0
  162. package/templates/fe/variants/ui/base-ui/src/components/ui/input.tsx +19 -0
  163. package/templates/fe/variants/ui/base-ui/src/components/ui/skeleton.tsx +13 -0
  164. package/templates/fe/variants/ui/base-ui/src/components/ui/table.tsx +113 -0
  165. package/templates/fe/variants/ui/base-ui/src/index.css +130 -0
  166. package/templates/fe/variants/ui/base-ui/src/lib/utils.test.ts +10 -0
  167. package/templates/fe/variants/ui/base-ui/src/lib/utils.ts +1 -0
  168. package/templates/fe/variants/ui/base-ui/variant.json +23 -0
  169. package/templates/fe/variants/ui/radix/components.json +25 -0
  170. package/templates/fe/variants/ui/radix/docs/agents/ui-radix.md +7 -0
  171. package/templates/fe/variants/ui/radix/src/components/ui/alert.tsx +75 -0
  172. package/templates/fe/variants/ui/radix/src/components/ui/button.tsx +66 -0
  173. package/templates/fe/variants/ui/radix/src/components/ui/input.tsx +18 -0
  174. package/templates/fe/variants/ui/radix/src/components/ui/skeleton.tsx +13 -0
  175. package/templates/fe/variants/ui/radix/src/components/ui/table.tsx +113 -0
  176. package/templates/fe/variants/ui/radix/src/index.css +130 -0
  177. package/templates/fe/variants/ui/radix/src/lib/utils.test.ts +10 -0
  178. package/templates/fe/variants/ui/radix/src/lib/utils.ts +1 -0
  179. package/templates/fe/variants/ui/radix/variant.json +23 -0
  180. package/templates/fullstack/.github/workflows/ci.yml +18 -0
  181. package/templates/fullstack/AGENTS.md +7 -0
  182. package/templates/fullstack/CLAUDE.md +1 -0
  183. package/templates/fullstack/README.md +16 -0
  184. package/templates/fullstack/_gitignore +11 -0
  185. package/templates/fullstack/package.json +16 -0
@@ -0,0 +1,30 @@
1
+ # Task: <title>
2
+
3
+ ## Goal
4
+
5
+ <one sentence: what changes for the API consumer>
6
+
7
+ ## Context
8
+
9
+ - Files: <src/modules/…, test/…, openapi.yaml>
10
+ - Related rules: <docs/ai-rules/… §…>
11
+
12
+ ## Increments
13
+
14
+ | # | What | Files | Verify | Commit |
15
+ |---|---|---|---|---|
16
+ | 1 | | | `bunx vitest run test/….test.ts` | `feat(scope): …` |
17
+ | 2 | | | | |
18
+
19
+ ## Definition of done
20
+
21
+ - [ ] `bun run verify` green on every commit
22
+ - [ ] `openapi.yaml` updated
23
+ - [ ] New routes covered: happy path, 422, 404
24
+ - [ ] `git log --oneline` shows one commit per increment
25
+
26
+ ## Deviations from ai-rules
27
+
28
+ | Rule § | Deviation | Why |
29
+ |---|---|---|
30
+ | | | |
@@ -0,0 +1,8 @@
1
+ node_modules
2
+ dist
3
+ coverage
4
+ .env
5
+ .env.*
6
+ !.env.example
7
+ *.log
8
+ .DS_Store
@@ -0,0 +1,54 @@
1
+ # __NAME__
2
+
3
+ Mock REST API (Bun + Express 5 + Zod) used as the backend for `simone-fe-template`
4
+ and as the `api/base` pilot of the `create-simone-app` generator. Standalone: no auth,
5
+ no database, no OIDC (not included on purpose).
6
+
7
+ ## Commands
8
+
9
+ | Command | What |
10
+ | ------------------------------------ | ---------------------------------------------------------------------------------------------------- |
11
+ | `bun run dev` | watch mode on :4000 |
12
+ | `bun run verify` | typecheck + lint + format + AGENTS check + tests (coverage ≥80%) — must be green before every commit |
13
+ | `bun run test:watch` | Vitest watch |
14
+ | `bun run seed:regen` | regenerate `src/data/*.json` (faker, seed 42, deterministic) |
15
+ | `bun run rules:sync` / `rules:check` | mirror ai-rules into `docs/ai-rules/` + `MANIFEST.json` / fail on drift |
16
+ | `bun run agents:build` | regenerate this file from `docs/agents/*.md` (never edit AGENTS.md by hand) |
17
+
18
+ ## Structure
19
+
20
+ | Path | Role |
21
+ | --------------------- | ------------------------------------------------------------------------- |
22
+ | `src/app.ts` | `createApp()`: middleware + routers, no listen |
23
+ | `src/server.ts` | listen on `PORT` (4000) |
24
+ | `src/lib/` | `http-error.ts`, `pagination.ts` (`{ data, page, limit, total }`) |
25
+ | `src/middleware/` | `validate.ts` (zod → 422), `errorHandler.ts` (`{ error, details? }`) |
26
+ | `src/modules/<name>/` | `*.schema.ts`, `*.repository.ts`, `*.router.ts` per module |
27
+ | `src/data/` | `schemas.ts` (entity zod types), committed JSON fixtures, `repository.ts` |
28
+ | `test/` | one supertest file per module |
29
+ | `openapi.yaml` | hand-written spec, served at `/docs.json` |
30
+ | `docs/ai-rules/` | read-only mirror of company ai-rules (+ `MANIFEST.json`) |
31
+ | `docs/agents/` | fragments concatenated into `AGENTS.md` |
32
+ | `.agents/skills/` | skills shared by Claude Code (`.claude/skills` symlink) and Codex |
33
+
34
+ ## Workflow
35
+
36
+ 1. Copy `TASK.md.template` → `TASK.md`, split the work into small increments.
37
+ 2. Per increment: failing test → minimal code → `bun run verify` green → one Conventional Commit whose body explains _why_ (Italian body, English subject).
38
+ 3. New route = schema + repository + router + test + `openapi.yaml` entry, same commit.
39
+ 4. Never edit `docs/ai-rules/*`, `AGENTS.md`, `src/data/*.json` by hand (sync / build / regen instead).
40
+
41
+ ## Non-negotiables
42
+
43
+ - Bun for everything (runtime, package manager, scripts). Vitest for tests (runs on Node → `src/` uses `node:*` only).
44
+ - TypeScript strict + `noUncheckedIndexedAccess`. No `any`, `@ts-ignore`, `enum`, non-null `!`; use `unknown` + guards, `as const`.
45
+ - ESLint 9 flat config with `no-duplicate-imports` + `consistent-type-imports` only; import order belongs to Prettier (`@trivago/prettier-plugin-sort-imports`).
46
+ - Every list endpoint returns `{ data, page, limit, total }`; `limit` ≤ 100; `sort=-field` for descending, whitelisted fields.
47
+ - Errors always `{ error, details? }` via `HttpError`; never leak internals on 500.
48
+ - `data-testid` naming `feature-element-qualifier` (see `docs/ai-rules/testing-selectors.md`) applies to any HTML this API ever serves.
49
+
50
+ ## ai-rules
51
+
52
+ | Topic | File |
53
+ | ------------------------------ | ------------------------------------ |
54
+ | test selectors (`data-testid`) | `docs/ai-rules/testing-selectors.md` |
@@ -0,0 +1,21 @@
1
+ import js from '@eslint/js'
2
+ import { defineConfig, globalIgnores } from 'eslint/config'
3
+ import tseslint from 'typescript-eslint'
4
+
5
+ export default defineConfig(
6
+ globalIgnores(['node_modules', 'dist', 'coverage', 'docs/ai-rules']),
7
+ js.configs.recommended,
8
+ tseslint.configs.strict,
9
+ tseslint.configs.stylistic,
10
+ {
11
+ // core-rules §10.2: only these two import rules; ordering is Prettier's job (§10.1)
12
+ rules: {
13
+ 'no-duplicate-imports': 'error',
14
+ '@typescript-eslint/consistent-type-imports': 'error',
15
+ '@typescript-eslint/no-unused-vars': [
16
+ 'error',
17
+ { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
18
+ ],
19
+ },
20
+ },
21
+ )
@@ -0,0 +1,233 @@
1
+ openapi: 3.1.0
2
+ info:
3
+ title: __NAME__
4
+ version: 0.1.0
5
+ description: Mock REST API (Bun + Express 5 + Zod). List endpoints share the `{ data, page, limit, total }` contract.
6
+ servers:
7
+ - url: http://localhost:4000
8
+ paths:
9
+ /api/health:
10
+ get:
11
+ summary: Liveness
12
+ responses:
13
+ '200':
14
+ description: OK
15
+ content:
16
+ application/json:
17
+ schema:
18
+ type: object
19
+ required: [status, uptime]
20
+ properties:
21
+ status: { type: string, const: ok }
22
+ uptime: { type: number }
23
+ /api/users:
24
+ get:
25
+ summary: List users
26
+ parameters:
27
+ - $ref: '#/components/parameters/page'
28
+ - $ref: '#/components/parameters/limit'
29
+ - name: q
30
+ in: query
31
+ description: Case-insensitive match on name or email
32
+ schema: { type: string }
33
+ - name: sort
34
+ in: query
35
+ description: One of name, email, role, createdAt; prefix with - for descending
36
+ schema: { type: string, pattern: '^-?(name|email|role|createdAt)$' }
37
+ responses:
38
+ '200':
39
+ description: Page of users
40
+ content:
41
+ application/json:
42
+ schema:
43
+ allOf:
44
+ - $ref: '#/components/schemas/Page'
45
+ - type: object
46
+ properties:
47
+ data:
48
+ {
49
+ type: array,
50
+ items: { $ref: '#/components/schemas/User' },
51
+ }
52
+ '422': { $ref: '#/components/responses/Validation' }
53
+ /api/users/{id}:
54
+ get:
55
+ summary: Get user
56
+ parameters: [{ $ref: '#/components/parameters/id' }]
57
+ responses:
58
+ '200':
59
+ description: User
60
+ content:
61
+ {
62
+ application/json:
63
+ { schema: { $ref: '#/components/schemas/User' } },
64
+ }
65
+ '404': { $ref: '#/components/responses/NotFound' }
66
+ /api/products:
67
+ get:
68
+ summary: List products
69
+ parameters:
70
+ - $ref: '#/components/parameters/page'
71
+ - $ref: '#/components/parameters/limit'
72
+ - name: category
73
+ in: query
74
+ schema: { $ref: '#/components/schemas/Category' }
75
+ - name: sort
76
+ in: query
77
+ schema: { type: string, pattern: '^-?(name|category|price|stock)$' }
78
+ responses:
79
+ '200':
80
+ description: Page of products
81
+ content:
82
+ application/json:
83
+ schema:
84
+ allOf:
85
+ - $ref: '#/components/schemas/Page'
86
+ - type: object
87
+ properties:
88
+ data:
89
+ {
90
+ type: array,
91
+ items: { $ref: '#/components/schemas/Product' },
92
+ }
93
+ '422': { $ref: '#/components/responses/Validation' }
94
+ /api/products/{id}:
95
+ get:
96
+ summary: Get product
97
+ parameters: [{ $ref: '#/components/parameters/id' }]
98
+ responses:
99
+ '200':
100
+ description: Product
101
+ content:
102
+ {
103
+ application/json:
104
+ { schema: { $ref: '#/components/schemas/Product' } },
105
+ }
106
+ '404': { $ref: '#/components/responses/NotFound' }
107
+ /api/orders:
108
+ post:
109
+ summary: Create order (prices resolved server-side)
110
+ requestBody:
111
+ required: true
112
+ content:
113
+ application/json:
114
+ schema:
115
+ type: object
116
+ required: [userId, items]
117
+ properties:
118
+ userId: { type: string }
119
+ items:
120
+ type: array
121
+ minItems: 1
122
+ items:
123
+ type: object
124
+ required: [productId, qty]
125
+ properties:
126
+ productId: { type: string }
127
+ qty: { type: integer, minimum: 1 }
128
+ responses:
129
+ '201':
130
+ description: Created order
131
+ content:
132
+ {
133
+ application/json:
134
+ { schema: { $ref: '#/components/schemas/Order' } },
135
+ }
136
+ '404': { $ref: '#/components/responses/NotFound' }
137
+ '422': { $ref: '#/components/responses/Validation' }
138
+ /api/stats:
139
+ get:
140
+ summary: Aggregates
141
+ responses:
142
+ '200':
143
+ description: Counts, revenue and top category by revenue
144
+ content:
145
+ application/json:
146
+ schema:
147
+ type: object
148
+ required: [users, products, orders, revenue, topCategory]
149
+ properties:
150
+ users: { type: integer }
151
+ products: { type: integer }
152
+ orders: { type: integer }
153
+ revenue: { type: number }
154
+ topCategory: { type: [string, 'null'] }
155
+ components:
156
+ parameters:
157
+ id: { name: id, in: path, required: true, schema: { type: string } }
158
+ page:
159
+ {
160
+ name: page,
161
+ in: query,
162
+ schema: { type: integer, minimum: 1, default: 1 },
163
+ }
164
+ limit:
165
+ {
166
+ name: limit,
167
+ in: query,
168
+ schema: { type: integer, minimum: 1, maximum: 100, default: 20 },
169
+ }
170
+ responses:
171
+ NotFound:
172
+ description: Resource not found
173
+ content:
174
+ { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
175
+ Validation:
176
+ description: Invalid input
177
+ content:
178
+ { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
179
+ schemas:
180
+ Error:
181
+ type: object
182
+ required: [error]
183
+ properties:
184
+ error: { type: string }
185
+ details:
186
+ type: array
187
+ items:
188
+ type: object
189
+ properties: { path: { type: string }, message: { type: string } }
190
+ Page:
191
+ type: object
192
+ required: [data, page, limit, total]
193
+ properties:
194
+ data: { type: array }
195
+ page: { type: integer }
196
+ limit: { type: integer }
197
+ total: { type: integer }
198
+ Category: { type: string, enum: [books, electronics, garden, toys, food] }
199
+ User:
200
+ type: object
201
+ required: [id, name, email, role, createdAt]
202
+ properties:
203
+ id: { type: string, format: uuid }
204
+ name: { type: string }
205
+ email: { type: string, format: email }
206
+ role: { type: string, enum: [admin, editor, viewer] }
207
+ createdAt: { type: string, format: date-time }
208
+ Product:
209
+ type: object
210
+ required: [id, name, category, price, stock]
211
+ properties:
212
+ id: { type: string, format: uuid }
213
+ name: { type: string }
214
+ category: { $ref: '#/components/schemas/Category' }
215
+ price: { type: number }
216
+ stock: { type: integer }
217
+ Order:
218
+ type: object
219
+ required: [id, userId, items, total, createdAt]
220
+ properties:
221
+ id: { type: string, format: uuid }
222
+ userId: { type: string, format: uuid }
223
+ items:
224
+ type: array
225
+ items:
226
+ type: object
227
+ required: [productId, qty, unitPrice]
228
+ properties:
229
+ productId: { type: string, format: uuid }
230
+ qty: { type: integer }
231
+ unitPrice: { type: number }
232
+ total: { type: number }
233
+ createdAt: { type: string, format: date-time }
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "__NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "bun --watch src/server.ts",
8
+ "build": "bun build src/server.ts --outdir dist --target bun",
9
+ "test": "vitest run --coverage",
10
+ "test:watch": "vitest",
11
+ "lint": "eslint .",
12
+ "lint:fix": "eslint . --fix",
13
+ "format": "prettier --write .",
14
+ "format:check": "prettier --check .",
15
+ "typecheck": "tsc --noEmit",
16
+ "verify": "bun run typecheck && bun run lint && bun run format:check && bun run agents:check && bun run test",
17
+ "rules:sync": "bun scripts/rules-sync.ts",
18
+ "rules:check": "bun scripts/rules-check.ts",
19
+ "seed:regen": "bun scripts/seed-regen.ts",
20
+ "agents:build": "bun scripts/agents-build.ts",
21
+ "agents:check": "bun scripts/agents-build.ts --check"
22
+ },
23
+ "dependencies": {
24
+ "yaml": "^2.9.1",
25
+ "zod": "^4.6.5"
26
+ },
27
+ "devDependencies": {
28
+ "@eslint/js": "^10.0.1",
29
+ "@faker-js/faker": "^10.6.0",
30
+ "@trivago/prettier-plugin-sort-imports": "^6.0.2",
31
+ "@types/bun": "^1.4.2",
32
+ "@types/supertest": "^7.2.1",
33
+ "@vitest/coverage-v8": "^5.0.1",
34
+ "eslint": "^9",
35
+ "prettier": "^3.9.8",
36
+ "supertest": "^7.2.2",
37
+ "typescript": "^5",
38
+ "typescript-eslint": "^8.70.0",
39
+ "vitest": "^5.0.1"
40
+ }
41
+ }
@@ -0,0 +1,26 @@
1
+ import { join } from 'node:path'
2
+
3
+ /** Fragment order, written by create-simone-app for the chosen variants. */
4
+ const FRAGMENTS: string[] = await Bun.file('docs/agents/fragments.json').json()
5
+ const OUT = 'AGENTS.md'
6
+ const HEADER =
7
+ '<!-- Generated by `bun run agents:build` from docs/agents/*.md. Edit the fragments, not this file. -->\n\n'
8
+
9
+ const parts = await Promise.all(
10
+ FRAGMENTS.map((name) => Bun.file(join('docs/agents', `${name}.md`)).text()),
11
+ )
12
+ const built = HEADER + parts.map((p) => p.trim()).join('\n\n') + '\n'
13
+
14
+ if (process.argv.includes('--check')) {
15
+ const current = (await Bun.file(OUT).exists())
16
+ ? await Bun.file(OUT).text()
17
+ : ''
18
+ if (current !== built) {
19
+ console.error(`${OUT} is stale: run \`bun run agents:build\``)
20
+ process.exit(1)
21
+ }
22
+ console.log(`${OUT} up to date (${built.split('\n').length} lines)`)
23
+ } else {
24
+ await Bun.write(OUT, built)
25
+ console.log(`wrote ${OUT} (${built.split('\n').length} lines)`)
26
+ }
@@ -0,0 +1,48 @@
1
+ import { join } from 'node:path'
2
+
3
+ import {
4
+ MANIFEST,
5
+ type Manifest,
6
+ RULES_DIR,
7
+ rulesSource,
8
+ sha256,
9
+ } from './rules.config'
10
+
11
+ const manifestFile = Bun.file(MANIFEST)
12
+ if (!(await manifestFile.exists())) {
13
+ console.error(`${MANIFEST} missing: run \`bun run rules:sync\``)
14
+ process.exit(1)
15
+ }
16
+ const manifest: Manifest = await manifestFile.json()
17
+ const source = rulesSource()
18
+ const upstreamAvailable = await Bun.file(join(source, 'README.md')).exists()
19
+ let failed = false
20
+
21
+ for (const entry of manifest.files) {
22
+ const local = join(RULES_DIR, entry.file)
23
+ if (!(await Bun.file(local).exists())) {
24
+ console.error(`missing ${local}`)
25
+ failed = true
26
+ continue
27
+ }
28
+ if ((await sha256(local)) !== entry.sha256) {
29
+ console.error(`${local} differs from MANIFEST (edited by hand?)`)
30
+ failed = true
31
+ }
32
+ if (upstreamAvailable) {
33
+ const upstream = join(source, entry.source)
34
+ if ((await sha256(upstream)) !== entry.sha256) {
35
+ console.error(
36
+ `${entry.source} changed upstream: run \`bun run rules:sync\``,
37
+ )
38
+ failed = true
39
+ }
40
+ }
41
+ }
42
+
43
+ if (!upstreamAvailable)
44
+ console.warn(`ai-rules not found at ${source}: checked local integrity only`)
45
+ if (failed) process.exit(1)
46
+ console.log(
47
+ `ai-rules ok (${manifest.files.length} files, synced ${manifest.syncedAt})`,
48
+ )
@@ -0,0 +1,35 @@
1
+ import { mkdir } from 'node:fs/promises'
2
+ import { basename, join } from 'node:path'
3
+
4
+ import {
5
+ MANIFEST,
6
+ type Manifest,
7
+ RULES,
8
+ RULES_DIR,
9
+ rulesSource,
10
+ sha256,
11
+ } from './rules.config'
12
+
13
+ const source = rulesSource()
14
+ if (!(await Bun.file(join(source, 'README.md')).exists())) {
15
+ console.error(`ai-rules not found at ${source} (set AI_RULES_PATH)`)
16
+ process.exit(1)
17
+ }
18
+
19
+ await mkdir(RULES_DIR, { recursive: true })
20
+ const manifest: Manifest = { syncedAt: new Date().toISOString(), files: [] }
21
+
22
+ for (const rule of RULES) {
23
+ const from = join(source, rule)
24
+ const to = join(RULES_DIR, basename(rule))
25
+ await Bun.write(to, Bun.file(from))
26
+ manifest.files.push({
27
+ file: basename(rule),
28
+ source: rule,
29
+ sha256: await sha256(to),
30
+ })
31
+ console.log(`synced ${rule}`)
32
+ }
33
+
34
+ await Bun.write(MANIFEST, JSON.stringify(manifest, null, 2) + '\n')
35
+ console.log(`wrote ${MANIFEST}`)
@@ -0,0 +1,28 @@
1
+ import { homedir } from 'node:os'
2
+ import { join } from 'node:path'
3
+
4
+ /** ai-rules files (relative to the ai-rules repo root) mirrored into docs/ai-rules. */
5
+ export const RULES = ['frontend/testing-selectors.md'] as const
6
+
7
+ export const RULES_DIR = 'docs/ai-rules'
8
+ export const MANIFEST = join(RULES_DIR, 'MANIFEST.json')
9
+
10
+ export const rulesSource = (): string =>
11
+ process.env.AI_RULES_PATH ?? join(homedir(), 'Progetti', 'ai-rules')
12
+
13
+ export interface ManifestEntry {
14
+ file: string
15
+ source: string
16
+ sha256: string
17
+ }
18
+
19
+ export interface Manifest {
20
+ syncedAt: string
21
+ files: ManifestEntry[]
22
+ }
23
+
24
+ export const sha256 = async (path: string): Promise<string> => {
25
+ const hasher = new Bun.CryptoHasher('sha256')
26
+ hasher.update(await Bun.file(path).arrayBuffer())
27
+ return hasher.digest('hex')
28
+ }
@@ -0,0 +1,64 @@
1
+ import { faker } from '@faker-js/faker'
2
+
3
+ import {
4
+ type Order,
5
+ PRODUCT_CATEGORIES,
6
+ type Product,
7
+ USER_ROLES,
8
+ type User,
9
+ } from '../src/data/schemas'
10
+
11
+ // Fixed seed + fixed ref date → identical fixtures on every run (fixtures are committed).
12
+ faker.seed(42)
13
+ faker.setDefaultRefDate('2026-01-01T00:00:00.000Z')
14
+
15
+ const money = (n: number): number => Math.round(n * 100) / 100
16
+ const pastDate = (): string =>
17
+ faker.date
18
+ .between({
19
+ from: '2024-01-01T00:00:00.000Z',
20
+ to: '2025-12-31T23:59:59.000Z',
21
+ })
22
+ .toISOString()
23
+
24
+ const users: User[] = Array.from({ length: 50 }, () => {
25
+ const firstName = faker.person.firstName()
26
+ const lastName = faker.person.lastName()
27
+ return {
28
+ id: faker.string.uuid(),
29
+ name: `${firstName} ${lastName}`,
30
+ email: faker.internet.email({ firstName, lastName }).toLowerCase(),
31
+ role: faker.helpers.arrayElement(USER_ROLES),
32
+ createdAt: pastDate(),
33
+ }
34
+ })
35
+
36
+ const products: Product[] = Array.from({ length: 20 }, () => ({
37
+ id: faker.string.uuid(),
38
+ name: faker.commerce.productName(),
39
+ category: faker.helpers.arrayElement(PRODUCT_CATEGORIES),
40
+ price: money(Number(faker.commerce.price({ min: 5, max: 500 }))),
41
+ stock: faker.number.int({ min: 0, max: 200 }),
42
+ }))
43
+
44
+ const orders: Order[] = Array.from({ length: 10 }, () => {
45
+ const items = faker.helpers
46
+ .arrayElements(products, { min: 1, max: 4 })
47
+ .map((p) => ({
48
+ productId: p.id,
49
+ qty: faker.number.int({ min: 1, max: 5 }),
50
+ unitPrice: p.price,
51
+ }))
52
+ return {
53
+ id: faker.string.uuid(),
54
+ userId: faker.helpers.arrayElement(users).id,
55
+ items,
56
+ total: money(items.reduce((sum, i) => sum + i.qty * i.unitPrice, 0)),
57
+ createdAt: pastDate(),
58
+ }
59
+ })
60
+
61
+ for (const [name, rows] of Object.entries({ users, products, orders })) {
62
+ await Bun.write(`src/data/${name}.json`, JSON.stringify(rows, null, 2) + '\n')
63
+ console.log(`wrote src/data/${name}.json (${rows.length})`)
64
+ }