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.
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.
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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.