Skip to content

Commit 52d5ddd

Browse files
committed
Release v0.3.1: detect_changes, risk scoring, editor support, SDK upgrade
- Add detect_changes tool: map git diff to affected symbols + blast radius with risk classification (unstaged/staged/all/branch scopes) - Add risk_labels param to trace_call_path: depth-based impact classification (CRITICAL/HIGH/MEDIUM/LOW) with impact_summary counts - Add Cursor + Windsurf MCP config auto-registration (install/uninstall) - Upgrade go-sdk v1.3.1 → v1.4.0 (security fixes, spec compliance) - Improve tool descriptions with inline regex examples for AI agents - Update skills documentation and README
1 parent b5fb8da commit 52d5ddd

17 files changed

Lines changed: 1603 additions & 58 deletions

File tree

README.md

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44

55
Single Go binary. No Docker, no external databases, no API keys. One command to install, say *"Index this project"* — done.
66

7-
Parses source code with [tree-sitter](https://tree-sitter.github.io/tree-sitter/), extracts functions, classes, modules, call relationships, and cross-service HTTP links. Exposes the graph through 12 MCP tools for use with Claude Code, Codex CLI, or any MCP-compatible client. Also includes a **CLI mode** for direct tool invocation from the shell — no MCP client needed.
7+
Parses source code with [tree-sitter](https://tree-sitter.github.io/tree-sitter/), extracts functions, classes, modules, call relationships, and cross-service HTTP links. Exposes the graph through 14 MCP tools for use with Claude Code, Codex CLI, Cursor, Windsurf, or any MCP-compatible client. Also includes a **CLI mode** for direct tool invocation from the shell — no MCP client needed.
88

99
## Features
1010

1111
- **35 languages**: Python, Go, JavaScript, TypeScript, TSX, Rust, Java, C++, C#, C, PHP, Lua, Scala, Kotlin, Ruby, Bash, Zig, Elixir, Haskell, OCaml, Objective-C, Swift, Dart, Perl, Groovy, Erlang, R, HTML, CSS, SCSS, YAML, TOML, HCL, SQL, Dockerfile
12-
- **One-command install**: `codebase-memory-mcp install` auto-detects Claude Code and Codex CLI, registers the MCP server, and installs task-specific skills
12+
- **Git diff impact mapping**: `detect_changes` maps uncommitted changes to affected graph symbols + blast radius with risk classification (CRITICAL/HIGH/MEDIUM/LOW)
13+
- **Risk-classified tracing**: `trace_call_path` with `risk_labels=true` adds impact classification to every node in the call chain
14+
- **One-command install**: `codebase-memory-mcp install` auto-detects Claude Code, Codex CLI, Cursor, and Windsurf, registers the MCP server, and installs task-specific skills
1315
- **Self-update**: `codebase-memory-mcp update` downloads the latest release, verifies checksums, and atomically swaps the binary
1416
- **Task-specific skills**: 4 skills (exploring, tracing, quality, reference) that prescribe exact tool sequences — Claude Code automatically uses graph tools instead of defaulting to grep
1517
- **Fast**: Sub-millisecond graph queries, incremental reindex 4x faster than full scan, optimized SQLite with LIKE pre-filtering for regex searches
@@ -84,7 +86,7 @@ Benchmarked on Apple M3 Pro, macOS Darwin 25.3.0:
8486
4. **Restart** Claude Code / Codex CLI
8587
5. Say **"Index this project"** — done.
8688

87-
The `install` command auto-detects Claude Code and Codex CLI, registers the MCP server, installs 4 task-specific skills, and ensures the binary is on your PATH. Use `--dry-run` to preview without making changes.
89+
The `install` command auto-detects Claude Code, Codex CLI, Cursor, and Windsurf, registers the MCP server, installs 4 task-specific skills, and ensures the binary is on your PATH. Use `--dry-run` to preview without making changes.
8890

8991
### Keeping Up to Date
9092

@@ -237,7 +239,7 @@ Add the MCP server to your project's `.mcp.json` (per-project, recommended) or `
237239
}
238240
```
239241

240-
Restart Claude Code after adding the config. Verify with `/mcp` — you should see `codebase-memory-mcp` listed with 12 tools.
242+
Restart Claude Code after adding the config. Verify with `/mcp` — you should see `codebase-memory-mcp` listed with 14 tools.
241243

242244
</details>
243245

@@ -330,7 +332,8 @@ The CLI uses the same SQLite database as the MCP server (`~/.cache/codebase-memo
330332
| Tool | Key Parameters | Description |
331333
|------|---------------|-------------|
332334
| `search_graph` | `label`, `name_pattern`, `project`, `file_pattern`, `relationship`, `direction`, `min_degree`, `max_degree`, `exclude_entry_points`, `limit` (default 100), `offset` | Structured search with filters. Use `project` to scope to a single repo when multiple are indexed. Supports pagination via `limit`/`offset` — response includes `has_more` and `total`. |
333-
| `trace_call_path` | `function_name` (required), `direction` (inbound/outbound/both), `depth` (1-5, default 3) | BFS traversal from/to a function (exact name match). Returns call chains with signatures, constants, and edge types. Capped at 200 nodes. |
335+
| `trace_call_path` | `function_name` (required), `direction` (inbound/outbound/both), `depth` (1-5, default 3), `risk_labels` (boolean) | BFS traversal from/to a function (exact name match). Returns call chains with signatures, constants, and edge types. Capped at 200 nodes. With `risk_labels=true`, adds CRITICAL/HIGH/MEDIUM/LOW classification and `impact_summary`. |
336+
| `detect_changes` | `scope` (unstaged/staged/all/branch), `base_branch`, `depth` (1-5, default 3) | Map git diff to affected graph symbols + blast radius. Returns changed files, changed symbols, and impacted callers with risk classification. Requires git in PATH. |
334337
| `query_graph` | `query` (required) | Execute Cypher-like graph queries (read-only). See [Supported Cypher Subset](#supported-cypher-subset) for what's supported. |
335338
| `get_graph_schema` || Node/edge counts, relationship patterns, sample names. Run this first to understand what's in the graph. |
336339
| `get_code_snippet` | `qualified_name` (required) | Read source code for a function by its qualified name (reads from disk). See [Qualified Names](#qualified-names) for the format. |
@@ -371,6 +374,20 @@ trace_call_path(function_name="ProcessOrder", depth=3, direction="outbound")
371374
trace_call_path(function_name="ProcessOrder", depth=2, direction="inbound")
372375
```
373376

377+
### Risk-classified impact analysis
378+
379+
```
380+
trace_call_path(function_name="ProcessOrder", direction="inbound", depth=3, risk_labels=true)
381+
```
382+
383+
### Detect changes (git diff impact)
384+
385+
```
386+
detect_changes()
387+
detect_changes(scope="staged")
388+
detect_changes(scope="branch", base_branch="main", depth=3)
389+
```
390+
374391
### Dead code detection
375392

376393
```
@@ -577,6 +594,7 @@ make install # go install
577594
| `trace_call_path` returns 0 results | Exact name match — no fuzzy matching | Use `search_graph(name_pattern=".*PartialName.*")` to discover the exact function name first. |
578595
| Queries return results from wrong project | Multiple projects indexed, no filter | Add `project="your-project-name"` to `search_graph`. Use `list_projects` to see indexed project names. |
579596
| Graph is missing recently added files | Auto-sync hasn't caught up yet, or project was never indexed | Wait a few seconds for auto-sync, or run `index_repository` manually. Auto-sync polls at 1–60s intervals depending on repo size. |
597+
| `detect_changes` fails with "git not found" | git not installed or not on PATH | Install git. Required at runtime only for `detect_changes`. |
580598
| Binary not found after install | `~/.local/bin` not on PATH | Add to your shell profile: `export PATH="$HOME/.local/bin:$PATH"` |
581599
| Cypher query fails with parse error | Unsupported Cypher feature | See [Supported Cypher Subset](#supported-cypher-subset). `WITH`, `COLLECT`, `OPTIONAL MATCH` are not supported. |
582600

@@ -606,7 +624,7 @@ internal/
606624
httplink/ Cross-service HTTP route/call-site matching
607625
cypher/ Cypher query lexer, parser, planner, executor
608626
selfupdate/ GitHub release checking, version comparison, asset download
609-
tools/ MCP tool handlers (12 tools) + CLI dispatch
627+
tools/ MCP tool handlers (14 tools) + CLI dispatch
610628
watcher/ Background auto-sync (mtime+size polling, adaptive intervals)
611629
discover/ File discovery with .cgrignore support
612630
fqn/ Qualified name computation

cmd/codebase-memory-mcp/assets/skills/codebase-memory-reference/SKILL.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ description: >
99

1010
# Codebase Memory MCP — Tool Reference
1111

12-
## Tools (12 total)
12+
## Tools (14 total)
1313

1414
| Tool | Purpose |
1515
|------|---------|
@@ -19,12 +19,14 @@ description: >
1919
| `delete_project` | Remove a project from the graph |
2020
| `search_graph` | Structured search with filters (name, label, degree, file pattern) |
2121
| `search_code` | Grep-like text search within indexed project files |
22-
| `trace_call_path` | BFS call chain traversal (exact name match required) |
22+
| `trace_call_path` | BFS call chain traversal (exact name match required). Supports `risk_labels=true` for impact classification. |
23+
| `detect_changes` | Map git diff to affected symbols + blast radius with risk scoring |
2324
| `query_graph` | Cypher-like graph queries (200-row cap) |
2425
| `get_graph_schema` | Node/edge counts, relationship patterns |
2526
| `get_code_snippet` | Read source code by qualified name |
2627
| `read_file` | Read any file from indexed project |
2728
| `list_directory` | List files/directories with glob filter |
29+
| `ingest_traces` | Ingest OpenTelemetry traces to validate HTTP_CALLS edges |
2830

2931
## Edge Types
3032

@@ -90,6 +92,46 @@ MATCH (a)-[r:FILE_CHANGES_WITH]->(b) WHERE r.coupling_score >= 0.5 RETURN a.name
9092
MATCH (f:Function)-[:CALLS]->(g:Function) WHERE g.name = 'ProcessOrder' RETURN f.name LIMIT 20
9193
```
9294

95+
## Regex-Powered Search (No Full-Text Index Needed)
96+
97+
`search_graph` and `search_code` support full Go regex, making full-text search indexes unnecessary. Regex patterns provide precise, composable queries that cover all common discovery scenarios:
98+
99+
### search_graph — name_pattern / qn_pattern
100+
101+
| Pattern | Matches | Use case |
102+
|---------|---------|----------|
103+
| `.*Handler$` | names ending in Handler | Find all handlers |
104+
| `(?i)auth` | case-insensitive "auth" | Find auth-related symbols |
105+
| `get\|fetch\|load` | any of three words | Find data-loading functions |
106+
| `^on[A-Z]` | names starting with on + uppercase | Find event handlers |
107+
| `.*Service.*Impl` | Service...Impl pattern | Find service implementations |
108+
| `^(Get\|Set\|Delete)` | CRUD prefixes | Find CRUD operations |
109+
| `.*_test$` | names ending in _test | Find test functions |
110+
| `.*\\.controllers\\..*` | qn_pattern for directory scoping | Scope to controllers dir |
111+
112+
### search_code — regex=true
113+
114+
| Pattern | Matches | Use case |
115+
|---------|---------|----------|
116+
| `TODO\|FIXME\|HACK` | multi-pattern scan | Find tech debt markers |
117+
| `(?i)password\|secret\|token` | case-insensitive secrets | Security scan |
118+
| `func\\s+Test` | Go test functions | Find test entry points |
119+
| `api[._/]v[0-9]` | API version references | Find versioned API usage |
120+
| `import.*from ['"]@` | scoped npm imports | Find package imports |
121+
122+
### Combining Filters for Surgical Queries
123+
124+
```
125+
# Find unused auth handlers
126+
search_graph(name_pattern="(?i).*auth.*handler.*", max_degree=0, exclude_entry_points=true)
127+
128+
# Find high fan-out functions in the services directory
129+
search_graph(qn_pattern=".*\\.services\\..*", min_degree=10, relationship="CALLS", direction="outbound")
130+
131+
# Find all route handlers matching a URL pattern
132+
search_code(pattern="(?i)(POST|PUT).*\\/api\\/v[0-9]\\/orders", regex=true)
133+
```
134+
93135
## Critical Pitfalls
94136

95137
1. **`search_graph(relationship="HTTP_CALLS")` does NOT return edges** — it filters nodes by degree. Use `query_graph` with Cypher to see actual edges.
@@ -107,4 +149,6 @@ MATCH (f:Function)-[:CALLS]->(g:Function) WHERE g.name = 'ProcessOrder' RETURN f
107149
| Find by name pattern | `search_graph(name_pattern="...")` |
108150
| Dead code | `search_graph(max_degree=0, exclude_entry_points=true)` |
109151
| Cross-service edges | `query_graph` with Cypher |
152+
| Impact of local changes | `detect_changes()` |
153+
| Risk-classified trace | `trace_call_path(risk_labels=true)` |
110154
| Text search | `search_code` or Grep |

cmd/codebase-memory-mcp/assets/skills/codebase-memory-tracing/SKILL.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,18 @@ Use graph tools to trace function call relationships. One `trace_call_path` call
1616

1717
### Step 1: Discover the exact function name
1818

19-
`trace_call_path` requires an **exact** name match. If you don't know the exact name, discover it first:
19+
`trace_call_path` requires an **exact** name match. If you don't know the exact name, discover it first with regex:
2020

2121
```
2222
search_graph(name_pattern=".*Order.*", label="Function")
2323
```
2424

25+
Use full regex for precise discovery — no full-text search needed:
26+
- `(?i)order` — case-insensitive
27+
- `^(Get|Set|Delete)Order` — CRUD variants
28+
- `.*Order.*Handler$` — handlers only
29+
- `qn_pattern=".*services\\.order\\..*"` — scope to order service directory
30+
2531
This returns matching functions with their qualified names and file locations.
2632

2733
### Step 2: Trace callers (who calls this function?)
@@ -88,9 +94,32 @@ query_graph(query="MATCH (s)-[r:OVERRIDE]->(i) WHERE i.name = 'Read' RETURN s.na
8894
query_graph(query="MATCH (a)-[r:USAGE]->(b) WHERE b.name = 'ProcessOrder' RETURN a.name, a.file_path LIMIT 20")
8995
```
9096

97+
## Risk-Classified Impact Analysis
98+
99+
Add `risk_labels=true` to get risk classification on each node:
100+
101+
```
102+
trace_call_path(function_name="ProcessOrder", direction="inbound", depth=3, risk_labels=true)
103+
```
104+
105+
Returns nodes with `risk` (CRITICAL/HIGH/MEDIUM/LOW) based on hop depth, plus an `impact_summary` with counts. Risk mapping: hop 1=CRITICAL, 2=HIGH, 3=MEDIUM, 4+=LOW.
106+
107+
## Detect Changes (Git Diff Impact)
108+
109+
Map uncommitted changes to affected symbols and their blast radius:
110+
111+
```
112+
detect_changes()
113+
detect_changes(scope="staged")
114+
detect_changes(scope="branch", base_branch="main")
115+
```
116+
117+
Returns changed files, changed symbols, and impacted callers with risk classification. Scopes: `unstaged`, `staged`, `all` (default), `branch`.
118+
91119
## Key Tips
92120

93121
- Start with `depth=1` for quick answers, increase only if needed (max 5).
94122
- Edge types in trace results: `CALLS` (direct), `HTTP_CALLS` (cross-service), `ASYNC_CALLS` (async dispatch), `USAGE` (read reference), `OVERRIDE` (interface implementation).
95123
- `search_graph(relationship="HTTP_CALLS")` filters nodes by degree — it does NOT return edges. Use `query_graph` with Cypher to see actual edges with properties.
96124
- Results are capped at 200 nodes per trace.
125+
- `detect_changes` requires git in PATH.

cmd/codebase-memory-mcp/install.go

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"os"
78
"os/exec"
@@ -61,7 +62,15 @@ func runInstall(args []string) int {
6162
fmt.Println("[Codex CLI] not found — skipping")
6263
}
6364

64-
fmt.Println("\nDone. Restart Claude Code / Codex to activate.")
65+
fmt.Println()
66+
67+
// Cursor
68+
installEditorMCP(binaryPath, cursorConfigPath(), "Cursor", cfg)
69+
70+
// Windsurf
71+
installEditorMCP(binaryPath, windsurfConfigPath(), "Windsurf", cfg)
72+
73+
fmt.Println("\nDone. Restart Claude Code / Codex / Cursor / Windsurf to activate.")
6574
return 0
6675
}
6776

@@ -91,6 +100,12 @@ func runUninstall(args []string) int {
91100
removeCodexInstructions(cfg)
92101
}
93102

103+
// Cursor
104+
removeEditorMCP(cursorConfigPath(), "Cursor", cfg)
105+
106+
// Windsurf
107+
removeEditorMCP(windsurfConfigPath(), "Windsurf", cfg)
108+
94109
fmt.Println("\nDone. Binary and databases were NOT removed.")
95110
return 0
96111
}
@@ -462,3 +477,123 @@ func execCLI(path string, args ...string) error {
462477
cmd.Stderr = os.Stderr
463478
return cmd.Run()
464479
}
480+
481+
// --- Editor MCP config (Cursor, Windsurf) ---
482+
483+
const mcpServerKey = "codebase-memory-mcp"
484+
485+
// cursorConfigPath returns the Cursor MCP config path.
486+
func cursorConfigPath() string {
487+
home, err := os.UserHomeDir()
488+
if err != nil {
489+
return ""
490+
}
491+
return filepath.Join(home, ".cursor", "mcp.json")
492+
}
493+
494+
// windsurfConfigPath returns the Windsurf MCP config path.
495+
func windsurfConfigPath() string {
496+
home, err := os.UserHomeDir()
497+
if err != nil {
498+
return ""
499+
}
500+
return filepath.Join(home, ".codeium", "windsurf", "mcp_config.json")
501+
}
502+
503+
// installEditorMCP upserts our MCP server entry in an editor's JSON config file.
504+
func installEditorMCP(binaryPath, configPath, editorName string, cfg installConfig) {
505+
if configPath == "" {
506+
return
507+
}
508+
509+
fmt.Printf("[%s] MCP config: %s\n", editorName, configPath)
510+
511+
if cfg.dryRun {
512+
fmt.Printf(" [dry-run] Would upsert %s in %s\n", mcpServerKey, configPath)
513+
return
514+
}
515+
516+
// Read existing config or start fresh
517+
root := make(map[string]any)
518+
if data, err := os.ReadFile(configPath); err == nil {
519+
if jsonErr := json.Unmarshal(data, &root); jsonErr != nil {
520+
// File exists but is invalid JSON — back up and overwrite
521+
fmt.Printf(" ⚠ Invalid JSON in %s, overwriting\n", configPath)
522+
root = make(map[string]any)
523+
}
524+
}
525+
526+
// Ensure mcpServers map exists
527+
servers, ok := root["mcpServers"].(map[string]any)
528+
if !ok {
529+
servers = make(map[string]any)
530+
}
531+
532+
// Upsert our server entry
533+
servers[mcpServerKey] = map[string]any{
534+
"command": binaryPath,
535+
}
536+
root["mcpServers"] = servers
537+
538+
// Write back
539+
if err := os.MkdirAll(filepath.Dir(configPath), 0o750); err != nil {
540+
fmt.Printf(" ⚠ mkdir %s: %v\n", filepath.Dir(configPath), err)
541+
return
542+
}
543+
out, err := json.MarshalIndent(root, "", " ")
544+
if err != nil {
545+
fmt.Printf(" ⚠ marshal JSON: %v\n", err)
546+
return
547+
}
548+
if err := os.WriteFile(configPath, append(out, '\n'), 0o600); err != nil {
549+
fmt.Printf(" ⚠ write %s: %v\n", configPath, err)
550+
return
551+
}
552+
fmt.Printf(" ✓ MCP server registered in %s\n", configPath)
553+
}
554+
555+
// removeEditorMCP removes our MCP server entry from an editor's JSON config file.
556+
func removeEditorMCP(configPath, editorName string, cfg installConfig) {
557+
if configPath == "" {
558+
return
559+
}
560+
561+
data, err := os.ReadFile(configPath)
562+
if err != nil {
563+
return // no config file, nothing to remove
564+
}
565+
566+
var root map[string]any
567+
if err := json.Unmarshal(data, &root); err != nil {
568+
return
569+
}
570+
571+
servers, ok := root["mcpServers"].(map[string]any)
572+
if !ok {
573+
return
574+
}
575+
if _, exists := servers[mcpServerKey]; !exists {
576+
return
577+
}
578+
579+
fmt.Printf("[%s] MCP config: %s\n", editorName, configPath)
580+
581+
if cfg.dryRun {
582+
fmt.Printf(" [dry-run] Would remove %s from %s\n", mcpServerKey, configPath)
583+
return
584+
}
585+
586+
delete(servers, mcpServerKey)
587+
root["mcpServers"] = servers
588+
589+
out, err := json.MarshalIndent(root, "", " ")
590+
if err != nil {
591+
fmt.Printf(" ⚠ marshal JSON: %v\n", err)
592+
return
593+
}
594+
if err := os.WriteFile(configPath, append(out, '\n'), 0o600); err != nil {
595+
fmt.Printf(" ⚠ write %s: %v\n", configPath, err)
596+
return
597+
}
598+
fmt.Printf(" ✓ Removed %s from %s\n", mcpServerKey, configPath)
599+
}

0 commit comments

Comments
 (0)