Skip to main content

dddlint

Polyglot ubiquitous language linter. Reads class, function, method, and type names across 306 languages and enforces them against a domain vocabulary: banned terms, non-canonical synonyms, and one concept spelled multiple ways.

Works with any language tree-sitter recognises, without per-language queries. Slots into pre-commit hooks, CI pipelines, and coding agent loops via a non-zero exit code on findings. Ships with an LSP server for inline editor diagnostics and rename code actions.

dddlint LSP diagnostics inline in an editor

📖 Full documentation

Install

uv add dddlint

# dddlint map needs an embedding backend; this one runs locally and offline
uv add 'dddlint[embed-local]'

# or, for a hosted model such as openai:text-embedding-3-small
uv add 'dddlint[embed]'

lint, html, and lsp need none of these: only map embeds anything.

Quick start

# lint current directory (looks for dddlint.yaml)
dddlint lint

# lint a specific path
dddlint lint src/

# report vocabulary insights from name embeddings, plus a scatter plot to open
dddlint map

# open an interactive language graph in the browser
dddlint html

# start the LSP server (stdio)
dddlint lsp

Config

Place dddlint.yaml at the project root. When a path is passed to lint or html, the config is looked up in that directory first, then falls back to the current working directory.

similarity_threshold: 0.85   # how alike two domain or context names may be
enforce_canonical: true       # flag alias terms in addition to forbidden ones
name_uniqueness: true         # flag one name claimed by two definitions in a context

# terms that must never appear in a definition name
forbidden:
  - util
  - helper
  - manager

# paths to skip, gitignore syntax, relative to this file's directory
exclude:
  - src/generated
  - "**/*_pb2.py"

# canonical terms and their aliases
synonyms:
  - canonical: customer
    aliases: [client, user, accountholder]
  - canonical: order
    aliases: [purchase, transaction]

# high-level business domains
domains:
  - name: commerce
    include: ["**/commerce/**"]
    synonyms:
      - canonical: order
        aliases: [purchase]

# bounded contexts, same structure as domains, applied after (context wins on conflict)
contexts:
  - name: billing
    include: ["**/billing/**"]
    forbidden: [discount]
    synonyms:
      - canonical: invoice
        aliases: [bill, statement]

Global rules apply everywhere. Domain rules apply to matching paths. Context rules apply after domains, so a context can override a domain synonym.

Rules

Rule Severity Description
forbidden error A definition name contains a banned term
alias warning A definition uses a non-canonical synonym, with a rename suggestion
duplicate warning One name is claimed by more than one definition in the same context, so rg name cannot answer which one
drift info The same concept is spelled multiple ways across the codebase
config:forbidden-canonical-clash error A term is both forbidden and a canonical synonym
config:alias-conflict warning The same alias maps to different canonicals in different scopes
config:duplicate-name info Two domains or contexts have suspiciously similar names

Config rules are checked against dddlint.yaml itself on every run.

Insights

dddlint map embeds every definition name and compares meaning rather than tokens, catching what drift cannot. It always exits 0, so it informs rather than gates.

Rule Description
near-synonym Names that mean the same thing while sharing no token
context-outlier A name whose vocabulary belongs to a different domain or context

Vectors are cached in .dddlint/embeddings.json, keyed by model and dimensions, so only new names are ever embedded. Configure the model under embeddings in dddlint.yaml.

Every run also writes dddmap.html beside the config and opens it: the whole vocabulary laid out by embedding similarity, verbs as triangles and nouns as dots, with one boundary drawn around each bounded context.

The dddlint vocabulary map: names placed by embedding similarity, with a boundary drawn around each bounded context

LSP

The LSP server publishes diagnostics on file open and save, scanning the entire workspace each time so cross-file drift is always caught. Alias findings include a code action to rename the identifier to the canonical term with case preserved (ClientRepo → CustomerRepo, get_client → get_customer). The rename is delegated to the language server that owns the file, since that is the one that can find the call sites — see wiring it up.

Neovim, add to init.lua:

vim.api.nvim_create_autocmd("BufReadPost", {
  callback = function(args)
    local root = vim.fs.root(args.buf, { "dddlint.yaml" })
    if root then
      vim.lsp.start({
        name = "dddlint",
        cmd = { "dddlint", "lsp" },
        root_dir = root,
      }, { bufnr = args.buf })
    end
  end,
})

The autocmd fires on every buffer, attaches only when dddlint.yaml is found, and is language-agnostic, so no filetype list is required.

VS Code, via a generic LSP client extension:

{
  "lsp.servers": {
    "dddlint": {
      "command": ["uvx", "dddlint", "lsp"],
      "filetypes": ["*"]
    }
  }
}

Helix, .helix/languages.toml:

[language-server.dddlint]
command = "dddlint"
args = ["lsp"]

Language support

Extraction is powered by tree-sitter-language-pack, which covers 306 languages including:

Ada · Agda · Arduino · Bash · C · C++ · C# · Clojure · COBOL · Crystal · CSS · D · Dart · Dockerfile · Elixir · Elm · Erlang · F# · Fortran · GDScript · GLSL · Go · GraphQL · Groovy · Hack · Haskell · HCL · HTML · Java · JavaScript · Julia · Kotlin · Lean · Lua · MATLAB · Mojo · Nix · OCaml · Odin · Pascal · Perl · PHP · PowerShell · Prolog · Python · R · Racket · Ruby · Rust · Scala · Scheme · Solidity · SQL · Svelte · Swift · Terraform · TLA+ · TOML · TypeScript · V · VHDL · Vim · Vue · WebAssembly · XML · YAML · Zig, and 243 more.

CI

# .github/workflows/dddlint.yml
- run: uvx dddlint lint

Exit code is 0 when clean, 1 when findings exist.

Pre-commit

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: dddlint
        name: dddlint
        entry: dddlint lint
        language: python
        pass_filenames: false

Offline use

The language pack downloads parsers on first use. For CI or air-gapped runs, warm the cache in the image:

python -c "import tree_sitter_language_pack as t; t.download_all()"

Metadata

Release files for dddlint 0.1.11

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dddlint 0.1.11
File Size Uploaded Provenance Actions
dddlint-0.1.11.tar.gz 1.0 MB With provenance Details for dddlint-0.1.11.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for dddlint 0.1.11
File Interpreter ABI Platform Actions
dddlint-0.1.11-py3-none-any.whl Python 3 none any Details for dddlint-0.1.11-py3-none-any.whl

Total release size: 1.0 MB

Release files / dddlint-0.1.11.tar.gz

Download URL dddlint-0.1.11.tar.gz
Size 1.0 MB
Tags Source
SHA-256 checksum
How to use checksums
daf38644990966057b8a1d8dea6796fcab073b247de2fef6a48c08a1cc5b8703
BLAKE2b-256 checksum
How to use checksums
0fcdafa9e125417f5bbbb7662924d01303bc174d85c6cdd46c90ab1f8613b50b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release files / dddlint-0.1.11-py3-none-any.whl

Download URL dddlint-0.1.11-py3-none-any.whl
Size 30.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d0fd9cc1fa56591dfc0b265f9c9ff748a6b696e3e729d76caf246c5469ee9aae
BLAKE2b-256 checksum
How to use checksums
b786b5e91dbefbf299ccff650b028dc769ad3b66184e2e20a77727b60c7ebc2b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Security for dddlint 0.1.11

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for dddlint-0.1.11.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page