← 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)