> your AI agent picks dependencies from memory; give it dated facts — try starlog.dev ↗ vet your agent's deps ↗ vibe-coding is fine. vibe-importing isn’t. — try starlog.dev ↗ vibe-importing isn’t fine ↗ your agent has never seen your private packages — try starlog.dev ↗ facts for private packages ↗ a linter for the dependencies your AI agent picks — try starlog.dev ↗ a linter for agent deps ↗

Back to Articles

How a 50MB Binary Indexes the Linux Kernel Faster Than You Can Run `git status`

[ View on GitHub ]

How a 50MB Binary Indexes the Linux Kernel Faster Than You Can Run git status

Hook

Indexing the Linux kernel's 28 million lines of code takes this tool 180 seconds. Loading your IDE's TypeScript language server probably took longer.

Context

AI coding assistants are burning through context windows because they can't see code structure. When Claude or Cursor needs to understand a function call chain across 50 microservices, it falls back to grepping file contents and stuffing entire modules into prompts—thousands of tokens per query, most of them irrelevant. Language servers like tsserver and pyright can answer 'go to definition,' but they're single-repo tools that can't trace HTTP calls across service boundaries, and they restart analysis from scratch on every editor boot.

The Model Context Protocol (MCP) was supposed to fix this by letting LLMs query external knowledge sources, but existing code intelligence tools weren't built for it. Sourcegraph requires Kubernetes deployments. Tree-sitter gives you parse trees but no semantic layer. CodeQL needs custom queries per analysis. What was missing: a tool that could index entire codebases into queryable graphs in seconds, persist those graphs as Git-committable artifacts, expose them via MCP, and do it all without npm install, Docker pull, or API keys.

Technical Insight

Hybrid LSP Resolvers

Git-versioned

Source Files

158 Languages

LZ4 Compression

In-Memory Buffer

Tree-sitter Parser

Vendored Grammars

Phase 1: Generic AST

Functions/Classes/Imports

Phase 2: Hybrid LSP

Type Inference in C

In-Memory SQLite

Property Graph + FTS5

Nomic Embedder

768-dim int8

.codebase-memory/

Zstd Snapshot

MCP Server

stdio JSON-RPC

14 Tools API

search/traverse/analyze

HTTP UI

3D Force Graph

Build Artifact

Merge Strategy

tsserver control-flow

pyright constraints

gopls packages

rust-analyzer traits

System architecture — auto-generated

The codebase-memory-mcp architecture makes three bets that explain its performance: vendoring everything into a static binary, reimplementing language server algorithms in C instead of shelling out, and treating the knowledge graph as a versioned build artifact rather than ephemeral analysis.

The binary embeds Nomic's 768-dimensional code embedding model (int8 quantized to 137MB), tree-sitter grammars for 158 languages, and custom type resolvers for 10 languages. When you run codebase-memory index ., it spins up an in-memory SQLite instance, LZ4-compresses source files in RAM, parses them through vendored tree-sitter, then runs a two-phase analysis pipeline. Phase one is generic AST traversal—extracting functions, classes, imports regardless of language. Phase two is the 'Hybrid LSP' layer: lightweight reimplementations of type inference algorithms from tsserver (control-flow analysis), pyright (constraint solving), gopls (package resolution), rust-analyzer (trait resolution), and six others.

Here's what a typical MCP query looks like from Claude's perspective:

{
  "method": "tools/call",
  "params": {
    "name": "search_definitions",
    "arguments": {
      "query": "authentication middleware",
      "filters": {
        "node_types": ["FUNCTION", "CLASS"],
        "languages": ["typescript", "python"]
      }
    }
  }
}

The server translates this into a SQLite query combining FTS5 full-text search with custom graph traversal indexes:

WITH semantic_matches AS (
  SELECT node_id, 
         bm25(nodes_fts) + 
         embedding_similarity(query_vec, node_embedding) * 2.0 +
         CASE WHEN has_decorator('@auth') THEN 5.0 ELSE 0.0 END
         AS score
  FROM nodes_fts 
  JOIN nodes USING(node_id)
  WHERE nodes_fts MATCH 'authentication middleware'
    AND node_type IN ('FUNCTION', 'CLASS')
)
SELECT n.*, sm.score,
       (SELECT json_group_array(json_object(
         'caller', src.qualified_name,
         'file', src.file_path
       ))
        FROM edges e 
        JOIN nodes src ON e.source_id = src.node_id
        WHERE e.target_id = n.node_id 
          AND e.edge_type = 'CALLS') AS callers
FROM semantic_matches sm
JOIN nodes n USING(node_id)
ORDER BY score DESC LIMIT 20;

The 11-signal scoring combines TF-IDF, cosine similarity on embeddings, API signature matching (detecting patterns like @app.route('/auth') during parse via Aho-Corasick), AST structural profiles (MinHash for clone detection), Halstead complexity metrics, and graph diffusion (PageRank-style importance from inbound CALLS edges). All of this runs in sub-millisecond time because the entire graph sits in RAM with custom indexes.

The 'Hybrid LSP' type resolution is the controversial part. Instead of running tsc --noEmit or launching pyright, it reimplements the core algorithms in ~5K lines of C per language. For TypeScript call graph edges, it doesn't need to resolve conditional types or infer generic constraints—it just needs to answer 'does this identifier refer to that function?' The implementation tracks symbol scopes, follows imports, and does basic generic substitution. Here's what it catches:

// userService.ts
export class UserService {
  async findById(id: string): Promise<User> { /**/ }
}

// authController.ts  
import { UserService } from './userService';
const svc = new UserService();
await svc.findById(req.params.id); // CALLS edge: authController → UserService.findById

What it misses: complex generic inference, conditional types, structural typing with Protocols. For 90% of call graph use cases—'who calls this function' or 'trace this API route to database queries'—that's acceptable. The alternative (shelling out to actual language servers) would add seconds of latency per query.

The Git-committable artifact design is the architectural innovation most tools miss. After indexing, the SQLite database gets zstd-compressed at level 9 (50:1 compression typical) and written to .codebase-memory/snapshot.db.zst. A .gitattributes entry marks it binary with merge strategy union, so concurrent branches don't cause conflicts. Incremental updates from the file watcher use zstd level 3 for faster writes. This means:

  1. CI builds can include indexing as a build step
  2. Code review diffs can show graph changes (added edges, new entry points)
  3. Agents get instant graph access without reindexing on every checkout
  4. Team members share the same semantic view without central infrastructure

Cross-repository linking uses CROSS_REPO_HTTP_CALL edges detected by matching AST patterns like app.post('/api/users') in one service with fetch('https://user-service/api/users') in another. The confidence scoring ranges 0.0-1.0 based on URL pattern similarity, HTTP method matching, and payload schema overlap detected via JSON schema extraction from validation code.

Gotcha

The Hybrid LSP approach has a hard accuracy ceiling. It won't resolve TypeScript mapped types, Python Protocol structural subtyping, or Rust trait object dynamic dispatch. For large TypeScript codebases using advanced type-level programming, expect 10-15% false negatives in call graphs—missing edges where type inference failed. The tool won't warn you about this; you'll discover it when cross-service tracing stops at a generic interface.

The single-binary design means zero extensibility. If you need custom analysis passes (detecting security patterns, tracking data lineage beyond function calls, integrating proprietary language extensions), you're forking the C codebase or waiting for upstream PRs. There's no plugin API. The RAM-first pipeline also has no memory backpressure—it assumes your entire codebase decompressed plus graph structure fits in available RAM. The Linux kernel indexing uses ~4GB peak; extrapolate from there. If you OOM, there's no disk-spilling fallback documented. Finally, cross-repo analysis requires all repositories indexed into one SQLite file, which creates organizational scaling problems: who maintains the unified graph? How do you handle access control when the graph contains service A's internals and service B's secrets? The tool doesn't address multi-tenant graph isolation.

Verdict

Use if: You're running AI coding agents (Claude, Cursor, Aider) against polyglot codebases or microservices and tired of token waste from context-stuffing; you want local-first tooling without Docker/API dependencies; your team can commit a 100MB compressed artifact to Git and treat it like a build output; you need 'good enough' call graphs across 10+ languages faster than language servers can boot. Skip if: You require provably correct static analysis for security/compliance (use Semgrep/CodeQL with sound semantics); your primary languages are outside the 10-language Hybrid LSP set and generic AST traversal loses too many edges; you're already invested in Sourcegraph/Kythe infrastructure and need enterprise features like RBAC, audit logs, or federated graphs across 500+ repositories; your codebase is primarily one language and you'd rather use that language's dedicated tooling (rust-analyzer, gopls) with full semantic accuracy.