You asked the AI to refactor a function. It looked clean. You merged it. Three days later a teammate files a bug — turns out the AI quietly changed the error-handling semantics, removed a null check it "thought" was redundant, and the new behavior only surfaces in an edge case your tests don't cover. You spent 20 minutes on the refactor and two hours on the debug. Or maybe you handed the AI a feature spec, watched it write 400 lines, and then realized it built around an abstraction that doesn't fit your codebase at all. Starting over is faster, but you still lost an hour.
Sound familiar?
I built this repo because I kept running into situations like that, and every guide I found online was either "just trust the AI" or thinly veiled advertising for whatever tool the author was using. Neither helped. This is the reference I wish had existed when I started taking AI coding tools seriously in my daily work.
| Section | What you'll find |
|---|---|
| What This Is / Isn't | Scope and honest disclaimers, up front |
| Tool Landscape 2026 | Honest comparison: Cursor, Claude Code, Copilot, Gemini CLI, Windsurf |
| The Core Mental Model | Why most developers get poor results and how to fix it |
| Quick Start | Three things you can do today |
| Context Engineering | How to set up AI tools so they actually know your codebase |
| Working Patterns | What works in practice, task by task |
| Anti-Patterns | Common habits that consistently produce bad results |
| Verification | How to review AI output without burning all the time you saved |
| Repo Contents | Everything in this repo, described |
| Contributing | How to add patterns, guides, and examples |
| This IS | This is NOT |
|---|---|
| A workflow guide grounded in real-world use | A tool advertisement or sponsored content |
| Framework-agnostic and tool-agnostic | Cursor-specific or Claude-specific |
| Updated as tools change | A one-time tutorial frozen in time |
| Honest about failure modes and limitations | A cheerleading session for AI coding |
| Written for developers with existing coding skills | A learn-to-code resource |
| Opinionated where opinions are backed by experience | A catalog of every possible feature |
The guides here assume you already write code. The goal isn't to help you avoid coding — it's to help you work with AI tools in a way that actually makes you faster without introducing a new category of subtle, hard-to-debug problems.
One more thing worth saying up front: this guide doesn't have a fixed "best tool." The landscape shifts fast, models improve on different timelines, and what works for a TypeScript monorepo at a 10-person startup is different from what works for a solo Python data project. I'll share what I've found and be clear about where my experience is limited.
There's no single right answer. The tools are genuinely different, and the right one depends on what you're building, how you work, and what you're willing to pay. Here's my honest read as of mid-2026. I'll flag where my experience with a tool is shallower.
Best for: In-editor AI-assisted development with full codebase context. If you're spending most of your day in an editor and want tight integration with your file tree, Cursor is where I'd start.
Pricing: Free tier (limited); Pro is around $20/month. Team plans available.
Strengths:
- Codebase indexing is genuinely good. It reads your files and can answer questions about your actual project, not a fictional one.
- The
.cursor/rules/system lets you write persistent instructions that apply across sessions — think of it as a standing brief that the AI reads every time. - Tab completion understands context across files, not just the current line.
- Composer (now called Agent mode) handles multi-file edits reasonably well for bounded tasks.
- The diff view for AI-generated changes is well-implemented and makes review fast.
Weaknesses:
- Context window management is opaque. You can't always tell what the model is and isn't seeing, which matters when you're debugging why it's producing something wrong.
- Hallucinated file paths are a real problem, especially on larger codebases. It'll reference files that don't exist with complete confidence.
- Agent mode on large, open-ended tasks often goes sideways. It works best with clear, bounded scope.
- Privacy: your code goes to Anthropic/OpenAI depending on which model you pick. Check your employer's policy before pointing it at proprietary code.
Verdict: My daily driver for in-editor work. The rules system and diff view alone make it worth using over a stock VS Code + extension setup. But don't give it open-ended mandates — scope your tasks.
Best for: Agentic, multi-step development tasks that require actual planning. Terminal-first. This is the tool I use when something is complex enough that I want a real back-and-forth before any code gets written.
Pricing: Usage-based through the Anthropic API. Costs add up on big tasks — budget accordingly, and use a model tier that fits the task rather than always defaulting to the most capable/expensive.
Strengths:
- Best long-context reasoning of any tool I've used. It reads your
AGENTS.md, keeps it in mind, and actually follows the conventions you've documented. - Reads and writes files natively, runs terminal commands, and self-corrects when it encounters errors.
- Explicitly designed for
AGENTS.md-style context files, and it follows them better than any other tool in my experience. - Great at tasks that require understanding the full shape of a codebase before touching it.
- The "show me your plan before writing code" workflow works extremely well with Claude Code because it's good at reasoning through implications.
Weaknesses:
- Costs. A complex agentic task with many file reads and writes adds up fast. You need to be deliberate about when to use it vs. something cheaper.
- Slower than in-editor tools. It's not the right tool for quick autocomplete or a fast "explain this function."
- No GUI. Terminal-only, which is fine for some workflows and a hard no for others.
Verdict: My go-to for any task that requires actual planning and multi-file coordination. The AGENTS.md workflow is the best context engineering approach I've found. Not for everyday autocomplete.
Best for: Teams already in the GitHub ecosystem with compliance requirements, or developers who want AI assistance without switching editors.
Pricing: ~$10/month individual, ~$19/month business. Enterprise has different pricing. Most enterprise orgs I've talked to have it on seat licenses.
Strengths:
- Natively integrated into VS Code, JetBrains, Neovim, and more — no editor switch required.
- Enterprise SSO, audit logs, and IP indemnification that most other tools don't offer yet.
- Copilot Chat has gotten meaningfully better over the past year, especially at explaining code.
- Familiar to most developers — low onboarding friction for teams.
Weaknesses:
- Inline completion quality is noticeably behind Cursor's tab. It's not bad, but side-by-side it's not close.
- Chat context doesn't index the full repository the way Cursor does. It's more "file-aware" than "codebase-aware."
- The reasoning quality on complex tasks lags behind Claude Code.
- Feels like it's been playing catch-up for a while.
Verdict: The right call for organizations with compliance requirements or when editor flexibility matters. If you have no constraints and are optimizing for raw output quality, there are better options. But "good enough and enterprise-safe" is a real value proposition.
Best for: Terminal-heavy workflows, analyzing very large codebases or files, and developers already in the Google ecosystem.
Pricing: Free tier with rate limits; API pricing varies. Gemini 2.5 Pro is available at reasonable rates.
Strengths:
- The context window is enormous. Genuinely useful for tasks where you need to reason over a large codebase, a long log file, or a big document. I've used it to analyze repos that were too large to fit into Claude Code's context without chunking.
- Fast. Noticeably snappier than Claude on most tasks.
- Google Workspace integration if that matters to your workflow.
Weaknesses:
- Less reliable on precise code edits. It understands code well but can be imprecise when making targeted changes.
- The developer tooling ecosystem around it is less mature than Anthropic's or OpenAI's.
- The CLI is functional but rough around the edges compared to Claude Code.
- I've seen it be confidently wrong on language-specific idioms more often than Claude.
Verdict: Keep it around for tasks that benefit from a huge context window — reading a massive codebase, analyzing a 10,000-line log, summarizing a long spec. Not where I'd go for precise, reliable code generation.
Best for: VS Code users who want a Cursor alternative with a slightly different agentic approach.
Pricing: Free tier; Pro pricing comparable to Cursor.
Strengths:
- Cascade (agentic mode) is notably capable and, in my limited experience, slightly more transparent about what it's doing than Cursor's Agent mode.
- Clean interface. Less cluttered than Cursor in some ways.
- Actively developing — it's shipping improvements at a solid pace.
Weaknesses:
- Smaller community, which means fewer examples, guides, and Stack Overflow answers when something goes wrong.
- The rules/context system is less mature than Cursor's
.cursor/rules/setup. - Fewer integrations.
Verdict: Solid second option if Cursor doesn't fit your workflow, or if you want to try an alternative that's meaningfully different rather than just re-skinned. Worth watching — it's improving fast.
If you want one recommendation to start: use Cursor for everyday in-editor work and Claude Code for complex, planned, agentic tasks. That combination covers most development scenarios well. Add Gemini CLI if you routinely work with large files or codebases. Use Copilot if your organization requires it. Try Windsurf if you want an alternative agentic editor.
Most developers who get poor results from AI coding tools share one pattern: they skip the planning step.
They open a chat, describe what they want in a sentence or two, hit send, paste the result into their editor, and move on. This works fine for small, isolated tasks. The AI writes a regex, converts some data format, or explains a function — and it's great, fast, consistently useful. That success reinforces the pattern.
Then you use the same approach for something bigger. A refactor. A new feature with some complexity. A bug that spans multiple files. And you get something that looks right but isn't — or something that works but doesn't fit how the rest of your codebase is structured — or something that breaks a contract you forgot to mention.
The problem isn't the AI. The problem is the workflow.
The model doesn't know your codebase the way you do. It doesn't know that you have a custom error-handling convention, that the UserService class is getting deprecated next sprint, that this module has a performance constraint because it runs in a mobile context, or that the function it's about to change is called from four places with slightly different assumptions.
You know these things. The model has only what you give it.
When you skip the planning step, you're asking the model to make judgment calls with incomplete information. It will make those calls — confidently, fluently, with well-formatted code — and many of them will be wrong in ways that aren't obvious on first read. The code will run. It might even pass your tests. The wrongness is in the semantics, the conventions, the edge cases, the implicit contracts.
This is why vibe coding gets a bad reputation. Not because AI can't write useful code — it absolutely can — but because the default "just describe what you want" workflow doesn't work for anything beyond simple, isolated tasks.
The single most effective change I made to my AI coding workflow was requiring myself to see a plan before any code gets written on non-trivial tasks.
Before asking for implementation, I ask: "Before you write any code, describe your approach to this task. What assumptions are you making? What are the tricky parts?"
Reading that plan costs two minutes. It catches misunderstandings before they're baked into 200 lines of code. The plan will sometimes reveal that the model is misunderstanding the task entirely, or that it's about to make a decision you disagree with. Fixing a plan is fast. Fixing generated code that took a wrong turn is slow and annoying.
This approach fails when the plan looks reasonable but hides an assumption you didn't scrutinize. I've been burned by that: the model described a sensible-sounding approach, I approved it, and it turned out "sensible approach" was built on a foundation I didn't realize it was assuming. So still read the plan critically — it's a sanity check, not a guarantee.
Here's the same task, two ways.
Before (the default pattern):
"Refactor the
processPaymentfunction to be more maintainable"
The AI rewrites it. Cleaner structure, better variable names, extracted a helper. Looks good. You merge it. Two weeks later: an edge case hits in production. The original code handled a specific error case in processPayment by returning a partial result and continuing — the refactored version throws immediately. The calling code in checkout.ts was relying on the old behavior. This bug is hard to track down because the behavior difference only matters when a payment gateway returns a specific error code that's rare in your test environment.
After (plan-first with context):
"I need to refactor
processPaymentinsrc/services/payment.ts. I've pasted the function below.It's called in
checkout.tsline 42 andsubscription.tsline 118 — I've pasted both callers below too.The goal is to make it easier to add new payment providers later. Here are the constraints:
- Don't change the error-handling behavior. Errors should still propagate as
PaymentErrorinstances with the same fields. The callers depend on this.- Don't change the function signature. It's called from multiple places I may not have pasted.
- Performance isn't a concern here — clarity is.
Before writing any code: describe your approach and flag any assumptions you're making."
You get a plan. You see immediately that it was going to restructure error handling. You push back. The plan gets revised. Implementation takes five minutes, produces clean code, and doesn't introduce a regression.
The second prompt took three more minutes to write. That's the trade.
Before any non-trivial AI coding task, I ask myself three things:
1. What context does the AI actually need? Not "what might be useful" but "what is necessary to avoid wrong assumptions." Relevant files, the function's callers if you're changing its behavior, the constraint that isn't obvious from the code itself.
2. What are the constraints I haven't stated explicitly? Error-handling contracts. Performance requirements. Conventions that exist in the codebase but aren't enforced by a linter. Things that must not change even if changing them would look like an improvement.
3. How will I verify this? If you can't describe how you'll check the output before it ships, the task needs more definition — or you need to build that verification step in. "I'll read it" isn't enough for anything beyond a trivial task.
These three questions take less time than you think, and they pay for themselves on almost every complex task.
Three concrete things you can do today, before you dig into any of the tool-specific guides. These have the highest return on investment of anything in this repo.
Spend 20 minutes writing a context file for the project you're working on right now. At minimum, cover:
- What the project does (one paragraph, not a pitch — functional description)
- Language, framework, and major library versions
- Conventions the AI should follow: naming, error handling, testing approach, file structure
- Things the AI should never do without asking: change the public API, modify the database schema, install new dependencies
- Anything that's non-obvious from the code itself: a performance-sensitive section, a deprecated module it shouldn't extend, a security boundary
Put the file at the project root and point your AI tool at it. In Cursor, this happens automatically. In Claude Code, it's read automatically if named AGENTS.md. In other tools, you may need to add it to your context manually.
Templates for different project types are in context-files/.
The difference between working with and without a good context file is large. It's the closest thing to a guaranteed improvement in this repo.
Starting today: for any task that involves more than one file or more than roughly 30 lines of changes, ask the AI to describe its approach before it writes any code.
Just add "Before writing any code, describe your approach and flag any assumptions you're making" to your prompt. That's it. Read what it says. Push back if something's off. Then ask it to proceed.
This single habit will catch more errors than any other technique in this guide.
Not skim. Read the diff.
In Cursor, use the built-in diff view. In Claude Code, review the file changes before approving. In any tool, git diff before you stage anything.
AI tools are good at making changes look clean and intentional. The semantic errors — the changed error-handling behavior, the removed null check, the subtly different function contract — hide in diffs. They don't announce themselves. You have to look.
This doesn't mean treating every AI change with suspicion. It means developing the habit of actually reviewing what changed, the same way you'd review a pull request from a new team member. That's the appropriate level of trust.
Context engineering is the practice of deliberately structuring the information you give AI tools so they can do useful work without wrong assumptions. It's the most underrated skill in AI-assisted development, and it's almost entirely absent from beginner guides.
Most guides focus on prompts. Prompts matter. But context — the persistent information your AI tool has access to before you write your first prompt in a session — matters more. A great prompt on top of no context produces mediocre results. A decent prompt on top of well-structured context produces good results consistently.
A good AGENTS.md or equivalent is not a documentation dump. It's a targeted brief. Think about what a competent contractor would need to know on their first day to not make embarrassing mistakes. Not everything about the project — the key constraints, conventions, and gotchas.
Project identity. What it does, who uses it, what it's not. One paragraph. The goal isn't comprehensiveness — it's giving the AI a frame so it can make reasonable judgments when something isn't specified.
Technical stack. Language, runtime version, framework, major libraries. The AI's knowledge of library APIs may be stale — telling it "we use Zod v3.22, not the version you might know" prevents it from generating code against a different API.
Conventions. The things your team does consistently that aren't enforced by a linter. How you structure files. How you name things. How you handle errors. How you write tests. These are the things that cause "technically correct but doesn't fit" output when they're missing.
Constraints. What the AI should not do without asking first. Changing public APIs, modifying database migrations, installing new packages, touching authentication code. Be explicit. The AI will make changes that seem like reasonable improvements unless you tell it not to.
Architecture notes. Anything non-obvious from the code itself. A performance-critical section. A module that's being deprecated. A security boundary. Abstractions that exist for reasons that aren't clear from reading the file.
Here's a stripped-down example for a Node.js web service:
# AGENTS.md
## What this is
A REST API for managing customer subscriptions. Handles billing via Stripe, sends transactional email via Postmark. Used by our customer dashboard frontend (separate repo: `customer-dashboard`).
## Stack
- Node.js 22, TypeScript 5.4 (strict mode)
- Express 4.x — we have NOT migrated to v5
- Prisma 5.x for database access (PostgreSQL)
- Zod for request validation (v3.22)
- Vitest for tests (not Jest)
## Conventions
- All route handlers must validate request bodies with Zod before accessing any fields
- Errors are thrown as `AppError` instances with a `statusCode` and `message` — never throw raw errors in route handlers
- Database access happens only in `src/repositories/` — never in route handlers or service layer directly
- Environment variables are accessed through `src/config.ts` — never `process.env` directly
## Constraints — ask before doing these
- Do not change the Stripe webhook handler in `src/webhooks/stripe.ts` without flagging it — changes here require manual testing against Stripe CLI
- Do not add new npm dependencies without listing them in your plan first
- Do not modify Prisma schema files — flag these for separate migration review
- Do not change any exported function signature in `src/lib/` — these are used by the frontend
## Known issues / tech debt
- `src/services/email.ts` is being refactored — don't extend it, work around it if possible
- The `UserSession` type in `src/types.ts` has two legacy fields (`legacyId`, `legacyPlan`) that must be preserved for backward compatibility even though they look redundantThat file takes 30 minutes to write for a real project. It prevents dozens of "the AI did something technically correct but wrong for our codebase" outcomes.
Cursor's .cursor/rules/ system lets you write rules in Markdown that apply to specific file patterns. This is more granular than a single AGENTS.md — you can have different rules for your test files, your API routes, your component library, your config files.
A few patterns that work well:
- A global rule with project-wide conventions and constraints
- A rule for
**/*.test.tsthat specifies your testing conventions and what test utilities to use - A rule for
src/api/**that specifies request/response conventions and validation requirements - A rule for migration files that tells Cursor to never modify existing migrations, only add new ones
The specificity is the point. A rule that fires only when you're editing a test file can be more prescriptive without cluttering your AI's context when you're doing something unrelated.
Templates and examples are in context-files/.
What actually works, organized by task type. These aren't abstract principles — they're patterns I've used or seen work in practice. I'll note where my experience is limited.
The biggest mistake on new features is going straight to implementation. The AI will write something. It'll probably even work. But if it doesn't fit your architecture, you're either living with tech debt or rewriting it — neither is a win.
Pattern: Architecture first, implementation second.
- Describe the feature to the AI along with the relevant parts of your codebase. Ask: "Where does this belong? What should the interface look like? What are you unsure about?"
- Get a rough architecture back. Push back on anything that doesn't fit. Agree on the structure.
- Only then: "Now implement [specific piece] based on the approach we discussed."
Breaking implementation into pieces — one file, one layer, one function at a time — produces better results than "implement the whole feature." The AI's attention drifts on long outputs. Small, bounded tasks stay focused.
AI tools are genuinely useful for debugging, but "I have a bug, here's the code, fix it" rarely works well. The AI doesn't know what the expected behavior is, what state the system is in when it fails, or what you've already tried.
Pattern: Give the AI the full context of the failure, not just the code.
A good debugging prompt includes:
- What you expected to happen
- What actually happened (error message, stack trace, unexpected output — paste it verbatim)
- The relevant code (the function, and its callers if the error crosses a boundary)
- What you've already ruled out
"Here's the error, here's the stack trace, here's the function it's failing in, here's how it's called. I've already verified the input is correct going in — the problem is somewhere in how this function processes it. What are the possible causes?"
That produces a useful list of hypotheses. Then: "Walk me through how I'd verify each of these." That produces a debugging plan. Much better than "fix it."
AI tools are good at spotting certain classes of problems in code review — missing error handling, edge cases in conditionals, obvious security issues, inconsistency with the patterns in the surrounding code. They're not good at knowing your business logic or your team's implicit requirements.
Pattern: Use AI to augment review, not replace it.
Paste the diff and ask: "What potential issues do you see? Focus on correctness and error handling rather than style." Treat the output as a second pair of eyes on the mechanical concerns, not as a substitute for your judgment on the business logic.
I've had AI catch genuine bugs in code review that I would have missed. I've also had it flag things as "potential issues" that were deliberate design decisions. Use it to expand coverage, not to shortcut judgment.
Refactoring is where the "subtle semantic change" problem is most dangerous. The code looks cleaner after. That masks regressions.
Pattern: Constrain what can and can't change before you start.
Before any refactor, explicitly state:
- What the observable behavior must preserve (return values, error handling, side effects)
- What the function signature must preserve if it has callers
- What you're actually trying to achieve (readability? performance? extensibility?)
Then ask for the refactor. Then diff carefully. For anything significant, run your test suite and read the diff before committing.
AI tools are good at writing tests — they're fairly mechanical, there's a clear structure to follow, and the AI can generate a lot of test cases quickly. The failure mode is writing tests that pass trivially or don't actually cover the edge cases that matter.
Pattern: Give the AI the function, the edge cases you care about, and a failing example.
"Here's the function. Here are three edge cases I want covered: [list]. Also, here's an input that should produce a specific output — write a test for that exact case first so I can verify you understood the expected behavior."
Having it write a specific test case from a known input/output pair is a quick way to verify it understood the function before it writes twenty tests you haven't validated.
One of the best uses of AI in development, with low risk. It reads code and explains it accurately more often than not. The failure mode is over-explanation — turning a clear function into a paragraph of obvious narration.
Pattern: Ask for audience-specific documentation.
"Write a docstring for this function. The audience is a developer on our team who knows the language but hasn't seen this module. Explain what it does, what the parameters mean, and what it returns. Don't explain how it does it — just what."
Specifying the audience and the scope of explanation produces tighter, more useful docs.
Good prompts aren't magic spells — they're structured communication. The patterns below aren't exhaustive, but they're the ones I return to the most. Copy-paste them, adapt the parts in brackets, and adjust based on what you're working with.
Use before any task involving more than one file or meaningful logic:
Before writing any code, describe your approach to [task].
Specifically:
- What files will you touch?
- What assumptions are you making about [specific thing you're uncertain about]?
- Are there any edge cases you're not sure how to handle?
I'll review your plan and ask questions before you proceed.
The last line matters. It signals that you expect a back-and-forth, not a one-shot output.
Use before asking for any refactoring that touches existing behavior:
Refactor [function/module] in [file] with these constraints:
- The public interface (function signatures, return types, error types) must not change
- Observable behavior must be preserved exactly — [describe one or two specific behaviors to preserve]
- [Any other hard constraint]
Goal: [what you're actually trying to achieve — readability, extensibility, etc.]
Paste of the current code: [paste]
Paste of any relevant callers: [paste]
Use when debugging. Much more productive than pasting an error and asking for a fix:
I'm seeing unexpected behavior in [function] in [file].
Expected: [describe what should happen]
Actual: [describe what's happening — paste the error verbatim if there is one]
Here's the function: [paste]
Here's how it's called in [file]: [paste]
I've already ruled out: [what you've checked]
What are the most likely causes? Don't write a fix yet — give me the top 2-3 hypotheses and how I'd verify each.
The "don't write a fix yet" instruction is key. You want a diagnostic list, not a speculative solution.
Use when you want AI to review a diff or a specific file:
Review this code for [correctness / error handling / security / consistency with the rest of the file — pick one or two, not "everything"]:
[paste code or diff]
Context: [one or two sentences about what this code does and where it lives]
Flag potential issues with brief explanations. Don't rewrite the code unless I ask — just note what you'd want to discuss.
Scoping the review ("correctness and error handling, not style") produces more useful output than "review this code." The AI trying to review everything produces a wall of suggestions, many of which won't matter for your purposes.
Use when AI-generated code is unclear or you want to build understanding before accepting:
Explain [this function / this approach / this architectural decision] as if you're explaining it to a developer who knows [language] but hasn't seen this pattern before.
Specifically:
- What problem does it solve?
- Why is it structured this way rather than [alternative]?
- What are the tricky parts I should understand before modifying it?
If the explanation reveals the AI didn't have a principled reason for its choices, that's important information before you accept the code.
Instead of "implement this feature," break it into layered steps:
Step 1: Define the interface only — types, function signatures, no implementation.
[description of feature]
Don't write any implementation yet.
Review and agree on the interface. Then:
Step 2: Implement [specific piece] based on the interface we agreed on.
Don't implement [other pieces] yet.
This keeps each chunk reviewable and prevents the AI from making large architectural decisions you haven't approved while buried in implementation.
One failure mode that doesn't get enough attention: context drift in long AI coding sessions.
You start a session, establish some context, make progress. An hour in, you're still in the same conversation. The model has accumulated a long history of messages, and its behavior starts to shift — it forgets constraints you stated early on, loses track of decisions you made, or contradicts something it agreed to twenty messages ago.
This happens. It's not a bug; it's a fundamental property of how context windows work. The further back something is in the conversation, the less weight it gets. Your "never modify the public API" constraint stated in message two carries less influence in message forty than it did at the start.
- The AI starts doing something you remember specifically saying not to do
- It contradicts a decision you made earlier in the session ("wait, I thought we agreed on X")
- Its output quality noticeably degrades — more hallucinated details, less coherent reasoning
- It references an older version of a file that's since been updated
Start a new session. For tasks longer than about an hour of focused AI interaction, seriously consider starting a fresh session with a crisp summary of where you are, what's been decided, and what comes next. Fresh context produces better output than an overloaded long conversation.
Write decisions down, not just in the chat. If you and the AI agree on an architectural approach, write it into your AGENTS.md or into a scratchpad file. Don't rely on the conversation history to hold it. The file persists; the conversation history degrades.
Re-state key constraints periodically. At natural breakpoints in a long session — when you move to a new file, when you finish one task and start another — briefly re-state the constraints that matter: "Remember, we're not changing the public API for this module. Continuing with [next task]..."
Use a session handoff prompt. When you know you'll need to continue work in a new session:
Summarize where we are:
- What was the task?
- What decisions did we make about implementation?
- What have we completed so far?
- What's the next step?
- Are there any unresolved questions or concerns?
I'll use this summary to start the next session.
That summary becomes the opening context for your next conversation. It's a lot more reliable than trusting your memory of a 60-message thread.
The developers I've seen get the best results from AI coding tools aren't the ones who found the perfect prompt — they're the ones who built consistent, repeatable habits that work even when they're tired or in a hurry.
A few that have stuck for me.
The mental model matters. A magic box either works or doesn't, and when it doesn't, you're frustrated. A smart junior developer writes things that need review, occasionally misunderstands requirements, makes confident mistakes about things they don't know, and benefits enormously from clear direction and explicit context.
That mental model keeps you appropriately skeptical without making you paranoid. You review the work. You ask clarifying questions when something doesn't look right. You don't merge things you don't understand. You recognize that giving good context is part of your job.
Writing a good AGENTS.md feels like overhead the first time you do it, especially on a project where you're already comfortable. You don't feel like you need it because you know the codebase. But you're not the only consumer — the AI is, and every session with the AI on that project is better with a good context file than without one.
Spend the time once. Keep it updated when the project changes. That file pays forward every time you use AI assistance on that codebase.
When you write a prompt that works particularly well — produces exactly the right output on the first or second try — save it. I keep a small directory of prompt templates organized by task type. Not the full prompt with project-specific details, but the structure: the key instructions, the constraint-stating approach, the format of the expected output.
Over time this library becomes genuinely valuable. You stop re-inventing prompts for tasks you've done before, and the templates evolve as you find better phrasings.
Not every task deserves the same approach. Develop a personal heuristic for what you fully delegate, what you use AI to draft and you finish, and what you just write yourself.
Some tasks I fully delegate to AI: test boilerplate, converting between data formats, writing docstrings, generating type definitions from examples. The output needs review but rarely needs significant rework.
Some tasks I use AI to start but finish myself: tricky algorithms, architecture-level decisions, anything that needs to fit subtle conventions that are hard to fully specify in a prompt. I take the AI's draft as a starting point and rewrite what doesn't fit.
Some tasks I don't use AI for at all: security-critical code without follow-up expert review, anything where the decision-making is the whole point, code I need to understand deeply because I'll own it long-term. AI assistance for these tasks often costs more in validation time than it saves.
Your categories will be different. Figure out yours, and don't apply the same "delegate to AI" reflex uniformly across task types.
There's a specific failure pattern worth watching for: you've been going back and forth with the AI for 15-20 minutes on something that should have taken five, the suggestions keep missing the mark, and you keep rephrasing without progress.
Stop. The AI isn't the problem; the conversation is. Either your context is insufficient (add it, or start fresh with a better opening), or the task is more complex than it appeared (break it down), or you're asking for something the AI genuinely isn't good at for this use case (just write it yourself).
The sunk cost fallacy applies to AI coding sessions. The time you spent on a bad conversation doesn't mean continuing it is a good idea.
These are the patterns that consistently produce bad results. I've made most of these mistakes myself.
"Improve this codebase." "Make it more maintainable." "Refactor this module."
These prompts give the AI complete freedom to make decisions you haven't approved. It will make them. Some will be good. Some will be subtly wrong for reasons you'll discover later.
The fix is to scope your requests. "Improve this codebase" becomes "I want to improve error handling in src/api/. Right now, errors are inconsistently wrapped. Can you look at these three files and describe the inconsistency you see, then propose a specific change?" That's a bounded task with a reviewable output.
The temptation is real: the AI wrote something complex, it seems to work, you're on a deadline. So you merge it without fully understanding what it does.
This is how "AI debt" accumulates. Code you don't understand, you can't maintain. You can't review PRs that touch it effectively. When it breaks, you're starting from scratch.
The rule I try to follow: if I can't explain what the code does at a high level, I'm not done yet. Either I ask the AI to explain it until I understand it, or I rewrite it in a way I can follow.
The AI produces something wrong. You paste the error back. It "fixes" it. New error. You paste that back. Loop.
This works sometimes for simple compilation errors. For logical bugs, it often produces a game of whack-a-mole where the AI is trying variations without understanding the root cause.
Better approach: when two rounds of fixes haven't solved it, stop and describe the problem from scratch. Explain what you're trying to do, what's going wrong, and what you've tried. That gives it better starting material than a string of error messages.
The AI-generated code is a draft. It's a starting point. It needs to be reviewed with the same attention you'd give code from a contractor who's smart but doesn't know your codebase.
"But the AI wrote it" is not a reason to skip code review. It's actually a reason to be more careful — because AI output can look polished and confident while containing errors that human-written code rarely contains (like silently changed semantics, or missing error cases that seem implicit from context).
"Here's an error: [stack trace]. Fix it."
The AI can sometimes fix errors from just a stack trace. But it's guessing about the codebase context. It'll often produce a fix that works locally but doesn't fit your actual code, or fixes the symptom without understanding the cause.
Add: the function where the error occurs, how it's called, and what you expected to happen. That's the minimum context to get a useful response.
AI tools generate plausible-looking security code. Authentication flows, input sanitization, cryptographic operations. Plausible-looking is not the same as correct.
Use AI to help write security-sensitive code — it's useful for generating a starting point, understanding patterns, and spotting obvious issues. But don't ship it without a human who knows security reviewing it. This isn't a knock on AI tools; it's a statement about the stakes of getting security code wrong.
Developing a good verification instinct is as important as developing good prompting habits. Here's what I actually do.
This is worth repeating because it's the single most important verification habit and the most commonly skipped.
For every AI-generated change, read the diff before accepting. Not the output in the chat — the actual file diff. In Cursor, use the diff view in Composer. In Claude Code, read the file changes before approving. In any workflow, git diff before staging.
What you're looking for: changes you didn't expect. The AI changed something you didn't ask it to change. It removed something. It changed how errors propagate. It modified a function you mentioned in passing. These things happen, and they're hard to spot in a wall of generated code but easy to spot in a focused diff.
Before accepting anything beyond a trivial change: run your test suite. This seems obvious and it still gets skipped. If your project doesn't have tests, that's a separate problem — but for the AI-assisted work you're doing right now, at minimum write a test for the specific behavior you're changing before you change it.
A test that passes before and fails after the AI change is valuable signal. A test that passes throughout is reassuring. "I didn't bother running the tests" is how you ship regressions.
If you're looking at AI-generated code and you're not sure why a specific part is written the way it is, ask. "Why did you write it this way instead of [alternative approach]?" If the answer is "good point, the alternative would be better," that's a problem — it generated something without a principled reason.
If you can't get a coherent explanation of why the code is structured the way it is, that's a signal the code itself may be poorly reasoned.
The most useful mental check I have: would I approve this as a PR from a teammate?
A PR that changed error-handling behavior without mentioning it in the description — no, I'd ask about it. A PR that added a dependency without explanation — no. A PR where the diff looks clean and the changes are exactly what was requested — yes.
Apply the same standard to AI output. The AI isn't getting a pass because it generated the code fast.
Everything in this repository, with descriptions.
vibe-coding/
├── README.md — You're here
├── CONTRIBUTING.md — How to add to this repo
├── LICENSE — MIT
├── .gitignore — Standard gitignore for most dev environments
├── tools/ — Per-tool guides
├── workflows/ — End-to-end workflow examples
├── prompts/ — Prompt patterns by task type
├── patterns/ — Reusable interaction patterns and anti-patterns
├── context-files/ — Templates: AGENTS.md, .cursor/rules/, system prompts
├── examples/ — Anonymized before/after examples
└── assets/ — Images and static files
Per-tool guides for the AI coding tools in the landscape table above. Each guide covers: the rules/context system for that specific tool, what it's particularly good and bad at, and workflow-specific tips. These are not installation tutorials — they're guides for after you're already set up and want to use the tool effectively.
End-to-end workflow documentation for common development scenarios. Each workflow document walks through a realistic task with the actual context setup used and the prompts used at each step — not cleaned-up idealized versions, but what actually worked. Scenarios include: starting a new feature, doing a large refactor, debugging across multiple files, AI-assisted code review, and writing a test suite for existing code.
Prompt patterns organized by task type. Each pattern includes a template with placeholders, notes on when it works, notes on when it fails or needs adaptation, and at least one concrete example. Organized into: code generation, code review, debugging, refactoring, testing, documentation, and architecture/planning.
Higher-level interaction patterns — the principles behind the prompts. "Always get a plan before implementation." "Give the AI a failing test before asking it to fix the code." "Constrain what can change before any refactor." Each pattern is grounded in concrete examples, and anti-patterns are documented alongside positive patterns.
Ready-to-use templates for the context files that AI tools read:
AGENTS.mdtemplates for different project types (web app, library, CLI tool, data pipeline, mobile)- Cursor
.cursor/rules/rule examples, organized by file type and use case - System prompt templates for Claude Code and other tools that accept custom system prompts
- A guide covering what to include, why, and how to maintain context files as a project evolves
Anonymized real examples. A prompt that produced subtly wrong code, what the improved prompt looked like, and a concrete description of the difference. These are the most practically useful content in the repo and the hardest to find elsewhere — most guides only show the happy path.
This repo is a living document, not a polished-and-done guide. Tools change, techniques that worked six months ago need revision, and the community has experience I don't.
If you've found something that works — a prompt pattern, a workflow, a tool tip — and it isn't here, I want to hear about it. If something here is wrong or outdated, please flag it.
See CONTRIBUTING.md for the full guidelines. The short version:
- Open an issue first for anything substantial, so we can agree it belongs here before you write it
- Include concrete examples, not just abstract advice
- Be honest about when things don't work — limitations are as useful as successes
- Match the tone: personal, specific, no marketing language
MIT — see LICENSE. Use this however you want.
