archprint
Archprint's MCP server gives AI coding agents read-only access to a TypeScript repo's inferred architecture rules, with evidence, recommendations, and change-level checks.
archprint_scan — list the architecture rules the repo already follows: plain-word statements, confidence (AUTO vs. SUGGEST), adoption status (enforce / review / report-only), conforming vs. breaking file counts, the breaking files, and the confidence score.
archprint_recommend — get a rule set for the repo from its evidence and detected stack: what to enforce now, what is report-only, what to review, and what comparable repos adopt that this one does not.
archprint_explain — drill into one rule by label (e.g.
AP-002,env-access,lib !-> app): statement, gate status, adoption, conformance, confidence floor, and every breaking file, plus gate conditions, fixes and when not to adopt it for AP-001/AP-002.archprint_check — check the current change (uncommitted edits included) against rules adopted via
init/generate, reporting only newly introduced violations with evidence, plus fixed ones and allowed exceptions; returns "skipped" if no rules are adopted.All tools are read-only, idempotent, non-destructive, and operate on local paths (default
.), with monorepo-root support forscanandrecommend.
Generates evidence-backed ESLint rules from a project's existing architecture patterns and wires them into the ESLint configuration, allowing architectural violations to be caught during linting.
Archprint
Mine the architecture rules your repo already enforces, with the evidence attached.
Try it in your browser, nothing to install · Quick start · Use in CI · Use with AI agents · Docs
What it does, in plain words
Every codebase has unwritten rules. "Pages never talk to the database directly." "Shared code never reaches back into the app." Nobody wrote them down, but the code follows them, until one day someone (or an AI assistant) breaks one without noticing.
Archprint reads a TypeScript project, finds the rules its code already follows, and shows you the proof for each one: how many files follow it, which files break it, and how sure it is. The rules you trust become automatic checks in the tools your team already runs, so a break is caught the next time lint runs (in your editor, a pre-commit hook or CI), not weeks later in review.
Think of it as a building inspector who surveys the house first and writes down how it was actually built, instead of handing you a rulebook from somewhere else.
For developers and tech leads: inferred, evidence-backed lint rules for ESLint and dependency-cruiser, generated with
init, connected withwire, and removed witheject.For reviewers:
archprint checkflags only the rule breaks a pull request introduces, inline on GitHub, never the existing backlog.For teams using AI coding agents: Claude Code, Cursor and other agents can ask Archprint for the project's rules, with the evidence, before they write code.
For anyone evaluating a codebase: a quick, honest picture of how a project is actually structured.
Your CLAUDE.md is guidance. Your lint rules are enforcement. Archprint closes the gap by generating the
enforcement from patterns your codebase already demonstrates, so you adopt rules you can trust instead of
authoring them by hand.
Related MCP server: context-ops-mcp
See it in action
The recordings below use archprint-demo, a small Next.js API whose routes reach the database only through a service layer.
1. Find the rules your code already follows. Each rule comes with its evidence.

2. See why one rule is trusted. The confidence check, step by step.

3. Turn the rules on, watch a break get caught, and remove it all again. Archprint writes its files, adds one
line to your ESLint config, lint flags a route that imports the database directly, and eject restores your
project exactly.

Try it yourself, nothing to install: open the demo in StackBlitz. The scan runs as soon as it opens, and the demo's README walks through enforcing a rule in ESLint, breaking it, and asking for the rules over MCP.
Use it with AI coding agents
AI coding agents can ask Archprint for a project's rules through MCP, an open standard that lets agents use outside tools. You ask in plain words; the agent calls Archprint on its own and answers with the evidence. Archprint only reports. Enforcement still runs in your linter.
Claude Code lists the rules with what is enforced and what is held for review, then adds a route and checks its own change with archprint before it reports back:

Cursor works the same way. In these recordings Cursor was set to Grok 4.7, not Claude. In the desktop app's chat, its agent called Archprint's scan tool on its own; this screenshot shows the tool result and the answer:

In the terminal (cursor-agent), it does the same: lists the rules, then adds the route and checks it with
archprint_check:

Measured: the same question with and without Archprint
We asked Claude Code (Opus 5.5) "What architecture rules does this repo already follow?" on the demo app (70 files), five runs each way. Median [range]:
Without Archprint | With Archprint | |
Tokens read | 81k [58k to 96k] | 52k [52k to 52k] |
Tokens written | 1.1k [1.1k to 1.4k] | 0.6k [0.6k to 0.6k] |
Cost per question | $0.083 [$0.078 to $0.153] | $0.037 [$0.032 to $0.076] |
Time | 17 s [15 to 19] | 10 s [9 to 31] |
Tool calls | 5 [4 to 10] | 2 [2 to 2] |
What the answers showed:
Both found the main rule: routes reach the database only through
lib/services/.With Archprint, every run gave the evidence for each rule and named the one file that breaks one (
lib/db.tsreadsprocess.envoutside the config layer). No run without it noticed that.Without Archprint, Claude also described naming conventions Archprint does not check.
Read these numbers with care: about 50k of the tokens read in both columns are Claude Code's own system prompt, and this is one small repo; larger ones are not measured yet. Method, harness and every answer.
Setup for Claude Desktop, Claude Code, Cursor and other clients is in MCP setup.
Quick start
Requires Node 20 or newer. Run these in a folder with a tsconfig.json. In a monorepo, scan and recommend
also accept the root and cover every app, while init and generate work on one app at a time (for example
apps/web); at a root with several apps they stop, list the apps, and ask you to rerun with one.
# 1. See the rules your code already follows, with the evidence. Changes nothing.
npx archprint scan .
# 2. Ask why one rule is trusted (use a rule label from the scan)
npx archprint explain AP-001 .
# 3. Set up enforcement for the rules your code already follows cleanly,
# and record what to review or adopt next in .archprint/config.json
npx archprint init .
# 4. Connect the generated rules to your ESLint / dependency-cruiser config (one managed line)
npx archprint wire
# 5. Run your linter as usual. Your code passes today; a new import that breaks a rule fails.
npx eslint .
# To undo everything, exactly:
npx archprint ejectSee it catch something. After step 4, break a rule on purpose: for example, make a route file import your database client directly, then run your linter. That is the whole loop the demo walks through in the browser.
To keep it in the project instead of using npx: npm install --save-dev archprint.
More commands for a deliberate, step-by-step setup:
# Inspect the evidence behind one rule
archprint explain AP-002 apps/web
# Write the auto-trusted (mechanical) rules to .archprint/, only for the linters your repo uses.
# Structural-inference rules are held for review; add --include-structural to emit them too.
archprint generate apps/web
# Confirm the generated rules pass on your repo before wiring
archprint generate apps/web --check
# Generate a single rule by id after reviewing it (including a SUGGEST rule)
archprint generate apps/web --rule AP-001
# Adopt only the rules you choose, including reviewed layer boundaries
archprint generate apps/web --include-structural --rules AP-002,no-utils-to-app
# Recommend a rule set from the evidence and the detected stack (fresh repos too)
archprint recommend apps/web
# Upgrading from 0.5.x? Move an older archprint-rules/ setup to the .archprint layout
archprint migrateOr build from source:
git clone https://github.com/Tommkruix/archprint
cd archprint
npm ci
npm run build
node dist/cli.js scan <path-to-your-app>How it decides what to trust
Archprint is deliberately cautious: one wrong rule hurts more than no rule. Every candidate rule passes two checks before it is turned on for you.
1. Is there enough evidence? Seeing 5 of 5 files follow a pattern is not proof; 40 of 40 is. Archprint scores each rule with a Wilson score lower bound, a standard statistical measure that combines how often the rule holds with how many files it was checked on. Each rule lands in one of three groups:
AUTO (enforceable): the 95% lower bound on conformance is at least 90%, with at most 3 exceptions and a confidently classified role.
SUGGEST (provisional): the pattern holds in at least 80% of files and the role is at least 50% certain, but one AUTO condition fails: the confidence floor is under 90% (too few files, or too many that break it), more than 3 files break it, or the role is under 80% certain. Surfaced for review, not auto-generated.
REJECT: not enough signal.
2. Could the rule itself be wrong? A rule can pass the numbers and still be wrong if Archprint guessed a
folder's purpose incorrectly. So only the mechanical families, which rest on unambiguous signals, are trusted
without review: forbidden imports (AP-001, AP-002), circular dependencies, test isolation, import style, console
isolation, and public-API barrels. An adversarial correctness audit (three rounds over four real repositories)
found zero false positives in these every round. All of them except circular dependencies are written as lint
rules; a cycle-free codebase with enough files for the gate adopts "no circular dependencies", which
archprint check enforces on each pull request (cycles need the whole import graph, so there is no lint rule for
them).
The structural families infer a "layer" or "role" from paths, which can be wrong (layer and role boundaries,
UI/data separation, entry purity, server/client, feature-slice and app isolation, env access, workspace package
API, stories isolation). Dependency hygiene, whose enforcement can over-flag, and dependency declaration are held
back too. All of these are held for your review by default and written as enforcement only with
--include-structural, regardless of their statistical score, until they earn the same clean record. Nothing
that could be wrong is enforced without you opting in.
Generated rules are green by construction: each one lets through the few known exception files it was inferred
from, so adopting it keeps your lint green while new violations are still caught, and a self-consistency check
refuses to write a rule whose evidence does not hold together. To run the generated rules against your code before
connecting them, use archprint generate --check.
What it can detect
Ships as: Auto = turned on as enforcement (mechanical families). Review = held for your review by
default; emit with --include-structural. Check = enforced by archprint check on each pull request. Report =
shown only, never enforced.
Archprint recognizes the stack (Next.js, Nest, SvelteKit, Nuxt, Remix) and classifies UI components across React
(.tsx), Angular (.component.ts, .directive.ts), and Vue and Svelte single-file components (it reads the
<script> block of .vue/.svelte files), so the component-aware rules apply regardless of framework.
Detector | Rule it can infer | Ships as |
Forbidden imports (AP-001, AP-002) | AP-001: a request entry (route handler) must not import the DB client. AP-002: a server entry must not import the UI layer | Auto |
Circular dependencies | The module graph should stay acyclic; adopted when there are no cycles, and | Check |
Test isolation | Production (non-test) code must not import test or spec files | Auto |
Dependency hygiene | Import third-party packages by their public entry, not a dependency's | Review |
Dependency declaration | Every imported third-party package must be declared in | Review |
Import style | Prefer workspace aliases over deep relative imports ( | Auto |
Console isolation | Library (non-CLI) code must not call | Auto |
Public API (barrel) boundaries | Files outside a feature or package must import it through its | Auto |
Layer boundaries | Files in one layer must not import another, inferred from the dominant dependency direction | Review |
Role layering | Semantic tiers keep their direction (a REPOSITORY must not import a SERVICE, a SERVICE must not import a CONTROLLER) | Review |
Entry purity | Framework entries (pages, routes, layouts) must not be imported by other first-party code | Review |
UI / data separation | Reusable UI components must not import the DB/data layer directly | Review |
Server / client boundary | A Next.js | Review |
Feature-slice isolation | Sibling slices under a | Review |
App isolation | Sibling apps under an | Review |
Env access | Read | Review |
Workspace package API | Import a monorepo workspace package by its name, not a deep path into its source | Review |
Stories isolation | Storybook | Review |
Orphan modules | Files nothing imports and that are not framework entries (dead code candidates) | Report |
Transitive reachability | A layer boundary that a plain import rule passes but that leaks through an intermediary layer | Report |
recommend (and init) sort every rule family into tiers: rules your code already follows (enforce now), rules
your code follows that Archprint reports but does not write, rules with thin
evidence (review and adopt), and rules that comparable repos commonly follow but yours does not yet (adopt from day
one). Each recommendation carries the share of comparable repos (your detected stack, else
overall) that already enforce that rule, mined from a census of tens of thousands of public TypeScript
repositories. So even a fresh repo, with little code to learn from, gets a stack-aware baseline backed by what the
ecosystem actually does rather than hand-picked defaults.
What it writes to your project
archprint generate (and init) writes .archprint/config.json plus one file for each linter your repo already
runs that has rules to enforce. A typical ESLint project gets two files, config.json and eslint.mjs; a repo
whose dependency-cruiser rules are adopted too gets a third. archprint detects ESLint and dependency-cruiser, so
you are never left with config for a tool you do not have. --emit <eslint|dependency-cruiser|all> forces the
format.
.archprint/config.json(always): the exact definition of each rule you adopted, with the evidence recorded at adoption and the resolution mode it was generated in (archprint checkreads it, so a check never re-infers a rule), the exceptions you allowed with a reason, what is enforced, followed but only reported, held for review, and worth adopting, and the list of managed outputsejectremoves..archprint/eslint.mjs(when ESLint is present): one self-contained ESLint flat-config file that inlines every inferred ESLint rule (marker-based forbidden imports,no-restricted-importsimport-style boundaries, console isolation, and, when you include structural rules and do not use dependency-cruiser, layer boundaries, undeclared (phantom) dependencies, the server/client boundary, package internals and entry purity) and needs no extra plugins: it adds rules to your existing ESLint setup, which already parses your TypeScript. So you can commit it, publish it, or hand it to another repo and adopt it in one line (import archprint from './.archprint/eslint.mjs'). Layer boundaries are the exception: they find your app relative to the file, so they only apply where it was generated. It self-ignores**/.archprint/**. The forbidden-import (AP-) rules and those structural rules ship as a generated local eslint plugin inside it, so wiring the eslint config enforces them too, no extra install..archprint/dependency-cruiser.json(when dependency-cruiser is present): oneforbiddenruleset with the mechanical boundaries (public-API deep-import, test-isolation); the review-held ones (layer, role-layering, feature-slice, app-isolation, entry-purity, dependency-internals, phantom deps, server/client) are added only with--include-structural, after you review them.A managed section in your
README.mdsummarizing what is enforced now, followed but only reported, held for review, and worth adopting (written byinit, orgenerate --readme), plus a managed.prettierignoreentry so the generated files stay out of your formatter.
--expand additionally writes the granular artifacts inside .archprint/: the per-family ESLint and
dependency-cruiser JSON, per-rule cards (.md) with passing and failing fixtures, the eslint-plugin-boundaries
element-types config, ts-arch tests, and the Mermaid and Graphviz DOT layer graph.
Staying in sync, and leaving cleanly. Re-running generate (or init) refreshes .archprint/ and drops any
rule the evidence no longer supports, so the output never drifts from the code. wire inserts a single managed
reference into each enforcement tool your repo uses (a flat eslint config, a .dependency-cruiser.json), one that
survives those regenerations; for a config it cannot safely edit (a JS dependency-cruiser config, say), it prints
the exact snippet to paste. eject removes Archprint's files and every wired reference, restoring each config
exactly. generate --check runs the generated ESLint rules against your repo and reports whether they pass, so you
can confirm before wiring. Upgrading from 0.5.x? archprint migrate moves an older archprint-rules/ setup to
this layout and rewires your configs in place.
Use in CI
archprint check reports only the violations a change introduces, compared with a base branch, for the rules
your team adopted with init or generate. The existing backlog never shows up, and every finding carries its
evidence. It works in any CI, and on GitHub it shows each finding inline on the pull request
(see it on a demo pull request).
On GitHub, use the archprint check Action (on the GitHub Marketplace):
# .github/workflows/archprint.yml
name: archprint
on: pull_request
permissions:
contents: read
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: Tommkruix/archprint-action@v1It checks out the pull request itself, so it needs no checkout step. The same check in plain steps, without the Action:
# .github/workflows/archprint.yml
name: archprint
on: pull_request
permissions:
contents: read
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- uses: actions/setup-node@v5
with:
node-version: 22
- run: npm ci
- run: npx archprint check --base ${{ github.event.pull_request.base.sha }} --format githubWarning by default. Add
--fail-on new(or the Action'sfail-on: new) to fail the job on a new violation, then mark the job a required status check in your branch protection so a pull request that adds one can't merge.Only adopted rules.
checkreads the adopted rules in.archprint/config.json, written byinitandgenerate, and checks only the rules recorded there: the mechanical rules, plus layer boundaries once you adopt them with--include-structural. Rules adopted in the same pull request are listed but never counted against it.Imports that resolve to nothing are TypeScript's job. Run
tsc --noEmitin CI, or rely on the type checknext buildruns by default: it reports a missing module (TS2307) with your project's own module resolution. Archprint does not generate this rule, and on a TypeScript appimport/no-unresolvedor dependency-cruiser'snot-to-unresolvabletend to report false errors onpathsaliases, packageexportsand bundler suffixes such as?raw.A justified exception needs a reason. When a file has a real reason to break a rule, record it:
npx archprint allow AP-001 app/api/health/route.ts --reason "Health check queries the database directly". It goes in theallowedlist of.archprint/config.json,checkstops counting it and lists it with its reason in the pull request, and the nextarchprint generatestops the generated rules flagging it. An entry without a reason is rejected, and an entry the code no longer needs is pointed out. Rules enforced through dependency-cruiser (public API) are not covered on that side yet.Removing rules is never silent. If a pull request deletes
.archprint/config.json, or the rules in it, that the base branch has,checkwarns and lists every rule that stops being checked. It does not fail the job, because dropping a rule can be a deliberate team decision. To make that decision need a reviewer, add a CODEOWNERS entry and turn on "Require review from Code Owners" in branch protection:/.archprint/ @your-team /.github/workflows/ @your-teamUpgrading: from 0.8.x or earlier, run
archprint generateonce to record the adopted rules. Until you do,checkposts a notice that it did not run and exits 0. Setups from 0.9.0 to 0.11.x keep working as they are:checkstill reads theirrules.jsonandallow.json, and the nextgeneratemoves both intoconfig.json.Other CI systems:
--format jsongives version-keyed output, and the exit code is the contract:0ok,1new violations with--fail-on new,2the CI setup is wrong (for example a shallow clone without the base commit). Check out the full history (fetch-depth: 0or your CI's equivalent).Safe on pull requests from forks. It needs only read access, uses no secrets, and never runs your code.
MCP setup
archprint mcp runs Archprint as an MCP server over stdio, so an agent can ask
what architecture rules your repo already follows, with the evidence, before it writes code. It exposes four
read-only tools: archprint_scan, archprint_recommend, archprint_explain, and archprint_check. Each rule
comes back stated in plain words, with its evidence, the files that break it, and whether archprint enforces it,
holds it for review, or only reports it. archprint_explain takes any rule label from the scan (for example
AP-002 or env-access). archprint_check reports the adopted rules the agent's current change breaks,
uncommitted edits included, so it can fix them before it finishes. Point Claude Desktop, Claude Code, Cursor, or
any MCP client at it:
{
"mcpServers": {
"archprint": { "command": "npx", "args": ["-y", "archprint", "mcp"] }
}
}Things to ask your agent. You do not name the tools; the agent picks them:
"What architecture rules does this repo already follow?"
"Which file breaks the env-access rule, and how should I fix it?"
"I am adding a new API route. What rules should it follow in this codebase?"
"Which rules should we enforce now, and which are worth adopting?"
Your code stays on your machine. This default is a local server: it reads your local checkout, so it is the one to use for private code, and your source never leaves your machine, whichever git host you use.
If the server will not start. If the client says the server failed to start or npx was not found, it cannot
see your shell's PATH. Desktop apps opened from the Dock or Start menu do not load it, which is common when Node
comes from nvm or Homebrew. A full path to npx alone is not enough, because npx itself needs node on the
PATH. Point both at the folder that dirname "$(which node)" prints, for example /opt/homebrew/bin:
{
"mcpServers": {
"archprint": {
"command": "/opt/homebrew/bin/npx",
"args": ["-y", "archprint", "mcp"],
"env": { "PATH": "/opt/homebrew/bin:/usr/bin:/bin" }
}
}
}Starting the editor from a terminal also works, because it then inherits your shell's PATH.
One-line installs. In Claude Code:
claude mcp add archprint -- npx -y archprint mcpIn Cursor, put the JSON above in .cursor/mcp.json in your project (or ~/.cursor/mcp.json for every project),
then enable archprint under Settings > MCP.
Scanning a public repo by URL. archprint mcp --http runs a remote server instead. It clones the repo shallow
to a temp dir, runs the same read-only analysis, returns the result, and deletes the clone (only public
github.com, gitlab.com, and bitbucket.org URLs; nothing is written or kept). The tools then take a repo URL
(and an optional ref). It listens on 0.0.0.0:8848/mcp by default (set --host 127.0.0.1 to keep it to your
own machine, or --port/$PORT to change the port) and answers health checks at /health (use this one on Cloud
Run, which reserves /healthz) and /healthz. Every request clones and scans, so a server anyone can reach spends
compute on anyone's behalf: keep it behind authentication, such as Cloud Run's IAM, unless you accept that cost.
The tools are read-only (they never write to the repo); use the CLI's generate/wire to actually emit and
enforce rules.
How it compares
Established TypeScript tools (dependency-cruiser, eslint-plugin-boundaries, Nx, Sheriff, ts-arch) all enforce architecture rules you write by hand. Archprint infers them from the actual import graph and gates each one on statistical evidence before proposing it. It then emits into those tools' formats, so it complements your stack rather than replacing it.
Verified against each tool's documentation (TypeScript ecosystem). The two columns that matter are the ones no other TypeScript tool fills:
Tool | Enforces arch rules | Auto-infers from the import graph | Attaches statistical evidence |
Archprint | yes | yes | yes |
dependency-cruiser | yes | no | no |
eslint-plugin-boundaries | yes | no | no |
@nx/enforce-module-boundaries | yes | no | no |
Sheriff | yes | no | no |
ts-arch | yes | no | no |
madge / knip | analysis only | no | no |
Honest caveat: in other ecosystems, Tach (Python) and ArchLint (Java) do auto-infer module boundaries, so Archprint's specific niche is auto-inference plus statistical evidence gating in the TypeScript ecosystem. Archprint also overlaps in detection with dependency-cruiser (cycles, orphans, reachability) and knip (dead code); rather than compete, it writes the rules it generates in those tools' formats.
Commands
archprint init [path]: zero-config setup. Detects the stack, enforces the rules the code already follows, and writes.archprint/plus a managed README section. Options:--expand,--include-structural,--out <dir>,--fast,--force.archprint scan [path]: reports the rules the repo already follows, with evidence. Changes nothing.--deepresolves through barrels and aliases.archprint explain <id> [path]: shows the gate breakdown for one rule, with a codeframe per exception plus how to fix, when not to use it, and how to enforce it.archprint recommend [path]: recommends a rule set from the repo's evidence and detected stack (works on a fresh repo too), and names the installed tool that will enforce each rule it can write.archprint generate [path]: writes the auto-trusted mechanical rules to.archprint/for the linters your repo uses; structural rules are held for review.--emit <eslint|dependency-cruiser|all>forces the format,--only <family>and--rules <ids>narrow the output (--ruleskeeps only the rules it names, such asAP-002,import-styleor the layer ruleno-lib-to-app),--checkruns the generated rules against your repo,--readmeadds the README section,--expandalso writes the per-family files, cards, fixtures and graph, and--rule <id>emits one reviewed rule. Also--include-structural,--no-graph,--out <dir>,--fast.archprint check [path]: reports the violations of your adopted rules that a change introduces, compared with--base <branch or commit>. Warning only unless--fail-on new.--format text|json|github,--out <dir>. See Use in CI.archprint allow <rule> <file> --reason "...": accepts one adopted rule being broken in one file, with a reason, recorded in.archprint/config.json.checkstops counting it and lists it in the pull request;generatestops the generated rules flagging it.--removetakes it back out.archprint wire: references the generated rules from the enforcement tools your repo uses (flat eslint config,.dependency-cruiser.json) through a managed, reversible reference.--out <dir>,--dry-run.archprint eject: removes Archprint's generated files, its config, the managed README section, and any wired references, restoring each config exactly.--out <dir>,--dry-run.archprint migrate(aliasupgrade): moves an olderarchprint-rules/setup to the.archprint/layout and rewires your configs in place.--dry-run.archprint mcp: runs Archprint as an MCP server so Claude, Cursor, and other agents can call the read-onlyscan,recommend,explain, andchecktools. Serves over stdio by default;--httpruns a remote server that scans a public repo by URL (scan,recommend, andexplain).
scan --json, recommend --json and check --format json emit stable, version-keyed JSON for scripting. Exit
codes are the contract: 0 on success, 1 on error (for check: new violations with --fail-on new), 2 for a
check that could not run because of the CI setup.
Example on a real repo
A real scan of inbox-zero (apps/web at commit 11281be, 2,232 TypeScript
files), trimmed to the first rules of each section:
Scanned 2,232 TypeScript files
Workspace aliases: 18 resolved
GENERATED RULES
AP-002 no-ui-layer-in-server-entry confidence 97%
A request handler must not import UI components.
Evidence: 216 of 217 files it applies to follow it (99.5%)
Exceptions: 1
LAYER BOUNDARIES (review before enforcing)
utils !-> app layer boundary confidence 99%
Evidence: 650/653 utils files conform (99.5%); 451 app file(s) depend on utils
Exceptions: 3
utils !-> components layer boundary confidence 99%
Evidence: 650/653 utils files conform (99.5%); 160 components file(s) depend on utils
Exceptions: 3
hooks !-> app layer boundary confidence 94%
Evidence: 65/65 hooks files conform (100%); 121 app file(s) depend on hooksAP-002 is a mechanical family, so it auto-generates as enforcement. The layer boundaries are inferred, so they
are shown for review, not written as enforcement unless you pass --include-structural. Every number is measured
from the import graph, not estimated.
Adopt the layer boundaries you agree with
A layer boundary is held for review because a folder name can be misread as a layer. Once you have read the
evidence, adopt the boundaries you agree with by id: utils !-> app becomes the rule no-utils-to-app.
# 1. Read the boundaries and their evidence
archprint scan apps/web
# 2. Write the rules you chose: AP-002 plus two of the boundaries above
archprint generate apps/web --include-structural --rules AP-002,no-utils-to-app,no-hooks-to-app
# 3. Reference the generated rules from your ESLint config (once)
archprint wireWithout dependency-cruiser, each boundary becomes an ESLint rule in .archprint/eslint.mjs, such as
archprint/no-utils-to-app, with no extra plugin; with dependency-cruiser, it goes there instead. The rule
exempts the files that already break the boundary, so your lint stays green, and it catches a new import across
the boundary, by relative path or alias. archprint check reports a new break in a pull request, and
archprint allow records a justified exception. --rules keeps only the rules it names, so list every rule you
want in the same run. If your CI pins archprint (for example the GitHub Action's archprint-version), use 0.13.0
or later there first. The rules reference
says exactly which imports and files the rule reads.
Technical notes
Fast and deep modes. scan defaults to a fast specifier-level pass (no type checker). generate defaults
to a deep pass that resolves through barrels and workspace aliases, since generation is the commitment point.
Structural analysis (cycles, orphans, reachability, public-API) always uses the fast graph: it is faithful to deep
resolution for those, and public-API detection in fact requires it (deep resolution would resolve through a barrel
and erase the barrel-versus-deep signal).
Determinism. The same repo at the same version produces the same output. Analysis is pure and sorted; there is no randomness, and the analysis engine is pinned to an exact version.
Status
Published on npm and safe to run on your real repo. Every rule is review-gated by default, reversible in one
command (archprint eject), and deterministic, and generated rules are green by construction on the code they
were inferred from.
Validated at scale: the latest census (archprint 0.12.2, October 2026) ran
scanover 92,861 public TypeScript repositories with no crashes; 494 (0.5%) could not be fetched or timed out, most of them deleted or very large repositories. The full workflow (init,check,allow,generate,check,wire,eject) ran on a 2,019-repo stratified sample: it found one bug, exceptions in route folders like Next.js[id], fixed in 0.12.3, and all 2,012 repositories it ran on came back exactly as they were aftereject.Production-ready today:
scanandrecommend,checkfor pull requests, and auto-enforcement of the mechanical families, with a self-consistency check at generate time, aninitscaffolder for fresh repos, and framework coverage across React, Angular, Vue, and Svelte. The engine (twenty detectors, the confidence gate, and emitters for a self-contained ESLint file, dependency-cruiser, ts-arch, and the layer graph) is in place and tested.Still ahead: hardening the structural families toward auto-enforcement (a real per-file role-confidence measure, layer cohesion, role-classifier ordering).
Versioning is still 0.x, so the CLI surface and rule format can refine between minor versions. That is a maturing surface, not experimental analysis. The compact
.archprint/layout arrived in 0.6.0, andarchprint migrateupgrades an older setup in place.
A companion benchmark, AgentRuleBench, measures the guidance-vs-enforcement question directly (a pre-registered, honest null result on the boundary it tested).
Words used here
Import: a line in one file that uses code from another. Archprint's rules are about which files may import which.
Lint rule / linter: an automatic check that runs on your code (ESLint is the most common one) and flags problems as you write.
AUTO / SUGGEST / REJECT: how confident Archprint is in a rule; see How it decides what to trust.
Mechanical / structural families: rules based on unambiguous signals (trusted without review) versus rules that depend on guessing a folder's role (held for your review).
MCP: an open standard that lets AI agents use outside tools such as Archprint.
Documentation
Full docs live at tommkruix.github.io/archprint and in
docs/: getting started, concepts (the confidence
gate, mechanical vs. structural, fast vs. deep, the generate/wire/eject lifecycle), and the
rule-family reference (what each rule detects, how it ships, and when not to use it).
Contributing
See CONTRIBUTING.md. The project lints, type checks, and tests itself; every change keeps coverage above its thresholds and ships a changeset.
License
Available Tools
4 toolsarchprint_checkCheck a change against adopted rulesARead-onlyIdempotent
Check the current change, including uncommitted edits, against the rules this repo adopted with archprint init or generate, and report only the violations the change introduces (file, line, rule, and the evidence for the rule), plus those it fixed and any exceptions allowed with a reason in .archprint/config.json. Use it before finishing a change. Compares with base (a branch or commit, default origin/HEAD). Read-only: the files of the base commit are copied to a temporary folder outside the repo and deleted afterwards; it runs no git hooks and changes nothing in the repo. Returns status "skipped" with the reason when the repo has not adopted rules.
| Name | Required | Description | Default |
|---|---|---|---|
| base | No | branch or commit to compare against (default origin/HEAD) | |
| path | No | repository directory where archprint was set up, on this machine (default ".") |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile, yet the description adds substantial context: base files are copied to a temp folder outside the repo and deleted, no git hooks run, nothing in the repo changes, and a 'skipped' status is returned when rules were never adopted. This goes well past what the annotations declare.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded and no sentence is filler — the read-only mechanics and the skipped-status behavior are genuinely useful. However, it is a single dense run-on sentence packing many clauses, which slightly hurts scanability.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the return burden and does so: it names the reported fields (file, line, rule, evidence), the fixed items, exceptions, and the 'skipped' status. An agent has everything needed to call and interpret it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the description largely restates the base default (origin/HEAD) already documented in the schema. It adds no new syntax or format guidance for `path` beyond what the schema provides, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('check the current change... against the rules this repo adopted'), and narrows scope precisely to violations the change introduces, plus fixed items and allowed exceptions. It does not name or contrast any sibling tool (explain, recommend, scan), so an agent must infer the boundary itself.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Use it before finishing a change' gives a clear temporal trigger, and the default base comparison is spelled out so the agent knows the context of a run. It offers no explicit when-not-to-use condition and no routing to the sibling tools, which would be needed for a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
archprint_explainExplain an architecture ruleARead-onlyIdempotent
Explain one rule from archprint_scan in depth, by its label (e.g. AP-002, env-access, or "lib !-> app"): its statement, gate status, adoption (enforce, review, or report-only, as in archprint_scan), observed conformance and confidence floor, and every file that breaks it (scan lists only a few). For the forbidden-import rules (AP-001, AP-002) it also returns each confidence-gate condition with its value and threshold, how to fix a violation, and when not to adopt the rule. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | rule label from archprint_scan, e.g. AP-002 or env-access | |
| path | No | app or monorepo-root directory on this machine (default "."), not a URL |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no output schema, the description carries the return-value burden and does so concretely: it enumerates statement, gate status, adoption mode, conformance and confidence floor, all offending files, plus the AP-001/AP-002 specific confidence-gate conditions, remediation steps and non-adoption guidance. 'Read-only' merely restates readOnlyHint, but the payload disclosure goes well beyond the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence opens with the core action and then enumerates the payoff, so an agent gets the point immediately. The middle portion is a dense comma-run of returned fields that could be tightened, but nothing is truly wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, yet the description specifies the full return shape, the rule-specific special cases (AP-001/AP-002), and remediation content, while the annotations cover safety and idempotency. An agent has everything needed to call this and interpret the result.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters are already documented, including the label examples and the 'not a URL' clarification for path. The description reinforces but does not extend that information, which is the expected baseline when the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Explain one rule') plus resource ('from archprint_scan ... by its label') and gives concrete label examples (AP-002, env-access, "lib !-> app"). It also distinguishes itself from archprint_scan by noting that scan lists only a few breaking files while this returns every one.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It clearly frames the input as a label obtained from archprint_scan and sets expectations for depth ('in depth', 'scan lists only a few'), which implies when to reach for it over scan. It does not, however, say anything about when to prefer archprint_check or archprint_recommend, so routing among all four siblings is only partially resolved.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
archprint_recommendRecommend architecture rulesARead-onlyIdempotent
Recommend an architecture rule set for this repo from its evidence and detected stack: what to enforce now, what the code already follows that archprint reports but does not write a rule for yet (reportOnly), what to review before enforcing, and what comparable repos commonly adopt that this repo does not yet. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | app or monorepo-root directory on this machine (default "."), not a URL |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, so the trailing 'Read-only.' is redundant rather than contradictory. The description's real contribution is describing what the recommendation contains (enforce-now, reportOnly, review-before-enforcing, peer-adopted gaps), which is meaningful behavioral context given there is no output schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single dense sentence with the core action and the read-only guarantee front-loaded, and no filler. It is long, but every clause maps to a distinct category of output, so nothing is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description usefully covers the return shape, and the one parameter is fully documented. The only real gap is the unstated relationship to archprint_scan as a prerequisite for the 'evidence' it consumes.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% for the single path parameter, and the schema itself clarifies it is a local directory defaulting to '.', not a URL. The description adds nothing about path beyond that, so the baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (recommend) and resource (an architecture rule set) and pins the scope to this repo's evidence and detected stack, then enumerates the four categories of recommendation. It is unambiguous about what the tool produces, though it never names the sibling tools to draw a boundary against archprint_scan or archprint_explain.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the enumeration (use it when you want to know what to enforce, what is reportOnly, what to review, what peers adopt), but there is no explicit when-to-use statement, no exclusion, and no stated prerequisite such as whether archprint_scan must run first to produce the evidence this tool consumes.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
archprint_scanScan architecture rulesARead-onlyIdempotent
List the architecture rules this TypeScript repo already follows, with the evidence: each rule stated in plain words, whether it is AUTO (clears the confidence gate) or SUGGEST (short of it), its adoption ("enforce": archprint init or generate writes a rule for it, which archprint wire connects to the linter, and public-API rules need dependency-cruiser; "review": held for a human to review, not enforced by default; "report-only": the code follows it but archprint writes no rule for it), how many files conform vs. break it, the files that break it, and the confidence. Read-only. path defaults to "." and a monorepo root scans every app.
| Name | Required | Description | Default |
|---|---|---|---|
| deep | No | resolve imports through barrels and aliases (slower, more accurate) | |
| path | No | app or monorepo-root directory on this machine (default "."), not a URL |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover read-only/idempotent/non-destructive, so the bar is lower, yet the description adds substantial semantics: the AUTO vs SUGGEST confidence gate and the three adoption levels (enforce/review/report-only) with their downstream implications. It stops short of describing cost or output volume beyond the `deep` hint.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded in the first clause, and the dense parenthetical definitions of AUTO/SUGGEST and the adoption levels all carry necessary meaning. It is long and heavily nested with quotes and parentheses, which slightly hurts scanability but does not waste words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description must enumerate return values, and it does: each rule in plain words, AUTO/SUGGEST status, adoption category, conform-vs-break counts, breaking files, and confidence. An agent has everything needed to interpret results.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents both parameters and the baseline is 3. The description adds meaning beyond the schema by explaining that a monorepo-root `path` scans every app, which the schema does not convey.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (list) and resource (architecture rules a TypeScript repo follows) plus the evidence returned. It is clearly a read/scan operation, but it does not explicitly differentiate itself from the sibling archprint_check or archprint_explain, leaving the agent to infer the distinction.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description notes that `path` defaults to '.' and that a monorepo root scans every app, which is useful invocation context. However, it never states when to use this scan versus archprint_check, archprint_explain, or archprint_recommend, so tool selection guidance is largely absent.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v0.10.0- Added
archprint_check
3 tool updates
v0.9.0- Changed
archprint_explain1 field changed- changed
Input schema / properties / path / descriptionPrevious value: -"app or monorepo-root directory (default \".\")"New value: +"app or monorepo-root directory on this machine (default \".\"), not a URL"
- Changed
archprint_recommend1 field changed- changed
Input schema / properties / path / descriptionPrevious value: -"app or monorepo-root directory (default \".\")"New value: +"app or monorepo-root directory on this machine (default \".\"), not a URL"
- Changed
archprint_scan1 field changed- changed
Input schema / properties / path / descriptionPrevious value: -"app or monorepo-root directory (default \".\")"New value: +"app or monorepo-root directory on this machine (default \".\"), not a URL"
3 tool updates
v0.8.3- First observed
archprint_explain - First observed
archprint_recommend - First observed
archprint_scan
TDQS
Scored across 4 tools
Each tool has a distinct role: check validates a change, scan lists rules, explain details one rule, and recommend proposes a rule set. There is mild overlap among the three read-only rule tools (scan and explain both report violating files), but descriptions make the boundaries clear enough to select correctly.
All four tools follow an identical archprint_<verb> pattern (check, explain, recommend, scan) with consistent snake_case and clear verb-based naming.
Four tools is slightly lean but each covers a distinct phase of the rule lifecycle (discover, understand, propose, validate). It is not bloated, though the small surface leaves little room for the write-side operations the domain implies.
The server covers reading rules, explaining them, recommending them, and checking changes, but has no tool to actually adopt/write rules even though archprint init, generate, and wire are referenced repeatedly in descriptions. An agent can recommend and validate but cannot complete the adoption lifecycle, a notable gap.
Maintenance
Related MCP Connectors
Stateless TS/JS compiler facts for agents: references, imports, impact. No repo index or OAuth.
Enforces architectural invariants and code quality rules inside your AI agent's edit loop.
Evidence-backed architecture-quality analysis for Python agent applications.
Code intelligence platform for AI agents. 20 tools for architecture, security & impact analysis.
Related MCP Servers
- AlicenseAqualityDmaintenanceAnalyzes codebases to generate dependency graphs and architectural insights across multiple programming languages, helping developers understand code structure and validate against architectural rules.627 npm20MIT
- AlicenseNot gradedqualityBmaintenanceGives your AI coding agent a bounded map of an unfamiliar TypeScript SaaS repo: where the code lives, what's risky to touch, and where the money / auth / user flows are. Provides eight MCP tools for orientation, task focus, risk assessment, and SaaS-specific observations without AST or type checking.14 npmMIT
- AlicenseAqualityBmaintenanceIndexes any TypeScript / React / Next.js repo into a queryable code graph and exposes 13 MCP tools — who-renders, who-calls, find-references, blast-radius, find-cycles, dead-code orphans, and local semantic search — so agents query structure instead of reading whole files. Built on ts-morph, so edges are resolved, not grepped.142MIT
- AlicenseNot gradedqualityDmaintenanceFramework-agnostic architecture-rule enforcement for AI agents: analyzes Python codebases import graphs against declarative rulesets to report layer, forbidden-import, and cycle violations with file and line numbers. Supports MCP, OpenAI, Anthropic, LangChain, LlamaIndex, and CLI integrations.MIT