Everything fabbro create put on disk, and the one rule that governs each folder. Learn this map once and you'll always know where a change belongs.
The project's entry points and metadata, all at the top level.
app.py — the application factory and every route. Thin: it parses a request, calls a module, returns a page or JSON. New routes go at # <fabbro:routes>.
db.py — the project's schema, SQL, and data functions; the one place with SQL. New tables go at -- <fabbro:schema>, new functions at # <fabbro:data_functions>.
forjedoctor.py — the pre-flight checker that fabbro doctor runs. Its filename is a sentinel the CLI looks for to locate the project root — don't rename it.
agent.md — the guide you load into an AI agent: the non-negotiable rules and where things live.
requirements.txt · requirements-dev.txt · Dockerfile · docker-compose.yml · .gitignore — dependencies, the production image, and the local dev stack (web + workers + volume).
Import it, never edit it. This is Fabbro itself: reusable, domain-agnostic infrastructure your project stands on. Editing it means you've forked the framework.
One file per module (auth.py ships as the reference). A module holds only logic — rules, orchestration. It calls db.py for data and core/ for infrastructure; it never writes raw SQL or its own security. Each module defines a typed exception and raises it with an actionable message.
Background jobs, each a separate process, talking to the app only through the database queue. default.py and mailer.py ship as examples; a worker registers tasks with @worker.task(...) and ends with worker.run(queue=...). Web and workers must point at the same local database file.
One YAML per core module. This is where configurable values (Argon2 cost, timeouts) and growing lists (the CSP, allowlists) live — edit YAML, not code. Security contracts that must never be switched off (WAL, HttpOnly cookies) stay fixed in the code on purpose. A missing key fails loudly rather than booting on a silent default.
security.yaml · database.yaml · auth.yaml · worker.yaml · mailer.yaml · registry_api.yaml
Secrets never live here — a YAML may name a secret, never hold its value. Secrets go in .env or the Fernet vault, resolved at runtime.
templates/ holds the Jinja pages and the three base layouts (layout_auth, layout_public, layout_app) that own the shell and the security contract (CSRF field, CSP nonce, no-flash theme). static/ is the design system — one CSS file, one JS file, one icon sprite, self-hosted fonts. To rebrand, override the tokens in static/css/fabbro.css; it propagates everywhere. See the Frontend Reference for the full vocabulary.
Created as you run, and kept out of git by .gitignore:
.env — your secrets (FERNET_KEY, SECRET_KEY). Never committed.
.env.local — non-secret dev infra (DB_PATH, APP_BASE_URL).
app.db — the SQLite database, created on first boot.
_dev/ holds these playbooks, the playbook server (serve.py), and skills/ + prompts/ to load into an AI agent. It's excluded from the production build — it never ships. docs/ holds the Backend and Frontend references.