DEV Community

EL MANSOURI HAMZA
EL MANSOURI HAMZA

Posted on Originally published at blog.hamzaelmansouri.online

Before You Vibe Code a System: Nine Things to Settle First (With a Readiness Score)

The prompt is the cheap part of building a system with AI. What decides whether you end up with software or a pile of files is what you settled before the first prompt: what it must do, what it must never touch, and how you'll know it works.

This guide gives you a risk table by system type, nine decisions to settle first, a readiness score with a worked example, a script that checks a repo in seconds, and a spec template you can paste into your agent.

Prototype or system: the failure cost is different

Andrej Karpathy coined "vibe coding" in February 2025 for a style of building where you give in to the vibes and "forget that the code even exists". Snyk It was half a joke and half a real workflow, and for a weekend project it works fine. Nothing bad happens when a throwaway app breaks.

A system is different. It has users, data other people depend on, and sometimes money or personal information passing through it. The workflow is the same, but now it has a failure cost, and that cost decides how much AI-written code you can accept without reading it.

Tier Example What breaks if it's wrong Review level Minimum readiness score
1 Prototype, personal tool, static page You lose an evening Skim it, run it 6 / 18
2 Internal dashboard, read-only Wrong numbers reach a decision Read every query 9 / 18
3 Back-office tool with writes and several users (CRM, bookings) Lost or corrupted records, wrong permissions Read all writes, permissions and migrations 12 / 18
4 Payments, personal or health data, anything regulated Money moves wrongly, data leaks, legal exposure Line-by-line review plus a security pass 15 / 18

The minimum scores are a rule of thumb, not an industry standard. Change them if you like, but keep the shape: the more a system can hurt someone, the more you settle before you prompt.

What the evidence says

Three data points are worth knowing, each with its limits.

Security. Veracode tested more than 100 models on 80 coding tasks and found that the generated code introduced an OWASP Top 10 vulnerability in 45% of cases, with cross-site scripting missed in 86% of the relevant samples. Veracode It is a vendor study of code-completion tasks, not of finished apps, but the direction is clear: if you don't specify security requirements, the model decides for you.

Speed you can't feel. METR ran a randomized trial with 16 experienced open-source developers on 246 real tasks. With AI allowed, tasks took 19% longer, even though the developers had predicted a 24% speedup and still believed they had been faster afterwards. Simon Willison The sample is small and the tools were early-2025 ones, so don't read it as "AI slows everyone down". Read it as "feeling fast is not a measurement".

Instructions are not controls. In July 2025, during a public 12-day vibe coding trial by SaaStr founder Jason Lemkin, Replit's agent ran destructive commands on day nine and erased a production database with records on 1,206 executives and over 1,196 companies, despite instructions not to change anything. eWeek Reports said the same database served preview, testing and production, and Replit responded by rolling out automatic separation of development and production databases. heise

None of this says "don't use AI to build systems". It says the risk sits in the decisions your prompt didn't cover.

The nine decisions to settle first

  1. A one-page spec. What the system does, for whom, and what it must not do. Written in the repo, not only in a chat window that will scroll away.
  2. The data model. Entities, fields and relations. If the agent invents tables as it goes, every later feature inherits its guesses, and migrations on live data are where mistakes get expensive.
  3. Auth and roles. Who can see and change what, checked on the server. "Add login later" is the most common way a prototype becomes a liability.
  4. A stack you can read. Pick a language and framework you can debug without the agent, and lock the dependencies. If you can't review it, you can't own it.
  5. Tests as the stopping point. A failing test that must turn green gives the agent a clear finish line and gives you a clean success signal.
  6. Secrets and environments. Separate development and production, keys outside the repo, and an agent that only ever receives development credentials.
  7. Deploy and rollback. How code reaches production and how you undo it. A backup you have never restored is a hope, not a backup.
  8. A scope limit. A first release of a few user stories with a definition of done. "Build me a CRM" is not a scope.
  9. A stop-and-read rule. The moments when you stop prompting and read the diff: anything touching auth, money, migrations or deletion, and any time the project shows the warning signs below.

The readiness score

Score each decision from 0 to 2. The total is out of 18.

# Decision 0 1 2
1 Spec Nothing written A paragraph in chat One-page spec in the repo, with non-goals
2 Data model The agent invents it Rough entity list Schema with relations, reviewed by you
3 Auth and roles "Later" Login exists, roles ad hoc Roles written down, enforced on the server
4 Stack You can't read it You know the language, not the framework You can read every layer, dependencies locked
5 Tests None Some, written after the code A failing test defines each task
6 Secrets and environments Keys in code or a committed .env Env vars, but dev and prod shared Separate environments, secrets outside the repo
7 Deploy and rollback Manual copy to the server Automated deploy, no tested rollback CI deploy, backup restored at least once
8 Scope "Build me a CRM" A feature list A few user stories with a definition of done
9 Stop-and-read rule None You read the code sometimes Written rule, plus size and repetition limits

Then compare the total with the minimum for your tier:

readiness gap = minimum for your tier - your score

gap <= 0    start building
gap 1-3     close those items first, usually a day of work
gap >= 4    don't start yet; you are at the planning stage, not the vibe stage
Enter fullscreen mode Exit fullscreen mode

A worked example

Two projects, scored honestly. These are illustrative, not real client data.

# Decision Habit tracker (tier 1) Clinic booking tool (tier 4)
1 Spec 1 2
2 Data model 1 1
3 Auth and roles 1 1
4 Stack 2 2
5 Tests 0 1
6 Secrets and environments 1 2
7 Deploy and rollback 1 1
8 Scope 2 1
9 Stop-and-read rule 0 0
Score 9 11
Minimum for the tier 6 15
Gap -3, go +4, not yet

The clinic tool actually scores higher, and it is still the one that shouldn't start. It holds personal data, so it needs a much higher bar, and its weak spots are exactly the ones that hurt: no real auth model, no tests, no rollback plan, and no rule for when a human reads the code.

Let a script check what a machine can see

Some of the nine decisions leave traces in the repo. This script, standard library only, checks seven of them: written spec, lockfile, tests, secrets in the repo, .env ignored by git, oversized files and a CI config. It covers decisions 1, 4, 5 and 6 well, and 7 and 9 only partly. The rest, such as the data model, roles and scope, still need a human.

✅ Tested on Python 3.12

#!/usr/bin/env python3
"""preflight.py - quick readiness check for an AI-assisted (vibe coded) repo.

Usage: python preflight.py [path-to-repo]
Standard library only. Exit code 1 if any check scores 0.
"""
import pathlib
import re
import subprocess
import sys

ROOT = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else ".").resolve()
SKIP_DIRS = {".git", "node_modules", "vendor", "venv", ".venv", "dist", "build", "__pycache__", ".next"}
CODE_EXT = {".py", ".js", ".ts", ".tsx", ".jsx", ".php", ".rb", ".go", ".java", ".rs", ".vue"}
LOCKFILES = {"package-lock.json", "yarn.lock", "pnpm-lock.yaml", "composer.lock",
             "poetry.lock", "uv.lock", "Pipfile.lock", "Cargo.lock", "go.sum", "Gemfile.lock"}
SECRET_PATTERNS = [
    re.compile(r"sk-[A-Za-z0-9_\-]{20,}"),                    # API-style keys
    re.compile(r"AKIA[0-9A-Z]{16}"),                          # AWS access key id
    re.compile(r"(?i)(api[_-]?key|secret|password|token)\s*[:=]\s*['\"][^'\"\s]{12,}['\"]"),
]
MAX_LINES = 500


def walk():
    for p in ROOT.rglob("*"):
        if p.is_file() and not any(part in SKIP_DIRS for part in p.relative_to(ROOT).parts):
            yield p


def git(*args):
    try:
        r = subprocess.run(["git", "-C", str(ROOT), *args], capture_output=True, text=True, timeout=20)
        return r.stdout if r.returncode == 0 else None
    except (OSError, subprocess.TimeoutExpired):
        return None


files = list(walk())
names = {p.name for p in files}
code = [p for p in files if p.suffix in CODE_EXT]
results = []  # (label, score 0-2, note)

# 1. Written spec
spec = [n for n in names if n.lower() in {"spec.md", "readme.md", "claude.md", "agents.md", "prd.md"}]
results.append(("Written spec / README", 2 if {"spec.md", "prd.md"} & {n.lower() for n in spec}
                else 1 if spec else 0,
                ", ".join(sorted(spec)) or "none found"))

# 2. Lockfile
lock = sorted(names & LOCKFILES)
results.append(("Dependency lockfile", 2 if lock else 0, ", ".join(lock) or "none found"))

# 3. Tests
tests = [p for p in code if re.search(r"(^|[/_.-])(test|tests|spec)([/_.-]|$)", str(p.relative_to(ROOT)).lower())]
ratio = len(tests) / len(code) if code else 0
results.append(("Automated tests", 2 if ratio >= 0.15 else 1 if tests else 0,
                f"{len(tests)} test files / {len(code)} code files"))

# 4. Secrets in tracked files
tracked = git("ls-files")
tracked_set = set(tracked.split("\n")) if tracked else None
env_tracked = [f for f in (tracked_set or []) if re.fullmatch(r"(.*/)?\.env(\.[a-z]+)?", f) and not f.endswith(".example")]
hits = []
for p in code:
    try:
        text = p.read_text(errors="ignore")
    except OSError:
        continue
    if any(rx.search(text) for rx in SECRET_PATTERNS):
        hits.append(str(p.relative_to(ROOT)))
bad = len(env_tracked) + len(hits)
results.append(("No secrets in the repo", 2 if bad == 0 else 0,
                "clean" if bad == 0 else f"tracked .env: {env_tracked or 'no'}; key-like strings in: {hits[:3] or 'no'}"))

# 5. .gitignore covers .env
gi = ROOT / ".gitignore"
covered = gi.exists() and re.search(r"^\.env", gi.read_text(errors="ignore"), re.M) is not None
results.append((".env ignored by git", 2 if covered else 0, "yes" if covered else "missing rule"))

# 6. File size (growing past control)
big = []
for p in code:
    try:
        n = sum(1 for _ in p.open(errors="ignore"))
    except OSError:
        continue
    if n > MAX_LINES:
        big.append((n, str(p.relative_to(ROOT))))
big.sort(reverse=True)
results.append((f"No file over {MAX_LINES} lines", 2 if not big else 1 if len(big) <= 2 else 0,
                "ok" if not big else ", ".join(f"{f} ({n})" for n, f in big[:3])))

# 7. CI
ci = (ROOT / ".github/workflows").exists() or (ROOT / ".gitlab-ci.yml").exists()
results.append(("CI config present", 2 if ci else 0, "found" if ci else "none found"))

total = sum(s for _, s, _ in results)
maximum = 2 * len(results)
print(f"\nPre-flight for {ROOT.name}\n" + "-" * 60)
for label, score, note in results:
    print(f"{'PASS' if score == 2 else 'WARN' if score == 1 else 'FAIL'}  {score}/2  {label:<26} {note}")
print("-" * 60)
verdict = "ready to build" if total >= maximum * 0.8 else \
          "build with guardrails" if total >= maximum * 0.5 else "prototype only"
print(f"Score: {total}/{maximum}  ->  {verdict}\n")
sys.exit(1 if any(s == 0 for _, s, _ in results) else 0)
Enter fullscreen mode Exit fullscreen mode

Run it on any repo:

python preflight.py ~/projects/my-app
Enter fullscreen mode Exit fullscreen mode

It exits with code 1 if any check scores zero, so you can also run it in CI. Here is real output from two sample repos: a rushed one with a committed .env, a hardcoded key and an 820-line file, and a tidy one.

$ python preflight.py demo-messy
Pre-flight for demo-messy
------------------------------------------------------------
WARN  1/2  Written spec / README      README.md
FAIL  0/2  Dependency lockfile        none found
FAIL  0/2  Automated tests            0 test files / 2 code files
FAIL  0/2  No secrets in the repo     tracked .env: ['.env']; key-like strings in: ['src/config.py']
FAIL  0/2  .env ignored by git        missing rule
WARN  1/2  No file over 500 lines     src/app.py (820)
FAIL  0/2  CI config present          none found
------------------------------------------------------------
Score: 2/14  ->  prototype only
Enter fullscreen mode Exit fullscreen mode
$ python preflight.py demo-solid
Pre-flight for demo-solid
------------------------------------------------------------
PASS  2/2  Written spec / README      SPEC.md
PASS  2/2  Dependency lockfile        uv.lock
PASS  2/2  Automated tests            2 test files / 3 code files
PASS  2/2  No secrets in the repo     clean
PASS  2/2  .env ignored by git        yes
PASS  2/2  No file over 500 lines     ok
PASS  2/2  CI config present          found
------------------------------------------------------------
Score: 14/14  ->  ready to build
Enter fullscreen mode Exit fullscreen mode

The secret scan uses simple patterns, so it will miss some keys and flag some false positives. Treat it as a smoke alarm, not as a security audit, and run a proper scanner before anything holds real customer data.

A spec you can paste into the agent

Keep this at the root of the repo and tell the agent to read it at the start of every session. Fill in the brackets.

# Project: [name]

## Goal
[Two sentences: what it does and for whom.]

## Users and roles
- [Role]: can [actions]. Cannot [actions].

## Data
- [Entity]: [fields]. Belongs to [entity].

## Must do in v1
1. [User story]
2. [User story]

## Must NOT do
- No payments, no file uploads, no email in v1.
- Do not add dependencies without asking.

## Stack and constraints
[Language, framework, database, hosting. Versions pinned.]

## Definition of done
- Every task starts with a failing test that then passes.
- All tests green, no file over 500 lines.

## Rules for the agent
- Never read or edit .env files. Use .env.example for variable names.
- Never run destructive database commands. Migrations are proposed, not run.
- One task per session. Summarize what changed at the end.
Enter fullscreen mode Exit fullscreen mode

Settle the dangerous parts in code, not in the prompt

The Replit story has one lesson that is easy to miss: the agent had been told not to change anything, and it did anyway. A rule the agent can break is a request, not a rule. Wherever you can, enforce it with permissions:

  • Give the agent development database credentials only. Production credentials live somewhere it cannot read.
  • Work on a branch and keep the main branch protected, so a human merges.
  • Restore a backup once before you need it, and write down how long it took.
  • Use your agent's permission settings to require approval for deletes, migrations and shell commands that touch data.

Signs the project is outgrowing you

These thresholds are heuristics from experience, not measurements. They are useful because they are concrete.

Sign Rule of thumb What to do
A code file keeps growing Over 500 lines Split it before adding more
The same logic in several places Three or more copies Extract it, then tell the agent where it lives
You can't explain a module Not in two sentences Stop prompting, read it, write it down
Fix one bug, break another Three prompts in a row on one issue Revert to the last green commit, restate the task as a failing test
Features get slower Each takes longer than the last Schedule a refactor session
The agent edits tests to make them pass Any time Review every diff that touches tests

A checklist before the first prompt

Check Why it matters
Tier chosen and minimum score written down It sets how much you may accept unread
Spec is in the repo The agent and you share one source of truth
Schema and roles reviewed by a human Later fixes here touch live data
Dev and prod separated Instructions are not controls
Backup restored once An untested backup is a guess
Tests exist before features They give you a finish line and a signal
Stop-and-read rule written You know when to take over
Pre-flight script runs in CI The basics stay checked as the code grows

The takeaway

Vibe coding is a fine way to start. It is a poor way to decide. Pick the tier your system belongs to, score the nine decisions, and close the gap before you prompt. If the gap is zero, let the agent run and review what matters. If it is large, you have just saved yourself a rewrite.

Sources

What is vibe coding, and is it secure? (Snyk)
2025 GenAI Code Security Report (Veracode)
Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity (Simon Willison on METR)
AI coding can make developers slower even if they feel faster (The Decoder)
AI Agent Wipes Production Database, Then Lies About It (eWeek)
Vibe coding service Replit deletes production database (heise)

Top comments (0)