code-graph-context 1.1.0 → 2.0.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 +221 -101
- package/dist/core/config/fairsquare-framework-schema.js +47 -60
- package/dist/core/config/nestjs-framework-schema.js +11 -1
- package/dist/core/config/schema.js +1 -1
- package/dist/core/config/timeouts.js +27 -0
- package/dist/core/embeddings/embeddings.service.js +122 -2
- package/dist/core/embeddings/natural-language-to-cypher.service.js +416 -17
- package/dist/core/parsers/parser-factory.js +5 -3
- package/dist/core/parsers/typescript-parser.js +614 -45
- package/dist/core/parsers/workspace-parser.js +553 -0
- package/dist/core/utils/edge-factory.js +37 -0
- package/dist/core/utils/file-change-detection.js +105 -0
- package/dist/core/utils/file-utils.js +20 -0
- package/dist/core/utils/index.js +3 -0
- package/dist/core/utils/path-utils.js +75 -0
- package/dist/core/utils/progress-reporter.js +112 -0
- package/dist/core/utils/project-id.js +176 -0
- package/dist/core/utils/retry.js +41 -0
- package/dist/core/workspace/index.js +4 -0
- package/dist/core/workspace/workspace-detector.js +221 -0
- package/dist/mcp/constants.js +153 -5
- package/dist/mcp/handlers/cross-file-edge.helpers.js +19 -0
- package/dist/mcp/handlers/file-change-detection.js +105 -0
- package/dist/mcp/handlers/graph-generator.handler.js +97 -32
- package/dist/mcp/handlers/incremental-parse.handler.js +146 -0
- package/dist/mcp/handlers/streaming-import.handler.js +210 -0
- package/dist/mcp/handlers/traversal.handler.js +130 -71
- package/dist/mcp/mcp.server.js +45 -6
- package/dist/mcp/service-init.js +79 -0
- package/dist/mcp/services/job-manager.js +165 -0
- package/dist/mcp/services/watch-manager.js +376 -0
- package/dist/mcp/services.js +2 -2
- package/dist/mcp/tools/check-parse-status.tool.js +64 -0
- package/dist/mcp/tools/impact-analysis.tool.js +84 -18
- package/dist/mcp/tools/index.js +13 -1
- package/dist/mcp/tools/list-projects.tool.js +62 -0
- package/dist/mcp/tools/list-watchers.tool.js +51 -0
- package/dist/mcp/tools/natural-language-to-cypher.tool.js +34 -8
- package/dist/mcp/tools/parse-typescript-project.tool.js +318 -58
- package/dist/mcp/tools/search-codebase.tool.js +56 -16
- package/dist/mcp/tools/start-watch-project.tool.js +100 -0
- package/dist/mcp/tools/stop-watch-project.tool.js +49 -0
- package/dist/mcp/tools/traverse-from-node.tool.js +68 -9
- package/dist/mcp/utils.js +35 -13
- package/dist/mcp/workers/parse-worker.js +198 -0
- package/dist/storage/neo4j/neo4j.service.js +147 -48
- package/package.json +4 -2
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List Projects Tool
|
|
3
|
+
* Lists all parsed projects in the database
|
|
4
|
+
*/
|
|
5
|
+
import { LIST_PROJECTS_QUERY } from '../../core/utils/project-id.js';
|
|
6
|
+
import { Neo4jService } from '../../storage/neo4j/neo4j.service.js';
|
|
7
|
+
import { TOOL_NAMES, TOOL_METADATA } from '../constants.js';
|
|
8
|
+
import { createErrorResponse, createSuccessResponse, debugLog } from '../utils.js';
|
|
9
|
+
export const createListProjectsTool = (server) => {
|
|
10
|
+
server.registerTool(TOOL_NAMES.listProjects, {
|
|
11
|
+
title: TOOL_METADATA[TOOL_NAMES.listProjects].title,
|
|
12
|
+
description: TOOL_METADATA[TOOL_NAMES.listProjects].description,
|
|
13
|
+
inputSchema: {},
|
|
14
|
+
}, async () => {
|
|
15
|
+
const neo4jService = new Neo4jService();
|
|
16
|
+
try {
|
|
17
|
+
await debugLog('Listing projects');
|
|
18
|
+
const results = await neo4jService.run(LIST_PROJECTS_QUERY, {});
|
|
19
|
+
if (results.length === 0) {
|
|
20
|
+
return createSuccessResponse('No projects found. Use parse_typescript_project to add a project first.');
|
|
21
|
+
}
|
|
22
|
+
const projects = results.map((r) => ({
|
|
23
|
+
projectId: r.projectId,
|
|
24
|
+
name: r.name,
|
|
25
|
+
path: r.path,
|
|
26
|
+
status: r.status ?? 'unknown',
|
|
27
|
+
nodeCount: r.nodeCount,
|
|
28
|
+
edgeCount: r.edgeCount,
|
|
29
|
+
updatedAt: r.updatedAt?.toString() ?? 'Unknown',
|
|
30
|
+
}));
|
|
31
|
+
await debugLog('Projects listed', { count: projects.length });
|
|
32
|
+
// Format output for readability
|
|
33
|
+
const header = `Found ${projects.length} project(s):\n\n`;
|
|
34
|
+
const formatStats = (p) => {
|
|
35
|
+
if (p.status === 'complete' && p.nodeCount !== null) {
|
|
36
|
+
return ` Stats: ${p.nodeCount} nodes, ${p.edgeCount ?? 0} edges`;
|
|
37
|
+
}
|
|
38
|
+
return '';
|
|
39
|
+
};
|
|
40
|
+
const projectList = projects
|
|
41
|
+
.map((p) => `- ${p.name} [${p.status}]\n` +
|
|
42
|
+
` ID: ${p.projectId}\n` +
|
|
43
|
+
` Path: ${p.path}\n` +
|
|
44
|
+
formatStats(p) +
|
|
45
|
+
(formatStats(p) ? '\n' : '') +
|
|
46
|
+
` Updated: ${p.updatedAt}`)
|
|
47
|
+
.join('\n\n');
|
|
48
|
+
const tip = '\n\nTip: Use the project name (e.g., "' +
|
|
49
|
+
projects[0].name +
|
|
50
|
+
'") in other tools instead of the full projectId.';
|
|
51
|
+
return createSuccessResponse(header + projectList + tip);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error('List projects error:', error);
|
|
55
|
+
await debugLog('List projects error', { error });
|
|
56
|
+
return createErrorResponse(error);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
await neo4jService.close();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List Watchers Tool
|
|
3
|
+
* Lists all active file watchers
|
|
4
|
+
*/
|
|
5
|
+
import { TOOL_NAMES, TOOL_METADATA } from '../constants.js';
|
|
6
|
+
import { watchManager } from '../services/watch-manager.js';
|
|
7
|
+
import { createErrorResponse, createSuccessResponse, debugLog } from '../utils.js';
|
|
8
|
+
export const createListWatchersTool = (server) => {
|
|
9
|
+
server.registerTool(TOOL_NAMES.listWatchers, {
|
|
10
|
+
title: TOOL_METADATA[TOOL_NAMES.listWatchers].title,
|
|
11
|
+
description: TOOL_METADATA[TOOL_NAMES.listWatchers].description,
|
|
12
|
+
inputSchema: {},
|
|
13
|
+
}, async () => {
|
|
14
|
+
try {
|
|
15
|
+
await debugLog('Listing watchers');
|
|
16
|
+
const watchers = watchManager.listWatchers();
|
|
17
|
+
if (watchers.length === 0) {
|
|
18
|
+
return createSuccessResponse('No active file watchers.\n\n' +
|
|
19
|
+
'To start watching a project:\n' +
|
|
20
|
+
'- Use start_watch_project with a projectId\n' +
|
|
21
|
+
'- Or use parse_typescript_project with watch: true (requires async: false)');
|
|
22
|
+
}
|
|
23
|
+
await debugLog('Watchers listed', { count: watchers.length });
|
|
24
|
+
const header = `Found ${watchers.length} active watcher(s):\n\n`;
|
|
25
|
+
const watcherList = watchers
|
|
26
|
+
.map((w) => {
|
|
27
|
+
const lines = [
|
|
28
|
+
`- ${w.projectId} [${w.status}]`,
|
|
29
|
+
` Path: ${w.projectPath}`,
|
|
30
|
+
` Debounce: ${w.debounceMs}ms`,
|
|
31
|
+
` Pending changes: ${w.pendingChanges}`,
|
|
32
|
+
];
|
|
33
|
+
if (w.lastUpdateTime) {
|
|
34
|
+
lines.push(` Last update: ${w.lastUpdateTime}`);
|
|
35
|
+
}
|
|
36
|
+
if (w.errorMessage) {
|
|
37
|
+
lines.push(` Error: ${w.errorMessage}`);
|
|
38
|
+
}
|
|
39
|
+
return lines.join('\n');
|
|
40
|
+
})
|
|
41
|
+
.join('\n\n');
|
|
42
|
+
const tip = '\n\nUse stop_watch_project with a project ID to stop watching.';
|
|
43
|
+
return createSuccessResponse(header + watcherList + tip);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error('List watchers error:', error);
|
|
47
|
+
await debugLog('List watchers error', { error });
|
|
48
|
+
return createErrorResponse(error instanceof Error ? error : new Error(String(error)));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
|
@@ -6,7 +6,7 @@ import { z } from 'zod';
|
|
|
6
6
|
import { NaturalLanguageToCypherService } from '../../core/embeddings/natural-language-to-cypher.service.js';
|
|
7
7
|
import { Neo4jService } from '../../storage/neo4j/neo4j.service.js';
|
|
8
8
|
import { TOOL_NAMES, TOOL_METADATA, MESSAGES } from '../constants.js';
|
|
9
|
-
import { createErrorResponse, createSuccessResponse, formatQueryResults, debugLog } from '../utils.js';
|
|
9
|
+
import { createErrorResponse, createSuccessResponse, formatQueryResults, debugLog, resolveProjectIdOrError, } from '../utils.js';
|
|
10
10
|
// Service instance - initialized asynchronously
|
|
11
11
|
let naturalLanguageToCypherService = null;
|
|
12
12
|
/**
|
|
@@ -30,20 +30,43 @@ export const createNaturalLanguageToCypherTool = (server) => {
|
|
|
30
30
|
title: TOOL_METADATA[TOOL_NAMES.naturalLanguageToCypher].title,
|
|
31
31
|
description: TOOL_METADATA[TOOL_NAMES.naturalLanguageToCypher].description,
|
|
32
32
|
inputSchema: {
|
|
33
|
+
projectId: z.string().describe('Project ID, name, or path (e.g., "backend" or "proj_a1b2c3d4e5f6")'),
|
|
33
34
|
query: z.string().describe('Natural language query to convert to Cypher'),
|
|
34
35
|
},
|
|
35
|
-
}, async ({ query }) => {
|
|
36
|
+
}, async ({ projectId, query }) => {
|
|
37
|
+
const neo4jService = new Neo4jService();
|
|
36
38
|
try {
|
|
39
|
+
// Resolve project ID from name, path, or ID
|
|
40
|
+
const projectResult = await resolveProjectIdOrError(projectId, neo4jService);
|
|
41
|
+
if (!projectResult.success)
|
|
42
|
+
return projectResult.error;
|
|
43
|
+
const resolvedProjectId = projectResult.projectId;
|
|
37
44
|
if (!naturalLanguageToCypherService) {
|
|
38
|
-
await debugLog('Natural language service not available', { query });
|
|
45
|
+
await debugLog('Natural language service not available', { projectId: resolvedProjectId, query });
|
|
39
46
|
return createSuccessResponse(MESSAGES.errors.serviceNotInitialized);
|
|
40
47
|
}
|
|
41
|
-
await debugLog('Natural language to Cypher conversion started', { query });
|
|
42
|
-
const cypherResult = await naturalLanguageToCypherService.promptToQuery(query);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
await debugLog('Natural language to Cypher conversion started', { projectId: resolvedProjectId, query });
|
|
49
|
+
const cypherResult = await naturalLanguageToCypherService.promptToQuery(query, resolvedProjectId);
|
|
50
|
+
// Validate Cypher syntax using EXPLAIN (no execution, just parse)
|
|
51
|
+
const parameters = { ...cypherResult.parameters, projectId: resolvedProjectId };
|
|
52
|
+
try {
|
|
53
|
+
await neo4jService.run(`EXPLAIN ${cypherResult.cypher}`, parameters);
|
|
54
|
+
}
|
|
55
|
+
catch (validationError) {
|
|
56
|
+
const message = validationError instanceof Error ? validationError.message : String(validationError);
|
|
57
|
+
await debugLog('Generated Cypher validation failed', {
|
|
58
|
+
cypher: cypherResult.cypher,
|
|
59
|
+
error: message,
|
|
60
|
+
});
|
|
61
|
+
return createErrorResponse(`Generated Cypher query has syntax errors:\n\n` +
|
|
62
|
+
`Query: ${cypherResult.cypher}\n\n` +
|
|
63
|
+
`Error: ${message}\n\n` +
|
|
64
|
+
`Try rephrasing your request or use a simpler query.`);
|
|
65
|
+
}
|
|
66
|
+
// Execute the validated query
|
|
67
|
+
const results = await neo4jService.run(cypherResult.cypher, parameters);
|
|
46
68
|
await debugLog('Cypher query executed', {
|
|
69
|
+
projectId: resolvedProjectId,
|
|
47
70
|
cypher: cypherResult.cypher,
|
|
48
71
|
resultsCount: results.length,
|
|
49
72
|
});
|
|
@@ -55,5 +78,8 @@ export const createNaturalLanguageToCypherTool = (server) => {
|
|
|
55
78
|
await debugLog('Natural language to Cypher error', { query, error });
|
|
56
79
|
return createErrorResponse(error);
|
|
57
80
|
}
|
|
81
|
+
finally {
|
|
82
|
+
await neo4jService.close();
|
|
83
|
+
}
|
|
58
84
|
});
|
|
59
85
|
};
|