@sulhadin/orchestrator 2.0.0 → 3.0.0-beta

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 (46) hide show
  1. package/README.md +49 -74
  2. package/bin/index.js +136 -82
  3. package/package.json +1 -1
  4. package/template/.claude/agents/conductor.md +146 -0
  5. package/template/.claude/agents/reviewer.md +88 -0
  6. package/template/.claude/commands/orchestra/blueprint.md +23 -0
  7. package/template/.claude/commands/orchestra/help.md +49 -0
  8. package/template/.claude/commands/orchestra/hotfix.md +13 -0
  9. package/template/.claude/commands/orchestra/pm.md +7 -0
  10. package/template/.claude/commands/orchestra/start.md +13 -0
  11. package/template/.claude/commands/orchestra/status.md +11 -0
  12. package/template/.claude/conductor.md +146 -0
  13. package/template/.claude/rules/acceptance-check.orchestra.md +13 -0
  14. package/template/.claude/rules/code-standards.orchestra.md +15 -0
  15. package/template/.claude/rules/commit-format.orchestra.md +14 -0
  16. package/template/.claude/rules/phase-limits.orchestra.md +21 -0
  17. package/template/.claude/rules/stuck-detection.orchestra.md +25 -0
  18. package/template/.claude/rules/testing-standards.orchestra.md +10 -0
  19. package/template/.claude/rules/verification-gate.orchestra.md +24 -0
  20. package/template/.claude/skills/fullstack-infrastructure.orchestra.md +810 -0
  21. package/template/.orchestra/README.md +10 -14
  22. package/template/.orchestra/config.yml +36 -0
  23. package/template/.orchestra/knowledge.md +4 -23
  24. package/template/.orchestra/roles/adaptive.md +14 -87
  25. package/template/.orchestra/roles/architect.md +17 -407
  26. package/template/.orchestra/roles/backend-engineer.md +13 -357
  27. package/template/.orchestra/roles/frontend-engineer.md +14 -419
  28. package/template/.orchestra/roles/orchestrator.md +48 -0
  29. package/template/.orchestra/roles/product-manager.md +73 -590
  30. package/template/CLAUDE.md +39 -139
  31. package/template/.orchestra/agents/worker.md +0 -557
  32. package/template/.orchestra/roles/code-reviewer.md +0 -265
  33. package/template/.orchestra/roles/owner.md +0 -290
  34. /package/template/{.orchestra/skills/accessibility.md → .claude/skills/accessibility.orchestra.md} +0 -0
  35. /package/template/{.orchestra/skills/auth-setup.md → .claude/skills/auth-setup.orchestra.md} +0 -0
  36. /package/template/{.orchestra/skills/best-practices.md → .claude/skills/best-practices.orchestra.md} +0 -0
  37. /package/template/{.orchestra/skills/code-optimizer.md → .claude/skills/code-optimizer.orchestra.md} +0 -0
  38. /package/template/{.orchestra/skills/core-web-vitals.md → .claude/skills/core-web-vitals.orchestra.md} +0 -0
  39. /package/template/{.orchestra/skills/crud-api.md → .claude/skills/crud-api.orchestra.md} +0 -0
  40. /package/template/{.orchestra/skills/debug.md → .claude/skills/debug.orchestra.md} +0 -0
  41. /package/template/{.orchestra/skills/deployment.md → .claude/skills/deployment.orchestra.md} +0 -0
  42. /package/template/{.orchestra/skills/frontend-design.md → .claude/skills/frontend-design.orchestra.md} +0 -0
  43. /package/template/{.orchestra/skills/react-best-practices.md → .claude/skills/react-best-practices.orchestra.md} +0 -0
  44. /package/template/{.orchestra/skills/review.md → .claude/skills/review.orchestra.md} +0 -0
  45. /package/template/{.orchestra/skills/testing.md → .claude/skills/testing.orchestra.md} +0 -0
  46. /package/template/{.orchestra/skills/web-quality-audit.md → .claude/skills/web-quality-audit.orchestra.md} +0 -0
@@ -0,0 +1,810 @@
1
+ ---
2
+ name: fullstack-infra
3
+ description: |
4
+ Fullstack TypeScript project infrastructure and coding standards skill. Use this skill whenever the user starts a new project, creates components, writes backend services, sets up a monorepo, scaffolds features, or writes any code in an existing TypeScript project. Also trigger when the user asks about project structure, coding conventions, component organization, API design, state management, or best practices. This skill defines mandatory standards — every piece of code must comply.
5
+ ---
6
+
7
+ # Fullstack TypeScript Infrastructure & Standards
8
+
9
+ This skill defines the architecture, conventions, and coding standards for all TypeScript projects. Every piece of code you write or modify must follow these rules. These are not suggestions — they are the engineering standards of the team.
10
+
11
+ Before writing any code, ask these project-scoping questions if not already answered:
12
+ - Is this a monorepo or single repo?
13
+ - Is the backend Express or Hono?
14
+ - Is i18n (multi-language support) needed?
15
+ - REST or GraphQL? (default: REST)
16
+
17
+ ---
18
+
19
+ ## Tech Stack
20
+
21
+ | Layer | Technology |
22
+ |------------------|-----------------------------------|
23
+ | Language | TypeScript (strict mode, always) |
24
+ | Frontend Build | Vite |
25
+ | Mobile | Expo (React Native) |
26
+ | State Management | Zustand (web & mobile) |
27
+ | Styling | Tailwind CSS (primary) + SCSS (edge cases only) |
28
+ | Web UI Components| shadcn/ui |
29
+ | HTTP Client | Axios (via wrapper) |
30
+ | Data Fetching | SWR (caches backend responses, uses Axios wrapper internally) |
31
+ | Backend | Express or Hono (ask which one) |
32
+ | ORM | Prisma |
33
+ | Auth | JWT |
34
+ | Testing | Vitest (unit), Playwright (E2E) |
35
+ | Linter/Formatter | Biome |
36
+ | Monorepo | Yarn Workspaces |
37
+ | Git | Trunk-based development, Conventional Commits |
38
+ | Env Validation | Zod schemas for .env variables |
39
+
40
+ ---
41
+
42
+ ## Core Principles
43
+
44
+ These principles are non-negotiable. Every function, component, and module must satisfy them.
45
+
46
+ ### SOLID
47
+
48
+ - **Single Responsibility**: One reason to change per module/class/function. A component renders UI — it does not fetch data, transform it, and handle errors all in one place.
49
+ - **Open/Closed**: Extend behavior through composition and props, not by modifying existing code. Use strategy patterns, render props, or hooks to add behavior.
50
+ - **Liskov Substitution**: Subtypes must be substitutable. If a component accepts `ButtonProps`, any extension of `ButtonProps` must work without breaking.
51
+ - **Interface Segregation**: Keep interfaces small and focused. Don't force consumers to depend on methods or props they don't use. Split large interfaces into smaller, role-specific ones.
52
+ - **Dependency Inversion**: Depend on abstractions. Services should accept interfaces, not concrete implementations. This enables testing and swapping implementations.
53
+
54
+ ### KISS
55
+
56
+ Write the simplest code that solves the problem. If a solution needs a comment to explain what it does, it's probably too complex. Prefer readable, obvious code over clever code.
57
+
58
+ ### YAGNI
59
+
60
+ Don't build for hypothetical future requirements. No feature flags for features that don't exist. No abstractions for a single use case. Three similar lines of code are better than a premature abstraction. Build what's needed now.
61
+
62
+ ### DRY (with judgment)
63
+
64
+ Avoid duplication, but don't abstract prematurely. If code is duplicated in 2+ places and serves the same purpose, extract it. If two pieces of code look similar but serve different domains, they can stay separate — they'll likely diverge over time.
65
+
66
+ ---
67
+
68
+ ## Workarounds Are Forbidden
69
+
70
+ Never apply a workaround, hack, or quick fix without explicit user approval. If you encounter a situation where the "right" solution seems too complex or time-consuming:
71
+
72
+ 1. Stop and explain the problem clearly
73
+ 2. Present the proper solution and the workaround
74
+ 3. Wait for explicit approval before applying a workaround
75
+
76
+ If the user approves, add a `// WORKAROUND:` comment explaining why it exists and link to a tracking issue if possible.
77
+
78
+ ---
79
+
80
+ ## Monorepo Structure
81
+
82
+ ```
83
+ monorepo/
84
+ ├── apps/
85
+ │ ├── <project>/ # Web app (Vite, Playwright E2E)
86
+ │ │ └── src/
87
+ │ │ ├── app/ # App config, routing, providers
88
+ │ │ ├── assets/ # Static files (images, fonts, icons)
89
+ │ │ ├── components/ # Shared components (used by 2+ modules)
90
+ │ │ ├── libs/ # App-specific shared utilities (used by 2+ modules within this app)
91
+ │ │ ├── modules/ # Feature modules (pages, widgets)
92
+ │ │ │ └── <domain>/
93
+ │ │ │ ├── components/ # Domain-specific components
94
+ │ │ │ ├── utils/ # Domain-specific utilities
95
+ │ │ │ └── <Page>.tsx
96
+ │ │ ├── store/ # Zustand stores
97
+ │ │ └── types/ # TypeScript type definitions
98
+ │ │
99
+ │ ├── <mobile>/ # Mobile app (Expo)
100
+ │ │ └── src/
101
+ │ │ ├── app/ # Expo Router / navigation
102
+ │ │ ├── components/ # Shared mobile components (2+ screens)
103
+ │ │ ├── design/ # Mobile UI components (isolated, minimal deps)
104
+ │ │ ├── libs/ # App-specific shared utilities
105
+ │ │ ├── modules/ # Feature modules
106
+ │ │ │ └── <domain>/
107
+ │ │ │ ├── components/
108
+ │ │ │ └── utils/
109
+ │ │ ├── store/ # Zustand stores
110
+ │ │ └── types/
111
+ │ │
112
+ │ └── api/ # Backend (Express or Hono)
113
+ │ └── src/
114
+ │ ├── routes/ # Route definitions
115
+ │ ├── controllers/ # Request/response handling (Express)
116
+ │ ├── handlers/ # Request handling (Hono — use instead of controllers/)
117
+ │ ├── services/ # Business logic (pure, testable)
118
+ │ ├── middlewares/ # Auth, error handler, validation, logging
119
+ │ ├── models/ # Prisma models / type definitions
120
+ │ ├── utils/ # Helper functions
121
+ │ ├── config/ # Env validation (Zod), DB config, constants
122
+ │ └── index.ts # App entry point
123
+
124
+ ├── packages/
125
+ │ ├── design/ # Design system (Atomic Design)
126
+ │ │ └── src/
127
+ │ │ ├── assets/ # Design tokens, icons, fonts
128
+ │ │ ├── components/
129
+ │ │ │ ├── atoms/ # Button, Input, Icon, Text, Badge
130
+ │ │ │ ├── elements/ # FormField, Card, Tooltip, Avatar
131
+ │ │ │ ├── molecules/ # SearchBar, NavItem, Dropdown, Modal
132
+ │ │ │ └── organisms/ # Header, Sidebar, DataTable, Form
133
+ │ │ ├── helpers/ # Design-related utilities
134
+ │ │ ├── hoc/ # Higher-order components
135
+ │ │ └── hooks/ # UI-related hooks
136
+ │ │
137
+ │ └── utils/ # Shared utilities across all apps
138
+ │ └── src/
139
+ │ ├── apis/ # API endpoint definitions
140
+ │ ├── client/ # HTTP client (Axios wrapper)
141
+ │ ├── constants/ # Shared constants
142
+ │ ├── hooks/ # Shared hooks (including SWR hooks)
143
+ │ ├── network/ # Network utilities, interceptors
144
+ │ ├── performance/ # Performance monitoring utilities
145
+ │ ├── ws/ # WebSocket client class
146
+ │ └── utils/ # General utility functions
147
+
148
+ ├── .github/ # CI/CD workflows
149
+ ├── biome.json # Linter/formatter config
150
+ ├── tsconfig.base.json # Shared TypeScript config
151
+ └── package.json # Root workspace config (Yarn)
152
+ ```
153
+
154
+ For **single-repo** projects, use the same structure but with only one app (no `packages/` unless shared code emerges naturally).
155
+
156
+ ---
157
+
158
+ ## Component Architecture
159
+
160
+ ### Web Components (shadcn/ui approach)
161
+
162
+ Components are installed via shadcn/ui CLI and customized in-place. Each component lives in its own folder:
163
+
164
+ ```
165
+ components/
166
+ └── Button/
167
+ ├── Button.tsx
168
+ ├── Button.scss # Only if Tailwind alone can't handle it
169
+ └── index.ts # Re-export
170
+ ```
171
+
172
+ Rules:
173
+ - shadcn/ui is the foundation for all web UI components
174
+ - Tailwind CSS is the primary styling approach — use SCSS only for complex animations, pseudo-element tricks, or cases Tailwind genuinely can't cover
175
+ - If a third-party UI dependency is added, wrap it in a component inside `packages/design` so it can be swapped later without touching consumers
176
+ - Every component must be reusable by design — accept props for customization, avoid hardcoded values
177
+
178
+ ### Mobile Components (minimal dependency approach)
179
+
180
+ Mobile uses the same folder structure but without shadcn/ui. Build components from scratch with minimal external dependencies:
181
+
182
+ ```
183
+ design/
184
+ └── Button/
185
+ ├── Button.tsx
186
+ └── index.ts
187
+ ```
188
+
189
+ Rules:
190
+ - Minimize external dependencies — fewer deps means faster builds and fewer breaking changes
191
+ - Build custom components that match the design system
192
+ - Share logic (not UI) with web through `packages/utils`
193
+
194
+ ### Component Placement Decision Tree
195
+
196
+ When creating a new component, follow this decision tree:
197
+
198
+ 1. **Is it a pure UI primitive with no business logic?** (Button, Input, Modal)
199
+ → `packages/design/src/components/<atomic-level>/`
200
+
201
+ 2. **Is it used by 2+ modules within the same app?**
202
+ → `apps/<app>/src/components/`
203
+
204
+ 3. **Is it used by only one module?**
205
+ → `apps/<app>/src/modules/<domain>/components/`
206
+
207
+ The same logic applies to utility functions:
208
+
209
+ 1. **Is it used across multiple apps?**
210
+ → `packages/utils/src/` (must have JSDoc with `@example`)
211
+
212
+ 2. **Is it used by 2+ modules within one app?**
213
+ → `apps/<app>/src/libs/` (must have JSDoc with `@example`)
214
+
215
+ 3. **Is it used by only one module?**
216
+ → `apps/<app>/src/modules/<domain>/utils/`
217
+
218
+ ### Classes vs Utility Functions
219
+
220
+ Use classes when there's state, configuration, or a third-party integration to encapsulate. Use plain functions for stateless, single-purpose utilities.
221
+
222
+ **When to use a class:**
223
+ - **Third-party API integrations**: Every external API (Stripe, SendGrid, AWS S3, etc.) gets its own class that wraps the SDK. This isolates the dependency, makes it swappable, and provides a consistent internal interface:
224
+ ```typescript
225
+ // services/PaymentService.ts
226
+ export class PaymentService {
227
+ private client: Stripe;
228
+
229
+ constructor(apiKey: string) {
230
+ this.client = new Stripe(apiKey);
231
+ }
232
+
233
+ async createCharge(amount: number, currency: string): Promise<Charge> {
234
+ return this.client.charges.create({ amount, currency });
235
+ }
236
+
237
+ async refund(chargeId: string): Promise<Refund> {
238
+ return this.client.refunds.create({ charge: chargeId });
239
+ }
240
+ }
241
+ ```
242
+ - WebSocket connections, database clients, or anything with lifecycle (init/destroy)
243
+ - When multiple related methods share configuration or state
244
+
245
+ **When to use a plain function:**
246
+ - Stateless, single-purpose transformations (`formatDate`, `slugify`, `calculateTax`)
247
+ - Pure utility logic with no side effects
248
+
249
+ **Dependency policy for utilities:**
250
+ - Prefer dependency-free implementations for small utilities — write them in `packages/utils/` with JSDoc + `@example`
251
+ - Use an npm package only when: the implementation is long and complex, it's out of scope for the project (e.g., date parsing, cryptography), or maintaining it in-house would be a burden
252
+ - If in doubt, start dependency-free. It's easier to add a dependency later than to remove one
253
+
254
+ ### Before Creating Any Function or Component
255
+
256
+ Always check `packages/utils/` and `apps/<app>/src/libs/` first. If a similar function exists:
257
+ - Extend it if it's missing functionality (without breaking existing consumers)
258
+ - Use it as-is if it covers the need
259
+ - Never create a duplicate
260
+
261
+ ---
262
+
263
+ ## Atomic Design Levels
264
+
265
+ | Level | Description | Examples |
266
+ |-----------|--------------------------------------|-----------------------------------|
267
+ | Atoms | Smallest indivisible UI units | Button, Input, Icon, Text, Badge |
268
+ | Elements | Single-purpose composed atoms | FormField, Card, Tooltip, Avatar |
269
+ | Molecules | Functional groups with local state | SearchBar, NavItem, Dropdown |
270
+ | Organisms | Complex sections with business logic | Header, Sidebar, DataTable, Form |
271
+
272
+ Rules:
273
+ - Atoms have no dependencies on other components
274
+ - Each level can only import from the same level or below
275
+ - Organisms can contain business logic; atoms, elements, and molecules must be pure UI
276
+
277
+ ---
278
+
279
+ ## State Management (Zustand)
280
+
281
+ ### Store Structure
282
+
283
+ ```typescript
284
+ // store/useAuthStore.ts
285
+ interface AuthState {
286
+ user: User | null;
287
+ token: string | null;
288
+ login: (credentials: LoginCredentials) => Promise<void>;
289
+ logout: () => void;
290
+ }
291
+
292
+ export const useAuthStore = create<AuthState>()((set) => ({
293
+ user: null,
294
+ token: null,
295
+ login: async (credentials) => {
296
+ const { data } = await authService.login(credentials);
297
+ set({ user: data.user, token: data.token });
298
+ },
299
+ logout: () => set({ user: null, token: null }),
300
+ }));
301
+ ```
302
+
303
+ Rules:
304
+ - One store per domain (auth, cart, ui, etc.)
305
+ - Keep stores flat — no deep nesting
306
+ - Business logic (API calls) can live in store actions, but complex logic should be extracted to services
307
+ - Use selectors for derived state to avoid unnecessary re-renders:
308
+ ```typescript
309
+ const userName = useAuthStore((state) => state.user?.name);
310
+ ```
311
+ - Never put server-cache data in Zustand — that's SWR's job. Zustand is for client state (UI state, auth, preferences)
312
+
313
+ ---
314
+
315
+ ## Network Layer
316
+
317
+ ### Architecture
318
+
319
+ ```
320
+ packages/utils/src/
321
+ ├── client/
322
+ │ ├── HttpClient.ts # Base Axios wrapper class (shared config, interceptors)
323
+ │ ├── UserApi.ts # Domain API class (extends or uses HttpClient)
324
+ │ ├── OrderApi.ts # Domain API class
325
+ │ └── index.ts # Export all API class instances
326
+ ├── network/
327
+ │ └── interceptors.ts # Request/response interceptors (auth token, error transform)
328
+ └── hooks/
329
+ └── use<Resource>.ts # SWR hooks per resource (consume API classes)
330
+ ```
331
+
332
+ ### Base HTTP Client
333
+
334
+ A single Axios-based class that all domain API classes inherit from:
335
+
336
+ ```typescript
337
+ // client/HttpClient.ts
338
+ import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
339
+
340
+ export class HttpClient {
341
+ protected client: AxiosInstance;
342
+
343
+ constructor(baseURL: string, config?: AxiosRequestConfig) {
344
+ this.client = axios.create({
345
+ baseURL,
346
+ headers: { 'Content-Type': 'application/json' },
347
+ ...config,
348
+ });
349
+
350
+ this.client.interceptors.request.use((cfg) => {
351
+ const token = useAuthStore.getState().token;
352
+ if (token) cfg.headers.Authorization = `Bearer ${token}`;
353
+ return cfg;
354
+ });
355
+ }
356
+
357
+ protected get<T>(url: string, config?: AxiosRequestConfig) {
358
+ return this.client.get<T>(url, config).then((res) => res.data);
359
+ }
360
+
361
+ protected post<T>(url: string, data?: unknown, config?: AxiosRequestConfig) {
362
+ return this.client.post<T>(url, data, config).then((res) => res.data);
363
+ }
364
+
365
+ protected put<T>(url: string, data?: unknown, config?: AxiosRequestConfig) {
366
+ return this.client.put<T>(url, data, config).then((res) => res.data);
367
+ }
368
+
369
+ protected patch<T>(url: string, data?: unknown, config?: AxiosRequestConfig) {
370
+ return this.client.patch<T>(url, data, config).then((res) => res.data);
371
+ }
372
+
373
+ protected delete<T>(url: string, config?: AxiosRequestConfig) {
374
+ return this.client.delete<T>(url, config).then((res) => res.data);
375
+ }
376
+ }
377
+ ```
378
+
379
+ ### Domain API Classes
380
+
381
+ Each domain gets its own class that extends `HttpClient`. If the file grows too large, split by domain — but all share the same base:
382
+
383
+ ```typescript
384
+ // client/UserApi.ts
385
+ import { HttpClient } from './HttpClient';
386
+
387
+ export class UserApi extends HttpClient {
388
+ constructor() {
389
+ super(import.meta.env.VITE_API_URL);
390
+ }
391
+
392
+ getAll(params?: UserFilters) {
393
+ return this.get<User[]>('/users', { params });
394
+ }
395
+
396
+ getById(id: string) {
397
+ return this.get<User>(`/users/${id}`);
398
+ }
399
+
400
+ create(data: CreateUserDto) {
401
+ return this.post<User>('/users', data);
402
+ }
403
+
404
+ update(id: string, data: UpdateUserDto) {
405
+ return this.patch<User>(`/users/${id}`, data);
406
+ }
407
+
408
+ remove(id: string) {
409
+ return this.delete<void>(`/users/${id}`);
410
+ }
411
+ }
412
+
413
+ // client/OrderApi.ts
414
+ import { HttpClient } from './HttpClient';
415
+
416
+ export class OrderApi extends HttpClient {
417
+ constructor() {
418
+ super(import.meta.env.VITE_API_URL);
419
+ }
420
+
421
+ getAll(params?: OrderFilters) {
422
+ return this.get<Order[]>('/orders', { params });
423
+ }
424
+
425
+ getById(id: string) {
426
+ return this.get<Order>(`/orders/${id}`);
427
+ }
428
+
429
+ cancel(id: string) {
430
+ return this.post<Order>(`/orders/${id}/cancel`);
431
+ }
432
+ }
433
+ ```
434
+
435
+ ```typescript
436
+ // client/index.ts — singleton instances
437
+ import { UserApi } from './UserApi';
438
+ import { OrderApi } from './OrderApi';
439
+
440
+ export const userApi = new UserApi();
441
+ export const orderApi = new OrderApi();
442
+ ```
443
+
444
+ ### SWR Hooks (consume API classes)
445
+
446
+ SWR hooks call the API class methods — no raw URLs or fetchers scattered around:
447
+
448
+ ```typescript
449
+ // hooks/useUsers.ts
450
+ import useSWR from 'swr';
451
+ import useSWRMutation from 'swr/mutation';
452
+ import { userApi } from '../client';
453
+
454
+ export function useUsers(filters?: UserFilters) {
455
+ return useSWR(['users', filters], () => userApi.getAll(filters));
456
+ }
457
+
458
+ export function useUser(id: string | null) {
459
+ return useSWR(id ? ['users', id] : null, () => userApi.getById(id!));
460
+ }
461
+
462
+ export function useCreateUser() {
463
+ return useSWRMutation('users', (_, { arg }: { arg: CreateUserDto }) =>
464
+ userApi.create(arg),
465
+ );
466
+ }
467
+ ```
468
+
469
+ Rules:
470
+ - Every backend communication goes through a domain API class — no raw `axios.get()` calls anywhere in components or hooks
471
+ - `HttpClient` is the single source of Axios configuration (base URL, interceptors, auth headers)
472
+ - Split API classes by domain when a single class exceeds ~150 lines
473
+ - SWR hooks consume API class methods — they never construct URLs or call Axios directly
474
+ - SWR handles all server data caching — never duplicate server data in Zustand
475
+ - Mutations use `useSWRMutation` or API class methods + `mutate()` for cache invalidation
476
+ - SWR keys use descriptive arrays (`['users', id]`) for precise cache invalidation
477
+
478
+ ---
479
+
480
+ ## Backend Architecture
481
+
482
+ ### Express
483
+
484
+ ```typescript
485
+ // routes/userRoutes.ts
486
+ import { Router } from 'express';
487
+ import { UserController } from '../controllers/userController';
488
+ import { authenticate } from '../middlewares/auth';
489
+ import { validate } from '../middlewares/validate';
490
+ import { createUserSchema } from '../models/schemas';
491
+
492
+ const router = Router();
493
+
494
+ router.get('/', authenticate, UserController.list);
495
+ router.post('/', authenticate, validate(createUserSchema), UserController.create);
496
+
497
+ export default router;
498
+ ```
499
+
500
+ ```typescript
501
+ // controllers/userController.ts
502
+ export class UserController {
503
+ static async list(req: Request, res: Response, next: NextFunction) {
504
+ try {
505
+ const users = await UserService.findAll(req.query);
506
+ res.json(users);
507
+ } catch (error) {
508
+ next(error);
509
+ }
510
+ }
511
+ }
512
+ ```
513
+
514
+ ### Hono
515
+
516
+ ```typescript
517
+ // routes/userRoutes.ts
518
+ import { Hono } from 'hono';
519
+ import { UserHandler } from '../handlers/userHandler';
520
+ import { authenticate } from '../middlewares/auth';
521
+ import { zValidator } from '@hono/zod-validator';
522
+ import { createUserSchema } from '../models/schemas';
523
+
524
+ const app = new Hono();
525
+
526
+ app.get('/', authenticate, UserHandler.list);
527
+ app.post('/', authenticate, zValidator('json', createUserSchema), UserHandler.create);
528
+
529
+ export default app;
530
+ ```
531
+
532
+ ### Service Layer
533
+
534
+ Services contain pure business logic — no HTTP concepts (req, res). This makes them testable and reusable:
535
+
536
+ ```typescript
537
+ // services/userService.ts
538
+ export class UserService {
539
+ static async findAll(filters: UserFilters) {
540
+ return prisma.user.findMany({ where: filters });
541
+ }
542
+
543
+ static async create(data: CreateUserDto) {
544
+ const existing = await prisma.user.findUnique({ where: { email: data.email } });
545
+ if (existing) throw new ConflictError('Email already in use');
546
+ return prisma.user.create({ data });
547
+ }
548
+ }
549
+ ```
550
+
551
+ ---
552
+
553
+ ## API Response Format (RFC 7807)
554
+
555
+ Follow HTTP standards. No wrappers on success — the status code tells the story.
556
+
557
+ ### Success Responses
558
+
559
+ ```typescript
560
+ // 200 OK — single resource
561
+ res.json({ id: 1, name: 'John', email: 'john@example.com' });
562
+
563
+ // 200 OK — paginated list
564
+ // Headers: X-Total-Count, X-Page, X-Per-Page, Link
565
+ res.set({
566
+ 'X-Total-Count': String(total),
567
+ 'X-Page': String(page),
568
+ 'X-Per-Page': String(perPage),
569
+ }).json(users);
570
+
571
+ // 201 Created
572
+ res.status(201).json(createdUser);
573
+
574
+ // 204 No Content (delete)
575
+ res.status(204).end();
576
+ ```
577
+
578
+ ### Error Responses (RFC 7807 Problem Details)
579
+
580
+ ```typescript
581
+ // 400 Bad Request — validation error
582
+ {
583
+ "type": "https://api.example.com/errors/validation",
584
+ "title": "Validation Failed",
585
+ "status": 400,
586
+ "detail": "Request body contains invalid fields",
587
+ "errors": [
588
+ { "field": "email", "message": "Invalid email format" }
589
+ ]
590
+ }
591
+
592
+ // 401 Unauthorized
593
+ {
594
+ "type": "https://api.example.com/errors/unauthorized",
595
+ "title": "Unauthorized",
596
+ "status": 401,
597
+ "detail": "Invalid or expired token"
598
+ }
599
+
600
+ // 404 Not Found
601
+ {
602
+ "type": "https://api.example.com/errors/not-found",
603
+ "title": "Not Found",
604
+ "status": 404,
605
+ "detail": "User with id 42 not found"
606
+ }
607
+
608
+ // 500 Internal Server Error (production — no details leaked)
609
+ {
610
+ "type": "https://api.example.com/errors/internal",
611
+ "title": "Internal Server Error",
612
+ "status": 500,
613
+ "detail": "An unexpected error occurred"
614
+ }
615
+ ```
616
+
617
+ ---
618
+
619
+ ## Error Handling
620
+
621
+ ### Backend — Error Class Hierarchy
622
+
623
+ ```typescript
624
+ export class AppError extends Error {
625
+ constructor(
626
+ public status: number,
627
+ message: string,
628
+ public type: string,
629
+ public isOperational = true,
630
+ ) {
631
+ super(message);
632
+ }
633
+ }
634
+
635
+ export class NotFoundError extends AppError {
636
+ constructor(detail = 'Resource not found') {
637
+ super(404, detail, 'not-found');
638
+ }
639
+ }
640
+
641
+ export class ValidationError extends AppError {
642
+ constructor(detail: string, public errors: FieldError[] = []) {
643
+ super(400, detail, 'validation');
644
+ }
645
+ }
646
+
647
+ export class UnauthorizedError extends AppError {
648
+ constructor(detail = 'Invalid or expired token') {
649
+ super(401, detail, 'unauthorized');
650
+ }
651
+ }
652
+
653
+ export class ConflictError extends AppError {
654
+ constructor(detail: string) {
655
+ super(409, detail, 'conflict');
656
+ }
657
+ }
658
+ ```
659
+
660
+ ### Backend — Global Error Middleware
661
+
662
+ ```typescript
663
+ const errorHandler: ErrorRequestHandler = (err, req, res, _next) => {
664
+ if (err instanceof AppError) {
665
+ return res.status(err.status).json({
666
+ type: `https://api.example.com/errors/${err.type}`,
667
+ title: err.message,
668
+ status: err.status,
669
+ detail: err.message,
670
+ ...(err instanceof ValidationError && { errors: err.errors }),
671
+ });
672
+ }
673
+
674
+ // Unexpected error — log it, don't expose details
675
+ logger.error(err);
676
+ res.status(500).json({
677
+ type: 'https://api.example.com/errors/internal',
678
+ title: 'Internal Server Error',
679
+ status: 500,
680
+ detail: 'An unexpected error occurred',
681
+ });
682
+ };
683
+ ```
684
+
685
+ ### Frontend — Error Boundaries + Toast
686
+
687
+ - Wrap route-level components with `ErrorBoundary` to catch render errors
688
+ - Use toast notifications for async operation failures (API errors, form submissions)
689
+ - Never swallow errors silently — always inform the user
690
+
691
+ ---
692
+
693
+ ## Environment Variables
694
+
695
+ Validate all environment variables at startup using Zod:
696
+
697
+ ```typescript
698
+ // config/env.ts
699
+ import { z } from 'zod';
700
+
701
+ const envSchema = z.object({
702
+ DATABASE_URL: z.string().url(),
703
+ JWT_SECRET: z.string().min(32),
704
+ PORT: z.coerce.number().default(3000),
705
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
706
+ });
707
+
708
+ export const env = envSchema.parse(process.env);
709
+ ```
710
+
711
+ If validation fails, the app crashes immediately at startup with a clear error message — not silently at runtime when the variable is first used.
712
+
713
+ ---
714
+
715
+ ## Testing Standards
716
+
717
+ ### Vitest (Unit & Integration)
718
+
719
+ - Test files live next to the code they test: `Button.test.tsx` alongside `Button.tsx`
720
+ - Test behavior, not implementation — test what the user sees and does
721
+ - Use `describe` / `it` blocks with readable descriptions
722
+ - Mock external dependencies (API calls, databases), not internal modules
723
+
724
+ ### Playwright (E2E)
725
+
726
+ - E2E tests live in the app's root: `apps/<project>/e2e/`
727
+ - Test critical user flows: login, checkout, form submissions
728
+ - Use page object pattern for maintainability
729
+ - Never rely on implementation details (CSS classes, test IDs only)
730
+
731
+ ---
732
+
733
+ ## Git Conventions
734
+
735
+ ### Trunk-Based Development
736
+
737
+ - `main` is always deployable
738
+ - Short-lived feature branches: `feat/user-auth`, `fix/cart-total`
739
+ - No long-lived branches — merge within 1-2 days
740
+ - Use feature flags for incomplete features that need to be merged
741
+
742
+ ### Conventional Commits
743
+
744
+ ```
745
+ <type>(<scope>): <description>
746
+
747
+ feat(auth): implement JWT refresh token rotation
748
+ fix(cart): correct total calculation with discount codes
749
+ refactor(api): extract user validation to middleware
750
+ docs(readme): add deployment instructions
751
+ test(orders): add integration tests for order creation
752
+ chore(deps): update prisma to v6
753
+ ```
754
+
755
+ Types: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci`, `build`
756
+
757
+ ---
758
+
759
+ ## Best Practices Checklist
760
+
761
+ Apply these on every piece of code:
762
+
763
+ ### TypeScript
764
+ - Enable `strict` mode — no exceptions
765
+ - No `any` — use `unknown` and narrow with type guards
766
+ - Prefer `interface` for object shapes, `type` for unions/intersections
767
+ - Use discriminated unions for state machines and variants
768
+ - Define return types explicitly on exported functions
769
+
770
+ ### React (Web & Mobile)
771
+ - Functional components only — no class components
772
+ - Custom hooks for reusable logic — prefix with `use`
773
+ - Memoize expensive computations with `useMemo`, not everything
774
+ - Use `React.lazy` + `Suspense` for code splitting (web)
775
+ - Avoid prop drilling — use composition or context for deep trees
776
+ - Keys must be stable, unique identifiers — never array index
777
+
778
+ ### Performance
779
+ - Lazy load routes and heavy components
780
+ - Virtualize long lists (`react-window` or `FlashList` for mobile)
781
+ - Debounce search inputs and other frequent events
782
+ - Use `loading` and `error` states from SWR — no manual loading booleans
783
+
784
+ ### Security
785
+ - Sanitize all user input — never trust client data
786
+ - Parameterized queries only (Prisma handles this)
787
+ - CORS configured per environment
788
+ - Rate limiting on auth endpoints
789
+ - Never store secrets in client code
790
+ - Validate request bodies with Zod schemas before processing
791
+
792
+ ---
793
+
794
+ ## i18n (Optional — Ask Per Project)
795
+
796
+ When i18n is needed, use `react-i18next`:
797
+
798
+ ```
799
+ src/
800
+ └── locales/
801
+ ├── en/
802
+ │ └── translation.json
803
+ └── tr/
804
+ └── translation.json
805
+ ```
806
+
807
+ Rules:
808
+ - All user-facing strings must use translation keys
809
+ - Never hardcode text in components
810
+ - Use namespaces for large apps to split translation files