Skip to main content
Glama

Archprint

npm version CI npm downloads license docs Glama MCP server score Listed on mcpservers.org

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 with wire, and removed with eject.

  • For reviewers: archprint check flags 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: mcp-code-indexer-react-ts

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.

archprint scan listing the rules a Next.js API already follows, with the evidence for each

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

archprint explain showing the confidence gate behind AP-001

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.

archprint init and wire adding the rules to ESLint, lint catching a route that imports the database, and eject restoring the config 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:

Claude Code calling archprint to list the enforced and held-for-review rules, then adding a route and checking the change with archprint

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:

Cursor's desktop chat, running Grok 4.7, answering from archprint's scan result: the rules and lib/db.ts as the one exception

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

Cursor's terminal agent, running Grok 4.7, listing the enforced, held-for-review and report-only rules, then adding a route and checking 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.ts reads process.env outside 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 eject

The rules you already wrote down

Your CLAUDE.md or AGENTS.md probably states rules already ("never call fetch in a component", "packages/ui must not import the app"). Agents read them as guidance. archprint gap-report checks them against the code: your agent maps each rule-like sentence to one of a few fixed templates, quoting it, and Archprint verifies the mapping and measures it. Ask your agent "Which of the rules in our CLAUDE.md does the code actually follow?", or give the CLI the mapping file your agent wrote:

{
  "rules": [
    {
      "source": { "file": "CLAUDE.md", "quote": "Never call fetch directly in a component" },
      "name": "no-fetch-in-components",
      "template": "forbid-usage",
      "files": ["src/components/**"],
      "usage": ["fetch"]
    }
  ]
}

Each sentence comes back as enforce now (nothing breaks it today), enforce with a baseline (broken today, with the files and lines), already an archprint rule, check the mapping (the quote is not in the file, or the mapping matches no code), or guidance only. Archprint never calls a model: the agent only maps prose to a template, and every number in the report is computed from your code. Adopt the ones you approve with archprint generate <app> --declare mapping.json (add --baseline for those the code breaks today): each becomes an ESLint rule whose error is your team's sentence, with its line in CLAUDE.md, and archprint check enforces it in pull requests. See the gap report for the templates.

See 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 (on its own, --check writes nothing)
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

# Check the rules your CLAUDE.md or AGENTS.md states against the code, from a mapping your agent wrote
archprint gap-report mapping.json .

# Enforce the ones you approve (with --baseline, also those the code breaks today)
archprint generate apps/web --declare mapping.json

# Upgrading from 0.5.x? Move an older archprint-rules/ setup to the .archprint layout
archprint migrate

Or 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. The exceptions are recorded with the rule in .archprint/config.json and only shrink: when you fix one, the next generate enforces the rule there too and says so, and a file that breaks an adopted rule later is never added to its exceptions. generate stops instead, names the file, and writes nothing until you fix it or accept it with archprint allow and a reason. 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 archprint check reports a new one

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 src/internal internals

Review

Dependency declaration

Every imported third-party package must be declared in package.json (no phantom/transitive deps)

Review

Import style

Prefer workspace aliases over deep relative imports (../../../)

Auto

Console isolation

Library (non-CLI) code must not call console.*

Auto

Public API (barrel) boundaries

Files outside a feature or package must import it through its index barrel, not deep import its internals

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 "use client" module must not import a server-only module

Review

Feature-slice isolation

Sibling slices under a features/modules/slices/domains container must not import each other

Review

App isolation

Sibling apps under an apps/services container must not import each other directly

Review

Env access

Read process.env only in the config/env layer

Review

Workspace package API

Import a monorepo workspace package by its name or a subpath it exports, not a deep path into its source

Review

Stories isolation

Storybook .stories files must not be imported by other code

Review

Orphan modules

Files nothing imports and that are not framework entries (candidates to check, about half are used)

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 check reads 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 outputs eject removes.

  • .archprint/eslint.mjs (when ESLint is present): one self-contained ESLint flat-config file that inlines every inferred ESLint rule (marker-based forbidden imports, import style, test isolation, console isolation, and, when you include structural rules and do not use dependency-cruiser, env access, workspace package APIs, 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 rules ship as a generated local eslint plugin inside it, so wiring the eslint config enforces them too, no extra install. Each rule has its own name, archprint/<rule id> (for example archprint/test-isolation or archprint/no-lib-to-app), so your own settings for core rules such as no-console or no-restricted-imports never replace archprint's, and archprint's never replace yours.

  • .archprint/dependency-cruiser.json (when dependency-cruiser is present): one forbidden ruleset 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 .prettierignore entry so the generated files stay out of your formatter, and, only when you pass --readme to init or generate, a managed section in your README.md listing each adopted rule with this repo's evidence (for example "45 of 45 files it applies to follow it"), what your code follows but no rule enforces, and what is held for review.

--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 refreshes .archprint/: it keeps every rule you adopted, names any the evidence no longer supports and drops it, so the output never drifts from the code. generate --reset (and init --force) start again from the default rule set and list the adopted rules they remove. 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 with your own ESLint (version 9 or later, installed in the repo) and reports whether they pass; on its own it checks the rules already generated and writes nothing. Once they are wired, it lints through your ESLint config, the one your editor and CI run, and names any generated rule that config turns off or overrides for some files. Before wiring, it checks the rules on their own. 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 counts like ESLint's bulk suppressions, with the base as the baseline: a file that already broke a rule and now breaks it more often is reported, with every break of that rule in the file. 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@v1

It 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 github
  • Warning by default. Add --fail-on new (or the Action's fail-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. check reads the adopted rules in .archprint/config.json, written by init and generate, 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 --noEmit in CI, or rely on the type check next build runs 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 app import/no-unresolved or dependency-cruiser's not-to-unresolvable tend to report false errors on paths aliases, package exports and 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 the allowed list of .archprint/config.json, check stops counting it and lists it with its reason in the pull request, and the next archprint generate stops the generated rules flagging it. An entry without a reason is rejected, and an entry the code no longer needs is pointed out.

  • Existing breaks burn down in the open. Each adopted rule records the files that already broke it, and check says how many are left and which the pull request fixed, for example Exceptions: 3 left, 1 fixed in this change. When a fixed exception is still recorded, it says to run archprint generate so the rule applies there too.

  • Removing rules is never silent. If a pull request deletes .archprint/config.json, or the rules in it, that the base branch has, check warns 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-team
  • Upgrading: from 0.8.x or earlier, run archprint generate once to record the adopted rules. Until you do, check posts a notice that it did not run and exits 0. Setups from 0.9.0 to 0.11.x keep working as they are: check still reads their rules.json and allow.json, and the next generate moves both into config.json.

  • Other CI systems: --format json gives version-keyed output, and the exit code is the contract: 0 ok, 1 new violations with --fail-on new, 2 the CI setup is wrong (for example a shallow clone without the base commit). Check out the full history (fetch-depth: 0 or 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 five read-only tools: archprint_scan, archprint_recommend, archprint_explain, archprint_check, and archprint_gap_report. 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 from the scan by its id, label or ESLint name (for example AP-002, env-access, no-lib-to-app, lib !-> app or archprint/no-lib-to-app). archprint_check reports the adopted rules the agent's current change breaks, uncommitted edits included, so it can fix them before it finishes. archprint_gap_report takes the agent's mapping of your CLAUDE.md or AGENTS.md sentences and returns which ones the code follows, breaks, or that match nothing. 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?"

  • "Which of the rules in our CLAUDE.md does the code actually follow?"

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 mcp

In 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); the gap report and check stay local, since they read your files and your change. 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, writes .archprint/, and lists every file it wrote or changed. Options: --readme (also add a managed section to README.md), --expand, --include-structural, --out <dir>, --fast, --force.

  • archprint scan [path]: reports the rules the repo already follows, with evidence. Changes nothing. --deep resolves through barrels and aliases.

  • archprint explain <rule> [path]: shows the evidence behind one rule, named by its id, label or ESLint name (AP-002, no-lib-to-app, "lib !-> app", archprint/no-lib-to-app): its gate, the files that break it, and how to adopt it. For the forbidden-import (AP-) rules it adds a codeframe per exception, how to fix, and when not to use 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> adopt one family or the rules they name (such as AP-002, import-style or the layer rule no-lib-to-app), --rules <ids> --baseline adopts the named rules even below the confidence gate, with what breaks them today recorded as exceptions that only shrink, every run keeps the rules you adopted before and --reset returns to the default set, --check runs the generated rules against your repo (writing nothing when passed on its own), --readme adds the README section, --expand also 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>, and how many recorded exceptions are left and which the change fixed. 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 (by id or ESLint name) being broken in one file, with a reason, recorded in .archprint/config.json. check stops counting it and lists it in the pull request; generate stops the generated rules flagging it. --remove takes 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 (alias upgrade): moves an older archprint-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-only scan, recommend, explain, and check tools. Serves over stdio by default; --http runs a remote server that scans a public repo by URL (scan, recommend, and explain).

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 hooks

AP-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 wire

Without 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 adds the rules it names to the ones you adopted before, so you can adopt boundaries one run at a time, and a plain archprint generate keeps them too (--reset returns to the default rule set). 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).

Path aliases. archprint reads paths and baseUrl from the app's tsconfig.json and its extends chain. When that file only lists other projects under references (the solution-style layout create-vite, Angular and Nx generate), archprint also reads the referenced project that compiles the app's files, such as tsconfig.app.json, as TypeScript does. Deep mode resolves each file with the compiler options of the project that includes it. A key whose * follows its prefix directly, such as "@*": ["./src/*"], maps each folder and source file of the target (@components/button is src/components/button), the way baseUrl folders do. In a SvelteKit project (one that declares @sveltejs/kit), archprint also reads $lib (kit.files.lib, src/lib by default) and kit.alias from svelte.config without running it, because SvelteKit writes them into a generated .svelte-kit/tsconfig.json a fresh checkout does not have; an alias the tsconfig maps itself wins.

Workspace packages. archprint reads a monorepo's packages from pnpm-workspace.yaml in a pnpm repository (one with a pnpm-lock.yaml or packageManager: pnpm@...), as pnpm does, and from package.json workspaces otherwise, each falling back to the other when it lists no packages. For your written rules (gap-report, and the doc- rules check and ESLint enforce), a package subpath import such as @acme/ui/button follows the package's exports as Node.js matches it, and the JavaScript workspaces nested below the folder you pass count too.

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 scan over 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 after eject.

  • Production-ready today: scan and recommend, check for pull requests, and auto-enforcement of the mechanical families, with a self-consistency check at generate time, an init scaffolder 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, and archprint migrate upgrades 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

MIT

Available Tools

5 tools
archprint_checkCheck a change against adopted rulesA
Read-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.

Inputs: base, path.

archprint_explainExplain an architecture ruleA
Read-onlyIdempotent

Explain one rule from archprint_scan in depth, by its id, label or ESLint name (e.g. AP-002, env-access, no-lib-to-app, "lib !-> app" or archprint/no-lib-to-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. A rule the team stated in CLAUDE.md or AGENTS.md and adopted (its id starts with doc-) comes back with the sentence the team wrote, its source line, what it enforces in plain words, its evidence and its exceptions. Read-only.

Inputs: id (required), path.

archprint_gap_reportVerify the rules in CLAUDE.md or AGENTS.mdA
Read-onlyIdempotent

Verify the rules a repo states in its context files (CLAUDE.md, AGENTS.md, .cursor/rules, ...) against its code. Read those files, then map each rule-like sentence to one template, quoting it exactly. archprint checks the quote is in the file and the mapping matches real files, measures how many files follow or break it, and returns a gap report: enforce now, enforce with a baseline (broken today, with the files), already an archprint rule, check the mapping, or guidance only. Show the report to the user: adopting a rule is their decision. Templates, with globs relative to path in ESLint syntax: "forbid-import" (files must not import targets, path globs matched on the file an import resolves to, or packages, specifier globs, each optionally limited to named imports such as {"name": "next/cache", "imports": ["unstable_cache"]}); "forbid-usage" (files must not use a global named in usage, such as "fetch", "window" or "process.env"); "forbid-file" (no new file may match files); "inferred" (the sentence is a rule archprint_scan lists; rule is its id); "none" (no template fits; give the reason, e.g. a style rule a standard linter covers). Read-only.

Inputs: mapping (required), path.

archprint_recommendRecommend architecture rulesA
Read-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.

Inputs: path.

archprint_scanScan architecture rulesA
Read-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.

Inputs: deep, path.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 5 tools

    Disambiguation4/5

    Each tool has a distinct role: scan enumerates rules the code follows, explain drills into one rule from scan, recommend synthesizes a rule set, gap_report validates rules stated in context files, and check diffs a change against adopted rules. The closest pair is scan vs gap_report (both measure conformance), but the descriptions clearly separate 'what the code already follows' from 'what the team states in CLAUDE.md'.

    Naming Consistency4/5

    All tools share the archprint_ prefix and use short, predictable suffixes (scan, explain, recommend, check, gap_report). The only deviation is gap_report, a noun compound rather than a verb, while the rest are single verbs, which slightly breaks the verb_noun rhythm.

    Tool Count5/5

    Five tools map cleanly onto one workflow: discover (scan), drill in (explain), suggest (recommend), validate team rules (gap_report), and gate a change (check). Nothing feels redundant and the set is well within a sensible range.

    Completeness3/5

    The surface covers the read/advisory lifecycle well, but every tool is explicitly read-only, so there is no way to actually adopt rules (archprint init/generate) or wire them to the linter even though the descriptions repeatedly reference those steps. An agent can diagnose but never act, which is a notable gap for the stated domain.

    Maintenance

    ActivityActive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      B
      maintenance
      Gives 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 npm
      MIT
    • A
      license
      A
      quality
      C
      maintenance
      Indexes 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.
      14
      2
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Framework-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
    • A
      license
      Not graded
      quality
      B
      maintenance
      Exposes four low-token tools — map, place, check, and plan — that let any coding agent read a codebase's declared systems, boundaries, and allowed dependency routes before it writes code. Agents can look up where a file belongs and what it may import, see whether their uncommitted edits crossed a boundary, and declare new dependencies up front so violations surface on the map, in the PR, and mid-turn instead of in review.
      45 npm
      15
      MIT