一键导入
enforce-architecture
// Use when adding imports, creating packages, or reviewing code to ensure tiered dependency rules are followed. Read this to understand the tier system.
// Use when adding imports, creating packages, or reviewing code to ensure tiered dependency rules are followed. Read this to understand the tier system.
Audit and install a self-correcting guardrail stack in any TypeScript project. Four-phase workflow: understand the guardrail system from this repo, inventory the target project, generate a gap analysis, then apply changes — creating missing configs and merging missing sections into existing ones.
Extend an existing guardrail installation with LLM discipline rules: complexity limits, cognitive complexity, naming conventions, ESM idioms, documentation coverage, and test coverage thresholds. Run after setup-guardrails.
Use on every commit. The pre-commit hooks run 8 checks in parallel. If any fail, read ALL errors, fix them in one pass, and retry.
Use when creating a new workspace package in a monorepo. Covers package creation, tier assignment, and all config updates required.
| name | enforce-architecture |
| description | Use when adding imports, creating packages, or reviewing code to ensure tiered dependency rules are followed. Read this to understand the tier system. |
This project enforces a tiered dependency hierarchy using eslint-plugin-boundaries. Every package is assigned to a tier. A package can import from any lower tier, but never from its own tier or higher.
This prevents circular dependencies, enforces separation of concerns, and makes the codebase navigable.
┌─────────────────────────────────────────────────────────┐
│ Apps (deployable apps) ← can import anything │
├─────────────────────────────────────────────────────────┤
│ Tier 4: orchestration ← imports 0-3 │
├─────────────────────────────────────────────────────────┤
│ Tier 3: domain/business logic ← imports 0-2 │
├─────────────────────────────────────────────────────────┤
│ Tier 2: infrastructure (DB, APIs) ← imports 0-1 │
├─────────────────────────────────────────────────────────┤
│ Tier 1: config, helpers, utils ← imports 0 only │
├─────────────────────────────────────────────────────────┤
│ Tier 0: types, logger, constants ← NO workspace deps │
└─────────────────────────────────────────────────────────┘
To see your project's actual tier assignments, read the eslint.config.js file at the project root. The tier arrays (tier0, tier1, etc.) list which packages belong to each tier.
ALWAYS check the tier rules before adding an import statement.
Ask yourself:
If #3 is NO → stop. The import is illegal. Find another way:
If you see an error like:
error 'boundaries/dependencies' - Not allowed to import '@scope/database' from 'helpers'
Read carefully — common mistakes:
When creating a new package, assign it to the correct tier:
| If the package... | Assign to |
|---|---|
| Has NO workspace dependencies | Tier 0 |
| Only depends on types/logger | Tier 1 |
| Wraps external services (DB, API, queue) | Tier 2 |
| Contains business/domain logic | Tier 3 |
| Orchestrates multiple domain + infra packages | Tier 4 |
| Is a deployable app (CLI, web server, worker) | App tier |
Then update eslint.config.js:
boundaries/elements with the correct patternExtract the types into a tier 0 package (e.g. shared-types). Both packages can then import from tier 0.
Create a new package at a lower tier and have both import from it.
Discuss with the user. Sometimes a package is misclassified. Reclassifying early is cheap; reclassifying after 50 imports is expensive.
Test files (__tests__/**) are excluded from boundary checks. This is intentional — tests can import anything.
| Situation | Problem |
|---|---|
| Tier 0 package has workspace dependencies | Tier 0 must be pure leaf packages |
| More than 8-10 packages in a single tier | Tier is too broad — split it |
| App tier importing directly from tier 0 | Not wrong, but consider if an orchestrator should exist |
| Circular dependency warnings | Architecture violation — restructure needed |
Sometimes you genuinely need to disable a rule. That's OK — but every escape hatch must leave a paper trail.
Every eslint-disable comment MUST include:
// eslint-disable-next-line)tech-debt.md entry)// GOOD — full paper trail
// eslint-disable-next-line @typescript-eslint/no-explicit-any — UseFormReturn<any>
// is polymorphic by design; see tech-debt.md#rhf-any-wrapper, tracked in #142
const form: UseFormReturn<any> = useForm(config);
// BAD — no context, no trail, future contributor has no idea why
// eslint-disable-next-line
const data = getResponse() as any;
// BAD — has the rule but no reason
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result: any = parseInput(raw);
eslint-disable without a reason is a future mystery.tech-debt.md when the disable represents known debt (see reference/tech-debt.md for the template).eslint-disable and verify each still has a valid reason + tracking reference. Prune any that no longer apply.Before adding an eslint-disable, ask yourself:
If the answer to any of 2–4 is no, fix that before committing.