The hands-on companion to the README. This guide walks you through every feature with step-by-step tutorials, real examples, and practical workflows.
Interactive version: View as HTML with syntax highlighting, sidebar navigation, and search.
- Part 1: Getting Started
- Part 2: Daily Workflow
- Part 3: Commands Deep Dive
- Part 4: Hook System Explained
- Part 5: StrictDB Database Layer
- Part 6: CLAUDE.md Customization
- Part 7: Testing
- Part 8: Deployment
- Part 9: Troubleshooting
- Part 10: FAQ
The Claude Code Starter Kit is a scaffold template that supercharges Claude Code with battle-tested rules, automated enforcement, and on-demand tools. It's not an app — it's the foundation that makes every app you build with Claude dramatically better.
Think of it as a senior engineer's playbook, encoded into files that Claude reads and follows:
- CLAUDE.md tells Claude what rules to follow
- Hooks enforce those rules automatically (no forgetting)
- Slash commands give you on-demand tools for common tasks
- Skills activate automatically when Claude detects relevant context
- Agents handle specialized work like security audits and test writing
Are you starting a brand new project?
├── YES → Use /new-project
│ ├── Want full opinions (TypeScript, ports, quality gates)?
│ │ └── /new-project my-app default
│ ├── Want just AI tooling, zero coding opinions?
│ │ └── /new-project my-app clean
│ ├── Building a Go API?
│ │ └── /new-project my-api go
│ ├── Building a Python API?
│ │ └── /new-project my-api python-api
│ └── Want to pick everything yourself?
│ └── /new-project my-app fullstack next dokploy tailwind pnpm
│
├── NO → Do you have an existing project?
│ ├── Not yet using starter kit?
│ │ └── /convert-project-to-starter-kit ~/projects/my-app
│ └── Already using starter kit? Want latest updates?
│ └── /update-project
│
└── Want to customize the template itself?
└── Clone the repo, modify commands/hooks/rules, use as your own source
Step 1: Install global config (one time only)
/install-globalThis merges security rules and hooks into ~/.claude/. It never overwrites your existing config.
Step 2: Create your project
/new-project my-app cleanThis creates ~/projects/my-app with all Claude Code tooling — commands, hooks, skills, agents, documentation templates — and zero coding opinions. You pick the language, framework, and database yourself.
For a fully opinionated setup (Next.js + TypeScript + StrictDB + Tailwind):
/new-project my-app defaultStep 3: Enter the project
cd ~/projects/my-appStep 4: Configure your environment
/setupThis walks you through setting up .env interactively — database, GitHub, Docker, analytics. It skips variables that already have values and never displays secrets back to you.
Step 5: Start building
claudeThat's it. Claude now has all the rules, hooks, and commands ready to go.
After running /new-project my-app clean, here's what's in your project:
my-app/
├── .claude/
│ ├── commands/ ← 26 slash commands (16 project + 10 kit management)
│ ├── skills/ ← Auto-triggered expertise templates
│ ├── agents/ ← Specialist subagents (reviewer, test writer)
│ ├── hooks/ ← 9 enforcement scripts that always run
│ └── settings.json ← Hooks wired to lifecycle events
├── project-docs/
│ ├── ARCHITECTURE.md ← System overview (fill in as you build)
│ ├── INFRASTRUCTURE.md ← Deployment details
│ └── DECISIONS.md ← Why you chose X over Y
├── tests/
│ ├── CHECKLIST.md ← Master test tracker
│ └── ISSUES_FOUND.md ← Bug tracking log
├── CLAUDE.md ← The rules Claude follows
├── CLAUDE.local.md ← Your personal preferences (gitignored)
├── .env ← Secrets (never committed)
├── .env.example ← Template with placeholders
├── .gitignore ← Includes .env, CLAUDE.local.md
└── .dockerignore ← Keeps secrets out of images
The default profile adds more: StrictDB (database layer, installed as an npm package), package.json, tsconfig.json, playwright.config.ts, vitest.config.ts, and framework-specific files.
The most effective workflow with Claude Code follows this cycle:
Write Code → /review → Fix Issues → /commit → Repeat
1. Write code — Tell Claude what you want to build. The CLAUDE.md rules guide every decision.
2. Review — Run /review to check against the 7-point checklist (security, types, errors, performance, testing, database, API versioning).
3. Fix — Address any Critical or Warning issues flagged by the review.
4. Commit — Run /commit to generate a conventional commit message from the staged changes.
5. Repeat — Start the next feature or fix.
Run /progress at any time to see:
- Source file counts by type
- Test coverage status
- Recent git activity
- Prioritized next actions
This gives you a quick snapshot without leaving the Claude session.
Use /clear between unrelated tasks. Research shows 39% performance degradation when mixing topics in a single conversation. Signs you should clear:
- Switching from frontend work to backend work
- Moving from bug fixing to feature development
- Claude seems confused about context or keeps referencing old code
- You've been in the same session for a long time and responses feel "off"
A 2% misalignment early in a conversation can cause 40% failure rate by the end. When in doubt, clear.
Auto-branch is ON by default. You never need to remember to create a branch. Every command that modifies code automatically creates a feature branch when it detects you're on main:
/refactor src/users.ts→ createsrefactor/usersbranch/create-api orders→ createsfeat/ordersbranch/commit→ creates a branch if you're on main
If Claude screws up on a branch — just delete it. Main was never touched.
For parallel sessions, use /worktree:
/worktree add-auth # Creates branch + separate working directory
/worktree add-payments # Another isolated workspaceEach worktree gets its own directory and branch, so you can run multiple Claude sessions simultaneously without conflicts.
Lists every command, skill, and agent grouped by category. Run this whenever you forget what's available.
Example output:
──────────────────────────────────────
GETTING STARTED
/help List all commands
/quickstart Interactive first-run
/install-global Install global config
/setup Configure .env
CODE QUALITY
/review Code review (7-point)
/refactor <file> Audit + refactor
/commit Smart commit
/security-check Scan for secrets
...
──────────────────────────────────────
Interactive first-run walkthrough for new users. Checks if global config is installed, asks for project name and profile, then walks you through scaffolding, setup, first dev server, first review, and first commit.
Best for: Someone who just cloned the starter kit and doesn't know where to start.
One-time setup that installs global Claude config into ~/.claude/:
- CLAUDE.md — Global security rules
- settings.json — Hook configurations and deny rules
- Hooks —
block-secrets.py,verify-no-secrets.sh,check-rulecatch.sh
Uses smart merge — if you already have config, it adds missing sections without overwriting yours. Reports exactly what was added, skipped, and merged.
Interactive .env configuration. Walks through:
- Database (StrictDB connection URI)
- GitHub (username, SSH vs HTTPS)
- Docker (Hub username, image name)
- Analytics (Rybbit site ID)
- RuleCatch (API key, region)
- Auth (auto-generates JWT secret)
Supports multi-region setups (US + EU with isolated databases). Skips variables that already have values. Use /setup --reset to reconfigure everything.
Opens this guide in your browser. Tries the GitHub Pages URL first, falls back to the local HTML file.
Systematic code review against a 7-point checklist:
- Security — OWASP Top 10, no secrets in code
- Types — No
any, proper null handling - Error Handling — No swallowed errors
- Performance — No N+1 queries, no memory leaks
- Testing — New code has explicit assertions
- Database — Using StrictDB correctly
- API Versioning — All endpoints use
/api/v1/
Issues are reported with severity levels:
- Critical — Must fix before commit (security issues, data loss risks)
- Warning — Should fix (performance, maintainability)
- Info — Consider improving (style, naming)
Each issue includes file:line reference and a suggested fix.
Audits a file against every rule in CLAUDE.md, then refactors:
- Branch check — not on main? Good. On main? Suggests
/worktree - File size — >300 lines? Splits into focused modules
- Function size — >50 lines? Extracts helpers
- TypeScript — removes
any, adds explicit types - Import hygiene — no barrel imports, proper
import type - Error handling — no swallowed errors
- Database access — StrictDB only
- API routes —
/api/v1/prefix - Promise.all — parallelizes independent awaits
- Security + dead code — removes unused code, checks for secrets
/refactor src/handlers/users.ts # Full audit + refactor
/refactor src/server.ts --dry-run # Report only, no changesPresents a named-step plan before making changes. You approve before anything is modified.
Smart commit with conventional commit format. Reviews staged changes and generates a message using type(scope): description:
feat(auth): add JWT token refresh endpointfix(db): prevent connection pool exhaustion on hot reloadrefactor(users): split handler into focused modules
Warns if changes span multiple concerns and suggests splitting into separate commits.
Scans the project for vulnerabilities:
- Secrets in source code (API keys, passwords, tokens)
.gitignorecoverage gaps- Sensitive files tracked by git
.envhandling audit- Dependency vulnerability scan (
npm audit)
Full project scaffolding with profiles:
/new-project my-app clean # AI tooling only, zero opinions
/new-project my-app default # Full stack: Next.js + StrictDB + Tailwind
/new-project my-api api fastify # API only with Fastify
/new-project my-api go # Go API with Gin + StrictDB
/new-project my-api go chi postgres # Go with Chi + PostgreSQL
/new-project my-app vue # Vue 3 SPA with Tailwind
/new-project my-app nuxt # Nuxt full-stack
/new-project my-app sveltekit # SvelteKit full-stack
/new-project my-api python-api # FastAPI + PostgreSQL
/new-project my-app django # Django full-stackEach profile configures the right language rules, frameworks, database integration (StrictDB), test infrastructure, and build tools. The clean profile gives you all AI tooling with zero coding opinions.
Scaffolds a production-ready API endpoint:
- Types —
src/types/<resource>.ts - Handler —
src/handlers/<resource>.ts - Route —
src/routes/v1/<resource>.ts - Tests —
tests/unit/<resource>.test.ts
Uses StrictDB, pagination, registered indexes, and /api/v1/ prefix.
/create-api users # Full CRUD for users
/create-api orders --no-db # Skip database integrationGenerates a Playwright E2E test with proper structure:
- Happy path with 3+ assertions (URL, visibility, content)
- Error cases (404, unauthorized, invalid input)
- Edge cases (empty state, large data, concurrent access)
Reads the source code to identify URLs, elements, and data to verify. Creates the test at tests/e2e/<feature>.spec.ts.
Scans actual code and generates ASCII diagrams:
/diagram architecture # Services, connections, data flow
/diagram api # All endpoints grouped by resource
/diagram database # Collections, indexes, relationships
/diagram infrastructure # Deployment topology, containers
/diagram all # Generate everything at onceWrites to project-docs/ARCHITECTURE.md and project-docs/INFRASTRUCTURE.md. Uses ASCII box-drawing — works everywhere, no external tools needed.
Reads and displays project-docs/ARCHITECTURE.md. If it doesn't exist, scaffolds a template.
Audits your Dockerfile against 12 best practices:
- Multi-stage builds
- Layer caching optimization
- Alpine base images
- Non-root user
- .dockerignore coverage
- Frozen lockfile
- Health checks
- No secrets in build args
- Pinned versions
Generates an optimized Dockerfile with before/after image size comparison.
Checks the filesystem and reports project status — file counts by type, test coverage, git activity, and prioritized next actions.
Generates a structured test plan for any feature:
- Prerequisites
- Happy path scenarios with expected outcomes
- Error cases and edge cases
- Pass/fail criteria table
- Sign-off tracker
Creates an isolated git worktree + branch:
/worktree add-auth # → task/add-auth branch + directory
/worktree feat/dashboard # → uses prefix as-isEach worktree gets its own branch and directory. Main stays untouched. Enables running multiple Claude sessions in parallel.
Launches the RuleCatch AI-Pooler live monitor in a separate terminal. Free monitor mode works instantly — no API key needed.
Shows: every tool call, token usage, cost per turn, files accessed, cost per session.
# Run in a separate terminal
pnpm ai:monitorZero token overhead — runs completely outside Claude's context.
Sets the default profile for /new-project. Accepts any profile name:
/set-project-profile-default vue
/set-project-profile-default go
/set-project-profile-default mongo next tailwind docker # Custom comboInteractive wizard to create a named profile in claude-mastery-project.conf. Asks about language, framework, database, hosting, package manager, analytics, and MCP servers.
Lists every project scaffolded by /new-project — creation date, profile, language, framework, database, and location. Checks which still exist on disk.
Removes a project from the registry and optionally deletes files from disk. Shows details before acting, asks for confirmation.
Merges starter kit infrastructure into an existing project non-destructively:
/convert-project-to-starter-kit ~/projects/my-app
/convert-project-to-starter-kit ~/projects/my-app --force # Skip promptsCreates a safety commit first. Detects your language, copies commands/hooks/skills/agents, merges CLAUDE.md sections and settings.json hooks. Undo with git revert HEAD.
Updates an existing starter-kit project with the latest commands, hooks, skills, agents, and rules:
/update-project # Pick from registered projects
/update-project --force # Skip confirmation promptsSmart merge — replaces starter kit files with newer versions while preserving any custom files you created. Shows a diff report (new, updated, unchanged, custom) before applying. Creates a safety commit first so you can git revert HEAD to undo.
Hooks are shell/python scripts that run automatically at specific points in Claude's lifecycle. Unlike CLAUDE.md rules (which are suggestions), hooks are guaranteed to run. They can't be forgotten or ignored.
┌─────────────────────────────────────────────┐
│ PreToolUse │
│ Runs BEFORE Claude uses a tool │
│ Can BLOCK the action (exit code 2) │
│ │
│ → block-secrets.py (Read/Edit/Write) │
│ → check-rybbit.sh (Bash - deploy) │
│ → check-branch.sh (Bash - git commit) │
│ → check-ports.sh (Bash - dev servers) │
│ → check-e2e.sh (Bash - git push) │
├─────────────────────────────────────────────┤
│ PostToolUse │
│ Runs AFTER Claude uses a tool │
│ Informational (exit 0 always) │
│ │
│ → lint-on-save.sh (Write) │
├─────────────────────────────────────────────┤
│ Stop │
│ Runs when Claude FINISHES a turn │
│ Can warn but typically doesn't block │
│ │
│ → verify-no-secrets.sh │
│ → check-rulecatch.sh │
│ → check-env-sync.sh │
└─────────────────────────────────────────────┘
What it does: Blocks Claude from reading or editing sensitive files like .env, credentials.json, SSH keys, and .npmrc.
When it triggers: Before any Read, Edit, or Write tool call.
What it blocks: Files matching sensitive filenames (.env, .env.local, secrets.json, id_rsa, id_ed25519, .npmrc, credentials.json, service-account.json) and sensitive path patterns (/.ssh/, /aws/credentials, private_key.pem).
Exit code 2 = operation blocked. Claude sees a message explaining why.
What it does: Blocks git commit on main/master when auto-branch is enabled.
When it triggers: Before any Bash command containing git commit.
Logic: If auto-branch is true (default) and you're on main/master, blocks with a message to create a feature branch first. Respects the auto_branch setting in claude-mastery-project.conf.
What it does: Blocks deployment commands if Rybbit analytics isn't configured.
When it triggers: Before docker push, vercel deploy, or Dokploy deployment commands.
Logic: Checks if analytics = rybbit is set in claude-mastery-project.conf. If yes, verifies NEXT_PUBLIC_RYBBIT_SITE_ID exists in .env with a real value (not a placeholder). Blocks with a link to https://app.rybbit.io if missing. Skips projects that don't use Rybbit.
What it does: Blocks dev server commands if the target port is already in use.
When it triggers: Before commands that start dev servers.
Logic: Detects the target port from -p, --port, PORT=, or known script names (dev:website→3000, dev:api→3001, etc.). If the port is in use, blocks and shows the PID + kill command.
What it does: Blocks git push to main/master if no E2E tests exist.
When it triggers: Before git push commands targeting main/master.
Logic: Checks tests/e2e/ for real .spec.ts or .test.ts files (excludes the example template). Blocks if no real E2E tests are found.
What it does: Automatically checks TypeScript compilation, ESLint, or Python linting after Claude writes a file.
When it triggers: After any Write tool call.
Logic: Detects file extension and runs the appropriate linter:
.ts/.tsx→tsc --noEmit.js/.jsx→eslint.py→ruff check
Informational only — never blocks.
What it does: Scans staged git files for accidentally committed secrets.
When it triggers: When Claude finishes a turn.
Logic: Checks staged file contents for API key patterns, AWS credentials (AKIA...), and credential URLs. Warns if found.
What it does: Reports RuleCatch violations detected during the session.
When it triggers: When Claude finishes a turn.
Logic: Checks if RuleCatch is installed. If not, exits silently — zero overhead for users who haven't set it up.
What it does: Compares key names between .env and .env.example.
When it triggers: When Claude finishes a turn.
Logic: If .env has keys that .env.example doesn't document, prints a warning. Informational only — never blocks. Never reads secret values.
Edit .claude/settings.json and comment out or remove the hook entry. Restart your Claude session for changes to take effect.
For example, to disable the branch check:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "bash .claude/hooks/check-rybbit.sh" },
// Removed: check-branch.sh
{ "type": "command", "command": "bash .claude/hooks/check-ports.sh" },
{ "type": "command", "command": "bash .claude/hooks/check-e2e.sh" }
]
}
]
}
}Or set auto_branch = false in claude-mastery-project.conf to disable branch protection without removing the hook.
Create a new script in .claude/hooks/:
#!/bin/bash
# .claude/hooks/my-custom-hook.sh
# Read the tool input from stdin (JSON)
INPUT=$(cat)
# Extract the command being run
COMMAND=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))" 2>/dev/null)
# Your logic here
if echo "$COMMAND" | grep -q "something-dangerous"; then
echo "BLOCKED: Reason for blocking" >&2
exit 2 # Exit 2 = block the operation
fi
exit 0 # Exit 0 = allow the operationThen wire it up in .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "bash .claude/hooks/my-custom-hook.sh" }
]
}
]
}
}- Check the hook is executable:
ls -la .claude/hooks/ - Test it manually:
echo '{"tool_input":{"command":"git commit"}}' | bash .claude/hooks/check-branch.sh; echo "Exit: $?" - Check settings.json is valid:
python3 -m json.tool .claude/settings.json - Restart Claude session — hooks are loaded at session start
- Check the matcher — does the tool name match? (
Read|Edit|Write,Bash,Write)
The #1 database failure in AI-assisted development is connection pool exhaustion. Without a centralized layer, Claude creates new database clients in every file that needs database access. Each client opens its own connection pool. During development with hot reload, connections multiply until the database rejects new connections.
StrictDB solves this with a singleton pattern — one pool per URI, shared across the entire application. It provides a unified API across database engines (MongoDB, PostgreSQL, MySQL, SQLite) so you write the same db.queryOne, db.queryMany, db.insertOne calls regardless of backend.
Tip: Install StrictDB-MCP as an MCP server to give Claude direct database awareness — schema discovery, query validation, and AI-first database workflows right in your editor.
All database access goes through StrictDB (installed as an npm package: npm install strictdb). No exceptions. Set STRICTDB_URI in your .env to point at any supported database engine.
import { StrictDB } from 'strictdb';
const db = StrictDB.getInstance();
// Single document/row lookup
const user = await db.queryOne<User>('users', { email: 'test@example.com' });
// Multiple documents/rows with sort and limit
const recentOrders = await db.queryMany<Order>('orders',
{ userId, status: 'active' },
{ sort: { createdAt: -1 }, limit: 20 }
);
// Join with lookup (limit enforced before lookup automatically)
const userWithOrders = await db.queryWithLookup<UserWithOrders>('users', {
match: { _id: userId },
lookup: { from: 'orders', localField: '_id', foreignField: 'userId', as: 'orders' },
unwind: 'orders',
});
// Count
const totalAdmins = await db.count('users', { role: 'admin' });Every write returns an OperationReceipt with { acknowledged, matchedCount, modifiedCount, insertedId } so you can verify the operation succeeded.
import { StrictDB } from 'strictdb';
const db = StrictDB.getInstance();
// Insert — returns OperationReceipt with insertedId
const receipt = await db.insertOne('users', { email, name, createdAt: new Date() });
console.log(receipt.insertedId);
await db.insertMany('events', batchOfEvents);
// Update — use $inc for counters (never read-modify-write)
const updateReceipt = await db.updateOne<User>('users', { _id: userId }, { $set: { name: 'New Name' } });
console.log(updateReceipt.modifiedCount); // 1
await db.updateOne<Stats>('stats', { date }, { $inc: { pageViews: 1 } }, true); // upsert
// Batch operations (auto-retries concurrent races)
await db.batch('sessions', [
{ updateOne: { filter: { sessionId }, update: { $inc: { events: 1 } }, upsert: true } },
{ updateOne: { filter: { sessionId }, update: { $set: { lastSeen: new Date() } } } },
]);
// Delete
await db.deleteOne('tokens', { token: expiredToken });import { StrictDB } from 'strictdb';
const db = StrictDB.getInstance();
// StrictDB manages pools per URI — choose a preset based on workload
await db.connect(undefined, { pool: 'high', label: 'API' }); // 20 connections
await db.connect(undefined, { pool: 'standard', label: 'Web' }); // 10 connections
await db.connect(undefined, { pool: 'low', label: 'Worker' }); // 5 connectionsimport { StrictDB } from 'strictdb';
const db = StrictDB.getInstance();
// Register at module load time
db.registerIndex({ collection: 'users', fields: { email: 1 }, unique: true });
db.registerIndex({ collection: 'sessions', fields: { userId: 1, startedAt: -1 } });
db.registerIndex({ collection: 'tokens', fields: { expiresAt: 1 }, expireAfterSeconds: 0 });
// Call once at startup
await db.ensureIndexes();StrictDB skips indexes that already exist, so ensureIndexes() is safe to call every startup.
All dev/test database queries go through scripts/db-query.ts:
pnpm db:query find-expired-sessions # Run a registered query
pnpm db:query:list # List all available queriesCreate query files in scripts/queries/:
// scripts/queries/find-expired-sessions.ts
import { StrictDB } from 'strictdb';
export default {
name: 'find-expired-sessions',
description: 'Find sessions that expired in the last 24 hours',
async run(args: string[]): Promise<void> {
const db = StrictDB.getInstance();
const cutoff = new Date(Date.now() - 24 * 60 * 60 * 1000);
const sessions = await db.queryMany('sessions',
{ expiresAt: { $lt: cutoff } },
{ sort: { expiresAt: -1 }, limit: 50 }
);
console.log(`Found ${sessions.length} expired sessions`);
},
};Register in scripts/db-query.ts and run. Production code stays clean — test queries live in scripts/queries/.
Mandatory for every Node.js entry point:
import { StrictDB } from 'strictdb';
const db = StrictDB.getInstance();
process.on('SIGTERM', () => db.gracefulShutdown(0));
process.on('SIGINT', () => db.gracefulShutdown(0));
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
db.gracefulShutdown(1);
});
process.on('unhandledRejection', (reason) => {
console.error('Unhandled Rejection:', reason);
db.gracefulShutdown(1);
});db.gracefulShutdown() is idempotent — safe to call from multiple signals. It closes all database connections managed by StrictDB before exiting.
CLAUDE.md is a markdown file that Claude reads at the start of every session. It contains the rules, patterns, and conventions that govern Claude's behavior in your project.
There are two levels:
| File | Scope | Checked into git? | Who sees it? |
|---|---|---|---|
CLAUDE.md |
Team rules | Yes | Everyone |
CLAUDE.local.md |
Personal preferences | No (gitignored) | Only you |
Open CLAUDE.md and add a rule to the appropriate section. Use strong language — Claude responds to "NEVER" and "ALWAYS" more consistently than "try to" or "prefer":
### Rule 11: Always Use Relative Imports
- NEVER use absolute paths in imports
- ALWAYS use relative paths from the current file
- This prevents path mapping issues across environmentsCLAUDE.local.md is gitignored. Use it for personal preferences:
## Communication Style
- Respond concisely — I prefer terse explanations
- Show me the code first, explain after
## Testing Preferences
- Always run E2E tests headed so I can watch
- Prefer unit tests over E2E for new features
## Personal Workflows
- When I say "quick deploy", I mean: build, test, push to staging
- When I say "full review", I mean: /review, /security-check, /commitThe most powerful pattern for improving Claude's behavior:
- Claude makes a mistake
- You fix the mistake
- You tell Claude: "Update CLAUDE.md so you don't make that mistake again"
- Claude adds a rule
- Mistake rates actually drop over time
Every mistake is a missing rule. Don't just fix bugs — fix the rules that allowed the bug. The file is checked into git, so the whole team benefits from every lesson learned.
Unit and integration tests use Vitest. Files go in tests/unit/ and tests/integration/.
pnpm test:unit # Run once
pnpm test:unit:watch # Watch mode
pnpm test:coverage # With coverage reportEnd-to-end tests use Playwright. Files go in tests/e2e/.
pnpm test:e2e # Kills test ports → spawns servers → runs tests
pnpm test:e2e:headed # With visible browser
pnpm test:e2e:ui # With Playwright UI mode
pnpm test:e2e:chromium # Chromium only (fast)
pnpm test:e2e:report # Open last HTML reportEvery E2E test must verify at least three things:
// CORRECT — 3 explicit assertions
await expect(page).toHaveURL('/dashboard'); // 1. URL
await expect(page.locator('h1')).toBeVisible(); // 2. Element visible
await expect(page.locator('[data-testid="user"]'))
.toContainText('test@example.com'); // 3. Data correct
// WRONG — passes even if the page is broken
await page.goto('/dashboard');
// no assertions!A test is NOT finished until it has:
- At least one URL assertion (
toHaveURL) - At least one element visibility assertion (
toBeVisible) - At least one content/data assertion (
toContainText,toHaveValue) - Error case coverage
Tests run on separate ports from development:
| Service | Dev Port | Test Port |
|---|---|---|
| Website | 3000 | 4000 |
| API | 3001 | 4010 |
| Dashboard | 3002 | 4020 |
This means you can run your dev server and tests simultaneously without conflicts. The playwright.config.ts is pre-wired to spawn test servers automatically.
pnpm test:kill-ports # Kill stale processes on test ports/create-e2e <feature>— Generates a complete E2E test by reading your source code and identifying what to verify/test-plan— Creates a structured test plan before you write any tests
The starter kit's /optimize-docker command audits your Dockerfile against 12 best practices. A typical optimized Dockerfile:
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
# Stage 2: Production
FROM node:20-alpine AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 appuser
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]When enabled (docker_test_before_push = true in claude-mastery-project.conf), no docker push is allowed until the image passes local verification:
- Build the image
- Run the container
- Wait 5 seconds
- Verify it's still running (didn't crash)
- Health endpoint returns 200
- No fatal errors in logs
- Clean up test container
- Only then push
Before deploying any website:
- Rybbit Analytics — Is
NEXT_PUBLIC_RYBBIT_SITE_IDset with a site-specific ID? - Secrets — No API keys, passwords, or tokens in the codebase?
- Tests — All tests passing?
- TypeScript —
pnpm typecheckpasses with zero errors? - Docker — Image tested locally? (if using Docker push gate)
- Branch — On a feature branch, not main?
Hooks not firing at all
- Check
.claude/settings.jsonis valid JSON:python3 -m json.tool .claude/settings.json - Verify hook files exist and have correct paths:
ls -la .claude/hooks/ - Restart Claude Code session — hooks are loaded at session start
- Check the
matcherfield matches the tool name exactly
Hook blocks unexpectedly
- Read the error message — it tells you exactly why it blocked
- For
check-branch.sh: setauto_branch = falseinclaude-mastery-project.conf - For
check-rybbit.sh: setanalytics = nonein conf, or add the Rybbit site ID - For
block-secrets.py: you're trying to access a sensitive file — use/setupinstead
Hook doesn't block when it should
- Test manually:
echo '{"tool_input":{"command":"git commit"}}' | bash .claude/hooks/check-branch.sh; echo "Exit: $?" - Check the matcher —
Bashonly matches Bash tool calls, not Read/Write
"Port already in use" error
# Find what's using the port
lsof -i :3000
# Kill it
kill -9 <PID>
# Or kill all test ports at once
pnpm test:kill-portsDev server starts but tests fail with connection refused
- Tests use different ports (4000/4010/4020). Check
playwright.config.ts - Run
pnpm test:kill-portsto clear stale test processes
"Cannot find module" after moving files
- Check your import paths — did you update all references?
- Run
pnpm typecheckto find all broken imports - Check
tsconfig.jsonpath mappings
"Type 'any' is not assignable"
- This is the starter kit working as intended —
anyis banned - Add proper type annotations or create a type in
src/types/
TypeScript compiles but runtime errors
- Check for type assertions (
as) that might be hiding mismatches - Verify your
tsconfig.jsonhasstrict: true
"Connection timed out" or "ServerSelectionError"
- Check
STRICTDB_URIin.env— is the connection string correct? - For cloud-hosted databases, whitelist your IP in the provider's Network Access settings
- Try connecting with a database CLI tool to verify the URI works
"Too many open connections"
- You're probably creating database clients outside StrictDB — don't
- Always use
import { StrictDB } from 'strictdb'and call methods on the instance - Check for hot-reload connection leaks — StrictDB handles this with
globalThis
Connection works locally but fails in Docker
- The container can't reach
localhost— use the actual database host URI - Check if
STRICTDB_URIis being passed to the container via env vars
"BLOCKED: You're on main"
- This is the auto-branch hook working correctly
- Prevention: ALWAYS run
git branch --show-currentat the START of any task, before editing files. If on main, branch immediately:git checkout -b feat/<task-name> - If already blocked: Create the branch now:
git checkout -b feat/my-feature— your staged changes carry over, then commit - Or disable with
auto_branch = falseinclaude-mastery-project.conf
Accidentally committed to main
- If not pushed:
git reset HEAD~1to undo the commit (keeps changes) - If pushed: create a revert commit
Merge conflicts after using /worktree
- Each worktree is isolated — merge the branch normally
git merge task/add-authfrom main
Extremely slow file operations
- Your project is probably on
/mnt/c/— move it to~/projects/ - Check:
pwdshould show/home/you/projects/..., NOT/mnt/c/Users/...
Playwright tests failing in WSL
- Install browser dependencies:
npx playwright install --with-deps - Make sure you're running VS Code in WSL mode (green icon bottom-left)
Hot reload not working
- File watching doesn't work across the Windows/Linux boundary
- Move project to WSL filesystem:
~/projects/
"pnpm: command not found"
- Install:
npm install -g pnpm - Or use corepack:
corepack enable
"ERR_PNPM_LOCKFILE_MISSING"
- Run
pnpm installto generate the lockfile - If converting from npm: delete
package-lock.json, runpnpm importthenpnpm install
Tests pass locally but fail in CI
- Different browser versions — pin in
playwright.config.ts - Timeouts too short — increase in the config
- Flaky selectors — use
data-testidattributes
"Target closed" or "Frame detached" errors
- Page navigated before assertion completed
- Add
await page.waitForURL(...)before assertions
Tests hanging
- Kill stale processes:
pnpm test:kill-ports - Check if
webServerin Playwright config is spawning correctly
Q: Do I need to install all the MCP servers? A: No. MCP servers are optional. The starter kit works fully without any MCP servers. Install the ones that match your workflow — Context7 for up-to-date docs, Playwright for browser testing, ClassMCP for CSS projects.
Q: Can I use this with JavaScript instead of TypeScript?
A: Yes. Use /new-project my-app clean to get zero coding opinions — including no TypeScript enforcement. The default profile enforces TypeScript because it's a best practice for AI-assisted development, but clean mode lets you choose.
Q: Does this work with monorepos?
A: The starter kit is designed for single-project repositories. For monorepos, you could use /convert-project-to-starter-kit on individual packages, or customize the template to work with your monorepo structure.
Q: Can I use npm or yarn instead of pnpm?
A: Yes. The default profile uses pnpm, but you can specify any package manager during /new-project setup or change it in claude-mastery-project.conf.
Q: What happens if a hook blocks something I need to do?
A: Read the error message — it explains why. If you genuinely need to bypass it, edit .claude/settings.json to temporarily remove the hook, or change the relevant config setting (e.g., auto_branch = false).
Q: Can I add my own slash commands?
A: Yes. Create a .md file in .claude/commands/. The filename becomes the command name. Add a YAML frontmatter block with description and allowed-tools. See existing commands for examples.
Q: Why does /review sometimes miss issues?
A: /review is a structured prompt, not a linter. For comprehensive analysis, use it together with /security-check and the lint-on-save hook. RuleCatch provides automated monitoring across all sessions.
Q: How do I update the starter kit in an existing project?
A: Run /update-project to pull the latest commands, hooks, skills, and rules into a registered project. It shows a diff report before applying and preserves your custom files. For projects not yet using the starter kit, use /convert-project-to-starter-kit first.
Q: Can I use Prisma or Mongoose instead of StrictDB?
A: StrictDB is recommended for AI-assisted development because it provides a simpler mental model with fewer abstractions. But for clean profile projects, you can use any ORM or ODM you prefer.
Q: What if I need multiple databases?
A: StrictDB supports multiple connections via different URIs. Call connect() with different URIs and labels. Each gets its own pool.
Q: How do I switch database engines (e.g., MongoDB to PostgreSQL)?
A: Change STRICTDB_URI in .env to point at the new engine (e.g., STRICTDB_URI=postgresql://...). StrictDB auto-detects the driver from the URI scheme. Your db.queryOne, db.queryMany, db.insertOne calls stay the same.
Q: Why 3 assertions minimum? A: A test with one assertion (like checking the URL) can pass even if the page is completely broken. Three assertions verify the URL is correct, the expected content is visible, and the data is accurate. This catches real bugs, not just "the page loaded."
Q: Do I need E2E tests for every feature?
A: The check-e2e.sh hook only blocks push to main if there are zero E2E tests. You don't need 100% E2E coverage — focus on critical user flows (login, checkout, data submission).
Q: How do I debug a failing E2E test?
A: Run pnpm test:e2e:headed to see the browser, or pnpm test:e2e:ui for Playwright's debugging UI. Check pnpm test:e2e:report for the last test report with screenshots and traces.
Q: Is the Docker push gate mandatory?
A: No, it's disabled by default. Enable with docker_test_before_push = true in claude-mastery-project.conf. Recommended for production workflows.
Q: Why does the kit check for Rybbit before deploying?
A: The check-rybbit.sh hook only activates if analytics = rybbit is set in your project profile. If you don't use Rybbit, set analytics = none or use the clean profile. The check prevents deploying without analytics tracking.
Q: Can I deploy to platforms other than Dokploy?
A: Yes. The kit supports Dokploy, Vercel, and static hosting (GitHub Pages, Netlify). Specify during /new-project or change hosting in your profile.
Q: Is RuleCatch required?
A: No. RuleCatch is completely optional. The check-rulecatch.sh hook skips silently if RuleCatch isn't installed. The free monitor mode (pnpm ai:monitor) works without any API key. The full experience (dashboards, violation tracking, alerts) requires a RuleCatch.AI account.
Q: What does the free monitor show? A: Every tool call Claude makes (Read, Write, Edit, Bash), token usage per turn, cost per session, and which files are being accessed — all updating live in a separate terminal.
This guide is part of the Claude Code Starter Kit by TheDecipherist.