dsl — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited dsl (Plugin) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<p align="center"> <img src="docs/hero.svg" alt="dsl, Domain-Shared Lexicon: the words you and the AI already share, written down once and loaded every session" width="900"> </p>
<p align="center"> <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-4d8759" alt="Apache-2.0"></a> <img src="https://img.shields.io/badge/Claude%20Code-skill-37708f" alt="Claude Code skill"> <img src="https://img.shields.io/badge/scope-vocabulary%20only-bf7d2a" alt="Vocabulary only"> <a href="https://github.com/p-vbordei/cubemap"><img src="https://img.shields.io/badge/companion-cubemap-6a5a8c" alt="companion: cubemap"></a> </p>
<p align="center"><i>A Claude Code skill that gives the AI the vocabulary you and your team<br>already speak, and keeps it from quietly meaning something else.</i></p>
A conversation rarely breaks on a hard word. It breaks on an easy one that means two different things.
You say "the heat tapped late" and you mean a furnace batch was drained behind schedule. The AI hears a temperature reading that arrived late. Nobody corrects it, because nobody noticed, and three turns later the furnace logic is wearing the shape of a charting library. The drift compounds: code uses one name, comments another, you a third, the AI a fourth.
dsl does one thing about that. It builds and maintains a shared glossary (Term · Definition · Aliases to avoid), loads it into the AI's context so the two of you mean the same thing every time, and ships a linter that checks the glossary holds. It does not write PRDs, draft ADRs, run design reviews, refactor your code, or grill your plans. There are good skills for those; this isn't trying to be one.
The rule it enforces is the oldest one in technical writing: one thing, one name; one word, one meaning. That is the spine of ASD-STE100, the controlled-English standard aerospace has used since 1986. The reason it works is that it is checkable, not that it is agreed to.
<p align="center"> <img src="docs/align.svg" alt="The same sentence splits into two meanings without a lexicon: what the user means versus what the AI hears, then collapses into one shared meaning with LEXICON.md" width="900"> </p>
LEXICON.md of Term | Definition | Aliases to avoid tables, the relationships between terms, the ambiguities you've already tripped on, and one example dialogue that shows the words used precisely.CLAUDE.md once, the lexicon enters context at the start of every session in that repo, including the fresh session you'll open three weeks from now.lexicon-lint.py audits the lexicon for contradictions and lints your repo for banned aliases. Exit code 1, no dependencies. A glossary nobody can check is a wish list.LEXICON.md or CLAUDE.md silently. You see the proposed content first, always.<p align="center"> <img src="docs/modes.svg" alt="Three ways the skill runs: on demand via commands, proactively flagging ambiguity in one line, and automatically loading at session start" width="900"> </p>
/dsl-init, /dsl-update, or /dsl-ingest and the skill walks the flow (interview, ingest, or scan)../LEXICON.md if present, matches your global domains against the current project, and loads only the ones that fit.A project lexicon (./LEXICON.md) applies to one repo, unconditionally. A global lexicon (~/.claude/LEXICON.md) holds the domains you work in across all your projects, and here's the part that matters: it is not one big bag dumped into every session. It's a stack of per-domain sections, each declaring Applies when: signals, and the AI loads only the domains the current project actually matches.
<p align="center"> <img src="docs/scopes.svg" alt="The project lexicon always applies; global domains load only on a strong signal (matching deps or two-plus README keywords), never on a lone filename" width="900"> </p>
Strong signals load a domain. Weak ones don't. A named dependency that matches (e.g. pyfoundry in your pyproject.toml), two or more keyword hits in the README, or an explicit domain: <name> line in the project: those load the domain. A single matching filename is a weak signal and is ignored on its own. Loading smelting vocabulary into a recipe side project is worse than loading nothing, so when in doubt the skill loads nothing. You can always force-attach a domain with /dsl-init.
Project always wins. If a term is defined in both the project and a matching global domain, the project definition takes precedence, and the AI says so, once.
Several similar skills exist: grill-me, grill-with-docs, the original DDD ubiquitous-language. They informed this one. The differences:
| dsl | grill-me | grill-with-docs | ubiquitous-language (deprecated) | |
|---|---|---|---|---|
| Scope discipline | Vocabulary only | Plan/design grilling | Glossary + ADRs | Glossary only |
| Machine-checkable | ✅ lexicon-lint.py, exit 1 | no | no | no |
| Global, multi-domain | ✅ with Applies when: matching | no | no | no |
| Off-domain protection | ✅ silent on side projects | no | no | no |
| Auto-load via CLAUDE.md | ✅ project @import, global Read+filter | no | no | no |
| User-provided source material | ✅ files, URLs, paste | no | no | no |
| Proactive ambiguity catch | ✅ non-derailing | no | partial | no |
Want a plan-stress-test session? Use grill-me. Want a full domain model with architecture decisions? Use Matt Pocock's grill-with-docs. Just want the AI and you to mean the same thing when you say Heat, Order, or Cluster? That's this.
Pairs with [`cubemap`](https://github.com/p-vbordei/cubemap), a companion skill for mapping a business from boardroom to code.cubemapdiscovers the client's vocabulary as it maps;dslstores and reloads it. Use the two together.
This repo ships skill and command files. Installation is a copy step plus a one-time CLAUDE.md edit.
git clone https://github.com/p-vbordei/dsl.git ~/src/dslmkdir -p ~/.claude/skills ~/.claude/commands
cp -R ~/src/dsl/skills/dsl ~/.claude/skills/dsl # SKILL.md + lexicon-lint.py
cp ~/src/dsl/commands/dsl-*.md ~/.claude/commands/The skill directory carries lexicon-lint.py alongside SKILL.md. Copy the whole directory, not just the one file. Verify it landed:
python3 ~/.claude/skills/dsl/lexicon-lint.py --selftest # -> selftest: okCLAUDE.mdFor a per-project lexicon, append the snippet from templates/claude-md-snippet-project.md to that project's ./CLAUDE.md. (Strip the leading HTML comment when you paste.)
For a global lexicon, append the snippet from templates/claude-md-snippet-global.md to ~/.claude/CLAUDE.md. (Same, strip the HTML comment.)
You don't have to do step 3 by hand. Running /dsl-init offers to do it for you and shows you the snippet before writing.
New skills and commands are picked up on the next session. Start a fresh session in any repo and run /dsl-init.
First time installing a Claude Code skill? Skills live in~/.claude/skills/<name>/SKILL.mdand are auto-discovered. Slash commands live in~/.claude/commands/<name>.md. No registration needed beyond placing the files.
You: /dsl-init
Claude: Scope? [project] / [global-new-domain] / [global-add-to-existing]
You: project
Claude: Source? [interview] / [ingest] / [scan] / [hybrid]
You: scan
Claude: I read README.md and the directory tree. Candidates: Heat, Tap,
Charge, Slag, Ingot, Ferroalloy. Heat: your README implies "one
furnace batch". Confirm?
You: yes
… (a few more)
Claude: About to write ./LEXICON.md with 6 terms in 1 domain, and append a
block to ./CLAUDE.md. Show diff? (y/n)
You: y
…
You: proceed
Claude: Wrote ./LEXICON.md: 6 terms in 1 domain. CLAUDE.md updated.From here on, the AI reads the lexicon at every session start in this repo and uses your terms instead of inventing its own.
Tight, opinionated, small enough to actually fit in context. From templates/lexicon-template.md:
## Domain: <name> # only for the global file
**Applies when:** # only for the global file
- Project deps include: ...
- README mentions any of: ...
- File extensions present: ...
- Explicit override: a line `domain: <name>` in project CLAUDE.md
### <subgroup>
| Term | Definition | Aliases to avoid |
|------|------------|-------------------|
| **Heat** | One quantity of molten metal produced in one furnace cycle. | melt |
| **Tap** *(v.)* | To open a furnace and drain a **Heat**. | pour |
| **Tap** *(n.)* | One drainage event on one furnace, producing one **Heat**. | tapping |
### Relationships
- A **Charge** produces one **Heat** per furnace cycle.
### Flagged ambiguities
- `pour` was used for both **Tap** *(v.)* and accidental spill. A spill is a `breakout`.
### Example dialogue
> **User:** What's the carbon target on the next **Heat**?
> **Domain expert:** Under 0.15%, heavy on scrap, light on coke.Project lexicons drop the ## Domain: wrapper, and every term applies. See templates/example-lexicon.md for a full two-domain example.
A glossary that nothing checks is the weakest possible intervention. In Ege Chelebi's test of ASD-STE100, the 1986 controlled-English standard written so a non-native aircraft mechanic could not misread a repair manual, a plain banned-words list was the worst performing condition (3% on one model, in the wrong direction). What cut slop by half or more was a system with machine-checkable rules. So dsl ships the checker.
lexicon-lint.py has two jobs. No dependencies, Python 3 stdlib, exit code 1 when it finds something.
Audit the lexicon: is the glossary itself coherent?
$ python3 ~/.claude/skills/dsl/lexicon-lint.py --audit
LEXICON.md: warning: line 17: **Charge** definition is 31 words (cap 25)
LEXICON.md: error: line 14: **Heat** lists 'melt' as an alias to avoid, but 'melt' is itself a defined term. Pick one
LEXICON.md: 9 terms, 14 aliases, 1 errors, 1 warningsErrors are structural contradictions: a term defined twice, an alias that is also a canonical term, one alias claimed by two terms, a missing definition, or an em dash. Warnings are definition style, lifted from STE: over 25 words, a semicolon, more than one sentence, or a definition that uses its own term. --strict makes warnings fail too.
Em dashes and en dashes are forbidden outright, in definitions and in every file the linter reads. Not a warning, not configurable. A dash is where two thoughts get welded together instead of one being chosen, which is the opposite of what a definition is for, and it is the single loudest tell in machine-written prose. Use a period, a comma, a colon, or parentheses. ASCII hyphens (well-known, --flag) are untouched. This repo passes its own check.
Lint the repo: does the code actually use the words?
$ python3 ~/.claude/skills/dsl/lexicon-lint.py docs/*.md src/**/*.py
docs/ops.md:31:8: 'melt' → use "Heat"
src/furnace.py:88:14: 'tapping' → use "Tap"
2 alias violations in 2 filesCode fences and inline code are skipped by default (--code includes them). A line carrying lexicon-ok is skipped, the escape hatch for a deliberate use. For a global lexicon, scope it with --domain smelting.
Wire it into a pre-commit hook when the vocabulary matters enough to block on:
git diff --cached --name-only --diff-filter=ACM -- '*.md' '*.py' \
| xargs -r python3 ~/.claude/skills/dsl/lexicon-lint.pyOne caveat, stated plainly. The alias column is a banned-word list and behaves like one: list a common English word (run, load, state) and it will fire on every unrelated use in your repo. Only list words someone would genuinely reach for to mean this term; put the rest under Flagged ambiguities, where a human reads them. The linter checks form, not truth. It cannot tell you a definition is wrong, only that it contradicts another one.
| Command | Purpose |
|---|---|
/dsl-init | Build a new lexicon from scratch (interview / ingest / scan / hybrid) |
/dsl-update [term] | Add or refine specific terms |
/dsl-ingest <path-or-url> | Extract terms from existing material (docs, transcripts, glossaries) |
lexicon-lint.py --audit | Check the lexicon for contradictions and loose definitions |
lexicon-lint.py FILE... | Find banned aliases in your docs and code |
CLAUDE.md itself (it appends a small snippet that references the lexicon)If you find yourself reaching for one of those, you want a different skill.
Apache License 2.0, Vlad Bordei <[email protected]>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.