Skip to content
Muhammet Şafak
tr
Journal 2 min read

archlint: Enforcing Architecture Boundaries in CI, Deterministically

Architecture decisions stay true on the wiki and rot in the code. archlint enforces the boundaries in architecture.json on every commit, no model.

Cover — a rack-mounted server badged archlint, its violet screen reading archlint check: FAILED and pkg/adapters -> pkg/domain

I wrote separately about why architecture decisions rot: an ADR says “the domain must not import infrastructure,” and two years later you find it importing the database in a hundred places — because nothing was watching the rule. This post is the tooling side of that problem: a small, standalone Go CLI — archlint.

architecture.json: put the rule in code

You declare the layers and their allowed dependencies in one file:

{
  "module": "github.com/acme/app",
  "layers": {
    "domain": ["internal/domain"],
    "db":     ["internal/db"],
    "http":   ["internal/http"]
  },
  "rules": {
    "domain": [],
    "db":     ["domain"],
    "http":   ["domain", "db"]
  }
}

domain may import nothing internal, db may import only domain, http may import both. Any other internal edge is a violation. A same-layer import is always allowed; [] means “may import no other layer.”

How it works

archlint check extracts every .go file’s imports with the standard library’s go/parser — no regex guessing, no compilation needed, exact. It maps each file and each import (after stripping the module path) to a layer, and reports the edge that breaks the rule, with file and line:

$ archlint check examples/sample
Scanned examples/sample against examples/sample/architecture.json — 2 layer(s).

1 boundary violation(s):
  internal/domain/bad.go:6  domain → db is not allowed  (import "github.com/acme/app/internal/db")

A violation exits 1 → the build goes red. The offending import is caught before it reaches main, not in an archaeology session two years later.

Design decisions

  • Deterministic, no model. The whole point is a guardrail you can gate CI on: same diff, same verdict, no model in the loop. That’s the difference from telling an LLM to “review the architecture.”
  • Zero dependencies (Go stdlib). The config is JSON for now — YAML adds a dependency, so it’s a follow-up.
  • Go first. go/parser gives Go imports exactly; a sharp MVP is done well in one language. TypeScript and Python, via their own parsers, are the next step.

In CI

From here on it’s the usual two lines of the workflows I build with GitHub Actions; all archlint asks is to be able to fail the job by returning a non-zero exit code on a violation.

- run: go install github.com/muhammetsafak/archlint/cmd/archlint@latest
- run: archlint check

Or with the packaged GitHub Action:

- uses: muhammetsafak/archlint@v0.3.0

Limits — the honest list

  • Go, for now. Imports are read with go/parser (exact); TS/Python via their own parsers is next.
  • Import boundaries, for now. It governs the dependency graph between layers. A synchronous call where an async one was required, or a direct DB query across a domain boundary, needs correlating runtime traces — a later phase.
  • JSON config (YAML is a follow-up). The deterministic design is deliberate: it has to be gate-able.

Try it

Write your architecture.json, run archlint check — or run archlint check examples/sample to watch it catch a planted violation. If you tell me which boundary you want next (TS? Python?), I’ll queue it.

Experiments on this topic

A frozen JSON envelope in place of language-specific serialization; it became BabelQueue, a polyglot queue standard.

What it does today

The frozen JSON envelope has four languages — PHP, Python, Go, Node.js — reading the same bytes, with no sidecar and no broker plugin. Teams running a polyglot queue can pick up the spec and the SDKs today.

Open source JSON Redis RabbitMQ +4 more
March 2026 — June 2026

A provider-agnostic Go CLI that has a model review my diff without the diff leaving the machine; it became CommitBrief.

What it does today

Reviews code locally, with the diff never leaving the machine; the `Provider` interface lets Anthropic, OpenAI, Gemini and Ollama plug into one contract. Signed binaries are published — anyone who does not want to hand their diff to a cloud provider can install it.

Open source Go Cobra go-git +4 more
January 2026 — April 2026
Share:

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

Related Posts

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind