code-graph-context 2.5.2 → 2.5.4

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.
@@ -4,7 +4,7 @@ import { getTimeoutConfig } from '../config/timeouts.js';
4
4
  export class NaturalLanguageToCypherService {
5
5
  assistantId;
6
6
  openai;
7
- MODEL = 'gpt-4o-mini'; // Using GPT-4 Turbo
7
+ MODEL = 'gpt-4o'; // GPT-4o for better Cypher generation accuracy
8
8
  schemaPath = null;
9
9
  cachedSemanticTypes = null;
10
10
  messageInstructions = `
@@ -66,9 +66,9 @@ const CLAIM_TASK_BY_ID_QUERY = `
66
66
  * Uses APOC locking to prevent race conditions between parallel workers
67
67
  */
68
68
  const CLAIM_NEXT_TASK_QUERY = `
69
- // Find available tasks not blocked by dependencies
69
+ // Find available or blocked tasks (blocked tasks may have deps completed now)
70
70
  MATCH (t:SwarmTask {projectId: $projectId, swarmId: $swarmId})
71
- WHERE t.status = 'available'
71
+ WHERE t.status IN ['available', 'blocked']
72
72
  AND ($types IS NULL OR size($types) = 0 OR t.type IN $types)
73
73
  AND ($minPriority IS NULL OR t.priorityScore >= $minPriority)
74
74
 
@@ -86,7 +86,7 @@ const CLAIM_NEXT_TASK_QUERY = `
86
86
  CALL apoc.lock.nodes([t])
87
87
 
88
88
  // Double-check status after acquiring lock (another worker may have claimed it)
89
- WITH t WHERE t.status = 'available'
89
+ WITH t WHERE t.status IN ['available', 'blocked']
90
90
 
91
91
  // Atomic claim
92
92
  SET t.status = 'claimed',
@@ -9,7 +9,7 @@ import { createErrorResponse, createSuccessResponse, resolveProjectIdOrError, de
9
9
  /**
10
10
  * Neo4j query to delete pheromones by swarm ID
11
11
  */
12
- const CLEANUP_BY_SWARM_QUERY = `
12
+ const CLEANUP_PHEROMONES_BY_SWARM_QUERY = `
13
13
  MATCH (p:Pheromone)
14
14
  WHERE p.projectId = $projectId
15
15
  AND p.swarmId = $swarmId
@@ -18,6 +18,17 @@ const CLEANUP_BY_SWARM_QUERY = `
18
18
  DETACH DELETE p
19
19
  RETURN count(p) as deleted, collect(DISTINCT agentId) as agents, collect(DISTINCT type) as types
20
20
  `;
21
+ /**
22
+ * Neo4j query to delete SwarmTask nodes by swarm ID
23
+ */
24
+ const CLEANUP_TASKS_BY_SWARM_QUERY = `
25
+ MATCH (t:SwarmTask)
26
+ WHERE t.projectId = $projectId
27
+ AND t.swarmId = $swarmId
28
+ WITH t, t.status as status
29
+ DETACH DELETE t
30
+ RETURN count(t) as deleted, collect(DISTINCT status) as statuses
31
+ `;
21
32
  /**
22
33
  * Neo4j query to delete pheromones by agent ID
23
34
  */
@@ -44,11 +55,16 @@ const CLEANUP_ALL_QUERY = `
44
55
  /**
45
56
  * Count queries for dry run
46
57
  */
47
- const COUNT_BY_SWARM_QUERY = `
58
+ const COUNT_PHEROMONES_BY_SWARM_QUERY = `
48
59
  MATCH (p:Pheromone)
49
60
  WHERE p.projectId = $projectId AND p.swarmId = $swarmId AND NOT p.type IN $keepTypes
50
61
  RETURN count(p) as count, collect(DISTINCT p.agentId) as agents, collect(DISTINCT p.type) as types
51
62
  `;
63
+ const COUNT_TASKS_BY_SWARM_QUERY = `
64
+ MATCH (t:SwarmTask)
65
+ WHERE t.projectId = $projectId AND t.swarmId = $swarmId
66
+ RETURN count(t) as count, collect(DISTINCT t.status) as statuses
67
+ `;
52
68
  const COUNT_BY_AGENT_QUERY = `
53
69
  MATCH (p:Pheromone)
54
70
  WHERE p.projectId = $projectId AND p.agentId = $agentId AND NOT p.type IN $keepTypes
@@ -65,9 +81,14 @@ export const createSwarmCleanupTool = (server) => {
65
81
  description: TOOL_METADATA[TOOL_NAMES.swarmCleanup].description,
66
82
  inputSchema: {
67
83
  projectId: z.string().describe('Project ID, name, or path'),
68
- swarmId: z.string().optional().describe('Delete all pheromones from this swarm'),
84
+ swarmId: z.string().optional().describe('Delete all pheromones and tasks from this swarm'),
69
85
  agentId: z.string().optional().describe('Delete all pheromones from this agent'),
70
86
  all: z.boolean().optional().default(false).describe('Delete ALL pheromones in project (use with caution)'),
87
+ includeTasks: z
88
+ .boolean()
89
+ .optional()
90
+ .default(true)
91
+ .describe('Also delete SwarmTask nodes (default: true, only applies when swarmId is provided)'),
71
92
  keepTypes: z
72
93
  .array(z.string())
73
94
  .optional()
@@ -75,7 +96,7 @@ export const createSwarmCleanupTool = (server) => {
75
96
  .describe('Pheromone types to preserve (default: ["warning"])'),
76
97
  dryRun: z.boolean().optional().default(false).describe('Preview what would be deleted without deleting'),
77
98
  },
78
- }, async ({ projectId, swarmId, agentId, all = false, keepTypes = ['warning'], dryRun = false }) => {
99
+ }, async ({ projectId, swarmId, agentId, all = false, includeTasks = true, keepTypes = ['warning'], dryRun = false }) => {
79
100
  const neo4jService = new Neo4jService();
80
101
  // Resolve project ID
81
102
  const projectResult = await resolveProjectIdOrError(projectId, neo4jService);
@@ -98,52 +119,83 @@ export const createSwarmCleanupTool = (server) => {
98
119
  dryRun,
99
120
  });
100
121
  const params = { projectId: resolvedProjectId, keepTypes };
101
- let deleteQuery;
102
- let countQuery;
122
+ let pheromoneDeleteQuery;
123
+ let pheromoneCountQuery;
103
124
  let mode;
104
125
  if (swarmId) {
105
126
  params.swarmId = swarmId;
106
- deleteQuery = CLEANUP_BY_SWARM_QUERY;
107
- countQuery = COUNT_BY_SWARM_QUERY;
127
+ pheromoneDeleteQuery = CLEANUP_PHEROMONES_BY_SWARM_QUERY;
128
+ pheromoneCountQuery = COUNT_PHEROMONES_BY_SWARM_QUERY;
108
129
  mode = 'swarm';
109
130
  }
110
131
  else if (agentId) {
111
132
  params.agentId = agentId;
112
- deleteQuery = CLEANUP_BY_AGENT_QUERY;
113
- countQuery = COUNT_BY_AGENT_QUERY;
133
+ pheromoneDeleteQuery = CLEANUP_BY_AGENT_QUERY;
134
+ pheromoneCountQuery = COUNT_BY_AGENT_QUERY;
114
135
  mode = 'agent';
115
136
  }
116
137
  else {
117
- deleteQuery = CLEANUP_ALL_QUERY;
118
- countQuery = COUNT_ALL_QUERY;
138
+ pheromoneDeleteQuery = CLEANUP_ALL_QUERY;
139
+ pheromoneCountQuery = COUNT_ALL_QUERY;
119
140
  mode = 'all';
120
141
  }
121
142
  if (dryRun) {
122
- const result = await neo4jService.run(countQuery, params);
123
- const count = result[0]?.count ?? 0;
143
+ const pheromoneResult = await neo4jService.run(pheromoneCountQuery, params);
144
+ const pheromoneCount = pheromoneResult[0]?.count ?? 0;
145
+ let taskCount = 0;
146
+ let taskStatuses = [];
147
+ if (swarmId && includeTasks) {
148
+ const taskResult = await neo4jService.run(COUNT_TASKS_BY_SWARM_QUERY, params);
149
+ taskCount = taskResult[0]?.count ?? 0;
150
+ taskCount = typeof taskCount === 'object' && 'toNumber' in taskCount ? taskCount.toNumber() : taskCount;
151
+ taskStatuses = taskResult[0]?.statuses ?? [];
152
+ }
124
153
  return createSuccessResponse(JSON.stringify({
125
154
  success: true,
126
155
  dryRun: true,
127
156
  mode,
128
- wouldDelete: typeof count === 'object' && 'toNumber' in count ? count.toNumber() : count,
129
- agents: result[0]?.agents ?? [],
130
- swarms: result[0]?.swarms ?? [],
131
- types: result[0]?.types ?? [],
157
+ pheromones: {
158
+ wouldDelete: typeof pheromoneCount === 'object' && 'toNumber' in pheromoneCount ? pheromoneCount.toNumber() : pheromoneCount,
159
+ agents: pheromoneResult[0]?.agents ?? [],
160
+ types: pheromoneResult[0]?.types ?? [],
161
+ },
162
+ tasks: swarmId && includeTasks ? {
163
+ wouldDelete: taskCount,
164
+ statuses: taskStatuses,
165
+ } : null,
132
166
  keepTypes,
133
167
  projectId: resolvedProjectId,
134
168
  }));
135
169
  }
136
- const result = await neo4jService.run(deleteQuery, params);
137
- const deleted = result[0]?.deleted ?? 0;
170
+ // Delete pheromones
171
+ const pheromoneResult = await neo4jService.run(pheromoneDeleteQuery, params);
172
+ const pheromonesDeleted = pheromoneResult[0]?.deleted ?? 0;
173
+ // Delete tasks if swarmId provided and includeTasks is true
174
+ let tasksDeleted = 0;
175
+ let taskStatuses = [];
176
+ if (swarmId && includeTasks) {
177
+ const taskResult = await neo4jService.run(CLEANUP_TASKS_BY_SWARM_QUERY, params);
178
+ tasksDeleted = taskResult[0]?.deleted ?? 0;
179
+ tasksDeleted = typeof tasksDeleted === 'object' && 'toNumber' in tasksDeleted ? tasksDeleted.toNumber() : tasksDeleted;
180
+ taskStatuses = taskResult[0]?.statuses ?? [];
181
+ }
138
182
  return createSuccessResponse(JSON.stringify({
139
183
  success: true,
140
184
  mode,
141
- deleted: typeof deleted === 'object' && 'toNumber' in deleted ? deleted.toNumber() : deleted,
142
- agents: result[0]?.agents ?? [],
143
- swarms: result[0]?.swarms ?? [],
144
- types: result[0]?.types ?? [],
185
+ pheromones: {
186
+ deleted: typeof pheromonesDeleted === 'object' && 'toNumber' in pheromonesDeleted ? pheromonesDeleted.toNumber() : pheromonesDeleted,
187
+ agents: pheromoneResult[0]?.agents ?? [],
188
+ types: pheromoneResult[0]?.types ?? [],
189
+ },
190
+ tasks: swarmId && includeTasks ? {
191
+ deleted: tasksDeleted,
192
+ statuses: taskStatuses,
193
+ } : null,
145
194
  keepTypes,
146
195
  projectId: resolvedProjectId,
196
+ message: swarmId && includeTasks
197
+ ? `Cleaned up ${pheromonesDeleted} pheromones and ${tasksDeleted} tasks`
198
+ : `Cleaned up ${pheromonesDeleted} pheromones`,
147
199
  }));
148
200
  }
149
201
  catch (error) {
@@ -352,33 +352,129 @@ export const createSwarmOrchestrateTool = (server) => {
352
352
  */
353
353
  function generateWorkerInstructions(swarmId, projectId, maxAgents, taskCount) {
354
354
  const recommendedAgents = Math.min(maxAgents, Math.ceil(taskCount / 2), taskCount);
355
- return `
356
- ## Worker Agent Instructions
355
+ // Generate unique agent IDs for each worker
356
+ const agentIds = Array.from({ length: recommendedAgents }, (_, i) => `${swarmId}_worker_${i + 1}`);
357
+ const workerPrompt = `You are a swarm worker agent.
358
+ - Agent ID: {AGENT_ID}
359
+ - Swarm ID: ${swarmId}
360
+ - Project: ${projectId}
357
361
 
358
- To execute this swarm, spawn ${recommendedAgents} worker agent(s) using the Task tool:
362
+ ## CRITICAL RULES
363
+ 1. NEVER fabricate node IDs - get them from graph tool responses
364
+ 2. ALWAYS use the blackboard task queue (swarm_claim_task, swarm_complete_task)
365
+ 3. Use graph tools (traverse_from_node, search_codebase) to understand context
366
+ 4. Exit when swarm_claim_task returns "no_tasks"
359
367
 
360
- \`\`\`
361
- Task({
362
- subagent_type: "general-purpose",
363
- prompt: "You are a swarm worker for swarm ${swarmId}. Project: ${projectId}.
368
+ ## WORKFLOW - Follow these steps exactly:
364
369
 
365
- Your workflow:
366
- 1. swarm_sense({ projectId: '${projectId}', swarmId: '${swarmId}', types: ['modifying', 'claiming'] })
367
- 2. swarm_claim_task({ projectId: '${projectId}', swarmId: '${swarmId}', agentId: '<your-id>' })
368
- 3. If task claimed: execute it, then swarm_complete_task with action: 'complete'
369
- 4. Loop until no tasks remain
370
+ ### Step 1: Claim a task from the blackboard
371
+ swarm_claim_task({
372
+ projectId: "${projectId}",
373
+ swarmId: "${swarmId}",
374
+ agentId: "{AGENT_ID}"
375
+ })
376
+ // If returns "no_tasks" → exit, swarm is complete
377
+ // Otherwise you now own the returned task
370
378
 
371
- Always leave pheromones when working. Exit when swarm_get_tasks shows no available/in_progress tasks.",
372
- run_in_background: true
379
+ ### Step 2: Start working and check for conflicts
380
+ swarm_claim_task({
381
+ projectId: "${projectId}",
382
+ swarmId: "${swarmId}",
383
+ agentId: "{AGENT_ID}",
384
+ taskId: "<TASK_ID_FROM_STEP_1>",
385
+ action: "start"
373
386
  })
387
+
388
+ // Check if another agent is working on related code
389
+ swarm_sense({
390
+ projectId: "${projectId}",
391
+ swarmId: "${swarmId}",
392
+ types: ["modifying", "warning"],
393
+ excludeAgentId: "{AGENT_ID}"
394
+ })
395
+
396
+ ### Step 3: Understand the code context (USE GRAPH TOOLS!)
397
+ // Use traverse_from_node to see relationships and callers
398
+ traverse_from_node({
399
+ projectId: "${projectId}",
400
+ filePath: "<TARGET_FILE_FROM_TASK>",
401
+ maxDepth: 2,
402
+ includeCode: true
403
+ })
404
+
405
+ // Or search for related code
406
+ search_codebase({
407
+ projectId: "${projectId}",
408
+ query: "<WHAT_YOU_NEED_TO_UNDERSTAND>"
409
+ })
410
+
411
+ ### Step 4: Do the work
412
+ - Use Read tool for full source code of files to modify
413
+ - Use Edit tool to make changes
414
+ - Mark nodes you're modifying:
415
+ swarm_pheromone({
416
+ projectId: "${projectId}",
417
+ nodeId: "<NODE_ID_FROM_GRAPH>",
418
+ type: "modifying",
419
+ agentId: "{AGENT_ID}",
420
+ swarmId: "${swarmId}"
421
+ })
422
+
423
+ ### Step 5: Complete the task via blackboard
424
+ swarm_pheromone({
425
+ projectId: "${projectId}",
426
+ nodeId: "<NODE_ID>",
427
+ type: "completed",
428
+ agentId: "{AGENT_ID}",
429
+ swarmId: "${swarmId}",
430
+ data: { summary: "<WHAT_YOU_DID>" }
431
+ })
432
+
433
+ swarm_complete_task({
434
+ projectId: "${projectId}",
435
+ taskId: "<TASK_ID>",
436
+ agentId: "{AGENT_ID}",
437
+ action: "complete",
438
+ summary: "<DESCRIBE_WHAT_YOU_DID>",
439
+ filesChanged: ["<LIST_OF_FILES_YOU_MODIFIED>"]
440
+ })
441
+
442
+ ### Step 6: Loop back to Step 1
443
+ Claim the next available task. Continue until no tasks remain.
444
+
445
+ ## IF YOU GET STUCK
446
+ swarm_complete_task({
447
+ projectId: "${projectId}",
448
+ taskId: "<TASK_ID>",
449
+ agentId: "{AGENT_ID}",
450
+ action: "fail",
451
+ reason: "<WHY_YOU_ARE_STUCK>",
452
+ retryable: true
453
+ })
454
+ Then claim another task.`;
455
+ const taskCalls = agentIds.map(agentId => {
456
+ const prompt = workerPrompt.replace(/\{AGENT_ID\}/g, agentId);
457
+ return `Task({
458
+ subagent_type: "general-purpose",
459
+ run_in_background: false,
460
+ prompt: \`${prompt}\`
461
+ })`;
462
+ }).join('\n\n');
463
+ return `
464
+ ## Worker Agent Instructions
465
+
466
+ Spawn ${recommendedAgents} worker agent(s) IN PARALLEL (all Task calls in ONE message):
467
+
468
+ \`\`\`javascript
469
+ ${taskCalls}
374
470
  \`\`\`
375
471
 
376
- Launch ${recommendedAgents} agents in parallel for optimal execution.
472
+ **CRITICAL:** Include ALL ${recommendedAgents} Task calls in a single message to run them in parallel.
377
473
 
378
474
  ## Monitoring Progress
379
475
 
380
476
  Check swarm progress:
381
- \`\`\`
477
+ \`\`\`javascript
382
478
  swarm_get_tasks({
383
479
  projectId: "${projectId}",
384
480
  swarmId: "${swarmId}",
@@ -386,9 +482,9 @@ swarm_get_tasks({
386
482
  })
387
483
  \`\`\`
388
484
 
389
- ## Cleanup (after completion)
485
+ ## Cleanup (after all workers complete)
390
486
 
391
- \`\`\`
487
+ \`\`\`javascript
392
488
  swarm_cleanup({
393
489
  projectId: "${projectId}",
394
490
  swarmId: "${swarmId}"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-graph-context",
3
- "version": "2.5.2",
3
+ "version": "2.5.4",
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",