Stop re-explaining your app to AI. Three stacked prompts plus a confirm-pass scaffold get shippable code from Claude on the first run instead of looping through 14 corrections.
This guide is reviewed for clarity, service accuracy, and AI-search readability. The next quarterly content review is tracked internally before unsupported metrics or client proof are added.
You're not bad at prompting. Your prompts are out of order.
Here's the loop you already know. New chat. You paste your stack. You paste the file structure. You explain what you want. The model hands back code that ignores half of it. So you correct it. Then you correct the correction. By the time it works, you've sent fourteen messages and burned an hour.
The code wasn't the problem. The order was.
Most people give the model an instruction first and the context second — if they give context at all. The model fills the gaps with guesses, and you spend the rest of the chat unwinding those guesses one at a time.
The fix is three prompts, stacked in a fixed order: Context, then Constraint, then Contract. Load everything the model needs, pin the rules before it writes a line, and demand an exact output shape. Run them as one block and the model builds against all three at once instead of guessing.
This is the full pack. Three copy-paste templates with fill-in slots, a bonus confirm-pass prompt that catches gaps before any code gets written, a variable key so you can adapt it to any project, and a complete worked example that ships a real feature in one run. Paste a repo, a goal, and a user into the stack, and you get contract-conforming code on the first try.
Why 14 prompts collapse into 1
The back-and-forth isn't extra work. It's the model resolving ambiguity out loud, on your time. Each correction is you supplying a piece of context, a constraint, or an output spec you should have supplied up front. The 3C stack front-loads all three so there's nothing left to resolve.
- Context first beats instruction first. When the model has to guess your stack, your file layout, or who the feature is for, it guesses from the most common pattern on the internet — not from your repo. Putting the full context above the ask is a documented way to improve how reliably models work from your material instead of their defaults. No context, wrong guess, paid for in corrections.
- Constraints kill the drift. Ask for "a function to handle signups" and you get 400 lines, three new dependencies, and a refactor you didn't request. Name the stack, the exact files, a line ceiling, and a banned-libraries list, and the model stays inside the box.
- A contract makes the first run shippable. When you specify file paths, function signatures, and return shapes, the output drops straight into your repo and runs. Forcing the model to fill a fixed output shape — instead of free-form prose plus code — is the same idea behind structured output, and it's why the first run actually compiles.
Same model. Same build. The stack just moves the thinking to the front, where it costs three prompts instead of fourteen.
The 3C Stack
Three scaffolds, run in order, in the same chat:
- Context — dump the repo, the goal, the user. The model learns your world before it touches code.
- Constraint — pin the stack, the files, the line budget, the banned libraries. The box is set before the first line.
- Contract — specify the exact files, signatures, and return shapes you expect back. The output is shippable, not a draft.
Below are the templates. Copy each one, fill the [BRACKETED] slots, and paste all three as a single message. The variable key after them explains every slot.
Prompt 1 — Context Dump
Rule: context before instruction. If the model has to guess your stack, it guesses wrong — so load the world before you give the ask.
prompt# CONTEXT You are working inside an existing codebase. Read everything below before doing anything. Project: [PROJECT_NAME] Stack: [LANGUAGE + FRAMEWORK + VERSION] Package manager: [PACKAGE_MANAGER] How it runs: [DEV_COMMAND] Repo structure (only the parts that matter): [PASTE_TREE] Relevant existing files (paste full contents): --- [FILE_PATH_1] --- [PASTE_FILE_1] --- [FILE_PATH_2] --- [PASTE_FILE_2] The goal, in plain English (no solution yet): [GOAL] The end user and the job they're trying to get done: [END_USER_AND_JOB] Do not write code yet. Reply only with "Context loaded." and a two-line summary of the goal as you understand it.
That last line matters. Making the model echo the goal back in two lines tells you instantly whether it understood you — before it spends 200 lines proving it didn't.
Prompt 2 — Constraint
This is the prompt that kills the 400-line drift. You're not asking nicely. You're setting hard limits.
prompt# CONSTRAINTS Build under these hard limits. Treat each as non-negotiable. Stack lock: use only [STACK + VERSION]. Match the existing code style exactly. Files you may touch: [FILES_TO_TOUCH]. Touch nothing else. New files allowed: [NEW_FILES_RULE]. Scope ceiling: [MAX_LINES] lines of new code total. If you need more, stop and explain why. Banned libraries: do not add or import [BANNED_LIBS]. Use only what's already installed. No refactors: do not rename, reformat, or "improve" anything outside the listed files. Error handling: [ERROR_PATTERN]. If any constraint conflicts with the goal, stop and ask before writing code.
Prompt 3 — Contract
The contract turns a draft into a deliverable. You define the exact shape of what comes back, so it lands in your repo and runs.
prompt# CONTRACT Return exactly this and nothing else. The output must drop into the repo and run. Deliverables: 1. File: [DELIVERABLE_PATH_1] — [WHAT_IT_CONTAINS] 2. File: [DELIVERABLE_PATH_2] — [WHAT_IT_CONTAINS] Function signatures (match exactly): [SIGNATURES] Return shapes / types: [RETURN_SHAPES] Output format: - One fenced code block per file, with the file path on the line above it. - No prose between blocks, except a one-line note if a constraint forced a tradeoff. - End with the exact command to run or test it. Build it now, conforming to the CONTEXT, CONSTRAINTS, and this CONTRACT.
Bonus — The Confirm-Pass Scaffold
This is the prompt that turns a good first run into a near-guaranteed one. Paste it as the last block in the stack, before "Build it now," and the model has to prove it understood everything before it writes code. Gaps surface as questions, not as broken output.
prompt# CONFIRM PASS — do this BEFORE writing any code Restate, in your own words, in this order: 1. CONTEXT: the stack, the files that matter, the goal, and the end user. 2. CONSTRAINTS: every limit I set (stack, files, line ceiling, banned libraries, error handling). 3. CONTRACT: the exact files, signatures, and return shapes you will produce. Then list: - GAPS: anything missing, ambiguous, or contradictory across the three. - ASSUMPTIONS: what you will assume if I don't answer. Do NOT write code yet. Wait for me to reply "confirmed" or to fill the gaps.
Thirty seconds reading the restatement saves the fourteen-message unwind. If the model's summary is wrong, you caught it for free. If it's right, you type "confirmed" and get your code.
Fill-in legend — what every slot means
Adapt the stack to any project by filling these in. Keep the brackets out of your final paste.
- [PROJECT_NAME] — what the app is, one line. "Hypd waitlist."
- [LANGUAGE + FRAMEWORK + VERSION] — the actual versions, not "modern JS." "Next.js 15 App Router + TypeScript 5."
- [PACKAGE_MANAGER] — npm, pnpm, yarn, pip, poetry. Stops the model inventing install commands.
- [DEV_COMMAND] — how the app starts.
pnpm dev,python app.py. Anchors the test step. - [PASTE_TREE] — the folders that matter, not the whole repo. Trim node_modules and noise.
- [FILE_PATH_n] / [PASTE_FILE_n] — the full contents of files the new code touches or imports. This is the single highest-leverage slot.
- [GOAL] — what should exist when it's done, in plain English. No solution, no tech choices.
- [END_USER_AND_JOB] — who uses it and the job they hire it for. Shapes the edge cases the model handles.
- [STACK + VERSION] — repeat the lock here so the constraint block stands alone.
- [FILES_TO_TOUCH] — exact paths the model may edit or create. The fence around the work.
- [NEW_FILES_RULE] — "yes, only
path/x.ts" or "no new files." - [MAX_LINES] — your line budget for new code. 60 for a small endpoint, 150 for a component. Forces tightness.
- [BANNED_LIBS] — dependencies you don't want pulled in. Name the usual suspects you keep seeing.
- [ERROR_PATTERN] — how failures are handled. "Never throw to the client; return typed JSON."
- [DELIVERABLE_PATH_n] / [WHAT_IT_CONTAINS] — each file you expect back and its job.
- [SIGNATURES] — exact function signatures the model must match.
- [RETURN_SHAPES] — the types or JSON bodies the code returns, including error cases.
Full worked example: a waitlist endpoint, one shot
Here's the whole stack filled in for a real feature — a public signup endpoint on a Next.js app that saves an email to Supabase. This is one message, pasted as a single block.
prompt# CONTEXT Project: Hypd waitlist Stack: Next.js 15 (App Router) + TypeScript 5, React 19 Package manager: pnpm How it runs: pnpm dev Repo structure (only what matters): src/ app/api/ (existing route handlers) lib/supabase.ts (exports a configured `supabase` client) Relevant existing files: --- src/lib/supabase.ts --- import { createClient } from '@supabase/supabase-js' export const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!, ) Goal: A public POST endpoint that accepts an email, validates it, and inserts a row into the `waitlist` table (columns: id uuid default, email text unique, created_at timestamptz default now()). Reject bad emails and duplicates cleanly. End user: a visitor on the landing page submitting their email to join the waitlist. # CONSTRAINTS Stack lock: Next.js App Router route handler only, TypeScript. No client component changes. Files you may touch: src/app/api/waitlist/route.ts (create it). New files allowed: yes, only that file. Scope ceiling: 60 lines of new code. Banned libraries: no zod, no validator, no new dependencies. Use the supabase client from src/lib/supabase.ts. No refactors outside that file. Error handling: never throw to the client; always return JSON with a status code. # CONTRACT Deliverables: 1. File: src/app/api/waitlist/route.ts — exports a POST handler. Signature: export async function POST(req: Request): Promise<Response> Return shapes (JSON body + status): - 200: { ok: true } - 400: { ok: false, error: "invalid_email" } - 409: { ok: false, error: "already_joined" } - 500: { ok: false, error: "server_error" } Output format: one fenced code block with the file path above it, then the test command. No other prose. # CONFIRM PASS — do this BEFORE writing any code Restate CONTEXT, CONSTRAINTS, and CONTRACT in your own words, list GAPS and ASSUMPTIONS, and wait for "confirmed."
The model's confirm-pass reply:
promptCONTEXT: Next.js 15 App Router + TS app. A configured supabase client exists at src/lib/supabase.ts. Goal: a public POST endpoint that validates an email and inserts it into the `waitlist` table, handling bad emails and duplicates. User is a landing-page visitor joining the list. CONSTRAINTS: Only create src/app/api/waitlist/route.ts. Max 60 new lines. No new deps, no zod/validator. No throwing to the client. No changes elsewhere. CONTRACT: Export `POST(req: Request): Promise<Response>`. Return 200 {ok:true}, 400 invalid_email, 409 already_joined, 500 server_error. GAPS: none blocking. ASSUMPTIONS: - The unique-violation Postgres error code is 23505; I'll map it to 409. - I'll lowercase the email before insert to keep the unique constraint consistent. - A malformed JSON body counts as invalid_email (400).
You reply confirmed. The one-shot output:
ts// src/app/api/waitlist/route.ts import { supabase } from '@/lib/supabase' const EMAIL_RE = /^[^s@]+@[^s@]+.[^s@]+$/ export async function POST(req: Request): Promise<Response> { let email: unknown try { ;({ email } = await req.json()) } catch { return Response.json({ ok: false, error: 'invalid_email' }, { status: 400 }) } if (typeof email !== 'string' || !EMAIL_RE.test(email)) { return Response.json({ ok: false, error: 'invalid_email' }, { status: 400 }) } const { error } = await supabase .from('waitlist') .insert({ email: email.toLowerCase() }) if (error) { if (error.code === '23505') { return Response.json({ ok: false, error: 'already_joined' }, { status: 409 }) } return Response.json({ ok: false, error: 'server_error' }, { status: 500 }) } return Response.json({ ok: true }, { status: 200 }) }
promptcurl -s -X POST localhost:3000/api/waitlist -H 'content-type: application/json' -d '{"email":"founder@example.com"}'
One file, inside the line budget, no new dependencies, every return shape from the contract, all four error cases handled, and a test command that runs. No corrections. That's the stack working.
How to run it in 30 seconds
- Fill all three templates, then the confirm-pass block, in one message.
- Paste the actual file contents the new code touches. This is where one-shot success is won or lost.
- Wait for the restatement. If it's wrong, fix the gap. If it's right, reply
confirmed. - Run the test command it hands back. Done.
At Hypd we run every build through this stack — it's how a feature that used to eat an afternoon of corrections ships before lunch. Save these four prompts somewhere you can grab them, swap the brackets per project, and the order does the work.
FAQ
Do I really need all four prompts every time? For a quick one-file change, Context plus Contract often gets there. The confirm-pass earns its keep on anything with real surface area — multiple files, tricky edge cases, or a stack the model tends to guess wrong. The thirty seconds it costs is always cheaper than the correction loop it prevents.
What if the model still gets it wrong on the first run? Read its confirm-pass restatement, not its code. A wrong build almost always traces back to a wrong line in the restatement — a missed constraint or a misread goal. Fix that one input and rerun the stack. You're debugging the prompt, not patching the output, which is faster and it sticks.
Does this only work with Claude? The stack is model-agnostic. Context-first ordering, hard constraints, and a fixed output contract make any capable coding model more reliable. It shines with Claude because it follows long, structured instructions closely, but the same four prompts lift results on whatever model you're building with.
