Claude Code skill — Domain-Shared Lexicon: keep the AI's vocabulary aligned with yours. Per-project + per-domain global glossary with smart matching so off-domain projects stay clean.
SaferSkills independently audited dsl (Agent Skill) 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.
A focused skill with one job: align the vocabulary between you (the AI) and the user for the domain they work in. Nothing else. Not plan reviews, not ADRs, not design decisions. Only the words and what they mean.
When the user says "the order is rejected" they may mean a specific lifecycle state with downstream effects. When you (the AI) hear "order" you may default to a generic e-commerce meaning. Drift compounds: code uses one name, comments another, the user a third, and you a fourth. The lexicon collapses all of that into a single source of truth that everyone references, including future-you in a fresh session.
The governing principle is borrowed from ASD-STE100, the 1986 controlled-English standard written so a non-native aircraft mechanic could not misread a repair manual: one thing, one name; one word, one meaning. The standard's power comes from being machine-checkable, not from being agreed to. That is why this skill ships lexicon-lint.py. A lexicon nobody can check is a wish list.
The lexicon stores domain terms and what they mean. That is the whole scope.
If you find yourself writing how a feature works, you've drifted. Stop and put it elsewhere.
| Scope | Path | Applies to |
|---|---|---|
| Project | ./LEXICON.md | This project only |
| Global | ~/.claude/LEXICON.md | The user's recurring domains across all their projects |
The global file is not one big bag. It is a collection of per-domain sections, each with explicit Applies when: signals. At session start, you match the current project against those signals and load only matching domains. A user whose primary work is metal smelting should not get smelting vocabulary auto-loaded into a recipe app side project.
See templates/lexicon-template.md for the canonical structure.
/dsl-init, /dsl-update, /dsl-ingest commands)The user explicitly asks. Run the matching command's flow.
You're working on something else and notice a term being used in two senses in the same conversation, or a domain term the user uses confidently that you cannot define. When this happens:
LEXICON.md to keep us aligned? (or /dsl-update later)"Threshold for raising: real ambiguity or genuine unknown, not stylistic preference. Do not interrupt for terms that have one clear meaning in context.
When loaded via CLAUDE.md at the start of a session:
CLAUDE.md @import.Read tool to load it (do not rely on @import, see "Why no global @import" below). Parse each ## Domain: section's Applies when: block and evaluate against the current project (see "Matching the project to a global domain"). Mentally apply only matching sections. Ignore the rest as if they weren't there.'Heat' has both global and project definitions, using the project one.Lexicon loaded: <N> terms (project: ./LEXICON.md, global: <matched-domain>). Omit any source that contributed zero. Skip the line entirely if nothing loaded.This project has domain-specific terminology. Want to build a lexicon? Run /dsl-init. Do not offer again in the same session if declined.@import@~/.claude/LEXICON.md in ~/.claude/CLAUDE.md would load the entire global lexicon into context every session, including off-domain vocabulary. That defeats the matching guarantee. A metallurgist's smelting terms would still enter context when they open a recipe app. Instead, the global CLAUDE.md snippet asks you to Read the lexicon at session start and filter by Applies when: yourself. Off-domain terms never enter context.
Each ## Domain: section in ~/.claude/LEXICON.md declares Applies when: signals. Evaluate them with this priority:
^\s*domain:\s*<name>\s*$ appears in the project's ./CLAUDE.md, or as the first non-blank, non-heading line of ./LEXICON.md, use that. Skip detection.Applies when: list, OR project deps (package.json, pyproject.toml, Cargo.toml, go.mod, etc.) include declared domain libraries.Load a global domain only when strong signal OR explicit override matches. Weak signals alone are not enough. They cause false positives across unrelated projects.
When in doubt, prefer not loading over polluting context with off-domain vocabulary. The user can always invoke /dsl-init to force-attach a global domain to the current project.
When invoked via /dsl-init or when the user accepts the proactive offer:
Ask once: Is this lexicon for this project only, or part of your recurring domain across projects? (Options: project / global-new-domain / global-add-to-existing.) Use AskUserQuestion if available.
Ask once: How should we extract terms? Options:
interview: I ask you questionsingest: you point me at a file, URL, or paste (docs, transcripts, existing glossaries, code)scan: I scan this repo's README.md, any GLOSSARY.md/TERMS.md-style files, and directory nameshybrid: combine the aboveFor ingest: ask for the source. Read it. Extract candidate terms. For scan: read the project's README.md and any file with a glossary-shaped name at the project root or one level deep (GLOSSARY.md, TERMS.md, LEXICON.md, DICTIONARY.md). Also use the directory tree (names only, not file contents) as a hint for domain. Extract candidates. Do not read `docs/`, `CONTRIBUTING.md`, ADRs, design docs, or source code. Those describe how the system works, which is out of scope. If the user wants terms extracted from a design doc, they should ingest it explicitly so they're aware that's a deliberate widening of scope.
Ask one question at a time. Prefer AskUserQuestion with 2-4 concrete options + Other.
Cover only:
What does <Term> mean here, in one sentence?Are <A> and <B> the same thing, or different?When you say <X>, do you mean <interpretation A> or <interpretation B>?Quick: what is a <Term> in your domain?You are an interviewer, not an executor. Do not start coding, refactoring, or restructuring anything as a side effect. The deliverable is the lexicon, full stop.
Stop when:
10 to 15 terms is a normal first pass. Don't pad.
Before writing, always show the proposed lexicon contents to the user and confirm. Never write a LEXICON.md silently.
Write ./LEXICON.md (or update ~/.claude/LEXICON.md for global). Follow templates/lexicon-template.md exactly. Group terms into tables by natural cluster (lifecycle / actors / artifacts). Include the Flagged ambiguities and Example dialogue sections.
For a new global domain, populate the Applies when: block carefully. These signals decide future auto-loading. Be specific: deps, README keywords, file patterns. Vague signals cause false positives.
Empty-lexicon guard: if the interview / ingest / scan yielded fewer than 2 terms (or no real ambiguities), do not write a file. Tell the user: Not enough domain-specific terms for a lexicon, skipping. Run /dsl-init again when you have more material.
Check the relevant CLAUDE.md. Always confirm with the user before writing or modifying their `CLAUDE.md`. Never write a CLAUDE.md file without first showing the user the snippet you intend to append.
./CLAUDE.md already references ./LEXICON.md via @./LEXICON.md. If not, propose appending the snippet from templates/claude-md-snippet-project.md. If ./CLAUDE.md doesn't exist, propose creating one containing only that snippet.~/.claude/CLAUDE.md already contains the global lexicon block. If not, propose appending the snippet from templates/claude-md-snippet-global.md. If ~/.claude/CLAUDE.md doesn't exist, propose creating one containing only that snippet.Project lexicon loading: the project snippet uses @./LEXICON.md, so the whole file enters context every session, which is the intended behaviour (project lexicons always apply). If the project lexicon is large enough to be a context concern (say, over 500 lines), tell the user; consider splitting by sub-domain into separate files referenced from ./LEXICON.md.
Global lexicon loading: the global snippet does not use @import. It instructs you to Read and filter at session start. This keeps off-domain vocabulary out of context.
Writing the file is not the end of the flow. Run the audit against what you just wrote:
python3 ~/.claude/skills/dsl/lexicon-lint.py --lexicon ./LEXICON.md --auditIt reports two classes:
Report the audit line verbatim to the user. Do not claim the lexicon is written until the audit has actually run. An unaudited lexicon is exactly the banned-word list this skill exists to replace.
State, in one line: Wrote <path>: <N> terms in <M> domains, audit clean. CLAUDE.md updated. Do not summarize the contents. The file is the artifact.
/dsl-update)When invoked because of a proactive ambiguity catch (mode 2 above), skip Step 2 and go straight to the specific term.
lexicon-lint.py)The audit checks the lexicon. The lint checks everything else: whether the repo actually uses the canonical terms.
# where has the codebase used a banned alias?
python3 ~/.claude/skills/dsl/lexicon-lint.py docs/*.md src/**/*.py
# a global lexicon, scoped to one domain
python3 ~/.claude/skills/dsl/lexicon-lint.py --lexicon ~/.claude/LEXICON.md \
--domain smelting docs/*.mdOutput is path:line:col: 'batch' -> use "Heat", and the exit code is 1 when anything is found. Code fences and inline code are skipped by default (--code includes them). A line carrying the marker lexicon-ok is skipped. That is the escape hatch for a deliberate use.
Use it when:
Report hits, do not auto-fix them. A hit is a question ("is this the domain term or ordinary English?"), not a defect. Renaming identifiers as a side effect is explicitly out of scope. Surface the list and let the user decide.
/dsl-ingest)X is a ..., we call this a ..., repeated capitalized terms, acronyms with expansions)array, function, endpoint) unless they have domain-specific meaning hereFor URLs, prefer WebFetch. For PDFs and Office files, ask the user to convert or paste the relevant section. Do not assume a converter is installed.
These rules apply when you write any lexicon file:
Aliases to avoid.lexicon-lint.py --audit enforces them:run, load, state) it will fire on every unrelated use in the codebase. List it only if someone would genuinely reach for it to mean this term. When it's a common word that still matters, say so in the Flagged ambiguities section instead.**Tap** *(v.)* and **Tap** *(n.)* are two entries, not one. This is the single most common source of quiet drift. STE's dictionary carries part of speech for exactly this reason.**Customer**. This makes relationships obvious.## Flagged ambiguities with a clear recommendation.Applies when: signals don't match.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.