Skip to content

Commit 8e55c84

Browse files
sdsrssclaude
andcommitted
fix(queries): detect main entry points in project_map and deduplicate module_overview exports
- project_map now detects program entry points (main functions with no callers) when no HTTP routes exist - module_overview deduplicates feature-gated symbols by (name, file_path), keeping highest caller_count - Bump version to 0.5.25 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5d3f4e3 commit 8e55c84

6 files changed

Lines changed: 55 additions & 7 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
},
66
"metadata": {
77
"description": "AST knowledge graph plugin for Claude Code — semantic search, call graph, HTTP tracing, impact analysis",
8-
"version": "0.5.24"
8+
"version": "0.5.25"
99
},
1010
"plugins": [
1111
{
1212
"name": "code-graph-mcp",
1313
"source": "./claude-plugin",
1414
"description": "AST knowledge graph for intelligent code navigation — auto-indexes your codebase and provides semantic search, call graph traversal, HTTP route tracing, and impact analysis via MCP tools",
15-
"version": "0.5.24",
15+
"version": "0.5.25",
1616
"author": {
1717
"name": "sdsrs"
1818
},

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "code-graph-mcp"
3-
version = "0.5.24"
3+
version = "0.5.25"
44
edition = "2021"
55

66
[features]

claude-plugin/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
"author": {
55
"name": "sdsrs"
66
},
7-
"version": "0.5.24",
7+
"version": "0.5.25",
88
"keywords": ["code-graph", "ast", "navigation", "mcp", "knowledge-graph"]
99
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sdsrs/code-graph",
3-
"version": "0.5.24",
3+
"version": "0.5.25",
44
"description": "MCP server that indexes codebases into an AST knowledge graph with semantic search, call graph traversal, and HTTP route tracing",
55
"license": "MIT",
66
"repository": {

src/storage/queries.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,38 @@ pub fn get_module_exports(conn: &Connection, dir_prefix: &str) -> Result<Vec<Mod
752752
caller_count: row.get(5)?,
753753
})
754754
})?;
755-
rows2.collect::<std::result::Result<Vec<_>, _>>().map_err(Into::into)
755+
let all: Vec<ModuleExport> = rows2.collect::<std::result::Result<Vec<_>, _>>()?;
756+
757+
// Deduplicate by (name, file_path) — keeps highest caller_count.
758+
// Handles feature-gated duplicates (e.g. #[cfg(feature)] producing two nodes for same symbol).
759+
let mut seen: HashMap<(&str, &str), usize> = HashMap::new();
760+
let mut deduped: Vec<ModuleExport> = Vec::with_capacity(all.len());
761+
for export in &all {
762+
let key = (export.name.as_str(), export.file_path.as_str());
763+
if let Some(&prev_idx) = seen.get(&key) {
764+
if export.caller_count > deduped[prev_idx].caller_count {
765+
deduped[prev_idx] = ModuleExport {
766+
node_id: export.node_id,
767+
name: export.name.clone(),
768+
node_type: export.node_type.clone(),
769+
signature: export.signature.clone(),
770+
file_path: export.file_path.clone(),
771+
caller_count: export.caller_count,
772+
};
773+
}
774+
} else {
775+
seen.insert(key, deduped.len());
776+
deduped.push(ModuleExport {
777+
node_id: export.node_id,
778+
name: export.name.clone(),
779+
node_type: export.node_type.clone(),
780+
signature: export.signature.clone(),
781+
file_path: export.file_path.clone(),
782+
caller_count: export.caller_count,
783+
});
784+
}
785+
}
786+
Ok(deduped)
756787
}
757788

758789
// --- Fuzzy name resolution ---
@@ -1118,6 +1149,23 @@ pub fn get_project_map(conn: &Connection) -> Result<(Vec<ModuleStats>, Vec<Modul
11181149
}
11191150
}
11201151

1152+
// 4b. Program entry points: main functions with no callers (Rust/Go/C/Python/Java)
1153+
if entry_points.is_empty() {
1154+
let sql = "SELECT n.name, f.path FROM nodes n \
1155+
JOIN files f ON f.id = n.file_id \
1156+
WHERE n.name = 'main' AND n.type = 'function' \
1157+
AND NOT EXISTS (SELECT 1 FROM edges e WHERE e.target_id = n.id AND e.relation = ?1) \
1158+
LIMIT 5";
1159+
let mut stmt = conn.prepare(sql)?;
1160+
let rows = stmt.query_map([REL_CALLS], |row| {
1161+
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
1162+
})?;
1163+
for row in rows {
1164+
let (name, file) = row?;
1165+
entry_points.push(EntryPoint { route: "main".into(), handler: name, file });
1166+
}
1167+
}
1168+
11211169
// 5. Hot functions (C1: filter test code, C3: use REL_CALLS constant)
11221170
let mut hot_functions = Vec::new();
11231171
{

0 commit comments

Comments
 (0)