Stockio

Reviewing AI-Written Code: A Workflow for AI Assistants

Review AI-generated changes with a repeatable workflow for defining scope, verifying dependencies, testing behavior, and keeping human ownership of every merged line.

By Grace Chen

AI Tools Editor ·

On this page

An AI coding assistant can produce a plausible implementation faster than a reviewer can establish whether it is correct. The solution is not to read faster. It is to make the change smaller, define evidence before generation, and separate the assistant's explanation from independently verified behavior. The developer accepting the change still owns its consequences.

Establish a contract before generating code

Write a short task contract containing the desired behavior, files allowed to change, non-goals, and observable acceptance criteria. For example: add a parser for a partner response, preserve existing error handling, introduce no dependencies, and reject malformed identifiers. This gives both the assistant and reviewer a boundary more useful than “make this better.”

For a JSON integration, use JSON to TypeScript with a sanitized sample to sketch the expected shape. Compare the generated interfaces with the actual provider contract, then give the assistant the corrected version. A sample is not a schema: optional properties, null values, and alternative response variants may be absent from one example.

Decide what information the assistant may receive. Remove credentials, customer records, private keys, and production logs unless your organization's approved environment explicitly permits them. Check retention and training policies rather than assuming all assistants handle data identically. Restrict tool permissions to the task's needs and keep destructive commands behind explicit approval.

Ask for a plan and a reviewable diff

Request a brief plan before edits: which functions change, what assumptions are being made, and which tests will demonstrate the result. Reject plans that introduce a broad abstraction for a narrow bug. Scope control is particularly valuable when generated code expands into unrelated cleanup that makes the behavioral change difficult to isolate.

A reusable prompt can be plain text rather than a complicated automation system:

Implement only the response parser and its tests.
Do not change dependencies, public exports, or unrelated formatting.
Treat network data as unknown until validated.
First list assumptions and the intended failure behavior.
After editing, list changed files and commands actually run.
Distinguish verified results from checks you could not execute.

Keep the first patch small enough to understand in one sitting. Inspect the full diff, including lockfiles, configuration, deleted tests, and generated artifacts. Do not review only the files highlighted in the assistant's summary. A correct function can arrive alongside an unsafe script change or a relaxed lint rule.

Consult your platform's guidance, such as GitHub's responsible-use documentation, for product-specific limitations. Treat that guidance as a starting point, not evidence that a particular patch is correct.

Verify interfaces and trust boundaries manually

Read the modified function and its callers without relying on the generated explanation. Identify what enters the system, where it becomes trusted, and which side effects it can trigger. Type assertions are not validation. A cast can silence the compiler while leaving malformed external input untouched.

This example intentionally accepts unknown input and narrows only the property required by the feature:

type PartnerRecord = { id: string };

export function parsePartnerRecord(value: unknown): PartnerRecord {
if (typeof value !== "object" || value === null) {
  throw new Error("Expected an object");
}
if (!("id" in value) || typeof value.id !== "string") {
  throw new Error("Expected a string identifier");
}
if (value.id.trim().length === 0) {
  throw new Error("Expected a non-empty identifier");
}
return { id: value.id };
}

The example does not establish uniqueness, authorization, or an identifier format beyond non-empty text. Those are separate requirements. Compare the implementation against the TypeScript narrowing documentation rather than accepting a confidently stated language rule.

Check every new API and package against official documentation and the installed version. Assistants can combine methods from different releases or suggest nonexistent options. For dependencies, verify the exact package name, maintainer, license, and need. Do not install a package simply to see whether the generated import starts working.

Test behavior independently of the implementation

Write acceptance tests from the task contract, not from the generated function's structure. If the assistant writes both implementation and tests, the same mistaken assumption can appear in both. Independent test design means adding cases that could falsify the proposed solution, not merely confirm its happy path.

For the parser, test null, arrays without identifiers, missing properties, numeric identifiers, whitespace-only text, and a valid record containing unrelated fields. Ask whether rejecting or preserving extra fields matters. Add a regression test that fails against the original defect and passes after the fix; otherwise the test may not actually protect the intended behavior.

Run relevant unit tests, type checks, and lint commands yourself or inspect trustworthy execution logs. A statement that tests “should pass” is not a test result. Record commands, exit status, and any environmental limitations. For UI changes, inspect keyboard interaction and visible error states as well as snapshots. The accessibility guide provides useful review questions for that manual pass.

Use MDN's JavaScript reference when checking runtime behavior. Compilation success does not prove that date parsing, coercion, asynchronous ordering, or browser support matches the product's requirements.

Review maintainability and capture the decision

Once correctness looks sound, ask whether the code fits existing conventions. Prefer established helpers over near-duplicates, ordinary control flow over clever abstractions, and comments explaining constraints over comments restating syntax. Readable ownership matters because the next maintainer may never see the original conversation.

Remove unused helpers, speculative options, and tests that assert implementation trivia. Check that error messages help callers act without exposing sensitive values. If generated code contains copied-looking license headers or unfamiliar large blocks, investigate provenance under your organization's policy rather than assuming generation removes licensing obligations.

Prepare a pull-request summary describing the behavioral change, evidence, remaining uncertainty, and any approved use disclosure. The author should be able to explain every changed line. If they cannot, request a smaller implementation or stop the merge. Use the developer tools category to build consistent team habits, but keep the binding policy in version-controlled project documentation.

Before approval, perform one adversarial pass: imagine the change is wrong despite all green checks. Ask what production condition the tests omitted, which permission boundary could be crossed, and whether a rollback restores the previous behavior. For asynchronous code, consider duplicate requests, cancellation, and stale responses. For filesystem tools, consider paths outside the intended workspace. Write down the most credible remaining risk and decide whether it needs another test or explicit acceptance. This exercise is not an invitation to demand impossible certainty; it turns vague unease into a concrete question that a reviewer can resolve.

Key takeaways

  • Define acceptance criteria and permissions before asking for implementation.
  • Review the entire diff, including configuration and dependencies.
  • Verify runtime boundaries instead of trusting generated types or explanations.
  • Design tests to challenge assumptions and record actual execution results.
  • Merge only code a human owner can explain and maintain.
Featured sponsor

Reach designers & developers at work

Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.

Become a sponsor

Keep reading

Related articles

More in Dev Tools & AI Coding
Dev Tools & AI Coding

10 Best AI Coding Assistants

Compare ten AI coding assistants for editor help, repository questions, and terminal workflows, with practical notes on strengths, trade-offs, privacy, and choosing a tool you can review.

Includes: JSON to TSSofia MarchettiSofia Marchetti
Dev Tools & AI Coding

Write SQL With AI: Generate and Optimize Queries Safely

Turn reporting questions into tested SQL with explicit data contracts, edge-case fixtures, execution-plan evidence, and safe application types before trusting an AI-generated query.

Includes: JSON to TSSofia MarchettiSofia Marchetti
Dev Tools & AI Coding

Cursor AI Tips: Give the AI the Right Codebase Context

Give Cursor focused repository context, trustworthy API examples, and clear edit boundaries so generated changes fit your architecture instead of inventing a parallel one.

Includes: JSON to TSGrace ChenGrace Chen
Dev Tools & AI Coding

Refactor Code With AI: Legacy JavaScript in Safe Steps

Refactor legacy JavaScript through narrow AI-assisted patches, characterisation tests and boundary types, while preserving hidden contracts and a practical rollback path.

Includes: JSON to TSRavi DesaiRavi Desai
Dev Tools & AI Coding

AGENTS.md and Cursor Rules Examples for AI Coding Agents

A coding agent needs more than a feature request. It needs to know which commands are safe, where architectural decisions live and what counts as finished. Without that context, an assistant can produce a.

Includes: JSON to TSSofia MarchettiSofia Marchetti
Dev Tools & AI Coding

Essential VS Code Extensions for Frontend Developers

Build a focused VS Code setup with ESLint, Prettier, framework support, and repeatable workspace settings, without piling on conflicting extensions.

Includes: JSON to TSRavi DesaiRavi Desai