enola
Catches architecture drift in CI, so agent-written code keeps to the rules your team agreed on.
- Role
- Core contributor
- Project
- enola-labs/enola, Apache-2.0
- My part
- 44 merged pull requests, August to October 2026: the constraints program, laws in Ruby, Rails and Ember support
- Gems
- enola, enola-rb, enola-guides and munola, which I maintain
$ enola check --fail-on=constraints .
FAIL — 2 structural regressions introduced.
could not see: 0 files and 1 directory excluded by ignore globs
law: 1 rule · 2 breaches · none excused
Regressions (fail):
- [constraints] 1.00 — Constraint background-jobs-never-invoke-controller-code violated: OrderReportJob#perform -> OrdersController via calls
forbidden calls edge
app/jobs/order_report_job.rb:2
def perform(order_id)
^^^^^^^^^^^^^^^^^^^^^
- [constraints] 1.00 — Constraint background-jobs-never-invoke-controller-code violated: OrderReportJob#perform -> OrdersController.summary_for via calls
forbidden calls edge
Agent-written code drifts from the architecture you agreed on.
A coding agent can write a change that looks right and still crosses a boundary nobody wrote down: a job that calls a controller, one module writing another’s tables. Review catches some of it, late. Linters judge one file at a time and know nothing about your architecture.
enola builds one graph of the whole system from the code, on your machine, with no AI model and no upload. Pin a baseline, and enola check reports only what a change introduced since then, so the problems a repository already had stay out of the report. Agents ask the same graph over MCP before they touch code, and a hook runs the check before they say they are done.
What I built in it.
-
Architectural law, declared over the graph.
The constraints program is mine: a team declares the rules of its architecture, and enola checks them against the graph, the same way every time, in CI and before an edit.
enola plantells an agent what governs a file before it writes there,constraints mineproposes laws from the code a team already has, and a shareable history store powersblameanddiff. It landed as a stack of five pull requests and shipped in v0.4.0, “Declared architectural law”.$ enola plan -paths app/billing/charge.rb .
Plan report for shop Target app/billing/charge.rb (path) No measured fact lives at this path — governance below covers code about to be written. Component billing (declared in enola/constraints/billing-is-documented.rb) rule billing-is-documented [ratchet]: every file of billing must be governed by a compiled page because: code under a decision needs the page that decided it rule billing-keeps-to-its-own-tables [ratchet]: billing must reach only the tables it owns because: a part that writes another part's table owns a bug it cannot see A report, never a gate: these verdicts are for the caller to weigh before editing.Real output, on a copy of the rails-shop example from enola-guides, before the file exists. -
Laws written as Ruby sentences.
A law reads like the rule a team would say out loud, with the reason next to it. Twenty-one rule forms, laws over the whole graph, a date a law starts from, and a ratchet mode that reports the breaches already there and fails only new ones. It was measured on a hundred-repository corpus before it shipped in v0.4.4.
enola/constraints/background-jobs-never-invoke-controller-code.rb
Enola.architecture "shop" do rails law "background jobs never invoke controller code" do jobs.must_not_call controllers why "rendering from a job goes through ApplicationController.renderer" mode :ratchet end end
-
Findings where CI reads them, and what enola could not see.
Verdicts come out as text, JSON, SARIF or CI annotations, so a breach shows up where CI and reviewers already look. Every report also says what it could not see, like the excluded directory in the run above, so silence is never mistaken for a pass.
-
Rails, Ember and Ruby, read faithfully.
Ember and Glimmer extraction, so an Ember app has a route graph and resolver joins. Rails extraction with the
dead-methodsandquery-loopsexplainers. The fact-provider seam that lets Prism and Rubydex read Ruby beside enola’s own extractors, and in October a wave of fixes to how Ruby is resolved. Declared intent compiles into the graph, so what a team decided is checked against what the code does. -
The gems that bring it to Rails teams.
I maintain the Ruby side: enola, a Ruby wrapper around the binary; enola-rb, the Rails layer, with
rails generate enola:installand rake tasks to snapshot and check; enola-guides, the guides, laws, skills and hooks; and munola, my own channel with a recipe catalogue bound by detection. From a freshrails newto a caught breach takes about five seconds.
How I work in open source.
Spec- and ADR-first, in direct collaboration with the project’s maintainer. Big features arrive as stacks of pull requests a reviewer can follow one at a time, measured before they ship, and the work keeps going after the release: 44 pull requests merged between August and October 2026.
The project’s README credits me as “the author of a large part of what this repository does.”
- enola on GitHubenola-labs/enola, Apache-2.0
- enola.techthe project's site
- enola-rbmy Rails and Bundler gems for enola
- enola-guidesmy guides for working this way
- How I work with knowledge basesthe practice enola is part of