code-graph-context 2.5.0 → 2.5.2
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 +0 -6
- package/dist/mcp/tools/swarm-claim-task.tool.js +15 -2
- package/dist/mcp/tools/swarm-complete-task.tool.js +19 -10
- package/dist/mcp/tools/swarm-get-tasks.tool.js +1 -1
- package/dist/mcp/tools/swarm-orchestrate.tool.js +26 -5
- package/dist/mcp/tools/swarm-post-task.tool.js +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -616,12 +616,6 @@ MIT - see [LICENSE](LICENSE)
|
|
|
616
616
|
|
|
617
617
|
---
|
|
618
618
|
|
|
619
|
-
## Documentation
|
|
620
|
-
|
|
621
|
-
- [Graph + LLM Inference Research](docs/GRAPH_LLM_INFERENCE_RESEARCH.md) - Research on GNN embeddings, Graph Neural Prompting, and inference integration
|
|
622
|
-
|
|
623
|
-
---
|
|
624
|
-
|
|
625
619
|
## Links
|
|
626
620
|
|
|
627
621
|
- [Issues](https://github.com/drewdrewH/code-graph-context/issues)
|
|
@@ -20,8 +20,14 @@ const CLAIM_TASK_BY_ID_QUERY = `
|
|
|
20
20
|
WHERE dep.status <> 'completed'
|
|
21
21
|
WITH t, count(dep) as incompleteDeps
|
|
22
22
|
|
|
23
|
-
// Only claim if
|
|
24
|
-
WHERE incompleteDeps = 0
|
|
23
|
+
// Only claim if all dependencies are complete
|
|
24
|
+
WHERE incompleteDeps = 0
|
|
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']
|
|
25
31
|
|
|
26
32
|
// Atomic claim
|
|
27
33
|
SET t.status = 'claimed',
|
|
@@ -57,6 +63,7 @@ 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
69
|
// Find available tasks not blocked by dependencies
|
|
@@ -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 = 'available'
|
|
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
|
|
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
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
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,
|
|
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
|
-
|
|
122
|
-
|
|
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,
|
|
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,
|
|
@@ -128,7 +128,7 @@ const GET_ACTIVE_WORKERS_QUERY = `
|
|
|
128
128
|
WHERE ($swarmId IS NULL OR p.swarmId = $swarmId)
|
|
129
129
|
AND p.type IN ['modifying', 'claiming']
|
|
130
130
|
WITH p.agentId as agentId, p.type as type,
|
|
131
|
-
max(p.
|
|
131
|
+
max(p.timestamp) as lastActivity,
|
|
132
132
|
count(p) as nodeCount
|
|
133
133
|
RETURN agentId, type,
|
|
134
134
|
lastActivity,
|
|
@@ -14,12 +14,12 @@ import { Neo4jService } from '../../storage/neo4j/neo4j.service.js';
|
|
|
14
14
|
import { TOOL_NAMES, TOOL_METADATA } from '../constants.js';
|
|
15
15
|
import { TaskDecompositionHandler, } from '../handlers/task-decomposition.handler.js';
|
|
16
16
|
import { createErrorResponse, createSuccessResponse, resolveProjectIdOrError, debugLog } from '../utils.js';
|
|
17
|
-
import { TASK_PRIORITIES, generateSwarmId, ORCHESTRATOR_CONFIG, } from './swarm-constants.js';
|
|
17
|
+
import { TASK_PRIORITIES, generateSwarmId, ORCHESTRATOR_CONFIG, getHalfLife, } from './swarm-constants.js';
|
|
18
18
|
/**
|
|
19
19
|
* Query to search for nodes matching the task description
|
|
20
20
|
*/
|
|
21
21
|
const SEMANTIC_SEARCH_QUERY = `
|
|
22
|
-
CALL db.index.vector.queryNodes('
|
|
22
|
+
CALL db.index.vector.queryNodes('embedded_nodes_idx', toInteger($limit), $embedding)
|
|
23
23
|
YIELD node, score
|
|
24
24
|
WHERE node.projectId = $projectId
|
|
25
25
|
AND score >= $minSimilarity
|
|
@@ -73,14 +73,15 @@ const CREATE_PHEROMONE_QUERY = `
|
|
|
73
73
|
projectId: $projectId
|
|
74
74
|
})
|
|
75
75
|
ON CREATE SET
|
|
76
|
+
p.id = randomUUID(),
|
|
76
77
|
p.swarmId = $swarmId,
|
|
77
78
|
p.intensity = $intensity,
|
|
78
|
-
p.
|
|
79
|
-
p.
|
|
79
|
+
p.timestamp = timestamp(),
|
|
80
|
+
p.halfLife = $halfLife,
|
|
80
81
|
p.data = $data
|
|
81
82
|
ON MATCH SET
|
|
82
83
|
p.intensity = $intensity,
|
|
83
|
-
p.
|
|
84
|
+
p.timestamp = timestamp(),
|
|
84
85
|
p.data = $data
|
|
85
86
|
MERGE (p)-[:MARKS]->(target)
|
|
86
87
|
RETURN p.nodeId AS nodeId
|
|
@@ -107,6 +108,25 @@ const CREATE_TASK_QUERY = `
|
|
|
107
108
|
updatedAt: timestamp(),
|
|
108
109
|
metadata: $metadata
|
|
109
110
|
})
|
|
111
|
+
|
|
112
|
+
// Link to target code nodes if they exist
|
|
113
|
+
WITH t
|
|
114
|
+
OPTIONAL MATCH (target)
|
|
115
|
+
WHERE target.id IN $targetNodeIds
|
|
116
|
+
AND target.projectId = $projectId
|
|
117
|
+
AND NOT target:SwarmTask
|
|
118
|
+
AND NOT target:Pheromone
|
|
119
|
+
WITH t, collect(DISTINCT target) as targets
|
|
120
|
+
FOREACH (target IN targets | MERGE (t)-[:TARGETS]->(target))
|
|
121
|
+
|
|
122
|
+
// Link to dependency tasks if they exist
|
|
123
|
+
WITH t
|
|
124
|
+
OPTIONAL MATCH (dep:SwarmTask)
|
|
125
|
+
WHERE dep.id IN $dependencies
|
|
126
|
+
AND dep.projectId = $projectId
|
|
127
|
+
WITH t, collect(DISTINCT dep) as deps
|
|
128
|
+
FOREACH (dep IN deps | MERGE (t)-[:DEPENDS_ON]->(dep))
|
|
129
|
+
|
|
110
130
|
RETURN t.id AS id
|
|
111
131
|
`;
|
|
112
132
|
export const createSwarmOrchestrateTool = (server) => {
|
|
@@ -278,6 +298,7 @@ export const createSwarmOrchestrateTool = (server) => {
|
|
|
278
298
|
swarmId,
|
|
279
299
|
type: 'proposal',
|
|
280
300
|
intensity: 1.0,
|
|
301
|
+
halfLife: getHalfLife('proposal'),
|
|
281
302
|
data: JSON.stringify({ task, swarmId }),
|
|
282
303
|
});
|
|
283
304
|
}
|
|
@@ -77,6 +77,14 @@ const CHECK_DEPENDENCIES_QUERY = `
|
|
|
77
77
|
size(incompleteDeps) as incompleteDeps,
|
|
78
78
|
[d IN incompleteDeps | {id: d.id, title: d.title, status: d.status}] as blockedBy
|
|
79
79
|
`;
|
|
80
|
+
/**
|
|
81
|
+
* Query to update task status to blocked
|
|
82
|
+
*/
|
|
83
|
+
const SET_TASK_BLOCKED_QUERY = `
|
|
84
|
+
MATCH (t:SwarmTask {id: $taskId, projectId: $projectId})
|
|
85
|
+
SET t.status = 'blocked', t.updatedAt = timestamp()
|
|
86
|
+
RETURN t.id as id
|
|
87
|
+
`;
|
|
80
88
|
export const createSwarmPostTaskTool = (server) => {
|
|
81
89
|
server.registerTool(TOOL_NAMES.swarmPostTask, {
|
|
82
90
|
title: TOOL_METADATA[TOOL_NAMES.swarmPostTask].title,
|
|
@@ -180,6 +188,13 @@ export const createSwarmPostTaskTool = (server) => {
|
|
|
180
188
|
}
|
|
181
189
|
}
|
|
182
190
|
const isBlocked = dependencyStatus.incompleteDeps > 0;
|
|
191
|
+
// Update task status to blocked if there are incomplete dependencies
|
|
192
|
+
if (isBlocked) {
|
|
193
|
+
await neo4jService.run(SET_TASK_BLOCKED_QUERY, {
|
|
194
|
+
taskId,
|
|
195
|
+
projectId: resolvedProjectId,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
183
198
|
return createSuccessResponse(JSON.stringify({
|
|
184
199
|
success: true,
|
|
185
200
|
task: {
|
package/package.json
CHANGED