Vibe Coding and Extensions
Working with more flow, using the agents, skills, MCP servers and plugins that extend what Claude Code can do.
Chapter 4 | Vibe Coding Written for: every level — beginners getting started, and experienced hands who want more throughput
What vibe coding is, and why it changed the field
Vibe coding is a term Andrej Karpathy (formerly head of AI at Tesla and at OpenAI) coined in 2025. It means writing software by telling an AI in plain language what you want and letting it produce the code. Your job is to set the direction, test, and give feedback — not to type every line by hand.
The difference between a traditional developer and a vibe coder
| Traditional developer | Vibe coder with Claude |
|---|---|
| Writes every function themselves | States the requirement and lets Claude write it |
| Has to remember the syntax of every language | Has to know what they want; no syntax to memorise |
| Googles for solutions one at a time | States the problem; Claude proposes solutions with trade-offs |
| Debugs entirely alone | Hands the error log to Claude to analyse and fix |
| Has to know every library API | States the goal; Claude picks a fitting library |
| Roughly 50-100 lines an hour | Roughly 500-2000 lines an hour (depending on the work) |
Vibe coding does not mean “you need to know nothing”
The most common misunderstanding is that vibe coding means needing no programming knowledge at all. The reality:
⚠️ What you still need to know even while vibe coding
Know what output you want: “an API that returns JSON” beats “make it work”
Know enough to review: you can read the main logic and tell what the code does
Know the red flags: a console.log that should not be there, a hardcoded password
Know when to stop: if Claude has looped five times without a fix, the method has to change
Know the boundaries: work that needs high security needs a more careful review than usual
What kinds of work does it suit?
| Strong fit ✅ | Moderate fit ⚠️ | Handle with care 🔴 |
|---|---|---|
| Prototype & MVP | Core business logic | Authentication / Authorization |
| Internal tools | API integration | Payment / Financial system |
| Automation scripts | Performance-critical code | Cryptography |
| Data processing | Multi-threaded code | Compliance / HIPAA / GDPR |
| CRUD features | Database schema design | Mission-critical infrastructure |
| Boilerplate code | Complex algorithms | Smart contract / Blockchain |
The right mindset for vibe coding
Think of yourself as a product manager, not a programmer
Your role shifts from “the person who writes the code” to “the person who sets the direction”. You need to know what you want, state the requirement clearly, and review the result. Claude takes the implementation.
💡 An analogy that helps
You = the architect: you decide how many rooms the house has, where they go, what it is built from
Claude = the builder: knows how to build, knows the techniques, works fast
If the architect is vague, the builder builds the wrong thing — and that is not the builder’s fault
The laws of vibe coding
| Law #1: Clear beats clever | A plain, clear prompt gives a better result than one that sounds clever but is vague |
|---|---|
| Law #2: Small beats large | Splitting the work into small pieces beats sending everything at once |
| Law #3: Test everything | Never assume Claude is right; test everything before it goes anywhere near production |
| Law #4: Context is gold | Claude works far better when it is given good context |
| Law #5: Fail fast | If Claude cannot fix it in three rounds, change the method or split the problem apart |
| Law #6: Commit often | Commit every time you have working code, in case Claude breaks everything on the next round |
Writing prompts for vibe coding
The most important skill a vibe coder has is not writing code — it is writing a good prompt. Here is a framework that works in practice.
The CLEAR framework for a good prompt
| C — Context | Say what the project is, what the tech stack is, and where in the system this sits |
|---|---|
| L — Language/Library | Name the language, the framework and the version explicitly |
| E — Expected Output | Say what you want, in what shape, and how long |
| A — Assumptions | State the assumptions Claude should hold, such as “assume the user is already authenticated” |
| R — Restrictions | The prohibitions and the limits, such as “no any”, “must run on Node 18” |
Example: a bad prompt versus a good one, side by side
Example 1: building a login form
| ❌ Bad make me a login form | ✅ Good Build a Login Form with React + TypeScript Use React Hook Form for validation Fields: email (format validation), password (min 8 chars) Show a loading state while submitting Errors: shown under the offending field, not in an alert On success → redirect to /dashboard Use Tailwind CSS, support dark mode |
|---|
Example 2: fixing a bug
| ❌ Bad why doesn’t this code work (pastes 200 lines) | ✅ Good In @src/api/user.ts, function getUserById when userId is null I get the error: “Cannot read properties of null” Expected: return null when the user does not exist Using Prisma + TypeScript strict mode |
|---|
Example 3: building a new feature
| ❌ Bad add search to the system | ✅ Good Add Search to the Product List Page Tech: Next.js 14, Prisma, PostgreSQL Requirements: - Search by: product name, brand, category - Debounce 300ms (do not hit the API on every keystroke) - Show a loading skeleton while the search runs - The URL follows the search term (?q=xxx) - Works together with pagination |
|---|
Prompt patterns used often in vibe coding
| Pattern 1: “Act as” — give Claude a persona |
|---|
Telling Claude to play a specific role gives its answers the perspective and the standard you are after:
> Act as a Senior Backend Engineer specialising in Node.js
and PostgreSQL performance optimization
Review @src/db/queries.ts and identify every N+1 query,
then propose a fix for each using Prisma
> Act as a Security Auditor
Audit @src/api/auth.ts for every vulnerability
Sort them by severity (Critical/High/Medium/Low)
> Act as a UX Developer who owns Accessibility
Review @src/components/Form.tsx
so it meets the WCAG 2.1 AA standard
| Pattern 2: Step-by-step — break the work into stages |
|---|
For complex work, spell out the stages you want instead of only naming the end result:
> Work through this one step at a time, waiting for my approval before the next:
Step 1: analyse @prisma/schema.prisma
and list every relationship in it
Step 2: identify the three most expensive queries
in @src/server/actions/*.ts
Step 3: propose an index strategy for each of those queries
Step 4: create a Prisma migration with a rollback plan
| Pattern 3: Constraint-first — state the limits before the goal |
|---|
Say what has to be avoided before you say what you want, and Claude will not have to redo the work:
> Constraints (read before doing anything):
- Do not install new dependencies
- Do not touch files outside src/features/payment/
- Must run on Node 18 (no newer APIs)
- Error messages must be in Thai
Task: build a retry mechanism for the payment API
If the API fails, retry 3 times with exponential backoff
Log every retry attempt
| Pattern 4: Example-driven — show the result you want |
|---|
Instead of describing it in words, show the input and output you expect:
> Build a formatCurrency function matching these examples:
Input → Expected Output:
1000 → "1,000 baht"
1500.50 → "1,500.50 baht"
0 → "0 baht"
-500 → "-500 baht"
1000000 → "1,000,000 baht"
Use TypeScript, and handle null/undefined as well
Write unit tests covering every case with Vitest
| Pattern 5: Iterative refinement — shape it in a loop |
|---|
You do not need a perfect result on the first try. Iterate:
# Round 1: a draft
> Build a rough Product Card component first
It does not have to look good yet, just get the structure right
# Round 2: add the detail
> Good. Now add:
- Hover effect
- Loading skeleton state
- Error state for when the image fails to load
# Round 3: responsive
> Make it responsive:
- Mobile: a list (1 column)
- Tablet: a 2-column grid
- Desktop: a 3-column grid
# Round 4: accessibility
> Add aria-label, role, keyboard navigation
A vibe coding workflow that holds up
This workflow comes out of several hundred hours of real work. It is built to give the best result without putting the code at risk.
Phase 1: Setup — every time, before you start (5 minutes)
| 1 | Always create a new branch Never work on main directly. Branch every time, even for small work |
|---|
git checkout -b vibe/feature-name
# example: git checkout -b vibe/user-profile-page
| 2 | Read CLAUDE.md before you start Always have Claude read the project’s context first |
|---|
> Read @CLAUDE.md and tell me what we are working on
and what rules this project has that I need to watch for
| 3 | Set the scope explicitly Tell Claude what this session covers and what is out of scope |
|---|
> This session we are building the User Profile Page
Scope: show user data, edit profile, upload avatar
Out of scope: email change, password change (a separate session)
Start with the data layer, then the UI
Phase 2: Build — the main working cycle
Repeat this cycle for every feature until it is finished:
| A | Describe — say what you want Use the CLEAR framework to write a clear prompt: context, expected output, constraints |
|---|
| B | Claude builds — let it finish Do not interrupt. Let Claude finish the work, then review the whole thing |
|---|
| C | Check — read the code Read the main logic, look for unhandled edge cases and security issues, before you run anything |
|---|
| D | Run — run it and test it Run the tests, run the dev server, click through it, watch the console for errors |
|---|
| E | Feedback — tell Claude the result If it works, commit and move on. If there is an issue, tell Claude, with a clear error log |
|---|
| F | Commit — record every milestone Commit whenever a sub-feature works. Do not wait for everything to be finished |
|---|
# a real session
# A: Describe
> Build a Prisma query that fetches a User Profile
include: posts (5 most recent), followers count, following count
exclude: password, resetToken
type-safe with TypeScript
# D: Run - run the query test
npx ts-node -e "const {getUserProfile} = require('./src/lib/user'); getUserProfile(1).then(console.log)"
# E: Feedback - when there is an error
> I get this error:
PrismaClientKnownRequestError: Invalid `prisma.user.findUnique()`
The column `User.followersCount` does not exist
Our schema has no such column; it has to be counted from the relation instead
# F: Commit once it passes
git add -A && git commit -m "feat(user): add getUserProfile query with relations"
Phase 3: Review — every time, before you merge
This step matters. Do not skip it even when you are in a hurry. Use Claude to review Claude’s own code:
# Self-review with Claude
git diff main...HEAD | claude -p \
"Review this code as a Senior Developer
Check:
1. Security vulnerabilities
2. Whether the error handling is complete
3. Edge cases that may have been missed
4. Performance issues
5. Unnecessary TypeScript any
Give me a list ordered from high to low priority"
# Targeted security check
git diff main...HEAD | claude -p \
"Check security only:
- SQL Injection
- XSS
- IDOR (Insecure Direct Object Reference)
- Missing authorization checks
- Sensitive data exposure"
Real vibe coding, from start to finish
Every example in this section is a real prompt and workflow that works on a Next.js + TypeScript + Prisma project. Adapt the tech stack as you need.
| Example 1: building a new REST API endpoint |
|---|
The situation: you need an API that lets a user view their Order History, with pagination.
# Prompt 1: analyse before building
> Look at @prisma/schema.prisma and @src/types/
then design an API endpoint for GET /api/orders
Requirements:
- Pagination: ?page=1&limit=10
- Filter: ?status=pending|completed|cancelled
- Sort: ?sort=date_desc|date_asc|amount_desc
- Return: orders array + totalCount + totalPages
Tell me whether the schema is missing any field
before you implement anything
# (Claude analyses it and reports that the schema is missing an index)
# Prompt 2: create the migration first
> Create a Prisma migration adding the indexes you recommended
Name the migration: add_order_query_indexes
# Prompt 3: build the service layer
> Create a getOrderHistory function in src/server/services/order.ts
Takes: userId, page, limit, status?, sort?
Return type: { orders: OrderWithItems[], totalCount: number }
Use a Prisma transaction for the count + fetch
Handle: invalid userId → throw NotFoundError
# Prompt 4: build the API route
> Create src/app/api/orders/route.ts
- GET handler calling getOrderHistory
- Validate query params with zod
- Auth check with getServerSession
- Error handling: 400, 401, 404, 500
- Rate limiting: 60 requests/minute
# Prompt 5: write the tests
> Write unit tests for getOrderHistory
covering these cases:
- Happy path: the correct orders come back
- Empty result: the user has no orders
- Invalid userId: throws
- Pagination: page 2 returns the right data
Use Vitest + a Prisma mock
| Example 2: fixing a performance bug |
|---|
The situation: the Dashboard page takes 8 seconds to load and has to come down under 2 seconds.
# Prompt 1: analyse the problem
> The Dashboard page in @src/app/dashboard/page.tsx
loads very slowly and I need to profile it
Please:
1. Add console.time/timeEnd to measure each section
2. Point out which part looks slowest from the code
3. Recommend a better profiling approach
# (Running it produces the data: the DB query takes 7 seconds)
# Prompt 2: analyse the query
> Here is the slow query (7 seconds):
[paste the query code]
The problems I can see:
- It fetches every user and then filters in JS
- There is no index on created_at
- It does not use pagination
Please rewrite it properly:
- Filter in the DB, not in JS
- Add the indexes it needs
- Add pagination
# Prompt 3: fetch in parallel
> The Dashboard fetches four things separately:
totalRevenue, newUsers, activeOrders, topProducts
Right now they run one after another (sequential)
Refactor them to run in parallel with Promise.all
and cache the results with unstable_cache (Next.js)
TTL: 5 minutes
| Example 3: refactoring old code |
|---|
The situation: there is an old function written badly, and it needs refactoring without any change in behaviour.
# Prompt 1: analyse first
> Analyse @src/utils/pricing.ts
and tell me:
1. What each function does
2. What code smells are in there
3. Whether there are any bugs hiding in it
Do not fix anything yet, just analyse
# Prompt 2: write the tests before refactoring
> Write comprehensive tests for
every function in @src/utils/pricing.ts
before we refactor, so we know behaviour does not change
Cover the edge cases the analysis turned up
# Prompt 3: refactor one function at a time
> Refactor the calculateDiscount function in pricing.ts
Goals:
- Remove the duplicated code
- Pull out the pure functions
- Add the missing TypeScript types
- Do not change behaviour (every test must still pass)
One function at a time; wait for my approval before moving on
| Example 4: building a whole feature from scratch (mini project) |
|---|
The situation: build a Notification System, from the database schema up to the UI.
# Session 1: Database Design
> Design a Notification system for this project
Requirements:
- Types: order_update, payment_success, promo, system
- Status: unread/read/archived
- Delivery: in-app (first), email (later)
- Scalability: some users may have 10,000+ notifications
Produce:
1. The Prisma schema
2. The migration
3. Seed data for testing
# Session 2: Backend
> Build the full notification service:
- createNotification(userId, type, data)
- getNotifications(userId, { unreadOnly, limit, cursor })
- markAsRead(userId, notificationIds[])
- markAllAsRead(userId)
- deleteNotification(userId, notificationId)
Use cursor pagination (not offset)
Add an optimistic update for markAsRead
# Session 3: API Routes
> Build the API routes:
GET /api/notifications
POST /api/notifications/read
POST /api/notifications/read-all
DELETE /api/notifications/:id
Auth required on every route
Rate limit: 100 req/minute
# Session 4: Real-time with Server-Sent Events
> Add real-time notifications:
- GET /api/notifications/stream (SSE endpoint)
- Client subscribes with EventSource
- Push immediately when a new notification arrives
- Reconnect automatically if the connection drops
# Session 5: UI
> Build a NotificationBell component:
- Badge showing the unread count
- Dropdown listing the 10 most recent
- Mark as read on click
- A "Mark all as read" button
- A "View all" link to the /notifications page
- Animate when a new notification arrives
Fixing bugs the vibe coding way
Debugging with Claude has an art to it. Giving it correct, specific information is the key.
The shape of a bug report Claude can fix quickly
# A bug report template for Claude
> Bug Report:
## Environment
- OS: macOS 14.3
- Node: 20.10
- Browser: Chrome 122 (for a frontend bug)
## Error Message (all of it)
TypeError: Cannot read properties of undefined (reading "map")
at ProductList (./src/components/ProductList.tsx:45:23)
at renderWithHooks (...)
## Steps to Reproduce
1. Log in with a normal account
2. Go to /products
3. Click the "On Sale" filter
4. The error appears immediately
## Expected
A list of sale products
## Actual
The page breaks, error in the console
## Code that is probably involved
@src/components/ProductList.tsx lines 40-50
## What I have already tried
- console.log products before .map → it is undefined
- The normal filter (without On Sale) → works fine
Systematic debugging techniques with Claude
Method 1: binary search the bug
> Problem: the checkout flow misbehaves and I do not know which step
Add logging so we can find where the problem is:
Add a console.log at the beginning of:
- validateCart()
- calculateTotal()
- createPaymentIntent()
- updateInventory()
- sendConfirmationEmail()
Log format: "✓ [function name] completed: [result]"
or "✗ [function name] failed: [error]"
Method 2: hypothesis testing
> Bug: some users can log in, some cannot
My hypotheses:
H1: Email is case-sensitive (john@test.com vs John@test.com)
H2: Some special character in the password is not escaped correctly
H3: The session expires too quickly
Please:
1. Analyse @src/lib/auth.ts and say whether H1, H2, H3 are possible
2. Add logging to test each hypothesis
3. Propose a fix for the most likely one
Method 3: rubber duck debugging
> I will explain the logic as I understand it; find the flaw:
My understanding:
1. The user clicks "Add to Cart"
2. The frontend calls POST /api/cart
3. The server increments the quantity of the existing product
4. It returns the new cart
5. The frontend updates state
Bug: adding the same product twice in quick succession
gives quantity 1, not 2
(Claude usually spots the race condition quickly)
Advanced techniques for vibe coding
Technique 1: the two-Claude technique
Use two Claudes on the same job — one builds, the other reviews — to catch the blind spots:
# Claude number 1: build the implementation
> Build an authentication middleware for Express
[implementation...]
# Claude number 2 (a fresh session): review
> Here is an authentication middleware that was just written:
[paste the code from Claude number 1]
Review it as a Security Expert:
- The vulnerabilities you can see
- Possible attack vectors
- Missing edge cases
Do not look only at the logic; look at it from an attacker's angle
Technique 2: documentation-driven development
Write the documentation first and have Claude implement against it. The code lands closer to the spec:
# Step 1: write the API doc first
> Write the API Documentation for the
Payment Webhook endpoint
in OpenAPI 3.0 format
Cover: request format, response codes,
error cases, security requirements
# Step 2: have Claude implement against the doc
> Implement the endpoint against this API doc:
@docs/api/payment-webhook.yaml
Do not add or remove behaviour relative to the spec
If the spec is unclear, ask instead of assuming
Technique 3: test-first vibe coding
Have Claude write the tests first and implement afterwards. The result covers far more:
# Step 1: write the test spec
> Write test cases for coupon validation:
- expired coupon → error
- coupon used past its limit → error
- coupon does not match the user tier → error
- price below the minimum → error
- happy path → returns the discount amount
- both percentage and fixed-amount coupons
Use Vitest, mock Prisma
Do not implement the actual function yet
# Step 2: let the tests be the spec for the implementation
> Now implement the validateCoupon function
so that every test in @src/__tests__/coupon.test.ts passes
Do not modify the tests
Technique 4: scaffolding plus customisation
Have Claude generate the boilerplate first and adjust it afterwards. That is faster than doing everything in one pass:
# Step 1: generate the scaffold quickly
> Generate CRUD boilerplate for the Product entity
- Service layer (create, read, update, delete, list)
- API routes
- TypeScript types
- Basic tests
Use the same pattern as @src/features/user/
# Step 2: layer on the business logic
> The Product scaffold is in place
Now add the specific business rules:
- The price must not fall below cost (see product.cost)
- Products in the "featured" category need at least 3 images
- When stock drops below 10, send an alert to the admin
Technique 5: progressive enhancement
Start from an MVP that works, then enhance one layer at a time. Feedback comes fast and the risk stays low:
# Layer 1: make it work first (no frills)
> Build search for products
Version 1: a basic SQL LIKE search
Nothing fancy, it just has to work
# Layer 2: add the UX
> Now add:
- Debounce 300ms
- Loading state
- A "No results" empty state
# Layer 3: performance
> Optimize:
- Add a full-text search index
- Cache the results of frequent searches
# Layer 4: advanced features
> Add:
- Search suggestions
- Recent searches
- Filters combined with search
Anti-patterns to avoid
Vibe coding has pitfalls that come up again and again. Knowing them in advance saves a lot of time.
Anti-pattern 1: prompt spaghetti
Prompting over and over without understanding the problem, which leaves the code messier each round.
| ❌ Prompt spaghetti > why is it erroring > still erroring after the fix > try another approach > still nothing, try again (ten rounds later, the code is worse) | ✅ The right way > Stop, reset, git stash > Describe the problem again from the start > Give the exact error plus context > One step at a time, approval before moving on (fixed in one or two rounds) |
|---|
Anti-pattern 2: context overload
Handing Claude every file at once, which confuses it and fills the context fast.
| ❌ Context overload > Read every file in src/ and tell me what could be refactored (50 files, 5,000 lines) Claude: confused, vague answers | ✅ Focused context > Look at @src/features/payment/ only what could be refactored focus on error handling (5 files, 300 lines) Claude: clear answers you can act on |
|---|
Anti-pattern 3: not committing before a large change
This is the most painful mistake there is. Claude makes the change, everything breaks, and there is no way back:
⚠️ The iron rule: always commit first
Before letting Claude do anything that touches several files → git commit
Before letting Claude do a large refactor → git commit
Before agentic mode (working autonomously) → git commit
If you forgot to commit and Claude has already changed the files → git stash to keep the old work
The commit message does not have to be pretty; “WIP before Claude refactor” is enough
Anti-pattern 4: vibing without tests
| ❌ No testing culture “Claude wrote it, it is probably fine” “No time to write tests” “We will test it at launch” → Bugs found in production → Hard to fix, because there are no tests | ✅ A testing culture Every new feature has at least a happy-path test Claude writes the tests along with the implementation Tests run before every commit → Bugs found in development → Refactoring can be done with confidence |
|---|
Anti-pattern 5: not reading the code Claude wrote
Claude can produce code that works but has a problem hiding in it. Not reading it at all is a risk not worth taking:
| Read at minimum | Function signatures, return types, the error handling pattern |
|---|---|
| What to flag | Hardcoded values, TODO comments, a console.log someone forgot to delete |
| Red flags | The any type, eval(), raw SQL strings, a missing auth check |
| If you cannot follow it | Have Claude explain the main logic first, then approve |
Vibe coding for people with no programming background
Vibe coding is not limited to developers. Someone who has never written code can use Claude to build what they need — but the approach is different, and worth understanding.
What to have ready before you start
Know what you want: you do not need to know how it is implemented, but you do need to know what the output looks like.
Have the environment ready: Node.js installed, VS Code, a terminal you can open.
Be patient and ready to iterate: the first result may not be complete; it will need refining.
Know how to copy and paste: know how to copy an entire error message across to Claude.
A workflow for non-developers
| 1 | Say what you want in plain language No technical vocabulary needed. Say it the way you understand it; Claude will ask if it is unclear |
|---|
> I need a program that:
- Takes a CSV file with customer names and purchase amounts
- Calculates the total per customer
- Emails me a summary every Sunday
I am on Windows and I do not know much about coding
| 2 | Let Claude recommend the approach Claude will propose something that fits your level of knowledge |
|---|
| 3 | Ask Claude to explain every step You do not have to guess what to do. Ask at any point |
|---|
> Tell me step by step what I need to do
and explain why each step is needed
If I have to type something in the terminal, say exactly what
| 4 | Copy the whole error across to Claude When an error appears, copy all of it; do not translate it |
|---|
> I ran it and got this error:
[paste the entire error]
I followed every step you gave me
Tools a non-developer can build with vibe coding
| Tool | Difficulty to vibe code | Benefit |
|---|---|---|
| Excel → Google Sheets automation | Easy | Saves time summarising data |
| Automated email digest | Easy | No summarising by hand |
| PDF → text extraction | Easy | Searchable documents |
| A personal data dashboard | Moderate | KPIs at a glance |
| A chatbot answering FAQs | Moderate | Less support workload |
| A simple website | Moderate | No freelancer needed |
| An internal admin tool | Moderate | Easier data management |
Measuring how a vibe coding session went
At the end of each vibe coding session, review how it went. That is how the skill compounds.
Metrics worth tracking
| Prompt efficiency | How many prompts did it take to get the result you wanted? Fewer is better |
|---|---|
| Iteration count | How many rounds before the code worked? Target: under 3 per feature |
| Test coverage | What percentage of the resulting code is covered by tests? Target: over 70% |
| Bug rate | After deploying, how many bugs a week come from vibe-coded work? |
| Velocity | How many features a day, compared with before Claude? |
| Token cost | How many tokens on average per feature? Can that come down? |
Session retrospective — reviewing after the work
Use Claude to run the retrospective at the end of a big session:
> Run a retrospective for this session:
We worked [X] hours and built [Y] features
We got stuck on [describe problems]
We used roughly [Z] prompts overall
Please analyse:
1. Which prompt gave the best result, and why?
2. Which prompt wasted the most time?
3. If we did this again, what would we change?
4. Is there a pattern worth adding to CLAUDE.md?
Glossary for this chapter
| Vibe coding | Writing software by telling an AI in plain language what you want and letting it produce the code |
|---|---|
| Prompt engineering | The craft of writing prompts that make an AI understand and do exactly what you meant |
| CLEAR framework | Context, Language, Expected Output, Assumptions, Restrictions |
| Iteration | Repeating to improve: a draft on the first round, more detail on the next |
| Scaffold | The basic structure of the code, generated quickly, with no business logic in it yet |
| Progressive enhancement | Starting with a simple version and enhancing it a layer at a time |
| Two-Claude technique | One Claude builds, another reviews, to catch the blind spots |
| Test-first | Writing the tests before implementing, which produces more correct code |
| Anti-pattern | An approach that looks sensible but tends to cause trouble later |
| Prompt spaghetti | Prompting over and over without understanding the problem, which makes the code messier |
| Context overload | Sending so much that Claude gets confused and the context fills up |
| Rubber duck debugging | Explaining the problem out loud to someone else, which often surfaces the cause on its own |
| Binary search debug | Finding a bug by halving the problem: test the first half, and if it is fine the bug is in the second |
| Retrospective | Reviewing what was done in order to learn from it and improve |
Chapter 4 | Vibe Coding — continued (page 20 onward) Written for: every level — debugging techniques, ways out for non-coders, and the AI Wiki and RAG for developers
How to stop Claude looping on a bug
The most frustrating thing in vibe coding is Claude going round in circles on a bug without ever fixing it. Try one approach, no; try a second, still no; back to the first. That can go on for hours. This section is about getting out of the loop systematically.
Why does Claude loop?
Understanding the cause is what makes the way out obvious. Loops come from four main causes:
| Cause 1: missing context | Claude does not know what has already been tried, so it proposes the same thing again without realising, because the conversation has grown too long |
|---|---|
| Cause 2: the problem is somewhere else | You are fixing A, but the real cause is B, so Claude keeps fixing A without knowing the problem lives elsewhere |
| Cause 3: hallucination | Claude is not sure of the answer but still tries to generate one, so the result is wrong in the same way each time |
| Cause 4: a vague prompt | The requirement is unclear, so Claude interprets it differently each round and produces different results |
The stop-the-loop protocol — follow this order every time
Once Claude has tried and failed twice, stop immediately and follow this protocol:
| Step 1: hard stop — halt everything first |
|---|
# Type this the moment Claude starts looping
> Stop. Do not try a new approach.
Collect everything that has already been tried:
1. Approaches tried that did not work (list them all)
2. What we know for certain from those experiments
3. What we still do not know and need more information about
4. Your hypothesis for where the problem actually is
Do not propose a new fix until you have answered those four
| Step 2: reset the context — clear the bias |
|---|
An over-long conversation leaves Claude biased by what has already been tried. A reset lets it see the problem fresh:
# Option A: /compact and carry on
/compact
# then brief Claude again on what the problem is
without telling it what has been tried
# Option B: start a completely new session
# quit claude and open it again
claude
# then give it fresh context in this bug report format:
> Fresh start: there is a bug [describe] in @[file]
Error message: [all of it]
Expected: [describe]
Actual: [describe]
Context: [tech stack, environment]
Do not refer to any previous session; analyse this from scratch
| Step 3: isolate — separate the problem out |
|---|
Instead of fixing the whole system, build a Minimal Reproducible Example (MRE) — the smallest piece of code that still shows the problem:
> Build a Minimal Reproducible Example for this bug:
I need code that:
1. Is as small as possible while still showing the problem
2. Runs without needing a database or an external API
3. Produces the same error we are seeing
If the MRE makes the problem disappear, that means the problem is
in the interaction between components, not in the core logic
An example: building a real MRE
# The problem: User.findMany() crashes in production
# but works fine in development
# MRE: create a small test file
> Create a file test-db.ts that:
1. Connects to the database directly, not through the middleware
2. Runs the same User.findMany() that crashes
3. Logs the result
Then run it in the production environment
to see whether the crash is in the query itself or in the middleware
| Step 4: change strategy — take a different approach |
|---|
If fixing it head-on does not work, change the strategy for the whole problem:
| Strategy | When to use it | How |
|---|---|---|
| Rewrite | The code is too tangled to fix | Delete it and write it again from the spec |
| Workaround | The root cause is hard and you need a fix now | Find a way around the problem, even if it is not elegant |
| Simplify | The feature is too complex | Cut the requirement down and do a simpler version |
| Change library | The library itself has the bug | Switch to a different library |
| Skip for now | The bug is not critical | Move past it and leave a TODO comment |
| External help | Claude genuinely cannot fix it | Stack Overflow, GitHub Issues, Discord |
Techniques for particular kinds of bug
| Bug type 1: bugs you cannot reproduce (intermittent bugs) |
|---|
A bug that only happens sometimes is the hardest kind to fix, and the kind Claude loops on most.
> This bug happens intermittently. Do not try to fix it directly.
Do this instead:
1. Add comprehensive logging first:
- a timestamp at every step
- the state of the important variables
- a request ID for tracking
2. Build log aggregation so the pattern becomes visible:
When does it happen? How often? Which kind of user?
3. Once there are enough logs, analyse them for the common factor
Do not change any code until the logs tell us the root cause
| Bug type 2: bugs that depend on the environment |
|---|
Works locally, breaks in staging or production. This is hard for Claude because it cannot see the real environment.
> This bug only happens in production
Analyse the differences between the environments:
Local:
- Node version: [x]
- Database: local PostgreSQL
- ENV vars: [list]
Production:
- Node version: [x]
- Database: AWS RDS
- ENV vars: [list]
Build a checklist of everything that could differ
and how to verify each item
Do not assume the code is wrong until every
environment issue has been ruled out
| Bug type 3: race condition bugs |
|---|
These happen when several parts of the code run at once and share state — hard for Claude to grasp, so spell it out.
> I suspect this is a race condition
Here is the scenario:
1. User A clicks "Add to Cart" (async)
2. User A clicks "Add to Cart" again before the first finishes
3. Both rounds read the old cart before updating
4. The first round saves quantity = 1
5. The second round also saves quantity = 1 (not 2)
Please:
1. Confirm whether this really is a race condition
2. Propose a suitable solution (optimistic lock,
pessimistic lock, queue, or something else)
3. Explain the trade-offs of each solution
4. Implement the best one for this use case
A CLAUDE.md section that prevents loops in advance
Add this section to CLAUDE.md so Claude handles bugs systematically from the outset:
## 🐛 Bug Fixing Protocol
### When you hit a bug, always follow this order:
1. **Understand first**: state how you understand the problem before anything else
2. **Hypothesize**: propose 2-3 hypotheses, not just one
3. **Test hypothesis**: find the fastest way to verify
4. **Fix root cause**: fix the cause, not the symptom
### Loop Prevention Rules:
- If two attempts fail → STOP and ask
- Never retry the same approach if the error message is unchanged
- If you do not know the root cause → say so plainly, do not guess
- If you need more information → have the user gather it first
### When stuck:
1. Summarise what is known for certain and what is not
2. Propose diagnostic steps the user can run
3. If external information is needed, name the URL or the doc
The “Explain like I’m wrong” technique — making Claude challenge its own assumptions
This one is powerful. It forces Claude to analyse from a new angle instead of confirming its existing bias:
> We have tried to fix this auth bug three times without success
Now I want you to assume that
"what we think the problem is may not be the real problem"
Re-analyse from zero:
1. If the JWT token is not the problem, where else could it be?
2. What have we not tested at all?
3. What assumptions might we have wrong?
4. If you were this bug, where would you be hiding?
Decision tree: when to stop vibing and do something else
Sometimes vibe coding is not the right solution. Learn the signals that it is time to change method:
| Claude has tried more than 3 rounds without success | → Reset the context and start again, or break the problem into smaller pieces |
|---|---|
| The error message changes but it is still broken | → A new bug from the old fix. git revert and use a different strategy |
| Claude says it is not confident | → Stop vibing. Go and read Stack Overflow or the official docs first |
| The problem involves a third-party library | → Check that library’s GitHub Issues first; it may be a known bug |
| The error is infrastructure-level (DNS, SSL, port) | → Leave Claude Code and read the server logs directly |
| The bug only happens in production | → You need production debugging tools: Sentry, Datadog, CloudWatch |
| Thirty minutes in and still nothing | → Take a break, ask someone, or post in a community |
For people who do not read code: finding a way out intelligently
When something goes wrong that they do not understand, people without a programming background tend to panic and try everything at random. This section is a workflow that lets you solve problems systematically without understanding the code at all.
The underlying principle: “describe it clearly and Claude will handle it”
People who do not read code have one advantage: no technical bias. That means they describe the problem from the user’s point of view, which is often closer to the mark. Use that.
A problem-solving workflow for non-coders
| 1 | Describe the behaviour you see, not what you think caused it Say “what happened”, not “why it happened” — your own interpretation of the cause is usually wrong and sends Claude to the wrong place |
|---|
| ❌ Interpreting the cause yourself “The database has probably broken” “It is probably a memory problem” “JavaScript is probably erroring” (Claude fixes what you said, not the real problem) | ✅ Describing behaviour “I click Save and the page goes blank” “The image does not show, just an empty box” “When I type an invalid email, nothing happens” (Claude analyses from the actual behaviour) |
|---|
| 2 | Copy everything you can see across to Claude Error messages, the screen, the logs — the more information, the better |
|---|
# How to copy an error from the browser console
1. Press F12 to open Developer Tools
2. Click the "Console" tab
3. Find the red text → right-click → Copy
4. Paste it into Claude Code
# How to copy an error from the terminal
1. Select the whole error message
2. Ctrl+C (Windows) or Cmd+C (Mac)
3. Paste it into Claude Code
# Tell Claude where you copied it from
> This is the error from the browser console:
[paste the error]
> This is the error from the terminal when I ran npm start:
[paste the error]
| 3 | Tell Claude what you did beforehand The order of actions matters. List every step you took before the problem appeared |
|---|
> Before the error, this is what I did:
1. Edited the file src/pages/home.js
(added a red button in the middle)
2. Saved the file
3. Refreshed the browser
4. Saw this error immediately
It was working fine before that
| 4 | Have Claude explain before it fixes Get Claude to explain what the problem is and how it will fix it before it does anything |
|---|
> Before you fix anything, explain to me:
1. What this problem is (without technical terms)
2. How you will fix it
3. Whether there is any risk, such as breaking something else
4. Whether there is a Plan B if the fix does not work
Wait for my approval before you actually change anything
| 5 | One step at a time, slowly No rush. Have Claude do one thing, then report the result before continuing |
|---|
> Fix it one step at a time and wait for me to report back
If I do not understand a step, I will ask
If a step produces a new error, I will copy it for you
A problem-reporting template for non-coders
Copy this template and use it the moment something goes wrong:
------- Template: a bug report for non-coders -------
> I have run into a problem and need help
What I want to happen:
[describe what should work]
What actually happens:
[describe what happens instead]
The steps I took:
1. [what I did]
2. [what I did]
3. the error appeared
Error message (all of it):
[paste the error here]
My system:
- Windows / Mac / Linux
- Browser: Chrome / Firefox / Safari
Did it work before: yes / it has never worked
Please:
1. Explain the problem in plain language first
2. Give me the fix one step at a time
3. Wait for me to confirm each step
------- end of template -------
When Claude’s answer does not land — how to ask a follow-up
If Claude answers and you still do not understand, there is nothing to be embarrassed about. Ask again. These are the prompts that come up most:
| Explain it more simply | > Explain that again in a way someone who does not program can follow, without technical terms |
|---|---|
| Ask for an example | > Could you give a more concrete example? |
| Ask for finer steps | > Break this step down further; what exactly do I do? |
| Confirm your understanding | > My understanding is [summarise what you understood]. Is that right? |
| Ask for an alternative | > Is there an easier way that does not involve writing code myself? |
| Ask what happens if you get it wrong | > If I make a mistake at this step, what happens and can it be undone? |
No-code alternatives — when vibe coding is too hard
Sometimes there is a route that involves no code at all, and Claude can point you at the right tool:
> I need [describe what you want]
but I have no programming background
Is there a tool or platform that can do this without writing code?
budget: [state it, or "free if possible"]
# Tools Claude tends to recommend:
- Websites: Webflow, Framer, Squarespace
- Automation: Zapier, Make (Integromat), n8n
- Database/App: Airtable, Notion, Glide
- Chatbot: Voiceflow, Landbot
- Forms: Typeform, Tally
- Dashboard: Retool, AppSmith
The AI Wiki — a knowledge base Claude can search
An AI Wiki is your project’s knowledge organised in a form Claude can search and use immediately, instead of you re-explaining the same context every session. It makes a large difference to the quality of the code Claude produces.
Why does an AI Wiki matter?
Picture it: a new developer joins the project knowing nothing. Without documentation, they ask the same questions over and over. Claude is the same. An AI Wiki is the onboarding document that lets Claude read once and get to work without asking.
A recommended AI Wiki structure
docs/ ← the Wiki's main folder
README.md ← an index of every doc
architecture/
overview.md ← system overview + diagram
data-flow.md ← how data moves through the system
decisions.md ← ADRs (why X was chosen over Y)
domain/
business-rules.md ← the complicated business rules
glossary.md ← domain-specific vocabulary
user-journeys.md ← every main user flow
api/
endpoints.md ← the full API reference
error-codes.md ← every error code + what it means
auth.md ← how to authenticate
database/
schema.md ← ER diagram + field descriptions
indexes.md ← the indexes that exist and why
migrations.md ← the history of important migrations
runbooks/
deploy.md ← how to deploy, step by step
rollback.md ← how to roll back in an emergency
common-issues.md ← frequent problems + their fixes
development/
setup.md ← how to set up a local environment
testing.md ← testing strategy + how to run it
code-style.md ← the conventions in detail
How to use the AI Wiki in Claude Code
Method 1: reference it directly in the prompt
# Reference it before starting work
> Before implementing the payment feature
read the Payment section of @docs/domain/business-rules.md
and @docs/api/auth.md to understand the auth flow
then design a solution consistent with the existing pattern
# Reference it when something is unclear
> Look at @docs/architecture/decisions.md
for how we decided to handle caching
and implement this consistently with that decision
Method 2: point to it in CLAUDE.md so Claude reads it automatically
# add this section to CLAUDE.md
## 📚 Documentation (read before working)
### Before implementing any feature:
- read @docs/domain/business-rules.md
- read @docs/architecture/overview.md
### Before any database change:
- read @docs/database/schema.md
- read @docs/database/migrations.md
### Before any API change:
- read @docs/api/endpoints.md
- read @docs/api/error-codes.md
Writing a good AI Wiki — the principles, with examples
| Example 1: a good business-rules.md |
|---|
# Business Rules
## Pricing Rules
### Discount Calculation
The order discounts are applied in (this matters; always apply in this order):
1. Coupon discount (off the full price)
2. Member discount (off the post-coupon price)
3. Bundle discount (off the post-member price)
4. The final price must not fall below the cost price
Example:
- Full price: 1,000 baht
- Coupon 10%: 1,000 × 0.9 = 900
- Member Gold 5%: 900 × 0.95 = 855
- Bundle 3%: 855 × 0.97 = 829.35
- Cost price: 800 → OK (829 > 800)
### Free Shipping Rules
- Free shipping: order total >= 500 baht (after discount)
- Exception: products in the "Heavy" category never ship free
- Exception: upcountry delivery requires an order total >= 800 baht
## Order Status Rules
| Status | Can change to | Cannot change to |
|--------|--------------|----------------|
| pending | confirmed, cancelled | shipped, completed |
| confirmed | shipped, cancelled | completed |
| shipped | completed | pending, confirmed, cancelled |
| completed | (nothing) | every status |
| cancelled | (nothing) | every status |
| Example 2: a good error-codes.md |
|---|
# API Error Codes Reference
## Format
Every error response has this shape:
{ "error": { "code": "E001", "message": "...", "details": {} } }
## Error Codes
### Authentication (E1xx)
| Code | HTTP | Meaning | What the user sees | Action |
|------|------|---------|------------|--------|
| E101 | 401 | Token expired | "Please log in again" | Redirect to login |
| E102 | 401 | Invalid token | "Session error" | Clear token + login |
| E103 | 403 | Not permitted | "You do not have access" | Show the 403 page |
### Validation (E2xx)
| Code | HTTP | Meaning | What the user sees | Action |
|------|------|---------|------------|--------|
| E201 | 400 | Bad email format | "That email format is not valid" | Highlight field |
| E202 | 400 | Password too short | "Password must be at least 8 characters" | Highlight field |
| E203 | 409 | Duplicate email | "An account already exists for this email" | Show a login link |
## Things to watch
- E500 must never expose the internal error message to the user
- Every E5xx must be logged in Sentry along with the request ID
- E101 and E102 must always clear the cookie as well
Having Claude build and maintain the AI Wiki
You do not have to write the whole Wiki yourself. Claude can build it and keep it current:
# Build the Wiki from the codebase
> Analyse the project and build an AI Wiki
Start from these files:
@src/server/actions/ @src/lib/ @prisma/schema.prisma
Produce:
1. docs/api/endpoints.md from all the route handlers
2. docs/database/schema.md from the Prisma schema
3. docs/domain/business-rules.md from the validation logic
# Update the Wiki after implementing a feature
> The Notification system is finished
Update the related docs:
- add the Notification endpoints to @docs/api/endpoints.md
- add the notification table to @docs/database/schema.md
- add the notification rules to @docs/domain/business-rules.md
# Build a runbook from experience
> We just fixed a dead Redis connection
Create a runbook at @docs/runbooks/common-issues.md
describing the symptoms, the root cause and the fix
so it goes faster next time
RAG (retrieval-augmented generation) for Claude Code
RAG is the technique of letting Claude search a large knowledge base before answering, instead of loading everything into context. It fits large codebases, large document sets, and information that changes often.
When is RAG worth it?
| Use RAG when | You do not need RAG when |
|---|---|
| The codebase is over 100 files | The project is small, under 50 files |
| There is a lot of documentation | There is little documentation and it fits in CLAUDE.md |
| The information changes often, such as API docs | The information is static and rarely changes |
| You need to search across several repositories | You work in a single repo |
| Context fills up often because of large files | There is still plenty of context left |
| A large team all needing the same knowledge | A solo developer, where memorising it is easy |
Simple RAG: MCP Filesystem plus search
The easiest approach adds no infrastructure at all. Use the MCP Filesystem server you already have, plus Claude’s built-in search:
# set up MCP Filesystem to cover all the docs
// .mcp.json at the project root (see local/project/user scopes in Chapter 2)
{
"mcpServers": {
"docs": {
"command": "npx",
"args": [
"-y", "@modelcontextprotocol/server-filesystem",
"/Users/john/Projects/myapp/docs",
"/Users/john/Projects/myapp/src"
]
}
}
}
# after that Claude can search on its own
> Search all of docs for anywhere that mentions
"rate limiting", then summarise it for me
> Find every function related to payment in src/
and list what each of them does
Mid-level RAG: LlamaIndex plus Claude
LlamaIndex is the most widely used RAG framework. It takes about 30 minutes to set up and supports Claude directly.
The setup steps
| 1 | Install the dependencies Requires Python 3.9+ and pip |
|---|
pip install llama-index llama-index-llms-anthropic
pip install llama-index-embeddings-huggingface
# use HuggingFace embeddings (free) instead of OpenAI
| 2 | Build an index from the documents The first indexing pass can take a while, depending on how large the docs are |
|---|
# build_index.py
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.llms.anthropic import Anthropic
from llama_index.core import Settings
import os
os.environ["ANTHROPIC_API_KEY"] = "sk-ant-..."
# load the documents from the folder
documents = SimpleDirectoryReader(
input_dir="./docs",
recursive=True, # read subfolders too
required_exts=[".md", ".txt", ".py"] # only the file types we want
).load_data()
# set Claude as the LLM
Settings.llm = Anthropic(model="claude-sonnet-5")
# build the vector index
index = VectorStoreIndex.from_documents(documents)
# save the index to disk (no need to rebuild every time)
index.storage_context.persist(persist_dir="./index_storage")
print(f"Indexed {len(documents)} documents")
| 3 | Query through RAG Once the index is ready, searching is immediate |
|---|
# query_rag.py
from llama_index.core import StorageContext, load_index_from_storage
from llama_index.llms.anthropic import Anthropic
from llama_index.core import Settings
# load the saved index
storage_context = StorageContext.from_defaults(
persist_dir="./index_storage"
)
index = load_index_from_storage(storage_context)
Settings.llm = Anthropic(model="claude-sonnet-5")
# build the query engine
query_engine = index.as_query_engine(
similarity_top_k=5, # pull the 5 most relevant documents
response_mode="tree_summarize" # summarise across several docs
)
# ask a question
response = query_engine.query(
"how to calculate the discount for a member on the Gold tier"
)
print(response)
print("\nSources:")
for node in response.source_nodes:
print(f" - {node.metadata['file_path']} (score: {node.score:.2f})")
| 4 | Connect RAG to Claude Code through MCP So Claude Code can call the RAG service directly |
|---|
# rag_server.py — a simple HTTP server
from flask import Flask, request, jsonify
from llama_index.core import StorageContext, load_index_from_storage
from llama_index.llms.anthropic import Anthropic
from llama_index.core import Settings
app = Flask(__name__)
# load the index at startup
storage_context = StorageContext.from_defaults(persist_dir="./index_storage")
index = load_index_from_storage(storage_context)
Settings.llm = Anthropic(model="claude-sonnet-5")
query_engine = index.as_query_engine(similarity_top_k=5)
@app.route("/query", methods=["POST"])
def query():
question = request.json["question"]
response = query_engine.query(question)
return jsonify({
"answer": str(response),
"sources": [n.metadata["file_path"] for n in response.source_nodes]
})
if __name__ == "__main__":
app.run(port=8765)
# run the server: python rag_server.py
# ask through bash from inside Claude Code:
> Run: curl -X POST http://localhost:8765/query
-H "Content-Type: application/json"
-d '{"question": "how payment retry is handled"}'
then use the answer to implement the feature
Advanced RAG: codebase search with embeddings
For a large codebase, semantic search is far more powerful than grep. It searches by meaning, even when the function names differ.
# code_search.py — search code by meaning
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core.node_parser import CodeSplitter
from llama_index.llms.anthropic import Anthropic
from llama_index.core import Settings
# load source code only
documents = SimpleDirectoryReader(
input_dir="./src",
recursive=True,
required_exts=[".ts", ".tsx", ".js", ".py"]
).load_data()
# use CodeSplitter to split the code by function/class
splitter = CodeSplitter(
language="typescript",
chunk_lines=50, # roughly 50 lines of code per chunk
chunk_lines_overlap=5
)
nodes = splitter.get_nodes_from_documents(documents)
Settings.llm = Anthropic(model="claude-sonnet-5")
index = VectorStoreIndex(nodes)
# search the code by meaning
retriever = index.as_retriever(similarity_top_k=10)
# example: find the code related to authentication
results = retriever.retrieve("check if user is logged in")
for r in results:
print(f"File: {r.metadata['file_path']}:{r.metadata['start_char_idx']}")
print(f"Score: {r.score:.3f}")
print(r.text[:200])
print("---")
A RAG workflow for real vibe coding
This is the workflow that puts RAG to work in daily development:
# .claude/commands/rag-search.md
# RAG Search Command
# search the knowledge base before implementing
# Usage: /project:rag-search [topic]
Do the following:
1. Run: curl -s -X POST http://localhost:8765/query
-H "Content-Type: application/json"
-d {"question": "information about $ARGUMENTS"}
2. Read the result and its sources
3. Summarise what the knowledge base knows about this topic
and which source files are involved
4. Propose how the new implementation should be done
so it stays consistent with the existing pattern
# usage:
> /project:rag-search payment retry logic
> /project:rag-search user authentication
> /project:rag-search error handling pattern
Auto-updating the RAG index when the code changes
If the index goes stale, Claude gets old information. Set up an auto-update with a Git hook:
# .git/hooks/post-commit
#!/bin/bash
# find the files that just changed
CHANGED=$(git diff --name-only HEAD~1 HEAD)
# if any .md or source file changed
if echo "$CHANGED" | grep -qE ".(md|ts|tsx|py)$"; then
echo "🔄 Updating RAG index..."
python build_index.py --incremental
echo "✅ RAG index updated"
fi
# make it executable
chmod +x .git/hooks/post-commit
# or use watch mode (run it in the background)
# watchmedo shell-command \
# --patterns="*.md;*.ts;*.py" \
# --command="python build_index.py --incremental" \
# --recursive \
# ./docs ./src
RAG for third-party documentation
Beyond internal docs, RAG also works on the documentation of the libraries you use, which lets Claude answer questions about a specific library version more accurately than its training data can.
# download the docs of the libraries you use
# for example: Next.js docs, Prisma docs
# scrape_docs.py
import requests
from bs4 import BeautifulSoup
import os
def scrape_docs(base_url, output_dir, max_pages=200):
"""download docs as markdown files"""
os.makedirs(output_dir, exist_ok=True)
# ... implementation
# docs worth indexing:
# - Prisma docs (they change very often)
# - Next.js App Router docs (version specific)
# - a partner's internal API docs
# - the company knowledge base
# then build the index together with the internal docs
python build_index.py --dirs ./docs ./external-docs
# in Claude Code
> Look at @docs/external/prisma/ and tell me
what API changes Prisma 5.x made relative to 4.x
that affect our code
RAG versus the alternatives: which to pick?
| Solution | Setup difficulty | Suits | Upside | Downside |
|---|---|---|---|---|
| CLAUDE.md only | Easy | Small projects, under 50 files | No setup at all | Size-limited, read every time |
| MCP Filesystem | Easy | Mid-size projects, 50-200 files | Claude searches on its own | No semantic search |
| LlamaIndex RAG | Moderate | Large projects, over 200 files | Semantic search | The index has to be maintained |
| Embedded vector DB | Hard | Enterprise, large teams | Fast, scales | Complex setup |
| External RAG service | Easy to moderate | Teams who do not want infrastructure | Managed, nothing to maintain | Extra cost, privacy concerns |
💡 Recommended for beginners
Start with CLAUDE.md + MCP Filesystem
When the project grows large enough that context fills up often, add LlamaIndex
The best RAG is the one your team can actually maintain, not the most sophisticated one
In summary: the vibe coding checklist for daily use
Everything in this chapter, collected into one file you can use as a quick reference:
# .claude/commands/daily-checklist.md
# Daily Vibe Coding Checklist
## Before the session (5 minutes)
□ git checkout -b feature/[feature name]
□ Read CLAUDE.md: > read @CLAUDE.md and summarise what we are doing
□ RAG search if the project is large: /project:rag-search [topic]
□ Set the scope: tell Claude what is in scope and what is out
## During the build (every iteration)
□ Clear prompt: use the CLEAR framework
□ One small piece at a time: do not send the whole requirement at once
□ Review before running: read the main logic, look for red flags
□ Test everything: run the tests before committing
□ Commit often: commit whenever a sub-feature is done
## When you hit a bug
□ Under 2 rounds: try fixing it directly
□ At 2 rounds: hard stop → re-analyse
□ Still nothing: reset the context → new session
□ Still nothing: build an MRE → isolate the problem
□ Still nothing: change strategy → do not force it
## Before ending the session
□ /project:review — review all the code
□ Run every test; they must pass before you commit
□ /project:handoff — create the handoff document
□ Update the Wiki if any doc has changed
□ git push origin [branch]
Further glossary for this chapter (continued)
| Loop bug | Claude going round in circles on a bug without succeeding; the loop has to be stopped and reset |
|---|---|
| Hard stop | Halting everything immediately and having Claude summarise what it knows before trying again |
| MRE (minimal reproducible example) | The smallest code that still shows the bug, which isolates the problem |
| Race condition | A bug that appears when two processes run at once and share state |
| Intermittent bug | A bug that occurs irregularly, hard to fix because it does not reproduce every time |
| AI Wiki | A knowledge base in Markdown, designed for Claude to read and search |
| RAG (retrieval-augmented generation) | Having an AI search for information before answering, rather than memorising everything |
| Vector index | A data structure holding text embeddings so meaning can be searched |
| Embedding | Turning text into numbers (a vector) that represent its semantic meaning |
| Semantic search | Searching by meaning rather than by keyword matching |
| LlamaIndex | A Python framework for building RAG pipelines, easy to use with an LLM |
| Incremental indexing | Updating the index only for the files that changed, instead of rebuilding it all |
| ADR (architecture decision record) | A document recording an important decision and why that option was chosen |