enolaBlogDoes your repository already have rules?

Blog

Does your repository already have rules?

Gert6 min read

Your code already follows patterns and conventions that the team treats as working agreements, even when they aren’t written down. You learn them through code review or by asking someone who has worked on that part of the system.

We wanted to help developers find those agreements in the code and discuss which ones the team wants to preserve.

We built Enola’s constraint miner to surface dependency patterns and exceptions that developers can bring to a team discussion. In ClickHouse, that exposed concrete dependencies worth reviewing. Teams can turn boundaries they agree on into constraints checked on future changes.

We ran Enola on ClickHouse: 12,164 files, 224,617 facts, and 2,166 candidate constraints. Enola gave us concrete patterns to consider as hidden rules, along with their exceptions for the team to review.

The patterns were already there. The team discussion is what gives them a purpose: why does this boundary exist, which exceptions make sense, and should future changes respect it?

The workflow is straightforward: mine, approve, enforce.

Mine: discover the patterns in your codebase

enola constraints mine reads an existing code snapshot and looks for patterns that hold across most of a component. You can use it when joining a project, reviewing an unfamiliar subsystem, or deciding which boundaries to document.

The documentation groups mining into four families: dependency edges, naming, properties, and method presence. Edge mining produces two kinds of proposals, giving you these five patterns:

  • Components that rarely cross a particular dependency boundary.
  • Components whose dependencies concentrate on a small set of targets.
  • Naming conventions followed by most symbols under a path.
  • Fact properties that usually occur together.
  • Methods defined by nearly every plain class under a path, excluding classes with inheritance or mixins.

Dependency candidates can use imports, calls, dependencies, or implementation relationships. The available candidates depend on the facts extracted from your code.

Run it on your repository

Generate a snapshot first, then mine it:

shell
enola --generate /path/to/repo
enola constraints mine /path/to/repo

Mining reads the saved snapshot, so regenerate it when you want to assess changed code. It does not add rules or change what enola check enforces.

The terminal report shows the top 50 candidates by default. To narrow the results and save the complete report:

shell
enola constraints mine \
  --min-support 20 \
  --min-confidence 0.95 \
  --max-exceptions 20 \
  --jsonl candidates.jsonl \
  /path/to/repo

These settings require a measured population of at least 20, a pattern that holds for at least 95% of that population, and no more than 20 observed exceptions. We used these thresholds for the ClickHouse run. Use --top 0 to print every candidate; the JSONL export includes the full report regardless of the display limit.

What the report contains

Each candidate includes matching and total counts, a confidence score, observed exceptions, and a proposed YAML declaration. The JSONL export also includes sample matches. Results are ranked by confidence multiplied by population size.

A confidence of 0.95 means the pattern holds for 95% of the measured population. It measures regularity in the snapshot. You still need to decide whether the pattern deserves a rule.

What we found in ClickHouse

One candidate looked convincing: 4,019 of 4,022 import edges leaving src/Storages/ stayed outside src/Server/.

Then we checked the source.

Storage code includes server HTTP headers and CertificateReloader.h. Those dependencies have concrete uses. The graph also attributed one exception to an ACME module edge, which needed closer inspection.

We also found cases where module aggregation compressed several includes into one edge. Checking the source remains part of reviewing a candidate.

For a developer exploring this part of ClickHouse, the candidate provides a focused question: which storage features need server code, and why? Enola gives the team a starting point, with source review supplying the detail.

Approve: decide what should hold

Before adopting a candidate, the team needs to answer three questions:

  • Does this boundary reflect how we want the code to evolve?
  • Do the selectors and resolved edges describe the source accurately?
  • Can we explain why the boundary should hold?

A separate source check found three plausible import boundaries: base/**, src/DataTypes/**, and src/Processors/** contained no direct Server/ includes in the source we reviewed.

There is a reasonable case for preserving them. Base libraries, type definitions, serialization, and query processors should remain independent of server transport and lifecycle code.

ClickHouse maintainers would need to agree with that reasoning. The absence of dependencies alone cannot settle the decision.

An approved rule should explain its purpose:

yaml · rule excerpt
- id: processors-do-not-import-server
  forbid: processors
  to: server
  via: imports
  mode: ratchet
  because: "query processors must not depend on server transport or lifecycle code"

When the team agrees, save the reviewed declaration in enola-intent.yaml or a YAML file under enola/constraints/. Include the component definitions, give the rule a clear ID, and replace the generated rationale with the team’s reason. The example above is a rule excerpt; its processors and server components must also be defined.

Mined proposals start in advisory mode. Choose ratchet or strict deliberately, and reuse existing components where appropriate; duplicate component names or rule IDs are validation errors.

Validate the declaration against your snapshot:

shell
enola constraints lint /path/to/repo

This checks the declaration and resolves its component selectors, including whether they match any facts. See the constraints guide and the repository reference for the full configuration.

Approval can also mean narrowing the scope, recording a justified exception, or rejecting the candidate. Writing down the reason helps the next developer understand the boundary without having to reconstruct the original decision.

Enforce: check the rules in CI

Once the team approves a rule, Enola can enforce it on pull requests. The GitHub Action compares the pull request with its base commit and reports constraint violations introduced by the change:

.github/workflows/architecture.yml
name: Architecture
on:
  pull_request:
  merge_group:

jobs:
  enola:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0
      - uses: enola-labs/enola-action@v2
        with:
          fail-on: constraints

With ratchet mode, a new violation fails the check. The report identifies the rule, dependency, source file, and rationale, so the author and reviewer can see which agreement the change crosses.

The team can then change the code or revise the rule in the same pull request. For other CI providers, enola check --fail-on=constraints provides the gate through its exit code. See the CI guide for setup.

The working agreements we started with become easier to share once they’re written down with a reason. A developer working in an unfamiliar component can understand the boundary, and CI can flag a change that crosses it.

That’s why we built Mine. It gives you evidence for a conversation about the conventions your team already follows and which ones are worth preserving. The report makes those patterns available to everyone working on the repository.

Frequently asked questions

Does Mine add rules to my repository?

No. It reads an existing snapshot and reports candidates. Your team chooses which proposals to adopt; mining does not change CI enforcement.

What does the confidence score mean?

It is the share of the measured population that follows the pattern. A score of 0.95 means 95% of that population conforms. The score helps you assess a pattern’s consistency; the team decides whether it should become a rule.

Do I need to write constraints before trying Mine?

No. Generate a snapshot with enola --generate /path/to/repo, then run enola constraints mine /path/to/repo. The constraints guide explains how the proposals relate to declared rules.

Can approved rules run outside GitHub Actions?

Yes. Other CI systems can use enola check --fail-on=constraints and its exit code to gate changes. The CI guide covers baseline setup.

enola is open source under Apache 2.0. Free, local, no data leaves your machine.

shell
curl -fsSL https://raw.githubusercontent.com/enola-labs/enola/main/install.sh | sh