Releases: anishmoncivarghese/sonde
Release list
v0.4.4 — explain empty graphs, point at sonde doc
A patch release of three clarity fixes, all found by using Sonde on a real project rather than by testing it.
sonde doc explained nothing when it had nothing to draw
Pointed at a flat Python package — 22 files in one directory — the document emitted an empty graph LR block and a dependency table with only a header. It read like a broken tool when the situation was merely uninformative.
Two separate causes, both fixed:
- The empty-case guard checked whether dependencies existed, but the diagram draws only dependencies with a resolved reference. On that project 14 pairs existed and all were heuristic-only, so the guard passed while the diagram had nothing to put in it. It now says how many pairs were dropped and why.
- A repository whose code lives in a single directory collapses to one module, so cross-module references cannot exist by construction. The references are real; the granularity has nothing to say about them. The document now states that and points at
sonde doc --module, which does have useful detail.
Note what is not fixed here: whether a flat package should be partitioned by file rather than directory is a design question, and this release does not pretend to have answered it. It makes the tool honest about the situation instead.
sonde doc was undiscoverable
After sonde init there was no indication the command existed. It is now named in init's output.
A pointer rather than a prompt, deliberately: offering to generate would write a second file into your repository, which init is careful about, and the document is not worth committing for every project shape — as the fix above demonstrates. The pointer appears only where setup completed, not on the error, conflict, or declined paths.
Full detail in CHANGELOG.md.
v0.4.3 — fail clearly on unsupported Node
A patch release worth taking promptly if you are on Node 20.
The bug
Running any real sonde command on Node 20 segfaulted with no output at all — exit 139, no message, no stack trace.
What made it bad is that everything a new user checks first said the install had worked:
npm install -g @cheppulabs/sondeprinted anEBADENGINEwarning among ordinary npm output and exited 0sonde --versionprinted0.4.2normally, because it never opens a databasesonde index .then died in silence
The bundled better-sqlite3 requires Node 22. On Node 20 it imports cleanly and only crashes when the database is used, so nothing surfaces the problem until the first command that matters — and then it crashes below the level where a message is possible.
The fix
sonde now refuses to start on Node below 22 and says why:
sonde requires Node 22 or newer, but this is Node 20.20.2.
The bundled better-sqlite3 segfaults on older versions, so sonde stops here
rather than crashing without explanation.
Upgrade Node (for example `nvm install 22`), then reinstall:
`npm install -g @cheppulabs/sonde`.
The guard is deliberately one-directional: a version string it cannot parse is accepted, not rejected. Blocking someone whose Node is fine would be worse than the problem it prevents.
How it was found
By installing the published package to actually use it — not by tests, review, or CI, all of which were green. Node 20 was LTS until recently, so this would have hit a large share of first-time users on their first command.
This is the third bug in a row found by running the published package rather than the repository, after the pyright resolution question in 0.3.0 and the missing-tsconfig.json case in 0.4.1. Verifying a release by clean install is now a documented rule in the project's AGENTS.md.
v0.4.2 — module detail fixes
A patch release fixing two defects in sonde doc --module, the on-demand symbol detail view. Neither affects the committed ARCHITECTURE.md.
Duplicate rows
sonde doc --module src/doc listed WriteOutcome.action four times as visually identical rows.
Those are four genuinely distinct symbols — WriteOutcome is a discriminated union, and each variant declares its own action property — so they share a qualified name, kind and file while having different stable keys. Repeating the row read as a bug; deduplicating silently would have hidden real structure. It now collapses into one row that states the count:
| `WriteOutcome.action` (4 declarations) | property | `src/doc/index.ts` | `src/cli` |
--include-tests only worked in one place
The flag filtered the committed document but never reached the module detail renderer, so --module always listed test modules under "Referenced by modules" regardless of what you passed.
Both views now share one definition of what a test module is, so they cannot drift apart, and hidden references are disclosed rather than quietly dropped:
References from test modules are hidden. Run with
--include-teststo show them.
The second one is the more interesting bug. It was not a logic error — the flag was simply never plumbed through a second call site, and both paths worked correctly in isolation. That class of defect is invisible from the code and only shows up when you read the output.
v0.4.1 — explain an empty dependency graph
A patch release fixing one confusing case in sonde doc.
What was wrong
Run sonde doc on a TypeScript project with no tsconfig.json and it listed every module and no dependencies at all — as if nothing in the codebase referenced anything else.
The result was technically honest: TypeScript module resolution needs a tsconfig.json, so nothing resolved, and Sonde does not invent edges it cannot evidence. But a document asserting a codebase has no internal structure reads like a broken tool, not like an honest one. That is exactly the silent-degradation case Sonde's own invariant 8 exists to prevent — degrade with a warning, never fail silently.
What changed
The document now says so in its header:
No
tsconfig.jsonwas found. TypeScript module resolution needs one, so cross-module references may be missing from this document.
The note appears only when TypeScript sources are actually indexed, so Python- and Swift-only repositories are unaffected. Nothing changes for a repository that already has a tsconfig.json.
How it was found
By installing 0.4.0 from npm and running it on a throwaway project — not by tests or review. This repository has always had a tsconfig.json, so the failure was invisible from inside it. The same check caught an analogous problem in 0.3.0, where the question was whether pyright could resolve from an installed package.
v0.4.0 — generated architecture documentation
Sonde can now generate the architecture documentation for the repository it indexes.
npm install -g @cheppulabs/sonde
cd your-project
sonde init # or `sonde init --resolve` for Python
sonde docThat writes an ARCHITECTURE.md describing your modules, how they depend on each other, and what each one exposes — derived from the graph, so it reports what the code does rather than what someone remembered. Sonde's own is committed in this repository.
What makes it different from other doc generators
It reports its own staleness. The header names the commit it describes and warns when files have changed since. Generated documentation usually fails one way — it is a snapshot, the code moves, and a stale document is worse than none because people trust it. Sonde already tracks drift, so the document can say when it has gone out of date.
It regenerates byte-identically. The stamp is a commit SHA, never a timestamp, so an unchanged repository produces an unchanged file. That is what stops a generated file from churning every diff until someone gitignores it. sonde doc --check fails a CI build when the committed document no longer matches the code.
It refuses to assert a dependency it cannot evidence. This is the part worth reading about.
The bug this release is really about
The first generated document put src/adapters/swift → src/adapters/typescript as the second-heaviest dependency in the entire codebase, backed by 62 references.
Those modules do not import each other at all.
They share the filenames symbols.ts, parser.ts and references.ts, so they share function names, and heuristic name-matching manufactured 62 references out of coincidence. The design document had argued that "fifty heuristic edges is strong evidence of coupling." It is not: modules with parallel structure produce the most name-match noise, not the least, so ranking by volume puts the most spurious relationships at the top.
Dependencies now rank by resolved references, and a module pair with no resolved reference between them is not drawn at all — it is counted and disclosed instead. The same fix cleaned up module surfaces, which had been listing add, value, child and node as public API.
Honest limitations
- The diagram is a summary, not the whole graph. It draws the heaviest dependencies and states how many it omitted. A diagram containing every dependency is unreadable and therefore shows nothing — this repository's complete graph is 615 arrows.
- Nothing exhaustive is committed. Dependency tables and module surfaces are capped with disclosure;
sonde doc --module <path>has the complete data. - Test modules are excluded by default.
--include-testsopts them back in. - It took three attempts. The first two generated documents were rejected at their own readability gate — 1,307 lines, then 363 with fabricated dependencies — before this one. Both rejections are recorded in the design document.
Also in this release
sonde doc --module <path> for symbol-level detail, --stdout, --check, and --include-tests.
Full detail in CHANGELOG.md.
v0.3.0 — Python support via a pyright-backed COMPILER tier
Sonde now indexes Python, alongside TypeScript and Swift.
npm install -g @cheppulabs/sonde
cd your-python-project
sonde init --resolveRead this before pointing it at Python
Two limitations, stated up front because they are cheap to disclose now and
expensive to discover later.
Python needs --resolve. Without it, Python indexes at the tree-sitter tier
only, which measured 62.81% unresolved reference sites on a real 56-file
project and 57.39% on pydantic — far past the 30% ceiling this project requires
before it will call a language supported. That tier is not fit for structural
queries, and Sonde shipped Python only once a compiler-backed tier cleared the
bar. sonde init --resolve is the supported path.
The gate measured placement, not correctness. It asked whether a reference
found a target, not whether the target was the right one. TypeScript's edges
are scored against tsc in ORACLE.md;
Python has no equivalent oracle, because the only available authority is the
same pyright that produced the edges — scoring them against it would measure
nothing. So Python edges are unverified for target correctness. If you find
a wrong one, that is a known gap rather than a surprise, and worth an issue.
The margin is thin on the worse corpus. 27.00% unresolved against a 30%
ceiling, and 29.72% once a known upward bias is fully reversed — roughly three
references from failing. Full arithmetic in
probes/python-placement/FINDINGS.md.
What the numbers actually are
Unresolved reference sites, measured against thresholds committed before the
measurement ran, on two real corpora:
| Corpus | Tree-sitter only | With --resolve |
|---|---|---|
| A 56-file application | 62.81% | 27.00% |
| pydantic (441 files) | 57.39% | 17.42% |
The first row is why the tree-sitter-only tier was built, measured, and then
refused registration. The gate exists to be able to say no, and it did.
How it works
A bundled pyright drives a COMPILER tier over LSP. No Python interpreter is
required — pyright is a TypeScript program and bundles typeshed — and Sonde
never loads a type checker from your repository, in any language.
Requests are issued serially, because throughput was measured flat across client
concurrency 1, 8, and 32: the server answers on one thread, so a scheduler would
have added complexity and returned nothing. That measurement is why this feature
is small (probes/pyright-feasibility/FINDINGS.md).
Also fixed
- Python stable keys are now guaranteed unique. Indexing pydantic failed
outright withUNIQUE constraint failed: symbol.stable_key. The 88 collisions
came from four distinct causes, each needing different treatment and none
permitted to use a line number: module-level rebinding is one variable,
@overloadfamilies are one runtime function, property accessors earn
role-suffixed keys (area,area@setter), and genuine redefinitions take an
ordinal that survives line moves. - The Swift SDK
EXTERNALfallback is scoped to Swift. It fired on any
reference carrying a scope hint, so Python names colliding with the Swift
table (append,Task,String,Int,filter) would have been attributed
to Swift frameworks — and sinceEXTERNALis excluded from the placement
denominator, that would have biased Python's own gate toward passing.
Install size
pyright is now a pinned hard dependency (~19 MB), matching how the TypeScript
compiler is already bundled. TypeScript-only users carry it without using it;
that trade was made deliberately so --resolve works with no extra setup step.
Full detail in CHANGELOG.md.
v0.2.2 — sonde init
Backfilled release note. This version shipped to npm on 2026-08-24; the GitHub
release is being recorded after the fact so the history has no gap.
Added
sonde init— collapses first-time setup (sonde indexplus hand-editing
.mcp.json) into one command.
npm install -g @cheppulabs/sonde
cd your-project
sonde initIt is merge-safe by design: it creates .mcp.json if absent, merges in the
sonde server entry if the file exists without one, no-ops if already
configured identically, and refuses to overwrite a conflicting or invalid
config rather than guessing. --yes skips the confirmation prompt; --resolve
and --json behave as they do for index.
Changed
- The npm publish workflow no longer auto-triggers on a pushed tag. npm's
shortest-lived tokens that can bypass 2FA for CI publishing expire in 7 days,
a rotation chore not worth owning at this release cadence — and the workflow
auto-firing with no token configured would have produced a failing run on
every future release. It is nowworkflow_dispatch-only.
v0.2.1 — compiler-tier fallback + parse_state
Backfilled release note. This version shipped to npm on 2026-08-24; the GitHub
release is being recorded after the fact so the history has no gap.
Fixed
- The TypeScript
--resolvepass silently skipped references with no named
enclosing symbol — the common case for code inside
describe(() => { it(() => { ... }) }), since anonymous test callbacks have
no named ancestor. The tree-sitter path already fell back to the file-level
symbol; the compiler pass now does too. On the Hono fixture this dropped
unresolvedroutereferences from 44 to 7, with the remaining 7 genuinely
outsidetsconfig.json'sincludelist. parse_statecould only beokorfailed, so a file that recovered
nearly all its declarations despite one bad expression was indistinguishable
from one that recovered nothing. Addedpartial: on the Hono corpus, 7 of 8
flagged files are genuinely partial and only 1 is truly failed.
Added
CHANGELOG.md, CI/npm/license badges, and a tag-triggered publish workflow
with a guard that fails the build if the pushed tag does not match
package.json's version, rather than silently publishing a mismatch.
v0.2.0 — TypeScript indexer + Swift adapter
Published to npm as @cheppulabs/sonde@0.2.0.
Install
npm install -g @cheppulabs/sonde
sonde index .
sonde mcp serve .What's in this release
- A symbol-level code graph in SQLite, built from TypeScript source via tree-sitter, with an optional
tsc-backed compiler-resolution pass (--resolve) - Three MCP tools:
find_symbols,query_graph,get_impact_radius - Measured, not asserted: edge accuracy checked against a
tscoracle, and structural recall/cost checked against a live agentic-search baseline on a real 19,409-line repository (Hono) — see BENCHMARK-LARGE.md - A Swift adapter, added after a pre-committed pass/fail gate measured PASS (25.16% unresolved / 74.84% placed) on a real 376-file Swift application — see probes/swift-narrowing/FINDINGS.md for the full record, including the corrected FAIL that preceded it
- Two upstream tree-sitter-swift parser bugs isolated and filed: alex-pinkus/tree-sitter-swift#610, #611
Honest scope
The claim is cost and determinism, not reach — a competent agentic search loop finds the same structural evidence. See README for the measured numbers.