Skip to content

Commit 3e3e06b

Browse files
committed
feat: agent intelligence roadmap phases 3-6 + parser ignore tests
Phases 3-6: process tracing, community detection, persistence, impact analysis, MCP tools/prompts, UI enhancements (file tree, search, staleness), CLI persistence (--index/--force/--status/--clean), per-symbol PageRank/betweenness, BM25 search, symbol disambiguation. Parser: +9 tests for gitignore filtering, .git skip, broken symlinks. 171 tests passing, 0 failures.
1 parent 2d5bb60 commit 3e3e06b

Some content is hidden

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

41 files changed

+4202
-156
lines changed

app/api/meta/route.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import { NextResponse } from "next/server";
2-
import { getProjectName } from "@/src/server/graph-store";
2+
import { getProjectName, getIndexedHead } from "@/src/server/graph-store";
33

44
export function GET(): NextResponse {
5-
return NextResponse.json({ projectName: getProjectName() });
5+
const indexedHash = getIndexedHead();
6+
return NextResponse.json({
7+
projectName: getProjectName(),
8+
staleness: {
9+
stale: false,
10+
indexedHash,
11+
},
12+
});
613
}

app/api/processes/route.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { NextResponse } from "next/server";
2+
import { getGraph } from "@/src/server/graph-store";
3+
4+
export function GET(): NextResponse {
5+
const graph = getGraph();
6+
7+
return NextResponse.json({
8+
processes: graph.processes,
9+
stats: {
10+
totalProcesses: graph.processes.length,
11+
maxDepth: graph.processes.reduce((max, p) => Math.max(max, p.depth), 0),
12+
},
13+
});
14+
}

app/api/search/route.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { NextResponse } from "next/server";
2+
import { getGraph } from "@/src/server/graph-store";
3+
import { createSearchIndex, search, getSuggestions } from "@/src/search/index";
4+
5+
export function GET(request: Request): NextResponse {
6+
const url = new URL(request.url);
7+
const query = url.searchParams.get("q") ?? "";
8+
const limitParam = url.searchParams.get("limit");
9+
const limit = limitParam ? Math.min(Math.max(parseInt(limitParam, 10), 1), 100) : 20;
10+
11+
if (!query) {
12+
return NextResponse.json({ error: "Missing query parameter 'q'" }, { status: 400 });
13+
}
14+
15+
const graph = getGraph();
16+
const index = createSearchIndex(graph);
17+
const results = search(index, query, limit);
18+
19+
if (results.length === 0) {
20+
const suggestions = getSuggestions(index, query);
21+
return NextResponse.json({ query, results: [], suggestions });
22+
}
23+
24+
const mapped = results.map((r) => ({
25+
file: r.file,
26+
score: r.score,
27+
symbols: r.symbols.map((s) => ({
28+
name: s.name,
29+
type: s.type,
30+
loc: s.loc,
31+
relevance: s.score,
32+
})),
33+
}));
34+
35+
return NextResponse.json({ query, results: mapped });
36+
}

app/api/symbols/[name]/route.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { NextResponse } from "next/server";
2+
import { getGraph } from "@/src/server/graph-store";
3+
import { getHints } from "@/src/mcp/hints";
4+
5+
export function GET(
6+
_request: Request,
7+
{ params }: { params: Promise<{ name: string }> },
8+
): Promise<NextResponse> {
9+
return params.then(({ name: symbolName }) => {
10+
const graph = getGraph();
11+
12+
const matches = [...graph.symbolMetrics.values()].filter(
13+
(m) => m.name === symbolName || m.symbolId.endsWith(`::${symbolName}`),
14+
);
15+
16+
if (matches.length === 0) {
17+
return NextResponse.json(
18+
{ error: `Symbol not found: ${symbolName}` },
19+
{ status: 404 },
20+
);
21+
}
22+
23+
const uniqueByFile = new Map<string, typeof matches[0]>();
24+
for (const m of matches) {
25+
if (!uniqueByFile.has(m.file)) {
26+
uniqueByFile.set(m.file, m);
27+
}
28+
}
29+
let deduped = [...uniqueByFile.values()];
30+
31+
if (deduped.length > 1) {
32+
const nonBarrel = deduped.filter((m) => !m.file.endsWith("/index.ts") && m.file !== "index.ts");
33+
if (nonBarrel.length > 0) deduped = nonBarrel;
34+
}
35+
36+
if (deduped.length > 1) {
37+
return NextResponse.json({
38+
disambiguation: deduped.map((m) => ({
39+
name: m.name,
40+
file: m.file,
41+
symbolId: m.symbolId,
42+
fanIn: m.fanIn,
43+
fanOut: m.fanOut,
44+
})),
45+
});
46+
}
47+
48+
const sym = deduped[0];
49+
50+
const callers = graph.callEdges
51+
.filter((e) => e.calleeSymbol === symbolName || e.target === sym.symbolId)
52+
.map((e) => ({ symbol: e.callerSymbol, file: e.source.split("::")[0] }));
53+
54+
const callees = graph.callEdges
55+
.filter((e) => e.callerSymbol === symbolName || e.source === sym.symbolId)
56+
.map((e) => ({ symbol: e.calleeSymbol, file: e.target.split("::")[0] }));
57+
58+
return NextResponse.json({
59+
name: sym.name,
60+
file: sym.file,
61+
fanIn: sym.fanIn,
62+
fanOut: sym.fanOut,
63+
callers,
64+
callees,
65+
nextSteps: getHints("symbol_context"),
66+
});
67+
});
68+
}

app/page.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22

3+
import { useState } from "react";
34
import { GraphProvider, useGraphContext } from "@/components/graph-provider";
45
import { GraphCanvas } from "@/components/graph-canvas";
56
import { ProjectBar } from "@/components/project-bar";
@@ -8,13 +9,16 @@ import { SearchInput } from "@/components/search-input";
89
import { DetailPanel } from "@/components/detail-panel";
910
import { SettingsPanel } from "@/components/settings-panel";
1011
import { Legend } from "@/components/legend";
12+
import { FileTree } from "@/components/file-tree";
13+
import { StaleBanner } from "@/components/stale-banner";
1114

1215
function App(): React.ReactElement | null {
1316
const {
1417
graphData,
1518
forceData,
1619
groupData,
1720
projectName,
21+
staleness,
1822
isLoading,
1923
error,
2024
config,
@@ -30,6 +34,8 @@ function App(): React.ReactElement | null {
3034
handleSearch,
3135
} = useGraphContext();
3236

37+
const [fileTreeOpen, setFileTreeOpen] = useState(false);
38+
3339
if (error) {
3440
return (
3541
<div className="w-screen h-screen flex items-center justify-center">
@@ -50,9 +56,16 @@ function App(): React.ReactElement | null {
5056

5157
return (
5258
<>
59+
<StaleBanner staleness={staleness} />
5360
<ProjectBar projectName={projectName} graphData={graphData} forceData={forceData} />
5461
<ViewTabs current={currentView} onChange={setCurrentView} />
55-
<SearchInput nodes={graphData.nodes} onSearch={handleSearch} />
62+
<SearchInput onSearch={handleSearch} />
63+
<FileTree
64+
nodes={graphData.nodes}
65+
onSelect={handleSearch}
66+
isOpen={fileTreeOpen}
67+
onTogglePanel={() => { setFileTreeOpen((prev) => !prev); }}
68+
/>
5669
<GraphCanvas
5770
nodes={graphData.nodes}
5871
edges={graphData.edges}

0 commit comments

Comments
 (0)