Zig port of codebase-memory-mcp — an MCP server that builds knowledge graphs from codebases.
After first clone, fetch vendored tree-sitter grammars:
mise install
mise run bootstrap
bash scripts/fetch_grammars.sh # direct fallback, idempotent, skips if already present
bash scripts/fetch_grammars.sh --force # re-fetch from upstreamzig build # compile
zig build test # run unit tests
zig build run # run MCP server (stdio)
zig build run -- --version
zig build run -- --help
zig build run -- cli <tool> [json]Cross-compile: zig build -Dtarget=aarch64-linux-musl
Set version: zig build -Dversion=1.0.0
src/
root.zig Module root, re-exports public API
main.zig CLI entry point + MCP server startup
store.zig SQLite graph store (nodes/edges/projects)
graph_buffer.zig In-memory graph buffer (build then dump to SQLite)
pipeline.zig Multi-pass indexing pipeline orchestrator
mcp.zig MCP JSON-RPC server (stdio + HTTP)
cypher.zig Cypher query engine (lexer/parser/executor)
discover.zig File discovery, language detection, gitignore
watcher.zig Adaptive polling for auto-reindex
registry.zig Function name resolution for call edges
minhash.zig MinHash fingerprinting for near-clone detection
- SQLite: vendored amalgamation in
vendored/sqlite3/, compiled as C via build.zig - tree-sitter: via
zig-tree-sitterpackage (build.zig.zon) - std.json: stdlib JSON for MCP protocol (no external JSON lib)
- Zig 0.15.x (minimum 0.15.2)
- Explicit allocator passing everywhere
std.heap.DebugAllocatorin debug, considerstd.heap.c_allocatorfor release- Arena allocators for per-file extraction lifetimes
- Error unions with inferred error sets where possible
- Tests live in
*_test.zigfiles alongside their module zig fmtfor formatting (enforced)mise run lintfor Zig lint checks (find src -name '*.zig' | zlint -S)zlintis bootstrapped bymise installin this repo via the pinned GitHub release.- If MCP stdio starts acknowledging only the first request in a piped session, treat
src/mcp.zigrunFilesframing as suspect first; this repo has already tripped overstd.Io.Reader.takeDelimiterExclusivethere, and the durable fix is an explicit newline-framed file read loop. - If
scripts/run_interop_alignment.shstarts timing out on a secondtools/listrequest afterinitialize, inspect the MCP response framing before widening the harness timeout.tools/listmust stay a single-line JSON-RPC response on this newline-delimited transport; embedding literal newlines in the payload breaks piped clients and the interop harness. - If
zig buildappears to leavezig-out/bin/cbmon stale behavior after asrc/main.zigedit, verify the executable with a fresh--cache-dir/--global-cache-dir/--prefixbuild. A stale installed binary can mask a compile error in the executable step even when an olderzig-out/bin/cbmis still present. Also avoid importingdiscover.zigdirectly fromsrc/main.zig; usecbm.discoverthere so test builds do not trip Zig's duplicate-module error. - If
zig buildorzig build testfails withNoSpaceLeft, checkdf -h . /Users/skooch/.cache/zigfirst. The worktree-local.zig-cachecan grow large enough to exhaust the shared Data volume; remove.zig-cachein the affected worktree and retry before treating it as a source failure. - If
git commitorgit addfails because.git/index.lockalready exists, treat it as a stale lock, remove it with a non-interactiverm -f .git/index.lock, and retry the git command. - Keep
scripts/run_interop_alignment.shinline Python compatible with the systempython3here (currently 3.9); avoidX | Ytype-union syntax in that heredoc or the parity harness will fail before running comparisons. - If
scripts/run_interop_alignment.shappears to hang after finishing a fixture while acbmchild is just polling inwaitForInput, inspect the Python harness finalizer before the MCP server. The durable fix is to close stdin, wait for the subprocess to exit with a bounded timeout, and only then drain trailing stdout/stderr, instead of blocking onstdout.read()first. - If
zig fmtor otherzigshimmed commands fail in a peer worktree becausemise.tomlis untrusted or contains a parser-hostile entry, bypass the shim and use the installed Zig binary directly (for example/Users/skooch/.local/share/mise/installs/zig/0.15.2/bin/zig) rather than stopping on the wrapper. - From peer worktrees under
../worktrees/, the original C repo is not at../codebase-memory-mcp; script defaults that compare against the C binary need a../../codebase-memory-mcpfallback or explicit override so interop/benchmark runs do not fail on path resolution alone. scripts/run_benchmark_suite.shruns withset -u; if you forward optional trailing args, guard the empty-array case instead of unconditionally expanding${EXTRA_ARGS[@]}or the script will fail before the Python harness starts.- Fresh worktrees may not include the untracked
vendored/grammars/andvendored/tree_sitter/directories required byzig build test; if the build fails with missingvendored/grammars/*/parser.c, runbash scripts/bootstrap_worktree.sh [primary-checkout]or copy those vendored directories from the primary checkout into the worktree before retrying verification. - If
bash scripts/bootstrap_worktree.shprints success butzig buildstill fails on missingvendored/grammars/*/parser.c, check for a partially populatedvendored/grammars/directory in the worktree. The bootstrap script must copy missing grammar subdirectories, not just skip work because the top-level directory already exists. - If
bash scripts/fetch_grammars.shexits withGrammars already presentwhilezig buildstill reports a missing vendored grammar likevendored/grammars/powershell/parser.c, treat the script's early-exit guard as stale. It must verify every required grammar, not justrust/parser.c, before skipping work. - The primary checkout can hit the same stale-grammar state as worktrees. If
zig build testfails in/Users/skooch/projects/codebase-memory-zigwith a missing vendored grammar likevendored/grammars/powershell/parser.c, runbash scripts/fetch_grammars.sh --forcebefore treating it as a source regression. - If
zig buildfails after adding a vendored grammar with errors likeunknown type name 'TSFieldMapSlice', check whether that grammar's localtree_sitter/header directory was copied alongsideparser.c. Existing grammars here rely on grammar-local headers first; copyvendored/grammars/<lang>/tree_sitter/before changing the sharedvendored/tree_sitter/headers. - The agent comparison harness is intentionally
zsh-only viascripts/run_agent_comparison.zsh; keep that entrypoint canonical instead of reintroducing a shell-compat wrapper. - The system
bashon this macOS host is3.2, so helper scripts must not rely on associative arrays. Ifscripts/fetch_grammars.shfails atdeclare -Awithset -u, rewrite the mapping layer with portablecasehelpers before retrying.
The original C codebase lives at ../codebase-memory-mcp. Key mappings:
| C | Zig |
|---|---|
CBMArena |
std.heap.ArenaAllocator |
CBMHashTable |
std.StringHashMap(T) |
CBM_DYN_ARRAY(T) |
std.ArrayList(T) |
cbm_str_intern() |
std.StringHashMap(void) on arena |
| yyjson manual JSON | std.json with struct reflection |
int return codes |
Error unions: !T |
Function pointers + void* |
*const fn(...) or comptime generics |
| pthreads | std.Thread + std.Thread.Pool |
These features are NOT being ported (dead code or half-baked):
traces/(ingest_traces stub, never implemented)pass_infrascan(dead parser functions, monolithic)pass_envscan,pass_k8s(half-baked, errors silently ignored)pass_compile_commands,pass_configures(niche, low-value)ui/HTTP server (Mongoose, separate concern)foundation/yaml.c,compat_regex,vmem(superseded by Zig stdlib)