Most advice about AI coding focuses on the prompt.
Be specific. Add context. Define the expected output. Mention the framework, the coding style, and the edge cases.
That advice is useful, but it skips an important question:
What should happen between the prompt and the code?
If an AI assistant misunderstands your repository, a detailed prompt does not guarantee a good implementation. It can still choose the wrong abstraction, edit too many files, add an unnecessary dependency, or write tests that confirm its own incorrect assumptions.
I have found a simple checkpoint that makes this easier to manage:
Before asking for code, ask for the implementation plan.
The assistant inspects the relevant files, explains the current behavior, identifies the smallest required change, and lists its assumptions. Only then does implementation begin.
It sounds like an extra step. In practice, it often saves time.
Why generated code can be difficult to review
AI-generated code is rarely presented as obviously broken.
It usually looks reasonable.
The names fit the project. The function is documented. The patch may include tests. The explanation sounds confident.
That surface quality is useful, but it can also make incorrect assumptions harder to notice.
Consider this request:
Add caching to the user service.
There is nothing unusual about it, but the instruction leaves several decisions open:
- Which operations should be cached?
- Does the project already have a cache abstraction?
- How are cache keys structured?
- When should an entry expire?
- What invalidates an entry?
- Should unsuccessful lookups be cached?
- What happens when the cache is unavailable?
- How should the behavior be tested?
A human developer would normally inspect the surrounding code before making those decisions. An AI assistant may inspect it too, but it may also fill gaps with patterns that are common elsewhere and wrong for this repository.
The result may be valid TypeScript, Python, Java, or C# while still being the wrong change.
Start with a small change contract
Before involving an assistant, I describe the task using four pieces of information:
- The outcome I want
- The relevant repository context
- The constraints that must be preserved
- The checks that will demonstrate success
For the caching task, that might look like this:
Goal:
Cache successful user profile reads to reduce repeated database queries.
Relevant context:
- The user service is in src/users/user-service.ts.
- The project already has a cache abstraction in src/cache/cache.ts.
- User updates are handled by updateUser.
- Unit tests use Vitest.
Constraints:
- Do not add dependencies.
- Do not change the public API.
- Do not cache unsuccessful lookups.
- A cache failure must not prevent a database read.
- Do not modify unrelated services.
Acceptance criteria:
- A repeated read for the same user can use the cached value.
- Updating a user invalidates the corresponding cache entry.
- Cache failures fall back to the database.
- Existing tests continue to pass.
- The new behavior has focused tests.
This is not a specification for the entire application. It is only a boundary around one change.
The assistant still has room to inspect the code and propose an implementation. It simply has less room to invent project requirements.
Ask for inspection, not implementation
The next instruction is the most important part of the workflow:
Inspect the relevant files and propose a minimal implementation plan.
Before writing code:
1. Summarize the current behavior.
2. Identify the files that would need to change.
3. Explain how the existing cache abstraction should be used.
4. List assumptions or missing information.
5. Describe the tests that should prove the change.
Do not modify any files yet.
The last sentence creates the checkpoint.
At this stage, I am not reviewing syntax or formatting. I am checking the assistant's understanding of the repository.
A useful plan should answer questions such as:
- Did it find the existing abstraction?
- Is it changing the correct layer?
- Does it understand how updates currently work?
- Is the proposed scope reasonable?
- Are its tests connected to the actual requirement?
- Is it making an assumption the repository cannot support?
Correcting a bad plan takes a minute. Removing the same assumption from a large patch takes much longer.
What a useful plan looks like
A weak plan often repeats the request:
1. Add caching to the service.
2. Add invalidation.
3. Add tests.
This does not reveal much about the assistant's understanding.
A more useful plan is specific enough to review:
1. Update getUserById in src/users/user-service.ts to check the existing
cache abstraction before querying the repository.
2. Use the current user cache-key helper rather than introducing a new
key format.
3. Cache only successful repository results.
4. Treat cache reads and writes as optional optimizations. If either fails,
preserve the existing database behavior.
5. Invalidate the same cache key after updateUser completes successfully.
6. Add focused tests for a cache hit, a cache miss, invalidation after an
update, and fallback after a cache error.
Assumption:
The existing cache implementation accepts the same serialized User value
returned by the repository. This should be verified before implementation.
Now there is something meaningful to review.
The plan shows where the change belongs, which existing behavior it preserves, and what still needs to be verified.
Implement one behavior at a time
After reviewing the plan, I still do not ask for the entire feature in one pass.
I start with one observable behavior:
Implement only the cache-read path.
Use the existing cache abstraction and preserve the current database
fallback. Do not add invalidation yet.
After making the change:
- Run the focused unit tests.
- Report which files changed.
- Explain what was verified.
- Stop before implementing the next part.
This keeps the diff small enough to understand.
If the cache-read behavior is correct, the next step might add invalidation. After that, the failure path can be tested. Each part gets its own feedback loop.
This approach may seem slower than requesting the complete implementation, but broad patches often hide unnecessary work. A small patch makes it easier to notice:
- unrelated formatting changes,
- new abstractions that the task does not need,
- duplicated helpers,
- dependencies added for convenience,
- changed public APIs,
- tests that do not prove the requested behavior.
My preferred rule is simple:
The diff should be small enough that I can explain every changed line.
Replace βbest practicesβ with checks
Instructions such as these sound helpful:
Use best practices.
Make it production-ready.
Handle all edge cases.
Keep the code clean.
The problem is that they are subjective. They also encourage the assistant to expand the task.
A more useful instruction connects the work to observable evidence:
Preserve the public API.
Use the existing cache and logging abstractions.
Do not add a dependency.
Add tests for cache hits, invalidation, and cache failure.
Run the unit tests, type checker, and linter.
If a command cannot be completed, report the exact reason instead of
assuming it passed.
The assistant can claim that code is clean or production-ready. A passing test cannot prove everything, but it provides better evidence than a confident explanation.
Useful checks may include:
- focused unit tests,
- integration tests,
- type checking,
- linting,
- formatting,
- schema validation,
- static analysis,
- dependency auditing,
- a production build.
The exact list depends on the repository. The important part is making the validation process explicit before implementation begins.
Give the assistant a short project map
Repeatedly explaining the same repository conventions wastes time.
A small project guide can make those conventions discoverable:
# Project Guide
## Structure
- `src/api` contains HTTP handlers.
- `src/domain` contains business rules.
- `src/data` contains database access.
- `src/shared` contains reusable infrastructure.
## Commands
- Install dependencies: `npm ci`
- Run tests: `npm test`
- Type check: `npm run typecheck`
- Lint: `npm run lint`
- Build: `npm run build`
## Conventions
- Keep HTTP handlers thin.
- Put business logic in the domain layer.
- Reuse existing dependencies and abstractions.
- Add focused tests for new behavior.
- Public API changes require an explicit decision.
## Restrictions
- Do not edit generated files.
- Do not read or modify local secret files.
- Do not run deployment commands.
- Do not change database schemas without approval.
This can live in AGENTS.md, contributing documentation, repository instructions, or another file supported by the tools used by the team.
It does not need to document everything.
A useful project map answers four basic questions:
- Where should code go?
- Which existing patterns should be reused?
- How should a change be verified?
- Which actions require human approval?
Limit what the assistant can do
A coding assistant may be able to edit files, execute shell commands, install packages, access the network, or connect to external tools.
Those capabilities should match the task.
Adding a focused unit test does not require deployment access. Refactoring a parser does not require production credentials. Updating documentation does not require permission to install arbitrary packages.
A practical default is:
Read broadly.
Write narrowly.
Run locally.
Ask before creating external effects.
For a typical repository task, that could mean:
- working on a separate branch or worktree,
- limiting writes to relevant directories,
- keeping secrets outside the environment,
- reviewing dependency installation,
- blocking deployment commands,
- requiring approval for network access,
- running commands in a container or sandbox when possible.
This matters because repository content is not automatically trustworthy. Instructions can appear in issue descriptions, comments, documentation, dependencies, generated output, or external tool responses.
The assistant should not treat every piece of text it encounters as an instruction to execute.
Review the repository state, not the explanation
After implementation, it is tempting to start with the assistant's summary:
Implemented caching with robust error handling and comprehensive tests.
That sounds reassuring, but it is not the result that needs review.
The result is the diff.
I review it in this order:
Scope
- Which files changed?
- Is every changed file relevant?
- Did formatting or naming change outside the task?
- Was a dependency added?
- Did the patch introduce a new abstraction?
Behavior
- Does the code satisfy each acceptance criterion?
- What happens on failure?
- Are unsuccessful results handled correctly?
- Is invalidation connected to successful updates?
- Does the public API remain unchanged?
Tests
- Do the tests prove the behavior or merely execute the code?
- Would a broken implementation cause them to fail?
- Are failure paths included?
- Were the tests actually run?
Maintainability
- Does the implementation follow existing patterns?
- Is there unnecessary duplication?
- Is the change more complex than the requirement?
- Would another developer understand why it was made?
Security
- Is untrusted input validated?
- Can sensitive information reach logs, prompts, fixtures, or generated files?
- Did network access or permissions change?
- Are model-generated values validated before being passed to another system?
A polished summary is helpful after this review, not instead of it.
Ask for a skeptical second pass
The assistant that wrote the patch has already committed to a particular interpretation of the task.
For a meaningful change, I use a separate review prompt:
Review the current diff as a skeptical maintainer.
Compare it with the original acceptance criteria and look specifically for:
- missed requirements,
- incorrect assumptions,
- unnecessary complexity,
- unrelated changes,
- weak tests,
- missing failure handling,
- security-sensitive behavior.
Return findings in priority order.
Do not rewrite the implementation yet.
This review is more useful when it searches for problems instead of generating praise.
It is still not a replacement for human review. It is another way to make assumptions visible before the change is merged.
Require an honest completion report
A coding assistant should be able to say that something remains unverified.
I include this instruction near the end of a task:
Do not silently guess project-specific behavior.
If an assumption cannot be verified from the repository, label it as an
assumption.
If a test or command cannot run, state what remains unverified and why.
A useful completion report looks like this:
## Completed
- Added cache reads through the existing cache abstraction.
- Added invalidation after successful user updates.
- Preserved the public service API.
- Added focused tests for cache hits and cache failures.
## Verification
- Focused unit tests passed.
- Type checking passed.
- Linting passed.
## Not verified
- Behavior against the managed production cache was not tested because
that service is unavailable in the local environment.
## Remaining assumption
- Cache serialization is consistent across all application instances.
This is more trustworthy than βEverything is complete and production-ready.β
Uncertainty is not a failure. Hidden uncertainty is.
A reusable prompt
Here is the complete prompt pattern I use for repository tasks:
You are working in an existing repository.
Goal:
[Describe the desired outcome.]
Relevant context:
[List the relevant files, modules, conventions, and existing utilities.]
Constraints:
- Preserve public APIs unless a change is explicitly requested.
- Reuse existing project patterns and dependencies.
- Keep the patch limited to the stated goal.
- Do not edit generated files.
- Do not expose secrets or sensitive repository content.
- Do not perform deployment or destructive operations.
Acceptance criteria:
[List the observable behaviors and required checks.]
First phase:
1. Inspect the relevant code.
2. Summarize the current behavior.
3. Propose the smallest reasonable implementation plan.
4. List the files that would change.
5. Identify assumptions and missing information.
6. Describe the tests that should prove the change.
7. Do not modify files yet.
After the plan is reviewed:
1. Implement one reviewable behavior at a time.
2. Run the most relevant checks after each step.
3. Keep unrelated files unchanged.
4. Inspect the final diff against every acceptance criterion.
5. Report changed files, completed checks, assumptions, and anything
that remains unverified.
There is no special phrase in this prompt that makes the assistant reliable.
Its value comes from the process:
Specify
β
Inspect
β
Plan
β
Review the assumptions
β
Implement a small change
β
Run checks
β
Review the diff
β
Repeat
Final thought
AI coding tools are getting better at producing code, but code generation is only one part of software development.
The harder parts are understanding the repository, choosing the right scope, preserving existing behavior, testing the result, and deciding whether the change should be merged.
Asking for the plan first creates a cheap point of control before implementation begins.
It does not guarantee a correct patch. Nothing does.
It does make incorrect assumptions easier to see while they are still cheap to fix.
How do you structure AI-assisted changes in your repositories? Do you ask for a plan first, or start by reviewing the generated implementation?
Sources and further reading
- Stack Overflow Developer Survey 2025: AI
- GitHub Octoverse 2025
- OWASP Secure Coding with AI Cheat Sheet
- OWASP Top 10 for LLM Applications
Connect with Me
If you found this guide helpful, let's connect and discuss modern development workflows!
- π» GitHub: johnnylemonny
- βοΈ DEV.to: johnnylemonny
Top comments (0)