Changelog
All notable changes to gonzalo are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning. While the project is pre-1.0, the minor version is bumped for new features and the patch version for fixes.
Unreleased
0.5.0 - 2026-08-22
Code-graph correctness. impact and reference resolution stop asserting edges
they cannot justify — a closure that returned half the repo for one seed, and a
name-matched resolver that credited std methods to same-named project functions.
Alongside that, the MCP server gains aggregate queries that answer questions about
a whole view rather than about a symbol name the caller already has.
Upgrading: the first index after this release does a one-time full re-walk
of every view (see EXTRACTION_VERSION below) — expected, not a fault. Reinstall
the binary as well as re-indexing: the resolution fixes live in the query path, so
a stale gonzalo-mcp keeps returning the old answers over freshly indexed data.
Fixed
-
A method call no longer claims a same-named free function (#223).
Resolution::UniqueGlobalattributed a reference to the sole definition of that name in the view — including when the name was really a std or dependency method. In gonzalo,.chain(ours_obj.keys())ingonzalo-coreresolved tofn chain(), a test fixture ingonzalo-graph, a crategonzalo-coredoes not depend on.References now record the shape of the call site (
RefKind::{Free, Method}), and a cross-file method call resolves to the newResolution::ReceiverUnknownrather than guessing. Measured over gonzalo's own source: of the 388 cross-file method calls that used to resolveUniqueGlobal, 294 pointed at a different crate —push,filter,send,bytes,nextand friends. Same-file method calls still resolveLocal, and free and path calls (foo(),a::b::foo()) are unaffected.Effect on
impact(#207), same 122-file source both runs:seed before after build_rust185 126 assemble60 23 resolve_references_to6 6 The 24 provably-false
gonzalo-corenodes in thebuild_rustclosure are now 0. Dropped edges are reported asreceiver_unknown_edges, counted separately fromambiguous_edgesbecause the cause differs: not "too many candidates" but "cannot claim any candidate".RefKindis omitted from the serialized slice when free, so a file of plain calls keeps its existing content hash. -
impactno longer merges unrelated code through shared identifiers (#207). The closure walked the name-matched caller graph, so one hop into a name with several definitions absorbed every subgraph sharing that identifier. The walk now keys nodes on(name, defining path)and consults the resolver for every edge: anAmbiguousreference is counted and dropped rather than traversed.On the gonzalo view, seeded at
build_rust: 356 → 178 reached names, with 10 ambiguous edges reported rather than followed. Seeds that were already sharp are unchanged (resolve_references_to: 5 → 5).The result is now a report rather than a name list — every node carries the path defining it,
ambiguous_edgessays how many edges could not be attributed (so a non-zero count means the true set may be larger), andtruncatedreports a walk stopped by the new optionalmax_depth. The daemon's HTTP and gRPC transports keep their existing name-list shape and so get the precision fix without the report fields.Of the remaining 178, 17 are still provably false and trace to a single
UniqueGlobalover-attribution — std'sIterator::chainresolving to a same-named test fixture. That is a distinct defect, filed as #223. -
An incremental re-index now prunes paths a laxer run admitted (#209 follow-up). The filter added in #218 only applied to newly walked or changed files, so an existing view kept its vendored bundles forever: a bundle never changes, so it never appears in the git diff and was never reconsidered — and once a base commit is recorded there is no full walk to clean it up. Upgrading therefore fixed new views only, which is the case least in need of fixing.
The carried-forward set is now re-checked against the current rules, including
.gitignore— necessary becausedocs/guide/book/is build output excluded by ignore rules rather than by any directory-name rule, so a path-only prune left it behind.Re-indexing the existing
caliban-ai/calibanview: 17 162 symbols → 8 549, with vendored symbols going 8 618 → 0 and the largest file becomingcaliban/src/tui/events.rs(142) instead of a copy ofmermaid.min.js. -
An unknown
repo/view_idis now an error, not an empty result (#210). Every graph query returned[]withisError: falsewhen the selector named no indexed view, so a one-character typo inview_idwas indistinguishable from a symbol that genuinely is not there — an agent read it as "nothing calls this" and reported a wrong answer as fact.Service::viewnow fails withNotFound, and the MCP layer turns that into a tool error naming the unresolved selector and listing the views that do exist, so a caller can correct itself in one round trip. A real miss inside a real view still returns[], so the two cases are finally distinguishable.diffgets the same check on bothview_aandview_b. -
Calls inside Rust macro arguments are now recorded as references (#216). Macro arguments parse as a
token_treeof raw tokens rather than expressions, soassert_eq!(f(), 1)contained nocall_expressionand the call tofwas never seen. Because assertions are where much of a codebase is exercised, this silently removed a large share of the call graph:callers,callees,impactandtop by=fan_inall undercounted, andunreferencedreported live functions as dead.Re-indexing gonzalo itself, with an identical file set (1 852 symbols both runs), references go from 10 247 to 12 196 — +1 949 edges, +19.0%.
Language::from_extension, the symbol that exposed the bug, goes from 0 recorded references to 28.Detection is token-level: an identifier whose immediate next sibling is a parenthesised token tree. A nested macro has a
!between the two and is excluded. It is deliberately over- rather than under-inclusive — a tuple-struct pattern likeSome(_)reads as a call — which matches a graph that already records constructors and enum variants as calls.The other 17 grammars were audited for the same opaque-node hole. Only C/C++ has one: a
#definebody is a single opaquepreproc_argtoken with no child nodes to read. It is left in place and pinned by a test so the gap is discoverable rather than silent. -
The indexer no longer walks vendored bundles or gitignored build output (#209).
is_indexableskipped onlytarget,.git, and dotted components, so half of a real repo's graph was not that repo's code. Membership now lives in one place (IndexFilter), shared by the full walk and the git-incremental driver so they cannot disagree: dependency/output directories (node_modules,vendor,dist,build,site-packages,third_party) and generated files (*.min.js,*.min.css,*.bundle.js,*-lock.json) are dropped on both paths, and the full walk additionally honours.gitignore.Re-indexing
caliban-ai/calibandrops it from 16 986 symbols to 8 501 (-50.0%) with zero symbols frombook/**or any*.min.js; the largest file in the view is nowcaliban/src/tui/events.rs(142 symbols) rather than a 4 231-symbol copy ofmermaid.min.js. This also removes a reproducibility hole — indexing gitignored output made the graph depend on whether anyone had run a build.gonzalo indexnow reports what it excluded (ignored: N files, M dirs not descended), and--include <path>re-admits a vendored path that a built-in rule would drop.--includedeliberately cannot override.gitignore, so no flag can make a view irreproducible.
Added
-
EXTRACTION_VERSION, and a full walk when it changes (#223). The incremental driver carries unchanged slices forward untouched, so a parser improvement never reached files that did not change — an existing view stayed permanently half-upgraded.gonzalo indexnow records the extraction format alongside the view and rebuilds in full when it differs, which is what lets #216's and #223's parsing changes actually reach an established view. -
A guide chapter for the MCP server (#208) —
docs/guide/src/mcp.md, covering install → index → register → verify → keep fresh, a tool reference grouped by the question each tool answers, and the capability boundaries. It leads with the thing nothing in the repo stated: the server only reads,gonzalo indexwrites, so an unindexed setup answers every query forever with no indication why. It also records the traps found while wiring the server up for real —GONZALO_ROOTnot expanding~(#211),~/.cargo/binmissing from the non-interactive shells an MCP client spawns, and needing to reconnect the server to pick up a newly installed binary — plus a troubleshooting table keyed by symptom. -
viewsdiscovery tool and a view count instatus(#210).viewslists every indexed(repo, view_id)with its file count and the commit it was indexed at, which makes the server self-describing rather than dependent on out-of-band documentation; comparingbase_commitagainst the checkout's HEAD also surfaces a stale view, the quieter form of the same problem.statusnow reports how many views are indexed, so the natural health-check call actually detects a server pointed at an empty or wrong store. Therepo/view_idschema descriptions now say they must match an indexed view and point atviews. -
Aggregate code-graph queries (#214) — three MCP tools that answer questions about a view rather than about a symbol name the caller already has:
overview(file/symbol/reference counts, a breakdown by kind and language, and the largest files),top(rank byfan_in,fan_out, ordefinitions— adefinitionsscore above 1 marks an ambiguous name), andlist(enumerate symbols filtered bypath_prefix,kind, andname_contains). Backed by new defaultGraphStoremethods, so every store implementation inherits them. Results are bounded and reporttotal+truncatedrather than silently cutting. -
unreferenceddead-code candidates (#214) — a fourth aggregate tool listing symbols with no inbound reference, filtered by the samepath_prefix/kind/name_containsand bounded the same way.exclude_tests(default on) drops members of amod tests/mod testblock by line range and anything under atests/directory; on gonzalo itself that is the difference between 515 hits and 40. Deliberately errs toward silence — a reference from anywhere counts, including from tests and from the symbol itself. Its false positives are documented in the tool description, the rustdoc, and a pinned test: calls inside macro arguments are not recorded at all (assert_eq!(f(), 1)registers nothing), and a function passed as a value is a path expression rather than a call, so both look uncalled.
0.4.0 - 2026-08-01
The remote-parity & backend-qualification release. Deletion and blobs — the two
gaps that kept the daemon substrate behind fs/s3 — close, so a daemon-backed
consumer now gets the full Store + BlobStore surface. Alongside them, an HA
soak harness doubles as a conditional-write qualifier for S3 backends, and its
first finding disqualifies Garage outright.
Added
Store::delete(#183) — OCC-aware record deletion across every substrate and the daemon, propagated bySync. Local-only semantics; see ADR 0018. (#185)- Blobs over the daemon (#184) — the content-addressed
BlobStoreis exposed ongonzalo-server(HTTPGET|PUT|DELETE /v1/blobs/{hash},GET /v1/blobs, plus gRPC), andServerStoreimplementsBlobStoreover both transports, so a daemon-backed consumer gets the fullStore+BlobStoresurface. Blobs previously worked only onfs/s3. Adds theGONZALO_MAX_BLOB_SIZEdaemon knob (default 64 MiB). (#192)
Changed
- S3 backends are now qualified, and Garage is not among them (#52) — atomic
If-Matchis a hard requirement for any S3-compatible backend. Garage does not provide it: gonzalo's conditional-write conformance case expects exactly 1 of 8 concurrent racers to commit, and Garage let 8/8 through on v1.0.1 and 3–8 through non-deterministically on v2.1.0 — the signature of a check-then-set, not an atomic CAS. Deployments running gonzalo over Garage can silently lose concurrent writes. RustFS (Apache-2.0) is the qualified backend; MinIO passes the qualifier but is rejected on project sustainability. See ADR 0019. (#186, #205)
Testing: an HA soak harness for stateless gonzalod replicas over an
S3-compatible store — backend-agnostic, doubling as the conditional-write
qualifier above (#52, #186); cross-crate integration tests extracted into
gonzalo-integration-tests (#190, #191).
Project: a crates.io publishing pipeline for the workspace, triggered on v*
tags (#187, #189).
Docs: competitor capability inventories and parity-gap matrices for mem0, Zep,
and Letta under docs/evaluation/ (#193); ADR 0019 recording the S3 backend
qualification (#205).
0.3.0 - 2026-07-11
The hardening & language-breadth release. A broad correctness and robustness sweep — a 20-finding QA pass turned into fixes across the core merge/OCC model, every storage substrate, the daemon, the ticket connectors, and the knowledge/vector layer — lands alongside eight new code-graph grammars that take language coverage from nine to seventeen.
Added
- Language breadth for the code graph — grammars for Ruby, PHP, Bash
(#87), Kotlin (#87), Swift (#87), Lua (#87), Scala (#87), and
Elixir (#87). Elixir is homoiconic (
def/defp/defmacro/defmoduleparse as ordinarycallnodes), so it uses a value-basedwalk()dispatch on the call target's text rather than a node-kind mapping. Coverage is now 17 languages. (#126, #127, #128, #129, #130, #181)
Changed
- Ticket
RecordKeys are board-scoped — a board-scoped source folds its connection/board discriminator into the key, so the same issue imported from two boards no longer collides onto one thrashing record. Stored keys for board sources change shape and re-import on the next sync. (#159)
Fixed
- core — record-key encoding is now a reversible, injective percent-style
codec, so distinct keys can never collide onto one physical path (silent
cross-key overwrite / OCC bypass); append-only merge preserves blank and
legitimately-repeated committed lines instead of stripping/de-duping them, and
the
Derived/gc semantics are corrected. (#131, #133) - storage substrates — the git substrate locks the
putcritical section (no lost updates under concurrent writers) and detects non-fast-forward push rejection; filesystem writes fsync the temp file and parent directory for crash durability; S3 list-pagination terminates when the continuation token is absent; graph-sqlite view-db paths use the injective encoder. (#132, #134, #144, #145) - daemon — authorization runs before request deserialization, internal backend errors are returned opaquely (no path/bucket/SQLite leakage), the PUT record route validates its URL path against the body key, and the remote client surfaces daemon 403/413 responses instead of masking them as a decode error. (#146, #147)
- code graph — JS/TS arrow-function and function-expression bindings are
extracted, PHP method and static calls are recorded, and Swift/Kotlin
struct/enum/interfacedeclarations get their correctSymbolKind. (#136) - knowledge / vector / domain — a corrupt knowledge-bearing body surfaces an
ingest error (rather than silently not indexing) and removed records are
de-indexed; non-finite vectors score
0.0and rank deterministically; the domain codec rejects aBody::Blobinstead of misparsing its content hash. (#139, #149, #154) - cli —
get/ticket getexit non-zero (message on stderr) when a record is absent,indexadvances the persistent SQLite graph only after the manifest commits, and--gcis honored under--watch. (#152) - ticket connectors — Jira routes a
Canceledmove to a won't-do status rather than Done; a closed GitLab issue is terminal and non-terminal moves no longer report a false success; Linear fails a mutation that returnssuccess: false; the GitHub REST connector followsLinkpagination so all issues import, not just the first 100. (#138, #140, #141, #142)
Testing: de-flaked gonzalo-parse's hung-worker timeout test, whose 300 ms
budget false-timed-out the healthy recovery parse under heavy build load. (#178)
0.2.0 - 2026-07-06
The code-graph release. Gonzalo grows a full code-graph capability —
tree-sitter parsing across nine languages, content-addressed slices with
two-level keying, a persistent SQLite GraphStore, and structural queries over
both the daemon and an MCP server — alongside real semantic search (a local
Candle embedder feeding an approximate ANN index over per-kind-chunked
knowledge), namespace-scoped daemon auth, and correct content-aware 3-way merges
on both store-sync and git pull. Distributed as the gonzalod container image.
Added
- Code-graph capability (EPIC A–K): tree-sitter parsing into path-agnostic,
content-addressed slices with two-level keying (content+grammar hash for
storage, per-view manifest for resolution); a persistent
SqliteGraphStore; assembly-time name resolution; and structural queries — definitions, references, callers, callees, transitive impact, and cross-view diff — served over the daemon and a dedicated code-graph MCP server. Parsing is crash-isolated behind a worker-subprocessParserPool. (ADR 0012; #48, #50, #54, #56, #61, #64, #66, #70, #71, #74, #77, #88, #89, #90, #10, #30) - Language breadth for the code graph — a
Languagedispatch with grammars for Rust, Python, JavaScript/TypeScript/TSX, Go, Java, C#, C, and C++. (#79, #81, #83, #84, #85, #86) - CLI indexing (
gonzalo index): index a source tree into a code-graph view, git-diff-driven incremental re-sync, a--watchfile-watcher for live re-index, and opt-in mark-sweep GC of unreferenced slices. (#74, #93, #94, #100, #104) - Knowledge store (
gonzalo-knowledge): a "what do we know about X" surface composingStore+VectorIndex+Embedder, a vector⋈graph join (semantically similar and structurally near), and per-kind chunking so long records retrieve at turn/section granularity. (ADR 0011; #30, #29) - Real semantic vector search:
gonzalo-embed— a local CPU sentence embedder (Candle + all-MiniLM-L6-v2) — andHnswVectorIndex, an approximate ANN backend, behind the existingEmbedder/VectorIndextraits. (ADR 0013, ADR 0014; #97, #9) - Daemon substrate selection & health: env-driven
fs|s3store selection with a native S3BlobStore, and unauthenticated/healthz+/readyzprobes for k8s. (#62, #63) - Namespace-scoped daemon auth: a token→principal model with per-namespace read/write scoping enforced on both transports, plus unforgeable author stamping. (ADR 0015; #11)
gonzalodcontainer image — the release artifact, published on av*tag. (#51, #65)
Changed
- Store sync — true 3-way merge:
AncestryStoreretains each version's body by revision hash sosynccan merge divergent structured records against their real common ancestor instead of an empty base. (ADR 0016; #2) - Git pull — content-aware non-fast-forward merge: a diverged pull now
reconciles per-record through gonzalo's class-aware
merge()into a two-parent merge commit, surfacing unresolved records instead of erroring. (ADR 0017; #7) - S3 native conditional writes:
If-Match/If-None-Matchclose the optimistic-concurrency TOCTOU window in the S3 substrate. (#5)
Fixed
- CI: serialize GitHub Pages deploys with a concurrency group. (#107)
Internal
- ADRs 0010–0017 added (ticket capability layer, two-level code-graph keying, local embedder, ANN backend, namespace auth, stored-ancestry 3-way merge, content-aware non-FF pull).
- Docs: an mdBook guide publishing the ADR log, changelog, and a synthesized Guiding Principles & Invariants page; README status badges. (#103, #38)
0.1.0 - 2026-07-03
The initial development line — a generic, versioned, conflict-aware persistence layer for caliban, built milestone by milestone (M1–M6).
Added
- Record/Store core (M1):
gonzalo-core— one uniformRecordmodel and a genericStoretrait, with revisions, optimistic-concurrencyparenttracking,PutResult::Conflict, per-RecordKindmerge, and a feature-gated substrate conformance suite. No I/O in the core. (ADR 0002, ADR 0005, ADR 0006) - Filesystem substrate + domain + facade (M1):
gonzalo-store-fs(mirrors caliban's on-disk layout, the zero-dependency default),gonzalo-domain(typedMemoryTier/Topic/Session/Checkpointviews), and thegonzalofacade. (ADR 0004, ADR 0008, ADR 0009) - Git & S3 substrates + Sync (M2):
gonzalo-store-git(commit-per-write, fast-forward pull/push) andgonzalo-store-s3(S3-compatible object store), plus theSyncengine reusing the core conflict/merge machinery. (ADR 0004, ADR 0005) - Daemon + remote substrate (M3):
gonzalo-proto(one canonical schema),gonzalo-server(gonzalod) serving the store over both gRPC (tonic) and HTTP/JSON (axum) on one core service layer with optional bearer auth, andgonzalo-store-serveras the client substrate. (ADR 0007) - Vector layer (M4):
gonzalo-vector—Embedder+VectorIndextraits with a caller-delegating default embedder and an exact in-memory cosine index. (ADR 0008) - Code-graph layer (M5):
gonzalo-graph— a tree-sitter Rust symbol/ref index (build_rust) behind aGraphStoretrait. (ADR 0008) - Admin CLI (M6):
gonzalo-cli(gonzalo) —list,get,status,migrate,sync.
Internal
- Project: established
docs/adr/(MADR-lite) with the initial retrospective ADRs 0001–0009; added CI (fmt/clippy/build/test), a line-coverage gate, the Kanban label taxonomy, and board/triage automation.