The Problem
You vibe-coded a feature in 30 minutes. It works. Your manager sees it and says "ship it." Two weeks later, it's in production and breaking in ways you never imagined. Users find edge cases. The code that "works" doesn't scale. You're debugging spaghetti at 2am.
The issue: vibes got you to working, but working isn't shipped.
Most developers either harden too early (killing momentum) or never harden at all (shipping prototypes). There's a middle path, but it requires a deliberate pipeline.
The Core Insight
Vibe coding and production coding are different phases, not different approaches.
Think of it like writing: first draft is for getting ideas out (vibes), revision is for clarity (validation), editing is for polish (hardening). You don't edit while drafting - that kills flow. You don't ship first drafts - that kills credibility.
The pipeline has three phases:
| Phase | Goal | AI Mode | Output |
|---|---|---|---|
| Explore | Make it work | Full vibes, accept suggestions freely | Working prototype |
| Validate | Prove it's right | Challenge AI, verify assumptions | Tested, documented code |
| Harden | Make it unbreakable | Use AI for edge cases, security review | Production-ready code |
The Walkthrough
Phase 1: Explore (The Vibe Zone)
This is pure vibe coding. Let the AI drive. Accept suggestions. Don't worry about:
- Code organization (you'll fix it later)
- Error handling (get the happy path first)
- Edge cases (they don't exist yet)
- Performance (premature optimization is evil)
What you're doing: Proving the concept works at all.
Capture during this phase:
- What worked (commit messages)
- What the AI struggled with (notes for validation)
- Assumptions you're making (TODO comments)
Git Strategy for Explore Phase
Work on a feature/explore-* branch. Commit frequently with WIP messages. These commits are for recovery, not history. You'll squash later.
Phase 2: Validate (The Skeptic Zone)
Stop vibing. Start questioning. Every piece of AI-generated code gets challenged:
- "What happens if this input is null?"
- "What's the time complexity here?"
- "Is there a security issue with this approach?"
What you're doing: Finding everything that's wrong before users do.
Actions in this phase:
- Write tests for the happy path
- Add tests for edge cases you identified in Explore
- Ask AI to review for common bugs
- Document what the code actually does (not what you hoped)
# Validation prompt template
"Review this code for:
1. Null/undefined handling
2. Array boundary errors
3. Type coercion issues
4. Race conditions (if async)
5. Input validation gaps
Code:
[paste code]"
Phase 3: Harden (The Production Zone)
The code works and is tested. Now make it bulletproof:
- Add proper error handling with user-friendly messages
- Implement logging for debugging production issues
- Add monitoring hooks
- Performance optimization (only now!)
- Security hardening
What you're doing: Making it survive contact with real users.
The Hardening Trap
Don't over-harden. A CLI tool used by your team doesn't need the same error handling as a public API. Match hardening effort to risk.
Transition Triggers
Explore → Validate when:
- The feature works end-to-end
- You could demo it to a stakeholder
- You're tempted to "just add one more thing"
Validate → Harden when:
- Test coverage is above 80% for critical paths
- Documentation exists for public interfaces
- No known bugs remain
Example: Building a CLI Tool
Let's trace a real example through all three phases.
Explore Phase (~30 minutes)
# Prompt: "Create a CLI that fetches weather for a city"
# AI generates something like:
import requests
import sys
city = sys.argv[1]
response = requests.get(f"https://wttr.in/{city}?format=3")
print(response.text)
It works! But it's fragile. Move to validate.
Validate Phase (~1 hour)
# Questions to ask:
# - What if no city provided?
# - What if API is down?
# - What if city has spaces?
# - What about rate limiting?
# Tests:
def test_no_city_argument():
# Should exit with helpful message
def test_invalid_city():
# Should handle gracefully
def test_city_with_spaces():
# "New York" should work
Harden Phase (~2 hours)
import requests
import sys
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def get_weather(city: str) -> str:
"""Fetch weather for a city from wttr.in."""
if not city:
raise ValueError("City name required")
city_encoded = city.replace(" ", "+")
try:
response = requests.get(
f"https://wttr.in/{city_encoded}?format=3",
timeout=10
)
response.raise_for_status()
return response.text.strip()
except requests.Timeout:
logger.error(f"Timeout fetching weather for {city}")
raise
except requests.RequestException as e:
logger.error(f"Error fetching weather: {e}")
raise
def main():
if len(sys.argv) < 2:
print("Usage: weather ", file=sys.stderr)
sys.exit(1)
city = " ".join(sys.argv[1:])
try:
weather = get_weather(city)
print(weather)
except Exception as e:
print(f"Error: Could not fetch weather for '{city}'",
file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Failure Patterns
1. Hardening in Explore Phase
Symptom: You spent 4 hours on a feature that should take 1, and you're still not sure it's the right approach.
Fix: Accept messy code until you've proven the concept. You can always delete it.
2. Shipping Explore Code
Symptom: "It works on my machine" becomes a mantra. Production errors spike.
Fix: No code reaches production without passing through Validate. Period.
3. Losing Explore Insights
Symptom: You rewrote something you already solved because you forgot how.
Fix: Capture notes during Explore. Use commit messages and TODO comments liberally.
4. Validate Paralysis
Symptom: You've been writing tests for a week and still haven't shipped.
Fix: Set time limits. If 80% of critical paths are tested, move to Harden.
Quick Reference
Phase Checklist:
- Explore: Does it work at all? (30 min - 2 hours)
- Validate: Is it correct? (1-4 hours)
- Harden: Is it production-ready? (2-8 hours)
Time Ratios (rough guide):
- Internal tool: 40% Explore, 30% Validate, 30% Harden
- Public API: 20% Explore, 30% Validate, 50% Harden
- Throwaway script: 90% Explore, 10% Validate, 0% Harden
Git Branch Strategy:
feature/explore-weather-cli # WIP commits, messy
feature/weather-cli # Clean commits after validate
main # Merged after harden