KAUSHIK K

Arklint

Architectural linter — enforces structural rules before bad code lands. Pre-commit, CI, and MCP-ready.

Mar 31, 2026

Visit Site ↗
Arklint

*NOTE: This image might be generated using ChatGPT.*

What is Arklint?

Arklint is an architectural linter - a tool that enforces structural rules on your codebase before bad patterns ever land in a PR. Think of it as ESLint, but for your architecture rather than your syntax. It prevents things like:

  • API routes importing database modules directly
  • Model classes defined outside the models/ directory
  • Conflicting HTTP client libraries coexisting in the same project
  • Services calling repositories that call routes (reversed layering)

It works on any language, runs 100% locally with no cloud dependency, and integrates into pre-commit hooks, GitHub Actions, and AI coding agents via an MCP server.


Why I Built It

The trigger was working with AI coding agents. Tools like Claude Code and Cursor are great at generating code fast - but they don't inherently know your project's architectural decisions. They'll happily import SQLAlchemy in a FastAPI route or drop a Pydantic model in a utility file, because nothing stops them.

I wanted a tool that could act as a guardrail at commit time and also teach agents the rules before they write code. That's the core idea behind Arklint.


Tech Stack

  • Python: The core engine - rule parsing, file scanning, import extraction, and CLI are all written in Python.
  • Typer + Rich: CLI framework and terminal output formatting. Rich handles the coloured violation output.
  • PyYAML: Config parsing for .arklint.yml.
  • FastMCP: Powers the MCP server that exposes rules to AI agents like Claude Code and Cursor.
  • npm + .NET shims: Thin wrappers that download the prebuilt binary on first run - no Python required for JS or .NET users.
  • GitHub Actions: CI pipeline, binary builds for Linux/macOS/Windows, and automated releases via PyPI, npm, and NuGet.

Rule Types

Arklint ships with five rule types:

boundary - Prevent files in a source directory from importing blocked packages:

- id: no-direct-db-in-routes
  type: boundary
  source: "routes/**"
  blocked_imports: ["sqlalchemy", "psycopg2"]
  severity: error

dependency - Enforce one HTTP client, one ORM, no conflicting packages:

- id: single-http-client
  type: dependency
  allow_only_one_of: ["requests", "httpx", "aiohttp"]
  severity: error

file-pattern - Keep model/schema classes in the right directories:

- id: models-in-models-dir
  type: file-pattern
  pattern: 'class\s+\w*(Model|Schema)\s*[:(]'
  allowed_in: ["models/**", "schemas/**"]
  severity: warning

pattern-ban - Ban patterns codebase-wide (e.g. print() statements, TODO markers):

- id: no-print-statements
  type: pattern-ban
  pattern: 'print\('
  exclude: ["tests/**"]
  severity: warning

layer-boundary - Enforce layered architecture direction:

- id: layered-architecture
  type: layer-boundary
  layers:
    - { name: routes, path: "routes/**" }
    - { name: services, path: "services/**" }
    - { name: repositories, path: "repositories/**" }
  allowed_dependencies:
    routes: [services]
    services: [repositories]
    repositories: []
  severity: error

Key Features

  • Official rule packs - arklint add arklint/fastapi, arklint/django, arklint/nextjs, arklint/express, arklint/clean-arch
  • AI rule generation - arklint learn --describe "routes must not touch the DB" --provider anthropic
  • MCP server - Expose your rules to Claude Code or Cursor so agents can validate code before writing it
  • Export for AI assistants - arklint export --format claude writes a CLAUDE.md with all your rules in natural language
  • Diff mode - arklint check --diff origin/main only scans changed files (fast CI)
  • GitHub PR annotations - --github-annotations emits inline review comments
  • Visualize - arklint visualize generates a Mermaid diagram of your layer dependencies
  • Watch mode - Re-runs on every file save during development

Challenges and Lessons Learned

Cross-language import extraction

Extracting imports correctly across Python, JS/TS, Go, Rust, Java, C#, Ruby, and PHP is harder than it sounds. Each language has different import syntax, aliasing patterns, and relative vs absolute paths. Getting this right required per-language parsers and a solid test suite.

Safari SVG favicon rendering

Building the brand identity involved an SVG favicon using <mask> and <clipPath> for a keystone icon with three colour zones. Safari's favicon renderer silently ignores masks - falling back to a blank icon. The fix was computing exact polygon boundaries geometrically and dropping masks entirely for the favicon.

Distributing a Python CLI to non-Python users

Most JS developers won't pip install anything. The solution was prebuilt binaries (via PyInstaller) released to GitHub, with a thin npm package and a .NET global tool that download the right binary on first run. Zero Python required for those users.

Making AI agents respect architectural rules

The MCP server approach (exposing list_rules, check_file, check_snippet) works well at validation time. But pairing it with arklint export --format claude to generate a CLAUDE.md teaches agents the rules at generation time - before they write a single line. The combination is much more effective than either alone.


Follow Along

If you're curious about the project, connect on LinkedIn: www.linkedin.com/in/kaushik13k 😊

Made with love by Kaushik