Architecture as code: turning your bounded contexts into a CI gate — not a Confluence page
The architectural rules a team agreed on in some kickoff meeting almost always live in a Confluence page nobody opens. The bypass merges because nobody remembered the rule. Here's how the per-repo .codeguards/review.yml rulebook turns those agreements into something the bot enforces on every PR — with a one-line citation of which rule fired, not a generic SOLID lecture.
The Confluence-page problem
You've seen this. The team sits down in week one, draws the architecture on a whiteboard, agrees on the layered model: Controller → Action → Service → Repository → Model. Someone writes it up. Three months later there's a hot-fix on a Sunday and an emergency PR drops DB::table('invoices')->where(...)->first() straight into a controller. It merges. The rule was real, the page was real, the PR review was real — but in the moment, nobody remembered the page existed.
The next architecture review is two quarters out. By then there are eleven such bypasses, half the team agrees the rule is fine "in this case", and the rule is effectively dead. Not because anyone disagreed with it — because nothing was checking it.
The fix: write the rule down where the bot can read it
The Code review pipeline reads .codeguards/review.yml from your repo on every PR. The file declares the rules you actually want enforced — bounded contexts, dependency direction, naming conventions, the stuff you keep saying in PR review by hand.
A small example, real-shaped:
// .codeguards/review.yml version: 1 tone: "professional" rules: architecture: - id: "controller-no-direct-model" severity: "high" applies: "app/Http/Controllers/**" forbid: - "Eloquent\\Model::*" - "DB::table" why: "Reach via Action -> Service -> Repository." - id: "service-never-injects-service" severity: "medium" applies: "app/**/Services/**" forbid_inject: "app/**/Services/**" why: "Compose at the Action layer, not service-to-service." tests: - id: "new-public-service-needs-feature-test" applies: "app/**/Services/**" require: "tests/Feature/**"
Push a PR that adds DB::table() to a controller, and the bot drops a remark on the offending line:
controller-no-direct-model · high
Controller is reaching the database directly — bypasses the repository layer. Reach via Action → Service → Repository.
The remark cites your rule by id, not a generic "consider refactoring". Author, line, fingerprint and rationale all land in the audit trail.
The rulebook is in version control — that's the point
Three things follow from the rulebook being a YAML file in the repo, not a setting in our dashboard:
- It's reviewable. Adding a rule is a PR. Removing one is a PR. The team that has to live with the rule signs off on it the same way they sign off on any other code change.
- It travels with the codebase. Fork the repo, the rulebook follows. Migrate to a new SCM, the rulebook follows. The policy is a property of the codebase, not of an account in someone's SaaS.
- It explains the rejection. When a remark fires, the engineer can read the exact rule that fired, in the same repo, in the same PR view. The bot's opinion is auditable.
What the rulebook is not
It's not a linter. We don't try to compete with phpstan or rubocop; the ruleset is for the things linters can't easily express — architectural rules that need to reason across files, dependency directions, "this layer should never call that layer", "every public service needs a feature test", "this directory should never import from that directory".
It's also not a substitute for human review. The bot enforces the rules the team has already agreed on. It doesn't decide whether the change should have been made. That's still a human conversation — the bot just stops the conversation from being about the same architecture-bypass-line for the eleventh time.
Where to put it in
If your repo has a docs/architecture.md or a wiki page that says "controllers never talk to Eloquent" or "services compose at the Action layer", that page is the seed of your rulebook. Translate the three or four rules you actually enforce in PR review today into .codeguards/review.yml entries. Don't try to encode every nuance — start with what you've already been saying out loud.
Without a rulebook, Code review still does the language-default style and quality remarks. The rulebook is what makes the bot opinionated about your codebase. It's also what makes the difference between "another PR-comment bot" and a CI gate the team actually trusts.
More on the surface and pricing: the Code review product page · why this is two products on one workspace.