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
npm
Or without installing globally:
Requires Node.js ≥ 18. Bundles mille.wasm — no native compilation needed.
go install
Embeds mille.wasm via wazero — fully self-contained binary.
pip / uv
# uv (recommended)
# pip
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 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:
[]
= "my-app"
= "."
= ["rust"]
[[]]
= "domain"
= ["src/domain/**"]
= "opt-in"
= []
= "opt-in"
= []
[[]]
= "usecase"
= ["src/usecase/**"]
= "opt-in"
= ["domain"]
= "opt-in"
= []
[[]]
= "infrastructure"
= ["src/infrastructure/**"]
= "opt-out"
= []
= "opt-out"
= []
[[]]
= "main"
= ["src/main.rs"]
= "opt-in"
= ["domain", "infrastructure", "usecase"]
= "opt-in"
= ["clap"]
[[]]
= "infrastructure"
= ["new", "build", "create", "init", "setup"]
TypeScript / JavaScript:
[]
= "my-ts-app"
= "."
= ["typescript"]
[]
= "./tsconfig.json"
[[]]
= "domain"
= ["domain/**"]
= "opt-in"
= []
= "opt-out"
= []
[[]]
= "usecase"
= ["usecase/**"]
= "opt-in"
= ["domain"]
= "opt-in"
= ["zod"]
[[]]
= "infrastructure"
= ["infrastructure/**"]
= "opt-out"
= []
= "opt-out"
= []
Use
languages = ["javascript"]for plain.js/.jsxprojects (no[resolve.typescript]needed).
Go:
[]
= "my-go-app"
= "."
= ["go"]
[]
= "github.com/myorg/my-go-app"
[[]]
= "domain"
= ["domain/**"]
= "opt-in"
= []
[[]]
= "usecase"
= ["usecase/**"]
= "opt-in"
= ["domain"]
[[]]
= "infrastructure"
= ["infrastructure/**"]
= "opt-out"
= []
[[]]
= "cmd"
= ["cmd/**"]
= "opt-in"
= ["domain", "usecase", "infrastructure"]
Python:
[]
= "my-python-app"
= "."
= ["python"]
[]
= "."
= ["domain", "usecase", "infrastructure"]
[[]]
= "domain"
= ["domain/**"]
= "opt-in"
= []
= "opt-out"
= []
[[]]
= "usecase"
= ["usecase/**"]
= "opt-in"
= ["domain"]
= "opt-out"
= []
[[]]
= "infrastructure"
= ["infrastructure/**"]
= "opt-out"
= []
= "opt-out"
= []
2. Run mille check
Output formats:
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 |
[]
= ["**/mock/**", "**/generated/**", "**/testdata/**"]
= ["**/*_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:
See mille.toml for the architecture rules applied to mille itself.
Documentation
- spec.md — Full specification (in Japanese)
- docs/TODO.md — Development roadmap