Stop playing architectural whack-a-mole in PR reviews. Enforce deterministic TypeScript guardrails that both humans and AI agents understand — set up in 5 minutes.
Deslop catches architectural violations that result in bugs, security vulnerabilities or serious tech debt before they happen.
No AI involved — just good old Code Graph & Dependency Analysis algorithms.
> deslop check .
🚀 Deslopping project: repo/my-app
Found 1 problem:
# arch#api-no-browser-globals#@/app/api/orders/route
API routes run in Node.js — browser globals like window and document don't exist here. Transitively importing a browser-only SDK crashes every request at runtime. Passes next build. Invisible without graph analysis.
Module '@/app/api/orders/route' transitively imports 'mixpanel-browser' via:
@/app/api/orders/route → @/lib/analytics → mixpanel-browser
ts
import { logOrderTelemetry } from '@/lib/analytics';
FIX: Use mixpanel-node in a dedicated @/lib/analytics.server module. Never import browser SDKs from route handlers.
√ Checked 412 modules in 870ms
Whether you're babysitting architecture manually on PR reviews, trusting AI coding assistants, or wrestling with fragile open-source linting setups — your team is paying a hidden tax in lost engineering hours.
"We already flagged this boundary leak last sprint. But we had to ship, so it slipped through anyway."
AGENTS.md or AI code reviewers? optimized for short-term statistical success have no and will confidently justify a critical boundary violation just to get the code compiling. Deslop is different: deterministic static graph traversal — no hallucinations, no false positives, no dice roll."Cursor generated the feature perfectly, all tests pass — but it quietly pulled a UI component into our pure domain layer."
"The staff engineer spent 3 weeks on a custom ESLint plugin and Dependency Cruiser config. It broke again after the Node upgrade."
"no-cross-layer-import: violation detected" — and absolutely no guidance on what to do next.
Most teams believe their architecture is enforced — until they run their first Deslop check. Line-by-line linters are blind to multi-hop transitive imports. Rot accumulates silently, deep inside your dependency graph.
> npx @ivy-apps/deslop check .
🚀 Deslopping project: repo/my-app
Found 1 problem:
# arch#feature-isolation#@/features/billing/checkout
No feature may import another feature transitively.
Module '@/features/billing/checkout' transitively imports '@/features/auth/debug-modal' via:
@/features/billing/checkout → @/components/shared/modal → @/features/auth/debug-modal
FIX: Extract shared logic to a @/lib or @/components module.
√ Checked 412 modules in 870ms
When you run it, you will find violations you didn't expect to exist. That is the moment Deslop pays for itself.
How to keep your codebase clean when humans and AI agents collaborate.
Tools like Cursor, Claude Code, and automated coding agents have transformed development velocity. An LLM can write a perfectly functioning feature in seconds that passes every unit test.
But AI agents completely lack your team's tribal knowledge. They don't know your clean architecture boundaries, your feature isolation layers, or your Next.js conventions. They simply pull whatever import resolves the TypeScript type, silently introducing spaghetti code deep into your dependency graph.
Line-by-line linters are completely blind to this. Deslop is different. It analyzes the entire module dependency graph instantly, ensuring that as your AI code generation scales, your architectural integrity remains unbroken.
AI Agent generates code
Feature written in seconds, all tests pass
Imports whatever resolves TypeScript
Transitively pulls react into @/domain — no linter catches it
Deslop catches the boundary violation
1 violation found · Checked 18,412 modules in 2.1s
A single, zero-maintenance declarative DSL for your entire macro-architecture.
To achieve comprehensive architecture governance today, teams end up wrestling with a fragile stack of 2 to 3 different open-source tools and third-party plugins. You need one plugin to forbid imports, a custom script to enforce that unit tests or Storybook components exist, and a third setup to check dependency boundaries. Every time ESLint updates or Node versions shift, your custom infrastructure breaks.
Deslop replaces this maintenance nightmare with a single, unified declarative YAML DSL.
In five lines of human-readable YAML, you get full coverage over import boundaries (forbids), exceptions (allows), mandatory module chains (uses), and companion file rules (exists). It's an entire architectural quality gate packaged into one lightweight tool that requires zero AST knowledge, zero regex, and zero ongoing DevOps overhead.
Did you know?
The 4 rules on the right take ~30 lines of YAML in Deslop. Equivalent coverage requires 200+ linesacross ESLint, Dependency Cruiser, and a custom script — Dependency Cruiser's transitive checks need dense regex rules with a , exists checks need a custom script on top, and your team inherits two tools with independent release cycles and no performance guarantees at scale.
id: features
name: Feature Modules
description: Isolation and quality rules for feature modules.
rules:
- id: no-server-apis
description: Features must not import Next.js server-only APIs.
target: "@/features/**"
forbids:
- import: "next/headers" # crashes in Client Components
- import: "next/cookies"
fix: Pass cookies/headers as arguments from a Server Component or action.
- id: actions-require-auth
description: Every server action must verify the user session.
target: "@/features/**/actions/**"
exclude: # removes modules from the target — use exceptions
- "@/features/auth/actions/**" # auth actions run before a session exists
uses:
- import: "@/lib/auth/session" # enforced by architecture, not code review
fix: Import getSession from @/lib/auth/session and handle the unauthed case.
- id: feature-isolation
description: No feature may import another feature.
target: "@/features/**"
forbids:
- import: "@/features/**" # e.g. @/features/auth can't import @/features/billing
transitive: true # catches indirect imports — unlike ESLint
allows:
- import: "{{TARGET_DIR}}/**" # own feature is fine
fix: Extract shared logic to @/lib or @/components.
- id: viewmodel-has-tests
description: Every feature viewmodel must ship with a unit test.
target: "@/features/**/use{{FileName}}ViewModel" # captures e.g. "Cart"
exists:
- module: "{{TARGET_DIR}}/use{{FileName}}ViewModel.test" # → useCartViewModel.test
fix: Add use{{FileName}}ViewModel.test alongside each viewmodel.You've already designed a great system. Now make it self-enforcing.
Without enforcement, architecture decays in one of two ways: senior engineers stuck re-explaining the same boundary violations review after review to engineers who either don't get it or quietly ignore it — or a platform team maintaining fragile ESLint plugins that belong to no one and break on every Node upgrade.
Deslop replaces both with five lines of readable YAML that any engineer can write and everyone can understand. No AST knowledge, no regex, no pipeline setup. Those YAML files become your architecture's living documentation — enforced in CI, version-controlled, and readable by new hires and AI coding agents alike.
Rules as living documentation
Written in plain English
The description and example fields make the intent unmistakable — no regex or AST expertise required.
Onboards humans and AI alike
New engineers and AI coding agents read the same rulebook. No tribal knowledge needed.
Enforced, not reviewed
The rule is the contract. CI enforces it. No reviewer needs to remember it sprint after sprint.
And Deslop doesn't just check the surface. Unlike ESLint — which can only trace cross-file dependencies by loading the full typescript-eslint compiler API, at a severe IDE and CI performance cost — Deslop traces the full dependency graph as its primary job, in milliseconds. Add transitive: true to any import rule and no developer nor AI agent can sneak a forbidden dependency through an intermediate file.
The bypass attempt
@/domain/UserService
Your pure domain layer
@/utils/ui-bridge
The sneaky middleman
react
The forbidden dep
react reachable transitively (2 hops) — flagged instantlyHow Deslop compares to the popular open-source tools.
| Feature | Deslop | ESLint + plugin | Dependency Cruiser |
|---|---|---|---|
| Rule format | Declarative YAML | JS config objects | Regex-heavy JS/JSON |
| Typical rule length | ~5 lines of YAML | ~20–40 lines of JS | ~10–20 lines of regex |
| Engine | Haskell | JavaScript | JavaScript |
| Forbid dependencies | forbids | Yes | forbidden |
| Allow exceptions | allows | Yes | allowed |
| Require a dependency | uses | No | required |
| Require companion files | exists | No | No |
| Transitive checks | transitive: true on any rule | possible via typescript-eslint, but severe IDE/CI performance cost | reachable attr, complex regex config required |
| Transitive require (uses) | uses + transitive: true | No | No |
| Named path variables | {{FileName}}, {{TARGET_DIR}} | No | No |
| AI-native fix output | Structured markdown fix field | No | No |
| Correct-code example in rule | example field, shown in violation output | No | comment text field only, not shown in output |
| Baseline (silence known violations) | deslop baseline → readable YAML, one key per violation | Bulk Suppressions (eslint --suppress-all, v9.24 Apr 2025) | verbose JSON objects per violation |
| Exclude from target | exclude list | Yes | Yes |
| Auto-fix relative imports | deslop fix, built-in | third-party plugin required | No |
| Multiple rule files | auto-loaded from deslop/rules/ | via JS imports into one config | extends in config |
| Windows OS support | No— coming soon | Yes | Yes |
| Dependency graph visualization | No | No | Yes |
| Monorepo / multiple tsconfigs | run per package; proper support WIP | parserOptions.project glob array via typescript-eslint | run per package; no multi-tsconfig from root |
Deslop is not a replacement for ESLint, Biome, or other linters — use those alongside it. Deslop focuses on a bigger-picture problem: enforcing the project architecture by analysing the entire module dependency graph, something line-by-line linters are not designed to do.
Under the hood
A pure Haskell engine builds a lossless pipeline from exact source tokens to a fully connected, whole-repo dependency graph — the structural foundation that makes architectural guardrails deterministic, not advisory. Deslop contains no AI: unlike probabilistic code review tools that suggest fixes, it evaluates your RuleBook as pure static analysis — identical result on every run, zero chance of hallucination or failure. Every rule sees the full transitive import chain, catching violations that .
Concrete Syntax Tree (CST)
Deslop builds a fully lossless CST, retaining all compiler trivia—every whitespace character and comment is accounted for. This allows the engine to round-trip edits directly to the original source text without destroying your formatting or causing noisy diffs.
Abstract Syntax Tree (AST)
The CST is lowered into a semantic AST. This is where structural bindings, control flow, and data flow are computed. It provides a deeper layer of static analysis that goes far beyond the capabilities of standard regex or purely text-based grep tools.
Whole-Repo Dependency Graph
Finally, Deslop resolves cross-file references to assemble a global topological graph of modules, imports, and symbols. This overarching view allows your RuleBook to enforce complex architectural boundaries and systemic invariants that single-file linters simply cannot see.
Biome is exceptional at single-file AST traversal — formatting, localized bug patterns, and direct-import checks. But it has no cross-file semantic model at all. By design, each file is analyzed in isolation with no graph and no transitive reachability. This is an architectural choice that keeps it fast — but it means you cannot enforce macro-architectural boundaries, regardless of plugins.
With typescript-eslint type-aware linting, custom rules can technically access the TypeScript CompilerHost and trace module symbols across file boundaries. But doing so requires loading the full TypeScript compiler on every lint pass — dragging your IDE to a crawl on every file save and bloating CI runtimes by several minutes. The resulting custom plugin is specialized, fragile, and breaks on every major ESLint or Node upgrade. Nobody owns it after the person who wrote it leaves.
Deslop is the structural complement. Keep your linters fast for single-file syntax rules; use Deslop as the dedicated native engine for whole-repo structural invariants — in milliseconds.
AGENTS.md tells the model what your architecture looks like. It doesn't verify that the generated code actually conforms to it. When an agent imports @/utils/bridge — which internally imports react — no instruction catches that transitive violation, because the model is not traversing the dependency graph before writing each import. The violation compiles, passes tests, and ships silently.
Beyond that, AGENTS.md compliance is inherently probabilistic. It's a system prompt read once per session, with no persistent enforcement across context windows. In a long agentic run, rules are followed until they're quietly forgotten. Deslop closes this with a hard compiler gate: a structured violation report the agent reads, acts on, and retries against — deterministic by design.
Built in Haskell. Purely functional — bringing strict determinism to our increasingly non-deterministic, LLM-driven world.
free on local, paid on CI
Why pay for Deslop when linters are free?
You don't pay for Deslop to format your code; you pay for Deslop to reclaim lost engineering hours. It pays for itself the very first time it stops a wayward AI agent or a rushed developer from shipping architectural debt and AI slop to production.
Best for local development
Run Deslop locally in your terminal. No account required.
Deslop is free whenever you run it in an interactive terminal — your everyday shell session. No account, no limits.
Interactive terminal — your regular shell, VS Code terminal, iTerm, etc. Always free.
Headless / automated environments — CI pipelines, GitHub Actions, and AI coding agents that run Deslop without an interactive session trigger a human verification step. This is intentional to keep the free tier sustainable.
To lift restrictions in CI or your AI harness, set your license key as an environment variable:
DESLOP_LICENSE_KEY=your_key_hereA Deslop Hobby license is the minimum recommended plan for CI and AI agent environments.
14-day free trial on all paid plans
Best for getting started
Best for startups
FAQ
Because LLMs are fundamentally non-deterministic probabilistic models, not compilers. You cannot prompt your way to a zero-trust architecture. When an AI agent gets a large prompt, encounters a high token load, or is simply instructed to “make this code compile and typecheck immediately,” it will aggressively optimize for short-term completion.
For example, if your AGENTS.mdsays “Never import React or UI code inside pure domain utilities,” but the agent needs a layout object type to clear a TypeScript compilation error, it will blindly pull import { ReactNode }from "react" straight into the domain layer to pass the compiler block. The agent ignores the markdown rules file because clearing the local compiler is its immediate statistical success metric.
You don't combat non-deterministic shortcuts with prose instructions; you combat them with a deterministic binary gate that catches the leak in 2 seconds flat.
“Free” is not the same as “zero cost.” To achieve equivalent architecture governance with open-source tools, you end up wrestling with a fragile stack of 2 to 3 different tools: one ESLint plugin to forbid imports, Dependency Cruiser for transitive boundary checks, and a custom script to enforce companion file existence. That's four rules in Deslop — roughly 30 lines of YAML — versus 200+ lines of dense regex config, custom AST plugins, and glue scripts spread across two tools with independent release cycles.
Then there is the ongoing maintenance tax. Every Node.js upgrade, every major ESLint version bump, every folder rename — and your custom infrastructure breaks. The engineer who built it has usually left by the time it does. Teams routinely report a Staff Engineer spending 3 weeks building a custom ESLint and Dependency Cruiser setup, only to watch it silently rot after the next Node upgrade. At €50–€150+/hr, those 3 weeks alone cost more than years of a Deslop PRO license.
And that is before accounting for the recurring cost: every week a senior engineer spends 15 minutes in PR review re-explaining the same boundary violation is roughly one hour of salary per month — €50 to €150, every month. A Deslop PRO license is €24.99/month for the entire team. The open-source alternative isn't free; it just hides the invoice inside your engineering payroll.
AI PR reviewers evaluate the diff, not the dependency graph. They see what changed in this PR — not the full transitive import chain of everything that was imported.
Consider this scenario: an AI agent generates a Next.js Server Component and adds import { getUserProfile }from "@/lib/user-profile". The AI reviewer scans the diff, sees a single clean import line, and approves the PR. What it cannot see is that @/lib/user-profile transitively imports @/store/userStore — a Zustand store using persist middleware with localStorage as its storage backend. The moment that Server Component is executed in Node.js: ReferenceError: localStorage is not defined. Every request crashes. The build passed. Every test passed. The AI reviewer approved.
Deslop catches this in under a second: @/app/dashboard/page → @/lib/user-profile → @/store/userStore. You are using a probabilistic text-scanner to solve a topological graph problem. Deslop is the right tool for the job.
Dependency Cruiser is a great tool for visualizing a graph, but it relies on dense, regular expressions that are incredibly difficult to scale and maintain. More importantly, it wasn't built for the AI era.
Dependency Cruiser tells you thatsomething is broken, but it can't tell an AI agent howto fix it. Deslop's structured markdown fix field allows coding agents to read the violation and instantly rewrite their own code correctly without human intervention.
Quite the opposite. Because Deslop is compiled natively in Haskell, it bypasses the performance bottlenecks of JavaScript-based tools. It parses and maps your entire project's module architecture graph in 2 to 3 seconds flat, making it an unnoticeable blip in your local pre-commit hooks or GitHub Actions pipeline.
Deslop is not just an import checker. It is a unified declarative YAML DSL for your entire architecture contract — import boundaries, dependency rules, and companion file existence — all in one version-controlled, human-readable RuleBook that engineers and AI agents can both understand.
The exists rule enforces that files matching a pattern always have a required companion. For example:
useCartViewModel.ts requires useCartViewModel.test.ts alongside it. No test file means CI fails — no custom script, no plugin, 2 lines of YAML.@/components/ui/** must have a Storybook .stories.tsx file. Visual documentation becomes a first-class architectural requirement..server.ts counterpart, preventing accidental client-side imports at the boundary.All of this — import rules, dependency constraints, testing standards, file structure conventions — lives in the same RuleBook. No tribal knowledge, no separate scripts, no custom AST plugins to maintain. One tool. One contract. Enforced in CI.
Technically, yes — and it is worth being honest about what that actually involves. Type-aware linting requires loading a full TypeScript CompilerHost instance on every lint pass. Starting a TypeScript compiler inside a JavaScript process is a heavyweight operation: it noticeably degrades IDE responsiveness on every file save and adds meaningful time to your CI lint step.
Beyond performance, the maintenance cost is the real tax. The custom plugin you write is specialized and fragile. It breaks on major ESLint version bumps, Node.js upgrades, and tsconfig.json structural changes. The engineer who built it is rarely still around when it breaks. The team inherits an opaque piece of internal infrastructure that nobody owns and nobody wants to debug.
Deslop offloads graph analysis entirely to a native Haskell engine — millisecond performance, zero upkeep, zero AST knowledge required. Keep ESLint fast for single-file syntax rules. Use Deslop as the dedicated engine for whole-repo structural invariants.
This is intentional, not a bug. The distinction Deslop draws is between interactive terminal sessions — you at your keyboard — and headless automated execution, which is CI-equivalent behavior regardless of where it runs. Without this boundary, the free tier would be trivially abused by teams running unlimited automated checks without a license by simply launching agents in a “local” environment.
The practical fix is straightforward: set DESLOP_LICENSE_KEY=your_key_herein your agent's environment. A Hobby license at €4.99/month covers this use case with 500 CI runs per month.
A free MCP server is coming soon. It will allow AI coding agents to invoke Deslop natively through the MCP protocol without triggering headless detection, making the full Deslop experience free for local agentic workflows.
id: features
name: Feature Modules
description: Isolation and quality rules for feature modules.
rules:
- id: no-server-apis
description: Features must not import Next.js server-only APIs.
target: "@/features/**"
forbids:
- import: "next/headers" # crashes in Client Components
- import: "next/cookies"
fix: Pass cookies/headers as arguments from a Server Component or action.
- id: actions-require-auth
description: Every server action must verify the user session.
target: "@/features/**/actions/**"
exclude: # removes modules from the target — use exceptions
- "@/features/auth/actions/**" # auth actions run before a session exists
uses:
- import: "@/lib/auth/session" # enforced by architecture, not code review
fix: Import getSession from @/lib/auth/session and handle the unauthed case.
- id: feature-isolation
description: No feature may import another feature.
target: "@/features/**"
forbids:
- import: "@/features/**" # e.g. @/features/auth can't import @/features/billing
transitive: true # catches indirect imports — unlike ESLint
allows:
- import: "{{TARGET_DIR}}/**" # own feature is fine
fix: Extract shared logic to @/lib or @/components.
- id: viewmodel-has-tests
description: Every feature viewmodel must ship with a unit test.
target: "@/features/**/use{{FileName}}ViewModel" # captures e.g. "Cart"
exists:
- module: "{{TARGET_DIR}}/use{{FileName}}ViewModel.test" # → useCartViewModel.test
fix: Add use{{FileName}}ViewModel.test alongside each viewmodel.- id: domain-no-ui
description: >-
The domain layer must stay framework-agnostic. Importing React or UI
components makes business logic untestable in isolation and breaks
the separation of concerns your whole architecture depends on.
example: >-
# WRONG — pulls React into pure business logic
import { toast } from '@/components/ui/toast';
# RIGHT — return a result; let the UI layer handle feedback
return { success: false, error: 'Insufficient funds' };
target: "@/domain/**"
forbids:
- import: "react"
transitive: true
- import: "@/components/**"
transitive: true
fix: Return plain values; let features or UI layers handle rendering.