code-graph-context 2.5.1 → 2.5.3

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.
@@ -23,6 +23,12 @@ const CLAIM_TASK_BY_ID_QUERY = `
23
23
  // Only claim if all dependencies are complete
24
24
  WHERE incompleteDeps = 0
25
25
 
26
+ // Acquire exclusive lock to prevent race conditions
27
+ CALL apoc.lock.nodes([t])
28
+
29
+ // Double-check status after acquiring lock
30
+ WITH t WHERE t.status IN ['available', 'blocked']
31
+
26
32
  // Atomic claim
27
33
  SET t.status = 'claimed',
28
34
  t.claimedBy = $agentId,
@@ -57,11 +63,12 @@ const CLAIM_TASK_BY_ID_QUERY = `
57
63
  `;
58
64
  /**
59
65
  * Query to claim the highest priority available task matching criteria
66
+ * Uses APOC locking to prevent race conditions between parallel workers
60
67
  */
61
68
  const CLAIM_NEXT_TASK_QUERY = `
62
- // Find available tasks not blocked by dependencies
69
+ // Find available or blocked tasks (blocked tasks may have deps completed now)
63
70
  MATCH (t:SwarmTask {projectId: $projectId, swarmId: $swarmId})
64
- WHERE t.status = 'available'
71
+ WHERE t.status IN ['available', 'blocked']
65
72
  AND ($types IS NULL OR size($types) = 0 OR t.type IN $types)
66
73
  AND ($minPriority IS NULL OR t.priorityScore >= $minPriority)
67
74
 
@@ -75,6 +82,12 @@ const CLAIM_NEXT_TASK_QUERY = `
75
82
  ORDER BY t.priorityScore DESC, t.createdAt ASC
76
83
  LIMIT 1
77
84
 
85
+ // Acquire exclusive lock to prevent race conditions
86
+ CALL apoc.lock.nodes([t])
87
+
88
+ // Double-check status after acquiring lock (another worker may have claimed it)
89
+ WITH t WHERE t.status IN ['available', 'blocked']
90
+
78
91
  // Atomic claim
79
92
  SET t.status = 'claimed',
80
93
  t.claimedBy = $agentId,
@@ -11,7 +11,7 @@ import { createErrorResponse, createSuccessResponse, resolveProjectIdOrError, de
11
11
  */
12
12
  const COMPLETE_TASK_QUERY = `
13
13
  MATCH (t:SwarmTask {id: $taskId, projectId: $projectId})
14
- WHERE t.status = 'in_progress' AND t.claimedBy = $agentId
14
+ WHERE t.status IN ['in_progress', 'claimed'] AND t.claimedBy = $agentId
15
15
 
16
16
  SET t.status = 'completed',
17
17
  t.completedAt = timestamp(),
@@ -29,17 +29,21 @@ const COMPLETE_TASK_QUERY = `
29
29
 
30
30
  // Check if waiting tasks now have all dependencies completed
31
31
  WITH t, collect(waiting) as waitingTasks
32
+
33
+ // Unblock tasks that have all dependencies met
32
34
  UNWIND (CASE WHEN size(waitingTasks) = 0 THEN [null] ELSE waitingTasks END) as waiting
33
35
  OPTIONAL MATCH (waiting)-[:DEPENDS_ON]->(otherDep:SwarmTask)
34
36
  WHERE otherDep.status <> 'completed' AND otherDep.id <> t.id
35
37
  WITH t, waiting, count(otherDep) as remainingDeps
36
- WHERE waiting IS NOT NULL AND remainingDeps = 0
37
38
 
38
- // Unblock tasks that now have all dependencies met
39
- SET waiting.status = 'available',
40
- waiting.updatedAt = timestamp()
39
+ // Update status for tasks with no remaining deps (but don't filter out the row)
40
+ FOREACH (_ IN CASE WHEN waiting IS NOT NULL AND remainingDeps = 0 THEN [1] ELSE [] END |
41
+ SET waiting.status = 'available', waiting.updatedAt = timestamp()
42
+ )
41
43
 
42
- WITH t, collect(waiting.id) as unblockedTaskIds
44
+ WITH t, CASE WHEN waiting IS NOT NULL AND remainingDeps = 0 THEN waiting.id ELSE null END as unblockedId
45
+ WITH t, collect(unblockedId) as allUnblockedIds
46
+ WITH t, [id IN allUnblockedIds WHERE id IS NOT NULL] as unblockedTaskIds
43
47
 
44
48
  RETURN t.id as id,
45
49
  t.title as title,
@@ -112,16 +116,21 @@ const APPROVE_TASK_QUERY = `
112
116
 
113
117
  // Check if waiting tasks now have all dependencies completed
114
118
  WITH t, collect(waiting) as waitingTasks
119
+
120
+ // Unblock tasks that have all dependencies met
115
121
  UNWIND (CASE WHEN size(waitingTasks) = 0 THEN [null] ELSE waitingTasks END) as waiting
116
122
  OPTIONAL MATCH (waiting)-[:DEPENDS_ON]->(otherDep:SwarmTask)
117
123
  WHERE otherDep.status <> 'completed' AND otherDep.id <> t.id
118
124
  WITH t, waiting, count(otherDep) as remainingDeps
119
- WHERE waiting IS NOT NULL AND remainingDeps = 0
120
125
 
121
- SET waiting.status = 'available',
122
- waiting.updatedAt = timestamp()
126
+ // Update status for tasks with no remaining deps (but don't filter out the row)
127
+ FOREACH (_ IN CASE WHEN waiting IS NOT NULL AND remainingDeps = 0 THEN [1] ELSE [] END |
128
+ SET waiting.status = 'available', waiting.updatedAt = timestamp()
129
+ )
123
130
 
124
- WITH t, collect(waiting.id) as unblockedTaskIds
131
+ WITH t, CASE WHEN waiting IS NOT NULL AND remainingDeps = 0 THEN waiting.id ELSE null END as unblockedId
132
+ WITH t, collect(unblockedId) as allUnblockedIds
133
+ WITH t, [id IN allUnblockedIds WHERE id IS NOT NULL] as unblockedTaskIds
125
134
 
126
135
  RETURN t.id as id,
127
136
  t.title as title,
@@ -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.1",
3
+ "version": "2.5.3",
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",