Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Worked Example: Evolving a Claude Code Setup

This example shows how the book’s primitives fit together on a normal project. The point is not to install every mechanism. The point is to promote a rule only when the next rung solves a real problem.

1. Start with project memory

Run /init, then prune the result. Keep facts Claude needs every session: commands, architecture boundaries, test expectations, and recurring project-specific gotchas.

# Project context

## Build and test
- Run `pnpm test` for unit tests.
- Run `pnpm typecheck` before committing TypeScript changes.
- Run `pnpm test:integration` for files under `src/api/` or `src/db/`.

## Code layout
- API handlers live in `src/api/handlers/<resource>.ts`.
- Shared database helpers live in `src/db/`, never inside handlers.

2. Move narrow rules to narrower scope

If a rule applies only under one path, move it into .claude/rules/ instead of making every Claude Code session carry it globally.

# API handler rules

Applies to `src/api/handlers/**`.

- Wrap every handler with `requireSessionAuth()`.
- Return errors as `{error: {code, message}}`.
- Never return raw database rows; use `serializeForApi()`.

3. Package repeated work as a skill

When you keep pasting the same procedure, turn it into a skill. Put the non-obvious parts first.

.claude/skills/write-api-endpoint/
  SKILL.md
  templates/
    handler.ts
---
name: write-api-endpoint
description: Use when adding or changing API handlers under src/api/handlers.
---

# Writing API Endpoints

## Gotchas

- Use `requireSessionAuth()`, not the legacy `authenticate()` helper.
- Responses must pass through `serializeForApi()`.
- Error responses use `{error: {code, message}}`.

4. Promote consequential failures to hooks

If a failure is expensive and mechanical to detect, do not rely on memory alone. Add a hook or permission rule.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash(git push --force*)",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/block-force-push.sh"
          }
        ]
      }
    ]
  }
}

5. Bound permissions

Allow the commands Claude Code runs constantly, keep risky operations on ask, and deny operations that should never happen in this repo.

{
  "permissions": {
    "allow": [
      "Bash(pnpm test*)",
      "Bash(pnpm typecheck)",
      "Bash(git status*)"
    ],
    "deny": [
      "Bash(git push --force*)",
      "Bash(rm -rf*)"
    ]
  }
}

6. Close the loop with evidence

After the change, Claude Code should run the relevant checks, read the output, fix failures, and repeat until the signal is clean.

1. Implement the endpoint.
2. Run `pnpm test -- users`.
3. Read the failure.
4. Fix the issue.
5. Re-run `pnpm test -- users`.
6. Run `pnpm typecheck`.
7. Report the passing checks.

7. Capture the lesson

If the work exposed a new recurring gotcha, update the right artifact before moving on: CLAUDE.md for broad project context, .claude/rules/ for path-specific rules, a skill for reusable procedure knowledge, or a hook for a hard guarantee.