start-vibing-stacks 1.5.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/setup.js CHANGED
@@ -176,7 +176,7 @@ export async function setupProject(projectDir, config, options = {}) {
176
176
  hooks: [
177
177
  {
178
178
  type: 'command',
179
- command: 'npx tsx .claude/hooks/run-hook.ts user-prompt-submit',
179
+ command: 'bash .claude/hooks/run-hook.sh user-prompt-submit',
180
180
  timeout: 10,
181
181
  },
182
182
  ],
@@ -187,7 +187,7 @@ export async function setupProject(projectDir, config, options = {}) {
187
187
  hooks: [
188
188
  {
189
189
  type: 'command',
190
- command: 'npx tsx .claude/hooks/run-hook.ts stop-validator',
190
+ command: 'bash .claude/hooks/run-hook.sh stop-validator',
191
191
  timeout: 30,
192
192
  },
193
193
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-vibing-stacks",
3
- "version": "1.5.1",
3
+ "version": "1.7.0",
4
4
  "description": "AI-powered multi-stack dev workflow for Claude Code. Supports PHP, Node.js, Python and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,88 @@
1
+ # Hook Development — Claude Code Hooks
2
+
3
+ **ALWAYS invoke when creating or modifying Claude Code hooks.**
4
+
5
+ ## Hook Types
6
+
7
+ | Event | When | Use Case |
8
+ |-------|------|----------|
9
+ | `UserPromptSubmit` | Before prompt is sent | Inject workflow, validate input |
10
+ | `Stop` | Before task completion | Validate state, block if dirty |
11
+ | `PreToolUse` | Before tool execution | Approve/block dangerous tools |
12
+ | `PostToolUse` | After tool execution | Log, validate output |
13
+
14
+ ## File Structure
15
+
16
+ ```
17
+ .claude/hooks/
18
+ ├── run-hook.sh # Entry point (bash → bun/tsx fallback)
19
+ ├── user-prompt-submit.ts # Prompt injection
20
+ └── stop-validator.ts # Task completion gate
21
+ ```
22
+
23
+ ## Hook Input (stdin JSON)
24
+
25
+ ```typescript
26
+ // UserPromptSubmit
27
+ interface PromptInput {
28
+ user_prompt: string;
29
+ session_id: string;
30
+ }
31
+
32
+ // Stop
33
+ interface StopInput {
34
+ stop_hook_active?: boolean; // Cycle detection
35
+ transcript?: string;
36
+ }
37
+ ```
38
+
39
+ ## Hook Output (stdout JSON)
40
+
41
+ ```typescript
42
+ // UserPromptSubmit — inject system message
43
+ { "continue": true, "systemMessage": "WORKFLOW: ..." }
44
+
45
+ // Stop — approve or block
46
+ { "continue": false, "decision": "approve", "reason": "All checks passed" }
47
+ { "continue": true, "decision": "block", "reason": "Uncommitted files" }
48
+ ```
49
+
50
+ ## Template: Stop Validator
51
+
52
+ ```typescript
53
+ #!/usr/bin/env node
54
+ import { execSync } from 'child_process';
55
+
56
+ function cmd(c: string): string {
57
+ try { return execSync(c, { encoding: 'utf8' }).trim(); } catch { return ''; }
58
+ }
59
+
60
+ const branch = cmd('git rev-parse --abbrev-ref HEAD');
61
+ const dirty = cmd('git status --porcelain');
62
+
63
+ const result = (!dirty && (branch === 'main' || branch === 'master'))
64
+ ? { continue: false, decision: 'approve', reason: 'Clean main branch' }
65
+ : { continue: true, decision: 'block', reason: `Branch: ${branch}, dirty: ${!!dirty}` };
66
+
67
+ console.log(JSON.stringify(result));
68
+ ```
69
+
70
+ ## settings.json Registration
71
+
72
+ ```json
73
+ {
74
+ "hooks": {
75
+ "Stop": [{ "hooks": [{ "type": "command", "command": "bash .claude/hooks/run-hook.sh stop-validator", "timeout": 30 }] }],
76
+ "UserPromptSubmit": [{ "matcher": "", "hooks": [{ "type": "command", "command": "bash .claude/hooks/run-hook.sh user-prompt-submit", "timeout": 10 }] }]
77
+ }
78
+ }
79
+ ```
80
+
81
+ ## Rules
82
+
83
+ 1. **Always use `run-hook.sh` as entry** — handles bun/tsx fallback
84
+ 2. **Read stdin with timeout** — hooks must not hang
85
+ 3. **Exit 0 always** — non-zero kills the session
86
+ 4. **Output valid JSON to stdout** — Claude Code parses it
87
+ 5. **Cycle detection** — check `stop_hook_active` flag in Stop hooks
88
+ 6. **Keep hooks fast** — timeout applies (10s for prompt, 30s for stop)
@@ -0,0 +1,90 @@
1
+ # Playwright Automation — E2E Testing
2
+
3
+ **ALWAYS invoke when writing E2E tests or browser automation.**
4
+
5
+ ## Structure
6
+
7
+ ```
8
+ tests/e2e/
9
+ ├── fixtures/ # Auth, DB cleanup, custom fixtures
10
+ ├── pages/ # Page Object Model
11
+ ├── flows/ # User journey tests
12
+ ├── api/ # API-only tests
13
+ └── playwright.config.ts
14
+ ```
15
+
16
+ ## Page Object Model
17
+
18
+ ```typescript
19
+ // tests/e2e/pages/base.page.ts
20
+ import { type Page, type Locator, expect } from '@playwright/test';
21
+
22
+ export abstract class BasePage {
23
+ protected readonly page: Page;
24
+ constructor(page: Page) { this.page = page; }
25
+
26
+ get loadingSpinner(): Locator { return this.page.getByTestId('loading-spinner'); }
27
+ get errorMessage(): Locator { return this.page.getByTestId('error-message'); }
28
+
29
+ async waitForLoad(): Promise<void> {
30
+ await this.loadingSpinner.waitFor({ state: 'hidden' });
31
+ }
32
+
33
+ async goto(path: string): Promise<void> {
34
+ await this.page.goto(path);
35
+ await this.waitForLoad();
36
+ }
37
+ }
38
+ ```
39
+
40
+ ## Auth Fixture
41
+
42
+ ```typescript
43
+ export function generateTestUser(): TestUser {
44
+ const ts = Date.now();
45
+ const rand = Math.random().toString(36).substring(7);
46
+ return {
47
+ name: `Test User ${ts}`,
48
+ email: `testuser_${ts}_${rand}@test.com`,
49
+ password: 'TestPassword123!',
50
+ };
51
+ }
52
+ ```
53
+
54
+ ## Multi-Viewport Testing
55
+
56
+ ```typescript
57
+ const viewports = [
58
+ { name: 'mobile', width: 375, height: 667 },
59
+ { name: 'tablet', width: 768, height: 1024 },
60
+ { name: 'desktop', width: 1280, height: 800 },
61
+ ] as const;
62
+
63
+ for (const viewport of viewports) {
64
+ test.describe(`Responsive - ${viewport.name}`, () => {
65
+ test.use({ viewport: { width: viewport.width, height: viewport.height } });
66
+ test('navigation adapts', async ({ page }) => { /* ... */ });
67
+ });
68
+ }
69
+ ```
70
+
71
+ ## Required data-testid
72
+
73
+ ```html
74
+ <input data-testid="email-input" />
75
+ <input data-testid="password-input" />
76
+ <button data-testid="submit-button" />
77
+ <div data-testid="error-message" />
78
+ <div data-testid="success-message" />
79
+ <div data-testid="loading-spinner" />
80
+ <nav data-testid="sidebar" />
81
+ <button data-testid="hamburger-menu" />
82
+ ```
83
+
84
+ ## FORBIDDEN
85
+
86
+ 1. **Hardcoded test data** — generate unique data with timestamps
87
+ 2. **`.skip()` or `.only()`** — never in committed code
88
+ 3. **No cleanup** — always track + clean created data
89
+ 4. **Mocked auth** — use real authentication flows
90
+ 5. **Single viewport** — test mobile + tablet + desktop
@@ -0,0 +1,67 @@
1
+ # Test Coverage — Testing Management
2
+
3
+ **ALWAYS invoke AFTER implementing any feature. Do NOT skip.**
4
+
5
+ ## Critical Rules
6
+
7
+ 1. **CLEANUP ALL TEST DATA** — fixture-based tracking
8
+ 2. **VERIFY IN DATABASE** — check DB state after UI actions
9
+ 3. **TEST ALL VIEWPORTS** — desktop, tablet, mobile minimum
10
+ 4. **REAL AUTH ONLY** — never mock authentication
11
+ 5. **UNIQUE DATA** — timestamps in emails/names
12
+ 6. **NO `.skip()`** — never in committed code
13
+
14
+ ## Files That NEED Tests
15
+
16
+ | Type | Test Expected | Required |
17
+ |------|--------------|----------|
18
+ | API Route | Unit + E2E | **YES** |
19
+ | Model/Entity | Unit | **YES** |
20
+ | Page/View | E2E flow | **YES** |
21
+ | Component (interactive) | E2E | YES |
22
+ | Hook/Service | Unit | YES |
23
+ | Utility (exported) | Unit | YES |
24
+
25
+ ## Required E2E Flows
26
+
27
+ - [ ] Registration — create user, verify in DB
28
+ - [ ] Login/Logout — auth state changes
29
+ - [ ] CRUD Create — item created, visible, in DB
30
+ - [ ] CRUD Read — item displayed correctly
31
+ - [ ] CRUD Update — changes reflected in DB
32
+ - [ ] CRUD Delete — removed from DB
33
+ - [ ] Permissions — forbidden requests blocked
34
+ - [ ] Responsive — works on all viewports
35
+
36
+ ## Stack-Specific Commands
37
+
38
+ ### PHP (PHPUnit + Pest)
39
+ ```bash
40
+ ./vendor/bin/phpunit
41
+ ./vendor/bin/pest --coverage --min=70
42
+ ```
43
+
44
+ ### Node.js (Vitest + Playwright)
45
+ ```bash
46
+ npx vitest run --coverage
47
+ npx playwright test
48
+ npx playwright test --ui
49
+ ```
50
+
51
+ ## Before Commit Checklist
52
+
53
+ - [ ] All new features have tests?
54
+ - [ ] Tests use fixtures for cleanup?
55
+ - [ ] Database state verified after UI actions?
56
+ - [ ] Tests run on all viewports?
57
+ - [ ] Coverage threshold met (≥70%)?
58
+ - [ ] No `.skip()` in tests?
59
+ - [ ] All tests passing?
60
+
61
+ ## FORBIDDEN
62
+
63
+ 1. **Skipping tests** — `test.skip()`, `.only()`
64
+ 2. **Mocking auth** — use real authentication
65
+ 3. **Fixed test users** — `test@test.com` → generate unique
66
+ 4. **No cleanup** — orphaned test data
67
+ 5. **No DB validation** — trusting UI without checking DB
@@ -0,0 +1,113 @@
1
+ # React Patterns — Modern Component Architecture
2
+
3
+ **ALWAYS invoke when writing React components, hooks, or state management.**
4
+
5
+ ## Component Patterns
6
+
7
+ ### Compound Components
8
+ ```tsx
9
+ const TabsContext = createContext<TabsContextValue | null>(null);
10
+
11
+ function Tabs({ children, defaultTab }: { children: ReactNode; defaultTab: string }) {
12
+ const [activeTab, setActiveTab] = useState(defaultTab);
13
+ return (
14
+ <TabsContext.Provider value={{ activeTab, setActiveTab }}>
15
+ <div className="tabs">{children}</div>
16
+ </TabsContext.Provider>
17
+ );
18
+ }
19
+ // + TabList, Tab, TabPanel components using useContext(TabsContext)
20
+ ```
21
+
22
+ ### Generic List Component
23
+ ```tsx
24
+ interface ListProps<T> {
25
+ items: T[];
26
+ renderItem: (item: T) => ReactNode;
27
+ keyExtractor: (item: T) => string;
28
+ }
29
+
30
+ function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
31
+ return <ul>{items.map(item => <li key={keyExtractor(item)}>{renderItem(item)}</li>)}</ul>;
32
+ }
33
+ ```
34
+
35
+ ## Custom Hooks
36
+
37
+ ```tsx
38
+ // useLocalStorage
39
+ function useLocalStorage<T>(key: string, initial: T) {
40
+ const [value, setValue] = useState<T>(() => {
41
+ if (typeof window === 'undefined') return initial;
42
+ try { return JSON.parse(window.localStorage.getItem(key) ?? '') } catch { return initial }
43
+ });
44
+ const set = useCallback((v: T | ((val: T) => T)) => {
45
+ setValue(prev => {
46
+ const next = v instanceof Function ? v(prev) : v;
47
+ window.localStorage.setItem(key, JSON.stringify(next));
48
+ return next;
49
+ });
50
+ }, [key]);
51
+ return [value, set] as const;
52
+ }
53
+
54
+ // useDebounce
55
+ function useDebounce<T>(value: T, delay: number): T {
56
+ const [debounced, setDebounced] = useState(value);
57
+ useEffect(() => { const t = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(t); }, [value, delay]);
58
+ return debounced;
59
+ }
60
+ ```
61
+
62
+ ## State Management
63
+
64
+ ### useReducer for Complex State
65
+ ```tsx
66
+ type Action =
67
+ | { type: 'FETCH_START' }
68
+ | { type: 'FETCH_SUCCESS'; payload: Item[] }
69
+ | { type: 'FETCH_ERROR'; payload: string };
70
+
71
+ function reducer(state: State, action: Action): State {
72
+ switch (action.type) {
73
+ case 'FETCH_START': return { ...state, loading: true, error: null };
74
+ case 'FETCH_SUCCESS': return { ...state, loading: false, items: action.payload };
75
+ case 'FETCH_ERROR': return { ...state, loading: false, error: action.payload };
76
+ }
77
+ }
78
+ ```
79
+
80
+ ### Context (avoid prop drilling)
81
+ ```tsx
82
+ const AppContext = createContext<AppContextValue | undefined>(undefined);
83
+ export function useApp() {
84
+ const ctx = useContext(AppContext);
85
+ if (!ctx) throw new Error('useApp must be used within AppProvider');
86
+ return ctx;
87
+ }
88
+ ```
89
+
90
+ ## Performance
91
+
92
+ ```tsx
93
+ // memo — prevent re-renders
94
+ const ExpensiveList = memo(function ExpensiveList({ items }: { items: Item[] }) { ... });
95
+
96
+ // useMemo — expensive calculations
97
+ const processed = useMemo(() => data.map(d => expensiveCalc(d)), [data]);
98
+
99
+ // useCallback — stable refs
100
+ const handleClick = useCallback(() => setCount(c => c + 1), []);
101
+
102
+ // Code splitting
103
+ const Heavy = lazy(() => import('./HeavyComponent'));
104
+ <Suspense fallback={<Loading />}><Heavy /></Suspense>
105
+ ```
106
+
107
+ ## FORBIDDEN
108
+
109
+ 1. **Class components** — function components only
110
+ 2. **Prop drilling** — use context or composition
111
+ 3. **Inline objects/functions in JSX** — causes re-renders
112
+ 4. **useEffect for derived state** — use useMemo
113
+ 5. **Mutating state directly** — always setState/dispatch
@@ -0,0 +1,92 @@
1
+ # shadcn/ui — Component Library Patterns
2
+
3
+ **ALWAYS invoke when adding or modifying shadcn/ui components.**
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npx shadcn@latest init
9
+ npx shadcn@latest add button card dialog input
10
+ ```
11
+
12
+ ## Theming
13
+
14
+ ```css
15
+ /* globals.css — CSS variables for theming */
16
+ @layer base {
17
+ :root {
18
+ --background: 0 0% 100%;
19
+ --foreground: 222.2 84% 4.9%;
20
+ --primary: 222.2 47.4% 11.2%;
21
+ --primary-foreground: 210 40% 98%;
22
+ --muted: 210 40% 96.1%;
23
+ --muted-foreground: 215.4 16.3% 46.9%;
24
+ --border: 214.3 31.8% 91.4%;
25
+ --ring: 222.2 84% 4.9%;
26
+ --radius: 0.5rem;
27
+ }
28
+ .dark {
29
+ --background: 222.2 84% 4.9%;
30
+ --foreground: 210 40% 98%;
31
+ --primary: 210 40% 98%;
32
+ --primary-foreground: 222.2 47.4% 11.2%;
33
+ }
34
+ }
35
+ ```
36
+
37
+ ## Component Customization
38
+
39
+ ```tsx
40
+ // CORRECT — extend via className + variants
41
+ import { Button } from '@/components/ui/button';
42
+ <Button variant="outline" size="lg" className="rounded-full">Custom</Button>
43
+
44
+ // WRONG — modify component source for one-off styles
45
+ ```
46
+
47
+ ## Composition Pattern
48
+
49
+ ```tsx
50
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
51
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
52
+
53
+ // Compose primitives, don't create monolithic components
54
+ <Dialog>
55
+ <DialogTrigger asChild>
56
+ <Button variant="outline">Open</Button>
57
+ </DialogTrigger>
58
+ <DialogContent>
59
+ <DialogHeader>
60
+ <DialogTitle>Title</DialogTitle>
61
+ </DialogHeader>
62
+ <Card><CardContent>...</CardContent></Card>
63
+ </DialogContent>
64
+ </Dialog>
65
+ ```
66
+
67
+ ## Form Pattern (with Zod)
68
+
69
+ ```tsx
70
+ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
71
+ import { useForm } from 'react-hook-form';
72
+ import { zodResolver } from '@hookform/resolvers/zod';
73
+
74
+ const form = useForm<z.infer<typeof schema>>({ resolver: zodResolver(schema) });
75
+
76
+ <Form {...form}>
77
+ <FormField control={form.control} name="email" render={({ field }) => (
78
+ <FormItem>
79
+ <FormLabel>Email</FormLabel>
80
+ <FormControl><Input {...field} /></FormControl>
81
+ <FormMessage />
82
+ </FormItem>
83
+ )} />
84
+ </Form>
85
+ ```
86
+
87
+ ## FORBIDDEN
88
+
89
+ 1. **Modifying component source for one-off needs** — use className/variants
90
+ 2. **Installing without init** — always `npx shadcn@latest init` first
91
+ 3. **Ignoring dark mode** — all components must work in both themes
92
+ 4. **Skipping accessibility** — shadcn components have built-in a11y, don't break it
@@ -0,0 +1,94 @@
1
+ # Tailwind Patterns — Utility-First CSS
2
+
3
+ **ALWAYS invoke when writing Tailwind CSS classes.**
4
+
5
+ ## Mobile-First Breakpoints
6
+
7
+ ```
8
+ sm: 640px → md: 768px → lg: 1024px → xl: 1280px → 2xl: 1536px
9
+ ```
10
+
11
+ ```tsx
12
+ <div className="w-full md:w-1/2 lg:w-1/3 xl:w-1/4">
13
+ {/* Mobile: full → Tablet: half → Desktop: third → Large: quarter */}
14
+ </div>
15
+ ```
16
+
17
+ ## Layout Patterns
18
+
19
+ ### Flexbox
20
+ ```tsx
21
+ // Center
22
+ <div className="flex items-center justify-center h-screen" />
23
+
24
+ // Responsive direction
25
+ <div className="flex flex-col md:flex-row gap-4" />
26
+ ```
27
+
28
+ ### Grid
29
+ ```tsx
30
+ // Responsive grid
31
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" />
32
+
33
+ // Auto-fit
34
+ <div className="grid grid-cols-[repeat(auto-fit,minmax(300px,1fr))] gap-4" />
35
+ ```
36
+
37
+ ## Dark Mode
38
+
39
+ ```tsx
40
+ <div className="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
41
+ <p className="text-gray-600 dark:text-gray-300">Adapts to theme</p>
42
+ </div>
43
+ ```
44
+
45
+ ## Component Patterns
46
+
47
+ ### Card
48
+ ```tsx
49
+ <div className="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-shadow">
50
+ ```
51
+
52
+ ### Button
53
+ ```tsx
54
+ <button className="bg-primary text-primary-foreground px-4 py-2 rounded-md font-medium hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
55
+ ```
56
+
57
+ ### Input
58
+ ```tsx
59
+ <input className="w-full px-3 py-2 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-md text-gray-900 dark:text-white placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" />
60
+ ```
61
+
62
+ ## Animations
63
+
64
+ ```tsx
65
+ <Loader className="h-5 w-5 animate-spin" /> // Spinner
66
+ <div className="h-4 w-24 bg-gray-200 animate-pulse" /> // Skeleton
67
+ ```
68
+
69
+ ### Custom (tailwind.config.js)
70
+ ```js
71
+ animation: {
72
+ 'fade-in': 'fadeIn 0.3s ease-in-out',
73
+ 'slide-up': 'slideUp 0.3s ease-out',
74
+ },
75
+ keyframes: {
76
+ fadeIn: { '0%': { opacity: '0' }, '100%': { opacity: '1' } },
77
+ slideUp: { '0%': { transform: 'translateY(10px)', opacity: '0' }, '100%': { transform: 'translateY(0)', opacity: '1' } },
78
+ }
79
+ ```
80
+
81
+ ## Show/Hide Responsive
82
+
83
+ ```tsx
84
+ <div className="hidden lg:block">Desktop only</div>
85
+ <div className="block lg:hidden">Mobile only</div>
86
+ <span className="sr-only">Screen reader only</span>
87
+ ```
88
+
89
+ ## FORBIDDEN
90
+
91
+ 1. **`!important`** — fix specificity properly
92
+ 2. **Arbitrary values when utilities exist** — use the scale
93
+ 3. **Mixing raw CSS with Tailwind** — stick to utilities
94
+ 4. **Inconsistent spacing** — follow the 4px scale (p-1=4px, p-2=8px, p-4=16px)
@@ -0,0 +1,86 @@
1
+ # Bun Runtime — Fast JavaScript Runtime
2
+
3
+ **ALWAYS invoke when using Bun for scripts, packages, bundling, or testing.**
4
+
5
+ ## Package Management
6
+
7
+ ```bash
8
+ bun install # Install deps (replaces npm install)
9
+ bun add zod # Add dependency
10
+ bun add -D vitest # Add dev dependency
11
+ bun remove lodash # Remove
12
+ bun update # Update all
13
+ ```
14
+
15
+ ## Scripts
16
+
17
+ ```bash
18
+ bun run dev # Run script from package.json
19
+ bun run build
20
+ bun --watch src/index.ts # Watch mode
21
+ ```
22
+
23
+ ## TypeScript (native, no config needed)
24
+
25
+ ```typescript
26
+ // Bun runs .ts files directly — no tsc/tsx needed
27
+ // bun src/index.ts
28
+
29
+ import { serve } from 'bun';
30
+
31
+ serve({
32
+ port: 3000,
33
+ fetch(req) {
34
+ const url = new URL(req.url);
35
+ if (url.pathname === '/api/health') {
36
+ return Response.json({ status: 'ok' });
37
+ }
38
+ return new Response('Not Found', { status: 404 });
39
+ },
40
+ });
41
+ ```
42
+
43
+ ## File I/O (Bun APIs)
44
+
45
+ ```typescript
46
+ // Fast file operations
47
+ const content = await Bun.file('data.json').text();
48
+ const parsed = await Bun.file('data.json').json();
49
+ await Bun.write('output.txt', 'Hello');
50
+
51
+ // Glob
52
+ const glob = new Bun.Glob('**/*.ts');
53
+ for await (const file of glob.scan('.')) { console.log(file); }
54
+ ```
55
+
56
+ ## Testing (built-in)
57
+
58
+ ```typescript
59
+ // *.test.ts — bun test
60
+ import { describe, it, expect } from 'bun:test';
61
+
62
+ describe('math', () => {
63
+ it('adds', () => expect(1 + 1).toBe(2));
64
+ });
65
+ ```
66
+
67
+ ```bash
68
+ bun test # Run all tests
69
+ bun test --coverage # With coverage
70
+ bun test --watch # Watch mode
71
+ ```
72
+
73
+ ## Environment Variables
74
+
75
+ ```typescript
76
+ // .env loaded automatically
77
+ const apiKey = Bun.env['API_KEY']; // Bun.env (recommended)
78
+ const dbUrl = process.env['DATABASE_URL']; // Also works
79
+ ```
80
+
81
+ ## FORBIDDEN
82
+
83
+ 1. **`node` command when `bun` works** — prefer bun for speed
84
+ 2. **`npx` when `bunx` works** — `bunx` is faster
85
+ 3. **Manual .env loading** — Bun loads `.env` automatically
86
+ 4. **CommonJS `require()`** — use ESM `import`
@@ -0,0 +1,77 @@
1
+ # Mongoose Patterns — MongoDB ODM
2
+
3
+ **ALWAYS invoke when writing Mongoose schemas, queries, or aggregations.**
4
+
5
+ ## Schema Pattern
6
+
7
+ ```typescript
8
+ import { Schema, model, type InferSchemaType } from 'mongoose';
9
+
10
+ const userSchema = new Schema({
11
+ name: { type: String, required: true, trim: true, minlength: 2 },
12
+ email: { type: String, required: true, unique: true, lowercase: true, index: true },
13
+ role: { type: String, enum: ['admin', 'user', 'moderator'] as const, default: 'user' },
14
+ profile: {
15
+ avatar: String,
16
+ bio: { type: String, maxlength: 500 },
17
+ },
18
+ tags: [{ type: String, index: true }],
19
+ isActive: { type: Boolean, default: true, index: true },
20
+ }, {
21
+ timestamps: true, // createdAt, updatedAt
22
+ toJSON: { virtuals: true, transform: (_, ret) => { delete ret.__v; return ret; } },
23
+ });
24
+
25
+ // Compound index
26
+ userSchema.index({ email: 1, isActive: 1 });
27
+ // Text index for search
28
+ userSchema.index({ name: 'text', 'profile.bio': 'text' });
29
+
30
+ type IUser = InferSchemaType<typeof userSchema>;
31
+ export const User = model('User', userSchema);
32
+ ```
33
+
34
+ ## Query Patterns
35
+
36
+ ```typescript
37
+ // Pagination
38
+ async function paginate(page: number, limit: number) {
39
+ const [items, total] = await Promise.all([
40
+ User.find({ isActive: true }).skip((page - 1) * limit).limit(limit).lean(),
41
+ User.countDocuments({ isActive: true }),
42
+ ]);
43
+ return { items, total, pages: Math.ceil(total / limit) };
44
+ }
45
+
46
+ // Aggregation
47
+ const stats = await User.aggregate([
48
+ { $match: { isActive: true } },
49
+ { $group: { _id: '$role', count: { $sum: 1 }, avgAge: { $avg: '$age' } } },
50
+ { $sort: { count: -1 } },
51
+ ]);
52
+ ```
53
+
54
+ ## Middleware
55
+
56
+ ```typescript
57
+ // Pre-save: hash password
58
+ userSchema.pre('save', async function (next) {
59
+ if (!this.isModified('password')) return next();
60
+ this.password = await bcrypt.hash(this.password, 12);
61
+ next();
62
+ });
63
+
64
+ // Pre-find: exclude inactive by default
65
+ userSchema.pre(/^find/, function (next) {
66
+ this.where({ isActive: { $ne: false } });
67
+ next();
68
+ });
69
+ ```
70
+
71
+ ## FORBIDDEN
72
+
73
+ 1. **No indexes on queried fields** — always index filter/sort fields
74
+ 2. **`find()` without `.lean()`** for read-only — wastes memory
75
+ 3. **Unbounded queries** — always `.limit()`
76
+ 4. **N+1 queries** — use `.populate()` or aggregation `$lookup`
77
+ 5. **String IDs without casting** — use `new Types.ObjectId(id)`
@@ -0,0 +1,123 @@
1
+ # Next.js App Router — Modern Patterns
2
+
3
+ **ALWAYS invoke when writing Next.js pages, layouts, or server components.**
4
+
5
+ ## File Conventions
6
+
7
+ ```
8
+ app/
9
+ ├── layout.tsx # Root layout (required)
10
+ ├── page.tsx # Home page
11
+ ├── loading.tsx # Loading UI (Suspense boundary)
12
+ ├── error.tsx # Error boundary ('use client')
13
+ ├── not-found.tsx # 404 page
14
+ ├── (auth)/ # Route group (no URL segment)
15
+ │ ├── login/page.tsx
16
+ │ └── register/page.tsx
17
+ ├── dashboard/
18
+ │ ├── layout.tsx # Nested layout
19
+ │ ├── page.tsx
20
+ │ └── [id]/page.tsx # Dynamic route
21
+ └── api/
22
+ └── route.ts # API route handler
23
+ ```
24
+
25
+ ## Server vs Client Components
26
+
27
+ ```tsx
28
+ // DEFAULT: Server Component (no directive needed)
29
+ async function UserList() {
30
+ const users = await db.user.findMany(); // Direct DB access
31
+ return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
32
+ }
33
+
34
+ // CLIENT: Only when needed (interactivity, hooks, browser APIs)
35
+ 'use client';
36
+ function Counter() {
37
+ const [count, setCount] = useState(0);
38
+ return <button onClick={() => setCount(c + 1)}>{count}</button>;
39
+ }
40
+ ```
41
+
42
+ ## Data Fetching
43
+
44
+ ```tsx
45
+ // Server Component — fetch with caching
46
+ async function Page() {
47
+ const data = await fetch('https://api.example.com/data', {
48
+ next: { revalidate: 3600 }, // ISR: revalidate every hour
49
+ });
50
+ return <div>{data}</div>;
51
+ }
52
+
53
+ // Dynamic data (no cache)
54
+ async function Page() {
55
+ const data = await fetch('https://api.example.com/data', {
56
+ cache: 'no-store',
57
+ });
58
+ }
59
+ ```
60
+
61
+ ## Server Actions
62
+
63
+ ```tsx
64
+ // app/actions.ts
65
+ 'use server';
66
+
67
+ import { revalidatePath } from 'next/cache';
68
+
69
+ export async function createUser(formData: FormData) {
70
+ const name = formData.get('name') as string;
71
+ await db.user.create({ data: { name } });
72
+ revalidatePath('/users');
73
+ }
74
+
75
+ // In component
76
+ <form action={createUser}>
77
+ <input name="name" />
78
+ <button type="submit">Create</button>
79
+ </form>
80
+ ```
81
+
82
+ ## Route Handlers (API)
83
+
84
+ ```tsx
85
+ // app/api/users/route.ts
86
+ import { NextRequest, NextResponse } from 'next/server';
87
+
88
+ export async function GET(request: NextRequest) {
89
+ const { searchParams } = new URL(request.url);
90
+ const page = Number(searchParams.get('page') ?? '1');
91
+ const users = await db.user.findMany({ skip: (page - 1) * 20, take: 20 });
92
+ return NextResponse.json(users);
93
+ }
94
+
95
+ export async function POST(request: NextRequest) {
96
+ const body = await request.json();
97
+ const result = schema.safeParse(body);
98
+ if (!result.success) return NextResponse.json(result.error, { status: 400 });
99
+ const user = await db.user.create({ data: result.data });
100
+ return NextResponse.json(user, { status: 201 });
101
+ }
102
+ ```
103
+
104
+ ## Metadata
105
+
106
+ ```tsx
107
+ // Static
108
+ export const metadata = { title: 'Dashboard', description: 'User dashboard' };
109
+
110
+ // Dynamic
111
+ export async function generateMetadata({ params }: { params: { id: string } }) {
112
+ const user = await getUser(params.id);
113
+ return { title: user.name };
114
+ }
115
+ ```
116
+
117
+ ## FORBIDDEN
118
+
119
+ 1. **`'use client'` on server-capable components** — default to server
120
+ 2. **Fetching in client when server fetch works** — use server components
121
+ 3. **`getServerSideProps` / `getStaticProps`** — App Router uses async components
122
+ 4. **API routes for server-only data** — use server components directly
123
+ 5. **Prop drilling through layouts** — use parallel routes or context
@@ -0,0 +1,87 @@
1
+ # tRPC API — End-to-End Type Safety
2
+
3
+ **ALWAYS invoke when building type-safe API routes with tRPC.**
4
+
5
+ ## Structure
6
+
7
+ ```
8
+ server/
9
+ ├── trpc.ts # tRPC init + context
10
+ ├── routers/
11
+ │ ├── _app.ts # Root router (merges all)
12
+ │ ├── user.router.ts
13
+ │ └── post.router.ts
14
+ └── middleware/
15
+ └── auth.ts # Auth middleware
16
+ ```
17
+
18
+ ## Setup
19
+
20
+ ```typescript
21
+ // server/trpc.ts
22
+ import { initTRPC, TRPCError } from '@trpc/server';
23
+ import superjson from 'superjson';
24
+
25
+ const t = initTRPC.context<Context>().create({ transformer: superjson });
26
+
27
+ export const router = t.router;
28
+ export const publicProcedure = t.procedure;
29
+
30
+ export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
31
+ if (!ctx.session?.user) throw new TRPCError({ code: 'UNAUTHORIZED' });
32
+ return next({ ctx: { ...ctx, user: ctx.session.user } });
33
+ });
34
+ ```
35
+
36
+ ## Router Pattern
37
+
38
+ ```typescript
39
+ // server/routers/user.router.ts
40
+ import { z } from 'zod';
41
+ import { router, protectedProcedure } from '../trpc';
42
+
43
+ export const userRouter = router({
44
+ me: protectedProcedure.query(async ({ ctx }) => {
45
+ return ctx.db.user.findUnique({ where: { id: ctx.user.id } });
46
+ }),
47
+
48
+ update: protectedProcedure
49
+ .input(z.object({ name: z.string().min(2) }))
50
+ .mutation(async ({ ctx, input }) => {
51
+ return ctx.db.user.update({
52
+ where: { id: ctx.user.id },
53
+ data: input,
54
+ });
55
+ }),
56
+
57
+ list: protectedProcedure
58
+ .input(z.object({ page: z.number().min(1).default(1), limit: z.number().max(100).default(20) }))
59
+ .query(async ({ ctx, input }) => {
60
+ const { page, limit } = input;
61
+ return ctx.db.user.findMany({ skip: (page - 1) * limit, take: limit });
62
+ }),
63
+ });
64
+ ```
65
+
66
+ ## Client Usage (React)
67
+
68
+ ```tsx
69
+ import { trpc } from '@/utils/trpc';
70
+
71
+ function Profile() {
72
+ const { data: user, isLoading } = trpc.user.me.useQuery();
73
+ const updateMutation = trpc.user.update.useMutation({
74
+ onSuccess: () => utils.user.me.invalidate(),
75
+ });
76
+
77
+ if (isLoading) return <Skeleton />;
78
+ return <div>{user?.name}</div>;
79
+ }
80
+ ```
81
+
82
+ ## FORBIDDEN
83
+
84
+ 1. **REST endpoints when tRPC covers it** — use tRPC procedures
85
+ 2. **Unvalidated input** — always use Zod `.input()`
86
+ 3. **Public procedures for auth-required data** — use `protectedProcedure`
87
+ 4. **Direct DB access in client** — always through tRPC procedures
@@ -0,0 +1,90 @@
1
+ # TypeScript Strict — Type Safety Patterns
2
+
3
+ **ALWAYS invoke when writing .ts/.tsx files.**
4
+
5
+ ## Required tsconfig.json
6
+
7
+ ```json
8
+ {
9
+ "compilerOptions": {
10
+ "strict": true,
11
+ "noUncheckedIndexedAccess": true,
12
+ "noImplicitAny": true,
13
+ "strictNullChecks": true,
14
+ "noImplicitReturns": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "exactOptionalPropertyTypes": true
17
+ }
18
+ }
19
+ ```
20
+
21
+ ## Index Access
22
+
23
+ ```typescript
24
+ // WRONG
25
+ const port = process.env.PORT;
26
+
27
+ // CORRECT — bracket notation
28
+ const port = process.env['PORT'];
29
+ const host = process.env['HOST'] ?? 'localhost';
30
+ ```
31
+
32
+ ## Null Handling
33
+
34
+ ```typescript
35
+ // Optional chaining + nullish coalescing
36
+ const avatar = user.profile?.avatar ?? '/default.png';
37
+
38
+ // WRONG — || treats 0, '', false as falsy
39
+ const count = input || 10;
40
+ // CORRECT — ?? only null/undefined
41
+ const count = input ?? 10;
42
+ ```
43
+
44
+ ## Type Guards
45
+
46
+ ```typescript
47
+ function isUser(value: unknown): value is User {
48
+ return typeof value === 'object' && value !== null && 'id' in value && 'email' in value;
49
+ }
50
+
51
+ // Assertion function
52
+ function assertDefined<T>(value: T | undefined | null, msg: string): asserts value is T {
53
+ if (value == null) throw new Error(msg);
54
+ }
55
+ ```
56
+
57
+ ## Const Assertions
58
+
59
+ ```typescript
60
+ // Exact types (not widened)
61
+ const endpoints = {
62
+ users: '/api/users',
63
+ posts: '/api/posts',
64
+ } as const;
65
+
66
+ // Discriminated unions
67
+ type Result<T> =
68
+ | { success: true; data: T }
69
+ | { success: false; error: string };
70
+ ```
71
+
72
+ ## Generic Patterns
73
+
74
+ ```typescript
75
+ // Constrained
76
+ function findById<T extends { id: string }>(items: T[], id: string): T | undefined {
77
+ return items.find(item => item.id === id);
78
+ }
79
+
80
+ // RequireFields utility
81
+ type RequireFields<T, K extends keyof T> = T & Required<Pick<T, K>>;
82
+ ```
83
+
84
+ ## FORBIDDEN
85
+
86
+ 1. **`any`** — use `unknown` instead
87
+ 2. **Non-null assertion without certainty** — check first
88
+ 3. **Type assertion to hide errors** — fix the actual issue
89
+ 4. **`@ts-ignore`** — use `@ts-expect-error` with comment
90
+ 5. **Ignoring strict errors** — always fix properly