Skip to content

Commit f8da885

Browse files
committed
feat: parser refactoring, Jedi call resolution, and performance optimizations
Major improvements to code-review-graph spanning parser architecture, call graph accuracy, and build performance. Parser refactoring: - Extract 16 per-language handler modules into code_review_graph/lang/ using a strategy pattern, replacing monolithic conditionals in parser.py - Thread-safe parser caches with double-check locking Call graph enrichment: - Jedi-based Python method call resolution at build time (jedi_resolver.py) - Pre-scan filtering by project function names (36s to 3s on large repos) - Typed variable call enrichment (Python, JS/TS, Kotlin/Java) - Star import resolution, namespace imports, CommonJS require() - Angular template parsing, JSX handler tracking - Module-level import tracking and module-qualified call resolution - Function/class references passed as call arguments PreToolUse search enrichment: - New enrich.py module and code-review-graph enrich CLI command - Injects graph context (callers, flows, community, tests) into agent search results passively via hook Dead code false positive reduction: - Framework decorators recognized as entry points - CDK construct methods, abstract overrides excluded - E2e test directories excluded from dead code detection Performance: - Community detection: 48.6s to 2.3s (21x speedup) via bulk node loading and adjacency-indexed cohesion computation - Jedi enrichment: 36s to 3s (12x) via pre-scan filtering - Batch file storage (50-file transactions) - Batch risk_index (2 GROUP BY queries replace per-node loops) Other: - Weighted flow risk scoring by criticality - Transitive TESTED_BY lookup for tests_for and risk scoring - DB schema v8: composite edge index (v7 reserved by PR #127) - --quiet and --json CLI flags - Search query deduplication, test function deprioritization - New [enrichment] optional dependency group for Jedi - 829+ tests across 26 test files (up from 615) Evaluated against Gadgetbridge (41k nodes, 280k edges): 8/10 PASS, call resolution rate improved from 28% to 39.6%.
1 parent 7093e56 commit f8da885

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+9831
-1670
lines changed

CHANGELOG.md

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,50 @@ Hotfix on top of 2.2.3 for two bugs surfaced by a full first-time-user smoke tes
103103

104104
### Added
105105
- **Codex platform install support** (PR #177): `code-review-graph install --platform codex` appends a `mcp_servers.code-review-graph` section to `~/.codex/config.toml` without overwriting existing Codex settings.
106-
- **Luau language support** (PR #165, closes #153): Roblox Luau (`.luau`) parsing functions, classes, local functions, requires, tests.
106+
- **Luau language support** (PR #165, closes #153): Roblox Luau (`.luau`) parsing -- functions, classes, local functions, requires, tests.
107107
- **REFERENCES edge type** (PR #217): New edge kind for symbol references that aren't direct calls (map/dispatch lookups, string-keyed handlers), including Python and TypeScript patterns.
108108
- **`recurse_submodules` build option** (PR #215): Build/update can now optionally recurse into git submodules.
109109
- **`.gitignore` default for `.code-review-graph/`** (PR #185): Fresh installs automatically add the SQLite DB directory to `.gitignore` so the database isn't accidentally committed.
110110
- **Clearer gitignore docs** (PR #171, closes #157): Documentation now spells out that `code-review-graph` already respects `.gitignore` via `git ls-files`.
111+
- **Parser refactoring**: Extracted 16 per-language handler modules into `code_review_graph/lang/` package using a strategy pattern, replacing monolithic conditionals in `parser.py`
112+
- **Jedi-based call resolution**: New `jedi_resolver.py` module resolves Python method calls at build time via Jedi static analysis, with pre-scan filtering by project function names (36s to 3s on large repos)
113+
- **PreToolUse search enrichment**: New `enrich.py` module and `code-review-graph enrich` CLI command inject graph context (callers, callees, flows, community, tests) into agent search results passively
114+
- **Typed variable call enrichment**: Track constructor-based type inference and instance method calls for Python, JS/TS, and Kotlin/Java
115+
- **Star import resolution**: Resolve `from module import *` by scanning target module's exported names
116+
- **Namespace imports**: Track `import * as X from 'module'` and CommonJS `require()` patterns
117+
- **Angular template parsing**: Extract call targets from Angular component templates
118+
- **JSX handler tracking**: Detect function/class references passed as JSX event handler props
119+
- **Framework decorator recognition**: Identify entry points decorated with `@app.route`, `@router.get`, `@cli.command`, etc., reducing dead code false positives
120+
- **Module-level import tracking**: Track module-qualified call resolution (`module.function()`)
121+
- **Thread safety**: Double-check locking on parser caches (`_type_sets`, `_get_parser`, `_resolve_module_to_file`, `_get_exported_names`)
122+
- **Batch file storage**: `store_file_batch()` groups file insertions into 50-file transactions for faster builds
123+
- **Bulk node loading**: `get_all_nodes()` replaces per-file SQL queries for community detection
124+
- **Adjacency-indexed cohesion**: Community cohesion computed in O(community-edges) instead of O(all-edges), yielding 21x speedup (48.6s to 2.3s on 41k-node repos)
125+
- **Phase timing instrumentation**: `time.perf_counter()` timing at INFO level for all build phases
126+
- **Batch risk_index**: 2 GROUP BY queries replace per-node COUNT loops in risk scoring
127+
- **Weighted flow risk scoring**: Risk scores weighted by flow criticality instead of flat edge counts
128+
- **Transitive TESTED_BY lookup**: `tests_for` and risk scoring follow transitive test relationships
129+
- **DB schema v8**: Composite edge index for upsert performance (v7 reserved by upstream PR #127)
130+
- **`--quiet` and `--json` CLI flags**: Machine-readable output for `build`, `update`, `status`
131+
- **829+ tests** across 26 test files (up from 615), including `test_pain_points.py` (1,587 lines TDD suite), `test_hardened.py` (467 lines), `test_enrich.py` (237 lines)
132+
- **14 new test fixtures**: Kotlin, Java, TypeScript, JSX, Python resolution scenarios
111133

112134
### Changed
113-
- Community detection is now bounded — large repos complete in reasonable time instead of hanging indefinitely.
135+
- Community detection is now bounded -- large repos complete in reasonable time instead of hanging indefinitely.
136+
- New `[enrichment]` optional dependency group for Jedi-based Python call resolution
137+
- Leiden community detection scales resolution parameter with graph size
138+
- Adaptive directory-based fallback for community detection when Leiden produces poor clusters
139+
- Search query deduplication and test function deprioritization
140+
141+
### Fixed
142+
- **Dead code false positives**: Decorators, CDK construct methods, abstract overrides, and overriding methods with called parents no longer flagged as dead
143+
- **E2e test exclusion**: Playwright/Cypress e2e test directories excluded from dead code detection
144+
- **Unique-name plausible caller optimization**: Faster dead code analysis via pre-filtered candidate sets
145+
- **Store cache liveness check**: Cached SQLite connections verified as alive before reuse
146+
147+
### Performance
148+
- **Community detection**: 48.6s to 2.3s (21x) on Gadgetbridge (41k nodes, 280k edges)
149+
- **Jedi enrichment**: 36s to 3s (12x) via pre-scan filtering by project function names
114150

115151
## [2.2.2] - 2026-04-08
116152

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ When using code-review-graph MCP tools, follow these rules:
4646

4747
```bash
4848
# Development
49-
uv run pytest tests/ --tb=short -q # Run tests (572 tests)
49+
uv run pytest tests/ --tb=short -q # Run tests (609 tests)
5050
uv run ruff check code_review_graph/ # Lint
5151
uv run mypy code_review_graph/ --ignore-missing-imports --no-strict-optional
5252

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ code-review-graph watch # Auto-update on file changes
230230
code-review-graph visualize # Generate interactive HTML graph
231231
code-review-graph wiki # Generate markdown wiki from communities
232232
code-review-graph detect-changes # Risk-scored change impact analysis
233+
code-review-graph enrich # Enrich search results with graph context
233234
code-review-graph register <path> # Register repo in multi-repo registry
234235
code-review-graph unregister <id> # Remove repo from registry
235236
code-review-graph repos # List registered repositories
@@ -296,6 +297,7 @@ Optional dependency groups:
296297
pip install code-review-graph[embeddings] # Local vector embeddings (sentence-transformers)
297298
pip install code-review-graph[google-embeddings] # Google Gemini embeddings
298299
pip install code-review-graph[communities] # Community detection (igraph)
300+
pip install code-review-graph[enrichment] # Jedi-based Python call resolution
299301
pip install code-review-graph[eval] # Evaluation benchmarks (matplotlib)
300302
pip install code-review-graph[wiki] # Wiki generation with LLM summaries (ollama)
301303
pip install code-review-graph[all] # All optional dependencies
@@ -319,7 +321,7 @@ pytest
319321
<summary><strong>Adding a new language</strong></summary>
320322
<br>
321323

322-
Edit `code_review_graph/parser.py` and add your extension to `EXTENSION_TO_LANGUAGE` along with node type mappings in `_CLASS_TYPES`, `_FUNCTION_TYPES`, `_IMPORT_TYPES`, and `_CALL_TYPES`. Include a test fixture and open a PR.
324+
Edit the appropriate language handler in `code_review_graph/lang/` (e.g., `_python.py`, `_kotlin.py`) or create a new one following `_base.py`. Add your extension to `EXTENSION_TO_LANGUAGE` in `parser.py`, include a test fixture, and open a PR.
323325

324326
</details>
325327

code-review-graph-vscode/src/backend/cli.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,22 +53,14 @@ export class CliWrapper {
5353
/**
5454
* Build (or fully rebuild) the graph database for a workspace.
5555
*/
56-
async buildGraph(
57-
workspaceRoot: string,
58-
options?: { fullRebuild?: boolean },
59-
): Promise<CliResult> {
60-
const args = ['build'];
61-
if (options?.fullRebuild) {
62-
args.push('--full');
63-
}
64-
56+
async buildGraph(workspaceRoot: string): Promise<CliResult> {
6557
return vscode.window.withProgress(
6658
{
6759
location: vscode.ProgressLocation.Notification,
6860
title: 'Code Review Graph: Building graph\u2026',
6961
cancellable: false,
7062
},
71-
() => this.exec(args, workspaceRoot),
63+
() => this.exec(['build'], workspaceRoot),
7264
);
7365
}
7466

code-review-graph-vscode/src/backend/sqlite.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ export class SqliteReader {
212212
if (row) {
213213
const version = parseInt(row.value, 10);
214214
// Must match LATEST_VERSION in code_review_graph/migrations.py
215-
const SUPPORTED_SCHEMA_VERSION = 6;
215+
const SUPPORTED_SCHEMA_VERSION = 8;
216216
if (!isNaN(version) && version > SUPPORTED_SCHEMA_VERSION) {
217217
return `Database was created with a newer version (schema v${version}). Update the extension.`;
218218
}

code_review_graph/changes.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -154,15 +154,19 @@ def compute_risk_score(store: GraphStore, node: GraphNode) -> float:
154154
Scoring factors:
155155
- Flow participation: 0.05 per flow membership, capped at 0.25
156156
- Community crossing: 0.05 per caller from a different community, capped at 0.15
157-
- Test coverage: 0.30 if no TESTED_BY edges, 0.05 if tested
157+
- Test coverage: 0.30 (untested) scaling down to 0.05 (5+ TESTED_BY edges)
158158
- Security sensitivity: 0.20 if name matches security keywords
159159
- Caller count: callers / 20, capped at 0.10
160160
"""
161161
score = 0.0
162162

163-
# --- Flow participation (cap 0.25) ---
164-
flow_count = store.count_flow_memberships(node.id)
165-
score += min(flow_count * 0.05, 0.25)
163+
# --- Flow participation (cap 0.25), weighted by criticality ---
164+
flow_criticalities = store.get_flow_criticalities_for_node(node.id)
165+
if flow_criticalities:
166+
score += min(sum(flow_criticalities), 0.25)
167+
else:
168+
flow_count = store.count_flow_memberships(node.id)
169+
score += min(flow_count * 0.05, 0.25)
166170

167171
# --- Community crossing (cap 0.15) ---
168172
callers = store.get_edges_by_target(node.qualified_name)
@@ -179,10 +183,10 @@ def compute_risk_score(store: GraphStore, node: GraphNode) -> float:
179183
cross_community += 1
180184
score += min(cross_community * 0.05, 0.15)
181185

182-
# --- Test coverage ---
183-
tested_edges = store.get_edges_by_target(node.qualified_name)
184-
has_test = any(e.kind == "TESTED_BY" for e in tested_edges)
185-
score += 0.05 if has_test else 0.30
186+
# --- Test coverage (direct + transitive) ---
187+
transitive_tests = store.get_transitive_tests(node.qualified_name)
188+
test_count = len(transitive_tests)
189+
score += 0.30 - (min(test_count / 5.0, 1.0) * 0.25)
186190

187191
# --- Security sensitivity ---
188192
name_lower = node.name.lower()

0 commit comments

Comments
 (0)