DEV Community

Aleksandr Rakitin
Aleksandr Rakitin

Posted on

Why I Stopped Debugging AI Code: The Case for Roguelite Development

From "Vibe Coding" feature factories to a disciplined Synthetic Team.

Traditional software development runs on two-week sprints. But when your lead engineer is an LLM that types 100 tokens per second, a sprint becomes a 15 minutes speedrun. And so does the accumulation of technical debt.
I'm a product manager by trade, not an engineer. I define the problems worth solving, the business strategy, the PRDs. I've always relied on a dedicated dev team to build a scalable solution.

Maybe a year ago, I started integrating AI into my workflow. At first, for researching, writing, prototyping. Then I realized I didn’t need a dev team to start building. And maybe I could go all the way?

So I tackled a problem that had bothered me for a while: writing cold outreach messages for networking.

We all know the pain. You find a perfect contact on LinkedIn who seems like a solid lead to your dream job. You know a generic template sounds pathetic, so you spend 20 minutes crafting a truly personal message. Which is fine, once. But in the 2026 job market, you need to do that hundreds of times.

I set out to build Opener Studio to solve this friction. I started "vibe coding" with Lovable. It felt like magic. I was shipping features in hours that used to take… well, forever, for a PM with zero dev experience and no engineers. It was unbelievably empowering.

Until I hit a wall.

Phase 1: The Vibe Code Trap

One problem with AI-assisted coding is that it is fast. Too fast. Too easy. It gets you into a flow state where you keep prompting ahead until you realize you have no idea where you are. At some point, I was building features mostly because I could. Never good for the user.

But something meaner sneaked up on me. As soon as my app grew beyond a few simple pages, the complexity started creeping through the cracks. Fixing one bug created two more. Error messages got hopelessly arcane. I was screaming prompts into the void, and the void would scream back with syntax errors.

AI doesn't necessarily hallucinate obvious errors; it makes subtle mistakes. It starts making "easy" decisions. It puts logic in the client that belongs in the backend. It creates race conditions. It defaults to localStorage when it should use a database.

I had a "coyote looking down" moment while building a guest-profile feature. It worked sometimes, but not every time - which is worse than never. I realized I built a skyscraper on a swamp, and I was soft-locked in it.

Suspended gravity coyote

I spent days arguing with the bot, trying to refactor the mess. I was spending more time in VS Code moving buttons manually because I was worried that asking the AI to do it would break the code elsewhere.

Clearly I was losing control. I knew then that I had to start using AI differently: less like a magic wand, more like a power tool. This is where I switched from Lovable to Cursor. In a few days, I was able to completely refactor the code, remove redundancies, apply a design system, and make it maintainable. Cursor felt like the right balance between magic and control. That was the first step.

Phase 2: Roguelite Development

Even with Cursor, I needed a methodology. One that embraced the speed of AI but enforced the discipline of product and engineering.

In video games, there’s a genre called Roguelite. In games like Hades or Returnal, death is not a failure. It is part of the loop. You run, you die, you lose your progress, but you keep your upgrades. You restart stronger.

I applied this logic to my development process.

In traditional development, you try to fix errors. You debug, you read logs, you watch the console. In AI-assisted development, fixing the bug often makes it worse for me - the context window gets polluted, discarded ideas get reimplemented, the solution quietly becomes fragile.

So, I stop trying to fix it. Here is what I do instead:

1. The Run
I let the AI sprint. We build fast and dirty. We test the design concept, not the code. We see what happens.

2. The Death
Eventually, the complexity catches up. The architecture drifts, gets messy. Instead of fixing it, I declare the end of the run.

3. The Upgrade
The code doesn’t matter, the experience does. I analyze what went wrong, I ask Gemini what other options we could have taken. I learn why the AI chose the wrong path (e.g., "It defaulted to localStorage because I didn't clarify I wanted it stored for later use"). I update my mental model and, more importantly, .cursorrules.

4. New Run
Then, I do the scariest thing for a new developer:

Bash
git restore .
git clean -fd
Enter fullscreen mode Exit fullscreen mode

I wipe the feature, open a new chat, and write the new prompt: with more direction, constraints, architectural principles - with lessons learned. I treat the code as disposable, as learning material. Then we rebuild the feature, but this time, I guide my synthetic partner down the right path. We’ve leveled up.

.cursorrules
# Project Context
You are the Lead Engineer for Opener Studio, a "Thick Backend" application built to cure "Blank Page Syndrome" for professionals.
Your goal is to build a snappy, "Studio-grade" interface with robust data integrity.

# Tech Stack
- Frontend: React + Vite + Tailwind CSS + Radix UI.
- Backend: Supabase (PostgreSQL, Auth, Edge Functions).
- Language: TypeScript (Strict mode).
- Icons: Lucide React.

# Architectural Rules (The "Roguelite" Constraints)

1. **Thick Backend, Thin Client:**
   - NEVER put business logic in the client.
   - The frontend only displays data and captures input.
   - All transformations, sorting, and AI calls happen in Supabase Edge Functions.

2. **State & Persistence:**
   - NEVER use `localStorage` for user data. It is forbidden.
   - All state must be persisted to Supabase tables immediately.
   - Use `tanstack-query` for data fetching (if applicable) to ensure freshness.

3. **Guest Mode First:**
   - The app must work without a `user_id`.
   - Always check `GuestSessionContext` before making Auth calls.
   - If user is null, fallback to Guest logic or prompt for sign-up (based on feature).

# Coding Standards

1. **Styling & Components:**
   - Use the existing design system (`src/components/design-system`).
   - Do not invent new Tailwind classes if a utility component exists.
   - Layouts: Prefer "Centered Layouts" (max-w-2xl mx-auto). Avoid split-pane layouts that squash inputs.

2. **Types:**
   - STRICT TypeScript. No `any`.
   - Import Database types directly from `src/types/supabase.ts`.

3. **Error Handling:**
   - Do not use `console.log` for user-facing errors.
   - Use the `toast` notification system for all API failures.
   - Never swallow errors in empty `try/catch` blocks.

# Behavioral Rules

1. **Ask Before Arch-Change:** If a request requires changing the database schema or adding a new library, STOP and ask for permission.
2. **One Step at a Time:** Do not implement 3 files at once. Implement one, verify it compiles, then move to the next.
Enter fullscreen mode Exit fullscreen mode

A crucial implication here is that you need source control: git init on Day 1. Obvious for a developer, but not so much for a solo vibe coder! Yet you cannot "restore" if you don't commit. I think of git not as 'version control,' but as a save point. Like save-scumming, but noble. It is my remedy against the "vibe rush" and the foundation of the Roguelite development method.

Phase 3: The Synthetic Team

Eventually, to make this work, I had to stop acting like a vibe coder and set up a professional team. Meet the squad:

CEO (Me): I define the "what" and the “why”. I write the PRDs, map the user flows, and define the constraints. Most importantly, I manage the team.

CTO (Gemini): My strategic partner. I don't ask it to write code; I ask it to design systems. Before we build, Gemini reviews the architecture and explains the trade-offs. Afterwards, we do retrospectives.

Lead Engineer (Cursor): The builder. It executes the CTO’s vision. It refactors, cleans, and builds the actual files. It’s still on me (and the CTO) to do code review, but Cursor handles the heavy lifting.

CMO (Gemini): Since it already knows the code, it helps me write the story behind the product, ensuring the marketing matches reality. Helps me write this post, as well.

Synthetic org chart

And, as with any team, you need clear roles, processes, hand-offs, guardrails, tools - typical managerial stuff. Roguelite sounds chaotic, but it requires discipline.

For me, discipline starts with documentation: you still need a solid PRD even if you think you can explain your whole app in a prompt. Discipline requires data: I integrated PostHog to know exactly what my users are doing. Discipline implies consistency: I rely on Codespaces and Vercel to build and deploy my app safely and reliably.

As for guardrails, .cursorrules works really well. One lesson I learned the hard way is that AI tends to shove state into the front end because it's easy in the short term. It writes complex useEffect chains to manage data that should never leave the server. That’s how the ”thick backend, thin client” principle got added to .cursorrules early on.

Takeaways

In Roguelite dev, I take the messy speed of AI and turn it into an advantage that agile always promised to deliver. Code is, indeed, disposable. Documentation, discipline, structured learnings are the real assets - always have been.

Run. Die. Learn. Repeat.

Git gud.


I am building Opener Studio in public. You can try the guest workspace without signing up at openerstudio.com.

Connect with me on LinkedIn on all things Product, AI-assisted building, Energy Management, and Opener Studio.

Top comments (0)