archprint 0.8.0 → 0.8.2
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/CHANGELOG.md +15 -0
- package/README.md +346 -250
- package/dist/cli/archprint-config.d.ts +1 -0
- package/dist/cli/archprint-config.d.ts.map +1 -1
- package/dist/cli/archprint-config.js +3 -0
- package/dist/cli/archprint-config.js.map +1 -1
- package/dist/cli/recommend.d.ts +1 -0
- package/dist/cli/recommend.d.ts.map +1 -1
- package/dist/cli/recommend.js +4 -1
- package/dist/cli/recommend.js.map +1 -1
- package/dist/cli/report.d.ts.map +1 -1
- package/dist/cli/report.js +17 -1
- package/dist/cli/report.js.map +1 -1
- package/dist/cli/wiring.d.ts.map +1 -1
- package/dist/cli/wiring.js +6 -3
- package/dist/cli/wiring.js.map +1 -1
- package/dist/mcp/remote/server.d.ts +1 -1
- package/dist/mcp/remote/server.js +1 -1
- package/dist/mcp/remote/server.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/mcp/server.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -8,96 +8,130 @@
|
|
|
8
8
|
|
|
9
9
|
**Mine the architecture rules your repo already enforces, with the evidence attached.**
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
14
|
+
## What it does, in plain words
|
|
17
15
|
|
|
18
|
-
|
|
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
19
|
|
|
20
|
-
|
|
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.
|
|
21
24
|
|
|
22
|
-
|
|
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.
|
|
23
27
|
|
|
24
|
-
**
|
|
25
|
-
|
|
26
|
-
|
|
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.
|
|
27
33
|
|
|
28
34
|
Your `CLAUDE.md` is guidance. Your lint rules are enforcement. Archprint closes the gap by generating the
|
|
29
35
|
enforcement from patterns your codebase already demonstrates, so you adopt rules you can trust instead of
|
|
30
36
|
authoring them by hand.
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
Archprint's read-only MCP server (`archprint mcp`), so the context it works from is your codebase's real,
|
|
34
|
-
evidence-backed boundaries rather than a hand-written summary. It reports; enforcement still runs in your
|
|
35
|
-
linter. See [Use with AI agents](#use-with-ai-agents-mcp).
|
|
36
|
-
|
|
37
|
-
Validated at scale: `scan` and `recommend` ran across all 92,861 real public TypeScript repositories with zero
|
|
38
|
-
crashes, and the full `init`/`wire`/`eject` round-trip ran clean on a 2,000-repo stratified sample. A companion
|
|
39
|
-
benchmark, [AgentRuleBench](https://github.com/Tommkruix/agentrulebench), measures the guidance-vs-enforcement
|
|
40
|
-
question directly (a pre-registered, honest null result on the boundary it tested).
|
|
41
|
-
|
|
42
|
-
**What auto-enforces vs. what you review.** Archprint is honest about which of its inferences it will stand
|
|
43
|
-
behind unattended. An adversarial correctness audit (three rounds over four real repositories) found that the
|
|
44
|
-
_mechanical_ families, ones grounded in unambiguous signals (no cycles, production must not import tests, no
|
|
45
|
-
`console` in library code, no undeclared dependencies, deep-relative import style, public-API barrels, and the
|
|
46
|
-
DB/UI-in-server-entry rule), had zero false positives every round. So those auto-generate as enforcement. The
|
|
47
|
-
families held for human review by default, emitted only with `--include-structural`, are the ones that infer a
|
|
48
|
-
"layer" or "role" from paths, which can be wrong (layer and role boundaries, UI/data separation, entry purity,
|
|
49
|
-
server/client, feature-slice and app isolation), plus dependency hygiene, whose enforcement can over-flag.
|
|
50
|
-
Nothing that could be wrong is written as enforcement without you opting in.
|
|
51
|
-
|
|
52
|
-
> Status: published on npm and safe to run on your real repo. Every rule is review-gated by default,
|
|
53
|
-
> reversible in one command (`archprint eject`), and deterministic, and a rule archprint marks
|
|
54
|
-
> "enforce now" is checked to pass on your code before it says so. `scan` and `recommend` are stable;
|
|
55
|
-
> the structural families stay review-only while they are hardened. Versioning is still 0.x, so the CLI
|
|
56
|
-
> surface and rule format can refine between minor versions (the compact `.archprint/` layout arrived in
|
|
57
|
-
> 0.6.0; `archprint migrate` upgrades an older setup in place), but the analysis is not experimental.
|
|
58
|
-
|
|
59
|
-
## What makes it different
|
|
60
|
-
|
|
61
|
-
Established TypeScript tools (dependency-cruiser, eslint-plugin-boundaries, Nx, Sheriff, ts-arch) all
|
|
62
|
-
**enforce** architecture rules you write by hand. Archprint **infers** them from the actual import graph and
|
|
63
|
-
**gates each one on statistical evidence** before proposing it. Across the TypeScript ecosystem, no other tool
|
|
64
|
-
does either (see the comparison below). It then emits into those existing tools' formats, so it complements
|
|
65
|
-
your stack rather than replacing it.
|
|
66
|
-
|
|
67
|
-
## Install
|
|
38
|
+
## See it in action
|
|
68
39
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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.
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
**2. See why one rule is trusted.** The confidence check, step by step.
|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
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
|
+

|
|
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:
|
|
69
|
+
|
|
70
|
+

|
|
71
|
+
|
|
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
|
+

|
|
76
|
+
|
|
77
|
+
In the terminal (`cursor-agent`), it asks once before running the tool, then answers from it:
|
|
78
|
+
|
|
79
|
+

|
|
80
|
+
|
|
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 |
|
|
87
|
+
| ----------------- | ------------------------- | ------------------------- |
|
|
88
|
+
| Tokens read | 81k [58k to 96k] | 52k [52k to 52k] |
|
|
89
|
+
| Tokens written | 1.1k [1.1k to 1.4k] | 0.6k [0.6k to 0.6k] |
|
|
90
|
+
| Cost per question | $0.083 [$0.078 to $0.153] | $0.037 [$0.032 to $0.076] |
|
|
91
|
+
| Time | 17 s [15 to 19] | 10 s [9 to 31] |
|
|
92
|
+
| Tool calls | 5 [4 to 10] | 2 [2 to 2] |
|
|
72
93
|
|
|
73
|
-
|
|
94
|
+
What the answers showed:
|
|
95
|
+
|
|
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.
|
|
100
|
+
|
|
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).
|
|
104
|
+
|
|
105
|
+
Setup for Claude Desktop, Claude Code, Cursor and other clients is in [MCP setup](#mcp-setup).
|
|
106
|
+
|
|
107
|
+
## Quick start
|
|
108
|
+
|
|
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.
|
|
74
112
|
|
|
75
113
|
```bash
|
|
114
|
+
# 1. See the rules your code already follows, with the evidence. Changes nothing.
|
|
76
115
|
npx archprint scan .
|
|
77
|
-
```
|
|
78
116
|
|
|
79
|
-
|
|
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 .
|
|
80
120
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
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
|
|
87
126
|
```
|
|
88
127
|
|
|
89
|
-
|
|
90
|
-
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`.
|
|
91
129
|
|
|
92
|
-
|
|
130
|
+
More commands for a deliberate, step-by-step setup:
|
|
93
131
|
|
|
94
132
|
```bash
|
|
95
|
-
#
|
|
96
|
-
|
|
97
|
-
archprint init apps/web
|
|
98
|
-
|
|
99
|
-
# See the rules your repo already follows, with the evidence
|
|
100
|
-
archprint scan apps/web
|
|
133
|
+
# Inspect the evidence behind one rule
|
|
134
|
+
archprint explain AP-002 apps/web
|
|
101
135
|
|
|
102
136
|
# Write the auto-trusted (mechanical) rules to .archprint/, only for the linters your repo uses.
|
|
103
137
|
# Structural-inference rules are held for review; add --include-structural to emit them too.
|
|
@@ -106,161 +140,145 @@ archprint generate apps/web
|
|
|
106
140
|
# Confirm the generated rules pass on your repo before wiring
|
|
107
141
|
archprint generate apps/web --check
|
|
108
142
|
|
|
109
|
-
# Inspect the gate evidence behind one rule
|
|
110
|
-
archprint explain AP-002 apps/web
|
|
111
|
-
|
|
112
143
|
# Generate a single rule by id after reviewing it (including a SUGGEST rule)
|
|
113
144
|
archprint generate apps/web --rule AP-001
|
|
114
145
|
|
|
115
146
|
# Recommend a rule set from the evidence and the detected stack (fresh repos too)
|
|
116
147
|
archprint recommend apps/web
|
|
117
148
|
|
|
118
|
-
#
|
|
119
|
-
archprint wire
|
|
120
|
-
|
|
121
|
-
# Remove archprint's files and any wired references (clean uninstall)
|
|
122
|
-
archprint eject
|
|
123
|
-
|
|
124
|
-
# 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
|
|
125
150
|
archprint migrate
|
|
126
151
|
```
|
|
127
152
|
|
|
128
|
-
|
|
129
|
-
evidence no longer supports, so the output never drifts from the current codebase. `wire` detects the
|
|
130
|
-
enforcement tools your repo already uses (a flat eslint config, a `.dependency-cruiser.json`) and inserts a
|
|
131
|
-
single managed reference into each, one that survives those regenerations; `eject` removes archprint's files
|
|
132
|
-
and every wired reference, restoring each config exactly. For a tool config it cannot safely edit (a JS
|
|
133
|
-
dependency-cruiser config, say), it prints the exact snippet to paste. The flagship forbidden-import rules
|
|
134
|
-
(AP-) ship as a generated local eslint plugin that the eslint reference activates, so wiring the eslint config
|
|
135
|
-
enforces them too, no extra install.
|
|
136
|
-
|
|
137
|
-
`recommend` sorts every rule family into three tiers: rules your code already
|
|
138
|
-
follows (enforce now), rules with thin evidence (review and adopt), and rules that
|
|
139
|
-
comparable repos commonly follow but yours does not yet (adopt from day one). Each
|
|
140
|
-
recommendation carries the evidence behind it: the share of comparable repos (your
|
|
141
|
-
detected stack, else overall) that already enforce that rule, mined from a census of
|
|
142
|
-
tens of thousands of public TypeScript repositories. The "adopt from day one" tier is
|
|
143
|
-
driven by that census rather than hand-picked defaults, so on a fresh repo, where
|
|
144
|
-
there is little code to infer from, it still gives you a stack-aware baseline backed
|
|
145
|
-
by what the ecosystem actually does.
|
|
146
|
-
|
|
147
|
-
## Example
|
|
148
|
-
|
|
149
|
-
A real scan of [inbox-zero](https://github.com/elie222/inbox-zero) (`apps/web`, 2,232 TypeScript files),
|
|
150
|
-
trimmed:
|
|
153
|
+
Or build from source:
|
|
151
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>
|
|
152
161
|
```
|
|
153
|
-
Scanned 2,232 TypeScript files
|
|
154
|
-
Workspace aliases: 18 resolved
|
|
155
162
|
|
|
156
|
-
|
|
157
|
-
AP-002 no-ui-layer-in-server-entry confidence 97%
|
|
158
|
-
Evidence: 216/217 role files conform (99.5% observed)
|
|
159
|
-
Exceptions: 1
|
|
163
|
+
## How it decides what to trust
|
|
160
164
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
Evidence: 650/653 utils files conform (99.5%); 451 app file(s) depend on utils
|
|
164
|
-
hooks !-> app layer boundary confidence 94%
|
|
165
|
-
Evidence: 65/65 hooks files conform (100%); 121 app file(s) depend on hooks
|
|
166
|
-
```
|
|
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
167
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
Every number is measured from the import graph, not estimated.
|
|
172
|
-
|
|
173
|
-
## What Archprint detects
|
|
174
|
-
|
|
175
|
-
Ships as: **Auto** = auto-generated as enforcement (mechanical families, 0 false positives across the
|
|
176
|
-
correctness audit). **Review** = held for human review by default; emit with `--include-structural` (the
|
|
177
|
-
inferred layer/role can be wrong, so it is not enforced silently). **Report** = surfaced only, never enforced.
|
|
178
|
-
|
|
179
|
-
Framework aware: Archprint recognizes the stack (Next.js, Nest, SvelteKit, Nuxt, Remix) and classifies UI
|
|
180
|
-
components across React (`.tsx`), Angular (`.component.ts`, `.directive.ts`), and Vue and Svelte single-file
|
|
181
|
-
components (it reads the `<script>` block of `.vue`/`.svelte` files), so the component-aware rules apply
|
|
182
|
-
regardless of framework.
|
|
183
|
-
|
|
184
|
-
| Detector | Rule it can infer | Ships as |
|
|
185
|
-
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------- |
|
|
186
|
-
| Forbidden imports (marker based) | A role (route handler, server entry) must not import a target (the DB client, the UI layer) | Auto |
|
|
187
|
-
| Circular dependencies | The module graph should stay acyclic (gated on how cycle free it already is) | Auto |
|
|
188
|
-
| Test isolation | Production (non-test) code must not import test or spec files | Auto |
|
|
189
|
-
| Dependency hygiene | Import third-party packages by their public entry, not a dependency's `src`/`internal` internals | Review |
|
|
190
|
-
| Dependency declaration | Every imported third-party package must be declared in `package.json` (no phantom/transitive deps) | Review |
|
|
191
|
-
| Import style | Prefer workspace aliases over deep relative imports (`../../../`) | Auto |
|
|
192
|
-
| Console isolation | Library (non-CLI) code must not call `console.*` | Auto |
|
|
193
|
-
| Public API (barrel) boundaries | Files outside a feature or package must import it through its `index` barrel, not deep import its internals | Auto |
|
|
194
|
-
| Layer boundaries | Files in one layer must not import another, inferred from the dominant dependency direction | Review |
|
|
195
|
-
| Role layering | Semantic tiers keep their direction (a REPOSITORY must not import a SERVICE, a SERVICE must not import a CONTROLLER) | Review |
|
|
196
|
-
| Entry purity | Framework entries (pages, routes, layouts) must not be imported by other first-party code | Review |
|
|
197
|
-
| UI / data separation | Reusable UI components must not import the DB/data layer directly | Review |
|
|
198
|
-
| Server / client boundary | A Next.js `"use client"` module must not import a `server-only` module | Review |
|
|
199
|
-
| Feature-slice isolation | Sibling slices under a `features`/`modules`/`slices`/`domains` container must not import each other | Review |
|
|
200
|
-
| App isolation | Sibling apps under an `apps`/`services` container must not import each other directly | Review |
|
|
201
|
-
| Env access | Read `process.env` only in the config/env layer | Review |
|
|
202
|
-
| Workspace package API | Import a monorepo workspace package by its name, not a deep path into its source | Review |
|
|
203
|
-
| Stories isolation | Storybook `.stories` files must not be imported by other code | Review |
|
|
204
|
-
| Orphan modules | Files nothing imports and that are not framework entries (dead code candidates) | Report |
|
|
205
|
-
| Transitive reachability | A layer boundary that a plain import rule passes but that leaks through an intermediary layer | Report |
|
|
206
|
-
|
|
207
|
-
## The confidence gate
|
|
208
|
-
|
|
209
|
-
Archprint never proposes a rule as enforceable on a thin sample. Each candidate is scored with a **Wilson
|
|
210
|
-
score lower bound** on its true conformance rate, which fuses the observed ratio and the sample size into one
|
|
211
|
-
number, so 5 of 5 clean files is not treated as evidence of a 90% rule but 40 of 40 is.
|
|
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:
|
|
212
171
|
|
|
213
172
|
- **AUTO** (enforceable): the 95% lower bound on conformance is at least 90%, with at most 3 exceptions and a
|
|
214
173
|
confidently classified role.
|
|
215
|
-
- **SUGGEST** (provisional): the pattern
|
|
216
|
-
|
|
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.
|
|
217
177
|
- **REJECT**: not enough signal.
|
|
218
178
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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.
|
|
185
|
+
|
|
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
|
+
|
|
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`.
|
|
197
|
+
|
|
198
|
+
## What it can detect
|
|
199
|
+
|
|
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.
|
|
202
|
+
|
|
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.
|
|
206
|
+
|
|
207
|
+
| Detector | Rule it can infer | Ships as |
|
|
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 |
|
|
210
|
+
| Circular dependencies | The module graph should stay acyclic (gated on how cycle free it already is); reported, no lint rule written yet | Report |
|
|
211
|
+
| Test isolation | Production (non-test) code must not import test or spec files | Auto |
|
|
212
|
+
| Dependency hygiene | Import third-party packages by their public entry, not a dependency's `src`/`internal` internals | Review |
|
|
213
|
+
| Dependency declaration | Every imported third-party package must be declared in `package.json` (no phantom/transitive deps) | Review |
|
|
214
|
+
| Import style | Prefer workspace aliases over deep relative imports (`../../../`) | Auto |
|
|
215
|
+
| Console isolation | Library (non-CLI) code must not call `console.*` | Auto |
|
|
216
|
+
| Public API (barrel) boundaries | Files outside a feature or package must import it through its `index` barrel, not deep import its internals | Auto |
|
|
217
|
+
| Layer boundaries | Files in one layer must not import another, inferred from the dominant dependency direction | Review |
|
|
218
|
+
| Role layering | Semantic tiers keep their direction (a REPOSITORY must not import a SERVICE, a SERVICE must not import a CONTROLLER) | Review |
|
|
219
|
+
| Entry purity | Framework entries (pages, routes, layouts) must not be imported by other first-party code | Review |
|
|
220
|
+
| UI / data separation | Reusable UI components must not import the DB/data layer directly | Review |
|
|
221
|
+
| Server / client boundary | A Next.js `"use client"` module must not import a `server-only` module | Review |
|
|
222
|
+
| Feature-slice isolation | Sibling slices under a `features`/`modules`/`slices`/`domains` container must not import each other | Review |
|
|
223
|
+
| App isolation | Sibling apps under an `apps`/`services` container must not import each other directly | Review |
|
|
224
|
+
| Env access | Read `process.env` only in the config/env layer | Review |
|
|
225
|
+
| Workspace package API | Import a monorepo workspace package by its name, not a deep path into its source | Review |
|
|
226
|
+
| Stories isolation | Storybook `.stories` files must not be imported by other code | Review |
|
|
227
|
+
| Orphan modules | Files nothing imports and that are not framework entries (dead code candidates) | Report |
|
|
228
|
+
| Transitive reachability | A layer boundary that a plain import rule passes but that leaks through an intermediary layer | Report |
|
|
229
|
+
|
|
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,
|
|
240
254
|
feature-slice, app-isolation, entry-purity, dependency-internals, phantom deps) are added only with
|
|
241
255
|
`--include-structural`, after you review them.
|
|
242
|
-
- **`.archprint/config.json`**:
|
|
243
|
-
|
|
244
|
-
- **A managed README
|
|
245
|
-
by `init`, or `generate --readme`), plus a managed `.prettierignore` entry so the generated files stay
|
|
246
|
-
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.
|
|
247
261
|
|
|
248
262
|
`--expand` additionally writes the granular artifacts inside `.archprint/`: the per-family ESLint and
|
|
249
|
-
dependency-cruiser JSON, per-rule cards (`.md`) with passing and failing fixtures, the
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
|
253
272
|
can confirm before wiring. Upgrading from 0.5.x? `archprint migrate` moves an older `archprint-rules/` setup to
|
|
254
273
|
this layout and rewires your configs in place.
|
|
255
274
|
|
|
256
|
-
##
|
|
275
|
+
## MCP setup
|
|
257
276
|
|
|
258
|
-
`archprint mcp` runs
|
|
259
|
-
|
|
260
|
-
read-only tools: `archprint_scan`, `archprint_recommend`, and `archprint_explain`. Each rule comes back stated
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
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:
|
|
264
282
|
|
|
265
283
|
```json
|
|
266
284
|
{
|
|
@@ -270,23 +288,46 @@ it:
|
|
|
270
288
|
}
|
|
271
289
|
```
|
|
272
290
|
|
|
273
|
-
|
|
274
|
-
|
|
291
|
+
**Your code stays on your machine.** This default is a local server: it reads your local checkout, so it is the
|
|
292
|
+
one to use for private code, and your source never leaves your machine, whichever git host you use.
|
|
275
293
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
`
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
294
|
+
**If the server will not start.** If the client says the server failed to start or `npx` was not found, it cannot
|
|
295
|
+
see your shell's `PATH`. Desktop apps opened from the Dock or Start menu do not load it, which is common when Node
|
|
296
|
+
comes from nvm or Homebrew. A full path to `npx` alone is not enough, because `npx` itself needs `node` on the
|
|
297
|
+
`PATH`. Point both at the folder that `dirname "$(which node)"` prints, for example `/opt/homebrew/bin`:
|
|
298
|
+
|
|
299
|
+
```json
|
|
300
|
+
{
|
|
301
|
+
"mcpServers": {
|
|
302
|
+
"archprint": {
|
|
303
|
+
"command": "/opt/homebrew/bin/npx",
|
|
304
|
+
"args": ["-y", "archprint", "mcp"],
|
|
305
|
+
"env": { "PATH": "/opt/homebrew/bin:/usr/bin:/bin" }
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Starting the editor from a terminal also works, because it then inherits your shell's `PATH`.
|
|
312
|
+
|
|
313
|
+
**Scanning a public repo by URL.** `archprint mcp --http` runs a remote server instead. It clones the repo shallow
|
|
314
|
+
to a temp dir, runs the same read-only analysis, returns the result, and deletes the clone (only public
|
|
315
|
+
`github.com`, `gitlab.com`, and `bitbucket.org` URLs; nothing is written or kept). The tools then take a `repo` URL
|
|
316
|
+
(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
|
|
317
|
+
own machine, or `--port`/`$PORT` to change the port) and answers health checks at `/health` (use this one on Cloud
|
|
318
|
+
Run, which reserves `/healthz`) and `/healthz`. Every request clones and scans, so a server anyone can reach spends
|
|
319
|
+
compute on anyone's behalf: keep it behind authentication, such as Cloud Run's IAM, unless you accept that cost.
|
|
284
320
|
|
|
285
321
|
The tools are read-only (they never write to the repo); use the CLI's `generate`/`wire` to actually emit and
|
|
286
322
|
enforce rules.
|
|
287
323
|
|
|
288
324
|
## How it compares
|
|
289
325
|
|
|
326
|
+
Established TypeScript tools (dependency-cruiser, eslint-plugin-boundaries, Nx, Sheriff, ts-arch) all **enforce**
|
|
327
|
+
architecture rules you write by hand. Archprint **infers** them from the actual import graph and **gates each one
|
|
328
|
+
on statistical evidence** before proposing it. It then emits into those tools' formats, so it complements your
|
|
329
|
+
stack rather than replacing it.
|
|
330
|
+
|
|
290
331
|
Verified against each tool's documentation (TypeScript ecosystem). The two columns that matter are the ones no
|
|
291
332
|
other TypeScript tool fills:
|
|
292
333
|
|
|
@@ -301,59 +342,114 @@ other TypeScript tool fills:
|
|
|
301
342
|
| madge / knip | analysis only | no | no |
|
|
302
343
|
|
|
303
344
|
Honest caveat: in other ecosystems, [Tach](https://github.com/gauge-sh/tach) (Python) and ArchLint (Java) do
|
|
304
|
-
auto-infer module boundaries, so Archprint's specific niche is auto-inference **plus statistical evidence
|
|
305
|
-
|
|
306
|
-
|
|
345
|
+
auto-infer module boundaries, so Archprint's specific niche is auto-inference **plus statistical evidence gating
|
|
346
|
+
in the TypeScript ecosystem**. Archprint also overlaps in detection with dependency-cruiser (cycles, orphans,
|
|
347
|
+
reachability) and knip (dead code); rather than compete, it writes the rules it generates in those tools' formats.
|
|
307
348
|
|
|
308
349
|
## Commands
|
|
309
350
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
351
|
+
- **`archprint init [path]`**: zero-config setup. Detects the stack, enforces the rules the code already follows,
|
|
352
|
+
and writes `.archprint/` plus a managed README section. Options: `--expand`, `--include-structural`,
|
|
353
|
+
`--out <dir>`, `--fast`, `--force`.
|
|
354
|
+
- **`archprint scan [path]`**: reports the rules the repo already follows, with evidence. Changes nothing.
|
|
355
|
+
`--deep` resolves through barrels and aliases.
|
|
356
|
+
- **`archprint explain <id> [path]`**: shows the gate breakdown for one rule, with a codeframe per exception plus
|
|
357
|
+
how to fix, when not to use it, and how to enforce it.
|
|
358
|
+
- **`archprint recommend [path]`**: recommends a rule set from the repo's evidence and detected stack (works on a
|
|
359
|
+
fresh repo too), and names the installed tool that will enforce each rule it can write.
|
|
360
|
+
- **`archprint generate [path]`**: writes the auto-trusted mechanical rules to `.archprint/` for the linters your
|
|
361
|
+
repo uses; structural rules are held for review. `--emit <eslint|dependency-cruiser|all>` forces the format,
|
|
362
|
+
`--only <family>` and `--rules <ids>` narrow the output, `--check` runs the generated rules against your repo,
|
|
363
|
+
`--readme` adds the README section, `--expand` also writes the per-family files, cards, fixtures and graph, and
|
|
364
|
+
`--rule <id>` emits one reviewed rule. Also `--include-structural`, `--no-graph`, `--out <dir>`, `--fast`.
|
|
365
|
+
- **`archprint wire`**: references the generated rules from the enforcement tools your repo uses (flat eslint
|
|
366
|
+
config, `.dependency-cruiser.json`) through a managed, reversible reference. `--out <dir>`, `--dry-run`.
|
|
367
|
+
- **`archprint eject`**: removes Archprint's generated files, its config, the managed README section, and any
|
|
368
|
+
wired references, restoring each config exactly. `--out <dir>`, `--dry-run`.
|
|
369
|
+
- **`archprint migrate`** (alias `upgrade`): moves an older `archprint-rules/` setup to the `.archprint/` layout
|
|
370
|
+
and rewires your configs in place. `--dry-run`.
|
|
371
|
+
- **`archprint mcp`**: runs Archprint as an MCP server so Claude, Cursor, and other agents can call the read-only
|
|
372
|
+
`scan`, `recommend`, and `explain` tools. Serves over stdio by default; `--http` runs a remote server that scans
|
|
373
|
+
a public repo by URL.
|
|
374
|
+
|
|
375
|
+
`scan --json` and `recommend --json` emit stable, version-keyed JSON for scripting. Exit codes are the contract:
|
|
376
|
+
`0` on success, `1` on error.
|
|
377
|
+
|
|
378
|
+
## Example on a real repo
|
|
379
|
+
|
|
380
|
+
A real scan of [inbox-zero](https://github.com/elie222/inbox-zero) (`apps/web`, 2,232 TypeScript files), trimmed:
|
|
330
381
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
deep resolution for those, and public-API detection in fact requires it (deep resolution would resolve through
|
|
335
|
-
a barrel and erase the barrel-versus-deep signal).
|
|
382
|
+
```
|
|
383
|
+
Scanned 2,232 TypeScript files
|
|
384
|
+
Workspace aliases: 18 resolved
|
|
336
385
|
|
|
337
|
-
|
|
386
|
+
GENERATED RULES
|
|
387
|
+
AP-002 no-ui-layer-in-server-entry confidence 97%
|
|
388
|
+
Evidence: 216/217 role files conform (99.5% observed)
|
|
389
|
+
Exceptions: 1
|
|
338
390
|
|
|
339
|
-
|
|
391
|
+
LAYER BOUNDARIES (review before enforcing)
|
|
392
|
+
utils !-> app layer boundary confidence 99%
|
|
393
|
+
Evidence: 650/653 utils files conform (99.5%); 451 app file(s) depend on utils
|
|
394
|
+
hooks !-> app layer boundary confidence 94%
|
|
395
|
+
Evidence: 65/65 hooks files conform (100%); 121 app file(s) depend on hooks
|
|
396
|
+
```
|
|
340
397
|
|
|
341
|
-
|
|
398
|
+
`AP-002` is a mechanical family, so it auto-generates as enforcement. The layer boundaries are inferred, so they
|
|
399
|
+
are shown for review, not written as enforcement unless you pass `--include-structural`. Every number is measured
|
|
400
|
+
from the import graph, not estimated.
|
|
401
|
+
|
|
402
|
+
## Technical notes
|
|
403
|
+
|
|
404
|
+
**Fast and deep modes.** `scan` defaults to a **fast** specifier-level pass (no type checker). `generate` defaults
|
|
405
|
+
to a **deep** pass that resolves through barrels and workspace aliases, since generation is the commitment point.
|
|
406
|
+
Structural analysis (cycles, orphans, reachability, public-API) always uses the fast graph: it is faithful to deep
|
|
407
|
+
resolution for those, and public-API detection in fact requires it (deep resolution would resolve through a barrel
|
|
408
|
+
and erase the barrel-versus-deep signal).
|
|
409
|
+
|
|
410
|
+
**Determinism.** The same repo at the same version produces the same output. Analysis is pure and sorted; there is
|
|
411
|
+
no randomness, and the analysis engine is pinned to an exact version.
|
|
412
|
+
|
|
413
|
+
## Status
|
|
414
|
+
|
|
415
|
+
Published on npm and safe to run on your real repo. Every rule is review-gated by default, reversible in one
|
|
416
|
+
command (`archprint eject`), and deterministic, and generated rules are green by construction on the code they
|
|
417
|
+
were inferred from.
|
|
418
|
+
|
|
419
|
+
- **Validated at scale:** `scan` and `recommend` ran over a corpus of 92,861 public TypeScript repositories (61,690
|
|
420
|
+
apps) with zero crashes; 91 repos (0.1%) could not be fetched or timed out. The full `init`/`wire`/`eject`
|
|
421
|
+
round-trip ran clean on a 2,000-repo stratified sample.
|
|
422
|
+
- **Production-ready today:** `scan` and `recommend`, and auto-enforcement of the mechanical families, with a
|
|
423
|
+
self-consistency check at generate time, an `init` scaffolder for fresh repos, and framework coverage across
|
|
424
|
+
React, Angular, Vue, and Svelte. The engine (twenty detectors, the confidence gate, and emitters for a
|
|
425
|
+
self-contained ESLint file, dependency-cruiser, ts-arch, and the layer graph) is in place and tested.
|
|
426
|
+
- **Still ahead:** hardening the structural families toward auto-enforcement (a real per-file role-confidence
|
|
427
|
+
measure, layer cohesion, role-classifier ordering).
|
|
428
|
+
- **Versioning is still 0.x,** so the CLI surface and rule format can refine between minor versions. That is a
|
|
429
|
+
maturing surface, not experimental analysis. The compact `.archprint/` layout arrived in 0.6.0, and
|
|
430
|
+
`archprint migrate` upgrades an older setup in place.
|
|
431
|
+
|
|
432
|
+
A companion benchmark, [AgentRuleBench](https://github.com/Tommkruix/agentrulebench), measures the
|
|
433
|
+
guidance-vs-enforcement question directly (a pre-registered, honest null result on the boundary it tested).
|
|
434
|
+
|
|
435
|
+
## Words used here
|
|
436
|
+
|
|
437
|
+
- **Import:** a line in one file that uses code from another. Archprint's rules are about which files may import
|
|
438
|
+
which.
|
|
439
|
+
- **Lint rule / linter:** an automatic check that runs on your code (ESLint is the most common one) and flags
|
|
440
|
+
problems as you write.
|
|
441
|
+
- **AUTO / SUGGEST / REJECT:** how confident Archprint is in a rule; see
|
|
442
|
+
[How it decides what to trust](#how-it-decides-what-to-trust).
|
|
443
|
+
- **Mechanical / structural families:** rules based on unambiguous signals (trusted without review) versus rules
|
|
444
|
+
that depend on guessing a folder's role (held for your review).
|
|
445
|
+
- **MCP:** an open standard that lets AI agents use outside tools such as Archprint.
|
|
342
446
|
|
|
343
|
-
|
|
344
|
-
is deterministic, and `scan`/`recommend` are battle-tested at census scale. The engine (twenty detectors, the
|
|
345
|
-
confidence gate, and emitters for a self-contained ESLint file, dependency-cruiser, ts-arch, and the layer
|
|
346
|
-
graph) is in place and tested, and an adversarial correctness audit (three rounds, four real repositories) drove
|
|
347
|
-
the false-positive rate on auto-generated rules to zero for the mechanical families, which is why those
|
|
348
|
-
auto-enforce while the structural-inference families are held for review. Versioning is still 0.x, so the CLI
|
|
349
|
-
surface and rule format can refine between minor versions, that is a maturing surface, not experimental
|
|
350
|
-
analysis; the compact `.archprint/` layout arrived in 0.6.0, and `archprint migrate` upgrades an older setup
|
|
351
|
-
in place.
|
|
447
|
+
## Documentation
|
|
352
448
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
(
|
|
449
|
+
Full docs live at [tommkruix.github.io/archprint](https://tommkruix.github.io/archprint/) and in
|
|
450
|
+
[`docs/`](./docs/): [getting started](./docs/getting-started.md), [concepts](./docs/concepts.md) (the confidence
|
|
451
|
+
gate, mechanical vs. structural, fast vs. deep, the generate/wire/eject lifecycle), and the
|
|
452
|
+
[rule-family reference](./docs/rules.md) (what each rule detects, how it ships, and when not to use it).
|
|
357
453
|
|
|
358
454
|
## Contributing
|
|
359
455
|
|