@claude-code-mastery/starter-kit 1.0.0 → 1.2.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.
@@ -0,0 +1,1251 @@
1
+ #!/usr/bin/env bash
2
+ # scaffold-default.sh — Fast batch scaffold for default profile projects
3
+ # Default profile: fullstack Next.js + StrictDB + Tailwind + Docker + SEO + CI
4
+ #
5
+ # Usage: bash scripts/scaffold-default.sh <project-path> <project-name> <starter-kit-root>
6
+ #
7
+ # Creates a complete default-profile project with progress indicators.
8
+ # This replaces ~40+ individual tool calls with a single script execution.
9
+
10
+ set -euo pipefail
11
+
12
+ # ── Arguments ──────────────────────────────────────────────────────────────────
13
+ PROJECT_PATH="$1"
14
+ PROJECT_NAME="$2"
15
+ STARTER_KIT="$3"
16
+ REGISTRY="${HOME}/.claude/starter-kit-projects.json"
17
+
18
+ # ── Global install detection ───────────────────────────────────────────────────
19
+ if [ -f "${HOME}/.claude/starter-kit-source-path" ]; then
20
+ GLOBAL_INSTALLED=true
21
+ CLAUDE_DIR="$STARTER_KIT"
22
+ else
23
+ GLOBAL_INSTALLED=false
24
+ CLAUDE_DIR="$STARTER_KIT/.claude"
25
+ fi
26
+
27
+ # ── Validation ─────────────────────────────────────────────────────────────────
28
+ if [ -d "$PROJECT_PATH" ]; then
29
+ echo "ERROR: Directory already exists: $PROJECT_PATH"
30
+ echo "Remove it first or choose a different name."
31
+ exit 1
32
+ fi
33
+
34
+ if [ ! -d "$CLAUDE_DIR/commands" ]; then
35
+ echo "ERROR: Starter kit not found at: $STARTER_KIT"
36
+ exit 1
37
+ fi
38
+
39
+ # ── Progress Tracking ──────────────────────────────────────────────────────────
40
+ TOTAL_STEPS=15
41
+ CURRENT=0
42
+ START_NS=$(date +%s%N)
43
+
44
+ progress() {
45
+ CURRENT=$((CURRENT + 1))
46
+ local pct=$((CURRENT * 100 / TOTAL_STEPS))
47
+ local now_ns=$(date +%s%N)
48
+ local elapsed_ms=$(( (now_ns - START_NS) / 1000000 ))
49
+
50
+ # Progress bar (20 chars)
51
+ local filled=$((pct / 5))
52
+ local empty=$((20 - filled))
53
+ local bar=""
54
+ for ((i=0; i<filled; i++)); do bar+="\xe2\x96\x88"; done
55
+ for ((i=0; i<empty; i++)); do bar+="\xe2\x96\x91"; done
56
+
57
+ # Estimated time remaining
58
+ local eta=""
59
+ if [ "$CURRENT" -eq "$TOTAL_STEPS" ]; then
60
+ eta="Done!"
61
+ elif [ "$elapsed_ms" -gt 0 ] && [ "$CURRENT" -gt 0 ]; then
62
+ local ms_per_step=$((elapsed_ms / CURRENT))
63
+ local remaining_ms=$(( (TOTAL_STEPS - CURRENT) * ms_per_step ))
64
+ if [ "$remaining_ms" -ge 1000 ]; then
65
+ local remaining_s=$((remaining_ms / 1000))
66
+ local remaining_frac=$(( (remaining_ms % 1000) / 100 ))
67
+ eta="~${remaining_s}.${remaining_frac}s remaining"
68
+ else
69
+ eta="~${remaining_ms}ms remaining"
70
+ fi
71
+ else
72
+ eta="estimating..."
73
+ fi
74
+
75
+ printf "[%d/%d] %3d%% $(echo -e "$bar") %-40s %s\n" \
76
+ "$CURRENT" "$TOTAL_STEPS" "$pct" "$1" "$eta"
77
+ }
78
+
79
+ # ── Header ─────────────────────────────────────────────────────────────────────
80
+ echo ""
81
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
82
+ echo " NEW PROJECT: $PROJECT_NAME (default profile)"
83
+ echo " Next.js + StrictDB + Tailwind + Docker + SEO + CI"
84
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
85
+ echo ""
86
+
87
+ # ── Step 1: Create directory structure ─────────────────────────────────────────
88
+ progress "Creating directory structure..."
89
+ if [ "$GLOBAL_INSTALLED" = "false" ]; then
90
+ mkdir -p "$PROJECT_PATH"/.claude/{commands,skills,agents,hooks}
91
+ fi
92
+ mkdir -p "$PROJECT_PATH"/project-docs
93
+ mkdir -p "$PROJECT_PATH"/src/app/api/v1/health
94
+ mkdir -p "$PROJECT_PATH"/src/handlers
95
+ mkdir -p "$PROJECT_PATH"/src/adapters
96
+ mkdir -p "$PROJECT_PATH"/src/types
97
+ mkdir -p "$PROJECT_PATH"/tests/{unit,integration,e2e}
98
+ mkdir -p "$PROJECT_PATH"/scripts/queries
99
+ mkdir -p "$PROJECT_PATH"/content
100
+ mkdir -p "$PROJECT_PATH"/.github/workflows
101
+ mkdir -p "$PROJECT_PATH"/public
102
+
103
+ # ── Steps 2-4: Copy Claude infrastructure (clone users only) ──────────────────
104
+ # npm users already have commands/skills/hooks installed globally in ~/.claude/
105
+ if [ "$GLOBAL_INSTALLED" = "false" ]; then
106
+ progress "Copying 16 project commands..."
107
+ for cmd in architecture commit create-api create-e2e diagram help \
108
+ optimize-docker progress refactor review security-check \
109
+ setup show-user-guide test-plan what-is-my-ai-doing worktree; do
110
+ cp "$CLAUDE_DIR/commands/${cmd}.md" "$PROJECT_PATH/.claude/commands/"
111
+ done
112
+
113
+ progress "Copying skills, agents, 10 hooks..."
114
+ cp -r "$CLAUDE_DIR/skills/code-review" "$PROJECT_PATH/.claude/skills/"
115
+ cp -r "$CLAUDE_DIR/skills/create-service" "$PROJECT_PATH/.claude/skills/"
116
+ cp "$CLAUDE_DIR/agents/code-reviewer.md" "$PROJECT_PATH/.claude/agents/"
117
+ cp "$CLAUDE_DIR/agents/test-writer.md" "$PROJECT_PATH/.claude/agents/"
118
+ for hook in block-dangerous-bash.py check-file-length.py lint-on-save.sh \
119
+ verify-no-secrets.sh check-rybbit.sh check-branch.sh \
120
+ check-ports.sh check-e2e.sh check-rulecatch.sh check-env-sync.sh; do
121
+ cp "$CLAUDE_DIR/hooks/${hook}" "$PROJECT_PATH/.claude/hooks/"
122
+ done
123
+ chmod +x "$PROJECT_PATH/.claude/hooks/"*.sh 2>/dev/null
124
+ chmod +x "$PROJECT_PATH/.claude/hooks/"*.py 2>/dev/null
125
+
126
+ progress "Writing settings.json (10 hooks)..."
127
+ cat > "$PROJECT_PATH/.claude/settings.json" << 'SETTINGS_EOF'
128
+ {
129
+ "hooks": {
130
+ "PreToolUse": [
131
+ {
132
+ "matcher": "Bash",
133
+ "hooks": [
134
+ {
135
+ "type": "command",
136
+ "command": "python3 .claude/hooks/block-dangerous-bash.py"
137
+ },
138
+ {
139
+ "type": "command",
140
+ "command": "bash .claude/hooks/check-rybbit.sh"
141
+ },
142
+ {
143
+ "type": "command",
144
+ "command": "bash .claude/hooks/check-branch.sh"
145
+ },
146
+ {
147
+ "type": "command",
148
+ "command": "bash .claude/hooks/check-ports.sh"
149
+ },
150
+ {
151
+ "type": "command",
152
+ "command": "bash .claude/hooks/check-e2e.sh"
153
+ }
154
+ ]
155
+ }
156
+ ],
157
+ "PostToolUse": [
158
+ {
159
+ "matcher": "Write|Edit",
160
+ "hooks": [
161
+ {
162
+ "type": "command",
163
+ "command": "python3 .claude/hooks/check-file-length.py"
164
+ },
165
+ {
166
+ "type": "command",
167
+ "command": "bash .claude/hooks/lint-on-save.sh"
168
+ }
169
+ ]
170
+ }
171
+ ],
172
+ "Stop": [
173
+ {
174
+ "hooks": [
175
+ {
176
+ "type": "command",
177
+ "command": "bash .claude/hooks/verify-no-secrets.sh"
178
+ },
179
+ {
180
+ "type": "command",
181
+ "command": "bash .claude/hooks/check-rulecatch.sh"
182
+ },
183
+ {
184
+ "type": "command",
185
+ "command": "bash .claude/hooks/check-env-sync.sh"
186
+ }
187
+ ]
188
+ }
189
+ ]
190
+ }
191
+ }
192
+ SETTINGS_EOF
193
+ else
194
+ progress "Skipping local .claude/ copy (commands/skills/hooks live globally)"
195
+ progress "Skipping local .claude/ copy (commands/skills/hooks live globally)"
196
+ progress "Skipping local .claude/ copy (commands/skills/hooks live globally)"
197
+ fi
198
+
199
+ # ── Step 4b: Create features.json (populated manifest) ────────────────────────
200
+ CREATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
201
+ cat > "$PROJECT_PATH/.claude/features.json" << FEATURES_EOF
202
+ {
203
+ "schemaVersion": 1,
204
+ "installedBy": "claude-code-mastery-starter-kit",
205
+ "language": "node",
206
+ "features": {
207
+ "mongo": {
208
+ "version": "1.0.0",
209
+ "installedAt": "${CREATED_AT}",
210
+ "updatedAt": null,
211
+ "files": [
212
+ "scripts/db-query.ts",
213
+ "scripts/queries/example-find-user.ts",
214
+ "scripts/queries/example-count-docs.ts"
215
+ ]
216
+ },
217
+ "vitest": {
218
+ "version": "1.0.0",
219
+ "installedAt": "${CREATED_AT}",
220
+ "updatedAt": null,
221
+ "files": [
222
+ "vitest.config.ts"
223
+ ]
224
+ },
225
+ "playwright": {
226
+ "version": "1.0.0",
227
+ "installedAt": "${CREATED_AT}",
228
+ "updatedAt": null,
229
+ "files": [
230
+ "playwright.config.ts"
231
+ ]
232
+ },
233
+ "docker": {
234
+ "version": "1.0.0",
235
+ "installedAt": "${CREATED_AT}",
236
+ "updatedAt": null,
237
+ "files": [
238
+ "Dockerfile"
239
+ ]
240
+ }
241
+ }
242
+ }
243
+ FEATURES_EOF
244
+
245
+ # ── Step 5: Copy query system ─────────────────────────────────────────────────
246
+ progress "Copying StrictDB query system..."
247
+ cp "$STARTER_KIT/scripts/db-query.ts" "$PROJECT_PATH/scripts/db-query.ts"
248
+ cp "$STARTER_KIT/scripts/queries/example-find-user.ts" "$PROJECT_PATH/scripts/queries/"
249
+ cp "$STARTER_KIT/scripts/queries/example-count-docs.ts" "$PROJECT_PATH/scripts/queries/"
250
+
251
+ # ── Step 6: Create Next.js app files ──────────────────────────────────────────
252
+ progress "Creating Next.js app structure..."
253
+
254
+ # src/app/layout.tsx — SEO + Rybbit analytics
255
+ cat > "$PROJECT_PATH/src/app/layout.tsx" << 'LAYOUT_EOF'
256
+ import type { Metadata } from 'next';
257
+ import './globals.css';
258
+
259
+ export const metadata: Metadata = {
260
+ title: {
261
+ default: 'My App',
262
+ template: '%s — My App',
263
+ },
264
+ description: 'Built with Claude Code Mastery Starter Kit',
265
+ robots: { index: true, follow: true },
266
+ openGraph: {
267
+ type: 'website',
268
+ title: 'My App',
269
+ description: 'Built with Claude Code Mastery Starter Kit',
270
+ siteName: 'My App',
271
+ },
272
+ twitter: {
273
+ card: 'summary_large_image',
274
+ title: 'My App',
275
+ description: 'Built with Claude Code Mastery Starter Kit',
276
+ },
277
+ };
278
+
279
+ export default function RootLayout({
280
+ children,
281
+ }: {
282
+ children: React.ReactNode;
283
+ }) {
284
+ return (
285
+ <html lang="en">
286
+ <head>
287
+ {process.env.NEXT_PUBLIC_RYBBIT_SITE_ID && (
288
+ <script
289
+ src={`${process.env.NEXT_PUBLIC_RYBBIT_URL || 'https://app.rybbit.io'}/api/script.js`}
290
+ data-site-id={process.env.NEXT_PUBLIC_RYBBIT_SITE_ID}
291
+ defer
292
+ />
293
+ )}
294
+ </head>
295
+ <body>{children}</body>
296
+ </html>
297
+ );
298
+ }
299
+ LAYOUT_EOF
300
+
301
+ # src/app/page.tsx
302
+ cat > "$PROJECT_PATH/src/app/page.tsx" << 'PAGE_EOF'
303
+ export default function Home() {
304
+ return (
305
+ <main className="flex min-h-screen flex-col items-center justify-center p-8">
306
+ <h1 className="text-4xl font-bold mb-4">Welcome</h1>
307
+ <p className="text-lg text-gray-600">
308
+ Your project is ready. Start building.
309
+ </p>
310
+ </main>
311
+ );
312
+ }
313
+ PAGE_EOF
314
+
315
+ # src/app/globals.css
316
+ cat > "$PROJECT_PATH/src/app/globals.css" << 'CSS_EOF'
317
+ @tailwind base;
318
+ @tailwind components;
319
+ @tailwind utilities;
320
+ CSS_EOF
321
+
322
+ # src/app/api/v1/health/route.ts
323
+ cat > "$PROJECT_PATH/src/app/api/v1/health/route.ts" << 'HEALTH_EOF'
324
+ import { NextResponse } from 'next/server';
325
+
326
+ export async function GET() {
327
+ return NextResponse.json({ status: 'ok', timestamp: new Date().toISOString() });
328
+ }
329
+ HEALTH_EOF
330
+
331
+ # src/instrumentation.ts — process signal handlers for Next.js
332
+ cat > "$PROJECT_PATH/src/instrumentation.ts" << 'INSTRUMENT_EOF'
333
+ export async function register() {
334
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
335
+ const { StrictDB } = await import('strictdb');
336
+
337
+ // Get or create the shared StrictDB instance
338
+ const db = await StrictDB.create({ uri: process.env.STRICTDB_URI! });
339
+
340
+ process.on('SIGTERM', () => db.gracefulShutdown(0));
341
+ process.on('SIGINT', () => db.gracefulShutdown(0));
342
+ process.on('uncaughtException', (err) => {
343
+ console.error('Uncaught Exception:', err);
344
+ db.gracefulShutdown(1);
345
+ });
346
+ process.on('unhandledRejection', (reason) => {
347
+ console.error('Unhandled Rejection:', reason);
348
+ db.gracefulShutdown(1);
349
+ });
350
+ }
351
+ }
352
+ INSTRUMENT_EOF
353
+
354
+ # ── Step 7: Create config files (tsconfig, next, tailwind, postcss) ───────────
355
+ progress "Creating TypeScript + Next.js + Tailwind configs..."
356
+
357
+ cat > "$PROJECT_PATH/tsconfig.json" << 'TSCONFIG_EOF'
358
+ {
359
+ "compilerOptions": {
360
+ "target": "ES2022",
361
+ "lib": ["dom", "dom.iterable", "esnext"],
362
+ "allowJs": true,
363
+ "skipLibCheck": true,
364
+ "strict": true,
365
+ "noEmit": true,
366
+ "noUncheckedIndexedAccess": true,
367
+ "noImplicitOverride": true,
368
+ "esModuleInterop": true,
369
+ "module": "esnext",
370
+ "moduleResolution": "bundler",
371
+ "resolveJsonModule": true,
372
+ "isolatedModules": true,
373
+ "jsx": "preserve",
374
+ "incremental": true,
375
+ "plugins": [{ "name": "next" }],
376
+ "paths": {
377
+ "@/*": ["./src/*"]
378
+ }
379
+ },
380
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
381
+ "exclude": ["node_modules"]
382
+ }
383
+ TSCONFIG_EOF
384
+
385
+ cat > "$PROJECT_PATH/next.config.ts" << 'NEXTCONFIG_EOF'
386
+ import type { NextConfig } from 'next';
387
+
388
+ const nextConfig: NextConfig = {
389
+ reactStrictMode: true,
390
+ output: 'standalone',
391
+ images: {
392
+ formats: ['image/webp'],
393
+ },
394
+ };
395
+
396
+ export default nextConfig;
397
+ NEXTCONFIG_EOF
398
+
399
+ cat > "$PROJECT_PATH/tailwind.config.ts" << 'TAILWIND_EOF'
400
+ import type { Config } from 'tailwindcss';
401
+
402
+ const config: Config = {
403
+ content: [
404
+ './src/app/**/*.{js,ts,jsx,tsx,mdx}',
405
+ './src/components/**/*.{js,ts,jsx,tsx,mdx}',
406
+ ],
407
+ theme: {
408
+ extend: {},
409
+ },
410
+ plugins: [],
411
+ };
412
+
413
+ export default config;
414
+ TAILWIND_EOF
415
+
416
+ cat > "$PROJECT_PATH/postcss.config.mjs" << 'POSTCSS_EOF'
417
+ /** @type {import('postcss-load-config').Config} */
418
+ const config = {
419
+ plugins: {
420
+ tailwindcss: {},
421
+ autoprefixer: {},
422
+ },
423
+ };
424
+
425
+ export default config;
426
+ POSTCSS_EOF
427
+
428
+ # ── Step 8: Create Vitest + Playwright configs ────────────────────────────────
429
+ progress "Creating Vitest + Playwright configs..."
430
+
431
+ cat > "$PROJECT_PATH/vitest.config.ts" << 'VITEST_EOF'
432
+ import { defineConfig } from 'vitest/config';
433
+ import path from 'path';
434
+
435
+ export default defineConfig({
436
+ test: {
437
+ globals: true,
438
+ environment: 'node',
439
+ include: ['tests/unit/**/*.test.ts', 'tests/integration/**/*.test.ts'],
440
+ exclude: ['tests/e2e/**/*'],
441
+ },
442
+ resolve: {
443
+ alias: {
444
+ '@': path.resolve(__dirname, './src'),
445
+ },
446
+ },
447
+ });
448
+ VITEST_EOF
449
+
450
+ cat > "$PROJECT_PATH/playwright.config.ts" << 'PLAYWRIGHT_EOF'
451
+ import { defineConfig, devices } from '@playwright/test';
452
+
453
+ export default defineConfig({
454
+ testDir: './tests/e2e',
455
+ fullyParallel: true,
456
+ forbidOnly: !!process.env.CI,
457
+ retries: process.env.CI ? 2 : 0,
458
+ reporter: [['html'], ['list']],
459
+ use: {
460
+ baseURL: 'http://localhost:4000',
461
+ trace: 'on-first-retry',
462
+ screenshot: 'only-on-failure',
463
+ },
464
+ projects: [
465
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
466
+ ],
467
+ webServer: [
468
+ {
469
+ command: 'pnpm dev:test:website',
470
+ port: 4000,
471
+ reuseExistingServer: !process.env.CI,
472
+ timeout: 30_000,
473
+ },
474
+ ],
475
+ });
476
+ PLAYWRIGHT_EOF
477
+
478
+ # E2E example test
479
+ cat > "$PROJECT_PATH/tests/e2e/home.spec.ts" << 'E2E_EOF'
480
+ import { test, expect } from '@playwright/test';
481
+
482
+ test.describe('Home Page', () => {
483
+ test('loads successfully with correct content', async ({ page }) => {
484
+ await page.goto('/');
485
+ await expect(page).toHaveURL('/');
486
+ await expect(page.locator('h1')).toBeVisible();
487
+ await expect(page.locator('h1')).toContainText('Welcome');
488
+ });
489
+
490
+ test('has correct page title and metadata', async ({ page }) => {
491
+ await page.goto('/');
492
+ await expect(page).toHaveTitle(/My App/);
493
+ const viewport = page.viewportSize();
494
+ expect(viewport).toBeTruthy();
495
+ });
496
+ });
497
+
498
+ test.describe('Health API', () => {
499
+ test('returns ok status', async ({ request }) => {
500
+ const response = await request.get('/api/v1/health');
501
+ expect(response.status()).toBe(200);
502
+ const body = await response.json();
503
+ expect(body.status).toBe('ok');
504
+ expect(body.timestamp).toBeTruthy();
505
+ });
506
+ });
507
+ E2E_EOF
508
+
509
+ # Unit test example
510
+ cat > "$PROJECT_PATH/tests/unit/example.test.ts" << 'UNIT_EOF'
511
+ import { describe, it, expect } from 'vitest';
512
+
513
+ describe('Example test', () => {
514
+ it('basic math works', () => {
515
+ expect(1 + 1).toBe(2);
516
+ });
517
+
518
+ it('string operations work', () => {
519
+ const greeting = 'Hello, World!';
520
+ expect(greeting).toContain('Hello');
521
+ expect(greeting).toHaveLength(13);
522
+ });
523
+ });
524
+ UNIT_EOF
525
+
526
+ # ── Step 9: Create package.json ───────────────────────────────────────────────
527
+ progress "Creating package.json..."
528
+
529
+ cat > "$PROJECT_PATH/package.json" << PKGJSON_EOF
530
+ {
531
+ "name": "$PROJECT_NAME",
532
+ "version": "0.1.0",
533
+ "private": true,
534
+ "type": "module",
535
+ "scripts": {
536
+ "dev": "next dev -p 3000",
537
+ "dev:website": "next dev -p 3000",
538
+ "dev:api": "next dev -p 3001",
539
+ "dev:dashboard": "next dev -p 3002",
540
+ "dev:test:website": "PORT=4000 next dev -p 4000",
541
+ "dev:test:api": "PORT=4010 next dev -p 4010",
542
+ "build": "next build",
543
+ "start": "next start",
544
+ "typecheck": "tsc --noEmit",
545
+ "test": "pnpm test:unit && pnpm test:e2e",
546
+ "test:unit": "vitest run",
547
+ "test:unit:watch": "vitest",
548
+ "test:coverage": "vitest run --coverage",
549
+ "test:e2e": "pnpm test:kill-ports && playwright test",
550
+ "test:e2e:ui": "pnpm test:kill-ports && playwright test --ui",
551
+ "test:e2e:headed": "pnpm test:kill-ports && playwright test --headed",
552
+ "test:e2e:chromium": "pnpm test:kill-ports && playwright test --project=chromium",
553
+ "test:e2e:report": "playwright show-report",
554
+ "test:kill-ports": "lsof -ti:4000,4010,4020 | xargs kill -9 2>/dev/null || true",
555
+ "db:query": "tsx scripts/db-query.ts",
556
+ "db:query:list": "tsx scripts/db-query.ts --list",
557
+ "clean": "rm -rf .next coverage test-results playwright-report"
558
+ },
559
+ "dependencies": {
560
+ "strictdb": "^0.1.0",
561
+ "mongodb": "^6.5.0",
562
+ "next": "^15.0.0",
563
+ "react": "^19.0.0",
564
+ "react-dom": "^19.0.0"
565
+ },
566
+ "devDependencies": {
567
+ "@playwright/test": "^1.42.0",
568
+ "@types/node": "^20.0.0",
569
+ "@types/react": "^19.0.0",
570
+ "@types/react-dom": "^19.0.0",
571
+ "autoprefixer": "^10.4.0",
572
+ "postcss": "^8.4.0",
573
+ "tailwindcss": "^3.4.0",
574
+ "tsx": "^4.7.0",
575
+ "typescript": "^5.0.0",
576
+ "vitest": "^2.0.0"
577
+ }
578
+ }
579
+ PKGJSON_EOF
580
+
581
+ # ── Step 10: Create Dockerfile (multi-stage Next.js standalone) ────────────────
582
+ progress "Creating Dockerfile..."
583
+
584
+ cat > "$PROJECT_PATH/Dockerfile" << 'DOCKER_EOF'
585
+ # Stage 1: Dependencies
586
+ FROM node:20-alpine AS deps
587
+ WORKDIR /app
588
+
589
+ RUN corepack enable && corepack prepare pnpm@latest --activate
590
+
591
+ COPY package.json pnpm-lock.yaml* ./
592
+ RUN pnpm install --frozen-lockfile || pnpm install
593
+
594
+ # Stage 2: Builder
595
+ FROM node:20-alpine AS builder
596
+ WORKDIR /app
597
+
598
+ RUN corepack enable && corepack prepare pnpm@latest --activate
599
+
600
+ COPY --from=deps /app/node_modules ./node_modules
601
+ COPY . .
602
+
603
+ # Build args for Next.js public env vars (baked at build time)
604
+ ARG NEXT_PUBLIC_RYBBIT_SITE_ID
605
+ ARG NEXT_PUBLIC_RYBBIT_URL
606
+ ENV NEXT_PUBLIC_RYBBIT_SITE_ID=$NEXT_PUBLIC_RYBBIT_SITE_ID
607
+ ENV NEXT_PUBLIC_RYBBIT_URL=$NEXT_PUBLIC_RYBBIT_URL
608
+
609
+ RUN pnpm build
610
+
611
+ # Stage 3: Runner
612
+ FROM node:20-alpine AS runner
613
+ WORKDIR /app
614
+ ENV NODE_ENV=production
615
+
616
+ RUN addgroup --system --gid 1001 appgroup && \
617
+ adduser --system --uid 1001 appuser
618
+
619
+ COPY --from=builder --chown=appuser:appgroup /app/.next/standalone ./
620
+ COPY --from=builder --chown=appuser:appgroup /app/.next/static ./.next/static
621
+ COPY --from=builder --chown=appuser:appgroup /app/public ./public
622
+
623
+ USER appuser
624
+ EXPOSE 3000
625
+ ENV PORT=3000
626
+ ENV HOSTNAME="0.0.0.0"
627
+
628
+ CMD ["node", "server.js"]
629
+ DOCKER_EOF
630
+
631
+ # ── Step 11: Create CI workflow ────────────────────────────────────────────────
632
+ progress "Creating GitHub Actions CI..."
633
+
634
+ cat > "$PROJECT_PATH/.github/workflows/ci.yml" << 'CI_EOF'
635
+ name: CI
636
+
637
+ on:
638
+ push:
639
+ branches: [main]
640
+ pull_request:
641
+ branches: [main]
642
+
643
+ jobs:
644
+ test:
645
+ runs-on: ubuntu-latest
646
+
647
+ steps:
648
+ - uses: actions/checkout@v4
649
+
650
+ - uses: pnpm/action-setup@v2
651
+ with:
652
+ version: latest
653
+
654
+ - uses: actions/setup-node@v4
655
+ with:
656
+ node-version: 20
657
+ cache: pnpm
658
+
659
+ - run: pnpm install --frozen-lockfile
660
+
661
+ - name: Type check
662
+ run: pnpm typecheck
663
+
664
+ - name: Unit tests
665
+ run: pnpm test:unit
666
+
667
+ - name: Install Playwright browsers
668
+ run: pnpm exec playwright install --with-deps chromium
669
+
670
+ - name: E2E tests
671
+ run: pnpm test:e2e
672
+
673
+ - name: Upload test results
674
+ if: failure()
675
+ uses: actions/upload-artifact@v4
676
+ with:
677
+ name: test-results
678
+ path: |
679
+ playwright-report/
680
+ test-results/
681
+ CI_EOF
682
+
683
+ # ── Step 12: Create CLAUDE.md (comprehensive, all rules) ──────────────────────
684
+ progress "Creating CLAUDE.md (all rules)..."
685
+
686
+ cat > "$PROJECT_PATH/CLAUDE.md" << 'CLAUDEMD_EOF'
687
+ # CLAUDE.md — Project Instructions
688
+
689
+ ---
690
+
691
+ ## Quick Reference — Scripts
692
+
693
+ | Command | What it does |
694
+ |---------|-------------|
695
+ | `pnpm dev` | Start dev server on port 3000 |
696
+ | `pnpm build` | Build for production |
697
+ | `pnpm start` | Run production build |
698
+ | `pnpm typecheck` | TypeScript type-check only |
699
+ | **Testing** | |
700
+ | `pnpm test` | Run ALL tests (unit + E2E) |
701
+ | `pnpm test:unit` | Unit/integration tests (Vitest) |
702
+ | `pnpm test:unit:watch` | Unit tests in watch mode |
703
+ | `pnpm test:coverage` | Unit tests with coverage |
704
+ | `pnpm test:e2e` | E2E tests (kills test ports first) |
705
+ | `pnpm test:e2e:ui` | E2E with Playwright UI |
706
+ | `pnpm test:e2e:headed` | E2E with visible browser |
707
+ | `pnpm test:kill-ports` | Kill test ports (4000, 4010, 4020) |
708
+ | **Database** | |
709
+ | `pnpm db:query <name>` | Run a dev/test database query |
710
+ | `pnpm db:query:list` | List all registered queries |
711
+
712
+ ---
713
+
714
+ ## Critical Rules
715
+
716
+ ### 0. NEVER Publish Sensitive Data
717
+
718
+ - NEVER commit passwords, API keys, tokens, or secrets to git/npm/docker
719
+ - NEVER commit `.env` files — ALWAYS verify `.env` is in `.gitignore`
720
+ - Before ANY commit: verify no secrets are included
721
+ - NEVER output secrets in suggestions, logs, or responses
722
+
723
+ ### 1. TypeScript Always
724
+
725
+ - ALWAYS use TypeScript for new files (strict mode)
726
+ - NEVER use `any` unless absolutely necessary and documented why
727
+ - When editing JavaScript files, convert to TypeScript first
728
+ - Types are specs — they tell you what functions accept and return
729
+
730
+ ### 2. API Versioning
731
+
732
+ ```
733
+ CORRECT: /api/v1/users
734
+ WRONG: /api/users
735
+ ```
736
+
737
+ Every API endpoint MUST use `/api/v1/` prefix. No exceptions.
738
+
739
+ ### 3. Database Access — StrictDB
740
+
741
+ **ALL database access uses StrictDB directly. No exceptions.**
742
+
743
+ - Install `strictdb` + your driver, use `StrictDB.create()` at app startup
744
+ - NEVER import native database drivers (`mongodb`, `pg`, etc.) directly
745
+ - Share a single StrictDB instance across the application
746
+ - All query inputs are automatically sanitized against injection
747
+
748
+ **Test queries go through `scripts/db-query.ts`:**
749
+ 1. Create a query file in `scripts/queries/<name>.ts`
750
+ 2. Register it in `scripts/db-query.ts`
751
+ 3. NEVER create standalone scripts or inline queries in `src/`
752
+
753
+ ### 4. Testing — Explicit Success Criteria
754
+
755
+ - ALWAYS define explicit success criteria for E2E tests
756
+ - "Page loads" is NOT a success criterion
757
+ - Every E2E test MUST verify: URL, visible elements, data displayed
758
+ - Minimum 3 assertions per test
759
+
760
+ ```typescript
761
+ // CORRECT
762
+ await expect(page).toHaveURL('/dashboard');
763
+ await expect(page.locator('h1')).toContainText('Welcome');
764
+ await expect(page.locator('[data-testid="user"]')).toContainText('test@example.com');
765
+
766
+ // WRONG — no assertions
767
+ await page.goto('/dashboard');
768
+ ```
769
+
770
+ ### 5. NEVER Hardcode Credentials
771
+
772
+ - ALWAYS use environment variables for secrets
773
+ - NEVER put API keys, passwords, or tokens directly in code
774
+ - NEVER hardcode connection strings — use environment variables from .env
775
+
776
+ ### 6. ALWAYS Ask Before Deploying
777
+
778
+ - NEVER auto-deploy, even if the fix seems simple
779
+ - NEVER assume approval — wait for explicit "yes, deploy"
780
+
781
+ ### 7. Quality Gates
782
+
783
+ - No file > 300 lines (split if larger)
784
+ - No function > 50 lines (extract helpers)
785
+ - All tests must pass before committing
786
+ - TypeScript must compile with no errors
787
+
788
+ ### 8. Parallelize Independent Awaits
789
+
790
+ ```typescript
791
+ // CORRECT — independent operations in parallel
792
+ const [users, products] = await Promise.all([getUsers(), getProducts()]);
793
+
794
+ // WRONG — sequential when independent
795
+ const users = await getUsers();
796
+ const products = await getProducts();
797
+ ```
798
+
799
+ ### 9. Git Workflow — NEVER Work Directly on Main
800
+
801
+ **Auto-branch hook is ON by default.** ALWAYS branch BEFORE editing any files:
802
+
803
+ ```bash
804
+ git branch --show-current
805
+ # If on main → create a feature branch IMMEDIATELY:
806
+ git checkout -b feat/<task-name>
807
+ ```
808
+
809
+ ### 10. Docker Push Gate
810
+
811
+ When enabled, ANY `docker push` is BLOCKED until the image passes local verification.
812
+
813
+ ---
814
+
815
+ ## Service Ports (FIXED)
816
+
817
+ | Service | Dev Port | Test Port |
818
+ |---------|----------|-----------|
819
+ | Website | 3000 | 4000 |
820
+ | API | 3001 | 4010 |
821
+ | Dashboard | 3002 | 4020 |
822
+
823
+ ---
824
+
825
+ ## When Something Seems Wrong
826
+
827
+ - Missing UI element? → Check feature gates BEFORE assuming bug
828
+ - Empty data? → Check if services are running BEFORE assuming broken
829
+ - 404 error? → Check service separation BEFORE adding endpoint
830
+ - Auth failing? → Check which auth system BEFORE debugging
831
+ - Test failing? → Read the error message fully BEFORE changing code
832
+
833
+ ---
834
+
835
+ ## Project Documentation
836
+
837
+ | Document | Purpose | When to Read |
838
+ |----------|---------|--------------|
839
+ | `project-docs/ARCHITECTURE.md` | System overview & data flow | Before architectural changes |
840
+ | `project-docs/INFRASTRUCTURE.md` | Deployment details | Before environment changes |
841
+ | `project-docs/DECISIONS.md` | Architectural decisions | Before proposing alternatives |
842
+
843
+ **ALWAYS read relevant docs before making cross-service changes.**
844
+
845
+ ---
846
+
847
+ ## Workflow Preferences
848
+
849
+ - Quality over speed — if unsure, ask before executing
850
+ - Plan first, code second — use plan mode for non-trivial tasks
851
+ - One task, one chat — `/clear` between unrelated tasks
852
+ - When testing: queue observations, fix in batch (not one at a time)
853
+
854
+ ---
855
+
856
+ ## Naming — NEVER Rename Mid-Project
857
+
858
+ If you must rename packages, modules, or key variables:
859
+
860
+ 1. Create a checklist of ALL files and references first
861
+ 2. Use IDE semantic rename (not search-and-replace)
862
+ 3. Full project search for old name after renaming
863
+ 4. Check: .md files, .txt files, .env files, comments, strings, paths
864
+ 5. Start a FRESH Claude session after renaming
865
+ CLAUDEMD_EOF
866
+
867
+ cat > "$PROJECT_PATH/CLAUDE.local.md" << 'LOCALMD_EOF'
868
+ # CLAUDE.local.md — Personal Overrides
869
+
870
+ > **This file is gitignored.** It's for YOUR personal preferences — things that shouldn't be shared with the team.
871
+ >
872
+ > **When to use this vs CLAUDE.md:**
873
+ > - `CLAUDE.md` = team rules (checked into git, everyone follows them)
874
+ > - `CLAUDE.local.md` = personal preferences (gitignored, only affects you)
875
+
876
+ ---
877
+
878
+ ## My Identity
879
+
880
+ - GitHub: YourUsername
881
+ - SSH: `git@github.com:YourUsername/<repo>.git`
882
+
883
+ ## Communication Style
884
+
885
+ <!-- Uncomment the style that fits you: -->
886
+ <!-- - Respond concisely — I prefer terse explanations -->
887
+ <!-- - Be thorough — explain your reasoning in detail -->
888
+ <!-- - Show me the code first, explain after -->
889
+
890
+ ## Commit Preferences
891
+
892
+ - When creating commits, use conventional commit format (feat:, fix:, docs:, etc.)
893
+
894
+ ## Local Environment
895
+
896
+ - Node version: 20.x
897
+ - Package manager: pnpm
898
+ - OS: (your OS here)
899
+ LOCALMD_EOF
900
+
901
+ # ── Step 13: Create project templates + config files ──────────────────────────
902
+ progress "Creating project docs + config files..."
903
+
904
+ cat > "$PROJECT_PATH/project-docs/ARCHITECTURE.md" << 'ARCH_EOF'
905
+ # Architecture
906
+
907
+ > System overview and data flow for the project.
908
+
909
+ ## Overview
910
+
911
+ <!-- Describe the high-level architecture here -->
912
+
913
+ ## Components
914
+
915
+ <!-- List major components and their responsibilities -->
916
+
917
+ ## Data Flow
918
+
919
+ <!-- Describe how data moves through the system -->
920
+
921
+ ## Dependencies
922
+
923
+ <!-- List external services and dependencies -->
924
+ ARCH_EOF
925
+
926
+ cat > "$PROJECT_PATH/project-docs/INFRASTRUCTURE.md" << 'INFRA_EOF'
927
+ # Infrastructure
928
+
929
+ > Deployment and environment details.
930
+
931
+ ## Environments
932
+
933
+ <!-- List environments: development, staging, production -->
934
+
935
+ ## Deployment
936
+
937
+ <!-- Describe the deployment process -->
938
+
939
+ ## Environment Variables
940
+
941
+ <!-- List required environment variables and their purpose -->
942
+
943
+ ## Monitoring
944
+
945
+ <!-- Describe monitoring and alerting setup -->
946
+ INFRA_EOF
947
+
948
+ cat > "$PROJECT_PATH/project-docs/DECISIONS.md" << 'DEC_EOF'
949
+ # Architectural Decisions
950
+
951
+ > Record of key technical decisions and their rationale.
952
+
953
+ ## Template
954
+
955
+ ### Decision: [Title]
956
+ - **Date:** YYYY-MM-DD
957
+ - **Status:** Accepted / Superseded / Deprecated
958
+ - **Context:** What prompted the decision
959
+ - **Decision:** What was decided
960
+ - **Consequences:** What are the trade-offs
961
+ - **Alternatives considered:** What else was evaluated
962
+
963
+ ---
964
+
965
+ <!-- Add decisions below -->
966
+ DEC_EOF
967
+
968
+ cat > "$PROJECT_PATH/tests/CHECKLIST.md" << 'CHECK_EOF'
969
+ # Test Checklist
970
+
971
+ > Track what needs testing and what's been verified.
972
+
973
+ ## Test Coverage
974
+
975
+ | Area | Unit Tests | Integration Tests | E2E Tests | Status |
976
+ |------|-----------|-------------------|-----------|--------|
977
+ | <!-- feature --> | <!-- yes/no --> | <!-- yes/no --> | <!-- yes/no --> | <!-- pending/done --> |
978
+
979
+ ## Manual Test Cases
980
+
981
+ - [ ] <!-- Describe manual test case -->
982
+
983
+ ## Regression Tests
984
+
985
+ - [ ] <!-- Describe regression test case -->
986
+ CHECK_EOF
987
+
988
+ cat > "$PROJECT_PATH/tests/ISSUES_FOUND.md" << 'ISSUES_EOF'
989
+ # Issues Found During Testing
990
+
991
+ > Track bugs and issues discovered during testing sessions.
992
+
993
+ ## Template
994
+
995
+ ### Issue: [Title]
996
+ - **Found:** YYYY-MM-DD
997
+ - **Severity:** Critical / High / Medium / Low
998
+ - **Status:** Open / Fixed / Won't Fix
999
+ - **Description:** What happened
1000
+ - **Steps to reproduce:** How to trigger the issue
1001
+ - **Expected behavior:** What should happen
1002
+ - **Actual behavior:** What actually happened
1003
+ - **Fix:** How it was resolved (if fixed)
1004
+
1005
+ ---
1006
+
1007
+ <!-- Add issues below -->
1008
+ ISSUES_EOF
1009
+
1010
+ # robots.txt + sitemap.xml
1011
+ cat > "$PROJECT_PATH/public/robots.txt" << 'ROBOTS_EOF'
1012
+ User-agent: *
1013
+ Allow: /
1014
+ Sitemap: https://example.com/sitemap.xml
1015
+ ROBOTS_EOF
1016
+
1017
+ cat > "$PROJECT_PATH/public/sitemap.xml" << 'SITEMAP_EOF'
1018
+ <?xml version="1.0" encoding="UTF-8"?>
1019
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
1020
+ <url>
1021
+ <loc>https://example.com/</loc>
1022
+ <priority>1.0</priority>
1023
+ </url>
1024
+ </urlset>
1025
+ SITEMAP_EOF
1026
+
1027
+ # JSON-LD structured data component
1028
+ cat > "$PROJECT_PATH/src/app/json-ld.tsx" << 'JSONLD_EOF'
1029
+ export function JsonLd() {
1030
+ const structuredData = {
1031
+ '@context': 'https://schema.org',
1032
+ '@type': 'WebSite',
1033
+ name: 'My App',
1034
+ url: process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com',
1035
+ description: 'Built with Claude Code Mastery Starter Kit',
1036
+ };
1037
+
1038
+ return (
1039
+ <script
1040
+ type="application/ld+json"
1041
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
1042
+ />
1043
+ );
1044
+ }
1045
+ JSONLD_EOF
1046
+
1047
+ # ── Step 14: Create .env + .gitignore + .dockerignore + README ────────────────
1048
+ progress "Creating .env, .gitignore, .dockerignore, README..."
1049
+
1050
+ touch "$PROJECT_PATH/.env"
1051
+
1052
+ cat > "$PROJECT_PATH/.env.example" << 'ENVEX_EOF'
1053
+ # Application
1054
+ NODE_ENV=development
1055
+ PORT=3000
1056
+
1057
+ # StrictDB
1058
+ STRICTDB_URI=mongodb+srv://user:password@cluster.mongodb.net/mydb?retryWrites=true&w=majority
1059
+
1060
+ # Rybbit Analytics
1061
+ NEXT_PUBLIC_RYBBIT_SITE_ID=your_rybbit_site_id
1062
+ NEXT_PUBLIC_RYBBIT_URL=https://app.rybbit.io
1063
+
1064
+ # Docker Hub
1065
+ DOCKER_HUB_USER=your_docker_username
1066
+ DOCKER_IMAGE_NAME=your_docker_username/your_app_name
1067
+
1068
+ # Dokploy Deployment
1069
+ DOKPLOY_URL=http://your-vps-ip:3000/api
1070
+ DOKPLOY_API_KEY=your_dokploy_api_key
1071
+ DOKPLOY_APP_ID=your_application_id
1072
+ DOKPLOY_REFRESH_TOKEN=your_webhook_refresh_token
1073
+
1074
+ # RuleCatch (optional)
1075
+ RULECATCH_API_KEY=dc_your_api_key_here
1076
+ RULECATCH_REGION=us
1077
+ ENVEX_EOF
1078
+
1079
+ cat > "$PROJECT_PATH/.gitignore" << 'GI_EOF'
1080
+ # Environment
1081
+ .env
1082
+ .env.*
1083
+ .env.local
1084
+
1085
+ # Dependencies
1086
+ node_modules/
1087
+
1088
+ # Build output
1089
+ .next/
1090
+ dist/
1091
+ out/
1092
+
1093
+ # Test artifacts
1094
+ coverage/
1095
+ test-results/
1096
+ playwright-report/
1097
+
1098
+ # IDE
1099
+ .idea/
1100
+ .vscode/
1101
+ *.swp
1102
+ *.swo
1103
+
1104
+ # OS
1105
+ .DS_Store
1106
+ Thumbs.db
1107
+
1108
+ # Claude local overrides
1109
+ CLAUDE.local.md
1110
+
1111
+ # Temporary AI research files
1112
+ _ai_temp/
1113
+ GI_EOF
1114
+
1115
+ cat > "$PROJECT_PATH/.dockerignore" << 'DI_EOF'
1116
+ .env
1117
+ .env.*
1118
+ .git/
1119
+ node_modules/
1120
+ .next/
1121
+ dist/
1122
+ coverage/
1123
+ test-results/
1124
+ playwright-report/
1125
+ *.md
1126
+ !README.md
1127
+ _ai_temp/
1128
+ DI_EOF
1129
+
1130
+ cat > "$PROJECT_PATH/README.md" << README_EOF
1131
+ # $PROJECT_NAME
1132
+
1133
+ > Scaffolded with [Claude Code Mastery Starter Kit](https://github.com/TheDecipherist/claude-code-mastery-project-starter-kit) (default profile)
1134
+
1135
+ ## Tech Stack
1136
+
1137
+ - **Framework:** Next.js (App Router)
1138
+ - **Language:** TypeScript (strict mode)
1139
+ - **Database:** StrictDB (unified driver)
1140
+ - **Styling:** Tailwind CSS
1141
+ - **Testing:** Vitest (unit) + Playwright (E2E)
1142
+ - **Deployment:** Docker (multi-stage, standalone)
1143
+
1144
+ ## Getting Started
1145
+
1146
+ \`\`\`bash
1147
+ pnpm install
1148
+ pnpm dev
1149
+ \`\`\`
1150
+
1151
+ Open [http://localhost:3000](http://localhost:3000) in your browser.
1152
+
1153
+ ## Available Commands
1154
+
1155
+ Run \`/help\` in Claude Code to see all 16 available commands.
1156
+
1157
+ ## Scripts
1158
+
1159
+ | Command | Description |
1160
+ |---------|-------------|
1161
+ | \`pnpm dev\` | Start dev server |
1162
+ | \`pnpm build\` | Build for production |
1163
+ | \`pnpm test\` | Run all tests |
1164
+ | \`pnpm test:unit\` | Unit tests |
1165
+ | \`pnpm test:e2e\` | E2E tests |
1166
+ | \`pnpm db:query <name>\` | Run a database query |
1167
+ | \`pnpm db:query:list\` | List available queries |
1168
+
1169
+ ## Project Documentation
1170
+
1171
+ | Document | Purpose |
1172
+ |----------|---------|
1173
+ | \`project-docs/ARCHITECTURE.md\` | System overview & data flow |
1174
+ | \`project-docs/INFRASTRUCTURE.md\` | Deployment details |
1175
+ | \`project-docs/DECISIONS.md\` | Architectural decisions |
1176
+ README_EOF
1177
+
1178
+ # ── Step 15: Git init + pnpm install + register project ───────────────────────
1179
+ progress "Git init + pnpm install + registering project..."
1180
+
1181
+ git -C "$PROJECT_PATH" init -q
1182
+ git -C "$PROJECT_PATH" add -A
1183
+ git -C "$PROJECT_PATH" commit -q -m "Initial project scaffold (default profile)"
1184
+
1185
+ # Install dependencies
1186
+ cd "$PROJECT_PATH" && pnpm install --silent 2>/dev/null || true
1187
+
1188
+ # Register in project registry
1189
+ python3 << PYEOF
1190
+ import json, os
1191
+ from datetime import datetime, timezone
1192
+
1193
+ registry = "$REGISTRY"
1194
+ if os.path.exists(registry):
1195
+ with open(registry) as f:
1196
+ data = json.load(f)
1197
+ else:
1198
+ os.makedirs(os.path.dirname(registry), exist_ok=True)
1199
+ data = {"projects": []}
1200
+
1201
+ data["projects"].append({
1202
+ "name": "$PROJECT_NAME",
1203
+ "path": os.path.realpath("$PROJECT_PATH"),
1204
+ "profile": "default",
1205
+ "language": "node",
1206
+ "framework": "next",
1207
+ "database": "mongo",
1208
+ "createdAt": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
1209
+ })
1210
+
1211
+ with open(registry, "w") as f:
1212
+ json.dump(data, f, indent=2)
1213
+ f.write("\n")
1214
+ PYEOF
1215
+
1216
+ # ── Summary ────────────────────────────────────────────────────────────────────
1217
+ END_NS=$(date +%s%N)
1218
+ TOTAL_MS=$(( (END_NS - START_NS) / 1000000 ))
1219
+ FILE_COUNT=$(find "$PROJECT_PATH" -type f -not -path '*/.git/*' -not -path '*/node_modules/*' | wc -l)
1220
+
1221
+ # Format elapsed time
1222
+ if [ "$TOTAL_MS" -ge 1000 ]; then
1223
+ TOTAL_S=$((TOTAL_MS / 1000))
1224
+ TOTAL_FRAC=$(( (TOTAL_MS % 1000) / 100 ))
1225
+ TIME_STR="${TOTAL_S}.${TOTAL_FRAC}s"
1226
+ else
1227
+ TIME_STR="${TOTAL_MS}ms"
1228
+ fi
1229
+
1230
+ echo ""
1231
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1232
+ echo " Completed in ${TIME_STR}"
1233
+ echo " Created at: $PROJECT_PATH"
1234
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1235
+ echo ""
1236
+ echo " ${FILE_COUNT} files | 16 commands | 2 skills | 2 agents | 9 hooks"
1237
+ echo ""
1238
+ echo " Stack: Next.js + StrictDB + Tailwind + Docker"
1239
+ echo " Testing: Vitest (unit) + Playwright (E2E)"
1240
+ echo " CI: GitHub Actions"
1241
+ echo ""
1242
+ echo " Next steps:"
1243
+ echo " cd $PROJECT_PATH"
1244
+ echo " pnpm dev # Start dev server"
1245
+ echo " claude # Start Claude Code — run /help to see commands"
1246
+ echo ""
1247
+ echo " Configure environment:"
1248
+ echo " cp .env.example .env"
1249
+ echo " # Edit .env with your StrictDB URI, Rybbit ID, etc."
1250
+ echo " # Or run /setup in Claude for interactive configuration"
1251
+ echo ""