codebase-brain-mcp 0.1.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/PKG-INFO +117 -22
  2. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/README.md +113 -21
  3. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/pyproject.toml +4 -1
  4. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/pyproject.toml.orig +4 -1
  5. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/analysis.py +18 -6
  6. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/chunking.py +46 -18
  7. codebase_brain_mcp-0.2.1/src/codebase_brain_mcp/chunking_js.py +255 -0
  8. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/evals.py +5 -2
  9. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/server.py +130 -42
  10. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/sources.py +1 -1
  11. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/store.py +13 -9
  12. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/LICENSE +0 -0
  13. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/__init__.py +0 -0
  14. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/__main__.py +0 -0
  15. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/brain.py +0 -0
  16. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/config.py +0 -0
  17. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/embeddings.py +0 -0
  18. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/history.py +0 -0
  19. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/py.typed +0 -0
  20. {codebase_brain_mcp-0.1.0 → codebase_brain_mcp-0.2.1}/src/codebase_brain_mcp/remote.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codebase-brain-mcp
3
- Version: 0.1.0
3
+ Version: 0.2.1
4
4
  Summary: An MCP server that gives AI agents a cited, searchable memory of a codebase.
5
5
  Keywords: mcp,model-context-protocol,rag,code-search,ai-agents,claude,git
6
6
  Author: Noman Nazir
@@ -18,6 +18,9 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
18
  Requires-Dist: fastembed>=0.9.1
19
19
  Requires-Dist: mcp>=2.3.0
20
20
  Requires-Dist: numpy>=2.5.4
21
+ Requires-Dist: tree-sitter>=0.25.2,<0.26
22
+ Requires-Dist: tree-sitter-javascript>=0.25
23
+ Requires-Dist: tree-sitter-typescript>=0.23.2
21
24
  Requires-Python: >=3.12
22
25
  Project-URL: Homepage, https://github.com/Nomannazir/codebase-brain-mcp
23
26
  Project-URL: Repository, https://github.com/Nomannazir/codebase-brain-mcp
@@ -73,8 +76,10 @@ A plug-in for any AI assistant that turns a codebase into something it can actua
73
76
 
74
77
  ## Why it's different (technical)
75
78
 
76
- - **Code-aware chunking.** Python is split by function, class and method using the real syntax tree, Markdown by
77
- heading. Each chunk knows its symbol (`TimestampSigner.unsign`) and exact lines, so citations point at real code.
79
+ - **Code-aware chunking.** Python (with `ast`) and JavaScript / TypeScript / JSX / TSX (with tree-sitter) are split
80
+ by function, class, method, arrow function, interface, type and enum, with their doc comments. Markdown is split
81
+ by heading. Each chunk knows its symbol (`TimestampSigner.unsign`, `Ky.#retry`) and exact lines, so citations
82
+ point at real code. Other languages are indexed in overlapping line windows.
78
83
  - **Hybrid retrieval.** SQLite FTS5 BM25 (with `camelCase` / `snake_case` identifier splitting) plus local
79
84
  embeddings, merged with reciprocal-rank fusion. Exact names and fuzzy questions both work.
80
85
  - **Local and free.** Embeddings run on your machine (`fastembed`, ONNX). No API keys, and no code leaves your
@@ -85,7 +90,8 @@ A plug-in for any AI assistant that turns a codebase into something it can actua
85
90
  the current lines, and links the pull requests behind each change. So agents can answer *"why is it built this
86
91
  way?"*, not just *"what does it do?"*.
87
92
  - **Impact analysis.** `impact_of_change` lists everything that uses a function, directly and transitively, plus the
88
- tests that exercise it, before an agent changes anything.
93
+ tests that exercise it, before an agent changes anything. It works across Python, JavaScript and TypeScript files,
94
+ and follows a constructor to the code that builds its class.
89
95
  - **Doc-drift detection.** `find_doc_drift` checks Markdown and reStructuredText docs against the code. It finds
90
96
  files, functions, settings and flags that don't exist; examples passing parameters a function doesn't take; imports
91
97
  that would fail; and sections last edited long before the code they describe changed. Each finding cites the doc
@@ -97,7 +103,7 @@ A plug-in for any AI assistant that turns a codebase into something it can actua
97
103
 
98
104
  | Tool | What it does |
99
105
  |---|---|
100
- | `index_repo(source, name?)` | Index a local path or an `https://` git URL (shallow clone). Reports progress. |
106
+ | `index_repo(source, name?)` | Index a local path or an `https://` git URL (blobless clone with full history). Reports progress. |
101
107
  | `ask_codebase(question, repo?, k?)` | The most relevant code and docs as cited sources, plus related commits, with instructions to answer only from them. |
102
108
  | `search(query, repo?, k?, path_prefix?, kinds?)` | Raw hybrid search, filterable by path and chunk kind. |
103
109
  | `explain_history(symbol? \| path, start_line?, end_line?, repo?)` | Every commit that changed a function or line range, newest first, with the relevant diff, current authorship, and PR titles and descriptions (GitHub). |
@@ -111,15 +117,67 @@ Also included: the resource `brain://repos`, and the prompt `answer_with_citatio
111
117
 
112
118
  ## Install
113
119
 
114
- Codebase Brain runs on your own machine, so there's no server or account, and it's free. With
115
- [uv](https://docs.astral.sh/uv/):
120
+ Codebase Brain runs on your own machine, so there's no server or account, and it's free. It works with any
121
+ MCP-compatible assistant. All you need is [uv](https://docs.astral.sh/uv/), and every client runs the same command:
122
+ `uvx codebase-brain-mcp`.
123
+
124
+ **Claude Code**
116
125
 
117
126
  ```bash
118
- # Claude Code
119
127
  claude mcp add codebase-brain -- uvx codebase-brain-mcp
128
+ ```
129
+
130
+ **Cursor:** `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project)
131
+
132
+ ```json
133
+ {
134
+ "mcpServers": {
135
+ "codebase-brain": { "command": "uvx", "args": ["codebase-brain-mcp"] }
136
+ }
137
+ }
138
+ ```
139
+
140
+ **Claude Desktop:** Settings → Developer → Edit Config (`claude_desktop_config.json`), then restart the app
141
+
142
+ ```json
143
+ {
144
+ "mcpServers": {
145
+ "codebase-brain": { "command": "uvx", "args": ["codebase-brain-mcp"] }
146
+ }
147
+ }
148
+ ```
149
+
150
+ **VS Code (GitHub Copilot agent mode):** `.vscode/mcp.json`
151
+
152
+ ```json
153
+ {
154
+ "servers": {
155
+ "codebase-brain": { "type": "stdio", "command": "uvx", "args": ["codebase-brain-mcp"] }
156
+ }
157
+ }
158
+ ```
159
+
160
+ **Windsurf:** `~/.codeium/windsurf/mcp_config.json`. **Gemini CLI:** `~/.gemini/settings.json`. Both use the same
161
+ `mcpServers` block as Cursor.
162
+
163
+ **OpenAI Codex CLI**
164
+
165
+ ```bash
166
+ codex mcp add codebase-brain -- uvx codebase-brain-mcp
167
+ ```
168
+
169
+ > **Tool doesn't start?** Desktop apps often don't see your shell's `PATH`. Run `which uvx` and put that full
170
+ > path in `"command"`, for example `"/Users/you/.local/bin/uvx"`.
120
171
 
121
- # Any MCP client (Claude Desktop, Cursor, ...): add this server to its config
122
- { "command": "uvx", "args": ["codebase-brain-mcp"] }
172
+ Optional: install the [skill](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/skills/codebase-brain/SKILL.md).
173
+ It tells the agent which tool fits which question, to cite every claim, to check history before removing odd code,
174
+ and to run impact analysis before editing. For Claude Code, run the commands below. Other agents that read
175
+ `SKILL.md` files take the same file in their own skills folder.
176
+
177
+ ```bash
178
+ mkdir -p ~/.claude/skills/codebase-brain
179
+ curl -fsSL https://raw.githubusercontent.com/Nomannazir/codebase-brain-mcp/main/skills/codebase-brain/SKILL.md \
180
+ -o ~/.claude/skills/codebase-brain/SKILL.md
123
181
  ```
124
182
 
125
183
  ## Quick start
@@ -148,7 +206,7 @@ src/itsdangerous/timed.py:72-158
148
206
  ...
149
207
  ```
150
208
 
151
- ### Use it from Claude Code
209
+ ### Use a local checkout instead (for development)
152
210
 
153
211
  ```bash
154
212
  claude mcp add codebase-brain -- uv run --directory /path/to/codebase-brain-mcp codebase-brain
@@ -209,10 +267,14 @@ because they're supposed to mention removed things.
209
267
  ## Evaluation
210
268
 
211
269
  Does search actually put the right code in front of the model? `codebase-brain eval` answers that with a benchmark.
212
- The benchmark has 32 questions about [pallets/itsdangerous](https://github.com/pallets/itsdangerous), pinned to
213
- commit `672971d`, each with the code or docs a good answer must come from. The questions are in
214
- [`evals/datasets/itsdangerous.toml`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/datasets/itsdangerous.toml). They were written by reading the code
215
- before any results were measured, and haven't been changed since.
270
+ There are two, each with 32 questions pinned to one commit, and each question lists the code or docs a good answer
271
+ must come from. The questions were written by reading the code before any results were measured, and haven't been
272
+ changed since.
273
+
274
+ ### Python: pallets/itsdangerous
275
+
276
+ 32 questions about [pallets/itsdangerous](https://github.com/pallets/itsdangerous) at commit `672971d`, in
277
+ [`evals/datasets/itsdangerous.toml`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/datasets/itsdangerous.toml).
216
278
 
217
279
  | Mode | Hit@1 | Hit@3 | Hit@5 | Hit@10 | MRR@10 (95% CI) | nDCG@10 | p50 latency |
218
280
  |---|---|---|---|---|---|---|---|
@@ -236,10 +298,40 @@ before any results were measured, and haven't been changed since.
236
298
  the top 5. They're listed in [`evals/results/itsdangerous.md`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/results/itsdangerous.md) and are the next
237
299
  thing to improve. A larger, held-out question set will come first, so any tuning isn't fitted to these 32.
238
300
 
239
- Reproduce it (the first run downloads the repo and the embedding model):
301
+ ### TypeScript: sindresorhus/ky
302
+
303
+ 32 questions about [sindresorhus/ky](https://github.com/sindresorhus/ky) at commit `3541888`, in
304
+ [`evals/datasets/ky.toml`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/datasets/ky.toml).
305
+ The readme is one 2,000-line file, so docs questions must find the right *section*, not just the file.
306
+
307
+ | Mode | Hit@1 | Hit@3 | Hit@5 | Hit@10 | MRR@10 (95% CI) | nDCG@10 | p50 latency |
308
+ |---|---|---|---|---|---|---|---|
309
+ | **hybrid** (default) | 47% | 66% | 69% | 78% | 0.563 (0.41–0.71) | 0.576 | 6 ms |
310
+ | keyword only (BM25) | 38% | 44% | 53% | 62% | 0.436 (0.29–0.59) | 0.455 | 1 ms |
311
+ | vector only (bge-small) | 47% | **69%** | **75%** | 78% | **0.594** (0.46–0.74) | **0.604** | 5 ms |
312
+
313
+ | Hit@5 by question type | describes behaviour (14) | about the docs (7) | names the code (11) |
314
+ |---|---|---|---|
315
+ | hybrid | 36% | **100%** | 91% |
316
+ | keyword only | 7% | 86% | 91% |
317
+ | vector only | **43%** | **100%** | **100%** |
318
+
319
+ **What this shows:**
320
+ - **Naming the code or asking about the docs works well:** 91–100% in the top 5.
321
+ - **Describing behaviour in plain words is the weak spot (36%).** ky has about 25,000 lines of tests next to
322
+ 5,000 lines of source, and the tests describe the same behaviour in plain words, so they outrank the source.
323
+ Every miss is listed in [`evals/results/ky.md`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/results/ky.md).
324
+ - **Hybrid is not better than vector-only here.** The intervals overlap heavily (hybrid beats vector on 5
325
+ questions and loses on 9), so neither is proven better. Keyword-only is clearly worst: hybrid beats it on 12
326
+ questions and loses on none.
327
+ - These results are published as measured. Tuning (for example, ranking source above tests) will be checked
328
+ against a new held-out set, so it isn't fitted to these questions.
329
+
330
+ Reproduce either benchmark (the first run downloads the repo and the embedding model):
240
331
 
241
332
  ```bash
242
333
  uv run codebase-brain eval evals/datasets/itsdangerous.toml --out evals/results
334
+ uv run codebase-brain eval evals/datasets/ky.toml --out evals/results
243
335
  ```
244
336
 
245
337
  How it's measured:
@@ -248,14 +340,15 @@ How it's measured:
248
340
  - **nDCG@10:** each target is credited once, so duplicate chunks don't inflate the score.
249
341
  - **Confidence intervals:** 95% bootstrap over questions, seeded.
250
342
  - **Relevance:** a result is relevant if it's in the target file and, when the target names a symbol, is that symbol
251
- or one of its methods.
343
+ or one of its methods. A docs target can name a section (`FAQ > How do I stop retrying early?`), and then that
344
+ section or one of its subsections counts.
252
345
  - **Dataset rot:** the harness fails if a target no longer exists at the pinned commit.
253
346
 
254
347
  ## How it works
255
348
 
256
349
  ```
257
- source ─► resolve (local dir / shallow https clone) ─► list files (.gitignore-aware, skips binaries and lockfiles)
258
- ─► chunk (ast / headings / line windows) ─► embed (fastembed) ─► SQLite: chunks + vectors + FTS5
350
+ source ─► resolve (local dir / blobless https clone) ─► list files (.gitignore-aware, skips binaries and lockfiles)
351
+ ─► chunk (ast / tree-sitter / headings / windows) ─► embed (fastembed) ─► SQLite: chunks + vectors + FTS5
259
352
  query ─► BM25 top 50 ┐
260
353
  ─► vector top 50 ┴─► reciprocal-rank fusion ─► filters ─► cited sources
261
354
  ```
@@ -274,7 +367,7 @@ query ─► BM25 top 50 ┐
274
367
  ## Development
275
368
 
276
369
  ```bash
277
- uv run pytest # 54 tests, offline (hash embedder, temporary git repos, in-process MCP client)
370
+ uv run pytest # 67 tests, offline (hash embedder, temporary git repos, in-process MCP client)
278
371
  uv run ruff check . # lint
279
372
  uv run ruff format . # format
280
373
  uv run pyright # types
@@ -285,9 +378,11 @@ uv run pyright # types
285
378
  - [x] Git history and PR discussions: `explain_history`, `search_history`
286
379
  - [x] Symbol references and transitive dependents: `impact_of_change`
287
380
  - [x] `find_doc_drift`: Markdown and reStructuredText, signatures, imports, settings, flags, staleness
288
- - [ ] tree-sitter chunking for more languages
381
+ - [x] JavaScript and TypeScript (tree-sitter): chunking, references, impact analysis
382
+ - [ ] More languages (Go, Rust, Java) with tree-sitter
289
383
  - [x] Retrieval evals with confidence intervals (`codebase-brain eval`)
290
384
  - [ ] Larger held-out eval set, then retrieval tuning (reranking, query expansion)
291
385
  - [x] Streamable HTTP with bearer-token auth, Docker image
292
- - [ ] Hosted demo, PyPI package, MCP Registry listing
386
+ - [x] PyPI package, MCP Registry listing
387
+ - [ ] Hosted demo
293
388
  - [ ] OpenTelemetry tracing
@@ -47,8 +47,10 @@ A plug-in for any AI assistant that turns a codebase into something it can actua
47
47
 
48
48
  ## Why it's different (technical)
49
49
 
50
- - **Code-aware chunking.** Python is split by function, class and method using the real syntax tree, Markdown by
51
- heading. Each chunk knows its symbol (`TimestampSigner.unsign`) and exact lines, so citations point at real code.
50
+ - **Code-aware chunking.** Python (with `ast`) and JavaScript / TypeScript / JSX / TSX (with tree-sitter) are split
51
+ by function, class, method, arrow function, interface, type and enum, with their doc comments. Markdown is split
52
+ by heading. Each chunk knows its symbol (`TimestampSigner.unsign`, `Ky.#retry`) and exact lines, so citations
53
+ point at real code. Other languages are indexed in overlapping line windows.
52
54
  - **Hybrid retrieval.** SQLite FTS5 BM25 (with `camelCase` / `snake_case` identifier splitting) plus local
53
55
  embeddings, merged with reciprocal-rank fusion. Exact names and fuzzy questions both work.
54
56
  - **Local and free.** Embeddings run on your machine (`fastembed`, ONNX). No API keys, and no code leaves your
@@ -59,7 +61,8 @@ A plug-in for any AI assistant that turns a codebase into something it can actua
59
61
  the current lines, and links the pull requests behind each change. So agents can answer *"why is it built this
60
62
  way?"*, not just *"what does it do?"*.
61
63
  - **Impact analysis.** `impact_of_change` lists everything that uses a function, directly and transitively, plus the
62
- tests that exercise it, before an agent changes anything.
64
+ tests that exercise it, before an agent changes anything. It works across Python, JavaScript and TypeScript files,
65
+ and follows a constructor to the code that builds its class.
63
66
  - **Doc-drift detection.** `find_doc_drift` checks Markdown and reStructuredText docs against the code. It finds
64
67
  files, functions, settings and flags that don't exist; examples passing parameters a function doesn't take; imports
65
68
  that would fail; and sections last edited long before the code they describe changed. Each finding cites the doc
@@ -71,7 +74,7 @@ A plug-in for any AI assistant that turns a codebase into something it can actua
71
74
 
72
75
  | Tool | What it does |
73
76
  |---|---|
74
- | `index_repo(source, name?)` | Index a local path or an `https://` git URL (shallow clone). Reports progress. |
77
+ | `index_repo(source, name?)` | Index a local path or an `https://` git URL (blobless clone with full history). Reports progress. |
75
78
  | `ask_codebase(question, repo?, k?)` | The most relevant code and docs as cited sources, plus related commits, with instructions to answer only from them. |
76
79
  | `search(query, repo?, k?, path_prefix?, kinds?)` | Raw hybrid search, filterable by path and chunk kind. |
77
80
  | `explain_history(symbol? \| path, start_line?, end_line?, repo?)` | Every commit that changed a function or line range, newest first, with the relevant diff, current authorship, and PR titles and descriptions (GitHub). |
@@ -85,15 +88,67 @@ Also included: the resource `brain://repos`, and the prompt `answer_with_citatio
85
88
 
86
89
  ## Install
87
90
 
88
- Codebase Brain runs on your own machine, so there's no server or account, and it's free. With
89
- [uv](https://docs.astral.sh/uv/):
91
+ Codebase Brain runs on your own machine, so there's no server or account, and it's free. It works with any
92
+ MCP-compatible assistant. All you need is [uv](https://docs.astral.sh/uv/), and every client runs the same command:
93
+ `uvx codebase-brain-mcp`.
94
+
95
+ **Claude Code**
90
96
 
91
97
  ```bash
92
- # Claude Code
93
98
  claude mcp add codebase-brain -- uvx codebase-brain-mcp
99
+ ```
100
+
101
+ **Cursor:** `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project)
102
+
103
+ ```json
104
+ {
105
+ "mcpServers": {
106
+ "codebase-brain": { "command": "uvx", "args": ["codebase-brain-mcp"] }
107
+ }
108
+ }
109
+ ```
110
+
111
+ **Claude Desktop:** Settings → Developer → Edit Config (`claude_desktop_config.json`), then restart the app
112
+
113
+ ```json
114
+ {
115
+ "mcpServers": {
116
+ "codebase-brain": { "command": "uvx", "args": ["codebase-brain-mcp"] }
117
+ }
118
+ }
119
+ ```
120
+
121
+ **VS Code (GitHub Copilot agent mode):** `.vscode/mcp.json`
122
+
123
+ ```json
124
+ {
125
+ "servers": {
126
+ "codebase-brain": { "type": "stdio", "command": "uvx", "args": ["codebase-brain-mcp"] }
127
+ }
128
+ }
129
+ ```
130
+
131
+ **Windsurf:** `~/.codeium/windsurf/mcp_config.json`. **Gemini CLI:** `~/.gemini/settings.json`. Both use the same
132
+ `mcpServers` block as Cursor.
133
+
134
+ **OpenAI Codex CLI**
135
+
136
+ ```bash
137
+ codex mcp add codebase-brain -- uvx codebase-brain-mcp
138
+ ```
139
+
140
+ > **Tool doesn't start?** Desktop apps often don't see your shell's `PATH`. Run `which uvx` and put that full
141
+ > path in `"command"`, for example `"/Users/you/.local/bin/uvx"`.
94
142
 
95
- # Any MCP client (Claude Desktop, Cursor, ...): add this server to its config
96
- { "command": "uvx", "args": ["codebase-brain-mcp"] }
143
+ Optional: install the [skill](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/skills/codebase-brain/SKILL.md).
144
+ It tells the agent which tool fits which question, to cite every claim, to check history before removing odd code,
145
+ and to run impact analysis before editing. For Claude Code, run the commands below. Other agents that read
146
+ `SKILL.md` files take the same file in their own skills folder.
147
+
148
+ ```bash
149
+ mkdir -p ~/.claude/skills/codebase-brain
150
+ curl -fsSL https://raw.githubusercontent.com/Nomannazir/codebase-brain-mcp/main/skills/codebase-brain/SKILL.md \
151
+ -o ~/.claude/skills/codebase-brain/SKILL.md
97
152
  ```
98
153
 
99
154
  ## Quick start
@@ -122,7 +177,7 @@ src/itsdangerous/timed.py:72-158
122
177
  ...
123
178
  ```
124
179
 
125
- ### Use it from Claude Code
180
+ ### Use a local checkout instead (for development)
126
181
 
127
182
  ```bash
128
183
  claude mcp add codebase-brain -- uv run --directory /path/to/codebase-brain-mcp codebase-brain
@@ -183,10 +238,14 @@ because they're supposed to mention removed things.
183
238
  ## Evaluation
184
239
 
185
240
  Does search actually put the right code in front of the model? `codebase-brain eval` answers that with a benchmark.
186
- The benchmark has 32 questions about [pallets/itsdangerous](https://github.com/pallets/itsdangerous), pinned to
187
- commit `672971d`, each with the code or docs a good answer must come from. The questions are in
188
- [`evals/datasets/itsdangerous.toml`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/datasets/itsdangerous.toml). They were written by reading the code
189
- before any results were measured, and haven't been changed since.
241
+ There are two, each with 32 questions pinned to one commit, and each question lists the code or docs a good answer
242
+ must come from. The questions were written by reading the code before any results were measured, and haven't been
243
+ changed since.
244
+
245
+ ### Python: pallets/itsdangerous
246
+
247
+ 32 questions about [pallets/itsdangerous](https://github.com/pallets/itsdangerous) at commit `672971d`, in
248
+ [`evals/datasets/itsdangerous.toml`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/datasets/itsdangerous.toml).
190
249
 
191
250
  | Mode | Hit@1 | Hit@3 | Hit@5 | Hit@10 | MRR@10 (95% CI) | nDCG@10 | p50 latency |
192
251
  |---|---|---|---|---|---|---|---|
@@ -210,10 +269,40 @@ before any results were measured, and haven't been changed since.
210
269
  the top 5. They're listed in [`evals/results/itsdangerous.md`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/results/itsdangerous.md) and are the next
211
270
  thing to improve. A larger, held-out question set will come first, so any tuning isn't fitted to these 32.
212
271
 
213
- Reproduce it (the first run downloads the repo and the embedding model):
272
+ ### TypeScript: sindresorhus/ky
273
+
274
+ 32 questions about [sindresorhus/ky](https://github.com/sindresorhus/ky) at commit `3541888`, in
275
+ [`evals/datasets/ky.toml`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/datasets/ky.toml).
276
+ The readme is one 2,000-line file, so docs questions must find the right *section*, not just the file.
277
+
278
+ | Mode | Hit@1 | Hit@3 | Hit@5 | Hit@10 | MRR@10 (95% CI) | nDCG@10 | p50 latency |
279
+ |---|---|---|---|---|---|---|---|
280
+ | **hybrid** (default) | 47% | 66% | 69% | 78% | 0.563 (0.41–0.71) | 0.576 | 6 ms |
281
+ | keyword only (BM25) | 38% | 44% | 53% | 62% | 0.436 (0.29–0.59) | 0.455 | 1 ms |
282
+ | vector only (bge-small) | 47% | **69%** | **75%** | 78% | **0.594** (0.46–0.74) | **0.604** | 5 ms |
283
+
284
+ | Hit@5 by question type | describes behaviour (14) | about the docs (7) | names the code (11) |
285
+ |---|---|---|---|
286
+ | hybrid | 36% | **100%** | 91% |
287
+ | keyword only | 7% | 86% | 91% |
288
+ | vector only | **43%** | **100%** | **100%** |
289
+
290
+ **What this shows:**
291
+ - **Naming the code or asking about the docs works well:** 91–100% in the top 5.
292
+ - **Describing behaviour in plain words is the weak spot (36%).** ky has about 25,000 lines of tests next to
293
+ 5,000 lines of source, and the tests describe the same behaviour in plain words, so they outrank the source.
294
+ Every miss is listed in [`evals/results/ky.md`](https://github.com/Nomannazir/codebase-brain-mcp/blob/main/evals/results/ky.md).
295
+ - **Hybrid is not better than vector-only here.** The intervals overlap heavily (hybrid beats vector on 5
296
+ questions and loses on 9), so neither is proven better. Keyword-only is clearly worst: hybrid beats it on 12
297
+ questions and loses on none.
298
+ - These results are published as measured. Tuning (for example, ranking source above tests) will be checked
299
+ against a new held-out set, so it isn't fitted to these questions.
300
+
301
+ Reproduce either benchmark (the first run downloads the repo and the embedding model):
214
302
 
215
303
  ```bash
216
304
  uv run codebase-brain eval evals/datasets/itsdangerous.toml --out evals/results
305
+ uv run codebase-brain eval evals/datasets/ky.toml --out evals/results
217
306
  ```
218
307
 
219
308
  How it's measured:
@@ -222,14 +311,15 @@ How it's measured:
222
311
  - **nDCG@10:** each target is credited once, so duplicate chunks don't inflate the score.
223
312
  - **Confidence intervals:** 95% bootstrap over questions, seeded.
224
313
  - **Relevance:** a result is relevant if it's in the target file and, when the target names a symbol, is that symbol
225
- or one of its methods.
314
+ or one of its methods. A docs target can name a section (`FAQ > How do I stop retrying early?`), and then that
315
+ section or one of its subsections counts.
226
316
  - **Dataset rot:** the harness fails if a target no longer exists at the pinned commit.
227
317
 
228
318
  ## How it works
229
319
 
230
320
  ```
231
- source ─► resolve (local dir / shallow https clone) ─► list files (.gitignore-aware, skips binaries and lockfiles)
232
- ─► chunk (ast / headings / line windows) ─► embed (fastembed) ─► SQLite: chunks + vectors + FTS5
321
+ source ─► resolve (local dir / blobless https clone) ─► list files (.gitignore-aware, skips binaries and lockfiles)
322
+ ─► chunk (ast / tree-sitter / headings / windows) ─► embed (fastembed) ─► SQLite: chunks + vectors + FTS5
233
323
  query ─► BM25 top 50 ┐
234
324
  ─► vector top 50 ┴─► reciprocal-rank fusion ─► filters ─► cited sources
235
325
  ```
@@ -248,7 +338,7 @@ query ─► BM25 top 50 ┐
248
338
  ## Development
249
339
 
250
340
  ```bash
251
- uv run pytest # 54 tests, offline (hash embedder, temporary git repos, in-process MCP client)
341
+ uv run pytest # 67 tests, offline (hash embedder, temporary git repos, in-process MCP client)
252
342
  uv run ruff check . # lint
253
343
  uv run ruff format . # format
254
344
  uv run pyright # types
@@ -259,9 +349,11 @@ uv run pyright # types
259
349
  - [x] Git history and PR discussions: `explain_history`, `search_history`
260
350
  - [x] Symbol references and transitive dependents: `impact_of_change`
261
351
  - [x] `find_doc_drift`: Markdown and reStructuredText, signatures, imports, settings, flags, staleness
262
- - [ ] tree-sitter chunking for more languages
352
+ - [x] JavaScript and TypeScript (tree-sitter): chunking, references, impact analysis
353
+ - [ ] More languages (Go, Rust, Java) with tree-sitter
263
354
  - [x] Retrieval evals with confidence intervals (`codebase-brain eval`)
264
355
  - [ ] Larger held-out eval set, then retrieval tuning (reranking, query expansion)
265
356
  - [x] Streamable HTTP with bearer-token auth, Docker image
266
- - [ ] Hosted demo, PyPI package, MCP Registry listing
357
+ - [x] PyPI package, MCP Registry listing
358
+ - [ ] Hosted demo
267
359
  - [ ] OpenTelemetry tracing
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codebase-brain-mcp"
3
- version = "0.1.0"
3
+ version = "0.2.1"
4
4
  description = "An MCP server that gives AI agents a cited, searchable memory of a codebase."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -29,6 +29,9 @@ dependencies = [
29
29
  "fastembed>=0.9.1",
30
30
  "mcp>=2.3.0",
31
31
  "numpy>=2.5.4",
32
+ "tree-sitter>=0.25.2,<0.26",
33
+ "tree-sitter-javascript>=0.25",
34
+ "tree-sitter-typescript>=0.23.2",
32
35
  ]
33
36
 
34
37
  [[project.authors]]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codebase-brain-mcp"
3
- version = "0.1.0"
3
+ version = "0.2.1"
4
4
  description = "An MCP server that gives AI agents a cited, searchable memory of a codebase."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -24,6 +24,9 @@ dependencies = [
24
24
  "fastembed>=0.9.1",
25
25
  "mcp>=2.3.0",
26
26
  "numpy>=2.5.4",
27
+ "tree-sitter>=0.25.2,<0.26", # 0.26.0 crashes the process (SIGBUS) while walking big files
28
+ "tree-sitter-javascript>=0.25",
29
+ "tree-sitter-typescript>=0.23.2",
27
30
  ]
28
31
 
29
32
  [project.urls]
@@ -13,10 +13,12 @@ from datetime import datetime, timedelta
13
13
  from pathlib import Path
14
14
 
15
15
  from . import history
16
+ from .chunking import DEFINITION_KINDS
16
17
  from .sources import SourceError
17
18
  from .store import Store, StoredChunk
18
19
 
19
20
  MAX_PER_LEVEL = 60
21
+ STRUCTURED_SUFFIXES = (".py", ".pyi", ".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx")
20
22
  STALE_AFTER = timedelta(days=180)
21
23
  MAX_STALENESS_CHECKS = 40
22
24
  _FENCE = re.compile(r"^(`{3,}|~{3,})\s*([\w+-]*)[^\n]*\n(.*?)^\1\s*$", re.MULTILINE | re.DOTALL)
@@ -31,7 +33,7 @@ _PATH = re.compile(r"^\.?/?[\w.-]+(/[\w.-]+)+/?$")
31
33
  _ENV = re.compile(r"^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$")
32
34
  _FLAG = re.compile(r"^(--[a-z][a-z0-9-]+)(=\S*)?$")
33
35
  _SYMBOL = re.compile(r"^[A-Za-z_]\w*(\.[A-Za-z_]\w*)*(\(\))?$")
34
- _TEST_PATH = re.compile(r"(^|/)(tests?|testing)/|(^|/)test_[^/]+$|_test\.\w+$")
36
+ _TEST_PATH = re.compile(r"(^|/)(tests?|testing|__tests__)/|(^|/)test_[^/]+$|_test\.\w+$|\.(test|spec)\.[jt]sx?$")
35
37
  _COMMON = set(dir(builtins)) | set(keyword.kwlist) | {"true", "false", "null", "none", "self", "cls"}
36
38
  _SEVERITY_ORDER = {"high": 0, "medium": 1, "low": 2}
37
39
  _CHANGELOG = re.compile(
@@ -57,6 +59,8 @@ def definition_span(matches: list[StoredChunk]) -> tuple[str, int, int, str | No
57
59
 
58
60
  # ---- impact_of_change -------------------------------------------------------------------------
59
61
 
62
+ _CONSTRUCTORS = {"__init__", "__new__", "constructor"}
63
+
60
64
 
61
65
  @dataclass(frozen=True)
62
66
  class Dependent:
@@ -79,13 +83,21 @@ class ImpactReport:
79
83
  notes: list[str] = field(default_factory=list)
80
84
 
81
85
 
86
+ def _used_as(symbol: str) -> str:
87
+ """The name other code uses to reach ``symbol``: a constructor is reached through its class."""
88
+ owner, _, short = symbol.rpartition(".")
89
+ if short in _CONSTRUCTORS and owner:
90
+ return owner.rsplit(".", 1)[-1]
91
+ return short
92
+
93
+
82
94
  def impact_of_change(store: Store, target: StoredChunk, span: tuple[int, int], depth: int = 2) -> ImpactReport:
83
95
  """Everything that uses ``target`` by name, directly and (up to ``depth``) transitively."""
84
96
  symbol = target.symbol or ""
85
97
  short = symbol.rsplit(".", 1)[-1]
86
98
  excluded = {c.id for c in store.find_symbol(symbol, target.path)}
87
99
  dependents: list[Dependent] = []
88
- frontier = [short]
100
+ frontier = [_used_as(symbol)]
89
101
  for level in range(1, max(1, min(depth, 3)) + 1):
90
102
  next_frontier: list[str] = []
91
103
  for name in frontier:
@@ -97,8 +109,8 @@ def impact_of_change(store: Store, target: StoredChunk, span: tuple[int, int], d
97
109
  dependents.append(
98
110
  Dependent(chunk=chunk, via=name, level=level, is_test=bool(_TEST_PATH.search(chunk.path)))
99
111
  )
100
- if chunk.symbol and chunk.kind in ("function", "method", "class"):
101
- next_frontier.append(chunk.symbol.rsplit(".", 1)[-1])
112
+ if chunk.symbol and chunk.kind in DEFINITION_KINDS:
113
+ next_frontier.append(_used_as(chunk.symbol))
102
114
  frontier = list(dict.fromkeys(next_frontier))
103
115
  if not frontier:
104
116
  break
@@ -114,8 +126,8 @@ def impact_of_change(store: Store, target: StoredChunk, span: tuple[int, int], d
114
126
  f"{len(twins)} other definition(s) share the name {short!r}; some dependents may use those "
115
127
  "instead. Check the import or receiver before changing anything."
116
128
  )
117
- if not target.path.endswith((".py", ".pyi")):
118
- notes.append("Only Python is analysed structurally; for other languages, also run search for the name.")
129
+ if not target.path.endswith(STRUCTURED_SUFFIXES):
130
+ notes.append("Only Python, JS and TS are analysed structurally; for other languages, also search the name.")
119
131
  notes.append("Found by name, so dynamic calls (getattr, string dispatch, callbacks) aren't included.")
120
132
  return ImpactReport(
121
133
  symbol=symbol,