archprint 0.8.1 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,27 +8,82 @@
8
8
 
9
9
  **Mine the architecture rules your repo already enforces, with the evidence attached.**
10
10
 
11
- Archprint scans a TypeScript repository's real import graph, finds the architectural boundaries the code
12
- already respects, and turns the ones that pass a statistical confidence gate into deterministic, ready to
13
- install lint rules. Every rule ships with the evidence behind it: how many files conform, how many break it,
14
- and how confident the inference is.
11
+ **[Try it in your browser](https://stackblitz.com/github/Tommkruix/archprint-demo)**, nothing to install ·
12
+ [Quick start](#quick-start) · [Use with AI agents](#use-it-with-ai-coding-agents) · [Docs](https://tommkruix.github.io/archprint/)
15
13
 
16
- **Find the rules your code already follows:**
14
+ ## What it does, in plain words
15
+
16
+ Every codebase has unwritten rules. "Pages never talk to the database directly." "Shared code never reaches back
17
+ into the app." Nobody wrote them down, but the code follows them, until one day someone (or an AI assistant)
18
+ breaks one without noticing.
19
+
20
+ Archprint reads a TypeScript project, finds the rules its code already follows, and shows you the proof for each
21
+ one: how many files follow it, which files break it, and how sure it is. The rules you trust become automatic
22
+ checks in the tools your team already runs, so a break is caught the next time lint runs (in your editor, a
23
+ pre-commit hook or CI), not weeks later in review.
24
+
25
+ Think of it as a building inspector who surveys the house first and writes down how it was actually built,
26
+ instead of handing you a rulebook from somewhere else.
27
+
28
+ - **For developers and tech leads:** inferred, evidence-backed lint rules for ESLint and dependency-cruiser,
29
+ generated with `init`, connected with `wire`, and removed with `eject`.
30
+ - **For teams using AI coding agents:** Claude Code, Cursor and other agents can ask Archprint for the project's
31
+ rules, with the evidence, before they write code.
32
+ - **For anyone evaluating a codebase:** a quick, honest picture of how a project is actually structured.
33
+
34
+ Your `CLAUDE.md` is guidance. Your lint rules are enforcement. Archprint closes the gap by generating the
35
+ enforcement from patterns your codebase already demonstrates, so you adopt rules you can trust instead of
36
+ authoring them by hand.
37
+
38
+ ## See it in action
39
+
40
+ The recordings below use [archprint-demo](https://github.com/Tommkruix/archprint-demo), a small Next.js API whose
41
+ routes reach the database only through a service layer.
42
+
43
+ **1. Find the rules your code already follows.** Each rule comes with its evidence.
17
44
 
18
45
  ![archprint scan listing the rules a Next.js API already follows, with the evidence for each](docs/public/demo/scan.gif)
19
46
 
20
- **See the evidence behind one:**
47
+ **2. See why one rule is trusted.** The confidence check, step by step.
21
48
 
22
49
  ![archprint explain showing the confidence gate behind AP-001](docs/public/demo/explain.gif)
23
50
 
24
- **Or just ask your agent.** Claude Code calls archprint over MCP on its own:
51
+ **3. Turn the rules on, watch a break get caught, and remove it all again.** Archprint writes its files, adds one
52
+ line to your ESLint config, lint flags a route that imports the database directly, and `eject` restores your
53
+ project exactly.
54
+
55
+ ![archprint init and wire adding the rules to ESLint, lint catching a route that imports the database, and eject restoring the config exactly](docs/public/demo/enforce.gif)
56
+
57
+ **Try it yourself, nothing to install:** [open the demo in StackBlitz](https://stackblitz.com/github/Tommkruix/archprint-demo).
58
+ The scan runs as soon as it opens, and the [demo's README](https://github.com/Tommkruix/archprint-demo#try-it)
59
+ walks through enforcing a rule in ESLint, breaking it, and asking for the rules over MCP.
60
+
61
+ ## Use it with AI coding agents
62
+
63
+ AI coding agents can ask Archprint for a project's rules through
64
+ [MCP](https://modelcontextprotocol.io), an open standard that lets agents use outside tools. You ask in plain
65
+ words; the agent calls Archprint on its own and answers with the evidence. Archprint only reports. Enforcement
66
+ still runs in your linter.
67
+
68
+ **Claude Code** calls Archprint by itself when you ask about the architecture:
25
69
 
26
70
  ![Claude Code answering "What architecture rules does this repo already follow?" by calling archprint](docs/public/demo/claude-code.gif)
27
71
 
28
- **Measured, with and without archprint.** The same question in Claude Code (Opus 5.5) on the demo app (70
29
- files), five runs each, median [range]:
72
+ **Cursor works the same way.** In these recordings Cursor was set to Grok 4.7, not Claude. In the desktop app's
73
+ chat, its agent called Archprint's scan tool on its own; this screenshot shows the tool result and the answer:
74
+
75
+ ![Cursor's desktop chat, running Grok 4.7, answering from archprint's scan result: the rules and lib/db.ts as the one exception](docs/public/demo/cursor-app.png)
76
+
77
+ In the terminal (`cursor-agent`), it asks once before running the tool, then answers from it:
78
+
79
+ ![Cursor's terminal agent, running Grok 4.7, approving archprint_scan once and answering with the rules and lib/db.ts as the one exception](docs/public/demo/cursor.gif)
30
80
 
31
- | | Without archprint | With archprint |
81
+ ### Measured: the same question with and without Archprint
82
+
83
+ We asked Claude Code (Opus 5.5) "What architecture rules does this repo already follow?" on the demo app (70
84
+ files), five runs each way. Median [range]:
85
+
86
+ | | Without Archprint | With Archprint |
32
87
  | ----------------- | ------------------------- | ------------------------- |
33
88
  | Tokens read | 81k [58k to 96k] | 52k [52k to 52k] |
34
89
  | Tokens written | 1.1k [1.1k to 1.4k] | 0.6k [0.6k to 0.6k] |
@@ -36,90 +91,47 @@ files), five runs each, median [range]:
36
91
  | Time | 17 s [15 to 19] | 10 s [9 to 31] |
37
92
  | Tool calls | 5 [4 to 10] | 2 [2 to 2] |
38
93
 
39
- Both found the main rule (routes reach the database only through `lib/services/`). With archprint, every run gave
40
- the evidence for each rule and named the one file that breaks one (`lib/db.ts` reads `process.env` outside the
41
- config layer); no run without it noticed that. Without archprint, Claude also described naming conventions
42
- archprint does not check. About 50k of the tokens read in both columns are Claude Code's own system prompt. This
43
- is one small repo; larger ones are not measured yet.
44
- [Method, harness and every answer](https://github.com/Tommkruix/archprint-demo/tree/main/bench).
94
+ What the answers showed:
45
95
 
46
- **Try it in your browser, nothing to install:** [open the demo in StackBlitz](https://stackblitz.com/github/Tommkruix/archprint-demo).
47
- The scan runs as soon as it opens, and the [demo's README](https://github.com/Tommkruix/archprint-demo#try-it)
48
- walks through enforcing a rule in ESLint, breaking it, and asking for the rules over MCP.
96
+ - Both found the main rule: routes reach the database only through `lib/services/`.
97
+ - With Archprint, every run gave the evidence for each rule and named the one file that breaks one (`lib/db.ts`
98
+ reads `process.env` outside the config layer). No run without it noticed that.
99
+ - Without Archprint, Claude also described naming conventions Archprint does not check.
49
100
 
50
- Your `CLAUDE.md` is guidance. Your lint rules are enforcement. Archprint closes the gap by generating the
51
- enforcement from patterns your codebase already demonstrates, so you adopt rules you can trust instead of
52
- authoring them by hand.
101
+ Read these numbers with care: about 50k of the tokens read in both columns are Claude Code's own system prompt,
102
+ and this is one small repo; larger ones are not measured yet.
103
+ [Method, harness and every answer](https://github.com/Tommkruix/archprint-demo/tree/main/bench).
53
104
 
54
- When an AI agent is doing the writing, it can read those inferred rules and their evidence on demand through
55
- Archprint's read-only MCP server (`archprint mcp`), so the context it works from is your codebase's real,
56
- evidence-backed boundaries rather than a hand-written summary. It reports; enforcement still runs in your
57
- linter. See [Use with AI agents](#use-with-ai-agents-mcp).
58
-
59
- Validated at scale: `scan` and `recommend` ran across all 92,861 real public TypeScript repositories with zero
60
- crashes, and the full `init`/`wire`/`eject` round-trip ran clean on a 2,000-repo stratified sample. A companion
61
- benchmark, [AgentRuleBench](https://github.com/Tommkruix/agentrulebench), measures the guidance-vs-enforcement
62
- question directly (a pre-registered, honest null result on the boundary it tested).
63
-
64
- **What auto-enforces vs. what you review.** Archprint is honest about which of its inferences it will stand
65
- behind unattended. An adversarial correctness audit (three rounds over four real repositories) found that the
66
- _mechanical_ families, ones grounded in unambiguous signals (no cycles, production must not import tests, no
67
- `console` in library code, no undeclared dependencies, deep-relative import style, public-API barrels, and the
68
- DB/UI-in-server-entry rule), had zero false positives every round. So those auto-generate as enforcement. The
69
- families held for human review by default, emitted only with `--include-structural`, are the ones that infer a
70
- "layer" or "role" from paths, which can be wrong (layer and role boundaries, UI/data separation, entry purity,
71
- server/client, feature-slice and app isolation), plus dependency hygiene, whose enforcement can over-flag.
72
- Nothing that could be wrong is written as enforcement without you opting in.
73
-
74
- > Status: published on npm and safe to run on your real repo. Every rule is review-gated by default,
75
- > reversible in one command (`archprint eject`), and deterministic, and a rule archprint marks
76
- > "enforce now" is checked to pass on your code before it says so. `scan` and `recommend` are stable;
77
- > the structural families stay review-only while they are hardened. Versioning is still 0.x, so the CLI
78
- > surface and rule format can refine between minor versions (the compact `.archprint/` layout arrived in
79
- > 0.6.0; `archprint migrate` upgrades an older setup in place), but the analysis is not experimental.
80
-
81
- ## What makes it different
82
-
83
- Established TypeScript tools (dependency-cruiser, eslint-plugin-boundaries, Nx, Sheriff, ts-arch) all
84
- **enforce** architecture rules you write by hand. Archprint **infers** them from the actual import graph and
85
- **gates each one on statistical evidence** before proposing it. Across the TypeScript ecosystem, no other tool
86
- does either (see the comparison below). It then emits into those existing tools' formats, so it complements
87
- your stack rather than replacing it.
88
-
89
- ## Install
105
+ Setup for Claude Desktop, Claude Code, Cursor and other clients is in [MCP setup](#mcp-setup).
90
106
 
91
- ```bash
92
- npm install --save-dev archprint
93
- ```
107
+ ## Quick start
94
108
 
95
- Then run it (or use `npx archprint …` without installing):
109
+ Requires Node 20 or newer. Run these in a folder with a `tsconfig.json`. In a monorepo, `scan` and `recommend`
110
+ also accept the root and cover every app, while `init` and `generate` work on one app at a time (for example
111
+ `apps/web`); at a root with several apps they stop, list the apps, and ask you to rerun with one.
96
112
 
97
113
  ```bash
114
+ # 1. See the rules your code already follows, with the evidence. Changes nothing.
98
115
  npx archprint scan .
99
- ```
100
116
 
101
- Or build from source:
117
+ # 2. Set up enforcement for the rules your code already follows cleanly,
118
+ # and record what to review or adopt next in .archprint/config.json
119
+ npx archprint init .
102
120
 
103
- ```bash
104
- git clone https://github.com/Tommkruix/archprint
105
- cd archprint
106
- npm ci
107
- npm run build
108
- node dist/cli.js scan <path-to-your-app>
121
+ # 3. Connect the generated rules to your ESLint / dependency-cruiser config (one managed line)
122
+ npx archprint wire
123
+
124
+ # Then run your linter as usual. To undo everything, exactly:
125
+ npx archprint eject
109
126
  ```
110
127
 
111
- Requires Node >= 20. Point Archprint at an app directory that has a `tsconfig.json` (for a monorepo, a
112
- package such as `apps/web`; a monorepo root is fine too, Archprint discovers the app directories).
128
+ To keep it in the project instead of using `npx`: `npm install --save-dev archprint`.
113
129
 
114
- ## Quick start
130
+ More commands for a deliberate, step-by-step setup:
115
131
 
116
132
  ```bash
117
- # One-shot setup: detect the stack, enforce the rules your code already follows,
118
- # and record what to adopt next in .archprint/config.json
119
- archprint init apps/web
120
-
121
- # See the rules your repo already follows, with the evidence
122
- archprint scan apps/web
133
+ # Inspect the evidence behind one rule
134
+ archprint explain AP-002 apps/web
123
135
 
124
136
  # Write the auto-trusted (mechanical) rules to .archprint/, only for the linters your repo uses.
125
137
  # Structural-inference rules are held for review; add --include-structural to emit them too.
@@ -128,85 +140,74 @@ archprint generate apps/web
128
140
  # Confirm the generated rules pass on your repo before wiring
129
141
  archprint generate apps/web --check
130
142
 
131
- # Inspect the gate evidence behind one rule
132
- archprint explain AP-002 apps/web
133
-
134
143
  # Generate a single rule by id after reviewing it (including a SUGGEST rule)
135
144
  archprint generate apps/web --rule AP-001
136
145
 
137
146
  # Recommend a rule set from the evidence and the detected stack (fresh repos too)
138
147
  archprint recommend apps/web
139
148
 
140
- # Reference the generated rules from the enforcement tools your repo uses (managed, reversible)
141
- archprint wire
142
-
143
- # Remove archprint's files and any wired references (clean uninstall)
144
- archprint eject
145
-
146
- # Upgrading from 0.5.x? move an older archprint-rules/ setup to the .archprint layout
149
+ # Upgrading from 0.5.x? Move an older archprint-rules/ setup to the .archprint layout
147
150
  archprint migrate
148
151
  ```
149
152
 
150
- Re-running `generate` (or `init`) refreshes the files in `.archprint/` and removes any rule the
151
- evidence no longer supports, so the output never drifts from the current codebase. `wire` detects the
152
- enforcement tools your repo already uses (a flat eslint config, a `.dependency-cruiser.json`) and inserts a
153
- single managed reference into each, one that survives those regenerations; `eject` removes archprint's files
154
- and every wired reference, restoring each config exactly. For a tool config it cannot safely edit (a JS
155
- dependency-cruiser config, say), it prints the exact snippet to paste. The flagship forbidden-import rules
156
- (AP-) ship as a generated local eslint plugin that the eslint reference activates, so wiring the eslint config
157
- enforces them too, no extra install.
158
-
159
- `recommend` sorts every rule family into three tiers: rules your code already
160
- follows (enforce now), rules with thin evidence (review and adopt), and rules that
161
- comparable repos commonly follow but yours does not yet (adopt from day one). Each
162
- recommendation carries the evidence behind it: the share of comparable repos (your
163
- detected stack, else overall) that already enforce that rule, mined from a census of
164
- tens of thousands of public TypeScript repositories. The "adopt from day one" tier is
165
- driven by that census rather than hand-picked defaults, so on a fresh repo, where
166
- there is little code to infer from, it still gives you a stack-aware baseline backed
167
- by what the ecosystem actually does.
168
-
169
- ## Example
170
-
171
- A real scan of [inbox-zero](https://github.com/elie222/inbox-zero) (`apps/web`, 2,232 TypeScript files),
172
- trimmed:
153
+ Or build from source:
173
154
 
155
+ ```bash
156
+ git clone https://github.com/Tommkruix/archprint
157
+ cd archprint
158
+ npm ci
159
+ npm run build
160
+ node dist/cli.js scan <path-to-your-app>
174
161
  ```
175
- Scanned 2,232 TypeScript files
176
- Workspace aliases: 18 resolved
177
162
 
178
- GENERATED RULES
179
- AP-002 no-ui-layer-in-server-entry confidence 97%
180
- Evidence: 216/217 role files conform (99.5% observed)
181
- Exceptions: 1
163
+ ## How it decides what to trust
182
164
 
183
- LAYER BOUNDARIES (review before enforcing)
184
- utils !-> app layer boundary confidence 99%
185
- Evidence: 650/653 utils files conform (99.5%); 451 app file(s) depend on utils
186
- hooks !-> app layer boundary confidence 94%
187
- Evidence: 65/65 hooks files conform (100%); 121 app file(s) depend on hooks
188
- ```
165
+ Archprint is deliberately cautious: one wrong rule hurts more than no rule. Every candidate rule passes two checks
166
+ before it is turned on for you.
167
+
168
+ **1. Is there enough evidence?** Seeing 5 of 5 files follow a pattern is not proof; 40 of 40 is. Archprint scores
169
+ each rule with a **Wilson score lower bound**, a standard statistical measure that combines how often the rule
170
+ holds with how many files it was checked on. Each rule lands in one of three groups:
171
+
172
+ - **AUTO** (enforceable): the 95% lower bound on conformance is at least 90%, with at most 3 exceptions and a
173
+ confidently classified role.
174
+ - **SUGGEST** (provisional): the pattern holds in at least 80% of files and the role is at least 50% certain,
175
+ but one AUTO condition fails: the confidence floor is under 90% (too few files, or too many that break it),
176
+ more than 3 files break it, or the role is under 80% certain. Surfaced for review, not auto-generated.
177
+ - **REJECT**: not enough signal.
178
+
179
+ **2. Could the rule itself be wrong?** A rule can pass the numbers and still be wrong if Archprint guessed a
180
+ folder's purpose incorrectly. So only the **mechanical** families, which rest on unambiguous signals, are trusted
181
+ without review: forbidden imports (AP-001, AP-002), circular dependencies, test isolation, import style, console
182
+ isolation, and public-API barrels. An adversarial correctness audit (three rounds over four real repositories)
183
+ found zero false positives in these every round. All of them except circular dependencies are written as lint
184
+ rules; for cycles, Archprint reports the result but does not write a rule yet.
189
185
 
190
- `AP-002` is a mechanical family, so it auto-generates as enforcement. The layer boundaries are inferred, so
191
- they are shown for review, not written as enforcement unless you pass `--include-structural`.
186
+ The **structural** families infer a "layer" or "role" from paths, which can be wrong (layer and role boundaries,
187
+ UI/data separation, entry purity, server/client, feature-slice and app isolation, env access, workspace package
188
+ API, stories isolation). Dependency hygiene, whose enforcement can over-flag, and dependency declaration are held
189
+ back too. All of these are held for your review by default and written as enforcement only with
190
+ `--include-structural`, regardless of their statistical score, until they earn the same clean record. Nothing
191
+ that could be wrong is enforced without you opting in.
192
192
 
193
- Every number is measured from the import graph, not estimated.
193
+ Generated rules are green by construction: each one lets through the few known exception files it was inferred
194
+ from, so adopting it keeps your lint green while new violations are still caught, and a self-consistency check
195
+ refuses to write a rule whose evidence does not hold together. To run the generated rules against your code before
196
+ connecting them, use `archprint generate --check`.
194
197
 
195
- ## What Archprint detects
198
+ ## What it can detect
196
199
 
197
- Ships as: **Auto** = auto-generated as enforcement (mechanical families, 0 false positives across the
198
- correctness audit). **Review** = held for human review by default; emit with `--include-structural` (the
199
- inferred layer/role can be wrong, so it is not enforced silently). **Report** = surfaced only, never enforced.
200
+ **Ships as:** **Auto** = turned on as enforcement (mechanical families). **Review** = held for your review by
201
+ default; emit with `--include-structural`. **Report** = shown only, never enforced.
200
202
 
201
- Framework aware: Archprint recognizes the stack (Next.js, Nest, SvelteKit, Nuxt, Remix) and classifies UI
202
- components across React (`.tsx`), Angular (`.component.ts`, `.directive.ts`), and Vue and Svelte single-file
203
- components (it reads the `<script>` block of `.vue`/`.svelte` files), so the component-aware rules apply
204
- regardless of framework.
203
+ Archprint recognizes the stack (Next.js, Nest, SvelteKit, Nuxt, Remix) and classifies UI components across React
204
+ (`.tsx`), Angular (`.component.ts`, `.directive.ts`), and Vue and Svelte single-file components (it reads the
205
+ `<script>` block of `.vue`/`.svelte` files), so the component-aware rules apply regardless of framework.
205
206
 
206
207
  | Detector | Rule it can infer | Ships as |
207
208
  | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------- |
208
209
  | 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 |
209
- | Circular dependencies | The module graph should stay acyclic (gated on how cycle free it already is) | Auto |
210
+ | Circular dependencies | The module graph should stay acyclic (gated on how cycle free it already is); reported, no lint rule written yet | Report |
210
211
  | Test isolation | Production (non-test) code must not import test or spec files | Auto |
211
212
  | Dependency hygiene | Import third-party packages by their public entry, not a dependency's `src`/`internal` internals | Review |
212
213
  | Dependency declaration | Every imported third-party package must be declared in `package.json` (no phantom/transitive deps) | Review |
@@ -226,63 +227,58 @@ regardless of framework.
226
227
  | Orphan modules | Files nothing imports and that are not framework entries (dead code candidates) | Report |
227
228
  | Transitive reachability | A layer boundary that a plain import rule passes but that leaks through an intermediary layer | Report |
228
229
 
229
- ## The confidence gate
230
-
231
- Archprint never proposes a rule as enforceable on a thin sample. Each candidate is scored with a **Wilson
232
- score lower bound** on its true conformance rate, which fuses the observed ratio and the sample size into one
233
- number, so 5 of 5 clean files is not treated as evidence of a 90% rule but 40 of 40 is.
234
-
235
- - **AUTO** (enforceable): the 95% lower bound on conformance is at least 90%, with at most 3 exceptions and a
236
- confidently classified role.
237
- - **SUGGEST** (provisional): the pattern looks like a rule (at least 80% observed) but the sample is too thin
238
- to be confident. Surfaced for review, not auto generated.
239
- - **REJECT**: not enough signal.
240
-
241
- The statistical gate is necessary but not sufficient: a rule can be statistically clean yet semantically wrong
242
- if the inferred "layer" or "role" is not real. So a second, evidence-based gate sits on top of it: only the
243
- _mechanical_ families (see the "Ships as: Auto" rows above), which an adversarial correctness audit found had
244
- zero false positives across three rounds and four repositories, auto-generate as enforcement. The
245
- _structural-inference_ families are capped at review regardless of their statistical score until they earn the
246
- same clean record. The bias is deliberate and conservative: one wrong enforced rule hurts credibility more than
247
- zero rules.
248
-
249
- ## Output formats
250
-
251
- `archprint generate` writes a minimal `.archprint/` directory: one self-contained rules file per linter your
252
- repo actually uses, plus a `config.json` that records what is enforced and what Archprint manages. It detects
253
- ESLint and dependency-cruiser and emits each rule for a tool you already run, so you are not left with config
254
- for a tool you do not have. `--emit <eslint|dependency-cruiser|all>` forces the format. The default output:
255
-
256
- - **`.archprint/eslint.mjs`**: one self-contained ESLint flat-config file that inlines every inferred ESLint
257
- rule (marker-based forbidden imports, `no-restricted-imports` import-style boundaries, console isolation) and
258
- needs only eslint, so you can commit it, publish it, or hand it to another repo and adopt it in one line
259
- (`import archprint from './.archprint/eslint.mjs'`). It self-ignores `**/.archprint/**`.
260
- - **`.archprint/dependency-cruiser.json`** (when dependency-cruiser is present): one `forbidden` ruleset with
261
- the mechanical boundaries (public-API deep-import, test-isolation); the review-held ones (layer, role-layering,
230
+ `recommend` (and `init`) sort every rule family into tiers: rules your code already follows (enforce now), rules
231
+ your code follows that Archprint reports but does not write yet (circular dependencies today), rules with thin
232
+ evidence (review and adopt), and rules that comparable repos commonly follow but yours does not yet (adopt from day
233
+ one). Each recommendation carries the share of comparable repos (your detected stack, else
234
+ overall) that already enforce that rule, mined from a census of tens of thousands of public TypeScript
235
+ repositories. So even a fresh repo, with little code to learn from, gets a stack-aware baseline backed by what the
236
+ ecosystem actually does rather than hand-picked defaults.
237
+
238
+ ## What it writes to your project
239
+
240
+ `archprint generate` (and `init`) writes a minimal `.archprint/` folder, and only for the linters your repo
241
+ actually uses. It detects
242
+ ESLint and dependency-cruiser and emits each rule for a tool you already run, so you are not left with config for
243
+ a tool you do not have. `--emit <eslint|dependency-cruiser|all>` forces the format.
244
+
245
+ - **`.archprint/eslint.mjs`**: one self-contained ESLint flat-config file that inlines every inferred ESLint rule
246
+ (marker-based forbidden imports, `no-restricted-imports` import-style boundaries, console isolation) and needs
247
+ no extra plugins: it adds rules to your existing ESLint setup, which already parses your TypeScript. So you can
248
+ commit it, publish it, or hand it to another repo and adopt it in one line
249
+ (`import archprint from './.archprint/eslint.mjs'`). It self-ignores `**/.archprint/**`. The forbidden-import
250
+ rules (AP-) ship as a generated local eslint plugin inside it, so wiring the eslint config enforces them too, no
251
+ extra install.
252
+ - **`.archprint/dependency-cruiser.json`** (when dependency-cruiser is present): one `forbidden` ruleset with the
253
+ mechanical boundaries (public-API deep-import, test-isolation); the review-held ones (layer, role-layering,
262
254
  feature-slice, app-isolation, entry-purity, dependency-internals, phantom deps) are added only with
263
255
  `--include-structural`, after you review them.
264
- - **`.archprint/config.json`**: the system file, what is enforced / held / worth adopting, and the managed
265
- outputs list `eject` uses.
266
- - **A managed README section** summarizing what is enforced now, held for review, and worth adopting (written
267
- by `init`, or `generate --readme`), plus a managed `.prettierignore` entry so the generated files stay out of
268
- your formatter.
256
+ - **`.archprint/config.json`**: what is enforced, followed but only reported, held for review, and worth
257
+ adopting, plus the list of managed outputs `eject` removes.
258
+ - **A managed section in your `README.md`** summarizing what is enforced now, followed but only reported, held for
259
+ review, and worth adopting (written by `init`, or `generate --readme`), plus a managed `.prettierignore` entry so the generated files stay
260
+ out of your formatter.
269
261
 
270
262
  `--expand` additionally writes the granular artifacts inside `.archprint/`: the per-family ESLint and
271
- dependency-cruiser JSON, per-rule cards (`.md`) with passing and failing fixtures, the
272
- eslint-plugin-boundaries element-types config, ts-arch tests, and the Mermaid and Graphviz DOT layer graph.
273
-
274
- `generate --check` runs the generated ESLint rules against your repo and reports whether they pass, so you
263
+ dependency-cruiser JSON, per-rule cards (`.md`) with passing and failing fixtures, the eslint-plugin-boundaries
264
+ element-types config, ts-arch tests, and the Mermaid and Graphviz DOT layer graph.
265
+
266
+ **Staying in sync, and leaving cleanly.** Re-running `generate` (or `init`) refreshes `.archprint/` and drops any
267
+ rule the evidence no longer supports, so the output never drifts from the code. `wire` inserts a single managed
268
+ reference into each enforcement tool your repo uses (a flat eslint config, a `.dependency-cruiser.json`), one that
269
+ survives those regenerations; for a config it cannot safely edit (a JS dependency-cruiser config, say), it prints
270
+ the exact snippet to paste. `eject` removes Archprint's files and every wired reference, restoring each config
271
+ exactly. `generate --check` runs the generated ESLint rules against your repo and reports whether they pass, so you
275
272
  can confirm before wiring. Upgrading from 0.5.x? `archprint migrate` moves an older `archprint-rules/` setup to
276
273
  this layout and rewires your configs in place.
277
274
 
278
- ## Use with AI agents (MCP)
275
+ ## MCP setup
279
276
 
280
- `archprint mcp` runs archprint as an [MCP](https://modelcontextprotocol.io) server over stdio, so an agent can
281
- ask what architecture rules your repo already follows, with the evidence, before it writes code. It exposes three
282
- read-only tools: `archprint_scan`, `archprint_recommend`, and `archprint_explain`. Each rule comes back stated
283
- in plain words, with its evidence and the files that break it, and `archprint_explain` takes any rule label from
284
- the scan (for example `AP-002` or `env-access`). Point Claude Desktop, Claude Code, Cursor, or any MCP client at
285
- it:
277
+ `archprint mcp` runs Archprint as an [MCP](https://modelcontextprotocol.io) server over stdio, so an agent can ask
278
+ what architecture rules your repo already follows, with the evidence, before it writes code. It exposes three
279
+ read-only tools: `archprint_scan`, `archprint_recommend`, and `archprint_explain`. Each rule comes back stated in
280
+ plain words, with its evidence and the files that break it, and `archprint_explain` takes any rule label from the
281
+ scan (for example `AP-002` or `env-access`). Point Claude Desktop, Claude Code, Cursor, or any MCP client at it:
286
282
 
287
283
  ```json
288
284
  {
@@ -292,23 +288,53 @@ it:
292
288
  }
293
289
  ```
294
290
 
295
- That default is a local stdio server, and it is the one to use for private code: it runs on your machine against
296
- your local checkout, so your source never leaves it, whichever git host you use.
291
+ **Things to ask your agent.** You do not name the tools; the agent picks them:
297
292
 
298
- To scan a public repository by URL instead, run a remote server over HTTP with `archprint mcp --http`. It clones
299
- the repo shallow to a temp dir, runs the same read-only analysis, returns the result, and deletes the clone (only
300
- public `github.com`, `gitlab.com`, and `bitbucket.org` URLs; nothing is written or kept). The tools then take a
301
- `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
302
- to your own machine, or `--port`/`$PORT` to change the port) and answers health checks at `/health` (use this one
303
- on Cloud Run, which reserves `/healthz`) and `/healthz`. Every request clones and scans, so a server anyone can
304
- reach spends compute on anyone's behalf: keep it behind authentication, such as Cloud Run's IAM, unless you accept
305
- that cost.
293
+ - "What architecture rules does this repo already follow?"
294
+ - "Which file breaks the env-access rule, and how should I fix it?"
295
+ - "I am adding a new API route. What rules should it follow in this codebase?"
296
+ - "Which rules should we enforce now, and which are worth adopting?"
297
+
298
+ **Your code stays on your machine.** This default is a local server: it reads your local checkout, so it is the
299
+ one to use for private code, and your source never leaves your machine, whichever git host you use.
300
+
301
+ **If the server will not start.** If the client says the server failed to start or `npx` was not found, it cannot
302
+ see your shell's `PATH`. Desktop apps opened from the Dock or Start menu do not load it, which is common when Node
303
+ comes from nvm or Homebrew. A full path to `npx` alone is not enough, because `npx` itself needs `node` on the
304
+ `PATH`. Point both at the folder that `dirname "$(which node)"` prints, for example `/opt/homebrew/bin`:
305
+
306
+ ```json
307
+ {
308
+ "mcpServers": {
309
+ "archprint": {
310
+ "command": "/opt/homebrew/bin/npx",
311
+ "args": ["-y", "archprint", "mcp"],
312
+ "env": { "PATH": "/opt/homebrew/bin:/usr/bin:/bin" }
313
+ }
314
+ }
315
+ }
316
+ ```
317
+
318
+ Starting the editor from a terminal also works, because it then inherits your shell's `PATH`.
319
+
320
+ **Scanning a public repo by URL.** `archprint mcp --http` runs a remote server instead. It clones the repo shallow
321
+ to a temp dir, runs the same read-only analysis, returns the result, and deletes the clone (only public
322
+ `github.com`, `gitlab.com`, and `bitbucket.org` URLs; nothing is written or kept). The tools then take a `repo` URL
323
+ (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
324
+ own machine, or `--port`/`$PORT` to change the port) and answers health checks at `/health` (use this one on Cloud
325
+ Run, which reserves `/healthz`) and `/healthz`. Every request clones and scans, so a server anyone can reach spends
326
+ compute on anyone's behalf: keep it behind authentication, such as Cloud Run's IAM, unless you accept that cost.
306
327
 
307
328
  The tools are read-only (they never write to the repo); use the CLI's `generate`/`wire` to actually emit and
308
329
  enforce rules.
309
330
 
310
331
  ## How it compares
311
332
 
333
+ Established TypeScript tools (dependency-cruiser, eslint-plugin-boundaries, Nx, Sheriff, ts-arch) all **enforce**
334
+ architecture rules you write by hand. Archprint **infers** them from the actual import graph and **gates each one
335
+ on statistical evidence** before proposing it. It then emits into those tools' formats, so it complements your
336
+ stack rather than replacing it.
337
+
312
338
  Verified against each tool's documentation (TypeScript ecosystem). The two columns that matter are the ones no
313
339
  other TypeScript tool fills:
314
340
 
@@ -323,59 +349,114 @@ other TypeScript tool fills:
323
349
  | madge / knip | analysis only | no | no |
324
350
 
325
351
  Honest caveat: in other ecosystems, [Tach](https://github.com/gauge-sh/tach) (Python) and ArchLint (Java) do
326
- auto-infer module boundaries, so Archprint's specific niche is auto-inference **plus statistical evidence
327
- gating in the TypeScript ecosystem**. Archprint also overlaps in detection with dependency-cruiser (cycles,
328
- orphans, reachability) and knip (dead code); rather than compete, it emits into those tools' formats.
352
+ auto-infer module boundaries, so Archprint's specific niche is auto-inference **plus statistical evidence gating
353
+ in the TypeScript ecosystem**. Archprint also overlaps in detection with dependency-cruiser (cycles, orphans,
354
+ reachability) and knip (dead code); rather than compete, it writes the rules it generates in those tools' formats.
329
355
 
330
356
  ## Commands
331
357
 
332
- | Command | What it does |
333
- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
334
- | `archprint init [path]` | Zero-config setup: detect the stack, enforce the rules the code already follows, and write `.archprint/` plus a managed README section. `--expand`, `--include-structural`, `--out <dir>`, `--fast`, `--force`. |
335
- | `archprint scan [path]` | Report the rules the repo already follows, with evidence. `--deep` resolves through barrels and aliases. |
336
- | `archprint generate [path]` | Write the auto-trusted mechanical rules to `.archprint/` for the linters your repo uses; structural rules held for review. `--emit <eslint\|dependency-cruiser\|all>` forces the format, `--only <family>` and `--rules <ids>` narrow the output, `--check` runs the generated rules against your repo, `--readme` adds the README section, `--expand` also writes the per-family files/cards/fixtures/graph, `--rule <id>` emits one reviewed rule. Also `--include-structural`, `--no-graph`, `--out <dir>`, `--fast`. |
337
- | `archprint migrate` (`upgrade`) | Move an older `archprint-rules/` setup to the `.archprint/` layout and rewire your configs in place. `--dry-run`. |
338
- | `archprint explain <id> [path]` | Show the gate breakdown for one rule, with a codeframe per exception plus how-to-fix, when-not-to-use, and how-to-enforce. |
339
- | `archprint recommend [path]` | Recommend a rule set from the repo's evidence and detected stack (works on a fresh repo too); names the installed tool that will enforce each rule. |
340
- | `archprint wire` | Reference the generated rules from the enforcement tools your repo uses (flat eslint config, `.dependency-cruiser.json`) via a managed, reversible reference. `--out <dir>`, `--dry-run`. |
341
- | `archprint eject` | Remove archprint's generated files, its config, the managed README section, and any wired references. `--out <dir>`, `--dry-run`. |
342
- | `archprint mcp` | Run archprint as an MCP server so Claude, Cursor, and other agents can call read-only `scan`, `recommend`, and `explain` tools for a repo's inferred architecture rules and evidence. Serves over stdio by default; `--http` runs a remote server that scans a public repo by URL. |
343
-
344
- ## Documentation
358
+ - **`archprint init [path]`**: zero-config setup. Detects the stack, enforces the rules the code already follows,
359
+ and writes `.archprint/` plus a managed README section. Options: `--expand`, `--include-structural`,
360
+ `--out <dir>`, `--fast`, `--force`.
361
+ - **`archprint scan [path]`**: reports the rules the repo already follows, with evidence. Changes nothing.
362
+ `--deep` resolves through barrels and aliases.
363
+ - **`archprint explain <id> [path]`**: shows the gate breakdown for one rule, with a codeframe per exception plus
364
+ how to fix, when not to use it, and how to enforce it.
365
+ - **`archprint recommend [path]`**: recommends a rule set from the repo's evidence and detected stack (works on a
366
+ fresh repo too), and names the installed tool that will enforce each rule it can write.
367
+ - **`archprint generate [path]`**: writes the auto-trusted mechanical rules to `.archprint/` for the linters your
368
+ repo uses; structural rules are held for review. `--emit <eslint|dependency-cruiser|all>` forces the format,
369
+ `--only <family>` and `--rules <ids>` narrow the output, `--check` runs the generated rules against your repo,
370
+ `--readme` adds the README section, `--expand` also writes the per-family files, cards, fixtures and graph, and
371
+ `--rule <id>` emits one reviewed rule. Also `--include-structural`, `--no-graph`, `--out <dir>`, `--fast`.
372
+ - **`archprint wire`**: references the generated rules from the enforcement tools your repo uses (flat eslint
373
+ config, `.dependency-cruiser.json`) through a managed, reversible reference. `--out <dir>`, `--dry-run`.
374
+ - **`archprint eject`**: removes Archprint's generated files, its config, the managed README section, and any
375
+ wired references, restoring each config exactly. `--out <dir>`, `--dry-run`.
376
+ - **`archprint migrate`** (alias `upgrade`): moves an older `archprint-rules/` setup to the `.archprint/` layout
377
+ and rewires your configs in place. `--dry-run`.
378
+ - **`archprint mcp`**: runs Archprint as an MCP server so Claude, Cursor, and other agents can call the read-only
379
+ `scan`, `recommend`, and `explain` tools. Serves over stdio by default; `--http` runs a remote server that scans
380
+ a public repo by URL.
381
+
382
+ `scan --json` and `recommend --json` emit stable, version-keyed JSON for scripting. Exit codes are the contract:
383
+ `0` on success, `1` on error.
384
+
385
+ ## Example on a real repo
386
+
387
+ A real scan of [inbox-zero](https://github.com/elie222/inbox-zero) (`apps/web`, 2,232 TypeScript files), trimmed:
345
388
 
346
- Full docs live in [`docs/`](./docs/): [getting started](./docs/getting-started.md),
347
- [concepts](./docs/concepts.md) (the confidence gate, mechanical vs. structural, fast vs. deep, the
348
- generate/wire/eject lifecycle), and the [rule-family reference](./docs/rules.md) (what each rule detects, how it
349
- ships, and when not to use it).
350
-
351
- ## Fast and deep modes
352
-
353
- `scan` defaults to a **fast** specifier level pass (no type checker). `generate` defaults to a
354
- **deep** pass that resolves through barrels and workspace aliases, since generation is the commitment point.
355
- Structural analysis (cycles, orphans, reachability, public-API) always uses the fast graph: it is faithful to
356
- deep resolution for those, and public-API detection in fact requires it (deep resolution would resolve through
357
- a barrel and erase the barrel-versus-deep signal).
389
+ ```
390
+ Scanned 2,232 TypeScript files
391
+ Workspace aliases: 18 resolved
358
392
 
359
- ## Determinism
393
+ GENERATED RULES
394
+ AP-002 no-ui-layer-in-server-entry confidence 97%
395
+ Evidence: 216/217 role files conform (99.5% observed)
396
+ Exceptions: 1
360
397
 
361
- Same repo plus same version produces the same output. Analysis is pure and sorted; there is no randomness.
398
+ LAYER BOUNDARIES (review before enforcing)
399
+ utils !-> app layer boundary confidence 99%
400
+ Evidence: 650/653 utils files conform (99.5%); 451 app file(s) depend on utils
401
+ hooks !-> app layer boundary confidence 94%
402
+ Evidence: 65/65 hooks files conform (100%); 121 app file(s) depend on hooks
403
+ ```
362
404
 
363
- ## Status and roadmap
405
+ `AP-002` is a mechanical family, so it auto-generates as enforcement. The layer boundaries are inferred, so they
406
+ are shown for review, not written as enforcement unless you pass `--include-structural`. Every number is measured
407
+ from the import graph, not estimated.
408
+
409
+ ## Technical notes
410
+
411
+ **Fast and deep modes.** `scan` defaults to a **fast** specifier-level pass (no type checker). `generate` defaults
412
+ to a **deep** pass that resolves through barrels and workspace aliases, since generation is the commitment point.
413
+ Structural analysis (cycles, orphans, reachability, public-API) always uses the fast graph: it is faithful to deep
414
+ resolution for those, and public-API detection in fact requires it (deep resolution would resolve through a barrel
415
+ and erase the barrel-versus-deep signal).
416
+
417
+ **Determinism.** The same repo at the same version produces the same output. Analysis is pure and sorted; there is
418
+ no randomness, and the analysis engine is pinned to an exact version.
419
+
420
+ ## Status
421
+
422
+ Published on npm and safe to run on your real repo. Every rule is review-gated by default, reversible in one
423
+ command (`archprint eject`), and deterministic, and generated rules are green by construction on the code they
424
+ were inferred from.
425
+
426
+ - **Validated at scale:** `scan` and `recommend` ran over a corpus of 92,861 public TypeScript repositories (61,690
427
+ apps) with zero crashes; 91 repos (0.1%) could not be fetched or timed out. The full `init`/`wire`/`eject`
428
+ round-trip ran clean on a 2,000-repo stratified sample.
429
+ - **Production-ready today:** `scan` and `recommend`, and auto-enforcement of the mechanical families, with a
430
+ self-consistency check at generate time, an `init` scaffolder for fresh repos, and framework coverage across
431
+ React, Angular, Vue, and Svelte. The engine (twenty detectors, the confidence gate, and emitters for a
432
+ self-contained ESLint file, dependency-cruiser, ts-arch, and the layer graph) is in place and tested.
433
+ - **Still ahead:** hardening the structural families toward auto-enforcement (a real per-file role-confidence
434
+ measure, layer cohesion, role-classifier ordering).
435
+ - **Versioning is still 0.x,** so the CLI surface and rule format can refine between minor versions. That is a
436
+ maturing surface, not experimental analysis. The compact `.archprint/` layout arrived in 0.6.0, and
437
+ `archprint migrate` upgrades an older setup in place.
438
+
439
+ A companion benchmark, [AgentRuleBench](https://github.com/Tommkruix/agentrulebench), measures the
440
+ guidance-vs-enforcement question directly (a pre-registered, honest null result on the boundary it tested).
441
+
442
+ ## Words used here
443
+
444
+ - **Import:** a line in one file that uses code from another. Archprint's rules are about which files may import
445
+ which.
446
+ - **Lint rule / linter:** an automatic check that runs on your code (ESLint is the most common one) and flags
447
+ problems as you write.
448
+ - **AUTO / SUGGEST / REJECT:** how confident Archprint is in a rule; see
449
+ [How it decides what to trust](#how-it-decides-what-to-trust).
450
+ - **Mechanical / structural families:** rules based on unambiguous signals (trusted without review) versus rules
451
+ that depend on guessing a folder's role (held for your review).
452
+ - **MCP:** an open standard that lets AI agents use outside tools such as Archprint.
364
453
 
365
- Archprint is safe to adopt today: every rule is review-gated and reversible via `archprint eject`, the analysis
366
- is deterministic, and `scan`/`recommend` are battle-tested at census scale. The engine (twenty detectors, the
367
- confidence gate, and emitters for a self-contained ESLint file, dependency-cruiser, ts-arch, and the layer
368
- graph) is in place and tested, and an adversarial correctness audit (three rounds, four real repositories) drove
369
- the false-positive rate on auto-generated rules to zero for the mechanical families, which is why those
370
- auto-enforce while the structural-inference families are held for review. Versioning is still 0.x, so the CLI
371
- surface and rule format can refine between minor versions, that is a maturing surface, not experimental
372
- analysis; the compact `.archprint/` layout arrived in 0.6.0, and `archprint migrate` upgrades an older setup
373
- in place.
454
+ ## Documentation
374
455
 
375
- Production-ready today: `scan` and `recommend` (insight), and auto-enforcement of the mechanical families,
376
- with a self-consistency check at generate time, an `init` scaffolder for fresh repos, and framework coverage
377
- across React, Angular, Vue, and Svelte. Still ahead: hardening the structural families toward auto-enforcement
378
- (a real per-file role-confidence measure, layer-cohesion, role-classifier ordering).
456
+ Full docs live at [tommkruix.github.io/archprint](https://tommkruix.github.io/archprint/) and in
457
+ [`docs/`](./docs/): [getting started](./docs/getting-started.md), [concepts](./docs/concepts.md) (the confidence
458
+ gate, mechanical vs. structural, fast vs. deep, the generate/wire/eject lifecycle), and the
459
+ [rule-family reference](./docs/rules.md) (what each rule detects, how it ships, and when not to use it).
379
460
 
380
461
  ## Contributing
381
462