Your codebase's vocabulary, enforced — and your agents can't drift it.Words, design tokens, and rulings live in a database; a tree-sitter scanchecks every named thing in ten languages against them; a pre-write hookcorrects your coding agent before drift ever lands.
# the CLI, works today
uvx --from "git+https://github.com/shinyobjectz/montology#subdirectory=.monty/cli" monty init
# the agent skill (Claude Code, Cursor, and friends)
npx skills add shinyobjectz/montology
# npm (the launcher)
npm install -g montology
montology
Your design system and your vocabulary, enforced — in any repo, by onecommand. montology reads what your code already declares — theTailwind theme, the CSS, every class and every named thing in tenlanguages — and turns it into an ontology with a gate: drift fails CIwith the file, the line, and the repair.

uvx --from "git+https://github.com/shinyobjectz/montology#subdirectory=.monty/cli" monty init
(that one-liner works today, from nothing but uv; npm install -g montology and PyPI are landing)
Sixty seconds to a drift report
cd your-repo
monty init # .monty/, agent wiring — and your Tailwind theme
# auto-adopted as design tokens (the theme is the law)
monty lint
warn design: rogue color #121212 ×2 (first at css/app.scss:37)
— nearest token: ink #1b1b1f (Δ31)
note design: #ffffff and #fafafa are Δ15 apart (11× / 4×) — one job,
two values; pick one and tokenize it
note design: class 'ghost-panel' used 6× but defined in no stylesheet
note design: 3 Tailwind arbitrary value(s) — each left the scale
(p-[13px], text-[#123456]…)
No config, no authoring — the theme you already wrote becomes the law,and every literal that escaped it gets a receipt. Recurring utilitycompositions surface too: monty design recipes mines the class stringsyour markup repeats (flex flex-wrap gap-2 items-center ×102 — onshadcn/ui's own repo) so they can become named things.
The firewall: your agent cannot write drift

Everything above is post-hoc. The guard runs before the write:monty init installs a PreToolUse hook (merge-safe, into.claude/settings.json) that lints every proposed Write/Edit against theontology in milliseconds — a declaration named after a retired word(renames are rulings; always blocks), a collision with an enforced word,a rogue hex when tokens exist. Deny is exit 2 with the repair on stderr:the harness feeds it straight back to the model, which corrects andretries. The agent physically cannot introduce a second gray or resurrecta renamed concept — it gets the token or the current word handed to itmid-edit. The guard fails open (malformed payload, no workspace, anyinternal error → allow silently) so it can never break an editor; humansin vim never meet it. Config: [guard] names/design = block | warn | off.
monty explain — the one-shot conceptual X-ray

Point montology at any repo cold: one command composes the declaredsurface, the vocabulary it has, the vocabulary it is asking for (withdefinitions drafted on the atomic tier when one serves — law-checked,refused over wrong), where meanings actually gather (semanticclusters vs the directory tree's claimed architecture: cross-cuttingconcepts, grab-bag directories), the design system as measured, andevery place the repo contradicts itself — straight to the terminal,because an instrument prints findings, it does not decorate them.
The part that keeps you: words
A repo's concepts drift exactly like its colors. montology's vocabularyis a database, not a doc — one word, one meaning, a one-line test,an optional dotted code — rendered into a generated agent skill andenforced against every declaration tree-sitter can parse (python,ts/tsx, js, go, rust, elixir, ruby, java, c, c++):

monty onto check thread # FREE / TAKEN / RULED — before naming ANYTHING
monty scan --candidates # the words your codebase is asking for
monty onto add thread "a stateful user↔agent session" --code atl.thread --pos noun
monty onto amend thread --definition "…" --why "a later ruling narrowed it"
monty lint # collisions (advisory by default), code-tree
# integrity, stale prose — each with its repair
Not every symbol sharing a word's name is drift, and treating them alikeproduces a list nobody reads. A word carries what it names — verb,noun, or value type — and the judgment follows from it: Store.open isEnglish doing ordinary work below the surface, while a noun answering fora second thing is the failure a vocabulary exists to prevent. A collisionyou keep is a recorded decision, in the database with the rest of thevocabulary:
monty onto except open --where "lib/**" --why "ordinary work below the surface"
monty onto except --drafts # what an old [scan] allow list would become
What an exception can never do is silence a divergence — onevalue-typed word declared as two different values (@type name :: term()in one module, @type name :: %{…} in another). That is a separate lawwith a separate line: an exception says a symbol may share the name, notthat the name may mean two things.
Rulings end arguments permanently: overloads ("say cell, notsandbox"), collisions with frameworks (whose word it is, who moved),and renames — the old name retires, old material stays readable, andmonty migrate old new --apply propagates the rename through the codeby token (tree-sitter positions, strings and comments untouched,losslessly round-trippable — proven on eight real repos).
When a ruling narrows a word you already authored, monty onto amendcorrects the record in place: the name and its history stay, every fieldthat changes is ledgered with the text it replaced, and an unknown name ora no-op is refused. Editing the database around the authoring path is thesame drift the gate exists to catch.
Meaning over time

Three instruments make a repo's meaning a tracked quantity:
monty vitals— the pulse: gate state, vocabulary state, designstate, guard compliance → one verdict (TENDED / DRIFTING /UNTENDED) with every reason carrying its repair — plus whether thefirewall is wired and the org upstream it inherits.--jsonis thedashboard shape;--strictexits 1 unless TENDED, so a repo can gateon its own tending. Track it per repo the way you track CI.monty drift— the telescope: the git history sampled intolexicon, palette and convergence curves (--csvfor the researchlane). First observation, excalidraw's full history: the palettefragmented ~10× in two years (4→11→27→42 distinct colors) whiledeclarations merely doubled — and their one-off CSS-variable cleanupdid not hold. Flask's concept lexicon, by contrast: 49 concepts in 15years, flat since 2019. Convergence is a property of tending, notof software.monty guard --stats— repair-following, measured: every hookdenial followed by a clean edit within 30 minutes is a complieddenial. The compliance dataset accumulates from ordinary use; everyhooked workspace is a passive experiment in whether enforcementcloses the literature's text-action disconnect.
The research notes — instruments, first measurements, prior art, openprotocols — live in research/FINDINGS.md.
Semantic hearing

The string laws enforce one word, one meaning. The [semantics] extrahears the dual — one meaning, one word — with POTION static embeddings(~30 MB, numpy-only; no torch, no runtime): monty onto audit flags twowords defined into the same idea, local words that duplicate inheritedorg words under different names, candidates that are secretly existingwords, and owner groupings that don't match where meanings cluster.Advisory permanently — a cosine score proposes, only a ruling decides.
One ontology, every repo
The org's vocabulary is authored once — any montology workspace's.monty/ontology.db is the artifact — and inherited everywhere:

monty init --from [email protected]:acme/ontology.git # or a path, or a .db URL
monty onto pull # refresh from the pin
Upstream rows refresh on every pull; local words always survive; a namedefined in both places is a loud conflict (local wins — reconciledeliberately). When the org renames a word, every repo's next pullprints the exact monty migrate command: that is how a rename crossesthe fleet.
The two models it carries (and the ones it refuses)
montology is deliberately near-modelless — the deterministic laws do theenforcing — but it carries exactly two, each chosen for a measured floor:
| model | size | lane | what it does | what it refuses |
|---|---|---|---|---|
POTION (potion-base-8M, model2vec) |
~30 MB, numpy-only | [semantics] extra |
static embeddings over definitions: onto similar, onto audit — duplicate meanings, org/local doubles, misfiled clusters. Millisecond inference, no torch, no runtime. |
deciding anything. A cosine score proposes; only a ruling makes vocabulary. |
| gemma3:270m (via Ollama, optional) | 292 MB, user-installed | monty gen <word> |
drafts ONE-LINE definitions under the word laws (refused over written wrong) when no host agent is present — the autonomous lane. | bodies and prose. The 270M capability floor is atomic one-liners; everything longer is the host agent's work or a served endpoint (MONTOLOGY_MODEL_URL). |
Nothing heavier ships, ever: no torch, no onnxruntime, no bundledweights. The host agent (Claude, Cursor, Codex) is always the bestdrafter available, and the gate never needs a model at all.
For agents
monty init wires the repo for Claude Code, Cursor, and Codex(merge-safe: sections are appended, JSON keys merged, global confignever touched). The generated words skill carries the whole vocabulary— words, tokens, recipes, rulings, doctrine — and the MCP server exposesontology_check, scan_candidates, ontology_lint, structural_searchand friends. Prose is rendered from the database, never authored; astale render fails the build.
Under the hood
tree-sitter (via tree-sitter-language-pack) measures declarations andCSS structurally; ast-grep (invoked, one static binary) powersstructural pattern search; SQLite holds the vocabulary. The stressbattery (stress/run.py, weekly in CI) proves four properties on eightreal repos — flask, excalidraw, gin, ripgrep, phoenix, sinatra,spring-petclinic, redis: merge-safe idempotent init, zero-error parsing,truthful collision reporting, and lossless migrate round-trips.
Contributors
git clone https://github.com/shinyobjectz/montology && cd montology
uv sync && just # the action surface
just check # the gate (montology lints itself, strictly:
# its own toml sets collisions = "enforce")
The marketing-era codebase lives at the marketing-era tag.