@sparkleideas/ruv-swarm 1.0.18-patch.1
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 +1565 -0
- package/bin/ruv-swarm-clean.js +1872 -0
- package/bin/ruv-swarm-memory.js +119 -0
- package/bin/ruv-swarm-secure-heartbeat.js +1549 -0
- package/bin/ruv-swarm-secure.js +1689 -0
- package/package.json +221 -0
- package/src/agent.ts +342 -0
- package/src/benchmark.js +267 -0
- package/src/claude-flow-enhanced.js +839 -0
- package/src/claude-integration/advanced-commands.js +561 -0
- package/src/claude-integration/core.js +112 -0
- package/src/claude-integration/docs.js +1548 -0
- package/src/claude-integration/env-template.js +39 -0
- package/src/claude-integration/index.js +209 -0
- package/src/claude-integration/remote.js +408 -0
- package/src/cli-diagnostics.js +364 -0
- package/src/cognitive-pattern-evolution.js +1317 -0
- package/src/daa-cognition.js +977 -0
- package/src/daa-service.d.ts +298 -0
- package/src/daa-service.js +1116 -0
- package/src/diagnostics.js +533 -0
- package/src/errors.js +528 -0
- package/src/github-coordinator/README.md +193 -0
- package/src/github-coordinator/claude-hooks.js +162 -0
- package/src/github-coordinator/gh-cli-coordinator.js +260 -0
- package/src/hooks/cli.js +82 -0
- package/src/hooks/index.js +1900 -0
- package/src/index-enhanced.d.ts +371 -0
- package/src/index-enhanced.js +734 -0
- package/src/index.d.ts +287 -0
- package/src/index.js +405 -0
- package/src/index.ts +457 -0
- package/src/logger.js +182 -0
- package/src/logging-config.js +179 -0
- package/src/mcp-daa-tools.js +735 -0
- package/src/mcp-tools-benchmarks.js +328 -0
- package/src/mcp-tools-enhanced.js +2863 -0
- package/src/memory-config.js +42 -0
- package/src/meta-learning-framework.js +1359 -0
- package/src/neural-agent.js +830 -0
- package/src/neural-coordination-protocol.js +1363 -0
- package/src/neural-models/README.md +118 -0
- package/src/neural-models/autoencoder.js +543 -0
- package/src/neural-models/base.js +269 -0
- package/src/neural-models/cnn.js +497 -0
- package/src/neural-models/gnn.js +447 -0
- package/src/neural-models/gru.js +536 -0
- package/src/neural-models/index.js +273 -0
- package/src/neural-models/lstm.js +551 -0
- package/src/neural-models/neural-presets-complete.js +1306 -0
- package/src/neural-models/presets/graph.js +392 -0
- package/src/neural-models/presets/index.js +279 -0
- package/src/neural-models/presets/nlp.js +328 -0
- package/src/neural-models/presets/timeseries.js +368 -0
- package/src/neural-models/presets/vision.js +387 -0
- package/src/neural-models/resnet.js +534 -0
- package/src/neural-models/transformer.js +515 -0
- package/src/neural-models/vae.js +489 -0
- package/src/neural-network-manager.js +1938 -0
- package/src/neural-network.ts +296 -0
- package/src/neural.js +574 -0
- package/src/performance-benchmarks.js +898 -0
- package/src/performance.js +458 -0
- package/src/persistence-pooled.js +695 -0
- package/src/persistence.js +480 -0
- package/src/schemas.js +864 -0
- package/src/security.js +218 -0
- package/src/singleton-container.js +183 -0
- package/src/sqlite-pool.js +587 -0
- package/src/sqlite-worker.js +141 -0
- package/src/types.ts +164 -0
- package/src/utils.ts +286 -0
- package/src/wasm-loader.js +601 -0
- package/src/wasm-loader2.js +404 -0
- package/src/wasm-memory-optimizer.js +783 -0
- package/src/wasm-types.d.ts +63 -0
- package/wasm/README.md +347 -0
- package/wasm/neuro-divergent.wasm +0 -0
- package/wasm/package.json +18 -0
- package/wasm/ruv-fann.wasm +0 -0
- package/wasm/ruv_swarm_simd.wasm +0 -0
- package/wasm/ruv_swarm_wasm.d.ts +391 -0
- package/wasm/ruv_swarm_wasm.js +2164 -0
- package/wasm/ruv_swarm_wasm_bg.wasm +0 -0
- package/wasm/ruv_swarm_wasm_bg.wasm.d.ts +123 -0
- package/wasm/wasm-bindings-loader.mjs +435 -0
- package/wasm/wasm-updates.md +684 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# GitHub Coordinator for ruv-swarm
|
|
2
|
+
|
|
3
|
+
Simple GitHub-based coordination for multiple Claude Code swarms working on the same repository.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### Option 1: GitHub CLI (Recommended)
|
|
8
|
+
|
|
9
|
+
The simplest approach uses the GitHub CLI (`gh`) that you already have:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install gh CLI if not already installed
|
|
13
|
+
# https://cli.github.com/
|
|
14
|
+
|
|
15
|
+
# Use the coordinator
|
|
16
|
+
npx ruv-swarm github-coordinate
|
|
17
|
+
|
|
18
|
+
# Or use directly in your swarm
|
|
19
|
+
const GHCoordinator = require('./gh-cli-coordinator');
|
|
20
|
+
const coordinator = new GHCoordinator({
|
|
21
|
+
owner: 'ruvnet',
|
|
22
|
+
repo: 'ruv-FANN'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
# Get available tasks
|
|
26
|
+
const tasks = await coordinator.getAvailableTasks();
|
|
27
|
+
|
|
28
|
+
# Claim a task
|
|
29
|
+
await coordinator.claimTask('my-swarm-id', issueNumber);
|
|
30
|
+
|
|
31
|
+
# Update progress
|
|
32
|
+
await coordinator.updateTaskProgress('my-swarm-id', issueNumber, 'Working on authentication...');
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Option 2: Claude Code Hooks
|
|
36
|
+
|
|
37
|
+
Add automatic GitHub coordination to your Claude Code sessions:
|
|
38
|
+
|
|
39
|
+
1. Add to `.claude/settings.json`:
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"hooks": {
|
|
43
|
+
"pre-task": "npx ruv-swarm hook github pre-task",
|
|
44
|
+
"post-edit": "npx ruv-swarm hook github post-edit",
|
|
45
|
+
"post-task": "npx ruv-swarm hook github post-task"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
2. Set environment variables:
|
|
51
|
+
```bash
|
|
52
|
+
export GITHUB_OWNER=ruvnet
|
|
53
|
+
export GITHUB_REPO=ruv-FANN
|
|
54
|
+
export CLAUDE_SWARM_ID=my-swarm-123 # Optional, auto-generated if not set
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
3. Now Claude Code will automatically:
|
|
58
|
+
- Claim GitHub issues when starting tasks
|
|
59
|
+
- Update issues with progress
|
|
60
|
+
- Release issues when done
|
|
61
|
+
|
|
62
|
+
### Option 3: GitHub MCP Server
|
|
63
|
+
|
|
64
|
+
If you have a GitHub MCP server, you can use it directly:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
// Use GitHub MCP tools
|
|
68
|
+
mcp__github__issues_list({ state: 'open', labels: ['swarm-available'] })
|
|
69
|
+
mcp__github__issues_update({ number: 123, labels: ['swarm-claimed'] })
|
|
70
|
+
mcp__github__issues_comment({ number: 123, body: 'Progress update...' })
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## How It Works
|
|
74
|
+
|
|
75
|
+
### Task Coordination via GitHub Labels
|
|
76
|
+
|
|
77
|
+
The system uses GitHub labels to coordinate work:
|
|
78
|
+
|
|
79
|
+
- `swarm-available` - Tasks ready to be claimed
|
|
80
|
+
- `swarm-{id}` - Task claimed by a specific swarm
|
|
81
|
+
- `swarm-conflict` - Multiple swarms trying to work on same area
|
|
82
|
+
|
|
83
|
+
### Workflow
|
|
84
|
+
|
|
85
|
+
1. **Task Discovery**: Swarms check for open issues without swarm labels
|
|
86
|
+
2. **Task Claiming**: Add a `swarm-{id}` label to claim an issue
|
|
87
|
+
3. **Progress Updates**: Post comments with progress updates
|
|
88
|
+
4. **Task Completion**: Remove label or close issue when done
|
|
89
|
+
|
|
90
|
+
### Conflict Prevention
|
|
91
|
+
|
|
92
|
+
- Each swarm has a unique ID
|
|
93
|
+
- Issues can only have one `swarm-*` label
|
|
94
|
+
- Comments show which swarm is working on what
|
|
95
|
+
- Dashboard shows all active swarms and their tasks
|
|
96
|
+
|
|
97
|
+
## Examples
|
|
98
|
+
|
|
99
|
+
### Basic Coordination
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
// Initialize coordinator
|
|
103
|
+
const coordinator = new GHCoordinator({
|
|
104
|
+
owner: 'ruvnet',
|
|
105
|
+
repo: 'ruv-FANN',
|
|
106
|
+
labelPrefix: 'swarm-' // default
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Get coordination status
|
|
110
|
+
const status = await coordinator.getCoordinationStatus();
|
|
111
|
+
console.log(`${status.swarmTasks} tasks being worked on`);
|
|
112
|
+
console.log(`${status.availableTasks} tasks available`);
|
|
113
|
+
|
|
114
|
+
// Show swarm assignments
|
|
115
|
+
for (const [swarmId, tasks] of Object.entries(status.swarmStatus)) {
|
|
116
|
+
console.log(`Swarm ${swarmId} is working on:`);
|
|
117
|
+
tasks.forEach(task => console.log(` - #${task.number}: ${task.title}`));
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### With Claude Code Integration
|
|
122
|
+
|
|
123
|
+
When using the hooks, Claude Code will automatically coordinate:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Start a task - automatically claims related GitHub issue
|
|
127
|
+
claude> Can you fix the authentication bug?
|
|
128
|
+
🎯 Pre-task: Looking for GitHub issues related to: fix authentication bug
|
|
129
|
+
✅ Claimed GitHub issue #123: Fix JWT token validation
|
|
130
|
+
|
|
131
|
+
# Make changes - automatically updates issue
|
|
132
|
+
claude> [edits auth.js]
|
|
133
|
+
📝 Updated GitHub issue #123 with edit progress
|
|
134
|
+
|
|
135
|
+
# Complete task - automatically updates/releases issue
|
|
136
|
+
claude> I've fixed the authentication issue
|
|
137
|
+
✅ Task Completed on GitHub issue #123
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Manual Coordination Commands
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Check coordination status
|
|
144
|
+
npx ruv-swarm github status
|
|
145
|
+
|
|
146
|
+
# List available tasks
|
|
147
|
+
npx ruv-swarm github tasks --available
|
|
148
|
+
|
|
149
|
+
# Claim a specific task
|
|
150
|
+
npx ruv-swarm github claim 123
|
|
151
|
+
|
|
152
|
+
# Release a task
|
|
153
|
+
npx ruv-swarm github release 123
|
|
154
|
+
|
|
155
|
+
# Show dashboard URLs
|
|
156
|
+
npx ruv-swarm github dashboard
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Configuration
|
|
160
|
+
|
|
161
|
+
### Environment Variables
|
|
162
|
+
|
|
163
|
+
- `GITHUB_OWNER` - Repository owner
|
|
164
|
+
- `GITHUB_REPO` - Repository name
|
|
165
|
+
- `GITHUB_TOKEN` - Personal access token (optional, uses gh CLI auth)
|
|
166
|
+
- `CLAUDE_SWARM_ID` - Unique swarm identifier
|
|
167
|
+
|
|
168
|
+
### Label Configuration
|
|
169
|
+
|
|
170
|
+
Customize the label prefix in the coordinator:
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
const coordinator = new GHCoordinator({
|
|
174
|
+
labelPrefix: 'ai-swarm-' // Use custom prefix
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Benefits
|
|
179
|
+
|
|
180
|
+
1. **Simple**: Uses existing GitHub features (labels, comments)
|
|
181
|
+
2. **Visible**: All coordination visible in GitHub UI
|
|
182
|
+
3. **No Extra Infrastructure**: Just needs `gh` CLI
|
|
183
|
+
4. **Conflict-Free**: Labels prevent duplicate work
|
|
184
|
+
5. **Auditable**: Full history in GitHub
|
|
185
|
+
|
|
186
|
+
## Limitations
|
|
187
|
+
|
|
188
|
+
- Requires GitHub CLI or API access
|
|
189
|
+
- Limited to GitHub's rate limits
|
|
190
|
+
- Basic conflict detection (label-based)
|
|
191
|
+
- No real-time updates (polling-based)
|
|
192
|
+
|
|
193
|
+
For more advanced coordination, consider using the full GitHubCoordinator with a database backend or a dedicated MCP server.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Hooks for GitHub Coordination
|
|
3
|
+
* Automatically coordinates swarm activities with GitHub
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const GHCoordinator = require('./gh-cli-coordinator');
|
|
7
|
+
// const fs = require('fs').promises; // Unused - will be used in future implementation
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
class ClaudeGitHubHooks {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.coordinator = new GHCoordinator(options);
|
|
13
|
+
this.swarmId = options.swarmId || this.generateSwarmId();
|
|
14
|
+
this.activeTask = null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
generateSwarmId() {
|
|
18
|
+
// Generate swarm ID from environment or random
|
|
19
|
+
return process.env.CLAUDE_SWARM_ID || `claude-${Date.now().toString(36)}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Pre-task hook: Claim a GitHub issue before starting work
|
|
24
|
+
*/
|
|
25
|
+
async preTask(taskDescription) {
|
|
26
|
+
console.log(`🎯 Pre-task: Looking for GitHub issues related to: ${taskDescription}`);
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// Search for related issues
|
|
30
|
+
const tasks = await this.coordinator.getAvailableTasks({ state: 'open' });
|
|
31
|
+
|
|
32
|
+
// Find best matching task (simple keyword matching for now)
|
|
33
|
+
const keywords = taskDescription.toLowerCase().split(' ');
|
|
34
|
+
const matchedTask = tasks.find(task => {
|
|
35
|
+
const taskText = `${task.title} ${task.body || ''}`.toLowerCase();
|
|
36
|
+
return keywords.some(keyword => taskText.includes(keyword));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (matchedTask) {
|
|
40
|
+
const claimed = await this.coordinator.claimTask(this.swarmId, matchedTask.number);
|
|
41
|
+
if (claimed) {
|
|
42
|
+
this.activeTask = matchedTask.number;
|
|
43
|
+
console.log(`✅ Claimed GitHub issue #${matchedTask.number}: ${matchedTask.title}`);
|
|
44
|
+
return { claimed: true, issue: matchedTask.number };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log('ℹ️ No matching GitHub issue found, proceeding without claim');
|
|
49
|
+
return { claimed: false };
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('❌ Pre-task hook error:', error.message);
|
|
52
|
+
return { error: error.message };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Post-edit hook: Update GitHub issue with progress
|
|
58
|
+
*/
|
|
59
|
+
async postEdit(filePath, changes) {
|
|
60
|
+
if (!this.activeTask) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const message = `Updated \`${path.basename(filePath)}\`\n\n${changes.summary || 'File modified'}`;
|
|
66
|
+
await this.coordinator.updateTaskProgress(this.swarmId, this.activeTask, message);
|
|
67
|
+
console.log(`📝 Updated GitHub issue #${this.activeTask} with edit progress`);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('❌ Post-edit hook error:', error.message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Post-task hook: Complete or release the GitHub issue
|
|
75
|
+
*/
|
|
76
|
+
async postTask(taskId, result) {
|
|
77
|
+
if (!this.activeTask) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
if (result.completed) {
|
|
83
|
+
const message = `✅ **Task Completed**\n\n${result.summary || 'Task completed successfully'}`;
|
|
84
|
+
await this.coordinator.updateTaskProgress(this.swarmId, this.activeTask, message);
|
|
85
|
+
|
|
86
|
+
// Option to auto-close issue (disabled by default)
|
|
87
|
+
if (result.autoClose) {
|
|
88
|
+
console.log(`🏁 Closing GitHub issue #${this.activeTask}`);
|
|
89
|
+
// Use gh CLI to close issue
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
await this.coordinator.releaseTask(this.swarmId, this.activeTask);
|
|
93
|
+
console.log(`🔓 Released GitHub issue #${this.activeTask}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.activeTask = null;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('❌ Post-task hook error:', error.message);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Conflict detection hook
|
|
104
|
+
*/
|
|
105
|
+
async detectConflicts() {
|
|
106
|
+
try {
|
|
107
|
+
const status = await this.coordinator.getCoordinationStatus();
|
|
108
|
+
|
|
109
|
+
// Check if multiple swarms are working on similar files
|
|
110
|
+
// const conflicts = []; // Unused - will be used in future implementation
|
|
111
|
+
|
|
112
|
+
// Simple conflict detection based on swarm count
|
|
113
|
+
if (Object.keys(status.swarmStatus).length > 1) {
|
|
114
|
+
console.log('⚠️ Multiple swarms detected, checking for conflicts...');
|
|
115
|
+
|
|
116
|
+
// More sophisticated conflict detection could be added here
|
|
117
|
+
// For now, just warn about multiple active swarms
|
|
118
|
+
return {
|
|
119
|
+
hasConflicts: false,
|
|
120
|
+
warningCount: Object.keys(status.swarmStatus).length - 1,
|
|
121
|
+
message: 'Multiple swarms active, coordinate through GitHub issues',
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { hasConflicts: false };
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error('❌ Conflict detection error:', error.message);
|
|
128
|
+
return { error: error.message };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get coordination dashboard URL
|
|
134
|
+
*/
|
|
135
|
+
async getDashboardUrl() {
|
|
136
|
+
const baseUrl = `https://github.com/${this.coordinator.config.owner}/${this.coordinator.config.repo}`;
|
|
137
|
+
return {
|
|
138
|
+
issues: `${baseUrl}/issues?q=is:issue+is:open+label:${this.coordinator.config.labelPrefix}${this.swarmId}`,
|
|
139
|
+
allSwarms: `${baseUrl}/issues?q=is:issue+is:open+label:${this.coordinator.config.labelPrefix}`,
|
|
140
|
+
board: `${baseUrl}/projects`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Hook registration for Claude Code
|
|
146
|
+
async function registerHooks() {
|
|
147
|
+
const hooks = new ClaudeGitHubHooks({
|
|
148
|
+
owner: process.env.GITHUB_OWNER || 'ruvnet',
|
|
149
|
+
repo: process.env.GITHUB_REPO || 'ruv-FANN',
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Register with Claude Code's hook system
|
|
153
|
+
return {
|
|
154
|
+
'pre-task': (args) => hooks.preTask(args.description),
|
|
155
|
+
'post-edit': (args) => hooks.postEdit(args.file, args.changes),
|
|
156
|
+
'post-task': (args) => hooks.postTask(args.taskId, args.result),
|
|
157
|
+
'check-conflicts': () => hooks.detectConflicts(),
|
|
158
|
+
'get-dashboard': () => hooks.getDashboardUrl(),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = { ClaudeGitHubHooks, registerHooks };
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub CLI-based Coordinator for @sparkleideas/ruv-swarm
|
|
3
|
+
* Uses gh CLI for all GitHub operations - simpler and more reliable
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
const fs = require('fs').promises;
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const Database = require('better-sqlite3');
|
|
10
|
+
|
|
11
|
+
class GHCoordinator {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.config = {
|
|
14
|
+
owner: options.owner || process.env.GITHUB_OWNER,
|
|
15
|
+
repo: options.repo || process.env.GITHUB_REPO,
|
|
16
|
+
dbPath: options.dbPath || path.join(__dirname, '..', '..', 'data', 'gh-coordinator.db'),
|
|
17
|
+
labelPrefix: options.labelPrefix || 'swarm-',
|
|
18
|
+
...options,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
this.db = null;
|
|
22
|
+
this.initialize();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async initialize() {
|
|
26
|
+
// Check if gh CLI is available
|
|
27
|
+
try {
|
|
28
|
+
execSync('gh --version', { stdio: 'ignore' });
|
|
29
|
+
} catch {
|
|
30
|
+
throw new Error('GitHub CLI (gh) is not installed. Install it from https://cli.github.com/');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Setup database for local coordination state
|
|
34
|
+
await this.setupDatabase();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async setupDatabase() {
|
|
38
|
+
const dataDir = path.dirname(this.config.dbPath);
|
|
39
|
+
await fs.mkdir(dataDir, { recursive: true });
|
|
40
|
+
|
|
41
|
+
this.db = new Database(this.config.dbPath);
|
|
42
|
+
this.db.exec(`
|
|
43
|
+
CREATE TABLE IF NOT EXISTS swarm_tasks (
|
|
44
|
+
issue_number INTEGER PRIMARY KEY,
|
|
45
|
+
swarm_id TEXT,
|
|
46
|
+
locked_at INTEGER,
|
|
47
|
+
lock_expires INTEGER
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
CREATE TABLE IF NOT EXISTS swarm_registry (
|
|
51
|
+
swarm_id TEXT PRIMARY KEY,
|
|
52
|
+
user TEXT,
|
|
53
|
+
capabilities TEXT,
|
|
54
|
+
last_seen INTEGER DEFAULT (strftime('%s', 'now'))
|
|
55
|
+
);
|
|
56
|
+
`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get available tasks from GitHub issues
|
|
61
|
+
*/
|
|
62
|
+
async getAvailableTasks(filters = {}) {
|
|
63
|
+
let cmd = `gh issue list --repo ${this.config.owner}/${this.config.repo} --json number,title,labels,assignees,state,body --limit 100`;
|
|
64
|
+
|
|
65
|
+
if (filters.label) {
|
|
66
|
+
cmd += ` --label "${filters.label}"`;
|
|
67
|
+
}
|
|
68
|
+
if (filters.state) {
|
|
69
|
+
cmd += ` --state ${filters.state}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const output = execSync(cmd, { encoding: 'utf8' });
|
|
73
|
+
const issues = JSON.parse(output);
|
|
74
|
+
|
|
75
|
+
// Filter out already assigned tasks
|
|
76
|
+
const availableIssues = issues.filter(issue => {
|
|
77
|
+
// Check if issue has swarm assignment label
|
|
78
|
+
const hasSwarmLabel = issue.labels.some(l => l.name.startsWith(this.config.labelPrefix));
|
|
79
|
+
// Check if issue is assigned
|
|
80
|
+
const isAssigned = issue.assignees.length > 0;
|
|
81
|
+
|
|
82
|
+
return !hasSwarmLabel && !isAssigned;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return availableIssues;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Claim a task for a swarm
|
|
90
|
+
*/
|
|
91
|
+
async claimTask(swarmId, issueNumber) {
|
|
92
|
+
try {
|
|
93
|
+
// Add swarm label to issue
|
|
94
|
+
const label = `${this.config.labelPrefix}${swarmId}`;
|
|
95
|
+
execSync(`gh issue edit ${issueNumber} --repo ${this.config.owner}/${this.config.repo} --add-label "${label}"`, { stdio: 'ignore' });
|
|
96
|
+
|
|
97
|
+
// Add comment to issue
|
|
98
|
+
const comment = `🐝 Task claimed by swarm: ${swarmId}\n\nThis task is being worked on by an automated swarm agent. Updates will be posted as progress is made.`;
|
|
99
|
+
execSync(`gh issue comment ${issueNumber} --repo ${this.config.owner}/${this.config.repo} --body "${comment}"`, { stdio: 'ignore' });
|
|
100
|
+
|
|
101
|
+
// Record in local database
|
|
102
|
+
this.db.prepare(`
|
|
103
|
+
INSERT OR REPLACE INTO swarm_tasks (issue_number, swarm_id, locked_at, lock_expires)
|
|
104
|
+
VALUES (?, ?, strftime('%s', 'now'), strftime('%s', 'now', '+1 hour'))
|
|
105
|
+
`).run(issueNumber, swarmId);
|
|
106
|
+
|
|
107
|
+
return true;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error(`Failed to claim task ${issueNumber}:`, error.message);
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Release a task
|
|
116
|
+
*/
|
|
117
|
+
async releaseTask(swarmId, issueNumber) {
|
|
118
|
+
try {
|
|
119
|
+
const label = `${this.config.labelPrefix}${swarmId}`;
|
|
120
|
+
execSync(`gh issue edit ${issueNumber} --repo ${this.config.owner}/${this.config.repo} --remove-label "${label}"`, { stdio: 'ignore' });
|
|
121
|
+
|
|
122
|
+
this.db.prepare('DELETE FROM swarm_tasks WHERE issue_number = ?').run(issueNumber);
|
|
123
|
+
return true;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error(`Failed to release task ${issueNumber}:`, error.message);
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Update task progress
|
|
132
|
+
*/
|
|
133
|
+
async updateTaskProgress(swarmId, issueNumber, message) {
|
|
134
|
+
try {
|
|
135
|
+
const comment = `🔄 **Progress Update from swarm ${swarmId}**\n\n${message}`;
|
|
136
|
+
execSync(`gh issue comment ${issueNumber} --repo ${this.config.owner}/${this.config.repo} --body "${comment}"`, { stdio: 'ignore' });
|
|
137
|
+
return true;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error(`Failed to update task ${issueNumber}:`, error.message);
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Create a task allocation PR
|
|
146
|
+
*/
|
|
147
|
+
async createAllocationPR(allocations) {
|
|
148
|
+
const branch = `swarm-allocation-${Date.now()}`;
|
|
149
|
+
|
|
150
|
+
// Create allocation file
|
|
151
|
+
const allocationContent = {
|
|
152
|
+
timestamp: new Date().toISOString(),
|
|
153
|
+
allocations,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const allocationPath = '.github/swarm-allocations.json';
|
|
157
|
+
await fs.writeFile(allocationPath, JSON.stringify(allocationContent, null, 2));
|
|
158
|
+
|
|
159
|
+
// Create PR using gh CLI
|
|
160
|
+
try {
|
|
161
|
+
execSync(`git checkout -b ${branch}`, { stdio: 'ignore' });
|
|
162
|
+
execSync(`git add ${allocationPath}`, { stdio: 'ignore' });
|
|
163
|
+
execSync('git commit -m "Update swarm task allocations"', { stdio: 'ignore' });
|
|
164
|
+
execSync(`git push origin ${branch}`, { stdio: 'ignore' });
|
|
165
|
+
|
|
166
|
+
const prBody = `## Swarm Task Allocation Update
|
|
167
|
+
|
|
168
|
+
This PR updates the task allocation for active swarms.
|
|
169
|
+
|
|
170
|
+
### Allocations:
|
|
171
|
+
${allocations.map(a => `- Issue #${a.issue}: Assigned to swarm ${a.swarm_id}`).join('\n')}
|
|
172
|
+
|
|
173
|
+
This is an automated update from the swarm coordinator.`;
|
|
174
|
+
|
|
175
|
+
const output = execSync(`gh pr create --repo ${this.config.owner}/${this.config.repo} --title "Update swarm task allocations" --body "${prBody}" --base main --head ${branch}`, { encoding: 'utf8' });
|
|
176
|
+
|
|
177
|
+
return output.trim();
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error('Failed to create allocation PR:', error.message);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get swarm coordination status
|
|
186
|
+
*/
|
|
187
|
+
async getCoordinationStatus() {
|
|
188
|
+
// Get issues with swarm labels
|
|
189
|
+
const cmd = `gh issue list --repo ${this.config.owner}/${this.config.repo} --json number,title,labels,assignees --limit 100`;
|
|
190
|
+
const output = execSync(cmd, { encoding: 'utf8' });
|
|
191
|
+
const issues = JSON.parse(output);
|
|
192
|
+
|
|
193
|
+
const swarmTasks = issues.filter(issue =>
|
|
194
|
+
issue.labels.some(l => l.name.startsWith(this.config.labelPrefix)),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// Group by swarm
|
|
198
|
+
const swarmStatus = {};
|
|
199
|
+
swarmTasks.forEach(issue => {
|
|
200
|
+
const swarmLabel = issue.labels.find(l => l.name.startsWith(this.config.labelPrefix));
|
|
201
|
+
if (swarmLabel) {
|
|
202
|
+
const swarmId = swarmLabel.name.replace(this.config.labelPrefix, '');
|
|
203
|
+
if (!swarmStatus[swarmId]) {
|
|
204
|
+
swarmStatus[swarmId] = [];
|
|
205
|
+
}
|
|
206
|
+
swarmStatus[swarmId].push({
|
|
207
|
+
number: issue.number,
|
|
208
|
+
title: issue.title,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
totalIssues: issues.length,
|
|
215
|
+
swarmTasks: swarmTasks.length,
|
|
216
|
+
availableTasks: issues.length - swarmTasks.length,
|
|
217
|
+
swarmStatus,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Clean up stale locks
|
|
223
|
+
*/
|
|
224
|
+
async cleanupStaleLocks() {
|
|
225
|
+
const staleTasks = this.db.prepare(`
|
|
226
|
+
SELECT issue_number, swarm_id FROM swarm_tasks
|
|
227
|
+
WHERE lock_expires < strftime('%s', 'now')
|
|
228
|
+
`).all();
|
|
229
|
+
|
|
230
|
+
for (const task of staleTasks) {
|
|
231
|
+
await this.releaseTask(task.swarm_id, task.issue_number);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return staleTasks.length;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Example usage with gh CLI - commented out to avoid no-unused-vars warning
|
|
239
|
+
// async function example() {
|
|
240
|
+
// const coordinator = new GHCoordinator({
|
|
241
|
+
// owner: 'ruvnet',
|
|
242
|
+
// repo: 'ruv-FANN',
|
|
243
|
+
// });
|
|
244
|
+
//
|
|
245
|
+
// // Get available tasks
|
|
246
|
+
// const tasks = await coordinator.getAvailableTasks({ state: 'open' });
|
|
247
|
+
// console.log(`Found ${tasks.length} available tasks`);
|
|
248
|
+
//
|
|
249
|
+
// // Claim a task for a swarm
|
|
250
|
+
// if (tasks.length > 0) {
|
|
251
|
+
// const claimed = await coordinator.claimTask('swarm-123', tasks[0].number);
|
|
252
|
+
// console.log(`Claimed task #${tasks[0].number}: ${claimed}`);
|
|
253
|
+
// }
|
|
254
|
+
//
|
|
255
|
+
// // Get coordination status
|
|
256
|
+
// const status = await coordinator.getCoordinationStatus();
|
|
257
|
+
// console.log('Coordination status:', status);
|
|
258
|
+
// }
|
|
259
|
+
|
|
260
|
+
module.exports = GHCoordinator;
|
package/src/hooks/cli.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI handler for @sparkleideas/ruv-swarm hooks
|
|
5
|
+
* Usage: npx @sparkleideas/ruv-swarm hook <type> [options]
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { handleHook } from './index.js';
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
|
|
13
|
+
// Skip if not a hook command
|
|
14
|
+
if (args[0] !== 'hook') {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const [, hookType] = args;
|
|
19
|
+
const options = parseArgs(args.slice(2));
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const result = await handleHook(hookType, options);
|
|
23
|
+
|
|
24
|
+
// Output JSON response for Claude Code to parse
|
|
25
|
+
console.log(JSON.stringify(result, null, 2));
|
|
26
|
+
|
|
27
|
+
// Exit with appropriate code
|
|
28
|
+
if (result.continue === false) {
|
|
29
|
+
process.exit(2); // Blocking error
|
|
30
|
+
} else {
|
|
31
|
+
process.exit(0); // Success
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error(JSON.stringify({
|
|
35
|
+
continue: true,
|
|
36
|
+
error: error.message,
|
|
37
|
+
stack: process.env.DEBUG ? error.stack : undefined,
|
|
38
|
+
}));
|
|
39
|
+
process.exit(1); // Non-blocking error
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parseArgs(args) {
|
|
44
|
+
const options = {};
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < args.length; i++) {
|
|
47
|
+
const arg = args[i];
|
|
48
|
+
|
|
49
|
+
if (arg.startsWith('--')) {
|
|
50
|
+
const key = arg.substring(2);
|
|
51
|
+
|
|
52
|
+
// Check if next arg is a value or another flag
|
|
53
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
|
|
54
|
+
// Next arg is the value
|
|
55
|
+
options[toCamelCase(key)] = args[i + 1];
|
|
56
|
+
i++; // Skip the value in next iteration
|
|
57
|
+
} else {
|
|
58
|
+
// Boolean flag
|
|
59
|
+
options[toCamelCase(key)] = true;
|
|
60
|
+
}
|
|
61
|
+
} else if (!args[i - 1]?.startsWith('--')) {
|
|
62
|
+
// Positional argument
|
|
63
|
+
if (!options._) {
|
|
64
|
+
options._ = [];
|
|
65
|
+
}
|
|
66
|
+
options._.push(arg);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return options;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function toCamelCase(str) {
|
|
74
|
+
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Run if called directly
|
|
78
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
79
|
+
main().catch(console.error);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { main };
|