Boundary-First Development
Build systems that don't depend on the skill of the builder.
Boundary-First Development (BFD) is an opinionated architecture philosophy for web applications. Strict contracts, backend authority, enforced consistency — chosen over developer freedom, aesthetic elegance, and pattern purity.
This document is the why. RULES.md is the law — numbered, citable, and sized to fit in a context window.
Using It
Three files, three jobs:
- README.md — the why. For humans deciding whether this is how they want to work.
- RULES.md — the law. Twenty-eight numbered rules, a glossary, and the PR checklist. Built to be loaded into an agent's context or a junior's head, whole.
- AGENTS.md — the hookup. How to bind any agent to the rules without touching what you already have.
Agents follow BFD when the rules are in their context, not because this repo is public. And BFD is a fixed point: it gets referenced, never copied into your instruction files and edited. Your AGENTS.md can contain whatever it contains — BFD is still BFD.
Claude Code — install the plugin once; "follow BFD" then works in every project:
/plugin marketplace add ddnet-repo/boundary-first-development
/plugin install bfd@ddnet
Or from the terminal, for scripts and machine setup:
claude plugin marketplace add ddnet-repo/boundary-first-development
claude plugin install bfd@ddnet
OpenCode — point your config (global or per-project opencode.json) at the canonical rules:
{ "instructions": ["https://raw.githubusercontent.com/ddnet-repo/boundary-first-development/main/RULES.md"] }
Anything else that only reads prose files: vendor RULES.md read-only and add a one-line pointer to your existing AGENTS.md. Details in AGENTS.md.
Session scope
"Follow BFD" activates per session, not per command. Once the skill fires, the ruleset is in the agent's context and governs everything until the session ends. A fresh session needs the signal again, and the phrase doesn't have to be literal — "boundary-first," or working in a project that declares it follows BFD, fires it too.
To make the signal standing instead of spoken:
- A repo that is always BFD: one line in its
CLAUDE.md — This project follows Boundary-First Development — invoke the bfd skill. Every session in that repo starts governed.
- A person who is always BFD: the same line in
~/.claude/CLAUDE.md covers every project on the machine.
- OpenCode with the
instructions URL is already standing — the rules load with every session.
A governed session builds to the rules from the first line — contracts before implementation, conventions as it types — and self-reviews against the PR checklist before calling work done. Review findings cite rule IDs.
What This Is
This philosophy was not written by someone who dislikes creativity. It was written by someone who loves hacking things together, making code do things it was never supposed to do, and solving problems in ways that make other engineers uncomfortable. That energy is fantastic for experimentation and side projects.
It is terrible for professional delivery.
Professional software should be boring. Boring means predictable deadlines, no crunch time, no fires. Boring means a new developer or an AI agent can sit down, read the contracts, and produce correct work without needing to understand the creative vision of whoever came before them.
If a system requires good developers to function correctly, it is a bad system. A well-designed system produces correct output from average input.
Save the cleverness for where it matters. Make the system itself as boring as possible.
The Principles
1. Contracts Are the Architecture
Every module exposes an interface with strict input and output structs. What happens inside the module is nobody's business.
A share module accepts content, a platform slug, and publishing options. It returns a result in a defined struct. Inside, there may be providers for Facebook, Instagram, YouTube — each written by a different person, in a different style, at a different skill level. None of that matters. The contract is the only thing that matters.
Failure is part of the contract. Every boundary returns a result struct — ok, data, error — and errors are enumerated codes, not free text. Exceptions never cross a boundary. An exception escaping a module is not an error-handling style; it is a contract violation.
- Providers are disposable. Swap them and the system doesn't notice.
- Teams scale without coordination. Hand someone an interface definition and say "make this work."
- Code review shrinks to what matters. Does the contract hold? Does the integration test pass? Ship it.
2. The Backend Is the Only Truth