For developers and technical writers who use AI tools (Claude Code, Codex, Cursor, Copilot, Gemini CLI, Aider) and need vocabulary to stay consistent across sessions.
AI tools invent English terms, coin abbreviations, and use inconsistent phrasing. You correct it once, but the next session starts fresh and the same terms drift again.
Before VEIL: AI uses "current state" in a response. You correct it to "present state". Next session, the AI uses "current state" again. Every session requires the same correction.
After VEIL: "present state" is registered as the preferred form. VEIL syncs the rule to CLAUDE.md / AGENTS.md. The next session, the AI reads the rule and outputs "present state" from the start.
Success condition: once a term is registered, it does not require re-explanation or correction across sessions or AI tools.
The more AI is in your workflow, the worse this gets. VEIL is not a static style guide — it runs a capture → normalize → sync → lint loop to enforce vocabulary consistency. Zero dependencies, fully local.
The canonical source of truth is ~/.veil/veil.db. ~/.veil/rules/ is the AI-readable markdown surface and mirror. CLAUDE.md / AGENTS.md / .cursorrules / .github/copilot-instructions.md / GEMINI.md / .aider.conf.yml are sync targets, not storage.
task close / conversation boundary
↓
/veil-capture (skill)
↓
AI extracts problem terms
↓
shared/runtime/veil-normalize.py — collapse variants, cross-check existing rules
↓
adopt only high-demand, high-impact terms
↓
record to ~/.veil/veil.db ← canonical source of truth
↓
generate ~/.veil/rules/{letter}.md ← mirror / AI-readable surface
↓
shared/runtime/veil-sync.py pushes rules to AI tool config files
↓
CLAUDE.md / AGENTS.md / .cursorrules etc. receive the rules
↓
shared/runtime/veil-lint.py checks the final response before sending
↓
next session: AI outputs with consistent vocabulary
| Component | Role |
|---|---|
/veil-capture skill |
At task close / conversation boundary: extract problem terms, record to SQLite canonical, generate mirror, run sync |
~/.veil/veil.db |
SQLite canonical source of truth |
~/.veil/rules/{letter}.md |
AI-readable markdown surface / mirror |
shared/runtime/veil-normalize.py |
Normalize candidate terms after capture; return existing matches and new candidates |
shared/runtime/veil-sync.py |
Push vocabulary rules to AI tool configuration files |
shared/runtime/veil-lint.py |
Check final text for registered source terms before sending |
shared/runtime/veil-status.py |
Show canonical / mirror / sync target / skill status and setup diagnostics |
shared/tools/veil-profile-audit.py |
Audit rule count and legacy flat rule presence in current profile |
shared/tools/veil-profile-export.py |
Export current profile as a domain profile pack |
shared/tools/veil-db.py |
SQLite canonical CLI: init-db / import-rules / readback / upsert-rule / export-mirror / export-html |
The Python scripts (veil-sync, veil-lint, veil-normalize, etc.) are CLI tools invoked from the terminal or by the skill. The /veil-capture skill installs as 2 files — one for Claude Code (~/.claude/commands/veil-capture.md) and one for Codex (~/.agents/skills/veil-capture/SKILL.md).
shared/tools/veil-db.py initializes, imports, and reads back the SQLite canonical, handles single-rule upsert, generates the markdown mirror, and generates ~/.veil/veil.html — a browser-based vocabulary list for reviewing and modifying registered terms.
~/.veil/behavior.md is an optional plain-text file. When present, its content is appended to every sync target alongside vocabulary rules. Use it to enforce output behavior that is not term-specific: tone, code style, response format, language constraints.
Example:
Do not use compound English words mixed into Japanese sentences.
Respond to Japanese prompts in Japanese.
Create the file manually — VEIL reads it automatically during sync. Vocabulary rules in ~/.veil/rules/ and behavior rules in ~/.veil/behavior.md are kept separate so each can evolve independently.
-
VEIL core
capturenormalizesynclintstatus- classification order skeleton
-
Domain profile
~/.veil/rules/- prohibited term set
- high-demand term set
- how to handle defined terms
- criteria for keeping proper nouns
lintenforcement level
The current default profile is for technical writing.
Requires Python 3.8 or later and Git.
# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/maruwork/veil/main/get-veil.sh | bash# Windows (PowerShell)
irm https://raw.githubusercontent.com/maruwork/veil/main/get-veil.ps1 | iexClones the repo to ~/tools/veil (macOS/Linux) or %USERPROFILE%\tools\veil (Windows), then runs the installer. Re-running updates the existing install via git pull.
To use a different location:
VEIL_REPO=~/dev/veil bash <(curl -fsSL https://raw.githubusercontent.com/maruwork/veil/main/get-veil.sh)$env:VEIL_REPO = "$env:USERPROFILE\dev\veil"
irm https://raw.githubusercontent.com/maruwork/veil/main/get-veil.ps1 | iexClone to a fixed location, then run the installer:
# macOS / Linux
git clone https://github.com/maruwork/veil.git ~/tools/veil
bash ~/tools/veil/install.sh# Windows (PowerShell)
git clone https://github.com/maruwork/veil.git $env:USERPROFILE\tools\veil
& "$env:USERPROFILE\tools\veil\install.ps1"The installer copies skill files to the tool directories, writes sync_script, veil_root, and lang to ~/.veil/config.json, initializes ~/.veil/veil.db, and auto-registers AI config files found in ~/.claude/ and the current directory as sync targets.
To install skill files only without the installer:
Claude Code
# macOS / Linux
cp skills/claude-code/veil-capture.md ~/.claude/commands/veil-capture.md# Windows (PowerShell)
Copy-Item skills\claude-code\veil-capture.md $env:USERPROFILE\.claude\commands\veil-capture.mdCodex
# macOS / Linux
cp -r skills/codex/veil-capture ~/.agents/skills/veil-capture# Windows (PowerShell)
Copy-Item -Recurse skills\codex\veil-capture $env:USERPROFILE\.agents\skills\veil-captureinstall.sh automatically registers AI config files found in ~/.claude/ and the directory it is run from. To register additional files:
python shared/runtime/veil-sync.py --add /path/to/CLAUDE.mdSiblings in the same directory (AGENTS.md, GEMINI.md, .cursorrules, etc.) are auto-registered alongside the given file. The rules are applied immediately on registration. Supported tools:
| Tool | Config file | Marker format |
|---|---|---|
| Claude Code | CLAUDE.md |
<!-- VEIL_START --> |
| Codex | AGENTS.md |
<!-- VEIL_START --> |
| Cursor | .cursorrules |
<!-- VEIL_START --> |
| GitHub Copilot | .github/copilot-instructions.md |
<!-- VEIL_START --> |
| Gemini CLI | GEMINI.md |
<!-- VEIL_START --> |
| Aider | .aider.conf.yml |
# VEIL_START |
Run at every task close or conversation boundary in Claude Code:
/veil-capture
Example output:
- common asset (current) → shared asset (candidate 1) | common resource (candidate 2)
- current state (current) → present state (candidate 1) | current state (keep)
- validator (current) → validator (keep) | checker (candidate 2)
Select current or a candidate.
The selected candidate is recorded as preferred in the canonical route. ~/.veil/rules/ is regenerated and rules are synced to AI tool config files. Candidates 2 and 3 are stored as alternatives in the DB.
- Candidate 1: recommended adopted term
- Candidate 2: required alternative displayed alongside
- Candidate 3: optional additional candidate
Adoption priority order:
- High-frequency terms causing active problems
- Terms core to VEIL operation itself
- Project-specific terms
- Low-frequency and boundary-ambiguous terms
Category 4 does not need to be registered urgently — skip and revisit later.
Classification goes in at least these 5 directions:
- Keep as a proper noun
- Translate as a common term
- Fix as a defined term
- Drop as a prohibited term
- Skip if classification is not yet clear
When candidate terms have variants, use the normalization helper before writing:
python shared/runtime/veil-normalize.py --stdin
python shared/runtime/veil-normalize.py --text "current states\ncurrent_state\nCurrent-State"This helper:
- Collapses case, hyphen, underscore, and light singular/plural variants
- Groups each normalized cluster with its variants and occurrence count
- Cross-checks against existing SQLite canonical / mirror (
Existing matches:group) - Suggests mirror target file for terms with no existing match (
New candidates:group)
Example output:
Reference rules: rules
Existing matches:
- current state → present state
New candidates:
- implementation plan x3 → i.md
The Existing matches: group is treated as already cross-checked — confirm the preferred term. In the New candidates: group, review terms with higher x{N} counts first. Final adoption decisions rest with the owner.
To analyze external text (e.g. Codex output):
/veil-capture <paste target text here>
Generate a browser-based vocabulary list to see all registered terms and their candidates:
python shared/tools/veil-db.py export-html # write ~/.veil/veil.htmlOpen ~/.veil/veil.html in a browser. Each row shows a registered term alongside its candidates (preferred form, candidate 2, candidate 3). Use the search box to filter. Re-run export-html any time the DB changes to keep the list current.
To change the preferred form, use the HTML list:
- Open
~/.veil/veil.html(runexport-htmlfirst if needed) - Find the term — hover over the target candidate and click Copy
- This copies a ready-to-paste instruction (
Change '{term}' to '{candidate}') to the clipboard - Paste into the AI chat — this triggers a new capture cycle that records the updated preferred form
- After capture: run
export-mirror,export-html, andveil-sync.pyto propagate the change
To update directly without AI:
python shared/tools/veil-db.py upsert-rule --term "current state" --preferred "present state" --level required
python shared/tools/veil-db.py export-mirror # regenerate markdown mirror
python shared/tools/veil-db.py export-html # regenerate HTML list
python shared/runtime/veil-sync.py # push to sync targetsThe markdown mirror under ~/.veil/rules/ can also be read or edited directly:
~/.veil/rules/
├── m.md # rules for terms starting with m
├── u.md # rules for terms starting with u
└── ...
# u
## 必須
- uncommitted → uncommitted (keep)
- untracked → untracked (keep)
## 推奨
- unstable wording → inconsistent phrasing
## 観察
- update path → update path (keep)After editing a mirror file directly, run import-rules to reload it into the canonical DB, then export-html and veil-sync.py to propagate. import-rules replaces all existing rules in the DB — a warning is printed before execution.
python shared/runtime/veil-sync.py # update all sync targets
python shared/runtime/veil-sync.py --list # list registered targets
python shared/runtime/veil-sync.py --add <path> # register a sync target
python shared/runtime/veil-sync.py --remove <path> # unregister a sync target
python shared/runtime/veil-sync.py --remove <path> --purge # unregister and remove VEIL block from fileCheck that recorded vocabulary is actually present in the text using shared/runtime/veil-lint.py before every final response.
python shared/runtime/veil-lint.py <file> # check a response file
python shared/runtime/veil-lint.py --stdin # check from stdin
python shared/runtime/veil-lint.py --text "I organized the current state" # check a string directlyCLEAN: no registered source terms found in the text- violation: registered source terms remain in the text — check the preferred term and fix
SKIP: no rules in~/.veil/rules/— does not exit with error
On a violation, shared/runtime/veil-lint.py returns the suggested fix and a line preview. Follow that guidance first; then revise the full sentence if needed.
veil-capture handles extraction, recording, and sync. shared/runtime/veil-lint.py handles pre-response checking. Keep these roles separate.
lint is not for enforcing the entire prose — it is a gate for registered high-impact terms and prohibited terms.
Check canonical DB rule count, mirror last-updated timestamp, and sync target state:
python shared/runtime/veil-status.pyRun setup diagnostics when something seems wrong:
python shared/runtime/veil-status.py --check[ERROR] exits with code 1. [WARN] only exits with code 0 and is safe to continue.
Check rule count and any remaining legacy flat rules in ~/.veil/rules/ using shared/tools/veil-profile-audit.py:
python shared/tools/veil-profile-audit.py
python shared/tools/veil-profile-audit.py --json
python shared/tools/veil-profile-audit.py --db ~/.veil/veil.dbaudit, normalize, and lint all accept --db to read from a SQLite source. veil-normalize.py maintains the existing-match return format and allows distinguishing the source via source_type and source in JSON output. veil-lint.py keeps rules-dir compatibility and maintains the violation / clean / skip / error return contract and exit codes. If the DB file exists but is unreadable, the CLI returns a structured error instead of a traceback.
The SQLite canonical support CLI is shared/tools/veil-db.py. Common operations:
python shared/tools/veil-db.py init-db # initialize ~/.veil/veil.db
python shared/tools/veil-db.py upsert-rule --term "foo" --preferred "bar" --level required
python shared/tools/veil-db.py readback --json
python shared/tools/veil-db.py export-mirror # regenerate ~/.veil/rules/
python shared/tools/veil-db.py export-html # regenerate ~/.veil/veil.htmlTo cut the current default profile as a domain profile pack:
python shared/tools/veil-profile-export.py --profile-name technical-writing-default
python shared/tools/veil-profile-export.py --profile-name finance-guardrail --domain finance --base-profile technical-writing-default
python shared/tools/veil-profile-export.py --profile-name technical-writing-default --output-dir ~/my-exports/custom-packBy default, output goes to ~/.veil/profile-exports/<profile-name>/:
*.mdrule filesmanifest.json
This is a read-only export — it does not modify the canonical route or ~/.veil/rules/ mirror. manifest.json includes domain, intended_use, and base_profile to preserve the branching contract.
To branch from an existing pack:
python shared/tools/veil-profile-export.py --base-manifest ~/.veil/profile-exports/technical-writing-default/manifest.json --profile-name medical-guardrail --domain medicalVEIL is not designed to register everything at once. Add high-demand terms first, a few at a time.
Basic rules:
- Classify first. Do not mix identifiers, proper nouns, descriptive terms, and project-specific terms and rush to a translation decision.
- Adopt high-demand terms first. Lock in frequently troublesome terms and terms that are core to VEIL operation itself.
- Skip uncertain terms. If a term is unclear whether it is a proper noun or common term, if translations conflict, or if the pain level is still low — do not rush to canonicalize.
- Do not add too many at once. Run a small set of adopted terms through
syncandlint, confirm they are working, then add the next batch.
Enforce tightly only at the key points, not everywhere:
- Enforce the flow tightly. Run
captureat every task close / conversation boundary, andlintbefore every final response. - Enforce high-impact terms tightly. Prioritize prohibited terms, VEIL core terms, and high-demand terms where drift causes real problems.
- Do not rush low-frequency or ambiguous terms. Skip them.
- Do not enforce the full natural text. Do not put natural paraphrases, machine-processed terms, and context-dependent terms behind hard gates.
veil/
├── README.md
├── CHANGELOG.md
├── LICENSE
├── get-veil.sh # one-liner installer (macOS / Linux)
├── get-veil.ps1 # one-liner installer (Windows)
├── install.sh # installer: skills + config + init-db (macOS / Linux)
├── install.ps1 # installer: skills + config + init-db (Windows)
├── pytest.ini # pytest configuration
├── pyrightconfig.json # Pyright type-check configuration
├── locale/ # locale strings
│ ├── en.json
│ └── ja.json
├── shared/
│ ├── runtime/
│ │ ├── veil-normalize.py # candidate term normalization and cross-check
│ │ ├── veil-sync.py # rule sync script (core tool)
│ │ ├── veil-lint.py # pre-response vocabulary check (core tool)
│ │ └── veil-status.py # canonical / mirror / sync target status
│ └── tools/
│ ├── veil-profile-audit.py # profile audit helper
│ ├── veil-profile-export.py # profile export helper
│ ├── veil-db.py # SQLite canonical support CLI
│ ├── veil_locale.py # locale detection and t() lookup
│ └── veil_rule_store.py # SQLite schema / upsert / mirror export shared helper
├── skills/ # skill templates
│ ├── claude-code/
│ │ └── veil-capture.md # Claude Code slash command
│ └── codex/
│ └── veil-capture/
│ └── SKILL.md # Codex skill
├── tests/ # pytest suite
│ ├── conftest.py
│ ├── helpers.py
│ ├── test_db.py
│ ├── test_lint.py
│ ├── test_locale.py
│ ├── test_normalize.py
│ ├── test_profile.py
│ ├── test_status.py
│ └── test_sync.py
└── docs/
└── veil-design.md # design reference
| File | Purpose |
|---|---|
| SUPPORT.md | Documentation pointers, issue routing, scope |
| CONTRIBUTING.md | Contribution flow and PR expectations |
| SECURITY.md | Vulnerability reporting |
| CODE_OF_CONDUCT.md | Community standards |
| CHANGELOG.md | Release history |