Skip to main content

Architecture Checker — static analysis for layered architecture rules

Project description

mille

Architecture Checker — static analysis CLI for layered architecture rules

mille is a CLI tool that enforces dependency rules for layered architectures (Clean Architecture, Onion Architecture, Hexagonal Architecture, etc.).

It is implemented in Rust, supports multiple languages from a single TOML config, and is designed to run in CI/CD pipelines.

Features

Feature Status
Internal layer dependency check (dependency_mode)
External library dependency check (external_mode)
DI entrypoint method call check (allow_call_patterns)
Rust support
Go support
Python support
TypeScript / JavaScript support

How to Install

cargo (Rust users)

cargo install mille

pip / uv (Python users)

# uv (recommended)
uv add --dev mille
uv run mille check

# pip
pip install mille
mille check

The Python package is a native extension built with maturin (PyO3). It provides both a CLI (mille check) and a Python API (import mille; mille.check(...)).

npm (Node.js users)

npm install -g @makinzm/mille
mille check

Or use it without installing globally:

npx @makinzm/mille check

Requires Node.js ≥ 18. The npm package bundles mille.wasm (the compiled Rust core) and runs it via Node.js's built-in node:wasi module — no native compilation or network access required at install time.

go install

go install github.com/makinzm/mille/packages/go/mille@latest

The Go wrapper embeds mille.wasm (the compiled Rust core) and runs it via wazero — a zero-dependency WebAssembly runtime. No network access or caching required; the binary is fully self-contained.

Direct binary download

Pre-built binaries for each platform are available on GitHub Releases:

Platform Archive
Linux x86_64 mille-<version>-x86_64-unknown-linux-gnu.tar.gz
Linux arm64 mille-<version>-aarch64-unknown-linux-gnu.tar.gz
macOS x86_64 mille-<version>-x86_64-apple-darwin.tar.gz
macOS arm64 mille-<version>-aarch64-apple-darwin.tar.gz
Windows x86_64 mille-<version>-x86_64-pc-windows-msvc.zip
# Example: Linux x86_64
curl -L https://github.com/makinzm/mille/releases/latest/download/mille-<version>-x86_64-unknown-linux-gnu.tar.gz | tar xz
./mille check

Quick Start

1. Create mille.toml

Place mille.toml in your project root:

Rust project example:

[project]
name      = "my-app"
root      = "."
languages = ["rust"]

[[layers]]
name            = "domain"
paths           = ["src/domain/**"]
dependency_mode = "opt-in"
allow           = []
external_mode   = "opt-in"
external_allow  = []

[[layers]]
name            = "usecase"
paths           = ["src/usecase/**"]
dependency_mode = "opt-in"
allow           = ["domain"]
external_mode   = "opt-in"
external_allow  = []

[[layers]]
name            = "infrastructure"
paths           = ["src/infrastructure/**"]
dependency_mode = "opt-out"
deny            = []
external_mode   = "opt-out"
external_deny   = []

[[layers]]
name            = "main"
paths           = ["src/main.rs"]
dependency_mode = "opt-in"
allow           = ["domain", "infrastructure", "usecase"]
external_mode   = "opt-in"
external_allow  = ["clap"]

  [[layers.allow_call_patterns]]
  callee_layer  = "infrastructure"
  allow_methods = ["new", "build", "create", "init", "setup"]

Python project example:

[project]
name      = "my-python-app"
root      = "."
languages = ["python"]

[resolve.python]
src_root      = "."
package_names = ["domain", "usecase", "infrastructure"]

[[layers]]
name            = "domain"
paths           = ["domain/**"]
dependency_mode = "opt-in"
allow           = []
external_mode   = "opt-out"
external_deny   = []

[[layers]]
name            = "usecase"
paths           = ["usecase/**"]
dependency_mode = "opt-in"
allow           = ["domain"]
external_mode   = "opt-out"
external_deny   = []

[[layers]]
name            = "infrastructure"
paths           = ["infrastructure/**"]
dependency_mode = "opt-out"
deny            = []
external_mode   = "opt-out"
external_deny   = []

TypeScript / JavaScript project example:

[project]
name      = "my-ts-app"
root      = "."
languages = ["typescript"]

[resolve.typescript]
tsconfig = "./tsconfig.json"

[[layers]]
name            = "domain"
paths           = ["domain/**"]
dependency_mode = "opt-in"
allow           = []
external_mode   = "opt-out"
external_deny   = []

[[layers]]
name            = "usecase"
paths           = ["usecase/**"]
dependency_mode = "opt-in"
allow           = ["domain"]
external_mode   = "opt-in"
external_allow  = ["zod"]

[[layers]]
name            = "infrastructure"
paths           = ["infrastructure/**"]
dependency_mode = "opt-out"
deny            = []
external_mode   = "opt-out"
external_deny   = []

Use languages = ["javascript"] for plain .js / .jsx projects (no [resolve.typescript] needed).

Go project example:

[project]
name      = "my-go-app"
root      = "."
languages = ["go"]

[resolve.go]
module_name = "github.com/myorg/my-go-app"

[[layers]]
name            = "domain"
paths           = ["domain/**"]
dependency_mode = "opt-in"
allow           = []

[[layers]]
name            = "usecase"
paths           = ["usecase/**"]
dependency_mode = "opt-in"
allow           = ["domain"]

[[layers]]
name            = "infrastructure"
paths           = ["infrastructure/**"]
dependency_mode = "opt-out"
deny            = []

[[layers]]
name            = "cmd"
paths           = ["cmd/**"]
dependency_mode = "opt-in"
allow           = ["domain", "usecase", "infrastructure"]

2. Run mille check

mille check

Exit codes:

Code Meaning
0 No violations
1 One or more errors detected
3 Configuration file error

Configuration Reference

[project]

Key Description
name Project name
root Root directory for analysis
languages List of languages to check (e.g. ["rust", "go"])

[[layers]]

Key Description
name Layer name
paths Glob patterns for files belonging to this layer
dependency_mode "opt-in" (deny all except allow) or "opt-out" (allow all except deny)
allow Layers allowed as dependencies (when dependency_mode = "opt-in")
deny Layers forbidden as dependencies (when dependency_mode = "opt-out")
external_mode "opt-in" or "opt-out" for external library usage
external_allow Regex patterns of allowed external packages (when external_mode = "opt-in")
external_deny Regex patterns of forbidden external packages (when external_mode = "opt-out")

[[layers.allow_call_patterns]]

Restricts which methods may be called on a given layer's types. Only valid on the main layer.

Key Description
callee_layer The layer whose methods are being restricted
allow_methods List of method names that are permitted

[resolve.typescript]

Key Description
tsconfig Path to tsconfig.json. When specified, mille reads compilerOptions.paths and resolves path aliases (e.g. @/*) as internal imports.

How TypeScript / JavaScript imports are classified:

Import Classification
import X from "./module" (starts with ./) Internal
import X from "../module" (starts with ../) Internal
import X from "@/module" (path alias in tsconfig.json) Internal
import X from "react" (npm package) External
import fs from "node:fs" (Node.js built-in) External

For relative imports, mille resolves the path from the importing file and matches it against layer glob patterns. For example, import { User } from "../domain/user" in usecase/user_usecase.ts resolves to domain/user, matching the layer glob domain/**.

For path aliases, mille expands the alias using compilerOptions.paths and treats the result as an internal import. For example, with "@/*": ["./src/*"], import { User } from "@/domain/user" resolves to src/domain/user.

[resolve.go]

Key Description
module_name Go module name (matches the module path in go.mod)

[resolve.python]

Key Description
src_root Root directory of the Python source tree (relative to mille.toml)
package_names List of your own package names (used to classify absolute imports as internal). e.g. ["domain", "usecase", "infrastructure"]

How Python imports are classified:

Import Classification
from .sibling import X (relative) Internal
import domain.entity (matches a package_names entry) Internal
import os, import sqlalchemy (others) External

Python API

In addition to the CLI, the Python package exposes a programmatic API:

import mille

# Run architecture check and get a result object
result = mille.check("path/to/mille.toml")  # defaults to "mille.toml"

print(f"violations: {len(result.violations)}")
for v in result.violations:
    print(f"  {v.file}:{v.line}  {v.from_layer} -> {v.to_layer}  ({v.import_path})")

for stat in result.layer_stats:
    print(f"  {stat.name}: {stat.file_count} file(s), {stat.violation_count} violation(s)")

Types exposed:

Class Attributes
CheckResult violations: list[Violation], layer_stats: list[LayerStat]
Violation file, line, from_layer, to_layer, import_path, kind
LayerStat name, file_count, violation_count

How it Works

mille uses tree-sitter for AST-based import extraction — no regex heuristics. The core engine is language-agnostic; language-specific logic is isolated to the parser and resolver layers.

mille.toml
    │
    ▼
Layer definitions
    │
Source files (*.rs, *.go, *.py, ...)
    │ tree-sitter parse
    ▼
RawImport list
    │ Resolver (stdlib / internal / external)
    ▼
ResolvedImport list
    │ ViolationDetector
    ▼
Violations → terminal output

Dogfooding

mille checks its own source code on every CI run:

mille check   # uses ./mille.toml

See mille.toml for the architecture rules applied to mille itself.

Documentation

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mille-0.0.9.tar.gz (1.1 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mille-0.0.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.8manylinux: glibc 2.17+ x86-64

File details

Details for the file mille-0.0.9.tar.gz.

File metadata

  • Download URL: mille-0.0.9.tar.gz
  • Upload date:
  • Size: 1.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.12.6

File hashes

Hashes for mille-0.0.9.tar.gz
Algorithm Hash digest
SHA256 7ba3e8869b25228ffc258e7ae95cfd547b64c5b90696bba9751da331374dc3a8
MD5 9310ca986c646c8fbf8be06a0969cb2d
BLAKE2b-256 d870bf7ff3bd2457d0e5317d9aae90e431eec787fdcb9f15118af4f16f10fbe6

See more details on using hashes here.

File details

Details for the file mille-0.0.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for mille-0.0.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 c09cdf06f7352f4f648034107083643a4a4abac77ee8e6c5afb0bb8ae83b3e94
MD5 2e436715d906c152b05ab07840b2c15a
BLAKE2b-256 dcbd89132b97467e83568257d26dc2fb36d3483990cc7e6fbadc7ff40b14717d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page