Changelog
All notable changes to cymbal are documented here.
[Unreleased]
Added
--test-pathonimpact/changed— layer repo-specific test-path patterns over the built-in classification conventions (e.g.--test-path qa/ --test-path '**/*_it.go'). Patterns use the same semantics as the CLI--path/--excludefilters (plain substring, or glob with**), and affect the production/test caller split,--no-tests, and the exactmetricsreference counts. Library side:index.NewClassifier/Classifier.Classify,index.ImpactOptions+FindImpactWithOptions, andFoldReferenceCountsWith.impact --no-testsnow works in--graphmode — previously the graph path silently ignored--no-testsand rendered test callers anyway. Test-classified nodes are now contracted out of the graph with hide-but-traverse semantics: a production caller reachable only through a test helper stays connected to the seed via a synthesized dashed edge marked"indirect": true, instead of being orphaned or silently kept.- Graphs report edge-fetch truncation —
--graphoutput built from more rows than the internal per-direction fetch cap (1000) now setsedges_truncated: truein JSON instead of silently rendering an incomplete graph. The trace core's truncation detection now also covers the graph's unresolved-exempt mode. - New test-filename conventions recognised —
.test.cjs/.spec.cjs,.test.mts/.spec.mts,.test.cts/.spec.cts(module-flavoured JS/TS), PHPUnit*Test.php, Kotest*Spec.kt, and Djangotests.py.
Changed
- BREAKING:
investigate --jsonnow returns one stable object shape for any symbol count — previously a single symbol emitted a bare object and a batch emitted a bare array, so piped batches (--stdin) flipped shape based on how many names survived dedup. Both now emit{symbols, resolve_scope, results: [...]}(matchingtrace/impact), and every per-symbol entry carries its ownsymbolkey (previously only error entries did). tracereports the effective (clamped) depth —trace --depth 10used to echodepth: 10in frontmatter/JSON while actually traversing the maximum of 5; the reported depth now matches the traversal (index.ClampTraceDepth).
Fixed
changed: in-hunk lines starting with--/++are no longer misparsed as file headers — a deleted line whose content begins with--renders in a unified diff as--- …(every deleted Lua comment; any block-comment content), which the parser previously treated as a---file header: it clobbered the file's paths and silently dropped the rest of the hunk's changed symbols. Hunk-body lines are now matched before header prefixes, which git only emits before a file's first hunk.changed: filenames containing spaces are now analyzed — git appends a tab separator after unquoted header paths containing a space (--- a/my file.go\t); the parser kept the tab, so both blob reads failed and the file was miscounted as "no parseable symbols". Exactly one trailing tab is now stripped (safe: a filename with a real tab is always C-quoted).changed: user git config can no longer silently empty the results —diff.mnemonicPrefix=true(headers becomei//w/),diff.noprefix,diff.srcPrefix/dstPrefix,color.diff=always(ANSI codes even when piped), and textconv drivers each broke the diff contract the parser assumes, producing "No changed symbols found" with no error. The git invocation now pins all of them (-coverrides plus--no-color --no-textconv).changed: realgit difffailures now surface as errors — a failure without stderr (or a non-exit error) was previously swallowed, and the run proceeded on empty output as a false "no changed symbols".changed: pure renames and mode-only changes are no longer miscounted as "changed file(s) had no parseable symbols"; merge-conflict (unmerged) files, previously invisible, are now counted and warned about (conflicted_filesin JSON).changed --base <ref>: the ref is now separated from paths with--, so a file named like the ref (e.g.main) no longer makes git fail with "ambiguous argument";--jsonemits"results": []instead ofnullwhen no symbols changed.- Path classification now works on Windows — the index stores rel paths with native separators, so every anchored pattern (
/tests/,src/test/java, …) silently failed to match on Windows:--no-testshid nothing, the production/test splits reported test code as production, andsearchranking's test-file penalty never applied.ClassifyPathnow normalizes\to/(and the bare-convention guard no longer inverts onsrc\Test.java). - Impact/trace BFS no longer re-queries duplicate frontier entries — a caller reached via several files (or a callee reached from several callers) was enqueued once per encounter and re-queried at the next depth; results were already deduplicated, so this was pure wasted SQL on hot symbols.
[0.14.0] - 2026-06-20
Added
- Cross-language resolution scope for
trace/impact/investigate/changed— cymbal resolves references by name only (no receiver/type/import resolution), so a call could mis-resolve to a same-named symbol in an unrelated language. A new--resolve-scopeflag (same|family|all, defaultfamily) constrains which languages a name resolves to, defaulting to the interop family of the symbol it resolves from so legitimate cross-language calls still resolve. Families:jvm(java/kotlin/scala),js(javascript/typescript/tsx),c(c/cpp); every other language scopes to itself. In--graphmode, an out-of-scope name collision is surfaced as a newscope_filteredentry in theunresolveddiagnostics list (distinct fromexternal), so the reason no edge was drawn is explicit. The active scope is reported asresolve_scopein frontmatter and JSON. An unknown--resolve-scopevalue is a hard error. tracefilters unresolved callees by default — callees that don't resolve to an indexed symbol (stdlib, third-party, builtins) are dropped by default; pass--include-unresolvedto keep them in text/JSON (and as dashedext:nodes in--graph). Graph mode still records all unresolved callees in theunresolveddiagnostics list regardless.- Ambiguous graph nodes are annotated — because resolution is name-only, two distinct symbols with the same name (e.g.
Dupin different packages, or a GoAppand a TSXApp) collapse into a single--graphnode. Such a node now carriesdefinition_countand adefinitionslist (path,language,start_line) in JSON, and aName (N defs)cue in mermaid/dot, so a consumer can see the conflation and investigate each definition rather than trusting one merged node. trace/impactflag a starting symbol that exists in more than one language — if the name you ask about is defined in several languages, the output now lists those languages assymbol_languages(e.g.App=go,tsx). It tells you the results cover every language's version of that name, since--resolve-scopedecides how a call resolves, not which same-named symbol you meant.impactandtracesplit callers/callees and flag truncation —impact's header/JSON now reporttotal_callers: N (P production, T test[, U unknown])so you can triage production blast radius separately from test fan-out at a glance. Bothimpactandtracegain atruncatedfield (frontmatter line + JSON boolean), set when a per-symbol--limitwas hit, so partial result sets are never presented as complete (detected by over-fetching one row past the limit). Callers are classified by file path with anchored patterns (/tests/,_test.go,*Test.java,*.spec.ts, …) — never baretest/specsubstrings, so production code like C#*Specification.csor Railstests_controller.rbis not mis-flagged; genuinely ambiguous paths (/testing/,/fixtures/,/examples/) classify asunknown. Validated across 8 repos / 7 languages.impact --no-tests— excludes callers in test files (keepsproductionandunknown). Classification happens during traversal, so test callers never consume the--limitbudget ahead of production callers; test nodes are hidden from output but still traversed so production code reachable through a test isn't lost.cymbal changed— diff-scoped impact in one call — maps a git diff to the symbols it touches and reports each one's references and transitive impact, so reviewing "what does this change affect?" is a single command instead of parsing the diff and runningimpactper symbol. Defaults to unstaged working-tree changes (git diff);--stageddiffs the staged changes,--base <ref>diffs the working tree against another single ref (e.g. your branch point — ranges likea..bare rejected, since their new side isn't the working tree). Changed symbols are attributed by parsing the actual diffed blobs on both sides — added/modified lines map to symbols in the new version, deleted lines to symbols in the old version — so whole-symbol deletions are named (not mis-attributed to a neighbour),--stagedattribution matches the staged content even with unstaged edits present, and each changed line maps to its enclosing navigable definition (function/method/type, including Python/Rust methods) rather than a function-local. Per symbol it prints exact, un-truncatedreferencescounts (reference_rows/referencing_files, split production/test/unknown) and a cappedimpactcaller summary with its owntruncatedflag; deleted symbols are listed separately, and ambiguous (multi-definition) names carrydefinition_count+ adefinitionslist. Bounded by--max-symbols(default 40) and a--max-impactsoft cap on total caller rows (default 500), with--limit/--depthper symbol and--no-testspassthrough. Impact and references are name-scoped (cymbal resolves by name), so counts for a name with several definitions may span them (reported asdefinition_count);--resolve-scope(same|family|all, defaultfamily) constrains cross-language resolution as ontrace/impact, and the active scope is echoed asresolve_scopein frontmatter and JSON. Operates only on the current worktree; arbitrary commit ranges whose new side isn't the working tree are unsupported.index.ReferenceCountsWithScope— library API returning complete (un-truncated) name-scoped reference counts split by production/test/unknown file class, forchangedand other callers.investigateaccepts--stdinfor piped batch lookups —cymbal investigatealready took multiple symbol names as positional args; it now also reads newline-separated names from stdin (--stdin), matchingsearch/show/refs/impact/trace/impls. This makes the standard pipe work —cymbal outline svc.go -s --names | cymbal investigate --stdin— and closes a gap where the SessionStart hook already advertised piped batching forinvestigate. Names from args and stdin are merged and deduped (first-seen order), with#-prefixed and blank lines skipped.impactnow reports exact reference metrics and completeness context — alongside the (limit-capped) caller analysis,impactadds ametricsblock: the exact, un-truncated count of references to the symbol (reference_rows/referencing_files, split production/test/unknown), so you get true reference breadth even when callers are truncated. JSON also gainsdepth,limit,definition_count, and — for ambiguous names — adefinitionslist of locations; lookup failures surface asreferences_error/definition_count_errorrather than a misleading zero. When a name has more definitions than were requested,definition_countis shown in the header too. No categorical risk label is emitted — raw, inspectable metrics are surfaced instead. References are name-scoped (exact for uniquely-named symbols, a conflated over-count for colliding names) and count reference sites, a distinct and larger metric than the deduplicated caller count.
Changed
- BREAKING: single-symbol
trace --json/impact --jsonnow return the same object shape as multi-symbol — previously a single symbol emitted a bare JSON array while multiple symbols emitted{symbols, …, results}. Both now emit the object form ({symbols, …, resolve_scope, results: [{row, hit_symbols}]}), so agent consumers parse one consistent shape regardless of symbol count. Scripts that parsed the top-level array must read.results[].rowinstead.
Fixed
--graphno longer mislabels class-nested methods as external — the graph builder's symbol metadata was restricted to top-level (depth=0) symbols, whiletrace/impactresolve against symbols at any depth. As a result, in class-based languages (Java, Python, TypeScript, …) a real method calling another method showed up intrace --graph/impact --graphas anexternal(stdlib/third-party) node with no resolved edge — even thoughtracetext andrefscorrectly identified it. Graph metadata now covers all depths, so nested methods resolve as real nodes/edges. Go was largely unaffected (its functions/methods are top-level).--graphkeeps a name that's in scope under one definition but excluded under another — when a name had several definitions,--graph-scope/--excludewere judged against a single arbitrary definition, so a node could be dropped even when an in-scope definition existed. Visibility now considers every definition of the name and displays an in-scope one.- OpenCode hook installs now target the plugin directory OpenCode actually loads — user-scope
cymbal hook install opencodenow writes to~/.config/opencode/plugins/cymbal-opencode.jsinstead of the Windows-native%APPDATA%\opencode\pluginspath that OpenCode 1.15.9 ignores. The installer also honorsOPENCODE_CONFIG_DIRby writing to$OPENCODE_CONFIG_DIR/plugins/cymbal-opencode.js, matching OpenCode's custom config directory behavior. - OpenCode's managed plugin now loads on OpenCode 1.15.13 — the generated plugin exposes a single
CymbalPluginexport instead of extra module exports that OpenCode tried to load as plugins, and it now nudges OpenCode's Bash, Grep, and Glob tools toward cymbal-first code navigation. Fixes #63.
[0.13.5] - 2026-05-19
Fixed
cymbal hook nudgenow fires on Claude Code's Grep, Glob, and Read tools (#47) — the matcher previously only knew the Bash schema (tool_input.command), so Claude's dedicated code-search tools hit the hook and exited silently. The dispatcher now reads each tool's structured input (pattern+ optionalglob/typefor Grep,patternfor Glob,file_pathfor Read) and emits acymbal search/cymbal ls/cymbal showsuggestion when the target is a code file. A non-code extension deny list (md, json, yaml, toml, log, csv, env, …) keeps the nudge quiet when the agent is reading data. All existing v0.13.3 / v0.13.4 gates (literal-text shape, regex signals, explicit file paths) still apply across every tool. Recommendedmatcherbecomes"Bash|Grep|Glob|Read".- Update notifications now refresh after multi-day staleness (#58) —
GetStatushonored the 6h failure backoff even for caches that were days stale, so a single failed live fetch followed by repeated runs could pin the user on whichever version was cached at the last successful check — indefinitely. The failure backoff is now capped atcheckTTL + failedRetryTT(~30h), past which a live retry is always attempted. Worst-case staleness is now bounded. - Worktree integration tests now portable on Windows — the
RepoCommonDir/EnumerateWorktreestest fixtures created their seed file viaexec.Command("sh", "-c", ...), which silently failed on Windows runners and blocked the release pipeline for v0.13.3 and v0.13.4. Switched toos.WriteFile.
[0.13.4] - 2026-05-19
Added
- Worktree federation for symbol lookup (#44) — when cwd is inside a git worktree,
cymbal search/show/investigate/impact/trace/impls/refsautomatically include indexed sibling worktrees of the same logical repo, so symbols added in one worktree are visible from any other worktree's cwd. Results from non-current worktrees carry aworktreelabel ([worktree:foo (feat/foo)]in text output,"worktree"JSON field). Federation respects per-worktree DB boundaries — graph traversal (impact/trace/impls/refs) stays within whichever worktree owns the seed symbol, so no cross-branch graph leakage. Pass--no-federateto opt out, or set--db/$CYMBAL_DBto pin a single DB. Unindexed sibling worktrees are skipped with a one-line stderr note (runcymbal index .inside each to include them). Federation fan-out is capped at 32 worktrees.
[0.13.3] - 2026-05-18
Fixed
- TS/TSX export signature precedence bug —
extractSignaturehad an operator-precedence bug atparser/parser.go:2511where&&bound tighter than||, so TSX and JavaScript callers silently bypassed the empty-signature guard. A function with no parameter list and a type_annotation could end up with a signature that was just the leading:return type. The deade.lang == "jsx"arms at:2475and:2511are also removed (.jsxregisters as"javascript"). AddsTestFeatureTSXExportFunctionsRetainSignaturewith a starts-with-colon bug-catcher. cymbal hook nudgefalse positives on grep/rg calls that were already correct — the PreToolUse nudge fired on string-value lookups (grep '"jsx"'), line-number searches in named files (grep Foo parser.go), and regex queries with|or^…$anchors, pushing agents to switch tocymbal searchwhen they shouldn't. Three new gates close the false positives: literal-text shape (embedded quotes, whitespace, non-identifier punctuation), explicit file targets (name.extshape in args), and single-char regex signals. The nudge template is also rewritten as advisory rather than declarative, giving agents an explicit "ignore this if your original tool was right" branch instead of an implicit correction.
[0.13.2] - 2026-05-08
Added
- OpenCode plugins now surface update notices through native OS notifications — when a newer cymbal version is available, the OpenCode plugin shows a platform-native notification (macOS Notification Center via
osascript, Linux vianotify-send, Windows via PowerShell) so users see updates regardless of TUI or Desktop mode. RespectsCYMBAL_NO_UPDATE_NOTIFIERand cymbal's per-version notification throttle. (#23) - New
cymbal hook notifycommand — emits a structured JSON payload with update availability, version, and install command for agent plugins that want to surface update notices outside hidden system context. Supports--format=json|textand--update=cache|if-stale.
Changed
- Bumped Go toolchain floor to 1.26.3 — resolves 10 stdlib vulnerabilities reported by
govulncheck, including callable traces innetandnet/httpreached from the update notifier. CI usesgo-version-file: go.mod; local builds with Go 1.21+ auto-fetch the new toolchain. (#54)
Fixed
cymbal investigateno longer leaks references across languages — when a name resolves to a single symbol, refs and transitive impact are filtered to the resolved symbol's language, so investigating a GoAppstruct in a polyglot repo no longer returns call sites from a TSX function with the same name.cymbal refskeeps its documented best-effort name-only behavior..tsxfiles are now parsed with the TSX grammar — anonymous arrow functions inside JSX props (onClick={async () => ...}) were being indexed as phantommethod asyncsymbols because the plain TypeScript grammar can't see JSX boundaries..tsxis now its own language entry usingtree-sitter-typescript's TSX grammar;.ts/.mts/.ctscontinue to use the TypeScript grammar.- Swift field and property accesses now record references —
cymbal refs <fieldName>previously returned zero hits becauseextractRefSwiftonly handled call expressions and named type uses. Member access throughnavigation_expression(e.g.self.field,field.method(),self.a.b = x) now produces refs.cymbal refsis still best-effort and name-only. - TypeScript
export functiondeclarations now retain their signature — exported functions andexport const fn = (x) => …arrow forms were emitting an emptysignaturefield because signature extraction looked for parameters on the outer wrapper node. The extractor now descends throughexport_statementandlexical_declarationwrappers before reading parameters and return type. - Swift symbol ranges anchor at the keyword, not at preceding attributes — protocols / classes / functions decorated with
@MainActor,@objc,public, etc. previously had their start line absorbed into the leading attributes block, socymbal showandcymbal outlinereported a range one line above the actualprotocol/class/funckeyword.
[0.13.1] - 2026-05-06
Changed
- Migrated from smacker to the official tree-sitter Go bindings — cymbal now uses
github.com/tree-sitter/go-tree-sitterand official grammar modules, with parser/tree lifetimes closed explicitly and symbol extraction updated for the official node and position APIs. (#50) - Vendored grammar packaging is now safe for Go library consumers — Dart, Elixir, and Swift grammars live under
internal/tsgrammarswith upstream metadata, avoiding non-propagatingreplacedirectives for downstreamgo getusers. - Path filtering is shared across commands and indexing — include/exclude matching now uses
internal/pathmatchwith normalized slash paths and recursive**glob support.
Fixed
- Generated tree-sitter parser tables are skipped during indexing — the walker ignores vendored
internal/tsgrammars/**/src/parser*.cfiles so initial indexing does not spend time parsing generated grammar sources. - Release tests now pass on Windows for the official tree-sitter release line — CLI JSON path assertions use platform-native separators, and index facade tests close cached SQLite stores before temporary database cleanup.
[0.12.5] - 2026-05-06
Fixed
- Release tests now pass on macOS arm64 runners — repository and file paths are canonicalized at the index, outline, and diff boundaries, so macOS
/var/...and/private/var/...temp-directory spellings use the same per-repo database and git-relative diff paths. - Release tests now pass on Windows runners — CLI path assertions now compare canonical repository roots, and facade tests close cached SQLite stores before temporary database cleanup so Windows can remove test DB files cleanly.
- OpenCode project-scope tests no longer depend on the developer's user config — tests now isolate config roots, and the managed plugin symlink guard still rejects symlinks inside the OpenCode plugin path without rejecting normal platform-level aliases like macOS
/var -> /private/var. - Makefile builds now embed the requested version —
make build VERSION=vX.Y.Znow passes$(VERSION)through ldflags instead of the stale hardcodedv0.12.1value.
[0.12.2] - 2026-05-02
Added
- Regression coverage now reaches the 80% product target — coverage excludes the internal
bench/evaluation harness from the product denominator and now covers parser/walker behavior, command-facing workflows, public index facade APIs, diff output, and update-notifier state transitions.CGO_CFLAGS="-DSQLITE_ENABLE_FTS5" make test-coveragereports 80%+ total product coverage. - Codecov now uses the same product coverage denominator as CI — CI converts Go's block coverprofile into LCOV line hits before upload,
codecov.ymlignores the internal bench harness plus non-executable test, entrypoint, type, and registry files, and command entrypoint regression tests keep Codecov's line-oriented calculation above the threshold. - OpenCode now has a first-class installer path —
cymbal hook install opencodeinstalls a cymbal-managed OpenCode plugin in the documented plugin directory for user or project scope, making OpenCode the main supported cymbal integration path instead of relying onAGENTS.mdbootstrap text. The managed plugin refreshes guidance throughcymbal hook remind --update=if-stale, so update guidance stays fresh automatically while cymbal still never self-updates by default.
Fixed
- Elixir and Scala parser coverage exposed real extraction gaps — Elixir function/macro symbols now use the callable name instead of including arguments, and Scala now has dedicated symbol/ref extraction for classes, traits, objects, methods, fields, and call expressions.
- Agent reminder text leads with the Grep/Glob/Read tools Claude Code actually routes through — the
cymbal hook remindtrailing line previously compared cymbal torg/grep/find/fd, but Claude Code's system prompt directs the model away from shelling out to those tools. The reminder now leads with Grep/Glob/Read and keeps shell tools in parens for shell-style agents (Cursor, Aider, Cline, etc.). Fixes #46. cymbalno longer dumps full Usage text on every RunE error — Cobra's default behavior printed the entire help block whenever a command returned an error (e.g.cymbal searchwith no matches), drowning the real error and confusing AI agents that re-read it as a hint to retry. The root command now setsSilenceUsage, so the error message itself still prints but the Usage/Flags block stays out of the way.--helpand unknown-flag paths continue to show Usage as expected.
[0.12.1] - 2026-04-23
Added
cymbal searchaccepts batched symbols and rg-style path operands —cymbal search Foo Barnow searches each symbol independently instead of joining the words into one query, and--stdinworks for newline-separated symbol batches.cymbal search <query> [path ...]also treats trailing files, directories, and globs as--pathfilters, so scoped text searches likecymbal search --text 'os\.WriteFile\(' tools/file.go tools/patch.gowork without translating the command shape.--fileis accepted as an alias for--path, and agent hook guidance now distinguishes symbol navigation from literal/regex text search.
Fixed
- Agent reminders can refresh stale update status —
cymbal hook remind --update=if-stalekeeps the default cache-only behavior unless explicitly requested, then performs a bounded live update check only when the cache is stale or missing. Claude Code installs now use the stale-aware reminder command, and Opencode docs now describe the bootstrap as best effort with Windows path guidance. Fixes #40. - SQLite WAL files are checkpointed on close —
Store.Closenow runsPRAGMA wal_checkpoint(TRUNCATE)before releasing the database handle, so committed index rows are folded back into the mainindex.dbartifact at process exit. This makes DBs created on Windows less likely to appear empty when later read from WSL through/mnt/c/.... Fixes #16. - Python private functions are indexed again — underscore-prefixed Python functions such as
_parse_tokenand__helperare no longer silently dropped from the symbol index. This restoressearch,outline,show,refs,trace,impact, andinvestigatecoverage for Python codebases that keep most implementation logic behind module-private helper functions. Fixes #41.
[0.12.0] - 2026-04-22
Added
Visual graph output for relationship commands.
trace,impact,importers, andimplscan now render Mermaid (TTY default), DOT, or JSON graphs directly from the existing verbs.bashcymbal trace <symbol> --graph cymbal impact <symbol> --graph cymbal importers <file> --graph cymbal impls <symbol> --graph- Shared Graph Flags:
--graph— triggers graph mode instead of standard text output.--graph-format mermaid|dot|json— overrides the format.--graph-limit N— caps output at the top-N most-connected nodes (preserving roots and adding a_truncatedsentinel).--include-unresolved— visualizes external/unindexed relationships (like stdlib imports or conformance to unindexed protocols) as dashed nodes, prefixed withext:.
- Shared Graph Flags:
Tightened rendering UX:
- Mermaid output automatically caps at 500 nodes to prevent UI rendering lockups, truncating by degree severity and alerting via stderr. You can use
--graph-limitto dial this in manually. impact --graphdefaults to a depth of1(rather thanimpact's normal text default of2), significantly improving visual readability on hot functions. You can still explicitly pass--depth 2to override.
- Mermaid output automatically caps at 500 nodes to prevent UI rendering lockups, truncating by degree severity and alerting via stderr. You can use
Fixed
- Repo boundaries are now enforced for direct file reads and indexing.
cymbal show <file>now resolves symlinks, refuses paths outside the indexed repository, and rejects.gitreads instead of opening arbitrary local files. The walker also skips symlinked files entirely, so a repo can no longer smuggle external source into the index viafoo.go -> /private/path. - JIT freshness now catches normal in-place edits again. The old directory-mtime shortcut could miss ordinary file saves and return stale
search/show/refs/traceresults until some later directory-level change happened.EnsureFreshnow falls back to the existing file-level incremental path, so ordinary edits, touches, and deletions refresh correctly. - Text search now respects global limits without racing. The pure-Go fallback had a shared-slice race and only weakly enforced
--limit; the ripgrep path buffered all stdout in memory.TextSearchnow cancels promptly once the global limit is reached, and thergfast path streams matches instead of slurping the whole result set first. - Frontmatter, cache files, and settings writes are safer for agent workflows. Frontmatter metadata now quotes unsafe scalar values instead of interpolating raw newlines/control characters, update-check state no longer persists arbitrary cached update commands, index/update-cache files are created with private permissions, and JSON settings/cache writes now use temp-file + rename semantics.
- Long-line and cache-path edge cases are handled correctly. File-reading paths now raise the scanner buffer so minified/generated single-line files do not fail with
token too long, andcymbal ls --reposnow scans the actual cache root (includingCYMBAL_CACHE_DIR) rather than the stale legacy~/.cymbal/repos/...location.
[0.11.8] - 2026-04-22
Added
- Cached update notifications with install-specific guidance — cymbal now checks the latest GitHub release on a throttled cache, then shows a small passive notice on interactive non-JSON commands when a newer version is available. The suggested upgrade command is tailored to how cymbal was installed (
brew upgrade 1broseidon/tap/cymbal, rerun the PowerShell installer,docker pull,go install, or the releases page), with environment overrides for custom setups viaCYMBAL_INSTALL_METHODandCYMBAL_UPDATE_COMMAND. Passive notices can be disabled entirely withCYMBAL_NO_UPDATE_NOTIFIER=1. - Update status in
cymbal versionand agent reminders —cymbal versionnow includes cached release status in human and JSON output, andcymbal hook remindappends the exact update command when the cache already knows a newer release exists. Reminder output stays cache-only, so agent session startup does not trigger extra network checks; agents that can run shell commands are instructed to run the suggested update command immediately, otherwise to tell the user what to run.
Fixed
- GitHub releases now publish changelog notes automatically. The tag workflow now checks out the tagged source, extracts the matching
## [X.Y.Z]section fromCHANGELOG.md, and uses it as the GitHub Release body. Releases now fail fast if the changelog entry for the tag is missing, which makes release notes part of the tag workflow instead of a manual post-step. - Update-notifier cache now has a cross-platform override.
updatecheck.cymbalDir()honors a newCYMBAL_CACHE_DIRenvironment variable before falling back toos.UserCacheDir()/ the home directory. This unblocks reminder tests on macOS (whereos.UserCacheDir()returns~/Library/Cachesregardless ofXDG_CACHE_HOME) and gives users a way to relocate the update-check cache on custom installs.
[0.11.6] - 2026-04-20
Fixed
- Claude Code
PreToolUsenudge hook no longer fails JSON validation. Theclaude-codeoutput shape emitted the deprecated top-leveldecision+systemMessagefields, which Claude Code's current schema rejects withHook JSON output validation failed — (root): Invalid input. Nudge now emitshookSpecificOutput.{hookEventName,permissionDecision,permissionDecisionReason,additionalContext}per the current hooks schema, and the suggestion is injected asadditionalContext(visible to the model) rather thansystemMessage(user-facing warning only). Theremindhook'sclaude-codeformat got the same treatment forSessionStart. Genericjsonformat ({"systemMessage":"..."}) is unchanged for non–Claude Code agents.
[0.11.5] - 2026-04-20
Fixed
- Lua function/method
start_linenow points at thefunctionkeyword, not the preceding whitespace. tree-sitter-lua (smacker fork) folds leading whitespace from the previous statement into the nextfunction_statementnode, sonode.StartPoint().Rowwas landing 1–3 lines early. The parser now anchors Lua function/method start lines to the name node, which matches what usersgrepfor. Caught by running./bench checkon the newly-added lazy.nvim corpus entry —cymbal show headlesspointed at the correct line, butsearch's reported line diverged from the file.
Added
- Four new bench corpus entries for the languages that gained dedicated coverage in 0.11.4 —
dapper(C#),monolog(PHP),lazy.nvim(Lua), andnvm(Bash). Pinned to tagged refs for reproducibility. Each repo supplies 1–3 canonical symbols plus a footgun scenario (common method name, stdlib collision, or cross-file name reuse) to exercise ranking and grep-style noise avoidance. Full suite still reports zero accuracy failures and no regressions vs baseline.
[0.11.4] - 2026-04-20
Fixed
- C#
usingextraction now uses AST fields instead of text trimming (follow-up to #35; Codex review P1). The previous implementation stripped"using"/"static"prefixes from the directive's raw text, which produced malformed paths onglobal using System.Text;(→"global System.Text") andusing Alias = System.IO.Path;(→"Alias = System.IO.Path"). The extractor now walksusing_directive/global_using_directivechildren, detects the alias form via a trailing=, and returns the realqualified_name/identifiertarget. Tests added for both shapes, plus a negative assertion that the old malformed outputs never appear. - PHP
useextraction now handles grouped and comma-separated imports. Previously only the first clause ofuse Foo\Bar, Baz\Qux;was emitted, and group formuse My\{A, B as C, D};/use function Foo\helper;/use const Foo\MAX;produced nothing at all. The extractor now appends oneImportper resolved path (prefixing group leaves with their common namespace) and relies onphpUseClausePathto stripas Aliassuffixes. Tests cover all three shapes. - PHP
new \Fully\Qualified\Name()refs now resolve to the leaf name.extractCallNamestrips.separators but PHP uses\for namespaces, so the previous ref was emitted as"\\Fully\\Qualified\\Name". It now collapses to"Name"so downstream commands (refs,impact) match the same symbol.
Added
Dedicated parser coverage for C#, PHP, Lua, and Bash (part of #22) — these four languages were parseable by tree-sitter but still falling through to generic symbol extraction, which left them with missing kinds and empty import/reference graphs. Each now has proper
classify/extractImport/extractRefpaths:- C# —
namespace,class,struct,interface,enum,record,delegate,method,constructor,destructor,property,field;using/using staticimports;invocation_expressionandobject_creation_expressionrefs. - PHP —
namespace,class,interface,trait,enum,function,method,const_element,enum_case;namespace_use_declarationimports;function_call_expression,member_call_expression,scoped_call_expression,nullsafe_member_call_expression, andobject_creation_expressionrefs. - Lua —
function/methodsymbols fromfunction_statement(includingM.fooandM:newforms);require("x")/require "x"/require 'x'imports; call refs for flatutil.debug(...)/M:new(...)shapes. - Bash —
functionsymbols fromfunction_definition;source x.sh/. x.shimports; command-invocation refs with a small ignore list for control-flow builtins (if/for/set/local/etc.) so real call signal isn't drowned by keywords.
This directly improves
cymbal refs,cymbal depends(PR #18), andcymbal dead(PR #17) accuracy on these languages. YAML is intentionally left in the generic path — its semantic model (no functions, no imports, no calls) doesn't fit cymbal's indexed shape. Swift, which was listed in #22, already had dedicated coverage before this change.- C# —
cymbal refs --stdin—refsnow accepts newline-separated symbol names on stdin, matchingshow/impact/trace/impls. This finishes the documented pipe idiom (cymbal outline big.go -s --names | cymbal refs --stdin) that was previously rejected asunknown flag: --stdin.
[0.11.3] - 2026-04-20
Added
cymbal versionand--version(fixes #26) — print the binary version, commit, build date, and Go toolchain. Release builds embed the tag, commit, and timestamp via-ldflags;go install-style builds fall back todebug.ReadBuildInfo()so module version and VCS stamp still surface. JSON mode is available via--json.cymbal outline --names(fixes #28) — emit one deduplicated symbol name per line. This flag was already documented in theimpact/trace/showhelp text and earlier CHANGELOG entries for pipe-driven multi-symbol workflows (cymbal outline big.go -s --names | cymbal show --stdin), but the flag itself had never been wired up.- Example Agent Skill for cymbal (fixes #23) — added
examples/skills/cymbal/{SKILL.md,README.md}as a ready-to-install skill for Claude Code and other runtimes that read the same format. The skill encodes the “use cymbal first” rule, a command decision tree, path-filter / stdin / JSON usage, and field-manual guidance (pivot rule, stop rules, anti-patterns, real constraints) so the behavior survives long-context drift better than a one-off prompt.
Fixed
cymbal trace --helpexample shorthand — corrected the help text to stop implying a nonexistent--depthshorthand.-dis the global DB flag; depth remains long-form only.
Changed
- PR body validation is now proportional to PR size — trivial PRs (small diffs or docs/trivial/dependencies labels) skip the checklist gate, larger PRs require only a real
## Summary, and unchecked checklist boxes are surfaced as warnings instead of hard failures. This removes boilerplate friction for one-line fixes while still preserving signal on larger changes.
[0.11.2] - 2026-04-18
Changed
cymbal hook install claude-codewires the reminder toSessionStartinstead ofUserPromptSubmit— the reminder now injects once per session (its intended purpose, percymbal hook remind --help) rather than on every user prompt. Users saw the ~730-byte primer re-injected every turn, paying thousands of tokens over a long session for content that only changes meaning at session boundaries.SessionStartprimes the agent once and stays out of the way after.- Re-running the installer migrates pre-0.11.2 installs automatically —
mergeClaudeHooksnow strips any prior cymbal-marked entries (including oldUserPromptSubmitones) before adding the newSessionStartentry. Unrelated hooks in either location are preserved via the marker-based filter.
Fixed
cymbal implson Rust —impl Trait for Type { }edges now resolve the implementer name instead of returning(anonymous), andcymbal impls --of Typenow walks through separateimplblocks to find conformances. Previously the SQL that resolves the owning symbol of an implements-ref excluded Rust'simplkind, socymbal impls Errorin mini-redis returned(anonymous) (anonymous)andcymbal impls --of Framereturned no edges at all.cymbal impls --of <outer>no longer over-reports conformances from nested types — in Swift (and any language where types nest), a big outer class like Alamofire'sSessionwas attributing conformances of nested private types (e.g.struct RequestConvertible: URLRequestConvertible) to the outer.--of Sessionnow correctly returns only the outer's direct conformances (justSendable), matchingFindImplementors' existing smallest-enclosing-symbol logic.- Rust
impl Foo<T>blocks now index under the bare nameFoo— the parser previously stored the generic form verbatim (impl Foo<T, U>), socymbal impls --of Foomissed blocks with generic parameters. Stripping generics at classification time means ripgrep'simpl Sink for JSONSink<'p, 's, M, W>now correctly resolves underJSONSink.
Changed
- Stricter bench ground truth — added trait-impls cases for ripgrep (
Sinkwith 9 implementors across 3 crates including blanket impls on&mut SandBox<S>;Matcherwith 3 same-namedRegexMatchertypes across pcre2/regex/testutils;JSONSinkinverse--ofdirection). Pushed total ground-truth checks from 59 to 61; cymbal passes all 61. Also tightened existingJSONSinkandSearchWorkersearch expectations to include their bare-namedimplblocks. - Bench internals refactored —
executeBench,generateReport, andtunedGrepScoresplit into per-phase / per-section / per-concern helpers. All three dropped from 32–39 cyclomatic complexity to single-digit (under the repo's pre-commit threshold of 30).
[0.11.0] - 2026-04-18
Added
cymbal impls <symbol>— find types that implement / conform to / extend a protocol, interface, trait, or base class. Language-agnostic: Swift protocol conformance, Go interface embedding, Java/C#/Kotlin/TypeScript implements clauses, Scala extends/with, Rustimpl Trait for Type, Dart interfaces/mixins, Python base classes, Rubyinclude/extend/<, PHP implements, and C++ base classes all register as implements-kind refs. External framework targets (e.g.LiveActivityIntentfrom ActivityKit) are stored by name and returned withresolved=false. Supports--of <type>for the inverse direction ("what does this type implement?"),--resolved/--unresolvedfilters, plus the standard--lang,--path,--exclude,--jsonflags.- Implements / Implementors sections in
investigateandcontext— when a symbol is a type-like kind (class, struct, interface, protocol, trait, enum, record, object, mixin, actor, extension), the output now includes who implements it and what it implements. External vs. local targets are marked inline. - Typed refs — refs now carry a
Kindfield (call,implements,use). This is the foundation for the implements graph and for trace noise reduction. - Multi-symbol invocation for
show,impls,impact,trace— every symbol-taking command now accepts N names in a single turn. Human output groups results under═══ <name> ═══headers; JSON mode returns a map keyed by the requested name. Forimpactandtrace, identical call sites are deduplicated across inputs and each surviving row carries ahit_symbolsattribution list (rendered inline as[sym1,sym2], returned as structured data in JSON). Per-symbol "not found" is a warning, not a hard failure — agents get partial results back. --stdinflag onshow,impls,impact,trace— read newline-separated symbol names from stdin, socymbal outline big.go -s --names | cymbal show --stdinworks cleanly. Comment lines (#) and blanks are skipped; positional args and stdin input are merged and deduplicated in first-seen order.cymbal hook— agent integration hooks (fixes #23). Two small, agent-agnostic subcommands that keep coding agents using cymbal instead of sliding back to rawgrep/findas context grows:cymbal hook nudgereads a would-be shell command (from argv or a Claude Code-style JSON payload on stdin) and, if it looks like a code search on source-like input, emits a short suggestion for the cymbal equivalent. Never blocks. Output formats:claude-code(default JSON forPreToolUse),text(stderr),json(generic). Detection is deliberately narrow:rg/grep/egrep/fgrep/ack/ag/find -name/fd, skipping short queries, heavy regexes, and non-source globs to avoid false-positive nagging.cymbal hook remindprints a tone-calibrated reminder block an agent can inject at session start or on demand. Formats:text,json,claude-code.cymbal hook install claude-code/uninstall claude-code— one-liner installer for Claude Code. MergesPreToolUse(matcher=Bash) +UserPromptSubmitentries into~/.claude/settings.jsonwith--scope user|projectand--dry-run. Idempotent, marker-tagged, preserves unrelated user config.- For other agents (Cursor, Windsurf, aider, Cline, Continue, Zed, Codex/OpenAI Agents SDK, generic shell), see
docs/AGENT_HOOKS.md— the two subcommands are the whole surface and each integration is one or two lines of rules-file or config-file glue.
Changed
cymbal tracedefaults to call-only edges — previouslytracesurfaced every identifier seen inside a symbol's line range, which made Swift output noisy with type annotations (UUID,Date,Sendable,@escaping, etc.) that weren't actually callees. Trace now filters tokind='call'by default. Use--kinds call,use(or--kinds call,use,implements) to opt back into the wider behavior.symbols.Ref.Kind— new field on the publicsymbols.Reftype. Empty is treated asuseby the store, so older callers keep working without changes.--limitonimpls,impact,trace— now documented as per-symbol, not a total cap across a multi-symbol call. Single-symbol behavior is unchanged.
Migration
refs.kindcolumn added viaALTER TABLE— existing databases are migrated automatically on first open; no action needed. Re-index (cymbal index) once to populatekindvalues for existing rows. Until you do, new commands (impls, investigate/context implements sections) will be empty whiletracewill correctly filter to the new default.index.FindTracesignature is now variadic —FindTrace(db, name, depth, limit, kinds...). Existing calls withoutkindskeep working and get the new call-only default.- Single-symbol output is unchanged — all multi-symbol rendering (banners,
symbols:frontmatter,hit_symbolsattribution, JSON map shape) only activates when more than one name is passed or--stdinis set.
[0.10.1] - 2026-04-17
Fixed
- Swift references, impact, and trace now work — Swift files were parsed for symbols but had no reference-extraction dispatch, so
refs,impact, andtracealways returned empty on Swift code. The newextractRefSwiftemits refs for call expressions (includingx.y.z()navigation-expression callees) and named type usages (FeedingStore,BabyTrackingService,Formatter, etc.) across annotations, inheritance clauses, generics, parameters, and return types. - Swift declaration classification is now accurate — tree-sitter-swift collapses
struct,class,enum,extension, andactorinto shared declaration node families. cymbal now disambiguates these by inspecting the leading keyword, so outlines and search results correctly label Swift declarations instead of misclassifying them as genericclassnodes. - Swift
actordeclarations are recognized explicitly — actor types now surface asactorsymbols instead of falling back toclass, and members nested inside actor bodies keep the correct parent symbol. search -i/--ignore-casenow implies--exact— case-insensitive symbol search now matches the CLI UX and changelog docs:-iupgrades symbol lookup to an exact case-insensitive match, while--text -iremains unsupported.
Added
search -i/--ignore-case— case-insensitive exact match for symbol search.-inow implies--exact, and remains unsupported with--text. Backed by aCOLLATE NOCASEpredicate; leaves FTS5 prefix/fuzzy search (already case-insensitive) untouched. Exposed onindex.SearchQueryasIgnoreCase.- Swift feature coverage tests — parser and store tests now cover Swift declaration classification, actor members, Swift reference extraction, and case-insensitive exact search behavior.
[0.10.0] - 2026-04-15
Highlights
Canonical ranking — search and show now return the most relevant definition first, not whatever SQLite happened to store first. A SymbolScore ranker penalises test, playground, docs, vendor, generated-code, and mirror-tree paths while boosting well-known source roots. Before: show createServer in Vite opened a playground copy. After: the real implementation ranks #1 across all benchmark repos (100% canonical @1, 1.00 MRR).
Faster freshness checks — EnsureFresh now checks directory mtimes before doing a full file walk. If nothing changed since the last index, the check completes in microseconds instead of hashing every file (~500 dir stats on a 10k-file repo instead of ~10k file hashes).
Process-scoped DB — all queries in a single command invocation share one SQLite connection instead of opening and closing it per function call. investigate went from 5+ DB opens to 1.
Added
--pathand--excludeglob filters onsearch,refs, andshow— scope results to specific directories or exclude test/vendor/generated paths. Composable with--kind,--lang,--exact.show --all— emit every matched definition, not just the top-ranked one. Useful when the agent needs to see all overloads or cross-module variants.refs --file <fragment>— restrict reference results to files whose path contains the given fragment. Useful for scopingrefs Contextto files that actually importcontext.go.- Structured alternatives in JSON —
showJSON responses includematch_countandalso: []SymbolResult;contextJSON responses includematch_countandmatches: []SymbolResult. Agents can follow up on alternatives without string-parsing frontmatter. search --textdelegates torg— when ripgrep is onPATH, text search shells out torgfor full SIMD/mmap speed. Falls back to the pure-Go implementation whenrgis absent.- Generated code ranking penalties — symbols in
.pb.go,_generated.go,_gen.go,.gen.ts,_pb2.py,__generated__,.g.dart,/generated/,/gen/paths are ranked below hand-written code. - Ground-truth precision/recall benchmark — the bench harness validates cymbal output against curated expected-definition and expected-reference sets per symbol across 7 corpus repos (43 checks, 100% pass rate).
- Canonical ranking hard-mode benchmark — measures search@1 accuracy, MRR, and show-exactness against 9 hand-picked disambiguation cases with a tuned-grep baseline for fair comparison.
- Grep footgun benchmark — explicit test cases proving cymbal's advantage on common names (e.g.
Context: 915 grep hits → 5 cymbal results;FastAPI: 11k → 8).
Fixed
contextno longer errors on ambiguous symbols — ranks all candidates and picks the top result instead of returningAmbiguousError. Output includesmatches: Nmetadata so agents know alternatives exist.- Ranking before SQL LIMIT — search over-fetches a wider candidate window, applies canonical ranking, then truncates. Canonical definitions are never silently dropped by DB row order. Exact-match queries fetch all rows (candidate set is inherently bounded); FTS queries over-fetch
min(limit×5, 500)rows and rank within tier boundaries (exact > prefix > fuzzy). - Improved signature extraction —
extractSignaturenow capturesreturn_typenodes for Go, Python, Rust, and TypeScript/JavaScript. Python signatures show-> ReturnType.
Breaking (library API)
index.SymbolContextno longer returns*AmbiguousError— it ranks all candidates and returns the top match. Callers that checked forAmbiguousErrorshould instead inspectContextResult.MatchCountandContextResult.Matches.
Changed
- Process-scoped store pool — all public index functions share a cached
*StoreperdbPathviaopenCached.CloseAll()is deferred inmain.goso handles flush on both success and error paths. - EnsureFresh directory-mtime fast path — records
last_index_nsafter each index run. On subsequent commands, walks only directories (skipping.git,node_modules,vendor, etc.) and checks mtimes. If no directory is newer, skips the full file-walk entirely. - DRY output rendering — extracted shared
renderJSONOrFrontmatterintocmd/render.go, deduplicating the json-or-frontmatter pattern across 6 commands. - Benchmark corpus enriched — all 7 corpus repos carry tier/complexity/tags metadata, full ground-truth specs, and 9 canonical disambiguation cases with prefer/avoid path annotations.
[0.9.3] - 2025-04-14
Added
- Unified language registry — added a new
langpackage as the single source of truth for language names, file extensions, special filenames, and tree-sitter grammar availability. - Broader file recognition — cymbal now recognizes additional source/config variants during file classification, including
.mjs,.cjs,.mts,.cts,.pyw,.cxx,.hxx,.hh,.kts,.rake,.gemspec,.sc, and.tfvars. - Recognition for non-parseable file types — cymbal can now classify additional file types for CLI/path heuristics even when they are not indexed, including
Dockerfile,Makefile,Jenkinsfile,CMakeLists.txt, JSON, TOML, Markdown, SQL, Vue, Svelte, Zig, Erlang, Haskell, OCaml, R, and Perl.
Changed
- Shared language resolution across indexing and parsing —
walker,parser, andindexnow all use the same registry-backed language lookup and parseable-language filtering, reducing drift between file discovery and parser support. - Recognized vs parseable languages are now explicit — indexing walks the parseable subset, while file classification can still identify recognized-but-non-indexable file types.
Docs
- Updated README agent-integration guidance to reference
AGENTS.mdinstead ofagent.md.
[0.9.2] - 2026-04-13
Fixed
- Go composite literal & JS/TS
newexpression ref extraction —refs,trace,impact, andinvestigatenow detect references from Go composite literals (Config{},http.Client{},map[K]V{},[]T{},[N]T{}), including qualified types (pkg.Type), and from JavaScript/TypeScriptnewexpressions (new Foo(),new pkg.Bar()) (PR #14).
[0.9.1] - 2026-04-11
Added
- C/C++ call-site reference extraction —
refs,trace,impact, andinvestigatenow return call-graph data for C and C++ files. Includes normalization for dot, arrow (->), and scope-resolution (::) separators, plus C++ template call stripping (std::max<int>→max) (PR #12, @Phototonic).
Changed
- Library usage guide — added
docs/library.mdand a README section covering how to import cymbal as a Go library. - Test helper dedup — extracted shared
debugParseResulthelper from duplicated closures in C/C++ feature tests.
[0.9.0] - 2026-04-09
Changed
- Library-ready package layout — moved all four
internal/packages to top-level importable packages:symbols/,parser/,index/,walker/. External Go projects can now import cymbal as a library (e.g.,import "github.com/1broseidon/cymbal/index"). The CLI (cmd/) continues to work unchanged. This is a breaking change for any code that importedinternal/paths directly (which was not possible for external consumers, but affects forks).
Added
- MIT license file.
- README badges — GitHub stars, Go Reference, Go Report Card, latest release.
[0.8.8] - 2026-04-08
Added
- Multi-language benchmark corpus and regression detection for the bench harness.
Removed
- Deprecated unused LLM summarization feature — removed
--summarize,--backend, and--modelflags fromcymbal index. The feature was underdeveloped (summaries only surfaced inoutline, not insearch,investigate, or other commands) and added significant indexing latency for minimal value. Removed upstream dependency ononeagent.
[0.8.7] - 2026-04-07
Added
- Salesforce Apex language support — classes, methods, fields, constructors, interfaces, and enums via
classifyJavaLikereuse (PR #6, @lynxbat). - Dart language support — classes, enums, mixins, extensions, type aliases, functions, methods, getters, setters, constructors, imports, and refs (PR #11, @Phototonic).
Fixed
- investigate member bleed across files —
investigateno longer mixes members from different symbols that share the same parent name across files or languages. Member lookup is now scoped to the resolved symbol's file. Fixes #9.
[0.8.6] - 2026-04-06
Added
- Elixir language support — modules (
defmodule), functions (def/defp), macros (defmacro), protocols (defprotocol), imports (alias/import/use/require), and cross-module refs. - HCL/Terraform language support — resources, variables, outputs, data sources, modules, providers, and locals blocks with synthesized names (e.g.,
aws_instance.web). - Protobuf language support — messages, enums, services, RPCs, and proto imports.
- Kotlin language support — proper symbol extraction for classes, interfaces, objects, enums, methods, and companion objects (merged PR #7).
- CI workflows — build, lint, test, security (
govulncheck), and dependency review checks on PRs and main branch. - PR template — required structure with summary, testing checklist, security notes, and rollout risk.
- PR body validation — CI check that enforces template usage and completed checklist items on non-draft PRs.
Changed
- Go version — bumped from 1.25.7 to 1.25.8 to resolve stdlib vulnerability flagged by new security check.
- Makefile — added
build-check,vulncheck, andcitargets.
[0.8.5] - 2026-04-05
Changed
- Smart truncation for type symbols —
showandinvestigatenow cap class/struct/interface/enum output at 60 lines instead of dumping the entire body (e.g., FastAPI class went from 170KB to 1.8KB). Full source remains available viacymbal show file:L1-L2. Members are listed separately ininvestigate. - Truncated member signatures — multi-line signatures in
investigatemember listings are collapsed to the first line, preventing huge docstring-heavy parameter lists from bloating output.
Fixed
- README accuracy — removed unsupported languages (HCL, Dockerfile, TOML, HTML, CSS) from supported list, corrected benchmark numbers to match actual RESULTS.md, fixed "Go, Python, and TypeScript" to "Go and Python" (no TS corpus repo).
- Benchmark token efficiency —
showandinvestigatefor large types now dramatically outperform ripgrep instead of losing badly (FastAPI show: -1413% → 84% savings; APIRouter agent workflow: -248% → 95% savings).
[0.8.4] - 2026-04-04
Added
- Auto-index on first query — no more manual
cymbal index .step. The first command in a repo automatically builds the index, with a progress indicator for large repos. Subsequent queries continue to refresh incrementally. Closes #3. (@Ismael) - Git worktree support —
FindGitRootnow detects.gitfiles (used by worktrees) in addition to.gitdirectories, so all commands work correctly insidegit worktreecheckouts. - Intel Mac builds — release pipeline now produces
darwin_amd64binaries and the Homebrew formula includes Intel Mac support. Closes #4. (@alec-pinson)
Fixed
- Correct index path documentation — README now documents the actual OS cache directory paths (
~/.cache/cymbal/on Linux,~/Library/Caches/cymbal/on macOS,%LOCALAPPDATA%\cymbal\on Windows) instead of the stale~/.cymbal/reference. Closes #5. (@candiesdoodle) - Proper error propagation — commands no longer call
os.Exit(1)on "not found" or "ambiguous" results. Errors now flow through cobra's error handling for consistentError:prefixed output and proper exit codes. - Non-git-repo warning — running cymbal outside a git repository now prints a clear warning (
not inside a git repository — results may be empty) instead of silently returning empty results.
[0.8.3] - 2026-04-02
Added
- GHCR container — pre-built multi-arch Docker image (linux/amd64, linux/arm64) published to
ghcr.io/1broseidon/cymbalon every release, tagged with version andlatest.
[0.8.2] - 2026-04-02
Added
- Docker support — Dockerfile, docker-compose.yml, and
CYMBAL_DBenvironment variable for running cymbal from a container with no local Go/CGO setup. Index stored at.cymbal/index.dbin the repo root. (@VertigoOne1) - PowerShell uninstall script (
uninstall.ps1) with optional-Purgeflag to remove index data. (@VertigoOne1)
Fixed
- Windows binary no longer requires MinGW DLLs (
libgcc_s_seh-1.dll,libstdc++-6.dll). Release workflow now statically links the C runtime on Windows. Fixes #1. - Quoted
$(pwd)in all Docker documentation examples to handle paths with spaces.
[0.8.1] - 2026-03-27
Fixed
cymbal structure"Try" suggestions now deduplicated by symbol name — no more repeated suggestions when the same symbol appears in multiple files.
[0.8.0] - 2026-03-27
Added
cymbal structure— structural overview of an indexed codebase. Shows entry points, most-referenced symbols (by call-site count), largest packages, and most-imported files. All derived from existing index data — no AI, no guessing. Answers "I've never seen this repo, where do I start?" Supports--json.- Batch mode for symbol commands —
investigate,show,refs,context, andimpactnow accept multiple symbols:cymbal investigate Foo Bar Baz. One invocation, one JIT freshness check, multiple results. Reduces agent round-trips. - Benchmark harness v2 —
go run ./bench runnow measures speed, accuracy (37/37 ground-truth checks), token efficiency vs ripgrep, JIT freshness overhead, and agent workflow savings across gin, kubectl, and fastapi.
[0.7.3] - 2026-03-27
Added
- JIT freshness — every query command (search, show, investigate, refs, importers, impact, trace, context, outline, diff, ls --stats) now automatically checks for changed files and reindexes them before returning results. No manual
cymbal indexneeded between edits. The index is always correct.- Hot path (nothing changed): ~2ms overhead on small repos, ~14ms on 3000-file repos
- Dirty path (files edited): only changed files are reparsed — 5 touched files on a 770-file repo adds ~40ms
- Deleted files are automatically pruned from the index
- No watch daemons, no hooks, no flags — it just works
index.EnsureFresh(dbPath)public API for programmatic use.
[0.7.2] - 2026-03-26
Added
- PowerShell install script for Windows (
install.ps1) —irm .../install.ps1 | iexfetches the latest release, extracts to%LOCALAPPDATA%\cymbal, and adds to PATH.
Fixed
- Database file created inside the project directory on Windows when
%USERPROFILE%is unset. Now usesos.UserCacheDir()(%LOCALAPPDATA%on Windows) as the primary data directory, with safe fallbacks that never produce a relative path.
[0.7.1] - 2026-03-26
Added
- Windows (amd64) binary in release pipeline — builds with Cgo on
windows-latest, packaged as.zip.
[0.7.0] - 2026-03-25
Added
- Flexible symbol resolution pipeline (
flexResolve) — shared byshow,investigate, andcontext:- Ambiguity auto-resolve: picks best match by path proximity and kind priority, notes alternatives in frontmatter (
matches: 2 (also: path:line)) - Dot-qualified names:
config.Loadresolves by filtering parent/path. Works forpkg.FunctionandClass.methodpatterns. - Fuzzy fallback: exact → case-insensitive (
COLLATE NOCASE) → FTS prefix match. Marks results withfuzzy: true.
- Ambiguity auto-resolve: picks best match by path proximity and kind priority, notes alternatives in frontmatter (
SearchSymbolsCIstore method for case-insensitive exact name match.InvestigateResolvedfor investigating pre-resolved symbols.
Changed
showandinvestigateno longer error on ambiguous symbols — they auto-resolve and return the best match with disambiguation hints in frontmatter.
[0.6.0] - 2026-03-25
Added
cymbal trace <symbol>— downward call graph traversal. Follows what a symbol calls, what those call, etc. (BFS, depth 3 default, max 5). Filters stdlib noise to surface project-defined symbols. Complementary toimpact(upward) andinvestigate(adaptive).
Changed
- Agent integration guide (README, CLAUDE.md, AGENTS.md) restructured around three core commands:
investigate(understand),trace(downward),impact(upward). Based on real-world observation of an agent making 22 sequential calls that trace + investigate handled in 4.
[0.5.1] - 2026-03-25
Fixed
showandinvestigatenow acceptfile:Symbolsyntax to disambiguate when multiple symbols share a name (e.g.,cymbal show config.go:Config,cymbal investigate internal/config/config.go:Config).showline range parser acceptsL-prefixed ranges (file.go:L119-L132) — was advertised in README but broken.
[0.5.0] - 2026-03-25
Added
cymbal investigate <symbol>— kind-adaptive symbol exploration. Returns the right shape of information based on what a symbol is: functions get source + callers + shallow impact; types get source + members + references; ambiguous names get ranked candidates. Eliminates the agent's decision loop of choosing between search/show/refs/impact.ChildSymbolsstore method for querying methods/fields by parent type name.- Benchmark suite now tracks output size (bytes + approximate tokens) and includes ripgrep refs/show equivalents for fair token efficiency comparison.
Fixed
- TypeScript/JavaScript
exportstatement dedup — exported functions, classes, interfaces, types, and enums no longer appear twice in the index (same pattern as the Python decorator fix in v0.4.1).
Changed
- README rewritten with workflow-centric agent integration guide.
investigateis the recommended default, specific commands are escape hatches.
[0.4.1] - 2026-03-24
Added
- Benchmark suite now tracks output size (bytes + approximate tokens) per query, comparing token efficiency across tools. Ripgrep refs and show equivalents added for fair comparison.
Fixed
- Python decorated functions and classes no longer appear twice in outline/search/show. Tree-sitter's
decorated_definitionwrapper was causing double emission — innerfunction_definition/class_definitionnodes are now skipped when their parent already emitted them.
[0.4.0] - 2026-03-24
Changed
- Indexing 2x faster — separated parse workers (parallel, CPU-bound) from serial writer with batched transactions, eliminating goroutine contention on SQLite's writer lock. Cold index dropped from 2.4s to 1.05s on cli/cli (729 files).
- Reindex 4x faster — mtime_ns + file size skip check with pre-loaded map avoids reading files or querying DB per-file. Reindex dropped from 57ms to 14ms on cli/cli.
- Prepared statement reuse — statements prepared once per batch (5 per batch vs 5 per file), reducing cgo overhead on large repos.
- Read-once parse+hash — workers read each file once and pass bytes to both parser and hasher, eliminating duplicate I/O.
- Row-based batch flushing — flush at 100 files OR 50k rows (symbols+imports+refs), preventing pathological batches from symbol-dense repos.
- Robust change detection — mtime stored as nanosecond integer + file size; skip only when both match exactly. Catches coarse FS timestamps and tools that preserve mtime.
- Walker language filtering — unsupported languages (.json, .md, .toml) filtered before stat, reducing channel traffic and allocations.
Added
- Benchmark suite (
bench/) comparing cymbal vs ripgrep vs ctags across Go, Python, and TypeScript repos with reproducible pinned corpus. - Progress indicator on stderr after 10s for large repos (e.g., kubernetes at 16k files).
ParseBytesfunction for parsing from pre-read byte slices.
Fixed
- Stale file pruning — deleted/renamed files are removed from the index on reindex by diffing walker paths against stored paths.
- Savepoint-per-file in batch writer — a single file write failure no longer corrupts the entire batch; partial data is rolled back cleanly.
- Accurate stats after commit — indexed/found counts published only after successful tx.Commit(), preventing inflation on commit failure.
- Split error types — skip reasons separated into unchanged, unsupported, parse_error, write_error; CLI shows non-zero counts conditionally.
[0.3.0] - 2026-03-24
Changed
- Per-repo databases — each repo gets its own SQLite DB at
~/.cymbal/repos/<hash>/index.db, eliminating cross-repo symbol bleed. Searching in repo A no longer returns results from repo B. - Removed
repostable andrepo_idcolumn — no longer needed since each DB is one repo - Added
metatable storingrepo_rootpath per database cymbal ls --reposlists all indexed repos with file/symbol counts--repoflag removed (repo identity comes from DB path now)--dbflag still works as override for all commands
Added
refsandimpactnow show surrounding call-site context (1 line above/below by default, adjustable with-C)- VitePress docs site at chain.sh/cymbal with chain.sh design language
Fixed
- Stale symbol entries from moved/deleted repos no longer pollute search results
[0.2.0] - 2026-03-23
Changed
- All commands now output agent-native frontmatter+content format by default (YAML metadata + content body, optimized for LLM token efficiency)
refsandimpactdeduplicate identical call sites — grouped by file with site countcontextcallers section uses the same dedupsearchresults ranked by relevance: exact name match first, then prefix, then contains- Default limits lowered: refs 50→20, impact 100→50, search 50→20
refs,impact, andcontextnow show actual source lines at call sites, not just line numbers
[0.1.0] - 2026-03-23
Added
- Core indexing engine with tree-sitter parsing, SQLite FTS5 storage, and AI summaries via oneagent
- Batched summarization with diff tracking and model selection
cymbal index— index a codebasecymbal ls— list files and repo statscymbal outline— show file structurecymbal search— symbol and text searchcymbal show— display symbol sourcecymbal refs— find references to a symbolcymbal importers— reverse import lookupcymbal impact— transitive caller analysiscymbal diff— git diff scoped to a symbolcymbal context— bundled source, callers, and imports in one call
Fixed
- Overlapping sub-repo detection prevents duplicate symbol indexing