|

Beyond Vibe Coding: My AI Workflow for a 200K-Line App

Geometric flows and glowing energy

Andrej Karpathy coined “vibe coding” in early 2025. The idea was simple: describe what you want, accept what the AI gives you, and don’t look too closely at the code. It was fun. It worked for throwaway projects. Then people tried to ship real software with it.

A December 2025 analysis of 470 GitHub pull requests told a different story. AI co-authored code had 1.7x more major issues β€” logic errors, flawed control flow, and misconfigurations. Security vulnerabilities were 2.74x higher than in human-written code.

That tracks with my experience. Yet I still built a 200,000-line production app where AI wrote the vast majority of the code. And it works.

The difference wasn’t better prompts. It was a better system.

What I built (and why the scale matters)

ForexFlow is a forex trading platform. It connects to OANDA (a broker), streams live prices, processes TradingView webhook signals, and detects trade setups. On top of that, it uses Claude AI for trade analysis and automated execution. It manages real money.

Under the hood, it’s a pnpm + Turborepo monorepo with five apps:

apps/ 
  web/ β€” Next.js 15 (frontend + 108 API routes) 
  daemons/ β€” Node.js daemon (54 REST endpoints, 39 WebSocket events) 
  desktop/ β€” Electron macOS app cf-worker/ β€” Cloudflare Durable Objects (webhook relay) 
  mcp-server/ β€” MCP bridge (Claude Code ↔ live trading data) 
  packages/ 
    types/ β€” Shared TypeScript contracts 
    shared/ β€” Pure utilities 
    db/ β€” Prisma ORM, 42 models, SQLite/Turso 

In total: 840 TypeScript files, 334 React components, 59 documentation files, and 195 commits. It supports three deployment modes β€” local dev, Electron desktop, and cloud on Railway with Turso.

I’m not listing these numbers to brag. The reason they matter is that the AI coding workflow for a 500-line side project is nothing like what works at this scale. Most “I built X with AI” case studies involve small, simple apps. The hard engineering problems only appear once the codebase grows large enough for the AI to contradict itself, forget its own patterns, and scatter inconsistencies across hundreds of files.

That’s where the system comes in.

The four layers that made AI coding actually work

Addy Osmani’s excellent LLM coding workflow post covers the basics well: specs first, small chunks, always review. I agree with all of it. However, I want to go deeper into the enforcement side β€” the layers that keep good practices alive across hundreds of sessions, not just the first few.

Layer 1: Instructions that travel with the code

Every Claude Code session loads .claude/CLAUDE.md β€” a version-controlled file that spells out the architecture, boundaries, and standards. This isn’t vague guidance. It’s a technical spec:

## Workflow 
- **No guessing**: do not invent files, APIs, or libraries. Check what exists before creating. 
- **Small changes**: prefer surgical edits over large rewrites. 

## Code Standards 
- **File size**: components ≀150 LOC, services ≀300 LOC. 
- **Strict TypeScript**: no `any`. Prefer discriminated unions, branded types for IDs, exhaustive switch/never. 

## Import Boundaries (strict) 
- `apps/*` may import from `packages/*`. 
- `apps/web` must NOT import from `apps/daemons`. 
- `packages/*` must NOT import from `apps/*`. 

That “no guessing” line is probably the single highest-ROI instruction I wrote. Without it, the AI would regularly import libraries that don’t exist, reference unbuilt API endpoints, and create duplicate helpers. One sentence wiped out an entire class of errors.

Still, the root file is just the top of the stack. Every app and package also has its own CLAUDE.md β€” 8 sub-project instruction files in total. For example, the daemon’s file covers StateManager patterns, per-instrument mutexes, and crash recovery. Meanwhile, the database package’s file covers the encryption pattern and the enrichSource() function. So when the AI works on a file in apps/daemons/, it gets the right context loaded in automatically.

This is what Anthropic’s own best practices recommend. But most people stop at a single CLAUDE.md. Scaling it per-workspace turned out to be a big win for keeping things consistent.

Layer 2: Path-scoped rules

On top of CLAUDE.md, there are 11 rule files in .claude/rules/. Each one is scoped to specific file patterns:

# .claude/rules/03-daemon-patterns.md 
---
paths:
  - "apps/daemons/**"
---

- StateManager is single source of truth β€” use event listeners, not polling.
- Trade.source always "oanda". True origin in Trade.metadata.placedVia.
- Per-instrument mutex for trade syncing and signal processing.
- Crash recovery: reset stuck "executing" states on startup.

When the AI opens a file matching apps/daemons/**, this rule loads on its own. There’s no need to remind it about the StateManager pattern every conversation β€” the tooling handles that.

Why does this matter at scale? Without rules, the AI picks different patterns for the same type of problem. One time it polls, the next time it uses events. One time it validates at the boundary, the next time it checks deep inside a function. One time it handles errors, the next time it silently swallows them. Rules get rid of that drift.

Here’s the full set:

RuleScopePurpose
TypeScript qualityAll .ts/.tsxStrict types, Zod validation, no silent catches
Web patternsapps/web/**App Router, mobile-first, shadcn/ui conventions
Daemon patternsapps/daemons/**StateManager, mutexes, crash recovery
CF Worker patternsapps/cf-worker/**Durable Objects, idempotency, queues
DB patternspackages/db/**One service per domain, AES-256-GCM encryption
AccessibilityComponentsAAA baseline, keyboard nav, 44px touch targets
Trading domainTrading codeOrder lifecycle, price precision, OANDA rules
Docs syncAll code + docsDocumentation map, same-commit updates

Layer 3: Hooks that enforce

Here’s where good intentions turn into real guarantees. Rules tell the AI what to do. Hooks make sure it actually did it.

I run three hooks, all registered in .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "command": ".claude/hooks/guard-bash.mjs"
          },
          {
            "command": ".claude/hooks/docs-sync-check.mjs"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "command": ".claude/hooks/format-on-edit.mjs"
          }
        ]
      }
    ]
  }
}

The docs-sync hook is the one I’m proudest of. It runs before every git commit and checks whether staged code changes have matching doc updates. Inside, there’s a map of 14 code-area patterns tied to the doc files that cover them:

const DOC_MAP = [
  {
    pattern: /^apps\/web\/src\/components\//,
    docs: ["apps/web/CLAUDE.md"],
    label: "web components",
  },
  {
    pattern: /^apps\/daemons\/src\//,
    docs: ["apps/daemons/CLAUDE.md", "docs/ai/realtime.md"],
    label: "daemon services",
  },
  {
    pattern: /^packages\/db\/prisma\//,
    docs: ["packages/db/CLAUDE.md"],
    label: "database schema",
  },
  // ... 14 mappings total
]

Add a new daemon service without staging updates to apps/daemons/CLAUDE.md? The commit gets denied. The AI then sees a message listing exactly which docs need work and why. After that, it updates them and retries.

The result: 59 documentation files that stay in sync with the code, on their own, across 195 commits. I never had to remember to ask. I never had to check for staleness. The system handled it.

The format hook runs Prettier on every file the AI writes or edits. It’s a small thing, but it cuts out a whole category of review noise.

The bash guard, meanwhile, blocks destructive commands like rm -rf /, mkfs, and fork bombs. When you hand an AI agent shell access, defense in depth matters.

All three of these are built-in Claude Code features that I think most people underuse. The official guidance is to let patterns show up first, then turn them into hooks. That matches my experience β€” I built each one after spotting the same failure mode more than once.

Layer 4: Skills as repeatable workflows

Skills are step-by-step recipes for common tasks that cut across multiple files. Take add-daemon-endpoint β€” it wires a new REST endpoint through five layers of the stack:

## Steps

1. **packages/types/src/index.ts** β€” Add request/response types
2. **apps/daemons/src/server.ts** β€” Add route handler
3. **apps/web/src/app/api/route.ts** β€” Create Next.js API proxy
4. **apps/web/src/hooks/use-*.ts** β€” Add hook method
5. Run `/verify`

Without this skill, the AI might add the daemon route but skip the API proxy. Or it might wire the hook but miss the shared types. Or it could do everything but forget to run checks. The skill makes sure the full vertical slice lands correctly every time.

In total, I have 9 skills for the most common patterns:

  • add-daemon-endpoint β€” types β†’ daemon β†’ API proxy β†’ hook β†’ verify
  • add-ws-event β€” wire a WebSocket event through all 5 layers
  • add-db-service β€” Prisma model β†’ migration β†’ service file β†’ exports
  • verify β€” lint + typecheck + test + format, auto-fix failures
  • a11y β€” AAA accessibility audit checklist
  • doc-check β€” find stale docs that point to code that no longer exists
  • refactor-small-files β€” enforce file size limits, break up large modules

One skill worth calling out: verify. It doesn’t just flag failures β€” it fixes the root cause and re-runs until everything passes. That closes the feedback loop inside a single session, rather than leaving broken type errors for me to find later.

The prompt library: rules the AI actually follows

Here’s something I learned the hard way: the AI doesn’t always follow its own rules.

Even with CLAUDE.md, path-scoped rules, and hooks in place, I’d still catch the AI cutting corners. It would skip accessibility checks on “simple” components. It would write 200-line files when the limit was 150. It would add a feature without updating the docs β€” even though the hook would block the commit anyway, wasting a round trip.

So I started keeping a library of copy-and-paste prompt snippets that I paste into the conversation itself. These aren’t replacements for the governance system. They’re reinforcements β€” a way to front-load the rules I care about most for a given task, right where the AI can’t miss them.

For example, before any UI work, I paste a block like this:

Before writing any component code:

- Max 150 LOC per file. If approaching limit, split.
- Mobile-first. Touch targets min 44x44px.
- AAA accessibility: semantic HTML, keyboard nav, visible focus, no color-only meaning, respect prefers-reduced-motion.
- No hover-only interactions.
- Run /a11y when done.

And before any cross-cutting feature work:

This feature touches multiple layers. Follow this order:

1. Add types to packages/types first.
2. Implement the daemon endpoint.
3. Create the API proxy route.
4. Add the frontend hook.
5. Build the UI component.
6. Run /verify before committing.
7. Update all relevant CLAUDE.md files and docs.

I keep about a dozen of these saved in a notes app, grouped by task type: UI work, daemon work, database changes, full-stack features, and refactoring. Each snippet takes five seconds to paste but saves minutes of back-and-forth when the AI goes off track.

The key insight is that rules in CLAUDE.md set the baseline. But putting specific rules directly in the prompt β€” right next to the task β€” gives them much more weight. Think of it like the difference between a company handbook and your manager telling you something face-to-face. Both matter, but the direct instruction gets followed more reliably.

Over time, the best snippets also feed back into the system. When I notice I’m pasting the same snippet every session, that’s a signal to promote it β€” either into a rule, a skill, or a hook. The prompt library is a staging ground for governance improvements.

The secret weapon: live system context

This is the piece I think most developers haven’t explored yet. For me, it was the biggest surprise multiplier.

ForexFlow includes an MCP server that bridges Claude Code to the live running system. It exposes 24 tools that let the AI query real data while writing code:

  • get_open_trades β€” current positions with live P&L
  • get_prisma_schema β€” the full database schema
  • get_db_services β€” every function that already exists (prevents duplicates)
  • get_signal_audit β€” full processing pipeline for debugging

When I’m building a feature that touches trade data, the AI reads the actual schema instead of guessing at data shapes. When I’m debugging a signal processing issue, it queries the real audit trail. And when I’m adding a database function, it checks what already exists first.

Most AI coding tools work in a vacuum β€” they can see files but not the running system. The MCP server closes that gap. This is where the spec-driven approaches people are writing about become real. The AI isn’t just reading a spec document. It’s reading the live system.

AI as a runtime product feature

The same discipline that made AI-assisted development work also shaped how I built AI into ForexFlow as a product feature. Two systems are worth covering.

Trade analysis

When you request analysis on an open trade, the system pulls together a structured context package in code β€” not in the prompt. It gathers trade details, account state, candles across three timeframes, locally-computed indicators (RSI, ATR, EMAs, support/resistance), correlated pairs, your win rate history, economic calendar events, and news. All of that goes to Claude with a structured system prompt that asks for specific JSON output.

The key design decision: the prompt stays the same every time. Only the data changes. As a result, AI behavior is predictable and the output is easy to parse.

The 3-tier cost pyramid

The bigger system is EdgeFinder β€” an automated trading pipeline built around cost control:

Tier 1 (free): Local technical analysis. 14 techniques β€” RSI, MACD, EMAs, Bollinger Bands, Smart Money Concepts, Fibonacci. All run in TypeScript with zero API calls. Every pair gets scanned on every cycle.

Tier 2 (cheap): Promising signals go to Claude Haiku for a quick pass/fail filter. Each call costs roughly $0.001–0.003. Most signals get rejected at this stage.

Tier 3 (expensive): Only the survivors reach Claude Sonnet for a deep analysis with economic data, news, and historical performance. Each call runs about $0.01–0.05.

Even after Sonnet says “execute,” there’s still an execution gate. It checks market hours, spread levels, and news proximity. On top of that, a circuit breaker kicks in after 4 daily losses or 3% drawdown β€” pausing the system until midnight. Position sizing is always handled by code, never by the LLM. That’s a line I won’t cross.

Every API call gets cost-tracked to the database β€” including rejected signals. Daily and monthly budget caps are enforced automatically.

What it honestly doesn’t do

I want to be clear about this: the system does not learn. There’s no fine-tuning and no feedback loop from trade outcomes back to the model. Each Claude call stands on its own. I track recommendation accuracy for my dashboards, but that data doesn’t train anything.

Historical performance does go into the Tier 3 context as read-only input. Claude can see something like “this strategy has a 60% win rate on EUR/USD.” But it can’t change its own behavior based on that. A real feedback loop would be the logical next step.

What I’d do differently

Write the governance system first. I built rules, hooks, and skills as I went β€” spotted a failure mode, wrote a rule; noticed a workflow gap, wrote a skill. That approach worked, but the first third of the project had far more rework. The AI was running without guardrails. If I started over, I’d set up at least the core rules and the verify skill before writing any application code.

Build the MCP server earlier. Letting the AI query live system state while coding was a bigger boost than I expected. I added it midway through the project and right away wished I’d had it from day one.

Trim the CLAUDE.md more often. As Anthropic’s own docs note, a bloated CLAUDE.md causes important rules to get buried. I could move more instructions into hooks β€” if the AI already follows a rule on its own, it doesn’t need to live in the instruction file.

The real takeaway

The story around AI coding has evolved fast. Early 2025 was “vibe coding” β€” let the AI write whatever. Late 2025 brought the backlash β€” AI code is buggy, don’t trust it. Now in 2026, we’re landing somewhere more useful: AI coding works when you build the system around it.

Rules constrain. Hooks enforce. Skills keep workflows consistent. MCP servers bring in live context. Import boundaries stop spaghetti code. File size limits stop bloat. Doc sync stops drift.

None of this is flashy. None of it is a prompting breakthrough. It’s plain engineering discipline, applied to AI tooling.

That’s the real AI coding workflow. Not better prompts β€” better constraints.

What is a CLAUDE.md file and why does it matter?

CLAUDE.md is a markdown instruction file that lives inside your project repository. Claude Code loads CLAUDE.md at the start of every session. The file defines your architecture, coding standards, import boundaries, and domain rules. CLAUDE.md travels with the code in version control. Every AI session inherits the same baseline context as a result. Large projects benefit from sub-project CLAUDE.md files in each workspace. Each sub-project file gives the AI scoped context based on which directory it is working in.

What is the difference between Claude Code rules, hooks, and skills?

Rules are markdown files scoped to file path patterns. Claude Code loads rules automatically when the AI opens matching files. Rules provide context-specific guidance, such as database conventions for DB code. Hooks are shell scripts that execute before or after specific tool actions. Hooks enforce behavior the AI cannot skip, such as blocking a commit when documentation is missing. Skills are step-by-step workflow recipes that the AI follows on demand. Skills ensure multi-file tasks get completed in the correct order. Rules guide behavior. Hooks enforce behavior. Skills standardize behavior.

Is this the same as vibe coding?

No. Vibe coding is an approach where developers describe intent and accept AI output without deep review. Vibe coding works well for throwaway projects and quick prototypes. Vibe coding breaks down at scale β€” studies show AI co-authored code contains more logic errors and security issues. The approach in this article follows agentic engineering principles instead. Agentic engineering means the AI writes code within a system of rules, hooks, skills, and live context. The governance system constrains AI output and enforces quality standards. The human acts as architect and reviewer, not just a prompter.

What is an MCP server and how does it help AI coding?

MCP stands for Model Context Protocol. An MCP server is a bridge that exposes callable tools to an AI coding agent. ForexFlow’s MCP server provides Claude Code with 24 tools. These tools query the live running system β€” open trades, account balance, database schema, and signal audit trails. The AI reads the actual schema instead of guessing at data shapes. The AI checks which functions already exist before writing new ones. Most AI coding tools can only read files. An MCP server lets them read the running system as well.

Does the AI learn from past trading results?

No. Each Claude API call is independent. The system does not use fine-tuning. No feedback loop sends trade outcomes back to the model. ForexFlow does track recommendation accuracy for dashboards and observability. That tracking data does not train the model. Historical performance data goes into the Tier 3 context as read-only input. Claude can read win rates and patterns from that input. Claude cannot modify its own weights or behavior based on that information.

How much does it cost to run AI features in production?

ForexFlow uses a 3-tier cost pyramid to control expenses. Tier 1 runs all technical analysis locally in TypeScript. Tier 1 costs nothing because it makes zero API calls. Tier 2 sends promising signals to Claude Haiku, the cheapest model. Tier 2 costs roughly $0.001–0.003 per call. Most signals are rejected at Tier 2. Tier 3 sends survivors to Claude Sonnet for deep analysis. Tier 3 costs about $0.01–0.05 per call. ForexFlow enforces daily budget caps at $5 and monthly caps at $100 by default. Every call β€” including rejected ones β€” is cost-tracked to the database.

Why use a prompt library when rules and CLAUDE.md already exist?

AI agents do not always follow their configured rules. CLAUDE.md and path-scoped rules set a strong baseline. The AI sometimes cuts corners on file size limits or accessibility checks despite those rules. A prompt snippet pasted directly into the conversation carries more weight than a background rule. Direct instructions sit right next to the task where the AI processes them with higher priority. The prompt library contains about a dozen reusable snippets grouped by task type. Each snippet takes five seconds to paste and saves minutes of correction. Frequently used snippets become candidates for promotion into permanent rules, skills, or hooks.

How do I set up a structured AI coding workflow?

Start by creating a CLAUDE.md file in your project root. CLAUDE.md should define your architecture, coding standards, and import boundaries. Keep the file focused β€” a bloated CLAUDE.md causes the AI to ignore important rules. Next, add 2–3 path-scoped rules for your highest-priority directories. Then create a verify skill that runs your linter, type checker, and tests. Hooks should come after you spot recurring failure modes like skipped documentation or formatting drift. Each hook should target one specific enforcement need. The governance system should grow over time, not get built all at once. Invest in constraints before investing in prompts.


ForexFlow is built with TypeScript, Next.js 15, Prisma, Claude AI, and strong opinions about file size limits. The .claude/ governance system described here is open for questions β€” reach out if you want to talk about the approach.

Share your thoughts

Your email address will not be published. Required fields are marked *

Latest Articles