code-graph-context 3.0.7 → 3.0.9
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/dist/mcp/constants.js +6 -3
- package/dist/mcp/handlers/graph-generator.handler.js +19 -14
- package/dist/mcp/handlers/incremental-parse.handler.js +2 -2
- package/dist/mcp/handlers/parallel-import.handler.js +1 -1
- package/dist/mcp/handlers/streaming-import.handler.js +2 -3
- package/dist/mcp/tools/parse-typescript-project.tool.js +2 -2
- package/dist/storage/neo4j/neo4j.service.js +4 -2
- package/package.json +1 -1
package/dist/mcp/constants.js
CHANGED
|
@@ -199,7 +199,10 @@ Actions: send (post or broadcast), read (retrieve), acknowledge (mark read). Cat
|
|
|
199
199
|
export const DEFAULTS = {
|
|
200
200
|
traversalDepth: 3,
|
|
201
201
|
skipOffset: 0,
|
|
202
|
-
|
|
202
|
+
/** Batch size for node imports (larger payloads due to embeddings) */
|
|
203
|
+
nodeBatchSize: 500,
|
|
204
|
+
/** Batch size for edge imports (lightweight — just IDs and type) */
|
|
205
|
+
edgeBatchSize: 2000,
|
|
203
206
|
maxResultsDisplayed: 30,
|
|
204
207
|
codeSnippetLength: 500, // Reduced from 1000 to control output size
|
|
205
208
|
chainSnippetLength: 700,
|
|
@@ -213,8 +216,8 @@ export const PARSING = {
|
|
|
213
216
|
streamingThreshold: 100,
|
|
214
217
|
/** Default number of files per chunk */
|
|
215
218
|
defaultChunkSize: 50,
|
|
216
|
-
/** Worker timeout in milliseconds (
|
|
217
|
-
workerTimeoutMs: 60 * 60 * 1000,
|
|
219
|
+
/** Worker timeout in milliseconds (default: 8 hours, configurable via WORKER_TIMEOUT_HOURS) */
|
|
220
|
+
workerTimeoutMs: (Number(process.env.WORKER_TIMEOUT_HOURS) || 8) * 60 * 60 * 1000,
|
|
218
221
|
};
|
|
219
222
|
// Job Management
|
|
220
223
|
export const JOBS = {
|
|
@@ -18,6 +18,7 @@ export class GraphGeneratorHandler {
|
|
|
18
18
|
neo4jService;
|
|
19
19
|
embeddingsService;
|
|
20
20
|
static EMBEDDED_LABEL = 'Embedded';
|
|
21
|
+
static GRAPH_NODE_LABEL = 'GraphNode';
|
|
21
22
|
projectId = null;
|
|
22
23
|
constructor(neo4jService, embeddingsService) {
|
|
23
24
|
this.neo4jService = neo4jService;
|
|
@@ -29,10 +30,10 @@ export class GraphGeneratorHandler {
|
|
|
29
30
|
setProjectId(projectId) {
|
|
30
31
|
this.projectId = projectId;
|
|
31
32
|
}
|
|
32
|
-
async generateGraph(graphJsonPath,
|
|
33
|
+
async generateGraph(graphJsonPath, clearExisting = true) {
|
|
33
34
|
console.error(`Generating graph from JSON file: ${graphJsonPath}`);
|
|
34
35
|
const graphData = await this.loadGraphData(graphJsonPath);
|
|
35
|
-
return this.generateGraphFromData(graphData.nodes, graphData.edges,
|
|
36
|
+
return this.generateGraphFromData(graphData.nodes, graphData.edges, clearExisting, graphData.metadata);
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
38
39
|
* Import nodes and edges directly from in-memory data.
|
|
@@ -41,11 +42,12 @@ export class GraphGeneratorHandler {
|
|
|
41
42
|
* @param skipIndexes - When true, skips index creation (caller manages indexes).
|
|
42
43
|
* Use this for chunked imports where indexes are created once before/after all chunks.
|
|
43
44
|
*/
|
|
44
|
-
async generateGraphFromData(nodes, edges,
|
|
45
|
+
async generateGraphFromData(nodes, edges, clearExisting = true, metadata = {}, skipIndexes = false) {
|
|
45
46
|
await debugLog('Starting graph generation', {
|
|
46
47
|
nodeCount: nodes.length,
|
|
47
48
|
edgeCount: edges.length,
|
|
48
|
-
|
|
49
|
+
nodeBatchSize: DEFAULTS.nodeBatchSize,
|
|
50
|
+
edgeBatchSize: DEFAULTS.edgeBatchSize,
|
|
49
51
|
clearExisting,
|
|
50
52
|
skipIndexes,
|
|
51
53
|
projectId: this.projectId,
|
|
@@ -58,8 +60,8 @@ export class GraphGeneratorHandler {
|
|
|
58
60
|
if (!skipIndexes) {
|
|
59
61
|
await this.createProjectIndexes();
|
|
60
62
|
}
|
|
61
|
-
await this.importNodes(nodes
|
|
62
|
-
await this.importEdges(edges
|
|
63
|
+
await this.importNodes(nodes);
|
|
64
|
+
await this.importEdges(edges);
|
|
63
65
|
if (!skipIndexes) {
|
|
64
66
|
await this.createVectorIndexes();
|
|
65
67
|
}
|
|
@@ -77,7 +79,7 @@ export class GraphGeneratorHandler {
|
|
|
77
79
|
throw error;
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
|
-
async ingestConfigFiles(projectPath, projectId, globs, excludeGlobs, maxFileSizeBytes
|
|
82
|
+
async ingestConfigFiles(projectPath, projectId, globs, excludeGlobs, maxFileSizeBytes) {
|
|
81
83
|
const files = await glob(globs, {
|
|
82
84
|
cwd: projectPath,
|
|
83
85
|
ignore: [...EXCLUDE_PATTERNS_GLOB, ...excludeGlobs],
|
|
@@ -129,7 +131,7 @@ export class GraphGeneratorHandler {
|
|
|
129
131
|
});
|
|
130
132
|
}
|
|
131
133
|
console.error(`[config-ingest] Importing ${nodes.length} config file nodes`);
|
|
132
|
-
await this.importNodes(nodes
|
|
134
|
+
await this.importNodes(nodes);
|
|
133
135
|
return { nodesCreated: nodes.length };
|
|
134
136
|
}
|
|
135
137
|
/**
|
|
@@ -161,14 +163,16 @@ export class GraphGeneratorHandler {
|
|
|
161
163
|
await this.neo4jService.run(QUERIES.CREATE_PROJECT_INDEX_SOURCEFILE);
|
|
162
164
|
await this.neo4jService.run(QUERIES.CREATE_PROJECT_ID_INDEX_EMBEDDED);
|
|
163
165
|
await this.neo4jService.run(QUERIES.CREATE_PROJECT_ID_INDEX_SOURCEFILE);
|
|
166
|
+
await this.neo4jService.run(QUERIES.CREATE_PROJECT_ID_INDEX_GRAPHNODE);
|
|
164
167
|
await this.neo4jService.run(QUERIES.CREATE_NORMALIZED_HASH_INDEX);
|
|
165
168
|
await this.neo4jService.run(QUERIES.CREATE_SESSION_BOOKMARK_INDEX);
|
|
166
169
|
await this.neo4jService.run(QUERIES.CREATE_SESSION_NOTE_INDEX);
|
|
167
170
|
await this.neo4jService.run(QUERIES.CREATE_SESSION_NOTE_CATEGORY_INDEX);
|
|
168
171
|
await debugLog('Project indexes created');
|
|
169
172
|
}
|
|
170
|
-
async importNodes(nodes
|
|
171
|
-
|
|
173
|
+
async importNodes(nodes) {
|
|
174
|
+
const batchSize = DEFAULTS.nodeBatchSize;
|
|
175
|
+
console.error(`Importing ${nodes.length} nodes with embeddings (batch size: ${batchSize})...`);
|
|
172
176
|
// Pipelined: write batch N to Neo4j while embedding batch N+1.
|
|
173
177
|
// This overlaps GPU work with Neo4j I/O.
|
|
174
178
|
let pendingWrite = null;
|
|
@@ -218,7 +222,7 @@ export class GraphGeneratorHandler {
|
|
|
218
222
|
// Node doesn't need embedding - prepare it immediately
|
|
219
223
|
nodeResults[index] = {
|
|
220
224
|
...node,
|
|
221
|
-
labels: node.labels,
|
|
225
|
+
labels: [...node.labels, GraphGeneratorHandler.GRAPH_NODE_LABEL],
|
|
222
226
|
properties: {
|
|
223
227
|
...this.flattenProperties(node.properties),
|
|
224
228
|
embedding: null,
|
|
@@ -242,7 +246,7 @@ export class GraphGeneratorHandler {
|
|
|
242
246
|
embeddedCount++;
|
|
243
247
|
nodeResults[item.index] = {
|
|
244
248
|
...item.node,
|
|
245
|
-
labels: embedding ? [...item.node.labels, GraphGeneratorHandler.EMBEDDED_LABEL] : item.node.labels,
|
|
249
|
+
labels: embedding ? [...item.node.labels, GraphGeneratorHandler.EMBEDDED_LABEL, GraphGeneratorHandler.GRAPH_NODE_LABEL] : [...item.node.labels, GraphGeneratorHandler.GRAPH_NODE_LABEL],
|
|
246
250
|
properties: {
|
|
247
251
|
...this.flattenProperties(item.node.properties),
|
|
248
252
|
embedding,
|
|
@@ -265,8 +269,9 @@ export class GraphGeneratorHandler {
|
|
|
265
269
|
}
|
|
266
270
|
return nodeResults;
|
|
267
271
|
}
|
|
268
|
-
async importEdges(edges
|
|
269
|
-
|
|
272
|
+
async importEdges(edges) {
|
|
273
|
+
const batchSize = DEFAULTS.edgeBatchSize;
|
|
274
|
+
console.error(`Importing ${edges.length} edges using APOC (batch size: ${batchSize})...`);
|
|
270
275
|
for (let i = 0; i < edges.length; i += batchSize) {
|
|
271
276
|
const batch = edges.slice(i, i + batchSize).map((edge) => ({
|
|
272
277
|
...edge,
|
|
@@ -10,7 +10,7 @@ import { ParserFactory } from '../../core/parsers/parser-factory.js';
|
|
|
10
10
|
import { detectChangedFiles } from '../../core/utils/file-change-detection.js';
|
|
11
11
|
import { resolveProjectId, getProjectName, UPSERT_PROJECT_QUERY } from '../../core/utils/project-id.js';
|
|
12
12
|
import { Neo4jService, QUERIES } from '../../storage/neo4j/neo4j.service.js';
|
|
13
|
-
import {
|
|
13
|
+
import { FILE_PATHS, LOG_CONFIG } from '../constants.js';
|
|
14
14
|
import { debugLog } from '../utils.js';
|
|
15
15
|
import { deleteSourceFileSubgraphs, loadExistingNodesForEdgeDetection, getCrossFileEdges, } from './cross-file-edge.helpers.js';
|
|
16
16
|
import { GraphGeneratorHandler } from './graph-generator.handler.js';
|
|
@@ -109,7 +109,7 @@ export const performIncrementalParse = async (projectPath, projectId, tsconfigPa
|
|
|
109
109
|
await debugLog('Incremental parse: starting graph import', {});
|
|
110
110
|
graphHandler.setProjectId(resolvedId);
|
|
111
111
|
try {
|
|
112
|
-
const result = await graphHandler.generateGraph(outputPath,
|
|
112
|
+
const result = await graphHandler.generateGraph(outputPath, false);
|
|
113
113
|
nodesImported = result.nodesImported;
|
|
114
114
|
edgesImported = result.edgesImported;
|
|
115
115
|
await debugLog('Incremental parse: graph import completed', { nodesImported, edgesImported });
|
|
@@ -117,6 +117,6 @@ export class ParallelImportHandler {
|
|
|
117
117
|
async importToNeo4j(nodes, edges) {
|
|
118
118
|
if (nodes.length === 0 && edges.length === 0)
|
|
119
119
|
return;
|
|
120
|
-
await this.graphGeneratorHandler.generateGraphFromData(nodes, edges,
|
|
120
|
+
await this.graphGeneratorHandler.generateGraphFromData(nodes, edges, false, {}, true);
|
|
121
121
|
}
|
|
122
122
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Orchestrates chunked parsing and import for large codebases
|
|
4
4
|
*/
|
|
5
5
|
import { ProgressReporter } from '../../core/utils/progress-reporter.js';
|
|
6
|
-
import { DEFAULTS } from '../constants.js';
|
|
7
6
|
import { debugLog } from '../utils.js';
|
|
8
7
|
export class StreamingImportHandler {
|
|
9
8
|
graphGeneratorHandler;
|
|
@@ -120,11 +119,11 @@ export class StreamingImportHandler {
|
|
|
120
119
|
return result;
|
|
121
120
|
}
|
|
122
121
|
async importChunkToNeo4j(nodes, edges) {
|
|
123
|
-
await this.graphGeneratorHandler.generateGraphFromData(nodes, edges,
|
|
122
|
+
await this.graphGeneratorHandler.generateGraphFromData(nodes, edges, false, {}, true);
|
|
124
123
|
}
|
|
125
124
|
async importEdgesToNeo4j(edges) {
|
|
126
125
|
if (edges.length === 0)
|
|
127
126
|
return;
|
|
128
|
-
await this.graphGeneratorHandler.generateGraphFromData([], edges,
|
|
127
|
+
await this.graphGeneratorHandler.generateGraphFromData([], edges, false, {}, true);
|
|
129
128
|
}
|
|
130
129
|
}
|
|
@@ -16,7 +16,7 @@ import { ParserFactory } from '../../core/parsers/parser-factory.js';
|
|
|
16
16
|
import { detectChangedFiles } from '../../core/utils/file-change-detection.js';
|
|
17
17
|
import { resolveProjectId, getProjectName, UPSERT_PROJECT_QUERY, UPDATE_PROJECT_STATUS_QUERY, } from '../../core/utils/project-id.js';
|
|
18
18
|
import { Neo4jService, QUERIES } from '../../storage/neo4j/neo4j.service.js';
|
|
19
|
-
import { TOOL_NAMES, TOOL_METADATA,
|
|
19
|
+
import { TOOL_NAMES, TOOL_METADATA, FILE_PATHS, LOG_CONFIG, PARSING, CONFIG_FILE_PATTERNS, } from '../constants.js';
|
|
20
20
|
import { deleteSourceFileSubgraphs, loadExistingNodesForEdgeDetection, getCrossFileEdges, } from '../handlers/cross-file-edge.helpers.js';
|
|
21
21
|
import { GraphGeneratorHandler } from '../handlers/graph-generator.handler.js';
|
|
22
22
|
import { StreamingImportHandler } from '../handlers/streaming-import.handler.js';
|
|
@@ -315,7 +315,7 @@ export const createParseTypescriptProjectTool = (server) => {
|
|
|
315
315
|
try {
|
|
316
316
|
// Set projectId for project-scoped operations (clear, indexes)
|
|
317
317
|
graphGeneratorHandler.setProjectId(finalProjectId);
|
|
318
|
-
const result = await graphGeneratorHandler.generateGraph(outputPath,
|
|
318
|
+
const result = await graphGeneratorHandler.generateGraph(outputPath, clearExisting);
|
|
319
319
|
// Recreate cross-file edges after incremental parse
|
|
320
320
|
if (!clearExisting && savedCrossFileEdges.length > 0) {
|
|
321
321
|
await debugLog('Recreating cross-file edges', { edgesToRecreate: savedCrossFileEdges.length });
|
|
@@ -87,6 +87,8 @@ export const QUERIES = {
|
|
|
87
87
|
// Create composite indexes on projectId + id for efficient lookups
|
|
88
88
|
CREATE_PROJECT_ID_INDEX_EMBEDDED: 'CREATE INDEX project_id_embedded_idx IF NOT EXISTS FOR (n:Embedded) ON (n.projectId, n.id)',
|
|
89
89
|
CREATE_PROJECT_ID_INDEX_SOURCEFILE: 'CREATE INDEX project_id_sourcefile_idx IF NOT EXISTS FOR (n:SourceFile) ON (n.projectId, n.id)',
|
|
90
|
+
// Create composite index on GraphNode for efficient edge resolution lookups
|
|
91
|
+
CREATE_PROJECT_ID_INDEX_GRAPHNODE: 'CREATE INDEX project_id_graphnode_idx IF NOT EXISTS FOR (n:GraphNode) ON (n.projectId, n.id)',
|
|
90
92
|
// Create index on normalizedHash for efficient structural duplicate detection
|
|
91
93
|
CREATE_NORMALIZED_HASH_INDEX: 'CREATE INDEX normalized_hash_idx IF NOT EXISTS FOR (n:Embedded) ON (n.normalizedHash)',
|
|
92
94
|
CREATE_NODE: `
|
|
@@ -96,8 +98,8 @@ export const QUERIES = {
|
|
|
96
98
|
`,
|
|
97
99
|
CREATE_RELATIONSHIP: `
|
|
98
100
|
UNWIND $edges AS edgeData
|
|
99
|
-
MATCH (start) WHERE start.id = edgeData.startNodeId AND start.projectId = $projectId
|
|
100
|
-
MATCH (end) WHERE end.id = edgeData.endNodeId AND end.projectId = $projectId
|
|
101
|
+
MATCH (start:GraphNode) WHERE start.id = edgeData.startNodeId AND start.projectId = $projectId
|
|
102
|
+
MATCH (end:GraphNode) WHERE end.id = edgeData.endNodeId AND end.projectId = $projectId
|
|
101
103
|
WITH start, end, edgeData
|
|
102
104
|
CALL apoc.create.relationship(start, edgeData.type, edgeData.properties, end) YIELD rel
|
|
103
105
|
RETURN count(*) as created
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-graph-context",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.9",
|
|
4
4
|
"description": "MCP server that builds code graphs to provide rich context to LLMs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://github.com/andrew-hernandez-paragon/code-graph-context#readme",
|