← back to ClawCoder

docs/superpowers/plans/2026-03-23-clawcoder-implementation.md

1610 lines

# ClawCoder Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Build a Next.js web app that generates, analyzes, and optimizes CLAUDE.md configurations using 15 age+skill profiles and a 12-criteria scoring engine with mass-update support.

**Architecture:** Next.js 15 App Router with server actions for API, PostgreSQL for persistence, Zustand for client state. Profile data lives as typed JSON seed files. The analyzer runs server-side with Claude API structured outputs for intelligent suggestions. The UI uses Tailwind CSS + Framer Motion with accessibility-first defaults.

**Tech Stack:** Next.js 15, TypeScript, Tailwind CSS, Framer Motion, PostgreSQL, Zustand, Claude API (@anthropic-ai/sdk), NextAuth.js

**Spec:** `docs/superpowers/specs/2026-03-23-clawcoder-profiles-analyzer-design.md`

---

## File Structure

```
/root/Projects/ClawCoder/
├── package.json
├── tsconfig.json
├── tailwind.config.ts
├── next.config.ts
├── .env.local                          # DB URL, API keys (gitignored)
├── prisma/
│   ├── schema.prisma                   # DB schema (profiles, analyses, users)
│   └── seed.ts                         # Seed 15 profiles into DB
├── src/
│   ├── app/
│   │   ├── layout.tsx                  # Root layout + theme provider
│   │   ├── page.tsx                    # Landing / profile selector
│   │   ├── onboarding/
│   │   │   └── page.tsx                # Full-screen wizard
│   │   ├── builder/
│   │   │   └── page.tsx                # Builder with rail + preview
│   │   ├── analyzer/
│   │   │   └── page.tsx                # Upload + analyze + suggestions
│   │   ├── mass-update/
│   │   │   └── page.tsx                # Table view + bulk actions
│   │   └── api/
│   │       ├── profiles/route.ts       # GET profiles, GET profile by slug
│   │       ├── analyze/route.ts        # POST analyze CLAUDE.md content
│   │       ├── generate/route.ts       # POST generate from profile + answers
│   │       └── suggestions/route.ts    # POST apply selected suggestions
│   ├── lib/
│   │   ├── profiles/
│   │   │   ├── types.ts                # Profile TypeScript types
│   │   │   ├── data.ts                 # 15 profile definitions (source of truth)
│   │   │   ├── matcher.ts              # Detect profile from existing CLAUDE.md
│   │   │   └── templates.ts            # Mustache templates per profile
│   │   ├── analyzer/
│   │   │   ├── types.ts                # Analysis types (criteria, scores, suggestions)
│   │   │   ├── scorer.ts               # 12-criteria scoring engine
│   │   │   ├── rules.ts                # Individual scoring rule implementations
│   │   │   ├── suggester.ts            # Generate actionable suggestions
│   │   │   └── applier.ts              # Apply suggestions to content
│   │   ├── generator/
│   │   │   ├── claude-md.ts            # Generate CLAUDE.md from profile + vars
│   │   │   ├── rules.ts                # Generate .claude/rules/*.md files
│   │   │   ├── hooks.ts                # Generate .claude/settings.json hooks
│   │   │   └── bundle.ts               # Bundle all artifacts into ZIP
│   │   ├── db.ts                       # Prisma client singleton
│   │   └── store.ts                    # Zustand store (client state)
│   ├── components/
│   │   ├── ui/
│   │   │   ├── pill-button.tsx         # Large pill-shaped answer button
│   │   │   ├── back-next-bar.tsx       # XL sticky nav bar
│   │   │   ├── progress-indicator.tsx  # Step N of M
│   │   │   ├── radar-chart.tsx         # 12-axis scoring visualization
│   │   │   ├── diff-viewer.tsx         # Side-by-side diff
│   │   │   └── grade-badge.tsx         # Letter grade (A+ through F)
│   │   ├── onboarding/
│   │   │   ├── question-screen.tsx     # Full-screen question layout
│   │   │   └── pill-grid.tsx           # Grid of pill answer options
│   │   ├── builder/
│   │   │   ├── rail.tsx                # Left collapsible rail
│   │   │   ├── preview-pane.tsx        # Live CLAUDE.md preview
│   │   │   └── section-editor.tsx      # Edit individual sections
│   │   ├── analyzer/
│   │   │   ├── upload-zone.tsx         # Drag-drop + paste area
│   │   │   ├── report-card.tsx         # Pros/cons/suggestions display
│   │   │   ├── suggestion-card.tsx     # Single suggestion with checkbox
│   │   │   └── suggestion-list.tsx     # Selectable list of suggestions
│   │   └── mass-update/
│   │       ├── record-table.tsx        # Table with multi-select
│   │       └── bulk-actions-bar.tsx    # Floating bulk actions
│   └── tests/
│       ├── lib/
│       │   ├── scorer.test.ts          # Scoring engine tests
│       │   ├── rules.test.ts           # Individual rule tests
│       │   ├── matcher.test.ts         # Profile matcher tests
│       │   ├── applier.test.ts         # Suggestion applier tests
│       │   └── generator.test.ts       # CLAUDE.md generator tests
│       └── components/
│           ├── pill-button.test.tsx     # UI component tests
│           └── upload-zone.test.tsx     # Upload component tests
```

---

## Phase 1: Foundation — Profile Data + Template Engine

### Task 1: Initialize Next.js Project

**Files:**
- Create: `package.json`, `tsconfig.json`, `tailwind.config.ts`, `next.config.ts`, `.env.local`, `.gitignore`

- [ ] **Step 1: Scaffold Next.js with TypeScript + Tailwind**

```bash
cd /root/Projects/ClawCoder
npx create-next-app@latest . --typescript --tailwind --eslint --app --src-dir --no-import-alias
```

- [ ] **Step 2: Install dependencies**

```bash
npm install @anthropic-ai/sdk zustand framer-motion prisma @prisma/client mustache
npm install -D @types/mustache vitest @testing-library/react @testing-library/jest-dom jsdom
```

- [ ] **Step 3: Configure .env.local**

```bash
cat > .env.local << 'EOF'
DATABASE_URL="postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/clawcoder"
ANTHROPIC_API_KEY="placeholder"
NEXTAUTH_SECRET="clawcoder-dev-secret-change-in-prod"
EOF
```

- [ ] **Step 4: Configure vitest**

Create `vitest.config.ts`:
```typescript
import { defineConfig } from 'vitest/config'
import path from 'path'

export default defineConfig({
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: ['./src/tests/setup.ts'],
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
})
```

Create `src/tests/setup.ts`:
```typescript
import '@testing-library/jest-dom'
```

- [ ] **Step 5: Add test script to package.json**

Add to scripts: `"test": "vitest run", "test:watch": "vitest"`

- [ ] **Step 6: Verify setup builds**

```bash
npm run build
```
Expected: Build succeeds

- [ ] **Step 7: Commit**

```bash
git init && git add -A && git commit -m "feat: initialize Next.js project with TypeScript, Tailwind, Prisma, Vitest"
```

---

### Task 2: Define Profile Types + Data

**Files:**
- Create: `src/lib/profiles/types.ts`
- Create: `src/lib/profiles/data.ts`
- Test: `src/tests/lib/profiles.test.ts`

- [ ] **Step 1: Write the failing test**

Create `src/tests/lib/profiles.test.ts`:
```typescript
import { describe, it, expect } from 'vitest'
import { PROFILES, getProfile, getProfilesByAge, getProfilesBySkill } from '@/lib/profiles/data'
import type { Profile, AgeGroup, SkillLevel } from '@/lib/profiles/types'

describe('Profile Data', () => {
  it('has exactly 15 profiles', () => {
    expect(PROFILES).toHaveLength(15)
  })

  it('covers all 5 age groups x 3 skill levels', () => {
    const ageGroups: AgeGroup[] = ['teen', 'young_adult', 'professional', 'pre_senior', 'senior']
    const skillLevels: SkillLevel[] = ['beginner', 'intermediate', 'expert']
    for (const age of ageGroups) {
      for (const skill of skillLevels) {
        const match = PROFILES.find(p => p.ageGroup === age && p.skillLevel === skill)
        expect(match, `Missing profile for ${age}/${skill}`).toBeDefined()
      }
    }
  })

  it('each profile has required fields', () => {
    for (const p of PROFILES) {
      expect(p.slug).toBeTruthy()
      expect(p.displayName).toBeTruthy()
      expect(p.ui.fontSize).toBeGreaterThanOrEqual(14)
      expect(p.ui.touchTargetMin).toBeGreaterThanOrEqual(40)
      expect(p.template.slug).toBeTruthy()
      expect(p.template.content).toContain('{{project_name}}')
      expect(p.skillBundle).toBeInstanceOf(Array)
      expect(p.mcpServers).toBeInstanceOf(Array)
      expect(p.learningPath).toBeInstanceOf(Array)
      expect(p.learningPath.length).toBeGreaterThan(0)
    }
  })

  it('senior profiles have larger fonts than teen profiles', () => {
    const seniorBeginner = getProfile('senior-beginner')!
    const teenBeginner = getProfile('teen-beginner')!
    expect(seniorBeginner.ui.fontSize).toBeGreaterThan(teenBeginner.ui.fontSize)
  })

  it('beginner profiles have fewer MCP servers than expert', () => {
    const teenBeg = getProfile('teen-beginner')!
    const teenExp = getProfile('teen-expert')!
    expect(teenBeg.mcpServers.length).toBeLessThan(teenExp.mcpServers.length)
  })

  it('getProfilesByAge returns 3 profiles per age group', () => {
    expect(getProfilesByAge('teen')).toHaveLength(3)
    expect(getProfilesByAge('senior')).toHaveLength(3)
  })

  it('getProfilesBySkill returns 5 profiles per skill level', () => {
    expect(getProfilesBySkill('beginner')).toHaveLength(5)
    expect(getProfilesBySkill('expert')).toHaveLength(5)
  })
})
```

- [ ] **Step 2: Run test to verify it fails**

```bash
npx vitest run src/tests/lib/profiles.test.ts
```
Expected: FAIL — modules not found

- [ ] **Step 3: Create types**

Create `src/lib/profiles/types.ts`:
```typescript
export type AgeGroup = 'teen' | 'young_adult' | 'professional' | 'pre_senior' | 'senior'
export type SkillLevel = 'beginner' | 'intermediate' | 'expert'
export type MotionLevel = 'minimal' | 'reduced' | 'normal' | 'full'
export type ContrastLevel = 'standard' | 'enhanced' | 'high'
export type LayoutDensity = 'compact' | 'comfortable' | 'spacious'
export type ThemeMode = 'light' | 'dark' | 'auto'

export interface UIConfig {
  fontSize: number
  fontScale: number
  motionLevel: MotionLevel
  contrast: ContrastLevel
  touchTargetMin: number
  layoutDensity: LayoutDensity
  theme: ThemeMode
  reducedMotion?: boolean
  hapticFeedback?: boolean
}

export interface TemplateConfig {
  slug: string
  verbosity: 'minimal' | 'low' | 'medium' | 'medium-high' | 'high' | 'maximum'
  safety: 'standard' | 'high' | 'maximum' | 'policy-driven'
  targetLines: [number, number]
  content: string
}

export interface LearningResource {
  title: string
  url: string
  type: 'video' | 'course' | 'article' | 'repo' | 'docs'
  level: SkillLevel
}

export interface SkillRef {
  name: string
  description: string
}

export interface MCPServerRef {
  name: string
  package: string
  installCmd: string
  requiresKey: boolean
  trustLevel: 'official' | 'verified' | 'community'
}

export interface SubagentRef {
  name: string
  model: 'haiku' | 'sonnet' | 'opus' | 'inherit'
  tools: string[]
}

export interface HookConfig {
  PreToolUse?: HookRule[]
  PostToolUse?: HookRule[]
  Stop?: HookRule[]
  SessionStart?: HookRule[]
  PreCompact?: HookRule[]
}

export interface HookRule {
  matcher: string
  hooks: { type: 'command'; command: string }[]
}

export interface Profile {
  slug: string
  ageGroup: AgeGroup
  skillLevel: SkillLevel
  displayName: string
  persona: string
  ui: UIConfig
  template: TemplateConfig
  skillBundle: SkillRef[]
  mcpServers: MCPServerRef[]
  hooks: HookConfig
  subagents: SubagentRef[]
  learningPath: LearningResource[]
  rulesFiles: { path: string; content: string }[]
}
```

- [ ] **Step 4: Create profile data (all 15 profiles)**

Create `src/lib/profiles/data.ts` with all 15 profiles from the spec. Each profile is a typed `Profile` object containing the full configuration from the design spec (UI config, template content, skill bundle, MCP servers, hooks, subagents, learning path).

This file will be ~800-1000 lines. It is the single source of truth for all profile data.

Export functions:
```typescript
export const PROFILES: Profile[] = [/* all 15 */]
export function getProfile(slug: string): Profile | undefined
export function getProfilesByAge(age: AgeGroup): Profile[]
export function getProfilesBySkill(skill: SkillLevel): Profile[]
```

- [ ] **Step 5: Run tests to verify they pass**

```bash
npx vitest run src/tests/lib/profiles.test.ts
```
Expected: ALL PASS

- [ ] **Step 6: Commit**

```bash
git add src/lib/profiles/ src/tests/lib/profiles.test.ts
git commit -m "feat: define 15 user profiles with types and data"
```

---

### Task 3: CLAUDE.md Template Engine

**Files:**
- Create: `src/lib/profiles/templates.ts`
- Create: `src/lib/generator/claude-md.ts`
- Create: `src/lib/generator/rules.ts`
- Create: `src/lib/generator/hooks.ts`
- Create: `src/lib/generator/bundle.ts`
- Test: `src/tests/lib/generator.test.ts`

- [ ] **Step 1: Write the failing test**

Create `src/tests/lib/generator.test.ts`:
```typescript
import { describe, it, expect } from 'vitest'
import { generateClaudeMd } from '@/lib/generator/claude-md'
import { generateRules } from '@/lib/generator/rules'
import { generateHooksConfig } from '@/lib/generator/hooks'
import { getProfile } from '@/lib/profiles/data'

const vars = {
  project_name: 'My App',
  project_description: 'A simple todo app',
  language: 'TypeScript',
  stack: 'Next.js + TypeScript',
  install_cmd: 'npm install',
  dev_cmd: 'npm run dev',
  test_cmd: 'npm test',
  run_cmd: 'npm start',
  lint_cmd: 'npm run lint',
  build_cmd: 'npm run build',
  indent_style: '2 spaces',
  naming_convention: 'camelCase',
}

describe('CLAUDE.md Generator', () => {
  it('generates valid markdown from teen-beginner profile', () => {
    const profile = getProfile('teen-beginner')!
    const result = generateClaudeMd(profile, vars)
    expect(result).toContain('# CLAUDE.md')
    expect(result).toContain('My App')
    expect(result).toContain('npm test')
    expect(result).not.toContain('{{')
  })

  it('generates shorter output for expert profiles', () => {
    const beginner = generateClaudeMd(getProfile('teen-beginner')!, vars)
    const expert = generateClaudeMd(getProfile('teen-expert')!, vars)
    expect(expert.split('\n').length).toBeLessThanOrEqual(beginner.split('\n').length)
  })

  it('generates rules files for intermediate+ profiles', () => {
    const profile = getProfile('young-adult-intermediate')!
    const rules = generateRules(profile, vars)
    expect(rules.length).toBeGreaterThan(0)
    expect(rules[0].path).toMatch(/^\.claude\/rules\//)
    expect(rules[0].content).toBeTruthy()
  })

  it('generates hooks config for profiles with hooks', () => {
    const profile = getProfile('teen-beginner')!
    const hooks = generateHooksConfig(profile)
    expect(hooks.PreToolUse).toBeDefined()
    expect(hooks.PreToolUse!.length).toBeGreaterThan(0)
  })

  it('expert profiles generate more rules files than beginner', () => {
    const beg = generateRules(getProfile('teen-beginner')!, vars)
    const exp = generateRules(getProfile('professional-expert')!, vars)
    expect(exp.length).toBeGreaterThanOrEqual(beg.length)
  })
})
```

- [ ] **Step 2: Run test to verify it fails**

```bash
npx vitest run src/tests/lib/generator.test.ts
```
Expected: FAIL — modules not found

- [ ] **Step 3: Implement template rendering**

Create `src/lib/profiles/templates.ts`:
```typescript
import Mustache from 'mustache'

// Disable HTML escaping for markdown output
Mustache.escape = (text: string) => text

export function renderTemplate(template: string, vars: Record<string, string>): string {
  return Mustache.render(template, vars)
}
```

- [ ] **Step 4: Implement CLAUDE.md generator**

Create `src/lib/generator/claude-md.ts`:
```typescript
import type { Profile } from '@/lib/profiles/types'
import { renderTemplate } from '@/lib/profiles/templates'

export function generateClaudeMd(
  profile: Profile,
  vars: Record<string, string>
): string {
  return renderTemplate(profile.template.content, vars)
}
```

- [ ] **Step 5: Implement rules generator**

Create `src/lib/generator/rules.ts`:
```typescript
import type { Profile } from '@/lib/profiles/types'
import { renderTemplate } from '@/lib/profiles/templates'

export interface GeneratedRule {
  path: string
  content: string
}

export function generateRules(
  profile: Profile,
  vars: Record<string, string>
): GeneratedRule[] {
  return profile.rulesFiles.map(rule => ({
    path: rule.path,
    content: renderTemplate(rule.content, vars),
  }))
}
```

- [ ] **Step 6: Implement hooks generator**

Create `src/lib/generator/hooks.ts`:
```typescript
import type { Profile, HookConfig } from '@/lib/profiles/types'

export function generateHooksConfig(profile: Profile): HookConfig {
  return { ...profile.hooks }
}

export function generateSettingsJson(profile: Profile): string {
  return JSON.stringify({ hooks: profile.hooks }, null, 2)
}
```

- [ ] **Step 7: Implement bundle generator**

Create `src/lib/generator/bundle.ts`:
```typescript
import type { Profile } from '@/lib/profiles/types'
import { generateClaudeMd } from './claude-md'
import { generateRules } from './rules'
import { generateHooksConfig } from './hooks'

export interface Bundle {
  claudeMd: string
  rules: { path: string; content: string }[]
  settingsJson: string
  profile: {
    slug: string
    skillBundle: string[]
    mcpServers: string[]
    subagents: string[]
    learningPath: { title: string; url: string }[]
  }
}

export function generateBundle(
  profile: Profile,
  vars: Record<string, string>
): Bundle {
  return {
    claudeMd: generateClaudeMd(profile, vars),
    rules: generateRules(profile, vars),
    settingsJson: JSON.stringify({ hooks: generateHooksConfig(profile) }, null, 2),
    profile: {
      slug: profile.slug,
      skillBundle: profile.skillBundle.map(s => s.name),
      mcpServers: profile.mcpServers.map(m => m.name),
      subagents: profile.subagents.map(a => a.name),
      learningPath: profile.learningPath.map(l => ({ title: l.title, url: l.url })),
    },
  }
}
```

- [ ] **Step 8: Run tests to verify they pass**

```bash
npx vitest run src/tests/lib/generator.test.ts
```
Expected: ALL PASS

- [ ] **Step 9: Commit**

```bash
git add src/lib/generator/ src/lib/profiles/templates.ts src/tests/lib/generator.test.ts
git commit -m "feat: template engine for CLAUDE.md, rules, hooks, and bundle generation"
```

---

## Phase 2: CLAUDE.md Analyzer — Scoring Engine

### Task 4: Scoring Engine — 12 Criteria

**Files:**
- Create: `src/lib/analyzer/types.ts`
- Create: `src/lib/analyzer/rules.ts`
- Create: `src/lib/analyzer/scorer.ts`
- Test: `src/tests/lib/scorer.test.ts`

- [ ] **Step 1: Write the failing test**

Create `src/tests/lib/scorer.test.ts`:
```typescript
import { describe, it, expect } from 'vitest'
import { analyzeClaudeMd } from '@/lib/analyzer/scorer'
import type { AnalysisResult } from '@/lib/analyzer/types'

const goodClaudeMd = `# CLAUDE.md

## Workflow
- Make small, reviewable changes.
- Run tests after behavior changes.

## Safety
- Don't modify .env or credentials.
- Don't force push.

## Commands
- Install: npm install
- Dev: npm run dev
- Test: npm test
- Lint: npm run lint

## Code style
Follow .claude/rules/code-style.md

## Architecture
@docs/ARCHITECTURE.md
`

const badClaudeMd = `# My Project
This is my project. It does stuff.
I like coding.
` + 'x\n'.repeat(250) // 250+ lines of junk

const secretsClaudeMd = `# CLAUDE.md
API_KEY=sk-abc123def456
password = hunter2
`

describe('CLAUDE.md Scorer', () => {
  it('scores a well-structured CLAUDE.md highly', () => {
    const result = analyzeClaudeMd(goodClaudeMd)
    expect(result.overallScore).toBeGreaterThan(60)
    expect(result.letterGrade).toMatch(/^[ABC]/)
  })

  it('scores a bloated CLAUDE.md poorly on line count', () => {
    const result = analyzeClaudeMd(badClaudeMd)
    const lineCountCrit = result.criteria.find(c => c.id === 'line-count')!
    expect(lineCountCrit.score).toBe(0)
  })

  it('detects secrets in content', () => {
    const result = analyzeClaudeMd(secretsClaudeMd)
    const secretsCrit = result.criteria.find(c => c.id === 'no-secrets')!
    expect(secretsCrit.score).toBe(0)
    expect(secretsCrit.findings.length).toBeGreaterThan(0)
  })

  it('returns pros, cons, and suggestions', () => {
    const result = analyzeClaudeMd(goodClaudeMd)
    expect(result.pros.length).toBeGreaterThan(0)
    expect(result.suggestions).toBeInstanceOf(Array)
  })

  it('detects modular imports', () => {
    const result = analyzeClaudeMd(goodClaudeMd)
    const modularCrit = result.criteria.find(c => c.id === 'modular-imports')!
    expect(modularCrit.score).toBeGreaterThan(0)
  })

  it('detects missing build commands', () => {
    const minimal = '# CLAUDE.md\n\nHello world\n'
    const result = analyzeClaudeMd(minimal)
    const cmdCrit = result.criteria.find(c => c.id === 'build-commands')!
    expect(cmdCrit.score).toBe(0)
  })
})
```

- [ ] **Step 2: Run test to verify it fails**

```bash
npx vitest run src/tests/lib/scorer.test.ts
```
Expected: FAIL

- [ ] **Step 3: Create analyzer types**

Create `src/lib/analyzer/types.ts`:
```typescript
export interface CriterionResult {
  id: string
  name: string
  weight: number
  maxScore: number
  score: number
  findings: Finding[]
}

export interface Finding {
  severity: 'critical' | 'warning' | 'info'
  message: string
  line?: number
}

export interface Suggestion {
  id: string
  criterion: string
  description: string
  before: string
  after: string
  priority: 'high' | 'medium' | 'low'
}

export interface AnalysisResult {
  overallScore: number
  maxScore: number
  percentage: number
  letterGrade: string
  criteria: CriterionResult[]
  pros: string[]
  cons: string[]
  suggestions: Suggestion[]
  detectedProfileSlug?: string
  lineCount: number
}
```

- [ ] **Step 4: Implement individual scoring rules**

Create `src/lib/analyzer/rules.ts` with 12 rule functions:
```typescript
import type { CriterionResult, Finding } from './types'

export function scoreLineCount(content: string): CriterionResult {
  const lines = content.split('\n').length
  const maxLines = 200
  const score = lines <= maxLines ? 15 : lines <= 300 ? 8 : 0
  const findings: Finding[] = []
  if (lines > maxLines) {
    findings.push({
      severity: lines > 300 ? 'critical' : 'warning',
      message: `CLAUDE.md is ${lines} lines (target: <${maxLines}). Consider modularizing into .claude/rules/.`,
    })
  }
  return { id: 'line-count', name: 'Line Count', weight: 15, maxScore: 15, score, findings }
}

export function scoreStructure(content: string): CriterionResult {
  const headers = (content.match(/^#{1,3}\s+/gm) || []).length
  const score = headers >= 3 ? 10 : headers >= 1 ? 5 : 0
  const findings: Finding[] = []
  if (headers < 3) {
    findings.push({ severity: 'warning', message: `Only ${headers} section headers found. Use ## headers to organize content.` })
  }
  return { id: 'structure', name: 'Structure', weight: 10, maxScore: 10, score, findings }
}

export function scoreSpecificity(content: string): CriterionResult {
  const cmdPatterns = /(?:npm|yarn|pnpm|pip|cargo|go|make|pytest|jest|vitest)\s+\w+/g
  const commands = (content.match(cmdPatterns) || []).length
  const score = commands >= 3 ? 12 : commands >= 1 ? 6 : 0
  const findings: Finding[] = []
  if (commands < 3) {
    findings.push({ severity: 'warning', message: 'Include specific commands (e.g., "npm test" not just "run tests").' })
  }
  return { id: 'specificity', name: 'Specificity', weight: 12, maxScore: 12, score, findings }
}

export function scoreSafetyRules(content: string): CriterionResult {
  const lower = content.toLowerCase()
  const safetyKeywords = ['safety', 'don\'t delete', 'don\'t modify', 'never', 'protected', 'credential', 'secret']
  const found = safetyKeywords.filter(k => lower.includes(k)).length
  const score = found >= 3 ? 12 : found >= 1 ? 6 : 0
  const findings: Finding[] = []
  if (found === 0) {
    findings.push({ severity: 'critical', message: 'No safety rules found. Add a ## Safety section.' })
  }
  return { id: 'safety-rules', name: 'Safety Rules', weight: 12, maxScore: 12, score, findings }
}

export function scoreBuildCommands(content: string): CriterionResult {
  const cmdTypes = ['install', 'dev', 'test', 'lint', 'build', 'format']
  const found = cmdTypes.filter(c => content.toLowerCase().includes(c + ':') || content.toLowerCase().includes(c + ' :')).length
  const score = found >= 3 ? 8 : found >= 1 ? 4 : 0
  const findings: Finding[] = []
  if (found < 3) {
    const missing = cmdTypes.filter(c => !content.toLowerCase().includes(c + ':') && !content.toLowerCase().includes(c + ' :'))
    findings.push({ severity: 'warning', message: `Missing command definitions for: ${missing.join(', ')}` })
  }
  return { id: 'build-commands', name: 'Build Commands', weight: 8, maxScore: 8, score, findings }
}

export function scoreCodeStyle(content: string): CriterionResult {
  const lower = content.toLowerCase()
  const hasStyle = lower.includes('style') || lower.includes('indent') || lower.includes('naming') || lower.includes('linter') || lower.includes('.claude/rules/')
  const score = hasStyle ? 5 : 0
  const findings: Finding[] = []
  if (!hasStyle) {
    findings.push({ severity: 'info', message: 'No code style guidance found. Consider defining or delegating to a linter.' })
  }
  return { id: 'code-style', name: 'Code Style', weight: 5, maxScore: 5, score, findings }
}

export function scoreModularImports(content: string): CriterionResult {
  const hasImports = content.includes('@docs/') || content.includes('@~/')
  const hasRulesRef = content.includes('.claude/rules/')
  const score = hasImports && hasRulesRef ? 10 : hasImports || hasRulesRef ? 5 : 0
  const findings: Finding[] = []
  if (!hasImports && !hasRulesRef) {
    findings.push({ severity: 'warning', message: 'No modular imports or .claude/rules/ references. Consider splitting into modular files.' })
  }
  return { id: 'modular-imports', name: 'Modular Imports', weight: 10, maxScore: 10, score, findings }
}

export function scoreNoSecrets(content: string): CriterionResult {
  const secretPatterns = [
    /(?:api[_-]?key|token|secret|password|credential)\s*[:=]\s*\S+/gi,
    /sk-[a-zA-Z0-9]{20,}/g,
    /ghp_[a-zA-Z0-9]{36}/g,
    /shpat_[a-zA-Z0-9]{32}/g,
    /(?:Bearer|Basic)\s+[a-zA-Z0-9+/=]{20,}/g,
  ]
  const findings: Finding[] = []
  const lines = content.split('\n')
  for (let i = 0; i < lines.length; i++) {
    for (const pattern of secretPatterns) {
      pattern.lastIndex = 0
      if (pattern.test(lines[i])) {
        findings.push({ severity: 'critical', message: `Potential secret detected`, line: i + 1 })
      }
    }
  }
  const score = findings.length === 0 ? 10 : 0
  return { id: 'no-secrets', name: 'No Secrets', weight: 10, maxScore: 10, score, findings }
}

export function scoreNoBloat(content: string): CriterionResult {
  const lines = content.split('\n')
  const emptyRuns = lines.reduce((acc, line, i) => {
    if (line.trim() === '' && i > 0 && lines[i - 1].trim() === '') return acc + 1
    return acc
  }, 0)
  const score = emptyRuns <= 3 ? 8 : emptyRuns <= 10 ? 4 : 0
  const findings: Finding[] = []
  if (emptyRuns > 3) {
    findings.push({ severity: 'info', message: `${emptyRuns} consecutive empty line pairs found. Clean up whitespace.` })
  }
  return { id: 'no-bloat', name: 'No Bloat', weight: 8, maxScore: 8, score, findings }
}

export function scoreSkillsSubagents(content: string): CriterionResult {
  const lower = content.toLowerCase()
  const hasSkills = lower.includes('skill') || lower.includes('/skill')
  const hasAgents = lower.includes('subagent') || lower.includes('.claude/agents')
  const score = hasSkills || hasAgents ? 3 : 0
  return { id: 'skills-subagents', name: 'Skills/Subagents', weight: 3, maxScore: 3, score, findings: [] }
}

export function scoreHookEnforcement(content: string): CriterionResult {
  const lower = content.toLowerCase()
  const hasHooks = lower.includes('hook') || lower.includes('.claude/settings')
  const score = hasHooks ? 3 : 0
  return { id: 'hook-enforcement', name: 'Hook Enforcement', weight: 3, maxScore: 3, score, findings: [] }
}

export function scoreMcpConfig(content: string): CriterionResult {
  const lower = content.toLowerCase()
  const hasMcp = lower.includes('mcp') || lower.includes('.mcp.json')
  const score = hasMcp ? 4 : 0
  return { id: 'mcp-config', name: 'MCP Configuration', weight: 4, maxScore: 4, score, findings: [] }
}
```

- [ ] **Step 5: Implement scorer orchestrator**

Create `src/lib/analyzer/scorer.ts`:
```typescript
import type { AnalysisResult } from './types'
import * as rules from './rules'

const allRules = [
  rules.scoreLineCount,
  rules.scoreStructure,
  rules.scoreSpecificity,
  rules.scoreSafetyRules,
  rules.scoreBuildCommands,
  rules.scoreCodeStyle,
  rules.scoreModularImports,
  rules.scoreNoSecrets,
  rules.scoreNoBloat,
  rules.scoreSkillsSubagents,
  rules.scoreHookEnforcement,
  rules.scoreMcpConfig,
]

function letterGrade(pct: number): string {
  if (pct >= 95) return 'A+'
  if (pct >= 90) return 'A'
  if (pct >= 85) return 'A-'
  if (pct >= 80) return 'B+'
  if (pct >= 75) return 'B'
  if (pct >= 70) return 'B-'
  if (pct >= 65) return 'C+'
  if (pct >= 60) return 'C'
  if (pct >= 55) return 'C-'
  if (pct >= 50) return 'D+'
  if (pct >= 45) return 'D'
  if (pct >= 40) return 'D-'
  return 'F'
}

export function analyzeClaudeMd(content: string): AnalysisResult {
  const criteria = allRules.map(rule => rule(content))
  const maxScore = criteria.reduce((sum, c) => sum + c.maxScore, 0)
  const totalScore = criteria.reduce((sum, c) => sum + c.score, 0)
  const percentage = Math.round((totalScore / maxScore) * 100)

  const pros = criteria
    .filter(c => c.score === c.maxScore)
    .map(c => `${c.name}: Looks good`)

  const cons = criteria
    .filter(c => c.score === 0 && c.findings.length > 0)
    .map(c => `${c.name}: ${c.findings[0].message}`)

  return {
    overallScore: totalScore,
    maxScore,
    percentage,
    letterGrade: letterGrade(percentage),
    criteria,
    pros,
    cons,
    suggestions: [], // populated by suggester.ts in Task 5
    lineCount: content.split('\n').length,
  }
}
```

- [ ] **Step 6: Run tests**

```bash
npx vitest run src/tests/lib/scorer.test.ts
```
Expected: ALL PASS

- [ ] **Step 7: Commit**

```bash
git add src/lib/analyzer/ src/tests/lib/scorer.test.ts
git commit -m "feat: 12-criteria CLAUDE.md scoring engine with secret detection"
```

---

### Task 5: Suggestion Engine + Applier

**Files:**
- Create: `src/lib/analyzer/suggester.ts`
- Create: `src/lib/analyzer/applier.ts`
- Create: `src/lib/analyzer/matcher.ts`
- Test: `src/tests/lib/applier.test.ts`

- [ ] **Step 1: Write the failing test**

Create `src/tests/lib/applier.test.ts`:
```typescript
import { describe, it, expect } from 'vitest'
import { generateSuggestions } from '@/lib/analyzer/suggester'
import { applySuggestions } from '@/lib/analyzer/applier'
import { analyzeClaudeMd } from '@/lib/analyzer/scorer'

const minimalMd = `# CLAUDE.md

## My project
This is a simple app.
`

describe('Suggestion Engine', () => {
  it('generates suggestions for a minimal CLAUDE.md', () => {
    const analysis = analyzeClaudeMd(minimalMd)
    const suggestions = generateSuggestions(minimalMd, analysis)
    expect(suggestions.length).toBeGreaterThan(0)
    expect(suggestions[0].description).toBeTruthy()
    expect(suggestions[0].after).toBeTruthy()
  })
})

describe('Suggestion Applier', () => {
  it('applies suggestions to content', () => {
    const analysis = analyzeClaudeMd(minimalMd)
    const suggestions = generateSuggestions(minimalMd, analysis)
    const safetyIdx = suggestions.findIndex(s => s.criterion === 'safety-rules')
    if (safetyIdx >= 0) {
      const result = applySuggestions(minimalMd, [suggestions[safetyIdx]])
      expect(result).toContain('Safety')
      expect(result.length).toBeGreaterThan(minimalMd.length)
    }
  })

  it('applies multiple suggestions without conflicts', () => {
    const analysis = analyzeClaudeMd(minimalMd)
    const suggestions = generateSuggestions(minimalMd, analysis)
    const result = applySuggestions(minimalMd, suggestions.slice(0, 3))
    expect(result).toBeTruthy()
    expect(result).toContain('# CLAUDE.md')
  })
})
```

- [ ] **Step 2: Run test to verify it fails**

```bash
npx vitest run src/tests/lib/applier.test.ts
```
Expected: FAIL

- [ ] **Step 3: Implement suggester**

Create `src/lib/analyzer/suggester.ts` — generates actionable suggestions based on analysis results. Each suggestion has `before` (current state) and `after` (proposed change) text, plus description and priority.

Key suggestions:
- Missing safety section → insert `## Safety` block
- Missing commands → insert `## Commands` block with placeholders
- Over line limit → suggest splitting into .claude/rules/
- Secrets detected → suggest removing and using env vars
- No modular imports → suggest adding .claude/rules/ references

- [ ] **Step 4: Implement applier**

Create `src/lib/analyzer/applier.ts` — takes content + selected suggestions, applies them in order (append-based for new sections, replace-based for modifications), returns updated content.

- [ ] **Step 5: Implement profile matcher**

Create `src/lib/analyzer/matcher.ts` — analyzes existing CLAUDE.md content and returns the closest matching profile slug by scoring similarity against each profile's template patterns.

- [ ] **Step 6: Run tests**

```bash
npx vitest run src/tests/lib/applier.test.ts
```
Expected: ALL PASS

- [ ] **Step 7: Commit**

```bash
git add src/lib/analyzer/suggester.ts src/lib/analyzer/applier.ts src/lib/analyzer/matcher.ts src/tests/lib/applier.test.ts
git commit -m "feat: suggestion engine and applier for CLAUDE.md optimization"
```

---

## Phase 3: API Routes + Database

### Task 6: Prisma Schema + Database Setup

**Files:**
- Create: `prisma/schema.prisma`
- Create: `prisma/seed.ts`
- Create: `src/lib/db.ts`

- [ ] **Step 1: Create Prisma schema**

Create `prisma/schema.prisma`:
```prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        String     @id @default(uuid())
  email     String?    @unique
  tier      String     @default("free")
  createdAt DateTime   @default(now()) @map("created_at")
  analyses  Analysis[]

  @@map("users")
}

model Analysis {
  id                  String               @id @default(uuid())
  userId              String?              @map("user_id")
  user                User?                @relation(fields: [userId], references: [id])
  inputContent        String               @map("input_content")
  inputSource         String?              @map("input_source")
  overallScore        Float?               @map("overall_score")
  letterGrade         String?              @map("letter_grade")
  criteriaScores      Json                 @map("criteria_scores")
  pros                Json                 @default("[]")
  cons                Json                 @default("[]")
  suggestions         Json                 @default("[]")
  detectedProfileSlug String?              @map("detected_profile_slug")
  createdAt           DateTime             @default(now()) @map("created_at")
  appliedSuggestions  AppliedSuggestion[]

  @@map("analyses")
}

model AppliedSuggestion {
  id              String   @id @default(uuid())
  analysisId      String   @map("analysis_id")
  analysis        Analysis @relation(fields: [analysisId], references: [id])
  suggestionIndex Int      @map("suggestion_index")
  appliedAt       DateTime @default(now()) @map("applied_at")
  resultContent   String?  @map("result_content")

  @@map("applied_suggestions")
}
```

- [ ] **Step 2: Create database and run migration**

```bash
createdb -U dw_admin clawcoder 2>/dev/null || true
npx prisma migrate dev --name init
```

- [ ] **Step 3: Create Prisma client singleton**

Create `src/lib/db.ts`:
```typescript
import { PrismaClient } from '@prisma/client'

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }

export const prisma = globalForPrisma.prisma || new PrismaClient()

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
```

- [ ] **Step 4: Commit**

```bash
git add prisma/ src/lib/db.ts
git commit -m "feat: Prisma schema with users, analyses, applied_suggestions tables"
```

---

### Task 7: API Routes

**Files:**
- Create: `src/app/api/profiles/route.ts`
- Create: `src/app/api/analyze/route.ts`
- Create: `src/app/api/generate/route.ts`
- Create: `src/app/api/suggestions/route.ts`

- [ ] **Step 1: Profiles API**

Create `src/app/api/profiles/route.ts`:
```typescript
import { NextResponse } from 'next/server'
import { PROFILES, getProfile, getProfilesByAge, getProfilesBySkill } from '@/lib/profiles/data'

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const slug = searchParams.get('slug')
  const age = searchParams.get('age')
  const skill = searchParams.get('skill')

  if (slug) {
    const profile = getProfile(slug)
    return profile
      ? NextResponse.json(profile)
      : NextResponse.json({ error: 'Profile not found' }, { status: 404 })
  }
  if (age) return NextResponse.json(getProfilesByAge(age as any))
  if (skill) return NextResponse.json(getProfilesBySkill(skill as any))
  return NextResponse.json(PROFILES)
}
```

- [ ] **Step 2: Analyze API**

Create `src/app/api/analyze/route.ts`:
```typescript
import { NextResponse } from 'next/server'
import { analyzeClaudeMd } from '@/lib/analyzer/scorer'
import { generateSuggestions } from '@/lib/analyzer/suggester'
import { detectProfile } from '@/lib/analyzer/matcher'
import { prisma } from '@/lib/db'

export async function POST(request: Request) {
  const { content, source } = await request.json()
  if (!content || typeof content !== 'string') {
    return NextResponse.json({ error: 'content is required' }, { status: 400 })
  }

  const analysis = analyzeClaudeMd(content)
  const suggestions = generateSuggestions(content, analysis)
  const detectedProfile = detectProfile(content)

  const saved = await prisma.analysis.create({
    data: {
      inputContent: content,
      inputSource: source || 'paste',
      overallScore: analysis.percentage,
      letterGrade: analysis.letterGrade,
      criteriaScores: analysis.criteria as any,
      pros: analysis.pros as any,
      cons: analysis.cons as any,
      suggestions: suggestions as any,
      detectedProfileSlug: detectedProfile,
    },
  })

  return NextResponse.json({
    id: saved.id,
    ...analysis,
    suggestions,
    detectedProfileSlug: detectedProfile,
  })
}
```

- [ ] **Step 3: Generate API**

Create `src/app/api/generate/route.ts`:
```typescript
import { NextResponse } from 'next/server'
import { getProfile } from '@/lib/profiles/data'
import { generateBundle } from '@/lib/generator/bundle'

export async function POST(request: Request) {
  const { profileSlug, vars } = await request.json()
  const profile = getProfile(profileSlug)
  if (!profile) {
    return NextResponse.json({ error: 'Profile not found' }, { status: 404 })
  }
  const bundle = generateBundle(profile, vars || {})
  return NextResponse.json(bundle)
}
```

- [ ] **Step 4: Suggestions apply API**

Create `src/app/api/suggestions/route.ts`:
```typescript
import { NextResponse } from 'next/server'
import { applySuggestions } from '@/lib/analyzer/applier'
import type { Suggestion } from '@/lib/analyzer/types'

export async function POST(request: Request) {
  const { content, selectedSuggestions } = await request.json()
  if (!content || !selectedSuggestions) {
    return NextResponse.json({ error: 'content and selectedSuggestions required' }, { status: 400 })
  }
  const result = applySuggestions(content, selectedSuggestions as Suggestion[])
  return NextResponse.json({ updatedContent: result })
}
```

- [ ] **Step 5: Verify APIs work**

```bash
npm run build
npm run dev &
sleep 3
curl -s http://localhost:3000/api/profiles | head -100
curl -s http://localhost:3000/api/profiles?slug=teen-beginner | head -50
kill %1
```

- [ ] **Step 6: Commit**

```bash
git add src/app/api/
git commit -m "feat: API routes for profiles, analyze, generate, and suggestions"
```

---

## Phase 4: UI Components

### Task 8: Design System Primitives

**Files:**
- Create: `src/components/ui/pill-button.tsx`
- Create: `src/components/ui/back-next-bar.tsx`
- Create: `src/components/ui/progress-indicator.tsx`
- Create: `src/components/ui/grade-badge.tsx`
- Create: `src/components/ui/diff-viewer.tsx`
- Create: `src/components/ui/radar-chart.tsx`

- [ ] **Step 1: Implement pill-button**

Accessible, large touch-target button with press animation. Respects `reducedMotion`. Min height 56px, min width 140px, full border-radius (pill shape).

- [ ] **Step 2: Implement back-next-bar**

Sticky bottom bar with Back/Next buttons. Min button height 56px. Always visible. Shows step count.

- [ ] **Step 3: Implement progress-indicator**

"Step N of M" text + thin progress bar. Accessible with aria-valuenow.

- [ ] **Step 4: Implement grade-badge**

Letter grade display (A+ through F) with color coding. Green for A/B, yellow for C/D, red for F.

- [ ] **Step 5: Implement diff-viewer**

Side-by-side or unified diff view for before/after comparison. Uses simple line-by-line diffing with green (added) and red (removed) highlighting.

- [ ] **Step 6: Implement radar-chart**

12-axis SVG radar chart showing scores for each criterion. Responsive, accessible with aria-label.

- [ ] **Step 7: Commit**

```bash
git add src/components/ui/
git commit -m "feat: design system primitives — pill button, nav bar, grade badge, diff viewer, radar chart"
```

---

### Task 9: Profile Selector + Onboarding Page

**Files:**
- Create: `src/app/page.tsx` (landing/profile selector)
- Create: `src/components/onboarding/question-screen.tsx`
- Create: `src/components/onboarding/pill-grid.tsx`
- Create: `src/app/onboarding/page.tsx`
- Create: `src/lib/store.ts` (Zustand store)

- [ ] **Step 1: Create Zustand store**

Create `src/lib/store.ts`:
```typescript
import { create } from 'zustand'
import type { Profile } from '@/lib/profiles/types'
import type { AnalysisResult, Suggestion } from '@/lib/analyzer/types'

interface ClawCoderStore {
  selectedProfile: Profile | null
  setProfile: (p: Profile) => void
  onboardingAnswers: Record<string, string>
  setAnswer: (key: string, value: string) => void
  currentStep: number
  setStep: (n: number) => void
  analysisResult: AnalysisResult | null
  setAnalysis: (a: AnalysisResult) => void
  selectedSuggestions: Set<string>
  toggleSuggestion: (id: string) => void
  selectAllSuggestions: (suggestions: Suggestion[]) => void
  clearSuggestions: () => void
  generatedContent: string
  setGeneratedContent: (c: string) => void
}

export const useStore = create<ClawCoderStore>((set) => ({
  selectedProfile: null,
  setProfile: (p) => set({ selectedProfile: p }),
  onboardingAnswers: {},
  setAnswer: (key, value) => set((s) => ({ onboardingAnswers: { ...s.onboardingAnswers, [key]: value } })),
  currentStep: 0,
  setStep: (n) => set({ currentStep: n }),
  analysisResult: null,
  setAnalysis: (a) => set({ analysisResult: a }),
  selectedSuggestions: new Set(),
  toggleSuggestion: (id) => set((s) => {
    const next = new Set(s.selectedSuggestions)
    next.has(id) ? next.delete(id) : next.add(id)
    return { selectedSuggestions: next }
  }),
  selectAllSuggestions: (suggestions) => set({ selectedSuggestions: new Set(suggestions.map(s => s.id)) }),
  clearSuggestions: () => set({ selectedSuggestions: new Set() }),
  generatedContent: '',
  setGeneratedContent: (c) => set({ generatedContent: c }),
}))
```

- [ ] **Step 2: Implement landing page with profile selector**

Grid of 15 profile cards organized by age group (rows) and skill level (columns). Each card shows: display name, persona description, key stats (font size, # skills, # MCP servers). Clicking a card navigates to `/onboarding?profile=slug`.

- [ ] **Step 3: Implement question-screen component**

Full-screen layout: top section (step label + question), middle (pill grid), bottom (back/next bar + progress). Animates between questions with Framer Motion (cross-fade, respects reduced motion).

- [ ] **Step 4: Implement pill-grid component**

Responsive grid of PillButton components. 2-4 columns depending on screen width. Each pill represents an answer option.

- [ ] **Step 5: Implement onboarding page**

9-step wizard from the spec: Profile, Comfort, Risk, Style, Tools, Workflow, Updates, Motion, Finish. Each step renders a QuestionScreen with appropriate pill options. Final step triggers generation.

- [ ] **Step 6: Commit**

```bash
git add src/app/page.tsx src/app/onboarding/ src/components/onboarding/ src/lib/store.ts
git commit -m "feat: profile selector landing page and onboarding wizard"
```

---

### Task 10: Analyzer Page — Upload + Report + Suggestions

**Files:**
- Create: `src/components/analyzer/upload-zone.tsx`
- Create: `src/components/analyzer/report-card.tsx`
- Create: `src/components/analyzer/suggestion-card.tsx`
- Create: `src/components/analyzer/suggestion-list.tsx`
- Create: `src/app/analyzer/page.tsx`

- [ ] **Step 1: Implement upload-zone**

Drag-and-drop zone + paste textarea. Accepts .md files and raw text. Sends content to `/api/analyze` on submit.

- [ ] **Step 2: Implement report-card**

Displays: letter grade (GradeBadge), percentage, radar chart (12 criteria), expandable pros (green), cons (red) sections.

- [ ] **Step 3: Implement suggestion-card**

Single suggestion with: checkbox, description, priority badge, expandable before/after diff preview (DiffViewer).

- [ ] **Step 4: Implement suggestion-list**

List of SuggestionCards with "Select All" / "Deselect All" controls. "Preview Changes" button shows unified diff. "Apply Selected" button calls `/api/suggestions`.

- [ ] **Step 5: Wire up analyzer page**

Full page flow: upload → loading → report + suggestions → apply → download/copy result. Includes profile detection: "Your config matches `young-adult-intermediate`" with upgrade CTA.

- [ ] **Step 6: Commit**

```bash
git add src/components/analyzer/ src/app/analyzer/
git commit -m "feat: CLAUDE.md analyzer page with upload, scoring, and suggestion workflow"
```

---

### Task 11: Mass Update Page

**Files:**
- Create: `src/components/mass-update/record-table.tsx`
- Create: `src/components/mass-update/bulk-actions-bar.tsx`
- Create: `src/app/mass-update/page.tsx`

- [ ] **Step 1: Implement record-table**

Table with columns: checkbox, file name, type (claude_md/rule/skill/hook), status, score, last modified. Sortable columns. Multi-select via checkboxes. "Select All" header checkbox.

- [ ] **Step 2: Implement bulk-actions-bar**

Floating bar that appears when items are selected. Shows count ("3 selected"). Buttons: "Analyze Selected", "Apply All Suggestions", "Export Bundle (.zip)", "Delete Selected" (with confirm modal).

- [ ] **Step 3: Wire up mass-update page**

Page shows all user's generated artifacts in a table. Supports: analyze multiple files at once, apply suggestions in batch, export as downloadable .zip bundle.

- [ ] **Step 4: Commit**

```bash
git add src/components/mass-update/ src/app/mass-update/
git commit -m "feat: mass update page with record table and bulk actions"
```

---

### Task 12: Builder Page — Rail + Preview

**Files:**
- Create: `src/components/builder/rail.tsx`
- Create: `src/components/builder/preview-pane.tsx`
- Create: `src/components/builder/section-editor.tsx`
- Create: `src/app/builder/page.tsx`

- [ ] **Step 1: Implement collapsible rail**

Left sidebar with collapsible sections: Profile Summary, CLAUDE.md, Rules, Skills, Subagents, Hooks, MCP Servers, Learning Path. Chevron toggle. Animate height (respect reduced motion).

- [ ] **Step 2: Implement preview-pane**

Live markdown preview of generated CLAUDE.md. Syntax highlighted. Shows effective output including resolved imports. "Copy" and "Download" buttons.

- [ ] **Step 3: Implement section-editor**

Inline editor for individual sections of the CLAUDE.md. Textarea with live preview. "Reset to template" button.

- [ ] **Step 4: Wire up builder page**

Two-panel layout: rail (left, collapsible on mobile) + preview (right, full-width on mobile). Editing a section in the rail updates the preview in real-time.

- [ ] **Step 5: Commit**

```bash
git add src/components/builder/ src/app/builder/
git commit -m "feat: builder page with collapsible rail and live CLAUDE.md preview"
```

---

## Phase 5: Integration + Polish

### Task 13: Root Layout + Navigation + Theme

**Files:**
- Modify: `src/app/layout.tsx`
- Create: `src/components/nav.tsx`

- [ ] **Step 1: Implement root layout**

Apply profile-driven theme variables (font size, contrast, motion). Persist theme in localStorage. Add `prefers-reduced-motion` media query support.

- [ ] **Step 2: Implement navigation**

Top nav bar with links: Home, Onboarding, Builder, Analyzer, Mass Update. Highlights active route. Responsive (hamburger on mobile).

- [ ] **Step 3: Commit**

```bash
git add src/app/layout.tsx src/components/nav.tsx
git commit -m "feat: root layout with theme system and navigation"
```

---

### Task 14: End-to-End Integration Test

**Files:**
- Create: `src/tests/e2e/flow.test.ts`

- [ ] **Step 1: Write integration test**

Test the full flow:
1. Select a profile → verify correct data loaded
2. Complete onboarding → verify bundle generated
3. Upload a CLAUDE.md → verify analysis returned with correct scoring
4. Apply suggestions → verify content updated
5. Verify profile detection matches expected

- [ ] **Step 2: Run all tests**

```bash
npx vitest run
```
Expected: ALL PASS

- [ ] **Step 3: Commit**

```bash
git add src/tests/
git commit -m "test: end-to-end integration test for full ClawCoder flow"
```

---

### Task 15: PM2 Deployment + Firewall

**Files:**
- Create: `ecosystem.config.cjs`

- [ ] **Step 1: Build for production**

```bash
cd /root/Projects/ClawCoder && npm run build
```

- [ ] **Step 2: Create PM2 config**

Create `ecosystem.config.cjs`:
```javascript
module.exports = {
  apps: [{
    name: 'clawcoder',
    script: 'node_modules/.bin/next',
    args: 'start -p 7350',
    cwd: '/root/Projects/ClawCoder',
    env: {
      NODE_ENV: 'production',
      PORT: 7350,
    },
  }],
}
```

- [ ] **Step 3: Start with PM2 and open firewall**

```bash
pm2 start ecosystem.config.cjs
sudo ufw allow from 76.33.146.135 to any port 7350 proto tcp
```

- [ ] **Step 4: Verify service is running**

```bash
curl -s http://45.61.58.125:7350 | head -20
pm2 status clawcoder
```

- [ ] **Step 5: Commit**

```bash
git add ecosystem.config.cjs
git commit -m "feat: PM2 deployment config on port 7350"
```

---

## Summary

| Phase | Tasks | Description |
|-------|-------|-------------|
| **1: Foundation** | Tasks 1-3 | Project scaffold, 15 profiles, template engine |
| **2: Analyzer** | Tasks 4-5 | 12-criteria scorer, suggestion engine, applier |
| **3: API + DB** | Tasks 6-7 | Prisma schema, 4 API routes |
| **4: UI** | Tasks 8-12 | Design system, onboarding, analyzer, mass update, builder |
| **5: Polish** | Tasks 13-15 | Layout, navigation, E2E tests, PM2 deployment |

**Total**: 15 tasks, each with 4-8 bite-sized steps.
**Port**: 7350 (IP-locked to Steve's IP)