Skip to content

About

No description, website, or topics provided.

Resources

Stars

132 stars

Watchers

8 watching

Forks

Repository files navigation

code-lens-aI

A code-intelligence MCP server for AI agents. Index a codebase once, then ask Claude / Cursor / any MCP client to understand it β€” with answers grounded in the real call graph, real architectural subsystems, and real source code, not hallucinated guesses.

Built on Tree-sitter + community detection, served over MCP with no database required (Neo4j optional). 24 languages, with JS / TS / TSX / Java parsed out of the box.

From a real session on a ~2,000-file TypeScript monorepo (73 detected subsystems): "This is exactly the right tool β€” it collapses what would be a long exploration into a handful of focused calls, and the community detection produces architectural insight you'd struggle to get from grep." 4 tool calls produced both a complete codebase tour AND a deep mental-model explanation. A naive grep/find/read exploration of the same repo: ~25–40 calls.


🌟 The three killer features

get_overview β€” Instant architectural map (the workhorse)

Without it: agent opens an unfamiliar 2,000-file monorepo and burns 10–20 grep/find/ls calls just to figure out what's there.

With it: one call returns the full architectural shape of the codebase β€” counts, languages, and the top architectural subsystems detected by Leiden community detection. For each subsystem, you get:

  • Its semantic label (set by an agent in a prior session, persists across re-runs) β€” e.g. auth-pipeline, request-routing, data-access
  • Its size (how many files belong to it)
  • Its spine files (the most central by PageRank β€” "of these 196 files, read these 6 first")
  • A one-sentence summary of what it does, with a freshness signal if the description has drifted
  • A heuristic-label fallback (folder-name) for any subsystem an agent hasn't manually labeled β€” so you never see a bare community-43

The PageRank-ranked spine files are the single most useful pedagogical signal. On real production codebases, PageRank correctly surfaces the foundational files in each subsystem β€” typically 2Γ— more central than anything else in that community, which lines up with what an experienced contributor would point you to.

When an agent has labeled a community, the summary: line carries it across sessions. Drifted descriptions get auto-flagged: ↳ written 14d ago; spine has shifted (3 dropped, 6 added since) β€” verify before relying.

Real result: on a ~2,000-file TypeScript monorepo, get_overview + 3 targeted cypher lookups produced the entire architectural mental model an experienced contributor would need. Without get_overview, you'd reverse-engineer the same layout from package.json files plus folder spelunking.

generate_wiki β€” Wiki skeleton in one MCP call

Without it: Claude in your IDE opens an unfamiliar codebase and burns ~30 tool calls (cat, ls, grep, repeated reads) just to gather the structural facts it needs to write a wiki.

With it: one call returns a structured markdown skeleton with all the facts pre-computed:

  • Per-community spine files (top-PageRank in each architectural subsystem)
  • Top functions in each subsystem (most-called, with signatures)
  • HTTP routes table extracted from Express / Koa / etc. handlers
  • Entry points (files no other file imports β€” likely application starts)
  • Test inventory (files where AST patterns matched describe/it/@Test)
  • Cross-community import edges as a data-flow hint
  • Glossary of the top-50 most-called symbols with file:line refs

Plus explicit [AGENT FILLS] placeholders for the prose the agent should write (per-subsystem purpose, codebase narrative). The structural 80% comes free; the agent does the synthesis 20%.

Real result: on a 123-file LangGraph backend, tool call count to produce a wiki dropped from ~30 to ~5-8.

impact_analysis β€” Decision-support, not a data dump

Without it: "What breaks if I change verifyJWT?" gets you a flat list of 47 callers with no signal about which matter.

With it, the first three lines answer the actual question:

## Verdict
⚠️ Risky change. 11 direct production callers across 3 communities,
   8 spine callers, target is itself a spine file.

> Based on visible callers in the indexed graph. May miss callers via
> re-exports, factory wrappers, or dynamic dispatch.

Then the breakdown:

  • Cross-community impact β€” "βœ… Contained" vs "⚠️ Crosses N boundaries"
  • Spine callers β€” dedicated section listing callers that are themselves load-bearing files (their changes ripple)
  • Test files to update β€” copy-paste-friendly path list
  • Production callers grouped by architectural community, with truncation that always preserves at least one entry per community
  • Risk score in metadata (sortable for tooling, never headlining the prose)

The verdict is honest: it always includes a caveat that callers via DI / re-export / dynamic dispatch may be missed. No silent confidently-wrong answers.


πŸ”§ How it works

Source code
    β”‚
    β–Ό Tree-sitter parse
    β”‚
Graph extraction (functions, classes, calls, imports, routes,
                  state objects, anonymous handlers, test files)
    β”‚
    β–Ό community detection (in process)
    β”‚
Communities + PageRank + boundary degree β†’ spine files
    β”‚
    β–Ό stored in
    β”‚
<repo>/.codelens/  (default)   β€”orβ€”   Neo4j  (--neo4j-uri; adds the cypher tool)
    β”‚
    β–Ό exposed via
    β”‚
MCP server (9 tools, +cypher on Neo4j; stdio transport)
    β”‚
    β–Ό
Claude Code, Cursor, Codex, any MCP client

What makes it sharper than grep + Read

Capability Native tools code-lens-aI
"Find code by exact name" grep search_code (also semantic search if --embed)
"Show me this function's body" cat/Read get_definition (no filesystem access required)
"What calls X?" grep -r (false positives) get_callers (real CALLS edges)
"What does X depend on?" manual import-tracing get_callees
"What breaks if I change X?" educated guess impact_analysis with verdict + cross-community + spine
"What architectural subsystems are here?" manual reading get_overview (Leiden communities + spine files)
"Write me a wiki" full-day exploration generate_wiki skeleton in 1 call

πŸ”„ Keeping the index fresh

The indexer already re-extracts only what changed (--incremental, diffed on content hashes). What was missing was anything to trigger it, so a graph went stale the moment you committed and the only signal was a warning at query time.

codelens hooks install          # in the repo you index
codelens hooks status
codelens hooks uninstall

That installs post-commit, post-checkout, post-merge and post-rewrite, so commits, branch switches, git pull and rebases all refresh the graph. Re-indexing runs detached with its exit status discarded β€” it can never block or fail a git operation.

Existing hooks are appended to, never replaced, and uninstall removes only the block it added.


🌍 Languages

24 languages. Four have hand-written extractors; the other twenty run through a config-driven walker (src/extractor/generic.ts) fed by one LanguageConfig per language, so adding a language is normally a config object rather than a new extractor.

Built in β€” no extra install:

Language Extras beyond the common shape
JavaScript, TypeScript, TSX HTTP routes, exports/re-exports, anonymous handlers, state objects
Java Annotations-based test detection, inner classes

Config-driven β€” install the grammars once:

npm run grammars:install

Python, Go, Rust, Ruby, C#, PHP, Scala, Groovy, C, C++, Objective-C, Kotlin, Swift, Elixir, Julia, PowerShell, OCaml, Common Lisp, DreamMaker (DM), Bash.

These extract the shape every language shares: types, interfaces, enums, functions, methods, properties, imports, calls, and inheritance.

Why the grammars are a separate install

Most tree-sitter grammar packages still declare a tree-sitter@^0.21/^0.22 peer range even though they load correctly against 0.25. Listing them as dependencies makes a strict npm resolver fail the entire install, so they are opt-in. Anything missing costs you that one language: the indexer logs it once and carries on.

Coverage is not uniform β€” what you actually get

Every language above was verified by parsing real fixtures and asserting on the resulting graph (src/__tests__/language-configs.test.ts), not by assuming node type names. Known limits, stated rather than papered over:

  • Ruby / Bash emit no IMPORTS edges. require and source are ordinary call nodes in those grammars, so import edges would have to be invented.
  • Common Lisp extracts defun only. The grammar leaves defclass and defmethod as undifferentiated lists and has no call node at all.
  • C# writes base classes and interfaces in one undifferentiated base_list, so both are emitted as EXTENDS.
  • Python skips class attributes; they are indistinguishable from module-level constants without type inference.
  • PowerShell does not model inheritance β€” a class and its base are sibling nodes with nothing to tell them apart.
  • Kotlin has no distinct interface node, so interface Foo appears as a Class.

Languages deliberately absent

Lua, Zig, Verilog, Dart, SQL and Vue have npm grammars that do not load against tree-sitter 0.25 β€” they are NAN-era builds that fail with "Invalid language object". Svelte and Pascal fail to compile at all, and there is no npm grammar for Fortran, Terraform, Astro or Apex. Configs exist for none of them, because a config for a grammar that cannot load is a language claimed but not delivered.


πŸš€ Quick start

Prerequisites

  • Node.js 20+

No database, no Docker. The graph lives in <repo>/.codelens/.

Install + index + wire up

# Install the CLI globally
npm install -g @marvikomo/codelens-ai

# Index a repo: parses, resolves calls, detects communities, writes .codelens/
codelens index /path/to/your/repo

# (Optional) semantic + hybrid search (downloads ~161 MB embedding model once)
codelens index /path/to/your/repo --embed

# Register the MCP server with Claude Code (one-time)
codelens mcp install --scope user --repo /path/to/your/repo

# Keep it fresh: re-index after every commit, checkout, merge and rebase
codelens hooks install /path/to/your/repo

Add .codelens/ to the repo's .gitignore.

Then in any Claude Code session:

Use codelens to give me an overview of this codebase.

The agent will see 9 tools (search_code, get_definition, read_code, get_callers, get_callees, impact_analysis, get_overview, label_community, generate_wiki) and decide which to use.

Optional: Neo4j backend

Pass --neo4j-uri (or set NEO4J_URI / NEO4J_USER / NEO4J_PASSWORD) and the same commands store the graph in Neo4j instead. You get one extra tool, cypher, for free-form read-only queries, and a graph that outlives the machine that built it.

codelens neo4j start                                   # bundled neo4j:5.15 via Docker
codelens index /path/to/your/repo \
  --neo4j-uri bolt://localhost:7687 --neo4j-user neo4j --neo4j-password password \
  --neo4j-clear --cluster
codelens mcp install --scope user \
  --neo4j-uri bolt://localhost:7687 --neo4j-user neo4j --neo4j-password password

Every tool produces the same answer on either backend. If NEO4J_URI is set in your shell but you want the local index, pass --local.

Day-to-day

codelens index /path/to/repo --incremental    # skip if nothing changed (local: rebuilds otherwise, ~seconds)
codelens mcp --repo /path/to/repo             # start the server by hand
codelens neo4j status|logs|stop               # only if you use the Neo4j backend

Development (from source)

git clone https://github.com/marvikomo/code-lens-aI.git
cd code-lens-aI
npm install
npm link                       # makes `codelens` resolve to this checkout
npm run dev -- index .         # or: ts-node src/cli.ts ...

πŸ“‹ The 10 MCP tools

Every tool's description tells the agent exactly when to use it β€” explicit "use this FIRST when…" nudges keep agents from reaching for cypher when a higher-level tool is the right answer. Real session feedback called this out as a design that "kept me from reaching for cypher when get_overview was correct."

Tool What it answers When agents reach for it
get_overview "What is this codebase?" β€” counts, languages, top communities, spine files, label freshness Call this FIRST on any unfamiliar repo
generate_wiki "Write a wiki" β€” structural skeleton with [AGENT FILLS] markers When asked to document or write architectural docs
impact_analysis "Is it safe to change X?" β€” verdict-first with spine + test/prod + cross-community Before recommending a refactor or non-trivial change
search_code "Find code that does X" β€” keyword (FTS), semantic (vector), or hybrid (RRF fusion) Locate symbols by name OR concept
get_definition "Show me X's full body and signature" β€” returns the body, not just a signature, saving a read_code round-trip When you know the symbol name
read_code "Read lines N-M of file Y" β€” direct, capped at 2000 lines Verify a claim or read context around a known location
get_callers "Who calls X?" β€” graph traversal over real CALLS edges, depth-configurable Understand reach of a function before changing it
get_callees "What does X call?" β€” outbound graph traversal Trace what a function depends on
label_community "Give this architectural subsystem a name" β€” agent-driven labeling, stamps freshness metadata Once per community, on first encounter
cypher Read-only Cypher escape hatch β€” schema cheat-sheet inlined in the tool description so agents query the right props on the right node kinds When the canned tools fall short of an exact query

🧠 Why agents actually reach for it

Four design choices that came directly from real-session feedback and now define the product:

1. Spine files turn "200-file subsystem" into "read these 6 first." PageRank + boundary-degree centrality picks out the load-bearing files in each community. The agent doesn't have to guess where to start β€” code-lens-aI tells it which 5–6 files every other file in the subsystem ultimately leans on. That's a pedagogical signal nothing else in the agent-coding space currently provides.

2. Persistent semantic labels β€” set once by an agent, used forever. After label_community is called, the label rides along on every subsequent get_overview and generate_wiki call across all future sessions. A community labeled auth-pipeline once stays that way until the graph genuinely shifts (and we'll tell the agent when it has). No re-labeling on every session.

3. Heuristic fallback so you never see "UNLABELED." When no agent has labeled a community yet, code-lens-aI derives a folder-name heuristic (e.g. auth, api, services) and marks it (heuristic). Agents get a useful name immediately AND a clear signal that they could upgrade it to a semantic label.

4. Description freshness signals β€” no silent staleness. Every agent-written description is timestamped + the spine snapshot is captured. Subsequent reads compute drift and flag stale descriptions: ↳ written 14d ago; spine has shifted (3 dropped, 6 added since) β€” verify before relying. Agents never propagate stale claims confidently.


πŸ› οΈ CLI reference

codelens <subcommand> [args]
codelens <repo-path> [options]                  # shorthand for: codelens index <path>

Subcommands

codelens index <path> [options]                 # analyze + index a repo
codelens mcp                                    # run MCP server (stdio)
codelens mcp install [options]                  # register MCP with Claude Code
codelens neo4j start|stop|status|logs           # manage bundled Neo4j container
codelens help                                   # show top-level help

codelens index options

# Output
--no-json              # don't emit graph JSON to stdout
-o file.json           # write JSON to file
--ignore foo,bar       # extra directory/file names to skip
--stats                # print summary stats to stderr

# Neo4j (also reads NEO4J_URI / NEO4J_USER / NEO4J_PASSWORD / NEO4J_DATABASE env vars)
--neo4j-uri <uri>
--neo4j-user <name>
--neo4j-password <pw>
--neo4j-clear          # DETACH DELETE all :CodeNode before re-indexing
--incremental          # re-index only changed files (git-aware), cascading to dependents

# Clustering (no plugin required β€” computed in process)
--cluster              # run Leiden + PageRank + spine selection
--cluster-only         # skip indexing; just re-cluster
--cluster-clear        # wipe community props + labels first
--cluster-min-size <n> # min files to materialize a :Community node (default 3)

# Embeddings (opt-in; ~161MB model on first run)
--embed                # compute embeddings for Function/Method/Class nodes
--embed-model <hf-id>  # override default jinaai/jina-embeddings-v2-base-code

# Semantic layers (opt-in; needs TYPESAFE_API_KEY β€” https://typesafe.ai)
--layers               # tag every File with an architectural layer via TypeSafe's Jev:
                       # api_surface | ui | business_logic | data_access | infrastructure
                       # | configuration | utilities | tests | unclear
--layers-model <id>    # override default jev-latest
--layers-min-confidence <0-1>  # report picks below this as low-confidence (default 0.6)

# Search-only mode (no <repo-path> needed)
--search "<query>"
--search-mode <m>      # fts | vector | hybrid (auto if omitted)
--search-limit <n>

codelens mcp install options

--scope <local|user|project>   # where to register (default: local)
                               #   local   = this directory only
                               #   user    = available in all your projects
                               #   project = shared via .mcp.json in repo
--neo4j-uri <uri>              # e.g. bolt://localhost:7687
--neo4j-user <name>
--neo4j-password <pw>
--neo4j-database <name>        # (optional)

If --neo4j-* flags are omitted, falls back to NEO4J_URI / NEO4J_USER / NEO4J_PASSWORD env vars.


🎯 What's actually in the graph

Every indexed codebase gets:

Node kinds: Repository, Folder, File, Class, Interface, TypeAlias, Enum, Function, Method, Property, Variable, Community, Unresolved

Edge types: CONTAINS, DEFINES, HAS_METHOD, HAS_PROPERTY, CALLS, IMPORTS, EXTENDS, IMPLEMENTS, IN_COMMUNITY

Properties on nodes:

  • Source code: body, signature, bodyTruncated
  • Location: path, startRow, endRow, startColumn, endColumn, language
  • HTTP handlers: httpMethod, route, routerObject (for router.post(...) patterns)
  • State/factory: builder (for Annotation.Root({...}), z.object({...}), etc.)
  • Tests: isTest, testFramework (jest / vitest / bun / junit / pytest detection)
  • Communities: community int, pagerank float, boundary int, is_core boolean
  • Embeddings: embedding float[768] (when --embed was run)
  • Semantic layers: layer string, layerConfidence float (when --layers was run) β€” a second axis to communities: MATCH (f:File {layer: 'data_access'}) RETURN f.path
  • FTS: indexed on name, signature, body, path

What gets uniquely captured that other indexers miss:

  • Anonymous route handlers β€” router.post("/users", async (req, res) => {...}) becomes a first-class Function node with httpMethod: "POST", route: "/users", and the handler body indexed for search
  • State objects β€” export const PlanAgentState = Annotation.Root({...}) becomes a Variable node with builder: "Annotation.Root" so you can find every state schema in one query
  • Test files β€” detected via AST pattern matching (not just file path), with framework name (jest / vitest / bun)
  • Architectural communities β€” Leiden detection on the file-import subgraph reveals real subsystems with ~80% accuracy on production codebases

πŸ—οΈ Architecture (one paragraph)

The CLI walks a repo, parses each file with Tree-sitter (24 languages), runs language-specific extractors that emit nodes (functions, classes, etc.) and edges (calls, imports) into an in-memory graphlib graph. Community detection (Louvain), PageRank, boundary degree and blast radius run in process over the file-IMPORTS subgraph β€” a few thousand nodes even on a large monorepo β€” and per-community spine selection marks the most-central files is_core. The result is written to <repo>/.codelens/ by default, or pushed to Neo4j when --neo4j-uri is given. Optionally, @xenova/transformers computes 768-dim vector embeddings for Function/Method/Class bodies (jina-base-code model, in-process, ~161 MB). At query time, the MCP server loads whichever store you built and exposes its tools through one GraphStore interface, shaping results into decision-support prose (impact_analysis), structural facts (generate_wiki), or β€” Neo4j only β€” raw rows (cypher).


πŸ“ Project structure

src/
β”œβ”€β”€ analyser/         # repo-walking + per-file extraction orchestration
β”œβ”€β”€ extractor/        # per-language tree-sitter walkers (jsts.ts, java.ts, base.ts)
β”œβ”€β”€ util/             # graph data model, language detection, parser factory
β”œβ”€β”€ store/            # GraphStore interface; local (.codelens/) and Neo4j backends
β”œβ”€β”€ indexers/         # Neo4j bulk-write
β”œβ”€β”€ clustering/       # In-process Louvain + PageRank + blast + spine
β”œβ”€β”€ embeddings/       # @xenova/transformers wrapper + batch pipeline
β”œβ”€β”€ search/           # FTS, vector, hybrid (RRF) implementations
β”œβ”€β”€ mcp/              # MCP server + 10 tool implementations
β”œβ”€β”€ cli-commands/     # subcommand handlers (neo4j docker wrapper, mcp install)
└── cli.ts            # entry point + subcommand dispatcher
scripts/
β”œβ”€β”€ mcp-smoke.ts          # end-to-end MCP smoke test (10 tools)
β”œβ”€β”€ mcp-impact-test.ts    # impact_analysis manual harness
β”œβ”€β”€ mcp-wiki-test.ts      # generate_wiki manual harness
└── mcp-label-test.ts     # label_community manual harness

🀝 Contributing

The project is iterating fast. Real product feedback drives the roadmap β€” recent shipped features (Verdict-first impact_analysis, agent-driven community labeling, generate_wiki) all came from real-use sessions in Claude Code where gaps surfaced and got planned + shipped within hours.

If you find a gap: open an issue with the actual prompt + expected output. The plan files in ~/.claude/plans/ document the design rationale for each shipped capability.


πŸ“„ License

MIT

About

No description, website, or topics provided.

Resources

Stars

132 stars

Watchers

8 watching

Forks

Releases

Packages

Used by

Contributors

Languages