Architecture Checker — static analysis for layered architecture rules
Project description
mille
Like a mille crêpe — your architecture, one clean layer at a time.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ presentation
· · · · · · · · · · · · · · · · · · (deps only flow inward)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ infrastructure
· · · · · · · · · · · · · · · · · ·
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ usecase
· · · · · · · · · · · · · · · · · ·
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ domain
mille is a static analysis CLI that enforces dependency rules for layered architectures — Clean Architecture, Onion Architecture, Hexagonal Architecture, and more.
One TOML config. Rust-powered. CI-ready. Supports multiple languages from a single config file.
What it checks
| Check | Rust | Go | TypeScript | JavaScript | Python |
|---|---|---|---|---|---|
Layer dependency rules (dependency_mode) |
✅ | ✅ | ✅ | ✅ | ✅ |
External library rules (external_mode) |
✅ | ✅ | ✅ | ✅ | ✅ |
DI method call rules (allow_call_patterns) |
✅ | ✅ | ✅ | ✅ | ✅ |
Install
cargo
cargo install mille
npm
npm install -g @makinzm/mille
mille check
Or without installing globally:
npx @makinzm/mille check
Requires Node.js ≥ 18. Bundles mille.wasm — no native compilation needed.
go install
go install github.com/makinzm/mille/packages/go/mille@latest
Embeds mille.wasm via wazero — fully self-contained binary.
pip / uv
# uv (recommended)
uv add --dev mille
uv run mille check
# pip
pip install mille
mille check
Binary download
Pre-built binaries are 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 |
Quick Start
1. Generate mille.toml with mille init
mille init
mille init analyzes actual import statements in your source files to infer layer structure and dependencies — no predetermined naming conventions needed. It prints the inferred dependency graph before writing the config:
Detected languages: rust
Scanning imports...
Using layer depth: 2
Inferred layer structure:
domain ← (no internal dependencies)
usecase → domain
external: anyhow
infrastructure → domain
external: serde, tokio
Generated 'mille.toml'
| Flag | Default | Description |
|---|---|---|
--output <path> |
mille.toml |
Write config to a custom path |
--force |
false | Overwrite an existing file without prompting |
--depth <N> |
auto | Layer detection depth from project root |
--depth and auto-detection: mille init automatically finds the right layer depth by trying depths 1–6, skipping common source-layout roots (src, lib, app, etc.), and selecting the first depth that yields 2–8 candidate layers. For a project with src/domain/entity, src/domain/repository, src/usecase/ — depth 2 is chosen, rolling entity and repository up into domain. Use --depth N to override when auto-detection picks the wrong level.
The generated config includes allow (inferred internal dependencies) and external_allow (detected external packages) per layer. After generating, review the config and run mille check to see results.
2. (Or) Create mille.toml manually
Place mille.toml in your project root:
Rust:
[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"]
TypeScript / JavaScript:
[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/.jsxprojects (no[resolve.typescript]needed).
Go:
[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"]
Python:
[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 = []
2. Run mille check
mille check
Output formats:
mille check # human-readable terminal output (default)
mille check --format github-actions # GitHub Actions annotations (::error file=...)
mille check --format json # machine-readable JSON
Exit codes:
| Code | Meaning |
|---|---|
0 |
No violations |
1 |
One or more violations detected |
3 |
Configuration file error |
Configuration Reference
[project]
| Key | Description |
|---|---|
name |
Project name |
root |
Root directory for analysis |
languages |
Languages to check: "rust", "go", "typescript", "javascript", "python" |
[[layers]]
| Key | Description |
|---|---|
name |
Layer name |
paths |
Glob patterns for files in this layer |
dependency_mode |
"opt-in" (deny all except allow) or "opt-out" (allow all except deny) |
allow |
Allowed layers (when dependency_mode = "opt-in") |
deny |
Forbidden layers (when dependency_mode = "opt-out") |
external_mode |
"opt-in" or "opt-out" for external library usage |
external_allow |
Allowed external packages (when external_mode = "opt-in") |
external_deny |
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 (or equivalent DI entrypoint).
| Key | Description |
|---|---|
callee_layer |
The layer whose methods are being restricted |
allow_methods |
List of method names that are permitted |
[ignore]
Exclude files from the architecture check entirely, or suppress violations for test/mock files.
| Key | Description |
|---|---|
paths |
Glob patterns — matching files are excluded from collection and not counted in layer stats |
test_patterns |
Glob patterns — matching files are still counted in layer stats but their imports are not violation-checked |
[ignore]
paths = ["**/mock/**", "**/generated/**", "**/testdata/**"]
test_patterns = ["**/*_test.go", "**/*.spec.ts", "**/*.test.ts"]
When to use paths vs test_patterns:
paths: Files that should not be analyzed at all (generated code, vendor directories, mocks)test_patterns: Test files that intentionally import across layers (e.g., integration tests that import both domain and infrastructure)
[resolve.typescript]
| Key | Description |
|---|---|
tsconfig |
Path to tsconfig.json. 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" |
Internal |
import X from "../module" |
Internal |
import X from "@/module" (path alias in tsconfig.json) |
Internal |
import X from "react" |
External |
import fs from "node:fs" |
External |
[resolve.go]
| Key | Description |
|---|---|
module_name |
Go module name (matches go.mod) |
[resolve.python]
| Key | Description |
|---|---|
src_root |
Root directory of the Python source tree (relative to mille.toml) |
package_names |
Your package names — imports starting with these are classified as internal. e.g. ["domain", "usecase"] |
How Python imports are classified:
| Import | Classification |
|---|---|
from .sibling import X (relative) |
Internal |
import domain.entity (matches package_names) |
Internal |
import os, import sqlalchemy |
External |
How it Works
mille uses tree-sitter for AST-based import extraction — no regex heuristics.
mille.toml
│
▼
Layer definitions
│
Source files (*.rs, *.go, *.py, *.ts, *.js, ...)
│ 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
- spec.md — Full specification (in Japanese)
- docs/TODO.md — Development roadmap
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mille-0.0.10.tar.gz.
File metadata
- Download URL: mille-0.0.10.tar.gz
- Upload date:
- Size: 1.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eb1561ebea811b2f128419cb3eb591213fdffa2b4f4e825f417710a9755d1a4b
|
|
| MD5 |
9c8de8d1095a04053a73f439cbba3a49
|
|
| BLAKE2b-256 |
3293c923331c565e7e50a3dc0be0ad534a1a5ad46e3c3366a19f434452291a24
|
File details
Details for the file mille-0.0.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: mille-0.0.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 1.4 MB
- Tags: CPython 3.8, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ced228cbf9f88bf155cf86abcc7d2b41bf6026dd10a05965b6a98c78e6800294
|
|
| MD5 |
7fd5c4d7f55c56d53e28f6bf3aa90500
|
|
| BLAKE2b-256 |
8993f85043fb17958ee74c57dc1268075e9e8a207018e5bf487f7c3293c7ad9e
|