bharatcode 0.1.0__py3-none-any.whl

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.
bharatcode/skills.py ADDED
@@ -0,0 +1,1746 @@
1
+ """
2
+ Skills system — interactive Q&A → fully tailored agent prompt per tech stack.
3
+ newsite and newapp ask for frontend + backend tech separately and produce
4
+ framework-specific instructions with a frontend/ and backend/ folder structure.
5
+ Custom skills from ~/.bharatcode/skills/*.md are also supported as-is.
6
+ """
7
+ from pathlib import Path
8
+ from rich.console import Console
9
+
10
+ SKILLS_DIR = Path.home() / ".bharatcode" / "skills"
11
+ console = Console()
12
+
13
+ # ── Tech stack option labels ───────────────────────────────────────────────────
14
+
15
+ FE_OPTIONS = [
16
+ "React + Vite",
17
+ "Vue 3 + Vite",
18
+ "Next.js 14",
19
+ "Angular 17",
20
+ "Svelte + Vite",
21
+ "Vanilla HTML / CSS / JS",
22
+ ]
23
+
24
+ BE_OPTIONS = [
25
+ "Flask (Python)",
26
+ "Django + DRF (Python)",
27
+ "Node.js + Express",
28
+ "FastAPI (Python)",
29
+ "Go + Gin",
30
+ "None (static / frontend only)",
31
+ ]
32
+
33
+ BE_OPTIONS_APP = [ # newapp — backend is required
34
+ "Flask (Python)",
35
+ "Django + DRF (Python)",
36
+ "Node.js + Express",
37
+ "FastAPI (Python)",
38
+ "Go + Gin",
39
+ ]
40
+
41
+ # ── Per-skill questions ────────────────────────────────────────────────────────
42
+
43
+ SKILL_QUESTIONS: dict[str, list[dict]] = {
44
+ "newsite": [
45
+ {"key": "name", "label": "Site name", "required": True},
46
+ {"key": "type", "label": "What kind of site",
47
+ "choices": ["portfolio", "landing page", "SaaS marketing", "e-commerce",
48
+ "blog", "agency", "product showcase", "community", "other"],
49
+ "required": True},
50
+ {"key": "desc", "label": "Describe it — what it does, who it's for",
51
+ "hint": "e.g. dark developer portfolio with projects, blog, and contact form"},
52
+ {"key": "frontend", "label": "Frontend technology",
53
+ "choices": FE_OPTIONS, "required": True},
54
+ {"key": "backend", "label": "Backend (choose None for static / no API needed)",
55
+ "choices": BE_OPTIONS, "required": True},
56
+ {"key": "sections", "label": "Key sections",
57
+ "hint": "e.g. hero, about, projects, skills, pricing, blog, testimonials, contact"},
58
+ {"key": "features", "label": "Special features",
59
+ "hint": "e.g. dark mode toggle, contact form with backend, blog CMS, animations"},
60
+ {"key": "theme", "label": "Visual theme",
61
+ "choices": ["dark", "light", "minimal", "bold / colorful", "corporate", "playful"]},
62
+ ],
63
+
64
+ "newapp": [
65
+ {"key": "name", "label": "App name", "required": True},
66
+ {"key": "desc", "label": "What does this app do?", "required": True},
67
+ {"key": "frontend", "label": "Frontend technology",
68
+ "choices": FE_OPTIONS, "required": True},
69
+ {"key": "backend", "label": "Backend technology",
70
+ "choices": BE_OPTIONS_APP, "required": True},
71
+ {"key": "database", "label": "Database",
72
+ "choices": ["PostgreSQL", "MySQL", "MongoDB", "SQLite", "Redis + PostgreSQL", "none"]},
73
+ {"key": "auth", "label": "Authentication",
74
+ "choices": ["JWT (email / password)", "JWT + Google OAuth", "Session-based", "no auth"]},
75
+ {"key": "features", "label": "Core features (comma-separated)",
76
+ "hint": "e.g. user profiles, dashboard, file uploads, real-time notifications, admin panel"},
77
+ {"key": "extras", "label": "Extra integrations (optional)",
78
+ "hint": "e.g. Stripe, SendGrid, WebSockets, S3, Redis cache, cron jobs"},
79
+ ],
80
+
81
+ "docker": [
82
+ {"key": "database", "label": "Database to include in compose",
83
+ "choices": ["PostgreSQL", "MySQL", "MongoDB", "Redis", "none"]},
84
+ {"key": "extras", "label": "Extra services",
85
+ "choices": ["Redis + Celery", "Nginx reverse proxy", "both", "none"]},
86
+ {"key": "multistage", "label": "Multi-stage build (smaller production image)?",
87
+ "choices": ["yes", "no"]},
88
+ ],
89
+
90
+ "ci-github": [
91
+ {"key": "test_fw", "label": "Test framework",
92
+ "hint": "e.g. pytest, Jest, JUnit, go test — or 'none'"},
93
+ {"key": "deploy", "label": "Deployment target",
94
+ "choices": ["AWS EC2", "AWS ECS / ECR", "GCP Cloud Run", "Azure App Service",
95
+ "VPS (SSH deploy)", "Heroku", "none"]},
96
+ {"key": "auto_deploy","label": "Auto-deploy on merge to main?",
97
+ "choices": ["yes", "no"]},
98
+ ],
99
+ }
100
+
101
+
102
+ # ── Interactive Q&A ───────────────────────────────────────────────────────────
103
+
104
+ def _ask_choice(label: str, choices: list[str]) -> str | None:
105
+ try:
106
+ import questionary
107
+ from questionary import Style
108
+ result = questionary.select(
109
+ f" {label}:",
110
+ choices=choices + ["↩ Skip"],
111
+ style=Style([
112
+ ("highlighted", "fg:cyan bold"),
113
+ ("pointer", "fg:cyan bold"),
114
+ ("selected", "fg:green"),
115
+ ("question", "fg:yellow bold"),
116
+ ]),
117
+ instruction=" (↑↓ move Enter select)",
118
+ ).ask()
119
+ return None if result == "↩ Skip" else result
120
+ except (ImportError, Exception):
121
+ console.print(f"\n [yellow]{label}:[/yellow]")
122
+ for i, c in enumerate(choices, 1):
123
+ console.print(f" [green]{i}[/green] {c}")
124
+ try:
125
+ raw = input(" Pick number (Enter to skip): ").strip()
126
+ except (EOFError, KeyboardInterrupt):
127
+ return None
128
+ if raw.isdigit():
129
+ idx = int(raw) - 1
130
+ if 0 <= idx < len(choices):
131
+ return choices[idx]
132
+ return None
133
+
134
+
135
+ def ask_skill_questions(name: str, prefilled: dict | None = None) -> dict | None:
136
+ """
137
+ Run the interactive Q&A for a skill.
138
+ prefilled: keys already known — those questions are skipped.
139
+ Returns answers dict, or None if the user cancelled.
140
+ """
141
+ questions = SKILL_QUESTIONS.get(name)
142
+ if not questions:
143
+ return {}
144
+
145
+ pre = prefilled or {}
146
+ answers = dict(pre)
147
+
148
+ console.print(f"\n[bold cyan] {name} setup[/bold cyan]\n")
149
+
150
+ for q in questions:
151
+ key = q["key"]
152
+ label = q["label"]
153
+ hint = q.get("hint", "")
154
+ choices = q.get("choices")
155
+ required = q.get("required", False)
156
+
157
+ if key in pre and pre[key]:
158
+ console.print(f" [dim]{label}:[/dim] [cyan]{pre[key]}[/cyan]")
159
+ continue
160
+
161
+ while True:
162
+ if choices:
163
+ result = _ask_choice(label, choices)
164
+ if result is None and required:
165
+ console.print(" [red]Required — please pick one.[/red]")
166
+ continue
167
+ if result:
168
+ answers[key] = result
169
+ break
170
+ else:
171
+ if hint:
172
+ console.print(f" [dim]{hint}[/dim]")
173
+ try:
174
+ val = input(f" {label}: ").strip()
175
+ except (EOFError, KeyboardInterrupt):
176
+ console.print("\n [dim]Cancelled.[/dim]")
177
+ return None
178
+ if not val and required:
179
+ console.print(" [red]Required.[/red]")
180
+ continue
181
+ if val:
182
+ answers[key] = val
183
+ break
184
+
185
+ console.print()
186
+ return answers
187
+
188
+
189
+ # ── Per-framework detail blocks ───────────────────────────────────────────────
190
+
191
+ _FE_PORTS = {
192
+ "React + Vite": 5173,
193
+ "Vue 3 + Vite": 5173,
194
+ "Svelte + Vite": 5173,
195
+ "Next.js 14": 3000,
196
+ "Angular 17": 4200,
197
+ "Vanilla HTML / CSS / JS": 3000,
198
+ }
199
+
200
+ _BE_PORTS = {
201
+ "Flask (Python)": 5000,
202
+ "Django + DRF (Python)": 8000,
203
+ "Node.js + Express": 5000,
204
+ "FastAPI (Python)": 8000,
205
+ "Go + Gin": 8080,
206
+ "None (static / frontend only)": None,
207
+ }
208
+
209
+
210
+ def _fe_detail(tech: str) -> str:
211
+ """Detailed file structure + coding rules for the chosen frontend tech."""
212
+
213
+ if tech == "React + Vite":
214
+ return """
215
+ FRONTEND: React + Vite
216
+ ══════════════════════
217
+ Folder structure (frontend/):
218
+ src/
219
+ components/
220
+ ui/ ← Button.jsx, Input.jsx, Modal.jsx, Card.jsx, Spinner.jsx
221
+ (pure presentational, no API calls, fully typed props)
222
+ layout/ ← Navbar.jsx, Footer.jsx, Sidebar.jsx, PageWrapper.jsx
223
+ [feature]/ ← feature-specific components (one sub-folder per domain)
224
+ pages/ ← one file per route: HomePage.jsx, DashboardPage.jsx, LoginPage.jsx
225
+ hooks/ ← useAuth.js, useFetch.js, useForm.js, useDebounce.js
226
+ services/
227
+ api.js ← axios instance + request/response interceptors (see below)
228
+ auth.service.js ← login(creds), register(data), logout(), refreshToken()
229
+ [domain].service.js ← one file per API domain
230
+ store/ ← Zustand store (preferred) or Context + useReducer
231
+ authStore.js ← user, token, setUser, clearAuth
232
+ utils/
233
+ formatters.js ← formatDate(), formatCurrency(), truncate()
234
+ validators.js ← isEmail(), isStrongPassword(), required()
235
+ constants.js ← ROUTES, USER_ROLES, API_PATHS
236
+ config/
237
+ api.js ← export const API_BASE = import.meta.env.VITE_API_URL
238
+ styles/
239
+ index.css ← global reset, CSS custom properties, font-face
240
+ App.jsx ← <BrowserRouter> + <Routes> setup ONLY
241
+ main.jsx ← ReactDOM.createRoot + StrictMode
242
+
243
+ services/api.js (complete — copy this pattern exactly):
244
+ import axios from 'axios';
245
+ const api = axios.create({
246
+ baseURL: import.meta.env.VITE_API_URL,
247
+ headers: { 'Content-Type': 'application/json' },
248
+ });
249
+ api.interceptors.request.use(config => {
250
+ const token = localStorage.getItem('token');
251
+ if (token) config.headers.Authorization = `Bearer ${token}`;
252
+ return config;
253
+ });
254
+ api.interceptors.response.use(
255
+ response => response,
256
+ async error => {
257
+ if (error.response?.status === 401) {
258
+ localStorage.removeItem('token');
259
+ window.location.href = '/login';
260
+ }
261
+ return Promise.reject(error);
262
+ }
263
+ );
264
+ export default api;
265
+
266
+ Domain service pattern (services/auth.service.js):
267
+ import api from './api.js';
268
+ export const authService = {
269
+ login: (data) => api.post('/api/auth/login', data).then(r => r.data),
270
+ register: (data) => api.post('/api/auth/register', data).then(r => r.data),
271
+ logout: () => api.post('/api/auth/logout').then(r => r.data),
272
+ me: () => api.get('/api/auth/me').then(r => r.data),
273
+ };
274
+
275
+ vite.config.js (dev proxy — eliminates CORS in development):
276
+ import { defineConfig } from 'vite';
277
+ import react from '@vitejs/plugin-react';
278
+ export default defineConfig({
279
+ plugins: [react()],
280
+ server: {
281
+ port: 5173,
282
+ proxy: { '/api': { target: 'http://localhost:BACKEND_PORT', changeOrigin: true } }
283
+ }
284
+ });
285
+
286
+ .env.example:
287
+ VITE_API_URL=http://localhost:BACKEND_PORT
288
+
289
+ package.json essentials:
290
+ react@18, react-dom@18, react-router-dom@6, axios, zustand
291
+ devDeps: vite, @vitejs/plugin-react
292
+
293
+ Coding rules:
294
+ - ALL API calls go through services/*.service.js — never axios/fetch directly in components
295
+ - All pages in pages/, reusable UI in components/ui/, layout in components/layout/
296
+ - React Router v6: useNavigate(), useParams(), <Outlet /> pattern
297
+ - No class components — functional components + hooks only
298
+ - Zustand for global state (auth, user, theme) — useState/useReducer for local state
299
+ - Never hardcode backend URLs in component files
300
+ - Error boundaries at the page level
301
+ - PropTypes or TypeScript interface for every component's props"""
302
+
303
+ if tech == "Vue 3 + Vite":
304
+ return """
305
+ FRONTEND: Vue 3 + Vite
306
+ ══════════════════════
307
+ Folder structure (frontend/):
308
+ src/
309
+ components/
310
+ base/ ← BaseButton.vue, BaseInput.vue, BaseModal.vue, BaseCard.vue
311
+ layout/ ← AppHeader.vue, AppFooter.vue, AppSidebar.vue
312
+ [feature]/ ← feature-specific components
313
+ views/ ← one .vue file per route: HomeView.vue, DashboardView.vue, LoginView.vue
314
+ router/
315
+ index.js ← createRouter, createWebHistory, route guards
316
+ stores/ ← Pinia (one store per domain)
317
+ auth.store.js ← useAuthStore: state, login(), logout(), fetchMe()
318
+ [domain].store.js
319
+ services/
320
+ api.js ← axios instance + interceptors
321
+ [domain].service.js
322
+ composables/ ← useAuth.js, useForm.js, usePagination.js, useNotify.js
323
+ utils/
324
+ formatters.js
325
+ validators.js
326
+ config/
327
+ api.js ← export const API_BASE = import.meta.env.VITE_API_URL
328
+ assets/
329
+ styles/
330
+ main.css
331
+ variables.css
332
+ App.vue
333
+ main.js ← createApp + use(router) + use(pinia) + mount
334
+
335
+ services/api.js (same axios pattern as React — copy):
336
+ import axios from 'axios';
337
+ const api = axios.create({ baseURL: import.meta.env.VITE_API_URL });
338
+ api.interceptors.request.use(config => {
339
+ const token = localStorage.getItem('token');
340
+ if (token) config.headers.Authorization = `Bearer ${token}`;
341
+ return config;
342
+ });
343
+ api.interceptors.response.use(r => r, err => {
344
+ if (err.response?.status === 401) { localStorage.removeItem('token'); window.location.href = '/login'; }
345
+ return Promise.reject(err);
346
+ });
347
+ export default api;
348
+
349
+ Pinia store pattern (stores/auth.store.js):
350
+ import { defineStore } from 'pinia';
351
+ import { authService } from '../services/auth.service.js';
352
+ export const useAuthStore = defineStore('auth', {
353
+ state: () => ({ user: null, token: localStorage.getItem('token') || null }),
354
+ getters: { isLoggedIn: s => !!s.token },
355
+ actions: {
356
+ async login(credentials) {
357
+ const { token, user } = await authService.login(credentials);
358
+ this.token = token; this.user = user; localStorage.setItem('token', token);
359
+ },
360
+ logout() { this.token = null; this.user = null; localStorage.removeItem('token'); },
361
+ },
362
+ });
363
+
364
+ vite.config.js:
365
+ server: { port: 5173, proxy: { '/api': { target: 'http://localhost:BACKEND_PORT', changeOrigin: true } } }
366
+
367
+ .env.example: VITE_API_URL=http://localhost:BACKEND_PORT
368
+
369
+ Coding rules:
370
+ - Composition API ONLY inside <script setup> — NEVER Options API
371
+ - Pinia for all shared state — no Vuex, no prop drilling past 2 levels
372
+ - All API calls in services/*.service.js — composables call services, components call composables
373
+ - defineModel() macro (Vue 3.4+) for two-way binding in child components
374
+ - Route guards in router/index.js using beforeEach for auth protection
375
+ - Named routes always (router.push({ name: 'dashboard' }) not string paths)"""
376
+
377
+ if tech == "Next.js 14":
378
+ return """
379
+ FRONTEND: Next.js 14 (App Router)
380
+ ══════════════════════════════════
381
+ Folder structure (frontend/):
382
+ app/
383
+ layout.tsx ← root layout: <html>, <body>, global providers, fonts
384
+ page.tsx ← home route (server component)
385
+ globals.css
386
+ (public)/ ← route group: public pages (no auth needed)
387
+ about/page.tsx
388
+ [page]/page.tsx
389
+ (auth)/ ← route group: login, signup
390
+ login/page.tsx
391
+ signup/page.tsx
392
+ (dashboard)/ ← route group: protected pages
393
+ layout.tsx ← auth guard: redirect if no session
394
+ page.tsx
395
+ [feature]/page.tsx
396
+ api/ ← API routes (ONLY for webhooks / third-party callbacks)
397
+ health/route.ts
398
+ components/
399
+ ui/ ← Button.tsx, Input.tsx, Card.tsx, Modal.tsx ("use client")
400
+ layout/ ← Header.tsx, Footer.tsx, Sidebar.tsx
401
+ [feature]/
402
+ lib/
403
+ api.ts ← typed fetch wrapper (see below)
404
+ auth.ts ← getSession(), requireAuth()
405
+ db.ts ← Prisma client (if DB in Next.js)
406
+ utils.ts ← cn(), formatDate(), formatCurrency()
407
+ types/
408
+ index.ts ← all shared TypeScript interfaces/types
409
+ hooks/ ← "use client" hooks: useAuth.ts, useForm.ts, useLocalStorage.ts
410
+ config/
411
+ site.ts ← SITE_NAME, SITE_URL, nav links
412
+ middleware.ts ← protects /dashboard/** routes, redirects to /login
413
+ next.config.js
414
+ .env.example
415
+ tsconfig.json
416
+
417
+ lib/api.ts (typed fetch wrapper):
418
+ const BASE = process.env.NEXT_PUBLIC_API_URL ?? '';
419
+ function getToken() { return typeof window !== 'undefined' ? localStorage.getItem('token') : null; }
420
+ export async function apiFetch<T>(path: string, opts: RequestInit = {}): Promise<T> {
421
+ const token = getToken();
422
+ const res = await fetch(`${BASE}${path}`, {
423
+ ...opts,
424
+ headers: {
425
+ 'Content-Type': 'application/json',
426
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
427
+ ...opts.headers,
428
+ },
429
+ });
430
+ if (!res.ok) {
431
+ const msg = await res.text();
432
+ throw new Error(msg || `HTTP ${res.status}`);
433
+ }
434
+ return res.json() as Promise<T>;
435
+ }
436
+ export const api = {
437
+ get: <T>(path: string) => apiFetch<T>(path, { method: 'GET' }),
438
+ post: <T>(path: string, body: unknown) => apiFetch<T>(path, { method: 'POST', body: JSON.stringify(body) }),
439
+ put: <T>(path: string, body: unknown) => apiFetch<T>(path, { method: 'PUT', body: JSON.stringify(body) }),
440
+ delete: <T>(path: string) => apiFetch<T>(path, { method: 'DELETE' }),
441
+ };
442
+
443
+ next.config.js (API proxy to backend):
444
+ /** @type {import('next').NextConfig} */
445
+ module.exports = {
446
+ async rewrites() {
447
+ return [{ source: '/api/:path*', destination: `${process.env.BACKEND_URL}/api/:path*` }];
448
+ },
449
+ };
450
+
451
+ .env.example:
452
+ NEXT_PUBLIC_API_URL=http://localhost:BACKEND_PORT
453
+ BACKEND_URL=http://localhost:BACKEND_PORT
454
+
455
+ Coding rules:
456
+ - Server components by default — add "use client" ONLY for: hooks, event listeners, browser APIs
457
+ - Server Actions for all form mutations — not /api routes for internal data changes
458
+ - /api routes ONLY for external webhooks (Stripe, GitHub, etc.)
459
+ - TypeScript everywhere — no any, no @ts-ignore
460
+ - NEXT_PUBLIC_ prefix for env vars needed in client components
461
+ - next/image for ALL images, next/font for ALL fonts
462
+ - Route groups (parentheses) to share layouts without adding URL segments
463
+ - NEVER use Pages Router patterns (getServerSideProps, getStaticProps, pages/ directory)
464
+ - middleware.ts: use next/server NextResponse.redirect for auth protection"""
465
+
466
+ if tech == "Angular 17":
467
+ return """
468
+ FRONTEND: Angular 17 (Standalone)
469
+ ══════════════════════════════════
470
+ Folder structure (frontend/):
471
+ src/app/
472
+ core/
473
+ guards/
474
+ auth.guard.ts ← CanActivateFn — redirects to /login if no token
475
+ interceptors/
476
+ auth.interceptor.ts ← HttpInterceptorFn — injects Bearer token into every request
477
+ error.interceptor.ts ← handles 401 (redirect), 500 (show toast)
478
+ services/
479
+ auth.service.ts ← login(), logout(), isLoggedIn(), currentUser signal
480
+ api.service.ts ← HttpClient wrapper (typed GET/POST/PUT/DELETE)
481
+ shared/
482
+ components/ ← ButtonComponent, InputComponent, ModalComponent, CardComponent
483
+ pipes/ ← DateFormatPipe, CurrencyInrPipe
484
+ directives/ ← ClickOutsideDirective, AutofocusDirective
485
+ features/
486
+ [feature]/
487
+ components/
488
+ [feature].component.ts ← standalone, imports: CommonModule, RouterModule, ...
489
+ services/
490
+ [feature].service.ts ← injects ApiService
491
+ [feature].routes.ts ← Routes array, lazy loaded
492
+ app.component.ts ← standalone root component
493
+ app.config.ts ← provideRouter, provideHttpClient, withInterceptors
494
+ app.routes.ts ← top-level routes with loadChildren lazy loading
495
+ environments/
496
+ environment.ts ← { production: false, apiUrl: 'http://localhost:BACKEND_PORT' }
497
+ environment.prod.ts ← { production: true, apiUrl: 'https://api.yourdomain.com' }
498
+ proxy.conf.json ← "/api": { "target": "http://localhost:BACKEND_PORT", "changeOrigin": true }
499
+
500
+ core/services/api.service.ts:
501
+ @Injectable({ providedIn: 'root' })
502
+ export class ApiService {
503
+ private http = inject(HttpClient);
504
+ private baseUrl = environment.apiUrl;
505
+ get<T>(path: string) { return this.http.get<T>(`${this.baseUrl}${path}`); }
506
+ post<T>(path: string, body: unknown) { return this.http.post<T>(`${this.baseUrl}${path}`, body); }
507
+ put<T>(path: string, body: unknown) { return this.http.put<T>(`${this.baseUrl}${path}`, body); }
508
+ delete<T>(path: string) { return this.http.delete<T>(`${this.baseUrl}${path}`); }
509
+ }
510
+
511
+ core/interceptors/auth.interceptor.ts:
512
+ export const authInterceptor: HttpInterceptorFn = (req, next) => {
513
+ const token = localStorage.getItem('token');
514
+ if (token) {
515
+ req = req.clone({ headers: req.headers.set('Authorization', `Bearer ${token}`) });
516
+ }
517
+ return next(req).pipe(
518
+ catchError(err => { if (err.status === 401) { inject(Router).navigate(['/login']); } throw err; })
519
+ );
520
+ };
521
+
522
+ app.config.ts:
523
+ export const appConfig: ApplicationConfig = {
524
+ providers: [
525
+ provideRouter(appRoutes),
526
+ provideHttpClient(withInterceptors([authInterceptor, errorInterceptor])),
527
+ provideAnimations(),
528
+ ],
529
+ };
530
+
531
+ angular.json (add proxyConfig):
532
+ "serve": { "options": { "proxyConfig": "proxy.conf.json" } }
533
+
534
+ Coding rules:
535
+ - Standalone components EVERYWHERE — never NgModules
536
+ - Angular Signals: signal(), computed(), effect() for reactive state
537
+ - inject() function in constructor body or at field declaration — never constructor injection style
538
+ - All API calls in *.service.ts — components call services, never HttpClient directly
539
+ - Lazy-load every feature route: loadChildren: () => import('./features/x/x.routes')
540
+ - environment.ts for all config — never hardcode URLs
541
+ - OnPush change detection strategy on all components"""
542
+
543
+ if tech == "Svelte + Vite":
544
+ return """
545
+ FRONTEND: Svelte + Vite
546
+ ═══════════════════════
547
+ Folder structure (frontend/):
548
+ src/
549
+ components/
550
+ ui/ ← Button.svelte, Input.svelte, Modal.svelte, Card.svelte
551
+ layout/ ← Navbar.svelte, Footer.svelte
552
+ [feature]/
553
+ pages/ ← one .svelte per route (use svelte-routing or SvelteKit)
554
+ stores/
555
+ auth.js ← writable store: { user, token }
556
+ [domain].js
557
+ services/
558
+ api.js ← fetch wrapper with auth header injection
559
+ [domain].service.js
560
+ utils/
561
+ formatters.js
562
+ validators.js
563
+ config/
564
+ api.js ← export const API_BASE = import.meta.env.VITE_API_URL
565
+ styles/
566
+ global.css
567
+ variables.css
568
+ App.svelte
569
+ main.js
570
+ vite.config.js
571
+ .env.example
572
+
573
+ stores/auth.js:
574
+ import { writable } from 'svelte/store';
575
+ function createAuthStore() {
576
+ const { subscribe, set, update } = writable({
577
+ user: null,
578
+ token: localStorage.getItem('token') || null,
579
+ });
580
+ return {
581
+ subscribe,
582
+ login: (user, token) => {
583
+ localStorage.setItem('token', token);
584
+ set({ user, token });
585
+ },
586
+ logout: () => {
587
+ localStorage.removeItem('token');
588
+ set({ user: null, token: null });
589
+ },
590
+ };
591
+ }
592
+ export const authStore = createAuthStore();
593
+
594
+ services/api.js:
595
+ import { get as getStore } from 'svelte/store';
596
+ import { authStore } from '../stores/auth.js';
597
+ const BASE = import.meta.env.VITE_API_URL;
598
+ async function request(method, path, body) {
599
+ const { token } = getStore(authStore);
600
+ const res = await fetch(`${BASE}${path}`, {
601
+ method,
602
+ headers: {
603
+ 'Content-Type': 'application/json',
604
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
605
+ },
606
+ ...(body ? { body: JSON.stringify(body) } : {}),
607
+ });
608
+ if (!res.ok) {
609
+ const msg = await res.text();
610
+ if (res.status === 401) { authStore.logout(); window.location.href = '/login'; }
611
+ throw new Error(msg || `HTTP ${res.status}`);
612
+ }
613
+ return res.json();
614
+ }
615
+ export const api = {
616
+ get: (path) => request('GET', path),
617
+ post: (path, body) => request('POST', path, body),
618
+ put: (path, body) => request('PUT', path, body),
619
+ delete: (path) => request('DELETE', path),
620
+ };
621
+
622
+ vite.config.js:
623
+ server: { port: 5173, proxy: { '/api': { target: 'http://localhost:BACKEND_PORT', changeOrigin: true } } }
624
+
625
+ Coding rules:
626
+ - Svelte stores for ALL shared state — components use $storeName reactive syntax
627
+ - All API calls through services/api.js — never fetch() directly in .svelte files
628
+ - $: reactive declarations for derived/computed values
629
+ - onMount for lifecycle effects (like useEffect in React)
630
+ - Dispatch custom events to parent instead of prop callbacks
631
+ - Slots for component composition"""
632
+
633
+ # Vanilla HTML / CSS / JS
634
+ return """
635
+ FRONTEND: Vanilla HTML / CSS / JS
636
+ ══════════════════════════════════
637
+ Folder structure (frontend/):
638
+ index.html ← main entry point (type="module" on script tags)
639
+ [page].html ← one file per page
640
+ css/
641
+ variables.css ← ALL custom properties: colors, spacing, fonts, shadows, radii
642
+ reset.css ← modern CSS reset (box-sizing, margin 0, line-height)
643
+ typography.css ← @font-face / Google Fonts, heading scale, body text
644
+ layout.css ← .container, grid wrappers, section padding, flex rows
645
+ navbar.css ← nav links, hamburger, mobile overlay, scroll-shrink
646
+ components.css ← .btn, .card, .badge, .form-group, .input — every reusable piece
647
+ [section].css ← hero.css, about.css, projects.css, services.css, etc.
648
+ animations.css ← @keyframes, .reveal, .fade-in, hover transitions
649
+ responsive.css ← ALL media queries, every breakpoint
650
+ main.css ← @import in correct order (variables first, reset second)
651
+ js/
652
+ config.js ← export const CONFIG = { API_BASE: 'http://localhost:BACKEND_PORT', ... }
653
+ utils.js ← $() querySelector, $$() querySelectorAll, debounce, throttle, formatDate
654
+ api.js ← fetch wrapper that reads CONFIG.API_BASE (see below)
655
+ auth.js ← getToken(), setToken(), clearToken(), isLoggedIn()
656
+ navbar.js ← mobile toggle, scroll-hide/show, active link highlighting
657
+ animations.js ← IntersectionObserver scroll-reveal, counter animation, parallax
658
+ theme.js ← dark/light toggle, localStorage persistence
659
+ forms.js ← field validation, error display, submit handler
660
+ [feature].js ← one file per distinct feature
661
+ main.js ← DOMContentLoaded init — imports and calls init functions only
662
+ assets/
663
+ images/
664
+ icons/
665
+ fonts/
666
+
667
+ js/api.js (complete — copy exactly):
668
+ import { CONFIG } from './config.js';
669
+ import { getToken, clearToken } from './auth.js';
670
+ async function request(method, path, body) {
671
+ const token = getToken();
672
+ const res = await fetch(`${CONFIG.API_BASE}${path}`, {
673
+ method,
674
+ headers: {
675
+ 'Content-Type': 'application/json',
676
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
677
+ },
678
+ ...(body ? { body: JSON.stringify(body) } : {}),
679
+ });
680
+ if (res.status === 401) { clearToken(); window.location.href = '/login.html'; }
681
+ if (!res.ok) { const msg = await res.text(); throw new Error(msg || `HTTP ${res.status}`); }
682
+ if (res.status === 204) return null;
683
+ return res.json();
684
+ }
685
+ export const api = {
686
+ get: (path) => request('GET', path),
687
+ post: (path, body) => request('POST', path, body),
688
+ put: (path, body) => request('PUT', path, body),
689
+ delete: (path) => request('DELETE', path),
690
+ };
691
+
692
+ Coding rules:
693
+ - ES6 modules (type="module") everywhere — no inline <script> blocks
694
+ - ALL fetch calls go through api.js — never raw fetch() in feature files
695
+ - CSS: use real values from the chosen palette — real pixel values, real colors, real spacing
696
+ - HTML: real, meaningful content — never lorem ipsum in any visible text
697
+ - JS: real event listeners, real DOM queries, real working logic
698
+ - Every section gets its own .css file imported in main.css
699
+ - config.js is the single source of truth for API URL and constants"""
700
+
701
+
702
+ def _be_detail(tech: str) -> str:
703
+ """Detailed file structure + coding rules for the chosen backend tech."""
704
+
705
+ if tech == "Flask (Python)":
706
+ return """
707
+ BACKEND: Flask (Python)
708
+ ═══════════════════════
709
+ Folder structure (backend/):
710
+ app/
711
+ __init__.py ← create_app(config_name='development') factory function
712
+ config.py ← Config, DevelopmentConfig, ProductionConfig classes
713
+ extensions.py ← db = SQLAlchemy(); jwt = JWTManager(); cors = CORS()
714
+ models/
715
+ __init__.py
716
+ user.py ← class User(db.Model): id, email, password_hash, role, created_at, is_active
717
+ [resource].py ← one model file per domain entity
718
+ routes/
719
+ __init__.py ← def register_routes(app): app.register_blueprint(auth_bp); ...
720
+ auth.py ← auth_bp = Blueprint('auth', __name__, url_prefix='/api/auth')
721
+ [resource].py ← [resource]_bp = Blueprint(...)
722
+ services/
723
+ auth_service.py ← register_user(), authenticate_user(), create_tokens()
724
+ [resource]_service.py
725
+ utils/
726
+ decorators.py ← @admin_required, @validate_json(schema)
727
+ validators.py ← validate_email(), validate_password_strength()
728
+ responses.py ← success_response(data, code=200), error_response(msg, code=400)
729
+ migrations/ ← flask db init, flask db migrate, flask db upgrade
730
+ tests/
731
+ test_auth.py
732
+ test_[resource].py
733
+ run.py ← if __name__ == '__main__': create_app('development').run(port=5000, debug=True)
734
+ requirements.txt
735
+ .env.example
736
+ Makefile
737
+
738
+ app/__init__.py (application factory — copy this pattern):
739
+ from flask import Flask
740
+ from .extensions import db, jwt, cors
741
+ from .config import config
742
+ def create_app(config_name='development'):
743
+ app = Flask(__name__)
744
+ app.config.from_object(config[config_name])
745
+ db.init_app(app)
746
+ jwt.init_app(app)
747
+ cors.init_app(app, resources={r'/api/*': {'origins': app.config['CORS_ORIGINS']}})
748
+ with app.app_context():
749
+ db.create_all()
750
+ from .routes import register_routes
751
+ register_routes(app)
752
+ return app
753
+
754
+ app/config.py:
755
+ import os
756
+ class Config:
757
+ SECRET_KEY = os.environ['SECRET_KEY']
758
+ SQLALCHEMY_DATABASE_URI = os.environ['DATABASE_URL']
759
+ JWT_SECRET_KEY = os.environ['JWT_SECRET_KEY']
760
+ JWT_ACCESS_TOKEN_EXPIRES = timedelta(minutes=15)
761
+ JWT_REFRESH_TOKEN_EXPIRES = timedelta(days=7)
762
+ CORS_ORIGINS = os.environ.get('CORS_ORIGINS', 'http://localhost:5173').split(',')
763
+ class DevelopmentConfig(Config):
764
+ DEBUG = True
765
+ SQLALCHEMY_ECHO = True
766
+ config = {'development': DevelopmentConfig, 'production': ProductionConfig}
767
+
768
+ Route pattern (every endpoint returns JSON):
769
+ @auth_bp.route('/register', methods=['POST'])
770
+ def register():
771
+ data = request.get_json()
772
+ if not data: return error_response('No data provided', 400)
773
+ result, error = auth_service.register_user(data)
774
+ if error: return error_response(error, 400)
775
+ return success_response(result, 201)
776
+
777
+ @auth_bp.route('/login', methods=['POST'])
778
+ def login():
779
+ data = request.get_json()
780
+ tokens, error = auth_service.authenticate_user(data.get('email'), data.get('password'))
781
+ if error: return error_response(error, 401)
782
+ return success_response(tokens)
783
+
784
+ @auth_bp.route('/me', methods=['GET'])
785
+ @jwt_required()
786
+ def me():
787
+ user_id = get_jwt_identity()
788
+ user = User.query.get_or_404(user_id)
789
+ return success_response(user.to_dict())
790
+
791
+ Consistent response envelope (utils/responses.py):
792
+ from flask import jsonify
793
+ def success_response(data, code=200): return jsonify({'data': data, 'error': None}), code
794
+ def error_response(msg, code=400): return jsonify({'data': None, 'error': msg}), code
795
+
796
+ GET /api/health endpoint (always include):
797
+ @app.route('/api/health')
798
+ def health(): return jsonify({'status': 'ok', 'timestamp': datetime.utcnow().isoformat()})
799
+
800
+ requirements.txt:
801
+ flask>=3.0, flask-sqlalchemy, flask-jwt-extended, flask-cors,
802
+ flask-migrate, psycopg2-binary, python-dotenv, bcrypt, email-validator
803
+
804
+ Coding rules:
805
+ - NEVER use app = Flask(__name__) at module level — always application factory
806
+ - One Blueprint per route group, all registered in routes/__init__.py
807
+ - SQLAlchemy ORM only — never raw SQL strings
808
+ - JWT via Flask-JWT-Extended: @jwt_required() on protected routes, get_jwt_identity()
809
+ - All config from os.environ — never hardcode secrets
810
+ - Passwords: bcrypt.generate_password_hash(pw, rounds=12) — never MD5/SHA
811
+ - Every model has a .to_dict() method for JSON serialization"""
812
+
813
+ if tech == "Django + DRF (Python)":
814
+ return """
815
+ BACKEND: Django + REST Framework (Python)
816
+ ══════════════════════════════════════════
817
+ Folder structure (backend/):
818
+ config/
819
+ __init__.py
820
+ settings/
821
+ __init__.py ← from .development import * (or set via DJANGO_SETTINGS_MODULE)
822
+ base.py ← installed apps, middleware, DRF config, JWT config
823
+ development.py ← DEBUG=True, local DB, CORS allow all
824
+ production.py ← DEBUG=False, production DB, ALLOWED_HOSTS, SECURE_* headers
825
+ urls.py ← urlpatterns: path('api/', include('apps.users.urls')), ...
826
+ wsgi.py
827
+ asgi.py
828
+ apps/
829
+ users/
830
+ models.py ← class User(AbstractUser): bio, avatar, ... (ALWAYS custom)
831
+ serializers.py ← UserSerializer, UserCreateSerializer, LoginSerializer
832
+ views.py ← UserViewSet, LoginView, RegisterView
833
+ urls.py ← router = DefaultRouter(); router.register('users', UserViewSet)
834
+ permissions.py ← IsOwnerOrReadOnly, IsAdmin
835
+ tests.py
836
+ [app]/ ← one Django app per domain (posts, products, orders, etc.)
837
+ requirements.txt
838
+ manage.py
839
+ .env.example
840
+ Makefile
841
+
842
+ config/settings/base.py essentials:
843
+ AUTH_USER_MODEL = 'users.User' ← MUST be set before first migration
844
+ INSTALLED_APPS = [..., 'rest_framework', 'corsheaders', 'apps.users', ...]
845
+ MIDDLEWARE = ['corsheaders.middleware.CorsMiddleware', ...]
846
+ REST_FRAMEWORK = {
847
+ 'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework_simplejwt.authentication.JWTAuthentication'],
848
+ 'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated'],
849
+ 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
850
+ 'PAGE_SIZE': 20,
851
+ }
852
+ SIMPLE_JWT = {
853
+ 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),
854
+ 'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
855
+ }
856
+ CORS_ALLOWED_ORIGINS = env.list('CORS_ALLOWED_ORIGINS', default=['http://localhost:5173'])
857
+
858
+ ViewSet pattern (apps/[app]/views.py):
859
+ class ArticleViewSet(ModelViewSet):
860
+ serializer_class = ArticleSerializer
861
+ permission_classes = [IsAuthenticated]
862
+ def get_queryset(self):
863
+ return Article.objects.filter(author=self.request.user).select_related('author')
864
+ def perform_create(self, serializer):
865
+ serializer.save(author=self.request.user)
866
+
867
+ Serializer pattern:
868
+ class ArticleSerializer(ModelSerializer):
869
+ author = UserSerializer(read_only=True)
870
+ class Meta:
871
+ model = Article
872
+ fields = ['id', 'title', 'content', 'author', 'created_at']
873
+ read_only_fields = ['id', 'author', 'created_at']
874
+
875
+ URL pattern (apps/[app]/urls.py):
876
+ router = DefaultRouter()
877
+ router.register(r'articles', ArticleViewSet, basename='article')
878
+ urlpatterns = router.urls
879
+
880
+ Auth endpoints (add to config/urls.py):
881
+ path('api/auth/token/', TokenObtainPairView.as_view()),
882
+ path('api/auth/token/refresh/', TokenRefreshView.as_view()),
883
+ path('api/auth/register/', RegisterView.as_view()),
884
+
885
+ GET /api/health/:
886
+ path('api/health/', lambda req: JsonResponse({'status': 'ok'})),
887
+
888
+ requirements.txt:
889
+ django>=5.0, djangorestframework, djangorestframework-simplejwt,
890
+ django-cors-headers, django-environ, psycopg2-binary, pillow
891
+
892
+ Coding rules:
893
+ - Custom User model from day 1 (AbstractUser) — impossible to change after first migration
894
+ - DRF ViewSets + DefaultRouter for standard CRUD — class-based views always
895
+ - JWT via simplejwt — never session auth for API endpoints
896
+ - All settings from environment variables via django-environ
897
+ - API versioned at /api/v1/ using namespace in urls.py
898
+ - select_related / prefetch_related in every queryset — never N+1
899
+ - Override get_queryset() to filter by request.user — never trust URL params for ownership"""
900
+
901
+ if tech == "Node.js + Express":
902
+ return """
903
+ BACKEND: Node.js + Express
904
+ ══════════════════════════
905
+ Folder structure (backend/):
906
+ src/
907
+ config/
908
+ db.js ← DB connection (mongoose.connect or Prisma client init)
909
+ env.js ← zod or joi schema validating all required env vars on startup
910
+ middleware/
911
+ auth.middleware.js ← verifyToken(req, res, next): checks Authorization header, attaches req.user
912
+ error.middleware.js ← (err, req, res, next): global error handler — LAST app.use()
913
+ validate.middleware.js ← validate(schema)(req, res, next): validates req.body with zod/joi
914
+ rateLimiter.js ← express-rate-limit configuration
915
+ routes/
916
+ index.js ← mount all routers: router.use('/auth', authRoutes); router.use('/...', ...)
917
+ auth.routes.js ← router.post('/register', validate(registerSchema), authController.register)
918
+ [resource].routes.js ← router.use(authMiddleware) for protected routes
919
+ controllers/
920
+ auth.controller.js
921
+ ← export const register = async (req, res, next) => { try { ... } catch (e) { next(e) } }
922
+ ← export const login = async (req, res, next) => { try { ... } catch (e) { next(e) } }
923
+ [resource].controller.js
924
+ models/
925
+ User.js ← Mongoose Schema / Prisma model definition
926
+ [Resource].js
927
+ services/
928
+ auth.service.js ← registerUser(data), loginUser(email, pw), refreshTokens(token)
929
+ [resource].service.js ← ALL business logic lives here, not in controllers
930
+ utils/
931
+ jwt.js ← signAccess(payload), signRefresh(payload), verifyToken(token)
932
+ hash.js ← hashPassword(pw), comparePassword(pw, hash)
933
+ apiResponse.js ← success(res, data, code=200), error(res, msg, code=400)
934
+ AppError.js ← class AppError extends Error { constructor(message, statusCode) }
935
+ app.js ← express setup, middleware, routes mounting
936
+ server.js ← app.listen(PORT) ONLY
937
+ package.json
938
+ .env.example
939
+
940
+ app.js (complete — copy this structure):
941
+ import express from 'express';
942
+ import cors from 'cors';
943
+ import { routes } from './routes/index.js';
944
+ import { errorMiddleware } from './middleware/error.middleware.js';
945
+ const app = express();
946
+ app.use(cors({ origin: process.env.CORS_ORIGIN, credentials: true }));
947
+ app.use(express.json({ limit: '10mb' }));
948
+ app.use(express.urlencoded({ extended: true }));
949
+ app.get('/api/health', (_, res) => res.json({ status: 'ok', timestamp: new Date().toISOString() }));
950
+ app.use('/api', routes);
951
+ app.use(errorMiddleware); // MUST be the last middleware
952
+ export default app;
953
+
954
+ Controller pattern (copy exactly):
955
+ export const register = async (req, res, next) => {
956
+ try {
957
+ const user = await authService.registerUser(req.body);
958
+ return success(res, user, 201);
959
+ } catch (err) { next(err); }
960
+ };
961
+
962
+ Global error middleware (copy exactly):
963
+ export const errorMiddleware = (err, req, res, next) => {
964
+ const status = err.statusCode || 500;
965
+ const message = err.message || 'Internal Server Error';
966
+ res.status(status).json({ data: null, error: message });
967
+ };
968
+
969
+ JWT pattern (utils/jwt.js):
970
+ import jwt from 'jsonwebtoken';
971
+ export const signAccess = (payload) => jwt.sign(payload, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '15m' });
972
+ export const signRefresh = (payload) => jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET, { expiresIn: '7d' });
973
+ export const verifyToken = (token, secret) => jwt.verify(token, secret);
974
+
975
+ package.json dependencies:
976
+ express, jsonwebtoken, bcryptjs, cors, dotenv, mongoose OR @prisma/client,
977
+ zod, express-rate-limit, morgan
978
+ devDeps: nodemon, jest / vitest
979
+
980
+ Coding rules:
981
+ - NEVER put business logic in controllers — controllers only call services and return responses
982
+ - errorMiddleware is the LAST app.use() in app.js — nothing after it
983
+ - async/await everywhere — zero callbacks
984
+ - Validate request BEFORE controller via validate middleware
985
+ - Refresh tokens stored in DB as SHA-256 hash — never the raw token
986
+ - CORS_ORIGIN from env — never '*' in production
987
+ - AppError class for all operational errors (wrong password, not found, etc.)
988
+ - server.js only has: import app; app.listen(PORT, cb) — nothing else"""
989
+
990
+ if tech == "FastAPI (Python)":
991
+ return """
992
+ BACKEND: FastAPI (Python)
993
+ ═════════════════════════
994
+ Folder structure (backend/):
995
+ app/
996
+ main.py ← FastAPI() instance, lifespan, middleware, include_router
997
+ config.py ← class Settings(BaseSettings): reads from .env automatically
998
+ database.py ← engine, SessionLocal, Base, get_db Depends
999
+ models/
1000
+ user.py ← class User(Base): __tablename__, columns, relationships
1001
+ [resource].py
1002
+ schemas/
1003
+ user.py ← UserCreate(BaseModel), UserUpdate, UserResponse, Token
1004
+ [resource].py ← [Resource]Create, [Resource]Update, [Resource]Response
1005
+ routers/
1006
+ auth.py ← router = APIRouter(prefix='/api/auth', tags=['auth'])
1007
+ [resource].py ← router = APIRouter(prefix='/api/[resource]', tags=['[resource]'])
1008
+ dependencies/
1009
+ auth.py ← get_current_user(token: str = Depends(oauth2_scheme), db = Depends(get_db))
1010
+ services/
1011
+ auth_service.py ← async register_user(db, user_in), authenticate_user(db, email, pw)
1012
+ [resource]_service.py
1013
+ utils/
1014
+ security.py ← hash_password(pw), verify_password(pw, hash), create_access_token(data)
1015
+ alembic/
1016
+ versions/
1017
+ env.py
1018
+ requirements.txt
1019
+ .env.example
1020
+ alembic.ini
1021
+
1022
+ app/main.py (complete):
1023
+ from fastapi import FastAPI
1024
+ from fastapi.middleware.cors import CORSMiddleware
1025
+ from contextlib import asynccontextmanager
1026
+ from .config import settings
1027
+ from .database import engine, Base
1028
+ from .routers import auth, [resource]
1029
+ @asynccontextmanager
1030
+ async def lifespan(app: FastAPI):
1031
+ Base.metadata.create_all(bind=engine) # replace with alembic in prod
1032
+ yield
1033
+ app = FastAPI(title=settings.APP_NAME, version='1.0.0', lifespan=lifespan)
1034
+ app.add_middleware(CORSMiddleware,
1035
+ allow_origins=settings.CORS_ORIGINS, allow_credentials=True,
1036
+ allow_methods=['*'], allow_headers=['*'])
1037
+ app.include_router(auth.router)
1038
+ app.include_router([resource].router, dependencies=[Depends(get_current_user)])
1039
+ @app.get('/api/health')
1040
+ async def health(): return {'status': 'ok'}
1041
+
1042
+ app/config.py:
1043
+ from pydantic_settings import BaseSettings
1044
+ from typing import list
1045
+ class Settings(BaseSettings):
1046
+ APP_NAME: str = 'My App'
1047
+ DATABASE_URL: str
1048
+ SECRET_KEY: str
1049
+ ACCESS_TOKEN_EXPIRE_MINUTES: int = 15
1050
+ CORS_ORIGINS: list[str] = ['http://localhost:5173']
1051
+ class Config: env_file = '.env'
1052
+ settings = Settings()
1053
+
1054
+ Router + schema pattern (copy exactly):
1055
+ @router.post('/register', response_model=UserResponse, status_code=201)
1056
+ async def register(user_in: UserCreate, db: Session = Depends(get_db)):
1057
+ existing = db.query(User).filter(User.email == user_in.email).first()
1058
+ if existing: raise HTTPException(status_code=400, detail='Email already registered')
1059
+ return await auth_service.register_user(db, user_in)
1060
+
1061
+ @router.post('/login', response_model=Token)
1062
+ async def login(form: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
1063
+ user = await auth_service.authenticate_user(db, form.username, form.password)
1064
+ if not user: raise HTTPException(status_code=401, detail='Invalid credentials')
1065
+ token = create_access_token({'sub': str(user.id)})
1066
+ return {'access_token': token, 'token_type': 'bearer'}
1067
+
1068
+ dependencies/auth.py:
1069
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl='/api/auth/login')
1070
+ async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
1071
+ try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
1072
+ except JWTError: raise HTTPException(401, 'Could not validate credentials')
1073
+ user = db.query(User).filter(User.id == payload.get('sub')).first()
1074
+ if not user: raise HTTPException(401, 'User not found')
1075
+ return user
1076
+
1077
+ requirements.txt:
1078
+ fastapi>=0.110, uvicorn[standard], sqlalchemy>=2.0, alembic,
1079
+ pydantic-settings, python-jose[cryptography], passlib[bcrypt], psycopg2-binary, python-dotenv
1080
+
1081
+ Coding rules:
1082
+ - Type EVERYTHING — Pydantic models for all request/response bodies, never dict
1083
+ - Separate ORM models (models/) from Pydantic schemas (schemas/) — never reuse between layers
1084
+ - Depends() for every injected dependency: DB session, current user, settings
1085
+ - pydantic-settings reads .env automatically via env_file = '.env' in Config
1086
+ - HTTPException with status_code + detail for all error responses
1087
+ - Alembic for migrations in production — db.create_all() only in development
1088
+ - Auto-generated /docs (Swagger UI) — document it in README, it's a feature"""
1089
+
1090
+ # Go + Gin
1091
+ return """
1092
+ BACKEND: Go + Gin
1093
+ ═════════════════
1094
+ Folder structure (backend/):
1095
+ cmd/server/
1096
+ main.go ← entry point: load config, init DB, wire dependencies, start server
1097
+ internal/
1098
+ config/
1099
+ config.go ← type Config struct; func Load() *Config — reads env via viper/godotenv
1100
+ db/
1101
+ db.go ← func Connect(cfg *Config) *gorm.DB — opens DB, auto-migrate
1102
+ middleware/
1103
+ auth.go ← JWT verification middleware: reads Authorization header, sets userID in context
1104
+ cors.go ← gin-contrib/cors setup with allowed origins from config
1105
+ logger.go ← request/response logging middleware
1106
+ handlers/
1107
+ auth.go ← Register, Login, RefreshToken, Me — each returns gin.HandlerFunc
1108
+ [resource].go ← List, Get, Create, Update, Delete
1109
+ models/
1110
+ user.go ← type User struct { gorm.Model; Email string `gorm:"uniqueIndex"; ... }
1111
+ [resource].go
1112
+ repository/
1113
+ interfaces.go ← type UserRepository interface { FindByEmail, Create, FindByID, ... }
1114
+ user_repo.go ← type userRepo struct { db *gorm.DB }; implements UserRepository
1115
+ [resource]_repo.go
1116
+ services/
1117
+ auth_service.go ← type AuthService interface; type authService struct { repo, cfg }
1118
+ [resource]_service.go
1119
+ router/
1120
+ router.go ← func NewRouter(deps *Deps) *gin.Engine — mounts all routes
1121
+ pkg/
1122
+ jwt/
1123
+ jwt.go ← GenerateAccessToken, GenerateRefreshToken, ValidateToken
1124
+ hash/
1125
+ hash.go ← HashPassword, CheckPasswordHash (bcrypt)
1126
+ response/
1127
+ response.go ← Success(c, data, code), Error(c, msg, code), Paginated(c, data, total)
1128
+ go.mod
1129
+ .env.example
1130
+ Makefile
1131
+
1132
+ cmd/server/main.go:
1133
+ func main() {
1134
+ cfg := config.Load()
1135
+ db := db.Connect(cfg)
1136
+ userRepo := repository.NewUserRepo(db)
1137
+ authService := services.NewAuthService(userRepo, cfg)
1138
+ r := router.NewRouter(&router.Deps{ AuthService: authService, Config: cfg })
1139
+ log.Printf("Server starting on :%s", cfg.Port)
1140
+ r.Run(":" + cfg.Port)
1141
+ }
1142
+
1143
+ router/router.go:
1144
+ func NewRouter(deps *Deps) *gin.Engine {
1145
+ r := gin.New()
1146
+ r.Use(gin.Recovery(), middleware.Logger(), middleware.CORS(deps.Config))
1147
+ api := r.Group("/api")
1148
+ api.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"status": "ok"}) })
1149
+ auth := api.Group("/auth")
1150
+ {
1151
+ auth.POST("/register", handlers.Register(deps.AuthService))
1152
+ auth.POST("/login", handlers.Login(deps.AuthService))
1153
+ auth.POST("/refresh", handlers.RefreshToken(deps.AuthService))
1154
+ }
1155
+ protected := api.Group("/")
1156
+ protected.Use(middleware.AuthRequired(deps.Config.JWTSecret))
1157
+ {
1158
+ protected.GET("/auth/me", handlers.Me(deps.AuthService))
1159
+ [resource] := protected.Group("/[resource]")
1160
+ [resource].GET("", handlers.List[Resource](deps.[Resource]Service))
1161
+ [resource].POST("", handlers.Create[Resource](deps.[Resource]Service))
1162
+ [resource].GET("/:id", handlers.Get[Resource](deps.[Resource]Service))
1163
+ [resource].PUT("/:id", handlers.Update[Resource](deps.[Resource]Service))
1164
+ }
1165
+ return r
1166
+ }
1167
+
1168
+ Handler pattern (copy exactly):
1169
+ func Register(svc services.AuthService) gin.HandlerFunc {
1170
+ return func(c *gin.Context) {
1171
+ var req RegisterRequest
1172
+ if err := c.ShouldBindJSON(&req); err != nil {
1173
+ response.Error(c, err.Error(), http.StatusBadRequest); return
1174
+ }
1175
+ user, err := svc.Register(c.Request.Context(), req)
1176
+ if err != nil {
1177
+ response.Error(c, err.Error(), http.StatusBadRequest); return
1178
+ }
1179
+ response.Success(c, user, http.StatusCreated)
1180
+ }
1181
+ }
1182
+
1183
+ go.mod dependencies:
1184
+ github.com/gin-gonic/gin, gorm.io/gorm, gorm.io/driver/postgres,
1185
+ github.com/golang-jwt/jwt/v5, golang.org/x/crypto, github.com/spf13/viper,
1186
+ github.com/gin-contrib/cors
1187
+
1188
+ Coding rules:
1189
+ - Dependency injection via constructor functions (NewAuthService, NewUserRepo) — zero global state
1190
+ - Repository interface defined alongside service, implemented in repository/ — swap DB without changing service
1191
+ - Error wrapping: return fmt.Errorf("authService.Register: %w", err)
1192
+ - Handlers are closures that take a service — never directly access DB in handlers
1193
+ - All config from env vars via viper — never hardcode ports, secrets, DB strings
1194
+ - Table-driven tests for services and handlers"""
1195
+
1196
+
1197
+ def _integration(fe: str, be: str) -> str:
1198
+ """Frontend ↔ Backend connectivity rules for any tech combo."""
1199
+ if be == "None (static / frontend only)":
1200
+ return """
1201
+ INTEGRATION: Static / Frontend Only
1202
+ ═════════════════════════════════════
1203
+ No backend folder is needed for this project.
1204
+ - If a contact form is needed, use a third-party service: Formspree or EmailJS.
1205
+ - If data display is needed, use a public API or mock JSON files in assets/.
1206
+ - Document in README: "This is a static frontend — no server required."
1207
+ - Deploy to: Vercel, Netlify, or GitHub Pages."""
1208
+
1209
+ fe_port = _FE_PORTS.get(fe, 5173)
1210
+ be_port = _BE_PORTS.get(be, 5000)
1211
+
1212
+ # Proxy config per frontend
1213
+ if fe in ("React + Vite", "Vue 3 + Vite", "Svelte + Vite"):
1214
+ proxy_block = f"""vite.config.js dev proxy:
1215
+ server: {{
1216
+ port: {fe_port},
1217
+ proxy: {{ '/api': {{ target: 'http://localhost:{be_port}', changeOrigin: true }} }}
1218
+ }}"""
1219
+ elif fe == "Next.js 14":
1220
+ proxy_block = f"""next.config.js rewrites (proxy in development):
1221
+ async rewrites() {{
1222
+ return [{{ source: '/api/:path*', destination: 'http://localhost:{be_port}/api/:path*' }}];
1223
+ }}"""
1224
+ elif fe == "Angular 17":
1225
+ proxy_block = f"""proxy.conf.json:
1226
+ {{ "/api": {{ "target": "http://localhost:{be_port}", "secure": false, "changeOrigin": true }} }}
1227
+ angular.json → architect.serve.options: {{ "proxyConfig": "proxy.conf.json" }}"""
1228
+ else: # Vanilla
1229
+ proxy_block = f"""Vanilla JS: No dev proxy. Use the full backend URL directly.
1230
+ In js/config.js: export const CONFIG = {{ API_BASE: 'http://localhost:{be_port}' }};
1231
+ In production, update CONFIG.API_BASE to your deployed backend URL."""
1232
+
1233
+ # CORS config per backend
1234
+ if be == "Flask (Python)":
1235
+ cors_block = f"CORS(app, resources={{r'/api/*': {{origins: [\"http://localhost:{fe_port}\"]}}}})"
1236
+ elif be == "Django + DRF (Python)":
1237
+ cors_block = f"CORS_ALLOWED_ORIGINS = ['http://localhost:{fe_port}']"
1238
+ elif be == "Node.js + Express":
1239
+ cors_block = f"cors({{ origin: process.env.CORS_ORIGIN }}) # CORS_ORIGIN=http://localhost:{fe_port}"
1240
+ elif be == "FastAPI (Python)":
1241
+ cors_block = f"CORSMiddleware(allow_origins=['http://localhost:{fe_port}'])"
1242
+ else: # Go + Gin
1243
+ cors_block = f"cors.Config{{AllowOrigins: []string{{\"http://localhost:{fe_port}\"}}}})"
1244
+
1245
+ return f"""
1246
+ INTEGRATION: {fe} ↔ {be}
1247
+ {'═' * (len(fe) + len(be) + 4)}
1248
+ Ports:
1249
+ Frontend: http://localhost:{fe_port}
1250
+ Backend: http://localhost:{be_port}
1251
+
1252
+ Step 1 — Backend CORS (must allow the frontend origin):
1253
+ {cors_block}
1254
+ In production: set CORS origin from environment variable — never hardcode localhost.
1255
+
1256
+ Step 2 — Dev proxy (eliminates CORS errors during development):
1257
+ {proxy_block}
1258
+
1259
+ Step 3 — API health check (always implement this first):
1260
+ Backend: GET /api/health → 200 {{"status": "ok", "timestamp": "..."}}
1261
+ Frontend: On app init, call GET /api/health to verify connectivity.
1262
+
1263
+ Step 4 — Consistent API response envelope (both sides must agree on this shape):
1264
+ Success: {{ "data": <payload>, "error": null }}
1265
+ Failure: {{ "data": null, "error": "<human readable message>" }}
1266
+ HTTP status codes must match: 200 OK, 201 Created, 400 Bad Request, 401 Unauthorized, 404 Not Found, 500 Server Error.
1267
+
1268
+ Step 5 — Authentication flow (JWT):
1269
+ 1. POST /api/auth/login → returns {{ access_token, refresh_token, user }}
1270
+ 2. Frontend stores access_token in localStorage; stores refresh_token in httpOnly cookie or localStorage.
1271
+ 3. Every subsequent request: Authorization: Bearer <access_token>
1272
+ 4. On 401: try POST /api/auth/refresh → new access_token; on failure, redirect to login.
1273
+ 5. Frontend api.js / api.service.js / ApiService handles this automatically in interceptors.
1274
+
1275
+ Step 6 — Environment variables (never hardcode URLs):
1276
+ frontend/.env: VITE_API_URL=http://localhost:{be_port} (or NEXT_PUBLIC_API_URL for Next.js)
1277
+ backend/.env: PORT={be_port}, DATABASE_URL=..., SECRET_KEY=..., CORS_ORIGINS=http://localhost:{fe_port}
1278
+ Both: provide .env.example with every variable documented.
1279
+
1280
+ Step 7 — Folder structure at project root:
1281
+ project-root/
1282
+ frontend/ ← {fe} code
1283
+ backend/ ← {be} code
1284
+ README.md ← complete setup instructions (both sides)
1285
+
1286
+ README.md must include:
1287
+ ## Prerequisites
1288
+ [list all required tools with version numbers]
1289
+
1290
+ ## Setup & Run
1291
+
1292
+ ### Backend
1293
+ cd backend
1294
+ [install command] # pip install -r requirements.txt OR npm install OR go mod download
1295
+ [setup command] # flask db upgrade OR python manage.py migrate OR npx prisma migrate dev
1296
+ [run command] # flask run OR uvicorn app.main:app --reload OR npm run dev OR go run ./cmd/server
1297
+
1298
+ ### Frontend
1299
+ cd frontend
1300
+ [install command] # npm install
1301
+ [run command] # npm run dev
1302
+
1303
+ Both must run simultaneously. Open http://localhost:{fe_port} in your browser."""
1304
+
1305
+
1306
+ # ── Prompt builders ───────────────────────────────────────────────────────────
1307
+
1308
+ def _build_newsite(answers: dict) -> str:
1309
+ name = answers.get("name", "Website")
1310
+ site_type = answers.get("type", "website")
1311
+ desc = answers.get("desc", "")
1312
+ fe = answers.get("frontend", "React + Vite")
1313
+ be = answers.get("backend", "None (static / frontend only)")
1314
+ sections = answers.get("sections", "")
1315
+ features = answers.get("features", "")
1316
+ theme = answers.get("theme", "")
1317
+
1318
+ req_lines = [f'Build a {site_type} called "{name}".']
1319
+ if desc: req_lines.append(f"Description: {desc}")
1320
+ if theme: req_lines.append(f"Visual theme: {theme}")
1321
+ if sections: req_lines.append(f"Sections: {sections}")
1322
+ if features: req_lines.append(f"Features: {features}")
1323
+ requirements = "\n".join(req_lines)
1324
+
1325
+ be_str = be.replace("None (static / frontend only)", "None")
1326
+
1327
+ return f"""## PROJECT: {name}
1328
+ ## TYPE: {site_type}
1329
+ {requirements}
1330
+
1331
+ ## TECH STACK
1332
+ Frontend: {fe}
1333
+ Backend: {be_str}
1334
+
1335
+ ## STEP 1 — THINK BEFORE WRITING ANY CODE
1336
+ Understand what this site is for, who uses it, and what impression it should make.
1337
+ Design a color palette, typography, and layout that genuinely fit the project.
1338
+ Plan every section, every page, every API endpoint before writing a single file.
1339
+
1340
+ ## STEP 2 — FOLDER STRUCTURE (non-negotiable)
1341
+ All code goes in TWO top-level folders:
1342
+ frontend/ ← all frontend code
1343
+ backend/ ← all backend code (skip if backend is None)
1344
+
1345
+ {_fe_detail(fe)}
1346
+
1347
+ {_be_detail(be) if "None" not in be else ""}
1348
+
1349
+ {_integration(fe, be)}
1350
+
1351
+ ## STEP 3 — DESIGN SYSTEM (use these — do NOT use grey defaults)
1352
+
1353
+ Pick one palette and use it consistently everywhere:
1354
+
1355
+ Option A — Dark (recommended for portfolios, SaaS, agency):
1356
+ :root {{
1357
+ --bg: #0f172a; --surface: #1e293b; --border: #334155;
1358
+ --primary: #6366f1; --primary-hover: #4f46e5; --accent: #06b6d4;
1359
+ --text: #f1f5f9; --text-muted: #94a3b8;
1360
+ --radius: 10px; --shadow: 0 4px 24px rgba(0,0,0,0.3);
1361
+ }}
1362
+
1363
+ Option B — Light (recommended for e-commerce, blog, corporate):
1364
+ :root {{
1365
+ --bg: #f8fafc; --surface: #ffffff; --border: #e2e8f0;
1366
+ --primary: #6366f1; --primary-hover: #4f46e5; --accent: #0ea5e9;
1367
+ --text: #0f172a; --text-muted: #64748b;
1368
+ --radius: 10px; --shadow: 0 2px 16px rgba(0,0,0,0.08);
1369
+ }}
1370
+
1371
+ Option C — Bold (recommended for landing pages, product showcase):
1372
+ :root {{
1373
+ --bg: #09090b; --surface: #18181b; --border: #27272a;
1374
+ --primary: #f97316; --primary-hover: #ea580c; --accent: #facc15;
1375
+ --text: #fafafa; --text-muted: #a1a1aa;
1376
+ --radius: 8px; --shadow: 0 4px 32px rgba(0,0,0,0.4);
1377
+ }}
1378
+
1379
+ Required component styles in every project:
1380
+ .btn-primary {{ background: var(--primary); color: #fff; padding: 12px 24px; border-radius: var(--radius); font-weight: 600; border: none; cursor: pointer; }}
1381
+ .btn-primary:hover {{ background: var(--primary-hover); transform: translateY(-1px); }}
1382
+ .card {{ background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 28px; }}
1383
+ .section {{ padding: 80px 0; }}
1384
+ .container {{ max-width: 1200px; margin: 0 auto; padding: 0 24px; }}
1385
+
1386
+ Design rules (non-negotiable):
1387
+ - HTML: REAL content — real headings, real body text, real copy. Zero lorem ipsum anywhere.
1388
+ - CSS: real pixel/rem values from the palette above. Every element styled. Not a wireframe.
1389
+ - JS: real working event listeners, real DOM manipulation, real logic.
1390
+ - Mobile-first: works at 375px. Hamburger menu for mobile nav.
1391
+ - Every section polished and complete — hero, cards, forms, footer.
1392
+
1393
+ ## STEP 4 — WRITE EVERY FILE using <<<FILE:absolute/path>>> marker
1394
+ Write every planned file completely. Do not skip any file.
1395
+ Write CSS files before JS files. Write every section's CSS.
1396
+ After all files, list what was created.
1397
+ End with: what you built, design decisions, and exact commands to run it."""
1398
+
1399
+
1400
+ def _build_newapp(answers: dict) -> str:
1401
+ name = answers.get("name", "App")
1402
+ desc = answers.get("desc", "")
1403
+ fe = answers.get("frontend", "React + Vite")
1404
+ be = answers.get("backend", "Flask (Python)")
1405
+ database = answers.get("database", "PostgreSQL")
1406
+ auth = answers.get("auth", "JWT (email / password)")
1407
+ features = answers.get("features", "")
1408
+ extras = answers.get("extras", "")
1409
+
1410
+ lines = [f'Build an app called "{name}".']
1411
+ if desc: lines.append(f"What it does: {desc}")
1412
+ if features: lines.append(f"Core features: {features}")
1413
+ if extras: lines.append(f"Extra integrations: {extras}")
1414
+ if database: lines.append(f"Database: {database}")
1415
+ if auth: lines.append(f"Authentication: {auth}")
1416
+ requirements = "\n".join(lines)
1417
+
1418
+ return f"""## PROJECT: {name}
1419
+ {requirements}
1420
+
1421
+ ## TECH STACK
1422
+ Frontend: {fe}
1423
+ Backend: {be}
1424
+
1425
+ ## STEP 1 — ANALYZE AND DESIGN BEFORE ANY CODE
1426
+ - What problem does this app solve? Who are the users?
1427
+ - What data entities exist? List every model and its fields.
1428
+ - What user roles exist? What can each role do?
1429
+ - What are ALL the pages / routes / screens?
1430
+ - What API endpoints does the backend need?
1431
+ Draw the full architecture in your mind before writing file 1.
1432
+
1433
+ ## STEP 1.5 — WRITE API_CONTRACT.md FIRST (the very first file, before ANY code)
1434
+ Write <<<FILE:API_CONTRACT.md>>> in the project root containing:
1435
+ - Every endpoint: METHOD /api/path → request JSON (exact keys + types) →
1436
+ response JSON (exact keys + types) → status codes (200/201/400/401/404)
1437
+ - Ports: backend {_BE_PORTS.get(be, 5000)}, frontend {_FE_PORTS.get(fe, 5173)}
1438
+ - Env var names BOTH sides use (VITE_API_URL, DATABASE_URL, JWT_SECRET, ...)
1439
+ - Auth: header format (Authorization: Bearer <token>), token lifetimes
1440
+
1441
+ This contract is LAW for the rest of the build:
1442
+ - Backend routes implement it EXACTLY — same paths, same JSON keys
1443
+ - Frontend services/api.js calls it EXACTLY — never invent an endpoint not in it
1444
+ - If the design must change mid-build: edit API_CONTRACT.md FIRST, then update
1445
+ BOTH sides to match. The contract and the code must never disagree.
1446
+ Frontend↔backend disconnection is the #1 way full-stack builds fail — the
1447
+ contract is what prevents it. Never skip this step.
1448
+
1449
+ ## STEP 2 — FOLDER STRUCTURE (non-negotiable)
1450
+ All code in two top-level folders:
1451
+ frontend/ ← {fe}
1452
+ backend/ ← {be}
1453
+
1454
+ {_fe_detail(fe)}
1455
+
1456
+ {_be_detail(be)}
1457
+
1458
+ {_integration(fe, be)}
1459
+
1460
+ ## STEP 3 — DATA MODELS & AUTH
1461
+ Database: {database}
1462
+ Auth: {auth}
1463
+
1464
+ Every model must have:
1465
+ - Proper field types, constraints, indexes
1466
+ - Created_at / updated_at timestamps
1467
+ - Relationships defined correctly (FK, many-to-many)
1468
+
1469
+ Auth implementation:
1470
+ - Passwords: bcrypt (cost 12) — NEVER MD5, SHA1, or plain text
1471
+ - JWT: access token (15 min) + refresh token (7 days)
1472
+ - Refresh tokens stored as SHA-256 hash in DB — never the raw token
1473
+ - Protected routes: require valid access token in Authorization: Bearer header
1474
+
1475
+ ## STEP 4 — DESIGN SYSTEM (use this exact palette — do NOT use grey defaults)
1476
+
1477
+ Dark theme CSS custom properties (put in index.css or variables.css):
1478
+ :root {{
1479
+ --bg: #0f172a; /* deep navy page background */
1480
+ --surface: #1e293b; /* card / panel background */
1481
+ --surface-2: #263348; /* elevated surface, hover */
1482
+ --border: #334155; /* subtle dividers */
1483
+ --primary: #6366f1; /* indigo — buttons, links */
1484
+ --primary-hover:#4f46e5; /* darker on hover */
1485
+ --accent: #06b6d4; /* cyan — highlights, badges */
1486
+ --success: #10b981; /* green — success states */
1487
+ --error: #ef4444; /* red — errors, destructive */
1488
+ --warning: #f59e0b; /* amber — warnings */
1489
+ --text: #f1f5f9; /* primary text */
1490
+ --text-muted: #94a3b8; /* secondary text, labels */
1491
+ --radius: 10px; /* border radius */
1492
+ --shadow: 0 4px 24px rgba(0,0,0,0.3);
1493
+ }}
1494
+
1495
+ Component patterns (apply consistently):
1496
+ Buttons: background: var(--primary); border-radius: var(--radius); padding: 10px 20px; font-weight: 600;
1497
+ Cards: background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 24px;
1498
+ Inputs: background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 10px 14px; color: var(--text);
1499
+ Navbar: background: var(--surface); border-bottom: 1px solid var(--border); height: 60px;
1500
+ Sidebar: background: var(--surface); border-right: 1px solid var(--border); width: 240px;
1501
+ Tables: border-collapse: collapse; th background: var(--surface-2); td padding: 12px 16px; border-bottom: 1px solid var(--border);
1502
+
1503
+ Mobile-first: every layout must work at 375px width. Use CSS Grid and Flexbox.
1504
+ Responsive breakpoints: 640px (sm), 768px (md), 1024px (lg).
1505
+ Typography: system-ui font stack. Headings 700 weight. Body 400. Labels 500.
1506
+
1507
+ ## STEP 5 — WRITE EVERY FILE using <<<FILE:absolute/path>>> marker
1508
+
1509
+ BUILD IN THIS ORDER (strictly — do not skip ahead):
1510
+
1511
+ Phase 0 — Contract:
1512
+ 0. API_CONTRACT.md (from STEP 1.5 — the very first file)
1513
+
1514
+ Phase 1 — Backend:
1515
+ 1. backend/.env.example
1516
+ 2. backend/ config + models + database setup
1517
+ 3. backend/ auth routes (register, login, /api/health)
1518
+ 4. backend/ all other routes and services
1519
+ 5. backend/ requirements.txt / package.json / go.mod
1520
+
1521
+ Phase 2 — Frontend:
1522
+ 6. frontend/.env.example and vite.config.js (with proxy to backend)
1523
+ 7. frontend/ services/api.js (axios with interceptors, uses VITE_API_URL)
1524
+ 8. frontend/ auth pages (Login, Register)
1525
+ 9. frontend/ all other pages and components
1526
+ 10. frontend/ package.json
1527
+
1528
+ Phase 3 — Documentation:
1529
+ 11. README.md with exact setup + run commands for BOTH sides
1530
+
1531
+ Writing rules:
1532
+ - Use <<<FILE:absolute/path>>> for EVERY file — no exceptions
1533
+ - Every file is complete — zero TODOs, zero stubs, zero "add your logic here"
1534
+ - Never write the same file twice — write it right the first time
1535
+
1536
+ ## STEP 6 — RUNTIME VERIFICATION (actually RUN it — never just claim it works)
1537
+ Run these for real with bash. Fix every error you hit before moving on:
1538
+
1539
+ 1. bash("cd " + backend folder + " && pip install -r requirements.txt", timeout=300)
1540
+ — a package-name error here means requirements.txt is wrong: fix it now
1541
+ 2. bash("cd " + backend folder + " && python app.py", run_in_background=true)
1542
+ → returns process_id (e.g. 'proc-1')
1543
+ 3. process_output(process_id="proc-1", wait_seconds=4)
1544
+ — any traceback in the boot log = fix the bug, restart, recheck
1545
+ 4. web_fetch("http://localhost:{_BE_PORTS.get(be, 5000)}/api/health")
1546
+ — must return the ok status. If connection refused: wrong port or app crashed.
1547
+ 5. bash("cd " + frontend folder + " && npm install", timeout=600)
1548
+ 6. bash("cd " + frontend folder + " && npm run build", timeout=300)
1549
+ — the production build catches missing imports, bad paths, and broken JSX
1550
+ without needing a browser. Fix EVERY build error.
1551
+ 7. process_kill(process_id="proc-1") — always stop the backend when done.
1552
+
1553
+ Only after all 7 pass, confirm the static checklist below:
1554
+
1555
+ [ ] API_CONTRACT.md exists and every frontend API call matches a backend route
1556
+ in it — same path, same method, same JSON keys (grep the frontend for
1557
+ api. / fetch( / axios and cross-check each one)
1558
+ [ ] GET /api/health returns 200 {{"status": "ok"}}
1559
+ [ ] Backend CORS configured for http://localhost:{_FE_PORTS.get(fe, 5173)}
1560
+ [ ] vite.config.js proxy: /api → http://localhost:{_BE_PORTS.get(be, 5000)}
1561
+ [ ] frontend/.env: VITE_API_URL=http://localhost:{_BE_PORTS.get(be, 5000)}
1562
+ [ ] services/api.js uses import.meta.env.VITE_API_URL — no hardcoded URLs in code
1563
+ [ ] All imports in every file match the actual file paths created
1564
+ [ ] requirements.txt / package.json has every package the code imports
1565
+ [ ] No file contains TODO, placeholder, stub, or "implement this"
1566
+ [ ] README has working copy-paste commands for backend AND frontend setup
1567
+
1568
+ If any item is false, fix it before finishing."""
1569
+
1570
+
1571
+ def _build_docker(answers: dict) -> str:
1572
+ database = answers.get("database", "none")
1573
+ extras = answers.get("extras", "none")
1574
+ multistage = answers.get("multistage", "yes")
1575
+
1576
+ services = []
1577
+ if database != "none": services.append(database)
1578
+ if "Redis" in (extras or ""): services.append("Redis")
1579
+ if "Nginx" in (extras or ""): services.append("Nginx")
1580
+ if "Celery" in (extras or ""): services.append("Celery worker")
1581
+
1582
+ return f"""## DOCKER REQUIREMENTS
1583
+ Database: {database}
1584
+ Extra services: {extras}
1585
+ Multi-stage build: {multistage}
1586
+ Services in compose: app{', ' + ', '.join(services) if services else ''}
1587
+
1588
+ ## WHAT TO BUILD
1589
+
1590
+ 1. Read the project fully — understand language, framework, start command, and port.
1591
+
1592
+ 2. Dockerfile:
1593
+ {" Multi-stage: builder stage (install + compile) → runtime stage (copy artifacts only)." if multistage == "yes" else " Single-stage Dockerfile."}
1594
+ - Slim base image: python:3.11-slim, node:18-alpine, golang:1.21-alpine, etc.
1595
+ - Non-root user: RUN useradd -r appuser; USER appuser
1596
+ - Layer caching: COPY dependency files first, install, then COPY source code
1597
+ - EXPOSE correct port
1598
+ - HEALTHCHECK: CMD curl -f http://localhost:PORT/health || exit 1
1599
+ - CMD: exec form only: ["gunicorn", ...] not shell form
1600
+
1601
+ 3. .dockerignore: __pycache__, node_modules, .env, .git, *.pyc, dist, build, coverage, *.log
1602
+
1603
+ 4. docker-compose.yml:
1604
+ version: '3.9'
1605
+ - app: build: ., env_file: .env, depends_on with condition: service_healthy, restart: unless-stopped
1606
+ {f" - {database.lower()}: official image, named volume, healthcheck" if database != "none" else ""}
1607
+ {" - redis: redis:7-alpine, named volume" if "Redis" in (extras or "") else ""}
1608
+ {" - celery: same image as app, command: celery -A app.celery worker -l info" if "Celery" in (extras or "") else ""}
1609
+ {" - nginx: nginx:alpine, volumes for nginx.conf + static, ports: 80:80 443:443" if "Nginx" in (extras or "") else ""}
1610
+
1611
+ 5. .env.example: every variable with example values
1612
+ 6. Makefile: build, up, down, logs, shell, test
1613
+ 7. README: docker-compose up --build and verify app starts
1614
+
1615
+ Write every file completely."""
1616
+
1617
+
1618
+ def _build_ci(answers: dict) -> str:
1619
+ test_fw = answers.get("test_fw", "")
1620
+ deploy = answers.get("deploy", "none")
1621
+ auto_deploy = answers.get("auto_deploy", "no")
1622
+
1623
+ def deploy_step(d):
1624
+ m = {"EC2": "SSH pull + restart", "ECS": "ECR push → ECS deploy",
1625
+ "Cloud Run": "GCR push → gcloud run deploy",
1626
+ "Azure": "az webapp container set", "VPS": "SSH docker-compose up -d",
1627
+ "Heroku": "heroku container:push + release"}
1628
+ for k, v in m.items():
1629
+ if k in d: return v
1630
+ return "deploy to target"
1631
+
1632
+ def ci_secrets(d):
1633
+ m = {"EC2": "EC2_HOST, EC2_USER, EC2_SSH_KEY",
1634
+ "ECS": "AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, ECR_REPOSITORY, ECS_CLUSTER",
1635
+ "Cloud Run": "GCP_PROJECT_ID, GCP_SA_KEY",
1636
+ "Heroku": "HEROKU_API_KEY, HEROKU_APP_NAME",
1637
+ "VPS": "VPS_HOST, VPS_USER, VPS_SSH_KEY"}
1638
+ for k, v in m.items():
1639
+ if k in d: return f" - {v}"
1640
+ return " - No deployment secrets needed"
1641
+
1642
+ return f"""## CI/CD REQUIREMENTS
1643
+ Test framework: {test_fw or "detect from project"}
1644
+ Deployment: {deploy}
1645
+ Auto-deploy on main: {auto_deploy}
1646
+
1647
+ ## WHAT TO BUILD
1648
+
1649
+ 1. Read project to detect: language, test command, package manager, Dockerfile presence.
1650
+
1651
+ 2. .github/workflows/ci.yml:
1652
+ Triggers: push/PR to main and develop
1653
+ Jobs (sequential, each depends_on previous):
1654
+
1655
+ a. lint — checkout → setup → cache deps → install → lint
1656
+ Python: ruff / flake8. Node: eslint. Go: golangci-lint.
1657
+
1658
+ b. test — checkout → setup → cache → install → run tests with coverage
1659
+ {test_fw or "Detect and run test command"}
1660
+ Upload coverage artifact.
1661
+
1662
+ c. build — push to main only
1663
+ Build Docker image, tag :sha-XXXX and :latest, push to registry.
1664
+
1665
+ {" d. deploy — push to main only" if auto_deploy == "yes" and "none" not in deploy else ""}
1666
+ {" " + deploy_step(deploy) if auto_deploy == "yes" and "none" not in deploy else ""}
1667
+
1668
+ 3. Caching: hashFiles on requirements.txt / package-lock.json / go.sum
1669
+ 4. Secrets needed:
1670
+ {ci_secrets(deploy)}
1671
+ 5. Add workflow_dispatch trigger for manual runs.
1672
+ 6. README: branch protection — require CI to pass before merge.
1673
+
1674
+ Write every file completely."""
1675
+
1676
+
1677
+ _PROMPT_BUILDERS = {
1678
+ "newsite": _build_newsite,
1679
+ "newapp": _build_newapp,
1680
+ "docker": _build_docker,
1681
+ "ci-github": _build_ci,
1682
+ }
1683
+
1684
+
1685
+ def build_skill_prompt(name: str, answers: dict) -> str | None:
1686
+ builder = _PROMPT_BUILDERS.get(name)
1687
+ if builder:
1688
+ return builder(answers)
1689
+ return get_skill_raw(name)
1690
+
1691
+
1692
+ # ── Custom file skills (backward compat) ──────────────────────────────────────
1693
+
1694
+ BUILTIN_SKILLS = {k: f"[interactive — run via /skill {k}]" for k in _PROMPT_BUILDERS}
1695
+
1696
+
1697
+ def get_skill_raw(name: str) -> str | None:
1698
+ SKILLS_DIR.mkdir(parents=True, exist_ok=True)
1699
+ f = SKILLS_DIR / f"{name}.md"
1700
+ return f.read_text(encoding="utf-8") if f.exists() else None
1701
+
1702
+
1703
+ def get_skill(name: str) -> str | None:
1704
+ if name in _PROMPT_BUILDERS:
1705
+ return f"[Use ask_skill_questions('{name}') + build_skill_prompt() for this skill]"
1706
+ return get_skill_raw(name)
1707
+
1708
+
1709
+ def load_skills() -> dict[str, str]:
1710
+ skills = dict(BUILTIN_SKILLS)
1711
+ SKILLS_DIR.mkdir(parents=True, exist_ok=True)
1712
+ for f in sorted(SKILLS_DIR.glob("*.md")):
1713
+ name = f.stem.lower().replace(" ", "-")
1714
+ if name not in skills:
1715
+ skills[name] = f.read_text(encoding="utf-8")
1716
+ return skills
1717
+
1718
+
1719
+ def show_skills(console) -> None:
1720
+ builtin = list(_PROMPT_BUILDERS.keys())
1721
+ custom = []
1722
+ SKILLS_DIR.mkdir(parents=True, exist_ok=True)
1723
+ for f in sorted(SKILLS_DIR.glob("*.md")):
1724
+ name = f.stem.lower().replace(" ", "-")
1725
+ if name not in builtin:
1726
+ custom.append((name, f.read_text(encoding="utf-8")))
1727
+
1728
+ descriptions = {
1729
+ "newsite": "Full-stack site — choose frontend + backend tech, frontend/ + backend/ folders",
1730
+ "newapp": "Full-stack app — choose frontend + backend tech, detailed per-framework rules",
1731
+ "docker": "Dockerize — multi-stage build, compose, healthcheck, .dockerignore",
1732
+ "ci-github": "GitHub Actions — lint → test → build → deploy, secrets, caching",
1733
+ }
1734
+
1735
+ console.print("\n[bold]Built-in Skills[/bold] [dim](interactive Q&A)[/dim]")
1736
+ for name in builtin:
1737
+ console.print(f" [green]{name:<18}[/green] [dim]{descriptions.get(name, '')}[/dim]")
1738
+
1739
+ if custom:
1740
+ console.print("\n[bold]Custom Skills[/bold] [dim](~/.bharatcode/skills/)[/dim]")
1741
+ for name, content in custom:
1742
+ preview = content.split("\n")[0][:60]
1743
+ console.print(f" [cyan]{name:<18}[/cyan] [dim]{preview}[/dim]")
1744
+
1745
+ console.print()
1746
+ console.print("[dim]Usage: /skill <name> Custom: ~/.bharatcode/skills/<name>.md[/dim]\n")