One architecture rule, three languages: archlint now does Go + TypeScript + Python
In a polyglot repo the rule is the same in every language; one architecture.json now enforces Go, TypeScript and Python boundaries in CI.
When I first built archlint it only spoke Go. But a real system isn’t one language: a Go API, a TypeScript frontend, Python workers. All three share the same architectural intent — “the domain layer must not import infrastructure” — yet there was no single tool enforcing that rule across all of them. Now archlint governs all three, from one architecture.json.
The boundary isn’t language-specific; the syntax is
At the layer level the rule is identical in every language: domain → db is forbidden. What differs between languages isn’t the rule, it’s how an import is written and which file it resolves to:
- Go: an absolute package path —
github.com/acme/app/internal/db. Resolve it by stripping the module prefix →internal/db. - TypeScript: relative (
../db/repo) or aliased (@/db/repo). Resolve the relative one against the file’s location, the alias through thealiasesmap inarchitecture.json. - Python: dotted and relative —
from ..db.repo import find_order. Resolve the leading dots against the file’s package, then map the module to a path.
archlint reduces all of them to one thing: a repo-relative path → a layer. From there the engine is the same: “which layer is this file in, which layer does this import reach, do the rules allow it?”
One config, mixed repo
The nice part: a single file can govern a three-language monorepo. Layer names are shared; the paths are language-specific:
{
"module": "github.com/acme/app",
"aliases": { "@/": "web/src/" },
"layers": {
"domain": ["internal/domain", "web/src/domain", "workers/app/domain"],
"infra": ["internal/infra", "web/src/infra", "workers/app/infra"]
},
"rules": { "domain": [], "infra": ["domain"] }
}
The domain layer exists in all three services (Go internal/, TS web/src/, Python workers/app/) and none of them may import infrastructure. One rule, three languages, one CI gate.
How it works: a pluggable-language design
Inside there’s a Language interface: each language knows only two things — how to extract a file’s imports and how to resolve an import to a repo-relative path. The rest of the linter is language-agnostic. Adding a language = adding one Language implementation.
The discipline held: instead of tree-sitter (a heavy CGo dependency), Go is read with the stdlib go/parser (exact), and TS and Python with regex scanners. The honest limit: those aren’t full parsers — an import-like line inside a comment or string can be a false positive. The README says so plainly.
Try it: three examples
Each plants a deliberate violation:
archlint check examples/sample # Go → internal/domain/bad.go
archlint check examples/ts-sample # TS → src/domain/bad.ts (resolves the "@/" alias)
archlint check examples/py-sample # Python → app/domain/bad.py (resolves "..db.repo")
All three print the same thing: domain → db is not allowed, exit 1, build red.
Why this beats a wiki page I argued separately on sade.dev: a written architecture decision rots unless something checks it on every commit. Now that “something” runs in three languages at once. Tell me which language the next boundary should be — I’ll queue it.
Experiments on this topic
A 4X that ends in one sitting: the constraint chose the scope
A dice-free strategy core that resolves 480 hexes in 60 turns and runs offline in the browser; it shipped as Regnarium.
What it does today
Runs a full 4X match on a 480-hex map in at most 60 turns; rules, map generation and the opponent AI live in one pure TypeScript core, and combat rolls no dice. Anyone with half an hour and an appetite for strategy can open it in a browser.
Two languages sharing one queue without the serialization lock-in
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.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.