English · Deutsch
Cross-language hexagonal-architecture checker — deterministic, side-effect-free, text-heuristic, shipped as a container image.
a-check enforces a repository's hexagonal layered architecture across languages, driven by a single config file. Seven universal rules, each one requirement in the requirements spec:
core-impurity— the core (domain, innermost layer) imports neither a port, an adapter, nor a framework/tech (AC-FA-RULE-001)lateral-adapter— an adapter imports no other adapter (except the shared sink) (AC-FA-RULE-002)tech-leak— a framework/tech appears only in its adapter (AC-FA-RULE-003)port-impurity— a port imports no adapter and no framework/tech (it may reference the core's domain types) (AC-FA-RULE-004)wrong-direction— layer edges are one-way (AC-FA-RULE-005)app-impurity— the application layer imports no adapter and no framework/tech (it may use the domain and ports) (AC-FA-RULE-007)port-direction-mismatch— an adapter talks only to ports of its direction (optionaldriving/drivendimension, orthogonal to the role) (AC-FA-RULE-008)
Imports are extracted text-heuristically per language (C++/Go/Rust/Kotlin/Java/Python/C#/TypeScript)
(AC-FA-EXTRACT-001).
Every finding names the file, line, rule and reason; exit codes: 0 clean, 1 findings,
2 usage/config error
(AC-FA-CLI-001).
Four functionally overlapping arch-check.sh variants grew across the sibling repositories —
C++ via #include heuristics (b-cad), Go via go list (d-check), Rust via use heuristics
(grid-guide), Kotlin via Gradle module boundaries (d-migrate): four languages, four
mechanisms, the same seven rules. a-check replaces them with one tool:
- Configuration instead of fork: repo-specific layer/tech rules live declaratively in
.a-check.yml(AC-FA-CONF-001), not in copied scripts. - One distribution path: a digest-pinned container image plus a shipped
a-check.mkinstead of N maintained copies (AC-FA-DIST-001).
It is the architecture counterpart to d-check (doc references): the same founding logic
(replace a family of drifting scripts with one tool), one abstraction level higher.
Architecture is an import graph with checkable invariants. Whether the core stays pure, an adapter imports laterally, or a layer edge runs against its direction is machine-decidable — a-check turns these invariants into a gate instead of a review opinion.
The rule is report, never repair: a-check is a pure read-only tool. The heuristic boundary (text-based, not a full parser per language) is disclosed, not hidden; an allowlist/marker exception is configurable (AC-QA-02).
- Determinism: identical input ⇒ byte-identical, stably sorted output (AC-QA-01).
- Hermetic & network-less: never writes into the checked repo, runs with
--network noneon distroless/static (AC-QA-02). - No silent defaults: any invalid
.a-check.ymlaborts with exit 2 (strict decode, AC-FA-CONF-001). - Reproducible: the image and
a-check.mkreference a@sha256:digest (AC-QA-03). - Dogfooding: a-check checks its own hexagonal architecture on every
make arch-check— against the self-configuration, 0 findings.
Against the published image (digest-pinned, network-less, read-only):
docker run --rm --network none -v "$PWD:/src:ro" \
ghcr.io/pt9912/a-check@sha256:aef28cfe25bb054b1b0eb28420222a45b9f6ce9425b7ffd0f55e6ae56f295b56 /srcConsumers wire a-check in as a make a-check gate — without a script copy: the shipped
a-check.mk (produced by a-check --print-mk) is include-d, plus an
.a-check.yml. A_CHECK_IMAGE is pinned to the release's @sha256: digest
(AC-FA-DIST-001,
AC-QA-03); raising the pin is a deliberate
commit. The release process is described in docs/user/releasing.md.
Locally (dogfooding, no pull):
make build # build the static/distroless image
make arch-check # a-check checks itself (network-less, read-only)In the repo root; strictly decoded (unknown key ⇒ exit 2):
version: 1
languages:
go: ["**/*.go"]
layers:
core: ["internal/core/**"]
ports: ["internal/ports/**"]
adapters: ["internal/adapters/**"]
edges:
- {from: adapters, to: ports}
- {from: ports, to: core} # ports may reference domain types
# - {from: adapters, to: core} # if adapters reference domain types directly
tech:
- {pattern: "net/http", adapter: adapters/http} # substring (default)
# - {pattern: "Q[A-Za-z]", adapter: adapters/ui, match: regex} # RE2 regex (e.g. Qt)The full schema is in the
specification §SPEC-CONF-001;
a living example is this repo's self-configuration.
a-check --print-config prints a commented skeleton.
| Document | Content |
|---|---|
docs/user/benutzerhandbuch.md |
User manual (German) — installation, usage, .a-check.yml, troubleshooting |
docs/user/releasing.md |
Release process — tagging, GHCR, digest pin |
spec/lastenheft.md |
Requirements (AC-FA-*, AC-QA-*), acceptance criteria |
spec/spezifikation.md |
Algorithms, .a-check.yml schema, exit codes (SPEC-*) |
spec/architecture.md |
Hexagon components and roles (ARC-*) |
docs/plan/adr/README.md |
Architecture decisions (ADR index) |
harness/README.md |
Harness entry: source precedence, guides, sensors |
AGENTS.md |
Briefing for AI coding agents, hard rules |
CHANGELOG.md |
Change history |
The specification, requirements and user manual are maintained in German. The
AC-*/SPEC-*/ARC-*anchors above therefore point to German headings.
The host needs only git, GNU make, bash and Docker
(AGENTS.md §3.1).
make help # available targets
make gates # all inner gates (lint/test/coverage-gate/arch-check/doc-check/gate-consistency/guard-selftest)
make ci # gates + image-test (CI-equivalent); make trace-check checks commit IDsThis project is licensed under the MIT License.