Download Latest Version semlith-v0.51.1-x86_64-pc-windows-msvc.zip (24.5 MB)
Home / v0.24.0
Name Modified Size InfoDownloads / Week
Parent folder
semlith-v0.24.0-aarch64-apple-darwin.tar.gz 2026-09-19 22.9 MB
semlith-v0.24.0-aarch64-unknown-linux-gnu.tar.gz 2026-09-19 22.4 MB
semlith-v0.24.0-x86_64-pc-windows-msvc.zip 2026-09-19 20.5 MB
semlith-v0.24.0-x86_64-unknown-linux-gnu.tar.gz 2026-09-19 23.5 MB
SHA256SUMS 2026-09-19 448 Bytes
README.md 2026-09-19 8.6 kB
Semlith 0.24.0 source code.tar.gz 2026-09-19 2.4 MB
Semlith 0.24.0 source code.zip 2026-09-19 2.6 MB
Totals: 8 Items   94.4 MB 0

The number, and the defect the number found

semlith ledger --verify now prints what the ledger adds up to beneath the chain result, and the README carries a savings paragraph again — the first since 0.17.2 removed the double-counted one, and only because it is now measured. Asking semlith brief all 107 questions of this repository's own retrieval harness against a store of src/: 3 982 tokens per answered question is what the agent was sent, 105 132 is what reading the 4.1 files those answers named would have cost whole. 26×, at 100 % coverage, counted by the store's own tokenizer. It is an upper bound and says so.

Measuring it found the defect that made it worth measuring. semlith brief at a terminal, and the portal's Brief view, both recorded every answer as a retrieval that found nothing: the ledger recovers the files an answer named by reading them back out of the rendered reply, and both of those hand it JSON instead. The first run of this paragraph's own measurement read saved 0 tokens over 0 of 107 retrievals, coverage 0 % — for the one command 0.23.0 exists for. A brief knows which files it named; it is asked now rather than parsed. Any 0.23.0 ledger holding CLI or portal briefs has under-counted them, and re-running the query is the only way to correct rows that are, by design, not rewritten.

The graph reads as a graph

Three things were wrong with what a freshly indexed project drew. The page opened on the busiest symbol, which in any codebase is its most reused name — new, len, get — earning its degree from forty unrelated callers the extractor could only match by spelling, so the first thing anyone saw was a star of dashed inferred lines between functions with nothing to do with each other. The opening symbol is now the one with the most edges the extractor actually resolved, and a name defined once in the store beats a name defined forty times however busy it is. When the node budget cuts a neighbourhood — and on a hub it always does — the resolved edges are drawn and the spelling matches are the ones left out, rather than whichever sorted first.

The store chips scoped the canvas and not the panel beside it, so a symbol defined in two open stores listed both stores' callers under a chip naming one of them: the rail contradicted the picture next to it. It is scoped to the same stores now.

And a view where nothing is connected says so. It is a real state — a language semlith parses for definitions but not yet for calls, or a scope holding both ends of no edge — and a field of unconnected boxes with no explanation reads as a broken page.

A corpus is the project, not its dependencies

The walk carries a table of generated and vendored directories of its own, for every language semlith supports. .gitignore was the only thing standing between a store and a dependency tree, and it is a statement about what is committed: it is absent from a folder somebody downloaded rather than cloned, it is absent where npm install ran outside a repository, and where a user keeps node_modules in their global gitignore the walk cannot see it at all. A Node project indexed under any of those three swallowed its whole dependency tree — tens of thousands of chunks nobody asked about, and as many graph nodes with no edge into the project's own code, which is what a field of unconnected dots on the Graph page actually was.

A name that is never somebody's own source — node_modules, __pycache__, .venv, .gradle, DerivedData, .dart_tool, _build and the rest — is stepped over outright. A name that often is somebody's own — target, build, dist, out, bin, obj, vendor, deps — is stepped over only when the manifest that generates it is sitting beside it, so a project with a hand-written build/ and no build file keeps it. SEMLITH_DEFAULT_IGNORES=0 turns the table off whole.

The index run names every directory it stepped over, rather than counting the files inside one: pruning the subtree is the point, and counting what is in it would undo the saving in order to report it. An existing store drops what it already holds on its next index pass; nothing needs migrating.

One skill, installed once and linked everywhere

The binary carries an Agent Skill named semlith, in agentskills.io format, and semlith setup writes it to ~/.semlith/skills/semlith/ and links that one copy into every user-level skill directory a documented client reads — the cross-client ~/.agents/skills, and Claude Code's, Qwen Code's and Kiro's. Canonical plus links rather than a copy per client: removing the canonical directory removes all of them, and a link that has gone stale is reported rather than silently left behind.

semlith setup --register-all also writes the short always-on rule block into the user-level rules files semlith knows a path for, between markers of its own and with a backup beside each file first. Nothing outside those markers is read or written, and a second run replaces what is between them rather than appending again. For a client whose rules file semlith does not know — Cursor and Copilot keep theirs in their own interface — doctor prints the block to paste.

semlith doctor now reports, per client, whether the skill is linked, whether the hook is present, and whether the rule block is there, in four states each: linked, absent, stale, or paste needed. The portal's Doctor page shows the same states from the same computation, beside the registration it already showed.

The MCP initialize result carries the instructions text server/discover has sent since 0.20.0. Every client still on a 2025 revision — which is most of them — never calls discover, so until now the sentence saying what this server is for reached only the clients that needed it least.

Gemini CLI is not part of this: its MCP registration is unchanged, but no skill link, rule block or hook is written for it, because none was ever run against a live Gemini CLI and a client whose hook was never exercised is not evidence.

The agent is steered, not only taught

semlith hook answers one PreToolUse event. When a registered store holds the file a client is about to read whole — or the tree it is about to grep — it adds one line naming the semlith_brief or semlith_read call that answers the same question, and otherwise it says nothing at all. It decides from registry.json alone, so a file no store holds costs one path comparison, and it never sends a permission decision: answering allow would approve a read the user's own rules were about to be consulted about. --strict refuses the first qualifying read of a session and then reverts to the line.

Each whole-file read it sees becomes one ledger row — kind raw-read, the path, what reading it whole cost, the client and the session — chained like every other row and uncredited, because semlith answered none of it. That is what makes Refunds a measurement rather than an estimate: until now the ledger counted only the questions semlith was asked, which leaves out every one it was not. The row goes through a running daemon or not at all; the hook never opens a store from inside a client's tool call, and --no-ledger and SEMLITH_LEDGER=0 stop these rows exactly as they stop the rest.

Every search filter negates

A leading ! on a --path, --ext or --lang value — and on the path, ext and lang fields of every MCP tool that takes them — excludes instead of including. Exclusions apply after the inclusions of their own kind, so --path 'src/**' --path '!src/vendor/**' is everything under src but the vendored tree, and an exclusion written on its own is everything except.

A filter that admits no indexed file now says so in one sentence wherever it happens — the terminal, semlith_search and semlith_brief — rather than in three wordings on the commands that had a check and silence on the ones that did not. The README's "no way to express not this path" limit is gone with it.

Install

macOS and Linux

:::sh
curl -fsSL https://raw.githubusercontent.com/semlith/semlith/main/install.sh | sh

Windows

:::powershell
irm https://raw.githubusercontent.com/semlith/semlith/main/install.ps1 | iex

Or download the archive for your platform below, unpack it, and put semlith on your PATH. Intel macOS has no prebuilt binary: ONNX Runtime no longer publishes x86_64 macOS builds.

Verify a download against SHA256SUMS before running it.

Source: README.md, updated 2026-09-19