Back to Blog

AI / Engineering / Workflow / Productivity / Story

From Vibe Coding to Engineering: Using AI as Agile Assistance

Vibe coding — prompt, accept, ship — is an easy habit to fall into. Here's how to use AI agents as agile assistance instead, under the specs, reviews, and engineering ownership that make code last.

|10 min read

Introduction

"Vibe coding" entered the vocabulary as a half-joke: describe what you want, let the model emit code, run it, and if it works, ship it. No reading the diff. No mental model of the system. The agent drives; you ride.

I use AI coding agents every day, and I want to be clear up front: this isn't a piece about gatekeeping who gets to call themselves a developer. Vibe coding is a stage, not a character flaw — most of us pass through some version of it, especially when a tool suddenly makes the first 80% feel effortless. The goal here isn't to mock that. It's to show what the next step looks like, so the speed you've found doesn't quietly turn into debt you can't pay down later.

The two approaches produce code that looks identical in the happy path and diverges the moment something is wrong — a race condition, a cascade bug, a silent data-loss edge case. Vibe-coded software tends to pass the demo and fail the on-call. The difference isn't the tool. It's who stays accountable for the result. This is how I try to work: AI as agile assistance — a fast, tireless pair that accelerates the parts I've already decided, under engineering ownership I keep on my side of the line.

What Vibe Coding Actually Is

Vibe coding isn't a synonym for "using AI." It's a specific set of habits — and naming them is the first step to outgrowing them:

  • No spec. The prompt is the requirement, so whatever the model infers silently becomes the design.
  • No review. The diff is accepted because the app rendered, not because anyone read it.
  • No mental model. When it breaks, debugging is hard, so the loop becomes re-prompt and hope.
  • Diffuse ownership. "The AI wrote it" starts standing in for an explanation of how it works.

It's seductive because it's genuinely fast at first — a working screen in minutes is a real feeling, and it's worth enjoying. The catch is that the cost is deferred, not removed. It tends to land later as code that's hard to change because no one built a model of why it's shaped the way it is.

A useful self-check: pick a line and ask yourself why it's there. If the honest answer is "the model put it there," that's not a failing — it's a signal of where to look next. The most reliable way out of vibe coding is to keep closing that gap, one line at a time, until the code is something you could defend in a review.

Ownership Is the Whole Game

The habit everything else hangs on: treat yourself as responsible for every line that ships, regardless of who or what typed it.

That one commitment pulls the rest along. If you own it, you want to understand it. If you want to understand it, you read it. If you read it, you start reviewing it the way a good senior reviews you — not "does this look plausible," but "where could this break, what did it skip, what did it quietly assume."

Here's a concrete example from building this very portfolio. I asked the agent to make a set of toolbar controls stop showing a hover glow. It reported done, and the change looked right — it added the opt-out class everywhere I'd expect. But I asked it to prove the glow was actually gone, because "looks applied" and "is applied" are different claims.

It wasn't gone. The opt-out was a plain class selector, but the controls were <button type="button">, which also matched a higher-specificity [type="button"]:hover rule already in the stylesheet. In a specificity tie-or-loss, the existing rule kept winning, so the opt-out rendered but never took effect. The bug was invisible in the one state anyone had checked.

This is the kind of thing that slips through a prompt-and-ship loop — not because anyone was careless, but because the screen looked fine. Catching it didn't take genius; it took the small habit of asking for proof before accepting "done."

The Real Fix vs. the Band-Aid

Now the part I want to be honest about, because the first instinct here is a trap I've fallen into myself.

The fast way to make the opt-out win is box-shadow: none !important. It works. It also feels like a fix while actually being a band-aid. !important doesn't resolve the conflict — it wins by force, opting out of the cascade rather than working with it. The next person (often future-you) who adds a legitimate shadow to that control will be baffled when nothing happens, then discover they now have to escalate to !important too. That's how stylesheets rot: an arms race of overrides where specificity stops meaning anything.

The real fix is to address specificity directly, so the right rule wins on its own terms:

  • Exclude at the source. Add :not(.no-hover-glow) to the glow rule itself, so opted-out controls never match it in the first place. No override needed — the conflict simply doesn't occur.
  • Or match the specificity. Scope the opt-out to the same shape it's competing with — e.g. [type="button"].no-hover-glow:hover — so it wins by being equally specific and later in the source order, not by brute force.

Both make the intent legible to the next reader: this control is deliberately excluded from the glow. !important says only something, somewhere, insisted. Reach for !important as a deliberate, last-resort escape hatch if you must — but recognize it as a stopgap and leave a comment saying so, not as the answer. The discipline isn't "never use it." It's "know it's a band-aid, and prefer the fix that future readers can follow."

AI as Agile Assistance

So what should you delegate? The mechanical breadth — the tedious-but-decided work — while you keep the judgment.

AI is genuinely excellent at:

  • Fan-out edits. Apply a pattern you've chosen across twenty files, consistently, without fatigue.
  • Boilerplate that follows a known shape. A modal that mirrors an existing modal; a table that mirrors an existing table.
  • Mechanical refactors. Extract a shared component once you've decided where the seam goes.
  • Recall and search. "Where is X wired, what imports Y, which files hand-roll this control."

In the same portfolio session, I had four tables each carrying their own copy of search, filtering, and pagination. I decided the abstraction — a single DataTable wrapper with optional toolbar and pagination, plus a usePagination hook — and the agent executed the extraction across all four and mirrored it into a second app. Hours of careful, error-prone editing compressed into minutes.

But the architectural calls were mine: that it should be shared, where the seam went, what the API looked like, and that it belonged in shared/ui. The agent was the hands; the design was the part I didn't hand off. That's the line worth holding, and it's a learnable one — you get better at it by making those calls explicitly instead of letting them happen by default.

That's also the "agile" part — not agile-the-ceremony, but agile-the-property: tight loops, fast feedback, small reversible steps. Propose a change, let the agent draft it, review and correct, iterate. Minutes per loop instead of hours, without giving up control. (Running tight loops all day does burn tokens — I keep that in check with a four-layer strategy to cut AI coding agent token burn.)

Habits That Keep AI Honest

A few practices that turn a confident-but-fallible model into a reliable collaborator. None of these are AI tricks — they're ordinary engineering habits the agent just makes cheaper to keep:

Specs before code. Plan the architecture, write it down, break the work into small tasks before generating a line — the planning, audit, and review loop I walk through in my production-ready AI coding CLI workflow. The agent fills in a structure you've defined rather than inventing one.

Root cause, not symptom. When something breaks, resist the override-that-happens-to-work (the !important story above is exactly this). Fix the actual conflict; a workaround that masks a defect is debt with interest.

Adversarial review. Assume the diff is wrong until shown otherwise. "Are you sure?" is one of the most valuable things you can say in a session — models are trained to sound confident, and confidence isn't correctness.

Smallest diff that satisfies scope. No drive-by rewrites, no premature abstraction, no touching files the task didn't need. New issue mid-task? It goes on the list, not into a silent fix.

Sync discipline. Code, docs, changelog, commit plan — kept consistent in the same pass. The agent is great at remembering to mirror; you decide what must mirror.

Where a habit is repetitive enough, push it down into an auto-loaded behavior layer of skills, hooks, and rules so it's enforced by tooling instead of memory.

An Engineer's Checklist for AI Code

If you're earlier in this journey, you don't need to internalize all of the above at once. Run generated code through this checklist before you ship it — it scales from a five-line snippet to a feature, and each pass builds the instincts that eventually make it automatic.

1. Comprehension — can I explain it?

  • Can you describe what each non-trivial line does, in your own words?
  • Do you understand why the model chose this approach over an obvious alternative?
  • Could you make a small change by hand, without re-prompting?

If any answer is "not really," that's your next thing to learn — read the docs for the unfamiliar part, then come back.

2. Foundation — does it fit the codebase?

  • Does it reuse existing patterns and components, or quietly invent a parallel way to do the same thing?
  • Is it in the right place (shared vs. feature-local), with the project's naming and structure?
  • Is it the smallest change that solves the problem, or did it rewrite more than you asked?

3. Security — is it safe by default?

  • Are inputs validated and outputs encoded? (Injection, XSS, path traversal hide in "it works" code.)
  • Are secrets kept server-side and out of the client bundle?
  • Are authentication and authorization actually enforced, not just assumed?
  • Did it add a dependency you haven't vetted?

4. Correctness — does it handle the unhappy path?

  • What happens on empty, null, huge, or malformed input?
  • Are errors handled, or does the happy path just assume success?
  • Is there a test — even one — for the case most likely to break?

5. Ownership — would I sign my name to it?

  • If this caused an incident at 2 a.m., could you debug it?
  • Did you read the whole diff, including the parts you didn't ask for?
  • Is anything in here a band-aid (!important, a magic timeout, a // TODO) that should be flagged rather than buried?

You won't hit every item every time, and that's fine. The point isn't perfection — it's building the muscle of looking, so that over time "looks done" and "is correct" stop being two different things in your head.

From Vibe Coding to Engineering: A Growth Path

Here's the encouraging part: the same AI that makes vibe coding easy is also the best tutor you've ever had. The skill gap is real, but it closes fast if you turn each task into a lesson instead of just an output. A practical progression:

  1. Read the diff, every time — start small. Don't audit a thousand lines on day one. Pick the one function you understand least and read just that. Tomorrow, two. Reading code is a muscle; it grows with reps.
  2. Make the model teach, not just produce. After it writes something, ask "explain why you chose this approach" and "what are the trade-offs versus the alternative?" Then verify the explanation — models can be confidently wrong, and catching that is the lesson.
  3. Reproduce one fix by hand. Once it solves a bug, close the chat and try to make the same change yourself. Struggling here isn't failure — it's the exact moment the knowledge moves from the screen into your head.
  4. Learn the fundamental under each bug. The hover-glow bug earlier wasn't really about a button — it was about CSS specificity and the cascade. Every bug is a doorway to a concept. Ask the model to teach you the underlying rule, then you'll recognize the whole class of bug next time.
  5. Add one test. Even a single assertion for the most likely failure forces you to think about what "correct" actually means here — which is most of engineering.
  6. Build the mental model out loud. Before accepting a feature, narrate how the pieces connect: "the form calls this, which hits that route, which writes here." If you can't narrate it, that's your study list for the week.

Do this consistently and something quietly flips: you stop asking the model for code and start directing it with intent. That's the whole transition. It isn't a gate you're let through — it's a habit you build, one reviewed diff at a time, and the tools make it faster to build than it has ever been.

What Stays on Your Side of the Line

A short list worth keeping for yourself, no matter how good the tools get:

  • Architecture and seams. What's shared, what's isolated, where the boundaries go.
  • Trade-offs. Performance vs. simplicity, build-now vs. defer, what "production-ready" means for this project.
  • The correctness bar. Whether a fix is real or just looks real; whether an edge case matters.
  • What ships. Final accountability for the diff, the commit, the release.

The agent can argue, suggest, and draft alternatives — and often does so usefully. But the decision, and the responsibility for it, stays with you. That's the whole idea in one line: AI proposes; the engineer disposes.

Conclusion

Vibe coding asks the model to be the engineer. It can't quite get there — not for lack of raw capability, but because engineering is largely the part that happens after the code exists: the reviewing, the trade-off weighing, the owning of consequences. Hand all of that off and you don't get a faster engineer; you get more code than anyone understands.

Used as agile assistance, the same tool is one of the largest productivity gains I've had in a decade of building software. You move faster because the agent handles the mechanical breadth while you hold the design, the review, and the accountability — tight loops, reversible steps, real velocity.

If you're somewhere on the road from vibe coding to engineering, you're not behind — you're exactly where the interesting growth happens. Keep using the tools. Just keep closing the gap between what the model wrote and what you understand, and let ownership lead. "Software engineer who uses AI" — the order of those words is the whole lesson, and it's one you grow into, not one you're born with.

Written by Erik Yuntantyo·Software Engineer·About me