ADR-013: Architecture Enforcement
Automated quality system for AI-generated code -- dependency-cruiser, Knip, jscpd, sonarjs, fitness tests, and AI-assisted review skills.
Status: Accepted Date: 2026-03-29 (enforcement audit TRO-94), matured 2026-04-03 (skills and scheduled audits TRO-97) Deciders: Kyle Olson (Solo Founder)
Decision
Audit every rule in root CLAUDE.md and all 14 package-level CLAUDE.md files. Classify each as machine-enforced, convertible, or semantic-only. Convert all convertible rules to machine enforcement. Add static analysis CI gates: dependency-cruiser (package boundaries), Knip (dead code), jscpd (duplication). Add hand-picked sonarjs complexity rules. Add Vitest architecture fitness tests. Create /arch-review and /dep-check Claude Code skills for qualitative assessment. Schedule monthly automated audits.
Context
Solo founder building a multi-tenant SaaS where AI coding agents write the majority of the code. AI agents produce code that compiles, passes type checks, and appears correct, but may silently violate architectural boundaries, duplicate existing patterns, or bypass security conventions.
The problem is not hypothetical. A Claude Code session hit a CI failure from dependency-cruiser and removed the check rather than fixing the root cause. This established the foundational principle: every rule that is not machine-enforced is effectively optional when AI agents are the primary developers.
A research phase evaluated 40+ tools. Tools were assessed on three criteria: (1) instant editor feedback so AI agents see violations while writing, (2) maturity for use as a CI gate, (3) low false-positive rate to avoid training agents to suppress warnings.
Decision Drivers
- Machine enforcement over documentation -- CI gates cannot be ignored
- Editor-time feedback -- violations caught while writing are cheaper than violations caught in CI
- Low false-positive rate -- false positives train AI agents to add
eslint-disablecomments - Maturity and adoption -- solo-maintainer projects are too risky for core enforcement
- Ratcheting over big-bang -- new rules as warnings first, promoted to errors after fixing violations
- Defense-in-depth -- overlapping tools catch different violation types
Alternatives Considered
SonarQube
Enterprise code quality platform with dashboards, security analysis, and trend tracking. Rejected because the marginal benefit over ESLint strict + sonarjs does not justify the operational overhead for a solo developer. Reconsidered if the team grows.
CodeScene
Commercial temporal coupling analysis (EUR 18/month). Rejected because social complexity metrics are meaningless for a solo developer. Replaced by scripts/hotspot-analysis.sh that cross-references git churn with ESLint complexity violations at zero cost.
ArchUnitTS
TypeScript architecture testing library (Java ArchUnit inspired). Solo maintainer, zero npm dependents at evaluation time. Rejected -- the planned fitness tests were recreated as dependency-cruiser forbidden rules with better tooling support (978K weekly npm downloads).
FTA (Fast TypeScript Analyzer)
Rust-based composite complexity scorer. ~295 GitHub stars. Rejected because the composite score is opaque with no actionable thresholds. ESLint complexity + sonarjs cognitive-complexity provide the same coverage with instant editor feedback and granular per-metric configuration.
Full sonarjs Recommended Preset
150+ error-level rules. Rejected due to high volume of false positives. 9 rules were hand-picked targeting the specific code smells AI agents produce most frequently.
Implementation
The implementation spans six tools across three enforcement layers:
| Layer | Tool | What It Catches |
|---|---|---|
| Editor-time | ESLint + sonarjs + naming-convention | Complexity, naming, banned imports, code smells |
| Editor-time | no-restricted-imports via restrictions.js | Direct SDK imports bypassing wrapper packages |
| CI | dependency-cruiser | Package layer violations, circular dependencies |
| CI | Knip | Dead code: unused exports, files, dependencies |
| CI | jscpd | Code duplication above 15% threshold |
| CI | Vitest fitness tests | authorizedProcedure in routers, MCP tool registration |
| Qualitative | /arch-review skill | 6-dimension quality audit with semantic rules |
| Qualitative | /dep-check skill | Dependency health, stale packages, CVEs |
For detailed configuration of each tool, see the reference pages in this topic:
- Dependency Graph Validation -- dependency-cruiser rules
- Dead Code Detection -- Knip workspace config
- Duplication Detection -- jscpd thresholds
- Complexity and Naming -- sonarjs rules and ESLint limits
- Fitness Tests -- Vitest architecture tests
- Hotspot Analysis -- churn x complexity analysis
- Review and Audits --
/arch-reviewskill and scheduled audits
Consequences
Positive
- Zero advisory-only rules -- every CLAUDE.md rule is either machine-enforced or explicitly marked for AI-assisted review
- Editor-time feedback -- complexity, naming, and import rules give AI agents instant red squiggles while writing
- Defense-in-depth -- ESLint catches single-file issues, dependency-cruiser catches cross-package issues, Knip catches dead code, jscpd catches duplication, fitness tests catch structural invariants
- Self-monitoring -- the arch-review-checklist includes rules about the enforcement system itself
Negative
- Tooling maintenance burden -- 5 enforcement tools each require configuration and version updates
- Configuration duplication -- the 5-layer hierarchy is encoded in both
.dependency-cruiserrc.mjsandrestrictions.js - Complexity thresholds are opinionated -- cyclomatic 20, cognitive 20, max-lines 400 are more permissive than common recommendations
Risks
- Enforcement config tampering -- mitigated by CODEOWNERS + arch-review-checklist Section 7
- Allowlist creep -- mitigated by
/arch-reviewskill checking allowlist justifications - Tool ecosystem changes -- mitigated by all tools having strong download numbers and active maintenance
- Skill drift from codebase -- mitigated by the convention "when a PR changes architecture, update docs in the same PR"
Validation
| Rule | Enforcement |
|---|---|
| Package layer hierarchy | dependency-cruiser 6 forbidden rules |
| External SDKs through wrappers | ESLint no-restricted-imports via restrictions.js |
Feature routers use authorizedProcedure | Vitest architecture test with allowlist |
No console.log | ESLint no-console: error |
| Naming conventions | @typescript-eslint/naming-convention |
| Cyclomatic/cognitive complexity max 20 | ESLint complexity + sonarjs/cognitive-complexity |
| File max 400 lines, function max 100 | ESLint max-lines + max-lines-per-function |
| No dead code | Knip in CI |
| Duplication max 15% | jscpd in CI |
| Semantic rules (RLS, CASL, audit logs) | /arch-review skill |
| Dependency health | /dep-check skill + monthly audit |
| Enforcement configs not weakened | CODEOWNERS + arch-review-checklist Section 7 |
References
- Architecture diagram
- Arch review checklist
- Full ADR source:
docs/architecture/decisions/013-architecture-enforcement.md
Architecture Enforcement
Automated multi-layer system that catches architectural violations at editor-time, pre-commit, and CI -- designed for a codebase built primarily by AI coding agents.
Layer Hierarchy
The 5-layer package DAG -- what each layer contains, allowed import directions, and how violations are caught by both ESLint and dependency-cruiser.