rho-graph 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +277 -0
- package/bin/rho-graph.js +2 -0
- package/dist/cli/commands/index-cmd.d.ts +2 -0
- package/dist/cli/commands/index-cmd.js +45 -0
- package/dist/cli/commands/index-cmd.js.map +1 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.js +55 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/install-hook.d.ts +3 -0
- package/dist/cli/commands/install-hook.js +37 -0
- package/dist/cli/commands/install-hook.js.map +1 -0
- package/dist/cli/commands/install-mcp.d.ts +3 -0
- package/dist/cli/commands/install-mcp.js +32 -0
- package/dist/cli/commands/install-mcp.js.map +1 -0
- package/dist/cli/commands/query.d.ts +2 -0
- package/dist/cli/commands/query.js +92 -0
- package/dist/cli/commands/query.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +2 -0
- package/dist/cli/commands/setup.js +15 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/status.d.ts +2 -0
- package/dist/cli/commands/status.js +40 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/visualize.d.ts +2 -0
- package/dist/cli/commands/visualize.js +45 -0
- package/dist/cli/commands/visualize.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +32 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.js +95 -0
- package/dist/config.js.map +1 -0
- package/dist/db/connection.d.ts +13 -0
- package/dist/db/connection.js +25 -0
- package/dist/db/connection.js.map +1 -0
- package/dist/db/queries.d.ts +106 -0
- package/dist/db/queries.js +247 -0
- package/dist/db/queries.js.map +1 -0
- package/dist/db/schema.d.ts +2 -0
- package/dist/db/schema.js +22 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/docker/neo4j.d.ts +5 -0
- package/dist/docker/neo4j.js +85 -0
- package/dist/docker/neo4j.js.map +1 -0
- package/dist/indexer/batch-writer.d.ts +35 -0
- package/dist/indexer/batch-writer.js +202 -0
- package/dist/indexer/batch-writer.js.map +1 -0
- package/dist/indexer/extractor.d.ts +35 -0
- package/dist/indexer/extractor.js +141 -0
- package/dist/indexer/extractor.js.map +1 -0
- package/dist/indexer/graph-writer.d.ts +12 -0
- package/dist/indexer/graph-writer.js +75 -0
- package/dist/indexer/graph-writer.js.map +1 -0
- package/dist/indexer/import-resolver.d.ts +8 -0
- package/dist/indexer/import-resolver.js +80 -0
- package/dist/indexer/import-resolver.js.map +1 -0
- package/dist/indexer/index.d.ts +21 -0
- package/dist/indexer/index.js +262 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/indexer/language-map.json +101 -0
- package/dist/indexer/parallel-pipeline.d.ts +41 -0
- package/dist/indexer/parallel-pipeline.js +82 -0
- package/dist/indexer/parallel-pipeline.js.map +1 -0
- package/dist/indexer/parser.d.ts +9 -0
- package/dist/indexer/parser.js +85 -0
- package/dist/indexer/parser.js.map +1 -0
- package/dist/indexer/staleness.d.ts +12 -0
- package/dist/indexer/staleness.js +60 -0
- package/dist/indexer/staleness.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +38 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/staleness-check.d.ts +7 -0
- package/dist/mcp/staleness-check.js +31 -0
- package/dist/mcp/staleness-check.js.map +1 -0
- package/dist/mcp/tools/get-callees.d.ts +3 -0
- package/dist/mcp/tools/get-callees.js +34 -0
- package/dist/mcp/tools/get-callees.js.map +1 -0
- package/dist/mcp/tools/get-callers.d.ts +3 -0
- package/dist/mcp/tools/get-callers.js +34 -0
- package/dist/mcp/tools/get-callers.js.map +1 -0
- package/dist/mcp/tools/get-class.d.ts +3 -0
- package/dist/mcp/tools/get-class.js +42 -0
- package/dist/mcp/tools/get-class.js.map +1 -0
- package/dist/mcp/tools/get-dependencies.d.ts +3 -0
- package/dist/mcp/tools/get-dependencies.js +26 -0
- package/dist/mcp/tools/get-dependencies.js.map +1 -0
- package/dist/mcp/tools/get-dependents.d.ts +3 -0
- package/dist/mcp/tools/get-dependents.js +26 -0
- package/dist/mcp/tools/get-dependents.js.map +1 -0
- package/dist/mcp/tools/get-file-structure.d.ts +3 -0
- package/dist/mcp/tools/get-file-structure.js +33 -0
- package/dist/mcp/tools/get-file-structure.js.map +1 -0
- package/dist/mcp/tools/get-function.d.ts +3 -0
- package/dist/mcp/tools/get-function.js +34 -0
- package/dist/mcp/tools/get-function.js.map +1 -0
- package/dist/mcp/tools/get-repo-structure.d.ts +3 -0
- package/dist/mcp/tools/get-repo-structure.js +39 -0
- package/dist/mcp/tools/get-repo-structure.js.map +1 -0
- package/dist/mcp/tools/reindex.d.ts +4 -0
- package/dist/mcp/tools/reindex.js +27 -0
- package/dist/mcp/tools/reindex.js.map +1 -0
- package/dist/mcp/tools/search-code.d.ts +3 -0
- package/dist/mcp/tools/search-code.js +43 -0
- package/dist/mcp/tools/search-code.js.map +1 -0
- package/dist/visualize/public/graph.js +445 -0
- package/dist/visualize/public/index.html +88 -0
- package/dist/visualize/queries.d.ts +14 -0
- package/dist/visualize/queries.js +84 -0
- package/dist/visualize/queries.js.map +1 -0
- package/dist/visualize/server.d.ts +19 -0
- package/dist/visualize/server.js +293 -0
- package/dist/visualize/server.js.map +1 -0
- package/docker-compose.yml +16 -0
- package/package.json +69 -0
- package/src/indexer/language-map.json +128 -0
- package/src/visualize/public/graph.js +445 -0
- package/src/visualize/public/index.html +88 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// src/visualize/queries.ts
|
|
2
|
+
export const INITIAL_LIMIT = 500;
|
|
3
|
+
export const EXPAND_FILE_LIMIT = 100;
|
|
4
|
+
export const EXPAND_FUNCTION_LIMIT = 50;
|
|
5
|
+
export const SEARCH_LIMIT = 25;
|
|
6
|
+
export function repoOverview(repoName) {
|
|
7
|
+
return {
|
|
8
|
+
cypher: `
|
|
9
|
+
MATCH (r:Repository)
|
|
10
|
+
WHERE $repoName IS NULL OR r.name = $repoName
|
|
11
|
+
MATCH (r)-[contains:CONTAINS_FILE]->(f:File)
|
|
12
|
+
OPTIONAL MATCH (f)-[imp:IMPORTS]->(other:File)
|
|
13
|
+
WHERE (r)-[:CONTAINS_FILE]->(other)
|
|
14
|
+
RETURN r, contains, f, imp, other
|
|
15
|
+
LIMIT toInteger($limit)
|
|
16
|
+
`,
|
|
17
|
+
params: { repoName: repoName ?? null, limit: INITIAL_LIMIT },
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function filterByFile(relativePath) {
|
|
21
|
+
return {
|
|
22
|
+
cypher: `
|
|
23
|
+
MATCH (f:File {relativePath: $relativePath})
|
|
24
|
+
OPTIONAL MATCH (f)-[contains:CONTAINS]->(sym)
|
|
25
|
+
WHERE sym:Function OR sym:Class
|
|
26
|
+
OPTIONAL MATCH (sym)-[hasMethod:HAS_METHOD]->(method:Function)
|
|
27
|
+
RETURN f, contains, sym, hasMethod, method
|
|
28
|
+
LIMIT toInteger($limit)
|
|
29
|
+
`,
|
|
30
|
+
params: { relativePath, limit: INITIAL_LIMIT },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function filterByFunction(name) {
|
|
34
|
+
return {
|
|
35
|
+
cypher: `
|
|
36
|
+
MATCH (fn:Function {name: $name})
|
|
37
|
+
OPTIONAL MATCH (file:File)-[contains:CONTAINS]->(fn)
|
|
38
|
+
OPTIONAL MATCH (fn)-[outCall:CALLS]->(callee:Function)
|
|
39
|
+
OPTIONAL MATCH (caller:Function)-[inCall:CALLS]->(fn)
|
|
40
|
+
RETURN fn, file, contains, outCall, callee, inCall, caller
|
|
41
|
+
LIMIT toInteger($limit)
|
|
42
|
+
`,
|
|
43
|
+
params: { name, limit: INITIAL_LIMIT },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function expandFile(filePath) {
|
|
47
|
+
return {
|
|
48
|
+
cypher: `
|
|
49
|
+
MATCH (f:File {path: $filePath})
|
|
50
|
+
MATCH (f)-[contains:CONTAINS]->(sym)
|
|
51
|
+
WHERE sym:Function OR sym:Class
|
|
52
|
+
OPTIONAL MATCH (sym)-[hasMethod:HAS_METHOD]->(method:Function)
|
|
53
|
+
RETURN f, contains, sym, hasMethod, method
|
|
54
|
+
LIMIT toInteger($limit)
|
|
55
|
+
`,
|
|
56
|
+
params: { filePath, limit: EXPAND_FILE_LIMIT },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function expandFunction(name, filePath) {
|
|
60
|
+
return {
|
|
61
|
+
cypher: `
|
|
62
|
+
MATCH (fn:Function {name: $name, filePath: $filePath})
|
|
63
|
+
OPTIONAL MATCH (fn)-[outCall:CALLS]->(callee:Function)
|
|
64
|
+
OPTIONAL MATCH (caller:Function)-[inCall:CALLS]->(fn)
|
|
65
|
+
RETURN fn, outCall, callee, inCall, caller
|
|
66
|
+
LIMIT toInteger($limit)
|
|
67
|
+
`,
|
|
68
|
+
params: { name, filePath, limit: EXPAND_FUNCTION_LIMIT },
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function searchByName(prefix) {
|
|
72
|
+
return {
|
|
73
|
+
cypher: `
|
|
74
|
+
CALL db.index.fulltext.queryNodes('code_search', $q) YIELD node, score
|
|
75
|
+
WITH node, score
|
|
76
|
+
WHERE node:Function OR node:Class
|
|
77
|
+
RETURN node
|
|
78
|
+
ORDER BY score DESC
|
|
79
|
+
LIMIT toInteger($limit)
|
|
80
|
+
`,
|
|
81
|
+
params: { q: `${prefix}*`, limit: SEARCH_LIMIT },
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/visualize/queries.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAO3B,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC;AACjC,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AACrC,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AACxC,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAC;AAE/B,MAAM,UAAU,YAAY,CAAC,QAAiB;IAC5C,OAAO;QACL,MAAM,EAAE;;;;;;;;KAQP;QACD,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ,IAAI,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE;KAC7D,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,YAAoB;IAC/C,OAAO;QACL,MAAM,EAAE;;;;;;;KAOP;QACD,MAAM,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE;KAC/C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO;QACL,MAAM,EAAE;;;;;;;KAOP;QACD,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE;KACvC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,OAAO;QACL,MAAM,EAAE;;;;;;;KAOP;QACD,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,iBAAiB,EAAE;KAC/C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,QAAgB;IAC3D,OAAO;QACL,MAAM,EAAE;;;;;;KAMP;QACD,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,qBAAqB,EAAE;KACzD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,OAAO;QACL,MAAM,EAAE;;;;;;;KAOP;QACD,MAAM,EAAE,EAAE,CAAC,EAAE,GAAG,MAAM,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE;KACjD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Neo4jConfig } from "../config.js";
|
|
2
|
+
interface VisualizationOptions {
|
|
3
|
+
neo4jConfig: Neo4jConfig;
|
|
4
|
+
port: number;
|
|
5
|
+
filter: {
|
|
6
|
+
repo?: string;
|
|
7
|
+
file?: string;
|
|
8
|
+
function?: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export declare function startVisualizationServer(opts: VisualizationOptions): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Test-only variant of startVisualizationServer:
|
|
14
|
+
* - returns the Server instance so tests can close it
|
|
15
|
+
* - does not call openBrowser
|
|
16
|
+
* - throws (does not process.exit) on connection failure
|
|
17
|
+
*/
|
|
18
|
+
export declare function startVisualizationServerForTest(opts: VisualizationOptions): Promise<import("node:http").Server>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// src/visualize/server.ts
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { join, dirname, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import neo4j from "neo4j-driver";
|
|
8
|
+
import { repoOverview, filterByFile, filterByFunction, expandFile, expandFunction, searchByName, } from "./queries.js";
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
async function runCypher(driver, q) {
|
|
11
|
+
const session = driver.session();
|
|
12
|
+
try {
|
|
13
|
+
const result = await session.run(q.cypher, q.params, { timeout: 10000 });
|
|
14
|
+
const nodes = new Map();
|
|
15
|
+
const edges = [];
|
|
16
|
+
for (const record of result.records) {
|
|
17
|
+
for (const key of record.keys) {
|
|
18
|
+
const val = record.get(key);
|
|
19
|
+
if (val && typeof val === "object" && "identity" in val && "labels" in val) {
|
|
20
|
+
const id = val.identity.toString();
|
|
21
|
+
if (!nodes.has(id)) {
|
|
22
|
+
nodes.set(id, {
|
|
23
|
+
id,
|
|
24
|
+
label: val.properties.name || val.properties.relativePath || id,
|
|
25
|
+
group: val.labels?.[0] ?? "Unknown",
|
|
26
|
+
properties: val.properties,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (val && typeof val === "object" && "type" in val && "start" in val && "end" in val) {
|
|
31
|
+
edges.push({
|
|
32
|
+
from: val.start.toString(),
|
|
33
|
+
to: val.end.toString(),
|
|
34
|
+
label: val.type,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { nodes: Array.from(nodes.values()), edges };
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await session.close();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function pickInitialQuery(opts, urlParams) {
|
|
46
|
+
// URL params take precedence over CLI flags so the browser can re-query without restart
|
|
47
|
+
const file = urlParams.get("file") ?? opts.filter.file;
|
|
48
|
+
const fn = urlParams.get("function") ?? opts.filter.function;
|
|
49
|
+
const repo = urlParams.get("repo") ?? opts.filter.repo;
|
|
50
|
+
if (file)
|
|
51
|
+
return filterByFile(file);
|
|
52
|
+
if (fn)
|
|
53
|
+
return filterByFunction(fn);
|
|
54
|
+
return repoOverview(repo);
|
|
55
|
+
}
|
|
56
|
+
async function handleRequest(res, url, driver, opts) {
|
|
57
|
+
const parsedUrl = new URL(url, "http://localhost");
|
|
58
|
+
const pathname = parsedUrl.pathname;
|
|
59
|
+
const params = parsedUrl.searchParams;
|
|
60
|
+
if (pathname === "/api/graph") {
|
|
61
|
+
try {
|
|
62
|
+
const data = await runCypher(driver, pickInitialQuery(opts, params));
|
|
63
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
64
|
+
res.end(JSON.stringify(data));
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.error("[viz] /api/graph error:", err);
|
|
68
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
69
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (pathname === "/api/expand") {
|
|
74
|
+
const type = params.get("type");
|
|
75
|
+
try {
|
|
76
|
+
let q;
|
|
77
|
+
if (type === "file") {
|
|
78
|
+
const filePath = params.get("filePath");
|
|
79
|
+
if (!filePath) {
|
|
80
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
81
|
+
res.end(JSON.stringify({ error: "missing filePath" }));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
q = expandFile(filePath);
|
|
85
|
+
}
|
|
86
|
+
else if (type === "function") {
|
|
87
|
+
const name = params.get("name");
|
|
88
|
+
const filePath = params.get("filePath");
|
|
89
|
+
if (!name || !filePath) {
|
|
90
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
91
|
+
res.end(JSON.stringify({ error: "missing name or filePath" }));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
q = expandFunction(name, filePath);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
98
|
+
res.end(JSON.stringify({ error: `unknown expand type: ${type}` }));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const data = await runCypher(driver, q);
|
|
102
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
103
|
+
res.end(JSON.stringify(data));
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
console.error("[viz] /api/expand error:", err);
|
|
107
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
108
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (pathname === "/api/search") {
|
|
113
|
+
const q = params.get("q");
|
|
114
|
+
if (!q) {
|
|
115
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
116
|
+
res.end(JSON.stringify({ error: "missing q" }));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const data = await runCypher(driver, searchByName(q));
|
|
121
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
122
|
+
res.end(JSON.stringify(data));
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
console.error("[viz] /api/search error:", err);
|
|
126
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
127
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// Serve static files
|
|
132
|
+
const publicDir = join(__dirname, "public");
|
|
133
|
+
const filePath = join(publicDir, pathname === "/" ? "index.html" : pathname);
|
|
134
|
+
const resolved = resolve(filePath);
|
|
135
|
+
const resolvedPublicDir = resolve(publicDir);
|
|
136
|
+
if (!resolved.startsWith(resolvedPublicDir + "/")) {
|
|
137
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
138
|
+
res.end(JSON.stringify({ error: "Forbidden" }));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
console.log(`[viz] serving file: ${filePath}`);
|
|
142
|
+
try {
|
|
143
|
+
const content = readFileSync(filePath);
|
|
144
|
+
const ext = filePath.endsWith(".js") ? "application/javascript" : "text/html";
|
|
145
|
+
console.log(`[viz] -> 200 ${ext} (${content.length} bytes)`);
|
|
146
|
+
res.writeHead(200, { "Content-Type": ext });
|
|
147
|
+
res.end(content);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
console.error(`[viz] -> 404: ${filePath}`, err instanceof Error ? err.message : err);
|
|
151
|
+
res.writeHead(404);
|
|
152
|
+
res.end("Not found");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export async function startVisualizationServer(opts) {
|
|
156
|
+
console.log(`[viz] connecting to Neo4j at ${opts.neo4jConfig.uri}...`);
|
|
157
|
+
const driver = neo4j.driver(opts.neo4jConfig.uri, neo4j.auth.basic(opts.neo4jConfig.username, opts.neo4jConfig.password), { connectionTimeout: 5000 });
|
|
158
|
+
// Verify Neo4j is reachable before starting the HTTP server.
|
|
159
|
+
try {
|
|
160
|
+
const pingSession = driver.session();
|
|
161
|
+
await pingSession.run("RETURN 1", {}, { timeout: 5000 });
|
|
162
|
+
await pingSession.close();
|
|
163
|
+
console.log("[viz] Neo4j connection OK");
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
console.error("[viz] Neo4j connection FAILED:", err);
|
|
167
|
+
console.error(`[viz] Is Neo4j running at ${opts.neo4jConfig.uri}?`);
|
|
168
|
+
await driver.close();
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
const server = createServer((req, res) => {
|
|
172
|
+
const url = req.url ?? "/";
|
|
173
|
+
console.log(`[viz] ${req.method} ${url}`);
|
|
174
|
+
if (url === "/health") {
|
|
175
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
176
|
+
res.end("ok");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
handleRequest(res, url, driver, opts).catch((err) => {
|
|
180
|
+
console.error("[viz] unhandled error:", err);
|
|
181
|
+
if (!res.headersSent) {
|
|
182
|
+
res.writeHead(500);
|
|
183
|
+
res.end("Internal server error");
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
server.on("connection", (socket) => {
|
|
188
|
+
console.log(`[viz] TCP connection from ${socket.remoteAddress}:${socket.remotePort}`);
|
|
189
|
+
});
|
|
190
|
+
server.on("error", (err) => {
|
|
191
|
+
console.error("[viz] server error:", err);
|
|
192
|
+
});
|
|
193
|
+
server.listen(opts.port, "0.0.0.0", () => {
|
|
194
|
+
const url = `http://localhost:${opts.port}`;
|
|
195
|
+
console.log(`Graph visualization running at ${url}`);
|
|
196
|
+
console.log(`[viz] listening on 0.0.0.0:${opts.port}`);
|
|
197
|
+
console.log(`[viz] serving static files from: ${join(__dirname, "public")}`);
|
|
198
|
+
openBrowser(url);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Test-only variant of startVisualizationServer:
|
|
203
|
+
* - returns the Server instance so tests can close it
|
|
204
|
+
* - does not call openBrowser
|
|
205
|
+
* - throws (does not process.exit) on connection failure
|
|
206
|
+
*/
|
|
207
|
+
export async function startVisualizationServerForTest(opts) {
|
|
208
|
+
const driver = neo4j.driver(opts.neo4jConfig.uri, neo4j.auth.basic(opts.neo4jConfig.username, opts.neo4jConfig.password), { connectionTimeout: 5000 });
|
|
209
|
+
const pingSession = driver.session();
|
|
210
|
+
try {
|
|
211
|
+
await pingSession.run("RETURN 1", {}, { timeout: 5000 });
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
await pingSession.close();
|
|
215
|
+
}
|
|
216
|
+
const server = createServer((req, res) => {
|
|
217
|
+
const url = req.url ?? "/";
|
|
218
|
+
if (url === "/health") {
|
|
219
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
220
|
+
res.end("ok");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
handleRequest(res, url, driver, opts).catch((err) => {
|
|
224
|
+
console.error("[viz-test] unhandled error:", err);
|
|
225
|
+
if (!res.headersSent) {
|
|
226
|
+
res.writeHead(500);
|
|
227
|
+
res.end("Internal server error");
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
return new Promise((resolve, reject) => {
|
|
232
|
+
server.once("error", reject);
|
|
233
|
+
server.listen(opts.port, "127.0.0.1", () => resolve(server));
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Launch the user's browser to `url` without ever blocking the Node event loop.
|
|
238
|
+
*
|
|
239
|
+
* Why this exists: the previous implementation used `execFileSync`, which holds
|
|
240
|
+
* the main thread until the child exits. In WSL2 (and other headless Linux
|
|
241
|
+
* environments) `xdg-open` can hang indefinitely searching for a browser, which
|
|
242
|
+
* pins the event loop and starves the HTTP server of request callbacks even
|
|
243
|
+
* though the TCP listener is bound. We MUST spawn detached + ignore stdio +
|
|
244
|
+
* unref so this function returns immediately regardless of what the child does.
|
|
245
|
+
*/
|
|
246
|
+
function openBrowser(url) {
|
|
247
|
+
let command;
|
|
248
|
+
let args;
|
|
249
|
+
if (process.platform === "win32") {
|
|
250
|
+
// The empty "" is a placeholder window title — `start` interprets the
|
|
251
|
+
// first quoted arg as a title, and omitting it breaks URLs with spaces.
|
|
252
|
+
command = "cmd";
|
|
253
|
+
args = ["/c", "start", "", url];
|
|
254
|
+
}
|
|
255
|
+
else if (process.platform === "darwin") {
|
|
256
|
+
command = "open";
|
|
257
|
+
args = [url];
|
|
258
|
+
}
|
|
259
|
+
else if (isWSL()) {
|
|
260
|
+
// WSL2: hand off to the Windows default browser via cmd.exe.
|
|
261
|
+
// The empty "" is `start`'s window-title placeholder (see win32 branch).
|
|
262
|
+
// Falls back to a printed URL via the spawn `error` handler if cmd.exe
|
|
263
|
+
// isn't on PATH (e.g. interop.appendWindowsPath=false in /etc/wsl.conf).
|
|
264
|
+
command = "cmd.exe";
|
|
265
|
+
args = ["/c", "start", "", url];
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
command = "xdg-open";
|
|
269
|
+
args = [url];
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
273
|
+
child.on("error", () => {
|
|
274
|
+
console.log(`Open your browser to: ${url}`);
|
|
275
|
+
});
|
|
276
|
+
child.unref();
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
console.log(`Open your browser to: ${url}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function isWSL() {
|
|
283
|
+
if (process.platform !== "linux")
|
|
284
|
+
return false;
|
|
285
|
+
try {
|
|
286
|
+
const version = readFileSync("/proc/version", "utf8");
|
|
287
|
+
return /microsoft/i.test(version);
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/visualize/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
services:
|
|
2
|
+
neo4j:
|
|
3
|
+
image: neo4j:5-community
|
|
4
|
+
container_name: code-graph-rag-neo4j
|
|
5
|
+
ports:
|
|
6
|
+
- "7474:7474"
|
|
7
|
+
- "7687:7687"
|
|
8
|
+
environment:
|
|
9
|
+
NEO4J_AUTH: neo4j/code-graph-rag
|
|
10
|
+
NEO4J_PLUGINS: '[]'
|
|
11
|
+
volumes:
|
|
12
|
+
- code-graph-rag-data:/data
|
|
13
|
+
restart: unless-stopped
|
|
14
|
+
|
|
15
|
+
volumes:
|
|
16
|
+
code-graph-rag-data:
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rho-graph",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Graph-RAG code indexer for AI agents — token-efficient code search via MCP tools backed by Neo4j",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"rho-graph": "bin/rho-graph.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc && cp src/indexer/language-map.json dist/indexer/language-map.json && cp -r src/visualize/public dist/visualize/",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"lint": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"graph-rag",
|
|
20
|
+
"code-search",
|
|
21
|
+
"neo4j",
|
|
22
|
+
"tree-sitter",
|
|
23
|
+
"ai",
|
|
24
|
+
"claude",
|
|
25
|
+
"cursor",
|
|
26
|
+
"llm",
|
|
27
|
+
"code-intelligence"
|
|
28
|
+
],
|
|
29
|
+
"author": "rhofield",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/rhofield/rho-graph.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/rhofield/rho-graph#readme",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/rhofield/rho-graph/issues"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@anthropic-ai/sdk": "^0.39.0",
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
42
|
+
"commander": "^12.1.0",
|
|
43
|
+
"glob": "^11.0.1",
|
|
44
|
+
"neo4j-driver": "^5.27.0",
|
|
45
|
+
"ora": "^8.1.1",
|
|
46
|
+
"tree-sitter-wasms": "^0.1.13",
|
|
47
|
+
"web-tree-sitter": "^0.24.3",
|
|
48
|
+
"zod": "^3.23.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^22.10.0",
|
|
52
|
+
"typescript": "^5.7.0",
|
|
53
|
+
"vitest": "^2.1.0"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18"
|
|
57
|
+
},
|
|
58
|
+
"files": [
|
|
59
|
+
"dist",
|
|
60
|
+
"bin",
|
|
61
|
+
"src/indexer/language-map.json",
|
|
62
|
+
"src/visualize/public",
|
|
63
|
+
"docker-compose.yml"
|
|
64
|
+
],
|
|
65
|
+
"directories": {
|
|
66
|
+
"doc": "docs",
|
|
67
|
+
"test": "tests"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"typescript": {
|
|
3
|
+
"function": ["function_declaration", "arrow_function", "method_definition"],
|
|
4
|
+
"class": ["class_declaration"],
|
|
5
|
+
"import": ["import_statement"],
|
|
6
|
+
"call": ["call_expression"],
|
|
7
|
+
"name_field": "name",
|
|
8
|
+
"body_field": "body",
|
|
9
|
+
"parameters_field": "parameters"
|
|
10
|
+
},
|
|
11
|
+
"tsx": {
|
|
12
|
+
"function": ["function_declaration", "arrow_function", "method_definition"],
|
|
13
|
+
"class": ["class_declaration"],
|
|
14
|
+
"import": ["import_statement"],
|
|
15
|
+
"call": ["call_expression"],
|
|
16
|
+
"name_field": "name",
|
|
17
|
+
"body_field": "body",
|
|
18
|
+
"parameters_field": "parameters"
|
|
19
|
+
},
|
|
20
|
+
"javascript": {
|
|
21
|
+
"function": ["function_declaration", "arrow_function", "method_definition"],
|
|
22
|
+
"class": ["class_declaration"],
|
|
23
|
+
"import": ["import_statement"],
|
|
24
|
+
"call": ["call_expression"],
|
|
25
|
+
"name_field": "name",
|
|
26
|
+
"body_field": "body",
|
|
27
|
+
"parameters_field": "parameters"
|
|
28
|
+
},
|
|
29
|
+
"python": {
|
|
30
|
+
"function": ["function_definition"],
|
|
31
|
+
"class": ["class_definition"],
|
|
32
|
+
"import": ["import_from_statement", "import_statement"],
|
|
33
|
+
"call": ["call"],
|
|
34
|
+
"name_field": "name",
|
|
35
|
+
"body_field": "body",
|
|
36
|
+
"parameters_field": "parameters"
|
|
37
|
+
},
|
|
38
|
+
"go": {
|
|
39
|
+
"function": ["function_declaration", "method_declaration"],
|
|
40
|
+
"class": ["type_declaration"],
|
|
41
|
+
"import": ["import_declaration", "import_spec"],
|
|
42
|
+
"call": ["call_expression"],
|
|
43
|
+
"name_field": "name",
|
|
44
|
+
"body_field": "body",
|
|
45
|
+
"parameters_field": "parameters"
|
|
46
|
+
},
|
|
47
|
+
"java": {
|
|
48
|
+
"function": ["method_declaration", "constructor_declaration"],
|
|
49
|
+
"class": ["class_declaration", "interface_declaration", "enum_declaration"],
|
|
50
|
+
"import": ["import_declaration"],
|
|
51
|
+
"call": ["method_invocation"],
|
|
52
|
+
"name_field": "name",
|
|
53
|
+
"body_field": "body",
|
|
54
|
+
"parameters_field": "parameters"
|
|
55
|
+
},
|
|
56
|
+
"rust": {
|
|
57
|
+
"function": ["function_item"],
|
|
58
|
+
"class": ["struct_item", "enum_item", "trait_item", "impl_item"],
|
|
59
|
+
"import": ["use_declaration"],
|
|
60
|
+
"call": ["call_expression"],
|
|
61
|
+
"name_field": "name",
|
|
62
|
+
"body_field": "body",
|
|
63
|
+
"parameters_field": "parameters"
|
|
64
|
+
},
|
|
65
|
+
"c": {
|
|
66
|
+
"function": ["function_definition"],
|
|
67
|
+
"class": ["struct_specifier"],
|
|
68
|
+
"import": ["preproc_include"],
|
|
69
|
+
"call": ["call_expression"],
|
|
70
|
+
"name_field": "declarator",
|
|
71
|
+
"body_field": "body",
|
|
72
|
+
"parameters_field": "parameters"
|
|
73
|
+
},
|
|
74
|
+
"cpp": {
|
|
75
|
+
"function": ["function_definition"],
|
|
76
|
+
"class": ["class_specifier", "struct_specifier"],
|
|
77
|
+
"import": ["preproc_include"],
|
|
78
|
+
"call": ["call_expression"],
|
|
79
|
+
"name_field": "declarator",
|
|
80
|
+
"body_field": "body",
|
|
81
|
+
"parameters_field": "parameters"
|
|
82
|
+
},
|
|
83
|
+
"ruby": {
|
|
84
|
+
"function": ["method", "singleton_method"],
|
|
85
|
+
"class": ["class", "module"],
|
|
86
|
+
"import": ["call"],
|
|
87
|
+
"call": ["call", "method_call"],
|
|
88
|
+
"name_field": "name",
|
|
89
|
+
"body_field": "body",
|
|
90
|
+
"parameters_field": "parameters"
|
|
91
|
+
},
|
|
92
|
+
"graphql": {
|
|
93
|
+
"function": ["operation_definition", "fragment_definition"],
|
|
94
|
+
"class": ["object_type_definition", "input_object_type_definition", "interface_type_definition", "enum_type_definition"],
|
|
95
|
+
"import": [],
|
|
96
|
+
"call": [],
|
|
97
|
+
"name_field": "name",
|
|
98
|
+
"body_field": "body",
|
|
99
|
+
"parameters_field": "arguments_definition"
|
|
100
|
+
},
|
|
101
|
+
"yaml": {
|
|
102
|
+
"function": [],
|
|
103
|
+
"class": [],
|
|
104
|
+
"import": [],
|
|
105
|
+
"call": [],
|
|
106
|
+
"name_field": "name",
|
|
107
|
+
"body_field": "body",
|
|
108
|
+
"parameters_field": "parameters"
|
|
109
|
+
},
|
|
110
|
+
"json": {
|
|
111
|
+
"function": [],
|
|
112
|
+
"class": [],
|
|
113
|
+
"import": [],
|
|
114
|
+
"call": [],
|
|
115
|
+
"name_field": "name",
|
|
116
|
+
"body_field": "body",
|
|
117
|
+
"parameters_field": "parameters"
|
|
118
|
+
},
|
|
119
|
+
"markdown": {
|
|
120
|
+
"function": [],
|
|
121
|
+
"class": [],
|
|
122
|
+
"import": [],
|
|
123
|
+
"call": [],
|
|
124
|
+
"name_field": "name",
|
|
125
|
+
"body_field": "body",
|
|
126
|
+
"parameters_field": "parameters"
|
|
127
|
+
}
|
|
128
|
+
}
|