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.
Files changed (37) hide show
  1. package/dist/core/embeddings/natural-language-to-cypher.service.js +2 -4
  2. package/dist/mcp/constants.js +56 -228
  3. package/dist/mcp/handlers/swarm/abandon.handler.js +61 -0
  4. package/dist/mcp/handlers/swarm/advance.handler.js +78 -0
  5. package/dist/mcp/handlers/swarm/claim.handler.js +61 -0
  6. package/dist/mcp/handlers/swarm/index.js +5 -0
  7. package/dist/mcp/handlers/swarm/queries.js +140 -0
  8. package/dist/mcp/handlers/swarm/release.handler.js +41 -0
  9. package/dist/mcp/handlers/swarm-worker.handler.js +2 -13
  10. package/dist/mcp/tools/detect-dead-code.tool.js +33 -65
  11. package/dist/mcp/tools/detect-duplicate-code.tool.js +44 -53
  12. package/dist/mcp/tools/impact-analysis.tool.js +1 -1
  13. package/dist/mcp/tools/index.js +9 -9
  14. package/dist/mcp/tools/list-projects.tool.js +2 -2
  15. package/dist/mcp/tools/list-watchers.tool.js +2 -5
  16. package/dist/mcp/tools/natural-language-to-cypher.tool.js +2 -2
  17. package/dist/mcp/tools/parse-typescript-project.tool.js +7 -17
  18. package/dist/mcp/tools/search-codebase.tool.js +11 -26
  19. package/dist/mcp/tools/session-bookmark.tool.js +7 -11
  20. package/dist/mcp/tools/session-cleanup.tool.js +2 -6
  21. package/dist/mcp/tools/session-note.tool.js +6 -21
  22. package/dist/mcp/tools/session-recall.tool.js +293 -0
  23. package/dist/mcp/tools/session-save.tool.js +280 -0
  24. package/dist/mcp/tools/start-watch-project.tool.js +1 -1
  25. package/dist/mcp/tools/swarm-advance-task.tool.js +56 -0
  26. package/dist/mcp/tools/swarm-claim-task.tool.js +24 -388
  27. package/dist/mcp/tools/swarm-cleanup.tool.js +3 -7
  28. package/dist/mcp/tools/swarm-complete-task.tool.js +14 -17
  29. package/dist/mcp/tools/swarm-get-tasks.tool.js +8 -26
  30. package/dist/mcp/tools/swarm-message.tool.js +10 -25
  31. package/dist/mcp/tools/swarm-pheromone.tool.js +7 -25
  32. package/dist/mcp/tools/swarm-post-task.tool.js +7 -19
  33. package/dist/mcp/tools/swarm-release-task.tool.js +53 -0
  34. package/dist/mcp/tools/swarm-sense.tool.js +10 -30
  35. package/dist/mcp/tools/traverse-from-node.tool.js +19 -41
  36. package/dist/mcp/utils.js +41 -1
  37. 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
- .enum(PHEROMONE_TYPES)
85
- .describe('Type of pheromone: exploring (browsing), modifying (active work), claiming (ownership), completed (done), warning (danger), blocked (stuck), proposal (awaiting approval), needs_review (review request), session_context (session working set)'),
86
- intensity: z
87
- .number()
88
- .min(0)
89
- .max(1)
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('Priority level: critical, high, normal, low, backlog'),
122
- targetNodeIds: z
123
- .array(z.string())
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 be completed before this task can start'),
137
- createdBy: z.string().describe('Agent ID or identifier of who created this task'),
138
- metadata: z.record(z.unknown()).optional().describe('Additional metadata (context, acceptance criteria, etc.)'),
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. If empty, returns all types. Options: exploring, modifying, claiming, completed, warning, blocked, proposal, needs_review'),
103
- nodeIds: z.array(z.string()).optional().describe('Filter by specific node IDs. If empty, searches all nodes.'),
104
- filePaths: z
105
- .array(z.string())
106
- .optional()
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 ID (useful for seeing what OTHER agents are doing)'),
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 (0.0-1.0, default: 0.3)'),
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(`Maximum depth to traverse (default: ${DEFAULTS.traversalDepth}, max: ${MAX_TRAVERSAL_DEPTH})`)
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
- includeCode: z
53
- .boolean()
54
- .optional()
55
- .describe('Include source code snippets in results (default: true, set to false for structure-only view)')
56
- .default(true),
57
- maxNodesPerChain: z
58
- .number()
59
- .int()
60
- .optional()
61
- .describe('Maximum chains to show per depth level (default: 5, applied independently at each depth)')
62
- .default(5),
63
- summaryOnly: z
64
- .boolean()
65
- .optional()
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('Maximum total unique nodes to return across all depths (default: 50). Limits output size.')
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, limit = 50, direction = 'BOTH', relationshipTypes, includeCode = true, maxNodesPerChain = 5, summaryOnly = false, snippetLength = DEFAULTS.codeSnippetLength, maxTotalNodes = 50, }) => {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-graph-context",
3
- "version": "2.14.1",
3
+ "version": "3.0.0",
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/drewdrewH/code-graph-context#readme",