Skip to content

Commit 340f22e

Browse files
Ship the analysis suite: impact, cycles, hierarchy, dead code, hotspots, and coupling (#32)
Six analysis features arrive behind a new analyze MCP tool, /api/analysis routes, and a dashboard Analysis tab with symbol-local Impact and Calls sections. Blast radius walks the inbound closure over stable ids with bounded depth; import cycles are canonicalized, including self-imports; call hierarchy expands callers and callees binding overloads to their implementation; unreferenced exports are presented as candidates with their caveats stated, not as verdicts; hotspots and change coupling are computed from indexed git history that now reports its own coverage truthfully: total commits, window size, and whether truncation actually happened, with the numeric window named only when it did. Ownership was deliberately deferred because the graph stores authors, not committers, and shipping it under the wrong name would lie. Every traversal enforces the target's project boundary from inside the query, every response carries normalized inputs, truncation flags, and user-facing caveat strings, and the same question through the service, the route, and the persona returns the same answer, held together by a 66-assertion cross-surface harness. Also fixed here after live use caught it: vector search threw away the whole response when an embedded File node appeared in the hits, because Files carry a derived identity rather than a persisted symbol id. File rows now derive their canonical id at the mapping boundary. This bug shipped with the identity redesign and is fixed on top of it. One adversarial review round found three blocker classes, all repaired red-first and re-verified against the reviewer's own harness. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 9a9a750 commit 340f22e

39 files changed

Lines changed: 4692 additions & 15 deletions

‎.claude/launch.json‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"version": "0.0.1",
3+
"configurations": [
4+
{
5+
"name": "codegraph-api",
6+
"runtimeExecutable": "pnpm",
7+
"runtimeArgs": ["--filter", "@codegraph/api", "dev"],
8+
"port": 3001
9+
},
10+
{
11+
"name": "codegraph-dashboard",
12+
"runtimeExecutable": "pnpm",
13+
"runtimeArgs": ["--filter", "@codegraph/dashboard", "dev", "--", "--port", "5173", "--strictPort"],
14+
"port": 5173
15+
}
16+
]
17+
}

‎CLAUDE.md‎

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# CodeGraph — AI Assistant Skill Document
22

3-
CodeGraph indexes codebases into a graph database (FalkorDB) and provides MCP tools for code search, context retrieval, knowledge management, and raw graph queries.
3+
CodeGraph indexes codebases into a graph database (FalkorDB) and provides MCP tools for code search, context retrieval, repository analysis, knowledge management, and raw graph queries.
44

55
## Quick Start
66

@@ -15,7 +15,7 @@ codebase({ action: "configure", projectAction: "set", projects: ["/path/to/proje
1515
codebase({ action: "reindex", mode: "full" })
1616
```
1717

18-
## Tool Reference (4 tool groups, 17 actions)
18+
## Tool Reference (5 tool groups, 24 actions)
1919

2020
### 1. `search` — Find code and knowledge
2121

@@ -90,12 +90,38 @@ knowledge({ action: "resolve_entities" })
9090
| `stats` | Graph node/edge counts | (none) |
9191
| `source` | Read source code | `path` |
9292
| `ping` | Test connectivity | (none) |
93+
| `profile` | Get a fast static and dynamic project snapshot | (none) |
9394

94-
### 4. `query` — Raw Cypher (power users)
95+
### 4. `analyze`: Bounded repository analysis
96+
97+
Use purpose-built static and history analysis instead of hand-writing Cypher.
98+
99+
| Action | Use When | Required Params |
100+
|--------|----------|-----------------|
101+
| `impact` | Need the static blast radius of a persisted symbol | `id` |
102+
| `import_cycles` | Need canonical import cycles within a project | `projectPath` |
103+
| `call_hierarchy` | Need direct callers, callees, or both | `id` |
104+
| `dead_code` | Need unreferenced export candidates | `projectPath` |
105+
| `hotspots` | Need frequently changed files ranked by current complexity or degree | `projectPath` |
106+
| `change_coupling` | Need file pairs that change together | `projectPath` |
107+
108+
**Examples:**
109+
```
110+
analyze({ action: "impact", id: "sym:v1:<64 lowercase hex characters>", depth: 3, limit: 100 })
111+
analyze({ action: "import_cycles", projectPath: "/path/to/project", maxDepth: 25, limit: 100 })
112+
analyze({ action: "call_hierarchy", id: "sym:v1:<64 lowercase hex characters>", direction: "both", limit: 100 })
113+
analyze({ action: "dead_code", projectPath: "/path/to/project", limit: 100 })
114+
analyze({ action: "hotspots", projectPath: "/path/to/project", since: "2026-01-01", scoreBy: "complexity", limit: 100 })
115+
analyze({ action: "change_coupling", projectPath: "/path/to/project", since: "2026-01-01", minSupport: 2, limit: 100 })
116+
```
117+
118+
Every result carries display-ready caveat strings and truncation metadata from the analysis layer. Hotspots and change coupling also report `historyCoverage`, including the observed commit count and date range. Impact, call hierarchy, import cycles, and unreferenced exports are static evidence, not proof of runtime behavior. Dead-code results are candidates and must never drive automated deletion. Git-backed results cover indexed history only.
119+
120+
### 5. `query`: Raw Cypher (power users)
95121

96122
Execute read-only Cypher against the code graph.
97123

98-
**Schema:** Nodes: File, Function, Class, Interface, Variable, Type, Component, Entity. Edges: CONTAINS, CALLS, IMPORTS, IMPORTS_SYMBOL, EXTENDS, IMPLEMENTS, EXPORTS, PARENT_SECTION, ABOUT, RELATES_TO. SAID is not a separate edge label: it's a RELATES_TO edge with a `type` property set to "SAID" (deliberate design, same as every other relationship kind RELATES_TO carries via its `type` property).
124+
**Schema:** Nodes: File, Function, Class, Interface, Variable, Type, Component, TypeRef, Entity, Project, Commit, Metadata, MarkdownDocument, Section, CodeBlock, Link. Edges: CONTAINS, IMPORTS, IMPORTS_SYMBOL, CALLS, EXTENDS, IMPLEMENTS, USES_TYPE, RETURNS, HAS_PARAM, HAS_METHOD, HAS_PROPERTY, RENDERS, INTRODUCED_IN, MODIFIED_IN, DELETED_IN, EXPORTS, PARENT_SECTION, ABOUT, RELATES_TO. SAID is not a separate edge label: it is a RELATES_TO edge with a `type` property set to "SAID", like every other relationship kind carried by RELATES_TO.
99125

100126
```
101127
query({ cypher: "MATCH (f:Function) WHERE f.name CONTAINS $name RETURN f.name, f.filePath LIMIT 20", params: { name: "parse" } })
@@ -117,6 +143,18 @@ query({ cypher: "MATCH (f:Function) WHERE f.name CONTAINS $name RETURN f.name, f
117143
1. `search({ action: "find", query: "retry logic", searchScope: "all" })` — search both code and knowledge
118144
2. Results include both code symbols and knowledge entities, ranked by RRF fusion
119145

146+
### Analyze Change Risk
147+
1. `analyze({ action: "impact", id: "<persisted-symbol-id>" })`: inspect the bounded static blast radius
148+
2. `analyze({ action: "call_hierarchy", id: "<persisted-symbol-id>", direction: "both" })`: inspect direct callers and callees
149+
3. Read every returned caveat and truncation field before drawing a conclusion
150+
151+
### Analyze Repository Health
152+
1. `analyze({ action: "import_cycles", projectPath: "<absolute-project-path>" })`: find canonical cycles
153+
2. `analyze({ action: "dead_code", projectPath: "<absolute-project-path>" })`: review unreferenced export candidates
154+
3. `analyze({ action: "hotspots", projectPath: "<absolute-project-path>" })`: rank frequently changed files
155+
4. `analyze({ action: "change_coupling", projectPath: "<absolute-project-path>" })`: find correlated file changes
156+
5. Treat historyCoverage as the observed indexed window, not all-time repository history
157+
120158
### Ingest Documents
121159
1. `knowledge({ action: "add", input: "/path/to/spec.pdf" })` — auto-detects format, chunks, extracts entities
122160
2. Supported: PDF, DOCX, HTML, CSV, URLs, raw text
@@ -134,14 +172,14 @@ query({ cypher: "MATCH (f:Function) WHERE f.name CONTAINS $name RETURN f.name, f
134172

135173
- **Don't** pass raw user input to `query` — use parameterized queries with `params`
136174
- **Don't** fetch everything — always use `limit` and `scope` to constrain results
137-
- **Don't** use `query` for things `search` can do — `search` has better defaults
175+
- **Don't** use `query` for things `search` or `analyze` can do. The purpose-built tools have safer bounds and clearer caveats.
138176
- **Don't** call `codebase({ action: "reindex" })` repeatedly — use `mode: "incremental"` (the default)
139177

140178
## Environment
141179

142180
- **Graph DB**: FalkorDB (Docker)
143181
- **Search**: Vector embeddings (local/Voyage/OpenRouter) + cross-encoder reranking (Voyage rerank-2) — MRR 0.938, S@1 88%, S@5 100%, ~440ms latency (post-purity baseline, 2026-05-10)
144-
- **Dashboard**: `http://localhost:3000/dashboard` (Graph Explorer + Operations tabs)
182+
- **Dashboard**: `http://localhost:3000/dashboard` (Graph Explorer + Analysis + Operations tabs)
145183
- **API**: `http://localhost:3001` (REST endpoints for dashboard)
146184
- **Build**: `pnpm turbo build` (monorepo with Turbo)
147185
- **Test**: `pnpm turbo test`

0 commit comments

Comments
 (0)