Skip to content

Releases

Versions prior to v0.5.0 were released without written notes. See the commit history for changes in older versions.

v0.8.12026-08-27

A gate in front of grep, and a skill you can actually change. Nothing stored changed shape and no pass has to run again: both additions read the graph that is already published.

  • kivgraph hook install registers a gate that runs before the agent’s own tools and refuses a search when the graph answers it better. It closes on one fact — two or more things declare the name, so a text search cannot separate what it finds — and that is the only threshold the measurements support. Over the six questions of benchmarks/mcp-token-cost the factor splits by how many things share a name and not at all by how many references a symbol has: the three we lose (DiscoverGo 0,83x, MergeAll 0,85x, CanonicalColumns 0,91x) are all unambiguous names, and the two we win biggest (Publish 6,42x, BuildPlan 1,92x) are the shared ones. A reference-count floor was written first and removed: the corpus has nothing above four references, so any number would have been invented.
  • The refusal names the call to make instead, and quotes the rows it is talking about, because a refusal without a next step is an obstacle rather than a redirect. KIVGRAPH_DISABLE_HOOK=1 runs one anyway.
  • Four clients host it: Claude Code, Claude Desktop, Codex and OpenCode. The first three read the same verdict off stdout, so one command serves them and only the file it is registered in differs. OpenCode gets a generated plugin, because its tool.execute.before returns Promise<void> and the only way to stop a tool there is to throw. Oh My Pi is the one that cannot: its own documentation calls its hook subsystem legacy.
  • It costs 2 ms on the path that allows and 5,6 ms when it asks the daemon, measured on this repository’s graph. Classification is pure and runs before the configuration is read, so nearly every call leaves by the first path. Every failure — unreadable payload, no configuration, a directory no registered repository holds, no daemon — writes nothing and exits 0. An explicit allow on that wire does not mean “carry on”, it means “skip the permission prompt”, so the gate never sends one.
  • kivgraph skill install now writes one file and links the clients to it. Three installed clients used to mean three copies and no source, which left the obvious question unanswered: what you do to change it. Editing one copy did not reach the others and the next install took it away. The canonical file is ~/.config/kivgraph/skills/kivgraph/SKILL.md, beside config.yaml, and one edit reaches every client.
  • An upgrade keeps that edit. Reinstalling is what an upgrade does, so install writes the canonical only when it is absent or still holds the shipped skill; skill status says when it carries local edits, and --force takes the shipped version back. A copy from an earlier install becomes a link without asking, because the bytes are the ones we would have written. Project scope still copies: that path is committed, and a link to an absolute path under one machine’s home would arrive broken on every other clone.
  • The desktop app is detected by the entry it actually ships. The Linux marker this repository looked for, claude.desktop, does not exist — the package installs com.anthropic.Claude.desktop — so mcp install and skill install were not detecting Claude Desktop either.

v0.8.02026-08-26

Requires reindex: run kivgraph index --full after updating. Until you do, a graph published by the previous version still answers, without this version's facts.

A tool for the question that names no symbol, a traversal that shows its route, and an index that no longer dies whole. Reindex after updating: the TypeScript loader reads more of a project than it did, and a published graph only grows when the pass runs again.

  • find_by_intent takes a plain-language description and returns ranked symbols — or, under view: "files", just the files to open. Measured against git grep -l -i on 24 questions over three repositories, none of which can contain an identifier of its own answer: the phrase alone finds 6 of 24, the phrase plus the identifier words a caller can guess finds 11, and naming repo as well finds 17, against 7 for the native search. A session costs the same either way — 0.96x, ceiling 1.09x — so the accuracy is free. It reads no code from disk: the term index is derived when the snapshot is read, in 22 ms, and nothing about the stored format changed.
  • trace_dependencies returns the path. It could say whether A reaches B; now it shows the route it took, so a claim of reachability can be checked instead of trusted.
  • A go.mod that holds no Go is not a failed index, and neither is a repository whose analyzer this machine lacks. Either one used to fail the whole pass: on a machine where a Hugo theme sat inside an indexed repository, a real index of five repositories produced nothing at all. Both are absences now — counted, named, and carried in index --full --json. Thanks to @Tutitoos for finding and fixing it.
  • TypeScript reads the project the compiler reads: a jsconfig as the project of a JavaScript package, an include entry naming a directory as the tree under it, and the source extensions the compiler accepts. On this workspace that is 22.299 symbols before and 32.419 after — +45 % — which is why this release asks for a reindex.
  • kivgraph audit inspects registered repositories without indexing them, and a bare kivgraph prints the command table instead of an error.
  • What an index could not load is now published, not just counted. All four languages carry not_loaded in the report and in the --json result; two of those counters existed and were printed nowhere. A language with no code reports zero symbols and so does a repository that could not be read, so a count that only lives in a log is a warning nobody has.
  • Details in docs/adr/0072-la-recuperacion-por-intencion-y-las-tres-senales-que-no-entraron.md and docs/adr/0073-la-prosa-no-paga-el-esquema.md.

v0.7.02026-08-25

kivgraph mcp install now registers a client against one daemon by default, and the daemon has an owner that starts it and restarts it. Nothing to reindex.

  • The daemon is the default registration. mcp install writes a url entry instead of a command, so every editor shares one process. On a workspace of 108.737 symbols with eight idle clients that is 11 MB of private pages against 80, and 61 against 328 while they are answering; peak is 26-29 MB against 179-186. --stdio writes exactly what the previous default wrote, and --daemon still parses.
  • An entry a previous install wrote is replaced, not refused. Switching transports leaves one registration and not two, without --force. An entry naming a different kivgraph installation still needs it.
  • kivgraph daemon install gives the daemon an owner — a launchd agent on macOS, a systemd user unit on Linux — keyed by state directory, so two configurations can hold two supervised daemons. daemon status and doctor report whether one exists; daemon remove takes it out. A platform with no supported supervisor says so instead of pretending it installed something.
  • kivgraph daemon --addr and --allow-remote work. They were declared in the flag set and printed by the help, and the daemon read them before they were parsed: it could only ever bind 127.0.0.1:7788. Anyone who passed --addr got the default port and no warning.
  • Three conditions still write the stdio entry, and each names itself: --scope project, because a url entry carries a token and that file gets committed; a platform with no supervisor; and a machine that has not run kivgraph init yet. None of them depends on whether a daemon happens to be running.
  • Details in docs/adr/0068-el-demonio-tiene-dueno.md and docs/adr/0069-el-demonio-es-el-defecto.md.

v0.6.02026-08-24

One process can now serve MCP to every editor you have open, and a server that nobody queries no longer pays for the graph. No reindex needed.

  • kivgraph daemon serves many clients from one process, over HTTP and a unix socket at once. kivgraph mcp install --daemon registers a client against it, writing a url entry instead of a command. HTTP is the door that matters: no MCP client configuration dials a unix socket. See Register with a client.
  • The graph is read by the first query that needs it, not at startup. On a workspace of 108.737 symbols, a server nobody asks anything holds 10 MB per client instead of 33, eight editors starting at once peak at 183 MB instead of 994, and a new client connects in 1423 ms instead of 96151. Measured in benchmarks/daemon-cost; with load the figures are unchanged. Details in docs/adr/0067-el-grafo-se-lee-cuando-alguien-pregunta.md.
  • An unreadable snapshot no longer kills the server at startup. It reaches a caller as INDEX_NOT_READY, and graph_status gains an optional snapshot_unreadable field naming the reason, so «could not be read» stops looking like «never indexed». Additive: a client that ignores it sees no change.
  • At eight clients the daemon costs 61 MB against 328 while answering, and 11 against 80 while idle. At one client it costs a couple of megabytes more than a plain serve, so it is worth running from the second client on.
  • Fixes: the daemon publishes its HTTP endpoint before binding the socket, so a client can no longer reach a daemon whose daemon.json does not exist yet; the unix socket is created with its mode already set, which is what made it work on bind-mounted filesystems; and the global help and shell completion now name the daemon’s --addr and --allow-remote.

v0.6.12026-08-24

Everything described under v0.6.0, which was tagged but never published: its build failed before the release step, so v0.6.1 is the first downloadable version of that work. Nothing to reindex.

  • The check that every release note names a real tag ran for the first time in this release and failed in CI while passing locally. CI checks out shallow and fetches no tags, so git tag returns nothing there — and an unreadable tag list is not evidence that a tag does not exist. It now skips and says so, instead of failing and pointing at the wrong thing. The check itself is unchanged wherever tags are present.

v0.6.22026-08-24

Everything described under v0.6.0, which is the first version of this work that actually publishes. v0.6.0 and v0.6.1 were tagged and both builds failed before the release step, so neither exists as a download. Nothing to reindex.

  • What failed was a check on this repository’s own release notes, not the product: it verified that every note names a real git tag, and it read that from whatever tags the checkout happened to carry. CI fetches none by default, and a checkout on a tag ref carries exactly one — the version being built, which is a partial list that looks complete. CI now fetches the tags, and the check skips instead of failing when it cannot tell a partial list from a complete one. Where the list is real it is unchanged.

v0.6.32026-08-24

Everything described under v0.6.0, which is the first version of this work that actually publishes. v0.6.0, v0.6.1 and v0.6.2 were tagged and every build failed before the release step, so none of them exists as a download. Nothing to reindex.

  • All three failures came from one check on this repository’s own release notes, never from the product. It verified that every note names a real git tag, reading that from whatever tags the checkout happened to carry: none by default, and exactly one — the version being built — when the checkout is a tag. fetch-tags cannot fix it either, because combined with a tag ref it makes git fetch the commit and the tag to the same place and the checkout fails outright. The check now runs where the tag list is real, a developer’s clone, and skips with a reason where it is not.

v0.6.42026-08-24

Everything described under v0.6.0, which is the first version of this work that publishes. v0.6.0 through v0.6.3 were tagged and every build failed before the release step, so none of them exists as a download. Nothing to reindex.

  • Three of those failures were one check on this repository’s own release notes, fixed in v0.6.3. The fourth was a test of the reconciliation tick that builds a published generation as its fixture: publishing enforces the production space policy — at least 15 % of the filesystem free — and the macOS runner had less. That is a precondition of the host, not of the code under test, so it now skips and names the reason instead of failing. It still runs, and still defends the invariant, wherever the host has the space.

v0.5.02026-08-22

Requires reindex: run kivgraph index --full after updating. Until you do, a graph published by the previous version still answers, without this version's facts.

A method now belongs to the type that declares it as its receiver, and that changes what a Go or Rust type reaches.

  • trace_dependencies on a Go struct or a Rust type now includes the reach of its methods. It did not before: a method is declared outside its type’s own span — func (h *T) M() in Go, an impl block in Rust — while a TypeScript class holds its methods inside its braces. The same question returned a complete answer for TypeScript and a silently incomplete one for the other two. Measured on the private benchmark corpus at depth: 1, a Go handler with nine methods went from 3 rows to 53, and a Rust type with two impl blocks from 2 to 18.
  • get_blast_radius is unchanged. A type’s own methods are not its consumers, so containment is not an answer to what breaks if you change it.
  • graph_status now reports schema_version_expected and schema_outdated beside schema_version, so a graph built by an older binary says so instead of leaving the comparison to you.

The canonical schema goes from 3 to 4. A generation published by the previous version still opens and answers normally — it is outdated, not corrupt — but it cannot carry facts its resolver never emitted, so a Go or Rust type’s reach stays incomplete until you rebuild it.