feat: add find_path and get_file_signatures MCP tools

- find_path: BFS traversal to trace relationship chains between two files (max N hops)
- get_file_signatures: lightweight context mode returning just function/type names
- db.find_path(): bidirectional BFS with shortest-path tracking
- db.get_all_files(): list all files with docs and staleness
- db.get_file_signatures(): return functions list without full doc payload

Inspired by Octocode's GraphRAG path-finding pattern.
Addresses Mike/Dmitry feedback on usable roll-up summaries.
This commit is contained in:
Jarvis Prime
2026-03-05 04:23:13 +00:00
parent e28e1a61b2
commit 3256e4e7c3
2 changed files with 96 additions and 0 deletions

56
db.py
View File

@@ -203,6 +203,62 @@ class GraphDB:
).fetchone()
return dict(row) if row else None
def find_path(self, source: str, target: str, max_depth: int = 4) -> list[list[str]]:
"""BFS to find all shortest paths between two files via import relationships."""
from collections import deque
# Build adjacency list (both directions — imports are directional but we trace both ways)
adj: dict[str, set[str]] = {}
rows = self.conn.execute("SELECT from_file, to_file FROM relationships").fetchall()
for r in rows:
adj.setdefault(r["from_file"], set()).add(r["to_file"])
adj.setdefault(r["to_file"], set()).add(r["from_file"])
if source not in adj or target not in adj:
return []
# BFS with path tracking
queue: deque[list[str]] = deque([[source]])
visited: set[str] = {source}
found_paths: list[list[str]] = []
found_depth: int | None = None
while queue:
path = queue.popleft()
if found_depth is not None and len(path) > found_depth:
break
if len(path) > max_depth + 1:
break
node = path[-1]
if node == target:
found_paths.append(path)
found_depth = len(path)
continue
for neighbor in adj.get(node, []):
if neighbor not in visited or (found_depth and len(path) < found_depth):
new_path = path + [neighbor]
queue.append(new_path)
if found_depth is None:
visited.add(neighbor)
return found_paths
def get_all_files(self) -> list[dict]:
"""Get all files with their docs and staleness."""
rows = self.conn.execute(
"SELECT path, repo, language, documentation, staleness FROM files ORDER BY path"
).fetchall()
return [dict(r) for r in rows]
def get_file_signatures(self, path: str) -> dict | None:
"""Get file with just functions list (signatures) — lightweight context."""
row = self.conn.execute(
"SELECT path, language, functions, staleness FROM files WHERE path=?", (path,)
).fetchone()
return dict(row) if row else None
def search_docs(self, query: str, limit: int = 10) -> list[dict]:
rows = self.conn.execute(
"""SELECT path, documentation, staleness FROM files