Skip to content

Commit d40b7fe

Browse files
prosdevclaude
andcommitted
fix(core,mcp): address code review — single graph build, BFS perf, traceTo error
- Build dependency graph once, pass to both computeHotPaths and connectedComponents - Return explicit error when traceTo is set but indexer is missing - Document directed-only limitation in traceTo description - Switch BFS from queue.shift() (O(n)) to index-based (O(1)) - Require 2+ path segments for subsystem labels (avoid "packages" alone) - Track traceTo adapter test gap in scratchpad Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c931002 commit d40b7fe

9 files changed

Lines changed: 101 additions & 21 deletions

File tree

.changeset/graph-algorithms.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@prosdevlab/dev-agent': patch
3+
---
4+
5+
Graph algorithms for dev_map and dev_refs
6+
7+
- `dev_map` hot paths now use PageRank over the weighted dependency graph — files depended on by other important files rank higher
8+
- `dev_map` shows connected subsystems ("Subsystems: packages/core (45 files), packages/cli (12 files)")
9+
- `dev_refs` new `traceTo` parameter traces the dependency chain between files through the call graph
10+
- All algorithms are hand-rolled pure functions (~230 lines), no new dependencies
11+
- Inspired by aider's repo map (PageRank over dependency graphs)

.claude/scratchpad.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
## Test Gaps
2727

28+
- **RefsAdapter integration test with `traceTo`.** The `traceTo` path tracing feature is tested at the algorithm level (shortestPath in graph.test.ts) but not at the adapter level. Needs a test that constructs RefsAdapter with a mock indexer, calls `execute()` with `traceTo`, and verifies the path output format. Also needs a test for the error case when indexer is missing.
2829
- **InspectAdapter integration test with PatternMatcher.** The InspectAdapter test constructs without a `patternMatcher` — the AST path is never exercised through the MCP layer. Needs a test that constructs `InspectAdapter` with `createPatternMatcher()`, mocks the search service, calls `execute()`, and verifies AST-enhanced results flow through. Requires mock search service setup — larger integration test scope.
2930

3031
## Notes

packages/core/src/map/graph.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,14 @@ export function connectedComponents(graph: Map<string, WeightedEdge[]>): string[
189189
for (const node of allNodes) {
190190
if (visited.has(node)) continue;
191191

192-
// BFS from this node
192+
// BFS from this node (index-based to avoid O(n) shift)
193193
const component: string[] = [];
194194
const queue = [node];
195+
let qi = 0;
195196
visited.add(node);
196197

197-
while (queue.length > 0) {
198-
const current = queue.shift()!;
198+
while (qi < queue.length) {
199+
const current = queue[qi++];
199200
component.push(current);
200201
for (const neighbor of adj.get(current) || []) {
201202
if (!visited.has(neighbor)) {
@@ -232,9 +233,10 @@ export function shortestPath(
232233
const visited = new Set<string>([from]);
233234
const parent = new Map<string, string>();
234235
const queue = [from];
236+
let qi = 0;
235237

236-
while (queue.length > 0) {
237-
const current = queue.shift()!;
238+
while (qi < queue.length) {
239+
const current = queue[qi++];
238240
for (const { target } of graph.get(current) || []) {
239241
if (visited.has(target)) continue;
240242
visited.add(target);

packages/core/src/map/index.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,10 @@ export async function generateCodebaseMap(
117117
'Counted components'
118118
);
119119

120-
// Compute hot paths and connected components (share the dependency graph)
120+
// Build dependency graph once, share between hot paths and components
121121
const t7 = Date.now();
122-
const hotPaths = opts.includeHotPaths ? computeHotPaths(allDocs, opts.maxHotPaths) : [];
123122
const graph = buildDependencyGraph(allDocs);
123+
const hotPaths = opts.includeHotPaths ? computeHotPaths(allDocs, graph, opts.maxHotPaths) : [];
124124
const rawComponents = connectedComponents(graph);
125125
const components = rawComponents
126126
.filter((c) => c.length > 1) // Only show multi-file subsystems
@@ -462,8 +462,11 @@ function applyChangeFrequency(node: MapNode, frequencyMap: Map<string, ChangeFre
462462
* Files that are depended on by other important files rank higher.
463463
* Sort by PageRank score, display real incoming edge count.
464464
*/
465-
function computeHotPaths(docs: SearchResult[], maxPaths: number): HotPath[] {
466-
const graph = buildDependencyGraph(docs);
465+
function computeHotPaths(
466+
docs: SearchResult[],
467+
graph: Map<string, import('./graph').WeightedEdge[]>,
468+
maxPaths: number
469+
): HotPath[] {
467470
const ranks = pageRank(graph);
468471

469472
// Count real incoming edges per file (distinct source files)
@@ -590,5 +593,8 @@ function findCommonPrefix(files: string[]): string {
590593
prefix = prefix.substring(0, prefix.lastIndexOf('/'));
591594
}
592595
}
593-
return prefix;
596+
// Require at least 2 path segments for a meaningful label
597+
// "packages" alone is too generic; "packages/core" is useful
598+
const segments = prefix.split('/').filter(Boolean);
599+
return segments.length >= 2 ? prefix : '';
594600
}

packages/mcp-server/src/adapters/built-in/refs-adapter.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export class RefsAdapter extends ToolAdapter {
120120
type: 'string',
121121
description:
122122
"Trace the dependency chain from this function's file to a target file " +
123-
'(e.g., "src/database.ts"). Shows the shortest path through the call graph.',
123+
'(e.g., "src/database.ts"). Follows directed call graph edges (A calls B, not B calls A).',
124124
},
125125
},
126126
required: ['name'],
@@ -158,6 +158,17 @@ export class RefsAdapter extends ToolAdapter {
158158
}
159159

160160
// Handle traceTo — find shortest dependency path
161+
if (traceTo && !this.indexer) {
162+
return {
163+
success: false,
164+
error: {
165+
code: 'INDEX_REQUIRED',
166+
message: 'Path tracing requires a repository index.',
167+
suggestion: 'Run "dev index" to index the repository first.',
168+
},
169+
};
170+
}
171+
161172
if (traceTo && this.indexer) {
162173
const sourceFile = (target.metadata.path as string) || '';
163174
const allDocs = await this.indexer.getAll({ limit: 10000 });

website/content/docs/tools/dev-map.mdx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,25 @@ This helps AI assistants quickly understand codebase organization and activity.
6363

6464
## Features
6565

66-
### Hot Paths
66+
### Hot Paths (PageRank)
6767

68-
Shows the most referenced files in your codebase. These are typically:
69-
- Core utilities used everywhere
70-
- Base classes/interfaces
71-
- Central orchestrators
68+
Shows the most architecturally important files in your codebase, ranked by PageRank
69+
over the weighted dependency graph. Unlike simple reference counting, PageRank identifies
70+
files that are depended on by other important files — not just files with many direct imports.
7271

73-
Useful for understanding which code is most critical.
72+
Inspired by [aider's repo map](https://github.com/Aider-AI/aider).
73+
74+
### Subsystems
75+
76+
Shows connected components in the dependency graph — groups of files that depend on each
77+
other, forming independent subsystems. Helps agents understand codebase boundaries.
78+
79+
```
80+
Subsystems (3 connected):
81+
1. packages/core (45 files)
82+
2. packages/cli (12 files)
83+
3. packages/mcp-server (18 files)
84+
```
7485

7586
### Change Frequency ✨ v0.4
7687

website/content/docs/tools/dev-refs.mdx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ This is invaluable for understanding impact of changes and navigating unfamiliar
1717
| `name` | string | required | Symbol name to query |
1818
| `direction` | string | `"both"` | `"callers"`, `"callees"`, or `"both"` |
1919
| `limit` | number | `10` | Maximum results |
20+
| `traceTo` | string || Trace dependency chain to a target file (e.g., `"src/database.ts"`) |
2021
| `tokenBudget` | number | `2000` | Max tokens for output |
2122

2223
## Examples
@@ -84,6 +85,28 @@ What does the validateToken function call?
8485
}
8586
```
8687

88+
### Trace Dependency Path
89+
90+
> "How does the auth module reach the database?"
91+
92+
```json
93+
{
94+
"name": "authenticate",
95+
"traceTo": "src/database.ts"
96+
}
97+
```
98+
99+
**Output:**
100+
```
101+
## Dependency Path: src/auth.ts → src/database.ts
102+
103+
src/auth.ts → src/user-service.ts → src/repository.ts → src/database.ts
104+
105+
**3 hops**
106+
```
107+
108+
Follows directed call graph edges (A calls B). Returns "No path found" if files are in separate subsystems.
109+
87110
## Use Cases
88111

89112
### Impact Analysis

website/content/latest-version.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
*/
55

66
export const latestVersion = {
7-
version: '0.10.5',
8-
title: 'AST-Based Pattern Analysis',
7+
version: '0.10.6',
8+
title: 'Graph Algorithms for dev_map and dev_refs',
99
date: 'March 31, 2026',
1010
summary:
11-
'dev_patterns uses tree-sitter AST queries for more accurate detection of error handling, imports, and type coverage across .ts, .tsx, .js, .jsx files.',
12-
link: '/updates#v0105--ast-based-pattern-analysis',
11+
'PageRank-based file ranking, subsystem detection, and dependency path tracing via traceTo.',
12+
link: '/updates#v0106--graph-algorithms-for-dev_map-and-dev_refs',
1313
} as const;

website/content/updates/index.mdx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,21 @@ What's new in dev-agent. We ship improvements regularly to help AI assistants un
99

1010
---
1111

12+
## v0.10.6 — Graph Algorithms for dev_map and dev_refs
13+
14+
*March 31, 2026*
15+
16+
**PageRank-based file ranking, subsystem detection, and dependency path tracing.**
17+
18+
- `dev_map` hot paths now use PageRank over the weighted dependency graph — files depended on by other important files rank higher than files with many shallow references
19+
- `dev_map` shows connected subsystems: groups of interdependent files identified via graph analysis
20+
- `dev_refs` new `traceTo` parameter: `dev_refs { name: "authenticate", traceTo: "src/database.ts" }` → traces the shortest dependency chain between files
21+
- Weighted edges with sqrt dampening (inspired by [aider's repo map](https://github.com/Aider-AI/aider))
22+
- PageRank: 2,000 nodes + 10,000 edges in 4ms — no performance impact
23+
- All algorithms hand-rolled (~230 lines), no new dependencies
24+
25+
---
26+
1227
## v0.10.5 — AST-Based Pattern Analysis
1328

1429
*March 31, 2026*

0 commit comments

Comments
 (0)