CockpitCockpit
← Back to blog

Vibe coding needs a bit of taste

Published May 12, 2026 · 6 min read

Vibe coding's hidden cost isn't speed — it's entropy. Cockpit's repo today is two piles: `packages/feature/` is the business, `packages/shared/` is the common floor, arrows go one way. From that picture, three old-school engineering habits — putting things in the right place, drawing real boundaries, deleting what doesn't fit — become more valuable when the agent is the one typing.

The agent finishes a run. Diff is a hundred-something lines. Looks fine. You hit approve.

Two weeks later, something's broken somewhere and you can't find it. Every function is reasonable. No name is bad enough to need changing. You just don't really want to open this repo anymore.

What's hard about vibe coding isn't making the agent faster. It's the codebase still looking like a codebase after the agent has run a thousand times.

The current shape

src/                       Next.js entry, no business code
 │
 ▼
packages/feature/          agent  console  explorer
                           workspace  review  comments  skills
 │                         (may reference each other, must stay acyclic)
 ▼
packages/shared/           ui  utils  i18n
                           (leaf; cannot import feature)

Three slots, that's the whole repo:

  • src/ is what Next.js itself needs — page files, thin shim routes that forward to the API handlers. No business code.
  • packages/feature/ is the business. Seven packages, each a standalone npm package.
  • packages/shared/ is the common floor. Three packages: UI components, utilities, the i18n dictionary.

Arrows go one way: anything on top may depend on anything below, but the bottom never imports up. ESLint watches this. Write it the wrong way and lint refuses to pass.

Every point that follows lands on a specific spot in that picture.

A few old-school things

Where a thing goes. Chat — API, UI, state, scheduled jobs, slash commands — all lives under packages/feature/agent, one folder. To change anything about chat you don't hunt across the repo. This isn't for tidiness. It's so a person — or an agent — can finish a job with one folder's worth of attention.

Boundaries. shared/ is not allowed to import from feature/. This rule isn't in a README that humans are supposed to remember; it's in ESLint, enforced by a tool. "Be considerate to the next reader" isn't a slogan — it's a lint error.

Delete with conviction. The dev dependencies I've removed weren't bad because they were old. They were bad because they didn't fit anywhere in the picture — not part of any feature, not part of the shared floor. Anything that doesn't fit the picture shouldn't be in the repo.

Don't invent vocabulary. The picture has two nouns: feature and shared. I didn't use domain, didn't use module, didn't use app or infra. An npm package is a contract everyone already understands; "feature" and "shared" are plain English. Every noun you'll find in this repo is either npm's own term or a word a middle schooler reads without thinking.

Three pictures from this refactor

src/ got emptied out. It used to hold components, hooks, contexts — the lot. Now it's only the top slot of the diagram — Next.js's entry. The business sits in the middle slot. When you open the repo you can tell at a glance where to look, because framework noise and business work are physically separated.

Only two horizontal piles. Adding a third is easy: feature, shared, then "infra"? Then "core"? Every new name needs explaining, and every explanation eventually triggers "well, which pile does this go in?" I held the line at two. Anyone who has ever installed an npm package needs zero extra training to read this repo — package.json, exports, dependencies — they already know how those work. I didn't make them learn anything new.

The moment of deletion. A test framework that hadn't been run in a year, a component sandbox nobody opened, a browser-automation harness from a finished experiment. One commit, all gone. I hesitated for two seconds before deleting, and nobody missed any of it after. Admitting I'd added something I shouldn't have is a thing I do faster every year.

How the agent moves inside this picture

A human reads code with intuition. An agent reads whatever fits in its context window. The picture itself helps the agent in two specific ways.

Blast radius has a ceiling. When the agent is changing chat, it opens packages/feature/agent and nothing else. It can't see, and doesn't need to see, console or explorer. The physical separation makes "agent casually broke an unrelated feature" structurally hard, not just unlikely.

The arrow direction teaches the agent how to write. When the agent is writing inside shared/, it can't see any feature — it literally cannot import a feature's internals. That forces it to write something genuinely general. When it's writing inside feature/, it knows it can't reach beyond shared/, and that knowledge lets it work without hedging.

A codebase's habits, the agent learns fast. That shortcut you took six months ago — it's already in the context window, presented as the project's style, and the agent will follow it. A repo without taste teaches the agent to be tasteless, fast. The flip side: when the words in the repo are ones the agent has read a million times — package.json, exports, dependencies — it just works. It hasn't read your homemade module system.

Last thing

Taste isn't a synonym for slowness. It's what lets "fast" last past the second month.

Set the boundaries, keep the vocabulary as small as you can, pull out what isn't being used. Let the agent run inside that shape — it can keep up. A year later you come back and you still want to open the repo. That's not a thing that lands in the changelog, but it's the thing that decides whether this project stays fun to work on.


npm i -g @surething/cockpit · GitHub · Try Online