code-graph-context 2.14.1 → 3.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/dist/core/embeddings/natural-language-to-cypher.service.js +2 -4
- package/dist/mcp/constants.js +56 -228
- package/dist/mcp/handlers/swarm/abandon.handler.js +61 -0
- package/dist/mcp/handlers/swarm/advance.handler.js +78 -0
- package/dist/mcp/handlers/swarm/claim.handler.js +61 -0
- package/dist/mcp/handlers/swarm/index.js +5 -0
- package/dist/mcp/handlers/swarm/queries.js +140 -0
- package/dist/mcp/handlers/swarm/release.handler.js +41 -0
- package/dist/mcp/handlers/swarm-worker.handler.js +2 -13
- package/dist/mcp/tools/detect-dead-code.tool.js +33 -65
- package/dist/mcp/tools/detect-duplicate-code.tool.js +44 -53
- package/dist/mcp/tools/impact-analysis.tool.js +1 -1
- package/dist/mcp/tools/index.js +9 -9
- package/dist/mcp/tools/list-projects.tool.js +2 -2
- package/dist/mcp/tools/list-watchers.tool.js +2 -5
- package/dist/mcp/tools/natural-language-to-cypher.tool.js +2 -2
- package/dist/mcp/tools/parse-typescript-project.tool.js +7 -17
- package/dist/mcp/tools/search-codebase.tool.js +11 -26
- package/dist/mcp/tools/session-bookmark.tool.js +7 -11
- package/dist/mcp/tools/session-cleanup.tool.js +2 -6
- package/dist/mcp/tools/session-note.tool.js +6 -21
- package/dist/mcp/tools/session-recall.tool.js +293 -0
- package/dist/mcp/tools/session-save.tool.js +280 -0
- package/dist/mcp/tools/start-watch-project.tool.js +1 -1
- package/dist/mcp/tools/swarm-advance-task.tool.js +56 -0
- package/dist/mcp/tools/swarm-claim-task.tool.js +24 -388
- package/dist/mcp/tools/swarm-cleanup.tool.js +3 -7
- package/dist/mcp/tools/swarm-complete-task.tool.js +14 -17
- package/dist/mcp/tools/swarm-get-tasks.tool.js +8 -26
- package/dist/mcp/tools/swarm-message.tool.js +10 -25
- package/dist/mcp/tools/swarm-pheromone.tool.js +7 -25
- package/dist/mcp/tools/swarm-post-task.tool.js +7 -19
- package/dist/mcp/tools/swarm-release-task.tool.js +53 -0
- package/dist/mcp/tools/swarm-sense.tool.js +10 -30
- package/dist/mcp/tools/traverse-from-node.tool.js +19 -41
- package/dist/mcp/utils.js +41 -1
- package/package.json +1 -1
|
@@ -80,31 +80,13 @@ export const createSwarmPheromoneTool = (server) => {
|
|
|
80
80
|
.string()
|
|
81
81
|
.optional()
|
|
82
82
|
.describe('File path to mark (alternative to nodeId — auto-resolves to SourceFile node)'),
|
|
83
|
-
type: z
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
.optional()
|
|
91
|
-
.default(1.0)
|
|
92
|
-
.describe('Pheromone intensity from 0.0 to 1.0 (default: 1.0)'),
|
|
93
|
-
agentId: z.string().describe('Unique identifier for the agent leaving the pheromone'),
|
|
94
|
-
swarmId: z.string().describe('Swarm ID for grouping related agents (e.g., "swarm_xyz")'),
|
|
95
|
-
sessionId: z
|
|
96
|
-
.string()
|
|
97
|
-
.optional()
|
|
98
|
-
.describe('Session identifier for cross-session recovery (e.g., conversation ID)'),
|
|
99
|
-
data: z
|
|
100
|
-
.record(z.unknown())
|
|
101
|
-
.optional()
|
|
102
|
-
.describe('Optional metadata to attach to the pheromone (e.g., summary, reason)'),
|
|
103
|
-
remove: z
|
|
104
|
-
.boolean()
|
|
105
|
-
.optional()
|
|
106
|
-
.default(false)
|
|
107
|
-
.describe('If true, removes the pheromone instead of creating/updating it'),
|
|
83
|
+
type: z.enum(PHEROMONE_TYPES).describe('Pheromone type'),
|
|
84
|
+
intensity: z.number().min(0).max(1).optional().default(1.0).describe('Pheromone intensity'),
|
|
85
|
+
agentId: z.string().describe('Agent leaving the pheromone'),
|
|
86
|
+
swarmId: z.string().describe('Swarm ID for grouping related agents'),
|
|
87
|
+
sessionId: z.string().optional().describe('Session identifier for cross-session recovery'),
|
|
88
|
+
data: z.record(z.unknown()).optional().describe('Metadata to attach to the pheromone'),
|
|
89
|
+
remove: z.boolean().optional().default(false).describe('Remove instead of create/update'),
|
|
108
90
|
},
|
|
109
91
|
}, async ({ projectId, nodeId, filePath, type, intensity = 1.0, agentId, swarmId, sessionId, data, remove = false, }) => {
|
|
110
92
|
const neo4jService = new Neo4jService();
|
|
@@ -109,33 +109,21 @@ export const createSwarmPostTaskTool = (server) => {
|
|
|
109
109
|
swarmId: z.string().describe('Swarm ID for grouping related tasks'),
|
|
110
110
|
title: z.string().min(1).max(200).describe('Short title for the task'),
|
|
111
111
|
description: z.string().describe('Detailed description of what needs to be done'),
|
|
112
|
-
type: z
|
|
113
|
-
.enum(TASK_TYPES)
|
|
114
|
-
.optional()
|
|
115
|
-
.default('implement')
|
|
116
|
-
.describe('Task type: implement, refactor, fix, test, review, document, investigate, plan'),
|
|
112
|
+
type: z.enum(TASK_TYPES).optional().default('implement').describe('Task type'),
|
|
117
113
|
priority: z
|
|
118
114
|
.enum(Object.keys(TASK_PRIORITIES))
|
|
119
115
|
.optional()
|
|
120
116
|
.default('normal')
|
|
121
|
-
.describe('
|
|
122
|
-
targetNodeIds: z
|
|
123
|
-
|
|
124
|
-
.optional()
|
|
125
|
-
.default([])
|
|
126
|
-
.describe('Code node IDs this task targets (from search_codebase)'),
|
|
127
|
-
targetFilePaths: z
|
|
128
|
-
.array(z.string())
|
|
129
|
-
.optional()
|
|
130
|
-
.default([])
|
|
131
|
-
.describe('File paths this task affects (alternative to nodeIds)'),
|
|
117
|
+
.describe('Task priority'),
|
|
118
|
+
targetNodeIds: z.array(z.string()).optional().default([]).describe('Code node IDs this task targets'),
|
|
119
|
+
targetFilePaths: z.array(z.string()).optional().default([]).describe('File paths this task affects'),
|
|
132
120
|
dependencies: z
|
|
133
121
|
.array(z.string())
|
|
134
122
|
.optional()
|
|
135
123
|
.default([])
|
|
136
|
-
.describe('Task IDs that must
|
|
137
|
-
createdBy: z.string().describe('Agent ID
|
|
138
|
-
metadata: z.record(z.unknown()).optional().describe('Additional metadata
|
|
124
|
+
.describe('Task IDs that must complete before this task starts'),
|
|
125
|
+
createdBy: z.string().describe('Agent ID creating this task'),
|
|
126
|
+
metadata: z.record(z.unknown()).optional().describe('Additional metadata'),
|
|
139
127
|
},
|
|
140
128
|
}, async ({ projectId, swarmId, title, description, type = 'implement', priority = 'normal', targetNodeIds = [], targetFilePaths = [], dependencies = [], createdBy, metadata, }) => {
|
|
141
129
|
const neo4jService = new Neo4jService();
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Neo4jService } from '../../storage/neo4j/neo4j.service.js';
|
|
3
|
+
import { TOOL_NAMES, TOOL_METADATA } from '../constants.js';
|
|
4
|
+
import { SwarmReleaseHandler, SwarmAbandonHandler } from '../handlers/swarm/index.js';
|
|
5
|
+
import { createErrorResponse, createSuccessResponse, resolveProjectIdOrError, debugLog } from '../utils.js';
|
|
6
|
+
export const createSwarmReleaseTaskTool = (server) => {
|
|
7
|
+
server.registerTool(TOOL_NAMES.swarmReleaseTask, {
|
|
8
|
+
title: TOOL_METADATA[TOOL_NAMES.swarmReleaseTask].title,
|
|
9
|
+
description: TOOL_METADATA[TOOL_NAMES.swarmReleaseTask].description,
|
|
10
|
+
inputSchema: {
|
|
11
|
+
projectId: z.string().describe('Project ID, name, or path'),
|
|
12
|
+
taskId: z.string().describe('Task ID to release'),
|
|
13
|
+
agentId: z.string().describe('Your agent identifier'),
|
|
14
|
+
reason: z.string().optional().describe('Reason for releasing or abandoning'),
|
|
15
|
+
trackAbandonment: z.boolean().optional().default(false).describe('Track as abandonment for retry metrics'),
|
|
16
|
+
},
|
|
17
|
+
}, async ({ projectId, taskId, agentId, reason, trackAbandonment = false }) => {
|
|
18
|
+
const neo4jService = new Neo4jService();
|
|
19
|
+
const projectResult = await resolveProjectIdOrError(projectId, neo4jService);
|
|
20
|
+
if (!projectResult.success) {
|
|
21
|
+
await neo4jService.close();
|
|
22
|
+
return projectResult.error;
|
|
23
|
+
}
|
|
24
|
+
const resolvedProjectId = projectResult.projectId;
|
|
25
|
+
try {
|
|
26
|
+
if (trackAbandonment) {
|
|
27
|
+
const { error, data } = await new SwarmAbandonHandler(neo4jService).abandon(resolvedProjectId, taskId, agentId, reason);
|
|
28
|
+
if (error) {
|
|
29
|
+
return createErrorResponse(`Cannot abandon task ${taskId}. ` +
|
|
30
|
+
(data ? `Current state: ${data.status}, claimedBy: ${data.claimedBy || 'none'}` : 'Task not found.'));
|
|
31
|
+
}
|
|
32
|
+
return createSuccessResponse(JSON.stringify({ action: 'abandoned', taskId: data.id, abandonCount: data.abandonCount }));
|
|
33
|
+
}
|
|
34
|
+
const { error, data } = await new SwarmReleaseHandler(neo4jService).release(resolvedProjectId, taskId, agentId, reason);
|
|
35
|
+
if (error) {
|
|
36
|
+
return createErrorResponse(`Cannot release task ${taskId}. ` +
|
|
37
|
+
(data ? `Current state: ${data.status}, claimedBy: ${data.claimedBy || 'none'}` : 'Task not found.'));
|
|
38
|
+
}
|
|
39
|
+
return createSuccessResponse(JSON.stringify({
|
|
40
|
+
action: 'released',
|
|
41
|
+
taskId: data.id,
|
|
42
|
+
...(data.abandonCount != null && { abandonCount: data.abandonCount }),
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
await debugLog('Swarm release task error', { error: String(error) });
|
|
47
|
+
return createErrorResponse(error instanceof Error ? error : String(error));
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
await neo4jService.close();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
};
|
|
@@ -99,46 +99,26 @@ export const createSwarmSenseTool = (server) => {
|
|
|
99
99
|
types: z
|
|
100
100
|
.array(z.enum(PHEROMONE_TYPES))
|
|
101
101
|
.optional()
|
|
102
|
-
.describe('Filter by pheromone types
|
|
103
|
-
nodeIds: z.array(z.string()).optional().describe('Filter by specific node IDs
|
|
104
|
-
filePaths: z
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
.describe('Filter by file paths (resolves to SourceFile nodeIds). Also matches pheromones placed via filePath.'),
|
|
108
|
-
agentIds: z
|
|
109
|
-
.array(z.string())
|
|
110
|
-
.optional()
|
|
111
|
-
.describe('Filter by specific agent IDs. If empty, returns pheromones from all agents.'),
|
|
112
|
-
swarmId: z.string().optional().describe('Filter by swarm ID. If empty, returns pheromones from all swarms.'),
|
|
102
|
+
.describe('Filter by pheromone types'),
|
|
103
|
+
nodeIds: z.array(z.string()).optional().describe('Filter by specific node IDs'),
|
|
104
|
+
filePaths: z.array(z.string()).optional().describe('Filter by file paths (resolves to SourceFile nodeIds)'),
|
|
105
|
+
agentIds: z.array(z.string()).optional().describe('Filter by specific agent IDs'),
|
|
106
|
+
swarmId: z.string().optional().describe('Filter by swarm ID'),
|
|
113
107
|
excludeAgentId: z
|
|
114
108
|
.string()
|
|
115
109
|
.optional()
|
|
116
|
-
.describe('Exclude pheromones from this agent
|
|
117
|
-
sessionId: z
|
|
118
|
-
.string()
|
|
119
|
-
.optional()
|
|
120
|
-
.describe('Filter pheromones by session ID. Use to recover context after compaction or session restart.'),
|
|
110
|
+
.describe('Exclude pheromones from this agent (see what others are doing)'),
|
|
111
|
+
sessionId: z.string().optional().describe('Filter by session ID for context recovery'),
|
|
121
112
|
minIntensity: z
|
|
122
113
|
.number()
|
|
123
114
|
.min(0)
|
|
124
115
|
.max(1)
|
|
125
116
|
.optional()
|
|
126
117
|
.default(0.3)
|
|
127
|
-
.describe('Minimum effective intensity after decay
|
|
128
|
-
limit: z
|
|
129
|
-
.number()
|
|
130
|
-
.int()
|
|
131
|
-
.min(1)
|
|
132
|
-
.max(500)
|
|
133
|
-
.optional()
|
|
134
|
-
.default(50)
|
|
135
|
-
.describe('Maximum number of pheromones to return (default: 50, max: 500)'),
|
|
118
|
+
.describe('Minimum effective intensity after decay'),
|
|
119
|
+
limit: z.number().int().min(1).max(500).optional().default(50).describe('Maximum pheromones to return'),
|
|
136
120
|
includeStats: z.boolean().optional().default(false).describe('Include summary statistics by pheromone type'),
|
|
137
|
-
cleanup: z
|
|
138
|
-
.boolean()
|
|
139
|
-
.optional()
|
|
140
|
-
.default(false)
|
|
141
|
-
.describe('Run cleanup of fully decayed pheromones (intensity < 0.01)'),
|
|
121
|
+
cleanup: z.boolean().optional().default(false).describe('Remove fully decayed pheromones'),
|
|
142
122
|
},
|
|
143
123
|
}, async ({ projectId, types, nodeIds, filePaths, agentIds, swarmId, excludeAgentId, sessionId, minIntensity = 0.3, limit = 50, includeStats = false, cleanup = false, }) => {
|
|
144
124
|
const neo4jService = new Neo4jService();
|
|
@@ -26,20 +26,9 @@ export const createTraverseFromNodeTool = (server) => {
|
|
|
26
26
|
.number()
|
|
27
27
|
.int()
|
|
28
28
|
.optional()
|
|
29
|
-
.describe(
|
|
29
|
+
.describe('Maximum relationship traversal depth')
|
|
30
30
|
.default(DEFAULTS.traversalDepth),
|
|
31
|
-
skip: z
|
|
32
|
-
.number()
|
|
33
|
-
.int()
|
|
34
|
-
.optional()
|
|
35
|
-
.describe(`Number of results to skip for pagination (default: ${DEFAULTS.skipOffset})`)
|
|
36
|
-
.default(DEFAULTS.skipOffset),
|
|
37
|
-
limit: z
|
|
38
|
-
.number()
|
|
39
|
-
.int()
|
|
40
|
-
.optional()
|
|
41
|
-
.describe('Maximum results per page (default: 50). Use with skip for pagination.')
|
|
42
|
-
.default(50),
|
|
31
|
+
skip: z.number().int().optional().describe('Results to skip for pagination').default(DEFAULTS.skipOffset),
|
|
43
32
|
direction: z
|
|
44
33
|
.enum(['OUTGOING', 'INCOMING', 'BOTH'])
|
|
45
34
|
.optional()
|
|
@@ -49,36 +38,25 @@ export const createTraverseFromNodeTool = (server) => {
|
|
|
49
38
|
.array(z.string())
|
|
50
39
|
.optional()
|
|
51
40
|
.describe('Filter by specific relationship types (e.g., ["INJECTS", "USES_REPOSITORY"]). If not specified, shows all relationships.'),
|
|
52
|
-
|
|
53
|
-
.
|
|
54
|
-
.optional()
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
.describe('
|
|
62
|
-
.default(5),
|
|
63
|
-
|
|
64
|
-
.
|
|
65
|
-
|
|
66
|
-
.describe('Return only summary with file paths and statistics (default: false)')
|
|
67
|
-
.default(false),
|
|
68
|
-
snippetLength: z
|
|
69
|
-
.number()
|
|
70
|
-
.int()
|
|
71
|
-
.optional()
|
|
72
|
-
.describe(`Code snippet character length when includeCode is true (default: ${DEFAULTS.codeSnippetLength})`)
|
|
73
|
-
.default(DEFAULTS.codeSnippetLength),
|
|
74
|
-
maxTotalNodes: z
|
|
75
|
-
.number()
|
|
76
|
-
.int()
|
|
41
|
+
displayOptions: z
|
|
42
|
+
.object({
|
|
43
|
+
includeCode: z.boolean().optional().default(true).describe('Include source code snippets'),
|
|
44
|
+
snippetLength: z
|
|
45
|
+
.number()
|
|
46
|
+
.int()
|
|
47
|
+
.optional()
|
|
48
|
+
.default(DEFAULTS.codeSnippetLength)
|
|
49
|
+
.describe('Code snippet character limit'),
|
|
50
|
+
summaryOnly: z.boolean().optional().default(false).describe('Return only file paths and statistics'),
|
|
51
|
+
maxNodesPerChain: z.number().int().optional().default(5).describe('Maximum chains per depth level'),
|
|
52
|
+
maxTotalNodes: z.number().int().optional().default(50).describe('Maximum unique nodes across all depths'),
|
|
53
|
+
limit: z.number().int().optional().default(50).describe('Maximum results per page'),
|
|
54
|
+
})
|
|
77
55
|
.optional()
|
|
78
|
-
.describe('
|
|
79
|
-
.default(50),
|
|
56
|
+
.describe('Output formatting options (all have sensible defaults)'),
|
|
80
57
|
},
|
|
81
|
-
}, async ({ projectId, nodeId, filePath, maxDepth = DEFAULTS.traversalDepth, skip = DEFAULTS.skipOffset,
|
|
58
|
+
}, async ({ projectId, nodeId, filePath, maxDepth = DEFAULTS.traversalDepth, skip = DEFAULTS.skipOffset, direction = 'BOTH', relationshipTypes, displayOptions, }) => {
|
|
59
|
+
const { includeCode = true, snippetLength = DEFAULTS.codeSnippetLength, summaryOnly = false, maxNodesPerChain = 5, maxTotalNodes = 50, limit = 50, } = displayOptions ?? {};
|
|
82
60
|
// Validate that either nodeId or filePath is provided
|
|
83
61
|
if (!nodeId && !filePath) {
|
|
84
62
|
return createErrorResponse('Either nodeId or filePath must be provided.');
|
package/dist/mcp/utils.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* MCP Server Utility Functions
|
|
3
3
|
* Common utility functions used across the MCP server
|
|
4
4
|
*/
|
|
5
|
-
import { resolveProjectIdFromInput } from '../core/utils/project-id.js';
|
|
5
|
+
import { resolveProjectIdFromInput, LIST_PROJECTS_QUERY } from '../core/utils/project-id.js';
|
|
6
6
|
import { MESSAGES } from './constants.js';
|
|
7
7
|
export { debugLog } from '../core/utils/file-utils.js';
|
|
8
8
|
/**
|
|
@@ -19,6 +19,39 @@ export const resolveProjectIdOrError = async (projectId, neo4jService) => {
|
|
|
19
19
|
return { success: false, error: createErrorResponse(message) };
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* Auto-resolve projectId when omitted. If only one project exists, use it.
|
|
24
|
+
* If multiple exist, return error listing them. If none, return error.
|
|
25
|
+
*/
|
|
26
|
+
export const autoResolveProjectId = async (projectId, neo4jService) => {
|
|
27
|
+
// If projectId provided, use normal resolution
|
|
28
|
+
if (projectId) {
|
|
29
|
+
return resolveProjectIdOrError(projectId, neo4jService);
|
|
30
|
+
}
|
|
31
|
+
// Auto-resolve: query all projects
|
|
32
|
+
try {
|
|
33
|
+
const projects = await neo4jService.run(LIST_PROJECTS_QUERY, {});
|
|
34
|
+
if (projects.length === 0) {
|
|
35
|
+
return {
|
|
36
|
+
success: false,
|
|
37
|
+
error: createErrorResponse('No projects found. Use parse_typescript_project to add a project first.'),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (projects.length === 1) {
|
|
41
|
+
return { success: true, projectId: projects[0].projectId };
|
|
42
|
+
}
|
|
43
|
+
// Multiple projects — can't auto-resolve
|
|
44
|
+
const projectList = projects.map((p) => ` - ${p.name || p.projectId} (${p.projectId})`).join('\n');
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
error: createErrorResponse(`Multiple projects found. Specify projectId:\n${projectList}`),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
52
|
+
return { success: false, error: createErrorResponse(message) };
|
|
53
|
+
}
|
|
54
|
+
};
|
|
22
55
|
/**
|
|
23
56
|
* Standard error response format for MCP tools
|
|
24
57
|
*/
|
|
@@ -46,6 +79,13 @@ export const createSuccessResponse = (text) => {
|
|
|
46
79
|
],
|
|
47
80
|
};
|
|
48
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* Standard "no results" response for MCP tools.
|
|
84
|
+
* Use instead of createSuccessResponse for empty result sets.
|
|
85
|
+
*/
|
|
86
|
+
export const createEmptyResponse = (message, suggestion) => {
|
|
87
|
+
return createSuccessResponse(JSON.stringify({ status: 'empty', message, ...(suggestion && { suggestion }) }));
|
|
88
|
+
};
|
|
49
89
|
/**
|
|
50
90
|
* Truncate code to specified max length, showing first and last portions
|
|
51
91
|
*/
|
package/package.json
CHANGED