Architecture observability, visible debt, and enforceable contracts for AI-era codebases.
Axiom reads .axi contracts, scans real TypeScript and JavaScript imports, and shows where the observed code graph drifts from declared architecture intent. It can fail CI for high-confidence boundaries, but its first job is to make architecture drift and accepted debt observable enough for humans and agents to act on.
Axiom is part of an ongoing experiment to make architecture hallucinations in AI-generated code observable and enforceable. Read the technical note: Architecture Hallucinations in LLM-Generated Code.
.axi contract -> declared graph
source imports -> observed graph
Axiom compares both -> architecture violations with file, line, rule, and fix
Axiom is not a prompt wrapper and not a style linter. It is an architecture observability layer with enforceable contracts: it turns boundaries that usually live in docs, reviews, and memory into visible graph feedback, warnings, intentional violations, and CI gates where the contract is clear enough.
Status: public alpha / developer preview. The validator is usable today, but the .axi language and JSON schemas may still evolve before a stable 1.0.
Axiom is deliberately not starting as a fully automatic architecture guardian. That would be too rigid and too noisy for real codebases.
The near-term product is:
- architecture drift awareness
- dependency direction tracking
- module boundary warnings
- module fan-in/fan-out concentration warnings
- semantic ownership mapping through
.axi - intentional violations that stay visible instead of being hidden
- hard failures only for explicit, high-confidence contracts
Code can be locally correct while globally collapsing. Axiom's job is to make that collapse visible before it becomes normal, then enforce only the parts of the contract that are clear enough to trust.
This matters more in AI-era repositories because agents can change many files quickly. Axiom should let agents communicate with the architecture contract, while the tool mediates what is a hard violation, what is accepted debt, and what is advisory drift.
A live MiroFish forecast was run on 2026-05-11 against Axiom's current product seed. It used synthetic stakeholder profiles and simulated social reactions, so it is not market proof or a replacement for real users. Its value is product-risk discovery.
Treat forecast output as a risk map, not an action script. Axiom should absorb the problems it surfaces, then decide changes through the product's own filter: is the signal reliably checkable, does the change help real adopters instead of only quieting skeptics, and does it preserve Axiom's core difference as an architecture contract validator rather than a broad semantic oracle?
The sharpest finding was symbol-level API health: Axiom can validate import and visibility intent, and it can catch direct hidden-path re-exports plus local import-then-export leaks from hidden internals, but it cannot prove that broad public API surfaces are semantically healthy.
A later targeted backtest of axi observe accepted the observability direction and picked module fan-in/fan-out concentration as the next low-noise signal to try. That signal is now opt-in because high coupling is an architecture pressure point, not automatic proof of bad design.
A targeted backtest after ownership lookup memoization accepted the performance improvement as a material reduction in CI-friction risk, but shifted the highest-signal objection toward observed-graph blind spots. Axiom now has opt-in unresolved import warnings for static internal-looking imports that the scanner can see but the resolver cannot map into the source graph.
The forecast also predicted rejection if Axiom looks like:
- Dependency Cruiser with a new syntax
- another noisy linter
- a slow CI step
- a false architecture firewall
So the public product promise is deliberately narrower: make drift visible, keep accepted debt reviewable, and fail CI only for explicit high-confidence rules. Read the summarized forecast in MiroFish Live Forecast: Axiom Reception, or the Chinese process/output excerpt in MiroFish Live Forecast 中文過程與輸出摘錄.
AI coding agents are fast, but they often guess architecture from nearby files. Humans do this too. If your boundary is "UI may use Services, but only through the public Services entry point", that rule needs to be machine-checkable.
Axiom lets you write:
module Services
path "src/services/**"
exposes "src/services/index.ts"
hides "src/services/internal/**"
module UI
path "src/ui/**"
depends on ServicesThen this is allowed:
import { getDashboardTitle } from "../services";And this is reported:
import { issueServiceToken } from "../services/internal/token";From this repository:
npm install
npm run build
node dist/cli.js check --root examples/basic-appThe example intentionally fails:
Axiom check failed.
violations: 2
error unexposed_import src/ui/view.ts:2
UI imports a non-exposed path from Services.
observed: UI -> Services via "../services/feature"
rule: Services exposes src/services/index.ts (axiom/main.axi:13)
fix: Import an exposed entry point from Services, or add an exposes rule for this public API.
error hidden_import src/ui/view.ts:3
UI imports hidden path from Services.
observed: UI -> Services via "../services/internal/token"
rule: Services hides src/services/internal/** (axiom/main.axi:14)
fix: Import an exposed entry point from Services, or move the shared code behind a public boundary.
For a smaller graph view:
node dist/cli.js observe --root examples/basic-app
node dist/cli.js graph --root examples/basic-app --violations-only
node dist/cli.js graph --root examples/basic-app --attentionChoose your next step:
- Existing project: start with
axi infer --root ., then follow Adopting Axiom In A Real Project. - CI path: read GitHub Actions And PR Summaries, then compare it with the dogfooded workflow in .github/workflows/ci.yml.
- Real contract shape: inspect examples/monorepo-workspace for package-level contracts.
- Tool comparison: read Comparison And Boundaries if you are asking how Axiom differs from ESLint, Dependency Cruiser, Nx, CodeQL, or custom scripts.
Axiom v0.5.8 currently supports:
- Module ownership with
path. - Multiple source paths per module.
- Allowed dependencies with
depends on. - Forbidden module edges with
forbids module. - Layer direction with
layers Core -> UI. - Public/private module surfaces with
exposesandhides. - Direct hidden-path re-exports and local import-then-export hidden leaks from exposed entry points.
- Opt-in public API surface warnings for broad exposed barrels with
--warn-public-api-surface. - Opt-in coupling concentration warnings for modules with high observed fan-in or fan-out with
--warn-coupling-concentration. - Opt-in unresolved import warnings for static relative or package
#importsthat Axiom can see but cannot resolve with--warn-unresolved-imports. - TypeScript/JavaScript import scanning through the TypeScript parser.
- Relative imports, barrel
index.*files, dynamic imports,require, and multiline imports. - TypeScript
pathsaliases fromtsconfig.json. - Package
importsand workspace packageexportsfor internal package-style imports. - Workspace package discovery from
package.jsonworkspaces andpnpm-workspace.yaml. - Common monorepo contract discovery under
apps/*andpackages/*. - Gradual adoption with default loose mode,
--warn-unowned, and--strict. - Intentional violations that require an expiration date and reason.
- Module
purposetext surfaced in graph and JSON output for lightweight intent awareness. - Human output and stable JSON output for CI and agents.
- Markdown architecture review summaries for PRs and agent repair loops with
axi observe --markdown. - Starter contract inference with
axi infer. - Architecture attention output with
axi observe. - Baseline-aware observed edge drift with
axi observe --baseline <graph-json>. - Focused graph output with
axi graph --violations-only. - Scan summaries with module, source-file, import, and observed-dependency counts.
Axiom v0 is intentionally honest about its blind spots:
- It does not fully observe runtime-only dependency paths such as string-based dependency injection, plugin registries, generated imports, or
eval. - It can optionally warn about static relative or package
#importsthat the scanner sees but cannot resolve with--warn-unresolved-imports, but it still cannot see non-literal runtime wiring. - It does not prove that a module is semantically well-designed. Axiom can catch direct hidden-path re-exports and local import-then-export leaks from hidden internals, and
--warn-public-api-surfacecan flag broadexport *barrels, but code can still become too coupled through wrappers, facades, or overly large public entry points. This is thesymbol-level API healthgap. - It does not prove that concentrated fan-in or fan-out is wrong.
--warn-coupling-concentrationsurfaces modules that may be turning into coordination hubs so humans and agents can review the pressure before it becomes hidden debt. - It does not replace ESLint, TypeScript, tests, or review. Axiom focuses on architecture intent: declared graph, observed graph, drift, warnings, intentional violations, and CI gates for clear contracts.
- It does not replace Dependency Cruiser, Nx boundaries, CodeQL, or custom repository scripts. See Comparison And Boundaries for where Axiom is useful and where other tools are stronger.
- It does not make
.aximaintenance free. Useaxi inferto start from the current graph, then tighten only the boundaries that matter. - It does not promise whole-monorepo speed without scope control. Use
include,exclude, and focused contract locations to keep large repositories comfortable in CI.
The product goal is not perfect automatic architecture governance. The goal is a shared, machine-checkable observability layer where humans and agents can see drift early, accept temporary debt visibly, and enforce high-confidence boundaries.
Axiom treats broken contracts as visible diagnostics instead of silently skipping them:
- Syntax errors become
parse_errordiagnostics with file and line locations. - Duplicate modules, missing module paths, unknown modules, unknown layers, duplicate layer orders, ambiguous owners, and declared dependency cycles are validation errors.
- The parser keeps collecting diagnostics after a bad line where it can, so one typo does not hide the rest of the contract.
.axifiles are declarative text. Axiom does not execute contract code, macros, or plugins.- Glob patterns are compiled for matching; Axiom does not expand a glob into an unbounded rule graph. Source discovery still walks real files, so large repositories should use scoped
includeandexcludepatterns.
There is no public hard budget yet for pathological glob complexity or contract size. Treat this as a known hardening frontier: keep contracts small, keep source scope explicit, and report cases where contract parsing or matching becomes expensive.
Axiom includes a repeatable synthetic performance smoke harness so scan comfort can be measured instead of hand-waved:
npm run perf:smoke
npm run perf:smoke -- --modules 100 --files-per-module 100 --cross-imports-per-file 2Local results on Windows x64 / Node v24.14.1 / Intel i5-8400:
| Run | Source files | Imports scanned | axi check duration |
|---|---|---|---|
| Initial baseline | 2,000 | 3,880 | 7.8s |
| Initial baseline | 10,000 | 19,700 | 78.7s |
| After ownership lookup memoization | 2,000 | 3,880 | 2.9s |
| After ownership lookup memoization | 10,000 | 19,700 | 10.0s |
These are cold synthetic runs, not production benchmark proof. The latest run shows that repeated ownership matching was a real early bottleneck, but large monorepos still need scoped include/exclude config, focused contract locations, pilot evidence, and future resolver/discovery caching before Axiom should claim broad CI comfort.
Linux numbers are collected separately by the Performance Smoke workflow on ubuntu-latest, which uploads JSON artifacts for 2k-file and 10k-file synthetic runs. The README should only publish those numbers after a workflow artifact exists, not by extrapolating from local Windows results.
Requirements:
- Node.js 20+
- npm
Local checkout:
npm install
npm run buildOptional global install from this checkout:
npm install -g .
axi check --root examples/basic-appAxiom's npm package target is @fatelvx/axiom. The unscoped axiom package name is already used by another package, so the first alpha release uses a scoped package.
Until the first npm publish, use this repository checkout. After publishing:
npm install -D @fatelvx/axiom
npx axi check --root .
npx @fatelvx/axiom check --root .axi check --root <project>
axi observe --root <project>
axi graph --root <project>
axi infer --root <project>Use them like this:
axi check: validate code against.axi; exits1on violations.axi observe: show the architecture attention surface; exits0and focuses violations, visible debt, and warnings.axi graph: inspect declared and observed graphs; exits0even with violations.axi graph --violations-onlyoraxi graph --attention: show failing edges, intentional violations, and warning guardrails.axi infer: print a starter.axidraft from existing imports.
Useful flags:
axi check --root . --json
axi observe --root .
axi observe --root . --markdown
axi observe --root . --warn-public-api-surface
axi observe --root . --warn-unresolved-imports
axi observe --root . --warn-coupling-concentration
axi check --root . --warn-unowned
axi check --root . --warn-public-api-surface
axi check --root . --warn-unresolved-imports
axi check --root . --warn-coupling-concentration
axi check --root . --strict
axi graph --root . --json
axi graph --root . --json > axiom-baseline.json
axi observe --root . --baseline axiom-baseline.json
axi observe --root . --baseline axiom-baseline.json --markdown
axi infer --root . --group-depth 2
axi infer --root . --group-by workspaceCreate axiom/main.axi:
layers Domain -> App -> UI
module Domain
path "src/domain/**"
layer Domain
exposes "src/domain/index.ts"
module Services
path "src/services/**"
layer App
depends on Domain
exposes "src/services/index.ts"
hides "src/services/internal/**"
module UI
path "src/ui/**"
layer UI
depends on ServicesRun:
axi check --root .This contract says:
- UI can depend on Services.
- Services can depend on Domain.
- Domain cannot depend outward on App or UI.
- Other modules should import Services through
src/services/index.ts. src/services/internal/**is private.
For an existing codebase, start with inference:
axi infer --root .For deeper folder grouping:
axi infer --root . --group-depth 2For monorepos:
axi infer --root . --group-by workspaceInference prints a draft to stdout and does not write files. Treat it as a starting point: rename modules, add layers, tighten depends on, and add exposes or hides after review.
Axiom understands common npm, pnpm, and Turborepo-style workspace layouts.
By default it discovers .axi specs from:
axiom/**/*.axi
*.axi
apps/*/axiom/**/*.axi
apps/*/*.axi
packages/*/axiom/**/*.axi
packages/*/*.axi
This lets a repo keep package-level contracts near the package:
apps/web/axiom/main.axi
packages/shared/.axi
Try the monorepo example:
node dist/cli.js check --root examples/monorepo-workspace
node dist/cli.js graph --root examples/monorepo-workspace --violations-onlyUse axiom.config.json specs when your workspace layout is different.
Axiom reads axiom.config.json from the project root when present:
{
"include": ["src/**"],
"exclude": ["src/**/*.test.ts", "src/generated/**"],
"specs": ["axiom/**/*.axi"],
"tsconfig": "tsconfig.json",
"intentionalViolationExpiryWarningDays": 30,
"warnUnresolvedImports": false,
"warnPublicApiSurface": false,
"warnCouplingConcentration": false
}Fields:
include: source files to scan. If omitted, Axiom scans supported source files outside default ignored directories.exclude: source files or directories to skip in addition to default ignored directories.specs:.axifiles to read. Defaults toaxiom/**/*.axiand*.axi.tsconfig: TypeScript config path used forpathsalias resolution. Defaults totsconfig.jsonwhen present.intentionalViolationExpiryWarningDays: warn when accepted intentional violations expire within this many days. Defaults to30.warnUnresolvedImports: opt into advisory warnings for owned files with static relative or package#importsthat Axiom cannot resolve.warnPublicApiSurface: opt into advisory warnings for broad exposed barrels such asexport *.warnCouplingConcentration: opt into advisory warnings for modules with high observed fan-in or fan-out.
Default discovery skips common dependency, build, cache, and temporary output folders:
.cache
.git
.next
.nuxt
.svelte-kit
.turbo
.vite
build
coverage
dist
node_modules
out
target
temp
tmp
Project-specific generated or runtime folders should go in your own exclude config.
By default, Axiom ignores source files that are not owned by any module path. This keeps partial adoption cheap.
Use warning mode to measure coverage:
axi check --root . --warn-unownedUse strict mode once every discovered source file should be owned:
axi check --root . --strictWhen a real project needs a temporary intentional violation, keep it visible in .axi:
module UI
path "src/ui/**"
forbids module ServicesInternal
accepts forbidden_dependency to ServicesInternal until 2027-06-30 because "legacy import while the public service API is split out"Intentional violations only apply to observed dependency and visibility violations. Expired intentional violations fail the check, invalid entries cannot hide violations, entries expiring within 30 days become warnings, and unused entries are warnings so old architecture debt stays visible after the code is cleaned up.
axi observe and axi observe --markdown show a dedicated visible debt ledger. That ledger is not limited to dependency edges, so accepted surface violations such as hidden_reexport still appear even when the focused observed graph has no edge to show.
Tune the warning window per project with intentionalViolationExpiryWarningDays in axiom.config.json, or for one command with --intentional-violation-warning-days <n>.
To inspect the symbol-level API health pressure point without turning it into a hard gate, opt into public surface warnings:
axi check --root . --warn-public-api-surface
axi observe --root . --warn-public-api-surface
axi graph --root . --attention --warn-public-api-surfaceToday this flags broad exposed barrels such as export * from "./feature" or export * as feature from "./feature". It is advisory: the check still exits 0 unless there are real violations. Treat it as a review prompt when an exposed entry point starts hiding coupling behind one public surface.
Separate from that advisory warning, hidden_reexport is a hard, high-confidence violation when an exposed file leaks a hidden path directly, either with export ... from "./internal" or with import ... from "./internal" followed by export { ... }. Public wrappers around hidden implementation imports are still allowed; Axiom only flags the explicit hidden symbol leak.
To inspect observed-graph blind spots without turning them into a hard gate, opt into unresolved import warnings:
axi observe --root . --warn-unresolved-imports
axi check --root . --warn-unresolved-imports
axi graph --root . --attention --warn-unresolved-importsToday this flags static relative imports and package #imports from owned files when Axiom can see the import but cannot resolve it to a source file. It is advisory: the check still exits 0 unless there are real violations. Treat it as a prompt to configure tsconfig or package imports, restore a missing file, or acknowledge that generated/runtime wiring is outside the observed graph.
To inspect architecture pressure without turning it into a hard gate, opt into coupling concentration warnings:
axi observe --root . --warn-coupling-concentration
axi check --root . --warn-coupling-concentration
axi graph --root . --attention --warn-coupling-concentrationToday this flags a module when the observed graph shows fan-in from at least four distinct modules or fan-out to at least four distinct modules. It is advisory: the check still exits 0 unless there are real violations. Treat it as a review prompt for modules that may be becoming coordination hubs, broad facades, or hidden dependency magnets.
axi check --json emits axiom.check.v4:
{
"schemaVersion": "axiom.check.v4",
"ok": false,
"summary": {
"modules": 2,
"specFiles": 1,
"sourceFiles": 2,
"importsScanned": 1,
"observedDependencies": 1,
"violations": 1,
"intentionalViolations": 0,
"warnings": 0
},
"violations": [
{
"code": "hidden_import",
"message": "UI imports hidden path from Services.",
"location": {
"filePath": "src/ui/view.ts",
"line": 3
},
"details": {
"observed": "UI -> Services",
"rule": "Services hides src/services/internal/**",
"suggestion": "Import an exposed entry point from Services, or move the shared code behind a public boundary."
}
}
]
}axi graph --json and axi observe --json emit axiom.graph.v9. Each observed dependency includes violations and intentionalViolations arrays. Graph JSON also includes a top-level intentionalDebt ledger so accepted non-edge violations, such as hidden public-surface re-exports, stay visible to PR comments and agents. With --violations-only, --attention, or observe, observedDependencies is filtered to the edges that need attention or have accepted architecture debt, warning guardrails are still shown with details, and summary.observedDependencies keeps the full count. The JSON filters object marks focused attention output.
If you are building a CI annotation, PR comment, dashboard, or agent integration on top of JSON output, follow JSON Consumers. In short: use axi check --json for hard gates, tolerate additive fields, and treat intentionalDebt[] as the authoritative accepted-debt ledger for graph / observe output.
Use an unfiltered graph JSON file as a baseline when you want to inspect architecture drift in a PR or agent run:
axi graph --root . --json > axiom-baseline.json
axi observe --root . --baseline axiom-baseline.jsonThe baseline comparison reports new and removed observed module edges. It is an observability surface, not a hard gate; JSON marks it as advisory_observed_edge_drift. Axiom rejects filtered baselines from --attention or --violations-only so drift is not computed from incomplete graph data.
Use Markdown output when the result should be pasted into a PR comment, agent repair loop, or review artifact:
axi observe --root . --markdown
axi observe --root . --baseline axiom-baseline.json --markdownMarkdown output is a review summary, not a new validator path. It separates hard violations, visible intentional debt, advisory warnings, and baseline drift so humans and agents can negotiate with the architecture contract without treating every signal as a hard gate. Visible debt is listed from the contract ledger, not only from dependency edges, so accepted surface leaks remain conspicuous.
Review output also states that Axiom does not auto-accept debt. Accepted debt must already be declared in .axi with an expiration date and reason, and expired or invalid intentional violations remain hard failures in axi check.
This repository dogfoods Axiom in GitHub Actions:
npm ci
npm run ciThe repository also has a separate performance smoke workflow that records Linux synthetic scan evidence without making the normal CI gate slower:
node scripts/perf-smoke.mjs --json
node scripts/perf-smoke.mjs --modules 100 --files-per-module 100 --cross-imports-per-file 2 --jsonFor your own project, add a script:
{
"scripts": {
"axiom": "axi check --root ."
}
}Then run that script in CI after installing dependencies.
For a fuller PR workflow, use GitHub Actions And PR Summaries. It shows how to keep axi check --json as the hard gate, convert hard violations into GitHub annotations, and append axi observe --markdown as review context without making advisory warnings or drift accidental blockers.
- Getting Started
- Adopting Axiom In A Real Project
- Comparison And Boundaries
- GitHub Actions And PR Summaries
- JSON Consumers
- Publishing The Public Alpha
- Contributing
- Basic App Example
- Monorepo Workspace Example
- GitHub Actions Example
Axiom can currently report:
forbidden_dependencyundeclared_dependencyhidden_importhidden_reexportbroad_public_surfacecoupling_concentrationunresolved_importunexposed_importunowned_source_fileinvalid_suppressionexpired_suppressionexpiring_suppressionunused_suppressionlayer_breachambiguous_module_ownercycle_dependencyunknown_moduleunknown_layerduplicate_moduleduplicate_layer_ordermissing_module_pathparse_errorno_spec_files
npm run ci
npm test
npm run axiom:self
npm run perf:smoke
npm run github-actions:smoke
npm run alpha:check
npm run check:fixture
node dist/cli.js check --root examples/basic-appQuestions, contract-design discussions, and rough ideas are welcome. Use GitHub Discussions for open-ended design conversation when available, or open an issue for bugs and concrete feature requests. See CONTRIBUTING.md before proposing .axi language changes.
Near-term:
- A clearer architecture attention view.
- Better monorepo performance and resolver caching.
- Downstream project CI recipes.
- More TypeScript module resolution hardening.
- Drift and architecture health surfaces that start advisory, not as hard gates.
- Evolution graph views for visible architecture change over time.
- Baseline-aware drift refinement for CI comments and agent repair loops.
- Pilot evidence for how Axiom complements ESLint architecture rules, Dependency Cruiser, Nx boundaries, CodeQL, and custom CI scripts.
- Symbol-level public API surface analysis as an advisory research area, not a v0 hard gate.
Later:
- Capability rules such as wall clock, network, filesystem, and random.
- AI context compiler as a derived output.
- Agent repair loop.
Apache-2.0. See LICENSE.