Architecture-as-code for any codebase. Define, query, and validate your project structure using .arch source files — designed for humans and AI agents alike.
AI coding agents are powerful but architecturally blind. They can write code, but they don't know where it belongs, what depends on what, or which boundaries to respect. arch gives any codebase a machine-readable architecture definition that agents read before making changes and update after.
For humans, it replaces scattered tribal knowledge with a queryable, version-controlled source of truth.
Quick install (recommended):
# Linux / macOS
curl -fsSL https://raw.githubusercontent.com/micsh/arch/main/install.sh | bash
# Windows (PowerShell)
irm https://raw.githubusercontent.com/micsh/arch/main/install.ps1 | iexOr build from source:
cargo install archPre-built binaries for all platforms are also available from GitHub Releases.
# In your project root — scaffold architecture from project structure
arch init
# Check everything is consistent
arch validate
# Who owns the "authentication" concept?
arch owns authentication
# Which source files aren't mapped to any module?
arch coverage
# Full health check (validate + coverage + drift)
arch stale
# Do actual imports match declared dependencies?
arch drift
# Do architectural rules hold against the real code?
arch fitness
# What are the dependency constraints on a module? (planning aid)
arch rules context
# Generate Mermaid container diagram
arch mermaid
# Any command with JSON output (for tooling / agent consumption)
arch drift --jsonyour-project/
architecture/
arch/
system.arch # System map: containers, rules, stories, guidance
containers/
backend.arch # Per-container module details
frontend.arch
...
llmcode/
backend/
auth.llm # Per-module AI agent documentation
...
GUIDE:
Before making code changes, read the relevant container .arch file.
After changes, update ownership and dependencies if they changed.
ENDGUIDE
SYSTEM: MyProject
DESC: A web application with API and frontend
IGNORE: tests/**; **/*.test.ts; scripts/**
CONT: backend | src/backend | REST API and business logic
CONT: frontend | src/frontend | React UI | depends_on: backend
RULE: backend-independence | no_dependency | from: backend | to: frontend
"Backend must not depend on frontend"
STORY: user-login | User submits credentials → validated → token issued → stored
frontend/login-page → backend/auth → backend/database → backend/auth
MOD: auth | file: auth/mod.rs
OWN: authentication; token-validation; session-management
DEP: backend/database
BND: Auth logic only — no direct DB queries, use database module
MOD: database | file: db/mod.rs
OWN: connection-pool; migrations; query-execution
BND: Data access only — no business logic
Run arch spec --arch to print the full .arch grammar reference in your terminal.
Run arch spec --llmcode to print the full .llm grammar reference.
| Command | Description |
|---|---|
arch init |
Scan project structure and generate initial .arch files |
arch init --deep |
Deep scan: infer modules from .csproj ProjectReference tags, Python packages |
arch validate |
Check integrity: files exist, cross-refs valid, schema correct |
arch coverage |
List source files not mapped to any module |
arch owns <concept> |
Find which module owns a concept, file, or module ID |
arch stale |
Full health check: validate + coverage + drift combined |
arch drift |
Compare declared dependencies against actual code imports |
arch fitness |
Validate architectural rules against actual code |
arch rules |
List all fitness rules from system.arch |
arch rules <module> |
Show what a module cannot depend on and what cannot depend on it |
arch stories |
Verify story flows against actual import connections |
arch context |
Print full architecture context as text (for AI agents) |
arch spec |
Show available grammar spec flags |
arch spec --arch |
Print full .arch grammar reference |
arch spec --llmcode |
Print full .llm grammar reference |
arch mermaid |
Generate Mermaid container dependency diagram |
arch mermaid --brief |
Generate diagram with IDs only (no descriptions) |
arch mermaid --stories |
Generate Mermaid flowcharts from story definitions |
arch mermaid --c4 |
Generate C4 Container diagram |
arch mermaid --svg [FILE] |
Render to SVG (requires mmdc) |
arch mermaid --update-readme |
Inject diagram into README.md between markers |
All commands except init, context, spec, and mermaid support --json for structured output.
Runs validate + coverage + drift in one pass — the single command for CI and pre-commit hooks:
$ arch stale
✅ Architecture is up to date (8 containers, all files mapped, no drift)
When issues are found, output is grouped by check:
[validate]
❌ Container 'auth': path 'src/auth' does not exist
[coverage]
📂 src/utils/helpers.rs
[drift]
⚠️ backend/api (api/mod.rs:5)
import: use crate::metrics → backend/metrics
⏰ 3 issue(s) found
Exit code 1 on validate errors or drift forbidden violations. Undeclared drift items are warnings (exit 0).
Parses actual import/open/use statements from source files, resolves them to architecture modules, and compares against declared depends_on and must_not_depend:
$ arch drift
🚫 1 forbidden dependency violation(s):
backend/database (db/mod.rs:3)
import: use crate::auth → backend/auth
⚠️ 2 undeclared dependency(ies):
backend/api (api/mod.rs:5)
import: use crate::shared → frontend/shared
backend/api (api/mod.rs:8)
import: use crate::metrics → backend/metrics
📊 12 modules scanned, 3 issue(s) found
Supports: F#, C#, Rust, TypeScript/JavaScript, Python, Go.
Rust pub use re-export resolution: When a Rust module re-exports types from a submodule via pub use submodule::* or pub use submodule::Type, arch drift recognises the re-export chain and resolves the import to the re-exporting facade module rather than producing a [broad match] false-positive. This means declaring a dependency on the facade (mod.rs) module is sufficient — you don't need to separately declare dependencies on every submodule it re-exports from.
Evaluates architecture rules from system.arch against the actual codebase:
$ arch fitness
✅ backend-independence
✅ database-leaf
❌ api-isolation — FAILED
backend/api → frontend/shared (forbidden: backend → frontend)
📋 types-purity (manual check) No function implementations — only type definitions
📊 4 rule(s): 2 passed, 1 failed, 1 manual
no_dependencyrules are checked against the real import graph — violations are errorsboundaryrules are prose constraints reported as manual-check items
Lists fitness rules and explains the dependency constraints on any module. Designed as a planning aid — run this before assigning a shared utility to a module to avoid architecture violations at implementation time:
$ arch rules context
Rules applying to: context
❌ cannot depend on: commands, scanner, cli, imports
context-independence — "Context is a shared loader — depends only on schema and resolve"
⚠️ other modules forbidden to depend on context:
schema (schema-independence — "Schema types are pure data — no logic dependencies")
imports (imports-independence — "Import extraction is a pure leaf utility — ...")
resolve (resolve-independence — "Resolution is a shared utility — ...")
$ arch rules # list all rules
$ arch rules --json # structured output
Validates that story flows in system.arch are backed by real import connections between modules:
$ arch stories
📖 trading-cycle — One complete trading cycle: fetch market data and account state...
app/orchestrator → app/data ✅
app/data → app/indicators ✅
app/indicators → app/awareness ✅
app/awareness → app/prompts ✅
app/prompts → app/ai ✅
app/ai → app/execution ✅
✅ 6/6 connections verified
📊 3/3 stories fully connected
For each consecutive pair (A→B) in a flow, stories checks that A imports from B or B imports from A (bidirectional — handles event-driven and callback patterns). Same-project modules in compiled languages (.NET, Rust) are considered implicitly connected.
When a module's file: points to a recognized entry point (__init__.py, mod.rs, index.ts, etc.) or a project file (.csproj, .fsproj, .vbproj), all source files in that directory tree are automatically covered by that module. This means you only need one module per package/directory — use OWN: for concepts, sub-modules for distinct boundary enforcement:
# Python: one module covers the entire models/ package and subpackages
MOD: models | file: models/__init__.py
OWN: market-snapshot; account-state; trade-decision; order-request
BND: Pure data structures only — no logic, no I/O
For .NET solutions, use one container per .csproj project. Point the module's file: at the .csproj — arch will recursively scan all .cs/.fs files in the project directory for imports:
# system.arch
CONT: data-processing | src/DataProcessing | Data pipeline and transformation | proj: MyApp.DataProcessing
CONT: common | src/Common | Shared utilities and types | proj: MyApp.Common
# containers/data-processing.arch
MOD: app | file: DataProcessing.Application.csproj
OWN: pipeline-orchestration; data-transforms
DEP: common/shared
For containers with many sibling projects (e.g., common/ with 30+ .csproj files), use FILES: to group multiple projects under one logical module:
# containers/common.arch
MOD: shared | file: Common.Utilities/Common.Utilities.csproj
FILES: Common.Extensions/Common.Extensions.csproj; Common.Helpers/Common.Helpers.csproj
OWN: utility-functions; extension-methods; helper-classes
BND: Shared utilities only — no business logic
Generates Mermaid diagrams from your architecture. Output is Mermaid text — paste into any Mermaid-compatible renderer (GitHub, VS Code, Mermaid Live Editor).
# Container-level dependency diagram with module subgraphs
arch mermaid
# Compact version with IDs only
arch mermaid --brief
# C4 Container diagram
arch mermaid --c4
# Story flow diagrams
arch mermaid --stories
# Render to SVG (requires @mermaid-js/mermaid-cli)
arch mermaid --svg # → architecture.svg
arch mermaid --svg diagram.svg # → custom path
arch mermaid --c4 --svg # C4 as SVG
# Auto-inject into README.md
arch mermaid --update-readme # replaces content between markers
arch mermaid --c4 --update-readme # C4 versionContainer diagram example:
graph LR
backend["REST API\n(5 modules)"]
frontend["React UI\n(3 modules)"]
frontend --> backend
C4 Container diagram example:
C4Container
title Container diagram for MyProject
System_Boundary(system, "MyProject") {
Container(backend, "backend", "", "REST API and business logic")
Container(frontend, "frontend", "", "React UI")
}
Rel(frontend, backend, "depends on")
Requires @mermaid-js/mermaid-cli:
npm i -g @mermaid-js/mermaid-cli
arch mermaid --svgAdd markers to your README.md where you want the diagram injected:
<!-- arch:mermaid:start -->
<!-- arch:mermaid:end -->Then run arch mermaid --update-readme (or combine with --c4). The diagram will be inserted as a fenced Mermaid code block between the markers. Useful in CI to keep documentation in sync.
All commands (except init and mermaid) support --json for structured output. Useful for CI pipelines, VS Code extensions, MCP servers, and agent tool integrations:
$ arch drift --json
{
"scanned": 12,
"issues": 1,
"forbidden": [
{
"module_id": "backend/database",
"file": "db/mod.rs",
"import_raw": "use crate::auth",
"line_number": 3,
"target_module": "backend/auth",
"kind": "forbidden"
}
],
"undeclared": []
}
$ arch owns authentication --json
{
"query": "authentication",
"matches": [
{
"module": "backend/auth",
"file": "auth/mod.rs",
"owns": "authentication",
"boundary": "Auth logic only — no direct DB queries"
}
]
}arch has two enforcement mechanisms with different costs and trade-offs:
| Mechanism | Best for | Requires |
|---|---|---|
no_import_from |
Denylist: "these modules must not import X" | Only a naming convention |
Symmetric group isolation |
no_dependency rules, one per member — no container change |
|
restrict_callers_to |
Allowlist: "only these modules may import X" | Module in its own container |
Note: when the peer group grows (e.g. a new command is added), the
no_dependencyruleset must grow with it — one new rule per new member. This is the same maintenance cost asno_import_fromin a peer-group context.
Decision guide:
- Correctness invariant (a violation causes a bug, not just a style issue) → always use
restrict_callers_to+ own container - Allowed set ≤ forbidden set → prefer
restrict_callers_to+ own container - Forbidden set < allowed set → use
no_import_from, no restructuring needed
Container isolation rule: a new container is warranted when the module IS a distinct architectural concept — not just to make the rule expressible. Ask: would this module warrant its own container even if arch didn't exist?
Sub-module precision note: restrict_callers_to operates at container level. If you need to restrict callers of a specific file within a shared container, isolate that file into its own container first.
Yield vs. codebase health: the number of
restrict_callers_torules a codebase needs correlates inversely with its architectural health. A full audit that yields one or two rules is a good sign, not a failure of the tool. The rule type is most valuable in codebases with auth boundaries, payment gateways, audit trail write points, initialization-once patterns, or accumulated coupling. If your architecture is already clean, you may not need it beyond your most critical invariants.
Use the IGNORE: field in system.arch to exclude files from coverage and drift checks. Patterns use glob syntax, separated by semicolons:
SYSTEM: MyProject
IGNORE: tests/**; **/*.test.ts; scripts/**; benchmarks/**
Ignored files won't be flagged as unmapped in arch coverage and won't be scanned for imports in arch drift.
The GUIDE:/ENDGUIDE block in system.arch provides instructions for AI agents working on the codebase. Teams can wire it into agent prompts however they like — the field is there as a convention. Typical guidance tells agents:
- Before coding — read the relevant container
.archfile for ownership and boundaries - After coding — update the
.archfile if ownership or dependencies changed - Cross-cutting changes — check story flows in
system.archto understand impact
Use arch context to dump the full architecture as readable text — useful for injecting into agent context windows.
The .arch format is intentionally simple — no special query language, no database, no build step. Agents read .arch files directly, the same way they read code.
When multiple AI agents work on a codebase, arch provides:
- Ownership routing — which agent/coder owns which modules
- Boundary enforcement — what each module should and shouldn't do
- Drift detection — real-time validation that code matches the architecture
- Impact analysis — stories show which modules a change affects
- Grammar specs — run
arch spec --archorarch spec --llmcodeto get full grammar references for agents that need to read or write.arch/.llmfiles
Add architecture validation to your PR pipeline. Copy examples/arch-ci.yml to .github/workflows/ or use this snippet:
# .github/workflows/arch-ci.yml
name: Architecture Check
on:
pull_request:
branches: [main]
jobs:
arch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download arch
run: |
curl -sL "https://github.com/micsh/arch/releases/latest/download/arch-linux-x64" -o arch
chmod +x arch
- run: ./arch validate
- run: ./arch drift
- run: ./arch fitnessValidate architecture before every commit. Copy the hook from examples/pre-commit:
cp examples/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commitThe hook runs arch validate and arch drift, blocking the commit if violations are found. Skip temporarily with git commit --no-verify.
Architecture is declared in plain-text .arch files. Run arch spec --arch for the full grammar reference.
| Keyword | Description |
|---|---|
GUIDE: / ENDGUIDE |
Multi-line agent guidance block |
SYSTEM: <name> |
Project name |
DESC: <text> |
System description |
IGNORE: <glob>; <glob> |
Glob patterns to exclude from coverage and drift |
CONT: <id> | <path> | <desc> |
Declare a container |
RULE: <id> | <type> | <fields...> |
Declare a fitness rule |
STORY: <id> | <description> |
Declare a story (followed by flow lines A → B) |
| Keyword | Description |
|---|---|
MOD: <id> | file: <path> |
Module entry point |
FILES: <path>; <path> |
Additional files covered by this module |
OWN: <concept>; <concept> |
Concepts this module owns. Entries are matched against actual import strings by the resolver — include namespace prefixes (e.g. Conductor.Core.Types) alongside concept labels. Using concept labels only may cause broad-match fallback and phantom fitness violations. |
BND: <text> |
Boundary constraint (what this module should NOT do) |
DEP: <container/module>; ... |
Declared dependencies |
NDEP: <container/module>; ... |
Explicit forbidden dependencies |
MODULE: <id> |
Protected module for restrict_callers_to rules |
| Type | Fields | Description |
|---|---|---|
no_dependency |
from:, to: |
Forbids dependency between modules/containers |
no_import_from |
from:, pattern: |
Forbids imports matching a glob pattern |
boundary |
module:/modules:, constraint: |
Enforces a prose constraint on what a module can do |
restrict_callers_to |
module:, to: |
Allowlist: only listed modules may import the protected module |
Example rule declaration:
RULE: no-test-imports | no_import_from | from: mypackage | pattern: tests*
Production code must not import from test modules
Stories are declared in system.arch as:
STORY: user-login | User submits credentials → validated → token issued
frontend/login-page → backend/auth → backend/database
Each line after the STORY: header is a flow step. → separates consecutive module pairs to verify.
| Field | Required | Description |
|---|---|---|
SYSTEM: |
✅ | Project name |
DESC: |
What this system does | |
IGNORE: |
Semicolon-separated glob patterns for files to exclude |
| Field | Required | Description |
|---|---|---|
id (first segment of CONT:) |
✅ | Unique container identifier |
path (second segment) |
✅ | Relative path to source directory |
desc (third segment) |
✅ | What this container does |
depends_on: |
Semicolon-separated list of container IDs this depends on | |
proj: |
Build system project name (e.g., crate, package, assembly) |
| Field | Required | Description |
|---|---|---|
MOD: |
✅ | Module id and entry-point file |
FILES: |
Additional files covered by this module | |
OWN: |
✅ | Semicolon-separated concepts this module owns. Include namespace prefixes (e.g. Conductor.Core.Types) alongside concept labels — entries are matched against actual import strings by the resolver; concept labels only may cause broad-match fallback and phantom fitness violations. |
BND: |
What this module should NOT do | |
DEP: |
Semicolon-separated container/module dependencies |
|
NDEP: |
Semicolon-separated forbidden dependencies |
arch drift parses imports and arch init detects project type for these languages:
| Language | Detection | Import syntax |
|---|---|---|
| Rust | Cargo.toml |
use crate::, mod, use super:: |
| F# | *.fsproj |
open Namespace.Module |
| C# | *.csproj |
using Namespace; |
| TypeScript/JS | package.json |
import ... from '...', require('...') |
| Python | pyproject.toml, setup.py |
import module, from module import ... |
| Go | go.mod |
import "package" |
Contributions welcome! Open an issue or submit a pull request.
MIT