claude-flow 2.0.0-alpha.76 → 2.0.0-alpha.78

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 CHANGED
@@ -45,8 +45,9 @@
45
45
  # 1. Install Claude Code globally
46
46
  npm install -g @anthropic-ai/claude-code
47
47
 
48
- # 2. Activate Claude Code with permissions
49
- claude --dangerously-skip-permissions
48
+ # 2. (Optional) Skip permissions check for faster setup
49
+ # Only use if you understand the security implications
50
+ # claude --dangerously-skip-permissions
50
51
  ```
51
52
 
52
53
  💡 **Windows Note**: If you encounter SQLite errors, Claude Flow will automatically use in-memory storage. For persistent storage options, see our [Windows guide](https://github.com/ruvnet/claude-code-flow/blob/main/docs/windows-installation.md).
@@ -61,7 +62,7 @@ npx claude-flow@alpha init --force
61
62
  npx claude-flow@alpha --help
62
63
 
63
64
  # 3a. Quick AI coordination (recommended for most tasks)
64
- npx claude-flow@alpha swarm "build me a REST API" --claude
65
+ npx claude-flow@alpha swarm "build me a REST API"
65
66
 
66
67
  # 3b. OR launch the full hive-mind system (for complex projects)
67
68
  npx claude-flow@alpha hive-mind wizard
@@ -269,7 +270,7 @@ Claude-Flow v2.0.0 introduces groundbreaking hive-mind architecture where a **Qu
269
270
 
270
271
  ```bash
271
272
  # Deploy intelligent swarm coordination
272
- npx claude-flow@alpha swarm "Build a full-stack application" --strategy development --claude
273
+ npx claude-flow@alpha swarm "Build a full-stack application" --strategy development
273
274
 
274
275
  # Launch hive-mind with specific specializations
275
276
  npx claude-flow@alpha hive-mind spawn "Create microservices architecture" --agents 8 --claude
@@ -501,8 +502,7 @@ npx claude-flow@alpha workflow create --name "Development Pipeline" --parallel
501
502
  npx claude-flow@alpha hive-mind spawn "Build e-commerce platform with React, Node.js, and PostgreSQL" \
502
503
  --agents 10 \
503
504
  --strategy parallel \
504
- --memory-namespace ecommerce \
505
- --claude
505
+ --memory-namespace ecommerce
506
506
 
507
507
  # Monitor progress in real-time
508
508
  npx claude-flow@alpha swarm monitor --dashboard --real-time
@@ -514,8 +514,7 @@ npx claude-flow@alpha swarm monitor --dashboard --real-time
514
514
  npx claude-flow@alpha swarm "Research AI safety in autonomous systems" \
515
515
  --strategy research \
516
516
  --neural-patterns enabled \
517
- --memory-compression high \
518
- --claude
517
+ --memory-compression high
519
518
 
520
519
  # Analyze results with cognitive computing
521
520
  npx claude-flow@alpha cognitive analyze --target research-results
package/bin/claude-flow CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
  # Claude-Flow Smart Dispatcher - Detects and uses the best available runtime
3
3
 
4
- VERSION="2.0.0-alpha.76"
4
+ VERSION="2.0.0-alpha.78"
5
5
 
6
6
  # Determine the correct path based on how the script is invoked
7
7
  if [ -L "$0" ]; then
@@ -11,7 +11,7 @@ import { existsSync } from 'fs';
11
11
  import { spawn } from 'child_process';
12
12
  import process from 'process';
13
13
 
14
- const VERSION = "2.0.0-alpha.76";
14
+ const VERSION = "2.0.0-alpha.78";
15
15
 
16
16
  // Get script directory and root directory
17
17
  const __filename = fileURLToPath(import.meta.url);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "2.0.0-alpha.76",
3
+ "version": "2.0.0-alpha.78",
4
4
  "description": "Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)",
5
5
  "main": "cli.mjs",
6
6
  "bin": {
@@ -122,7 +122,8 @@
122
122
  "ora": "^7.0.1",
123
123
  "p-queue": "^8.1.0",
124
124
  "ruv-swarm": "^1.0.14",
125
- "ws": "^8.18.3"
125
+ "ws": "^8.18.3",
126
+ "yaml": "^2.8.0"
126
127
  },
127
128
  "optionalDependencies": {
128
129
  "better-sqlite3": "^12.2.0",
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Dynamic Agent Loader - Reads agent definitions from .claude/agents/ directory
3
+ * This is the single source of truth for all agent types in the system
4
+ */
5
+
6
+ import { readFileSync, existsSync } from 'node:fs';
7
+ import { glob } from 'glob';
8
+ import { resolve, dirname } from 'node:path';
9
+ import { parse as parseYaml } from 'yaml';
10
+
11
+ export interface AgentDefinition {
12
+ name: string;
13
+ type?: string;
14
+ color?: string;
15
+ description: string;
16
+ capabilities?: string[];
17
+ priority?: 'low' | 'medium' | 'high' | 'critical';
18
+ hooks?: {
19
+ pre?: string;
20
+ post?: string;
21
+ };
22
+ content?: string; // The markdown content after frontmatter
23
+ }
24
+
25
+ export interface AgentCategory {
26
+ name: string;
27
+ agents: AgentDefinition[];
28
+ }
29
+
30
+ class AgentLoader {
31
+ private agentCache: Map<string, AgentDefinition> = new Map();
32
+ private categoriesCache: AgentCategory[] = [];
33
+ private lastLoadTime = 0;
34
+ private cacheExpiry = 60000; // 1 minute cache
35
+
36
+ /**
37
+ * Get the .claude/agents directory path
38
+ */
39
+ private getAgentsDirectory(): string {
40
+ // Start from current working directory and walk up to find .claude/agents
41
+ let currentDir = process.cwd();
42
+
43
+ while (currentDir !== '/') {
44
+ const claudeAgentsPath = resolve(currentDir, '.claude', 'agents');
45
+ if (existsSync(claudeAgentsPath)) {
46
+ return claudeAgentsPath;
47
+ }
48
+ currentDir = dirname(currentDir);
49
+ }
50
+
51
+ // Fallback to relative path
52
+ return resolve(process.cwd(), '.claude', 'agents');
53
+ }
54
+
55
+ /**
56
+ * Parse agent definition from markdown file
57
+ */
58
+ private parseAgentFile(filePath: string): AgentDefinition | null {
59
+ try {
60
+ const content = readFileSync(filePath, 'utf-8');
61
+
62
+ // Split frontmatter and content
63
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
64
+ if (!frontmatterMatch) {
65
+ console.warn(`No frontmatter found in ${filePath}`);
66
+ return null;
67
+ }
68
+
69
+ const [, yamlContent, markdownContent] = frontmatterMatch;
70
+ const frontmatter = parseYaml(yamlContent);
71
+
72
+ if (!frontmatter.name || !frontmatter.metadata?.description) {
73
+ console.warn(`Missing required fields (name, metadata.description) in ${filePath}`);
74
+ return null;
75
+ }
76
+
77
+ return {
78
+ name: frontmatter.name,
79
+ type: frontmatter.type,
80
+ color: frontmatter.color,
81
+ description: frontmatter.metadata.description,
82
+ capabilities: frontmatter.metadata.capabilities || frontmatter.capabilities || [],
83
+ priority: frontmatter.priority || 'medium',
84
+ hooks: frontmatter.hooks,
85
+ content: markdownContent.trim(),
86
+ };
87
+ } catch (error) {
88
+ console.error(`Error parsing agent file ${filePath}:`, error);
89
+ return null;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Load all agent definitions from .claude/agents directory
95
+ */
96
+ private async loadAgents(): Promise<void> {
97
+ const agentsDir = this.getAgentsDirectory();
98
+
99
+ if (!existsSync(agentsDir)) {
100
+ console.warn(`Agents directory not found: ${agentsDir}`);
101
+ return;
102
+ }
103
+
104
+ // Find all .md files in the agents directory
105
+ const agentFiles = await glob('**/*.md', {
106
+ cwd: agentsDir,
107
+ ignore: ['**/README.md', '**/MIGRATION_SUMMARY.md'],
108
+ absolute: true,
109
+ });
110
+
111
+ // Clear cache
112
+ this.agentCache.clear();
113
+ this.categoriesCache = [];
114
+
115
+ // Track categories
116
+ const categoryMap = new Map<string, AgentDefinition[]>();
117
+
118
+ // Parse each agent file
119
+ for (const filePath of agentFiles) {
120
+ const agent = this.parseAgentFile(filePath);
121
+ if (agent) {
122
+ this.agentCache.set(agent.name, agent);
123
+
124
+ // Determine category from file path
125
+ const relativePath = filePath.replace(agentsDir, '');
126
+ const pathParts = relativePath.split('/');
127
+ const category = pathParts[1] || 'uncategorized'; // First directory after agents/
128
+
129
+ if (!categoryMap.has(category)) {
130
+ categoryMap.set(category, []);
131
+ }
132
+ categoryMap.get(category)!.push(agent);
133
+ }
134
+ }
135
+
136
+ // Build categories array
137
+ this.categoriesCache = Array.from(categoryMap.entries()).map(([name, agents]) => ({
138
+ name,
139
+ agents: agents.sort((a, b) => a.name.localeCompare(b.name)),
140
+ }));
141
+
142
+ this.lastLoadTime = Date.now();
143
+ }
144
+
145
+ /**
146
+ * Check if cache needs refresh
147
+ */
148
+ private needsRefresh(): boolean {
149
+ return Date.now() - this.lastLoadTime > this.cacheExpiry;
150
+ }
151
+
152
+ /**
153
+ * Ensure agents are loaded and cache is fresh
154
+ */
155
+ private async ensureLoaded(): Promise<void> {
156
+ if (this.agentCache.size === 0 || this.needsRefresh()) {
157
+ await this.loadAgents();
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Get all available agent types
163
+ */
164
+ async getAvailableAgentTypes(): Promise<string[]> {
165
+ await this.ensureLoaded();
166
+ return Array.from(this.agentCache.keys()).sort();
167
+ }
168
+
169
+ /**
170
+ * Get agent definition by name
171
+ */
172
+ async getAgent(name: string): Promise<AgentDefinition | null> {
173
+ await this.ensureLoaded();
174
+ return this.agentCache.get(name) || null;
175
+ }
176
+
177
+ /**
178
+ * Get all agent definitions
179
+ */
180
+ async getAllAgents(): Promise<AgentDefinition[]> {
181
+ await this.ensureLoaded();
182
+ return Array.from(this.agentCache.values()).sort((a, b) => a.name.localeCompare(b.name));
183
+ }
184
+
185
+ /**
186
+ * Get agents organized by category
187
+ */
188
+ async getAgentCategories(): Promise<AgentCategory[]> {
189
+ await this.ensureLoaded();
190
+ return this.categoriesCache;
191
+ }
192
+
193
+ /**
194
+ * Search agents by capabilities, description, or name
195
+ */
196
+ async searchAgents(query: string): Promise<AgentDefinition[]> {
197
+ await this.ensureLoaded();
198
+ const lowerQuery = query.toLowerCase();
199
+
200
+ return Array.from(this.agentCache.values()).filter(agent => {
201
+ return (
202
+ agent.name.toLowerCase().includes(lowerQuery) ||
203
+ agent.description.toLowerCase().includes(lowerQuery) ||
204
+ agent.capabilities?.some(cap => cap.toLowerCase().includes(lowerQuery)) ||
205
+ false
206
+ );
207
+ });
208
+ }
209
+
210
+ /**
211
+ * Check if an agent type is valid
212
+ */
213
+ async isValidAgentType(name: string): Promise<boolean> {
214
+ await this.ensureLoaded();
215
+ return this.agentCache.has(name);
216
+ }
217
+
218
+ /**
219
+ * Get agents by category name
220
+ */
221
+ async getAgentsByCategory(category: string): Promise<AgentDefinition[]> {
222
+ const categories = await this.getAgentCategories();
223
+ const found = categories.find(cat => cat.name === category);
224
+ return found?.agents || [];
225
+ }
226
+
227
+ /**
228
+ * Force refresh the agent cache
229
+ */
230
+ async refresh(): Promise<void> {
231
+ this.lastLoadTime = 0; // Force reload
232
+ await this.loadAgents();
233
+ }
234
+ }
235
+
236
+ // Singleton instance
237
+ export const agentLoader = new AgentLoader();
238
+
239
+ // Convenience functions
240
+ export const getAvailableAgentTypes = () => agentLoader.getAvailableAgentTypes();
241
+ export const getAgent = (name: string) => agentLoader.getAgent(name);
242
+ export const getAllAgents = () => agentLoader.getAllAgents();
243
+ export const getAgentCategories = () => agentLoader.getAgentCategories();
244
+ export const searchAgents = (query: string) => agentLoader.searchAgents(query);
245
+ export const isValidAgentType = (name: string) => agentLoader.isValidAgentType(name);
246
+ export const getAgentsByCategory = (category: string) => agentLoader.getAgentsByCategory(category);
247
+ export const refreshAgents = () => agentLoader.refresh();
@@ -5,14 +5,14 @@
5
5
 
6
6
  import { HelpFormatter } from './help-formatter.js';
7
7
 
8
- export const VERSION = '2.0.0-alpha.76';
8
+ export const VERSION = '2.0.0-alpha.78';
9
9
 
10
10
  export const MAIN_HELP = `
11
11
  🌊 Claude-Flow v${VERSION} - Enterprise-Grade AI Agent Orchestration Platform
12
12
 
13
13
  🎯 ENTERPRISE FEATURES: Complete ruv-swarm integration with 87 MCP tools, neural networking, and production-ready infrastructure
14
14
  🐝 NEW: Advanced Hive Mind System with Queen-led coordination, collective intelligence, and unlimited scaling
15
- ⚡ ALPHA 76: Fixed hooks pre-task command not exiting properly
15
+ ⚡ ALPHA 78: Removed legacy Deno warnings, enhanced SPARC slash commands
16
16
 
17
17
  USAGE:
18
18
  claude-flow <command> [options]
@@ -137,29 +137,40 @@ async function preTaskCommand(subArgs, flags) {
137
137
 
138
138
  console.log(` 💾 Saved to .swarm/memory.db`);
139
139
 
140
- // Execute ruv-swarm hook if available
141
- const isAvailable = await checkRuvSwarmAvailable();
142
- if (isAvailable) {
143
- console.log(`\n🔄 Executing ruv-swarm pre-task hook...`);
144
- const hookResult = await execRuvSwarmHook('pre-task', {
145
- description,
146
- 'task-id': taskId,
147
- 'auto-spawn-agents': autoSpawnAgents,
148
- ...(agentId ? { 'agent-id': agentId } : {}),
149
- });
150
-
151
- if (hookResult.success) {
152
- await store.store(
153
- `task:${taskId}:ruv-output`,
154
- {
155
- output: hookResult.output,
156
- timestamp: new Date().toISOString(),
157
- },
158
- { namespace: 'hooks:ruv-swarm' },
159
- );
160
-
161
- printSuccess(`✅ Pre-task hook completed successfully`);
140
+ // Execute ruv-swarm hook if available (with timeout for npx scenarios)
141
+ try {
142
+ const checkPromise = checkRuvSwarmAvailable();
143
+ const timeoutPromise = new Promise((_, reject) =>
144
+ setTimeout(() => reject(new Error('Timeout')), 3000)
145
+ );
146
+
147
+ const isAvailable = await Promise.race([checkPromise, timeoutPromise]);
148
+
149
+ if (isAvailable) {
150
+ console.log(`\n🔄 Executing ruv-swarm pre-task hook...`);
151
+ const hookResult = await execRuvSwarmHook('pre-task', {
152
+ description,
153
+ 'task-id': taskId,
154
+ 'auto-spawn-agents': autoSpawnAgents,
155
+ ...(agentId ? { 'agent-id': agentId } : {}),
156
+ });
157
+
158
+ if (hookResult.success) {
159
+ await store.store(
160
+ `task:${taskId}:ruv-output`,
161
+ {
162
+ output: hookResult.output,
163
+ timestamp: new Date().toISOString(),
164
+ },
165
+ { namespace: 'hooks:ruv-swarm' },
166
+ );
167
+
168
+ printSuccess(`✅ Pre-task hook completed successfully`);
169
+ }
162
170
  }
171
+ } catch (err) {
172
+ // Skip ruv-swarm hook if it times out or fails
173
+ console.log(`\n⚠️ Skipping ruv-swarm hook (${err.message})`);
163
174
  }
164
175
 
165
176
  console.log(`\n🎯 TASK PREPARATION COMPLETE`);
@@ -169,8 +180,10 @@ async function preTaskCommand(subArgs, flags) {
169
180
  memoryStore.close();
170
181
  }
171
182
 
172
- // Ensure the process exits cleanly
173
- process.exit(0);
183
+ // Force exit after a short delay to ensure cleanup
184
+ setTimeout(() => {
185
+ process.exit(0);
186
+ }, 100);
174
187
  } catch (err) {
175
188
  printError(`Pre-task hook failed: ${err.message}`);
176
189
 
@@ -179,7 +192,10 @@ async function preTaskCommand(subArgs, flags) {
179
192
  memoryStore.close();
180
193
  }
181
194
 
182
- process.exit(1);
195
+ // Force exit after a short delay to ensure cleanup
196
+ setTimeout(() => {
197
+ process.exit(1);
198
+ }, 100);
183
199
  }
184
200
  }
185
201
 
@@ -51,6 +51,7 @@ export async function createClaudeSlashCommands(workingDir) {
51
51
  // Create claude-flow specific commands
52
52
  await createClaudeFlowCommands(workingDir);
53
53
  } catch (err) {
54
- console.log(` ⚠️ Could not create Claude Code slash commands: ${err.message}`);
54
+ // Legacy slash command creation - silently skip if it fails
55
+ // SPARC slash commands are already created successfully
55
56
  }
56
57
  }
@@ -299,7 +299,8 @@ export async function initCommand(subArgs, flags) {
299
299
  await createClaudeSlashCommands(workingDir);
300
300
  }
301
301
  } catch (err) {
302
- printWarning(`Could not create Claude Code slash commands: ${err.message}`);
302
+ // Legacy slash command creation - silently skip if it fails
303
+ // SPARC slash commands are already created successfully above
303
304
  }
304
305
  }
305
306
  }
@@ -1069,7 +1070,7 @@ ${commands.map((cmd) => `- [${cmd}](./${cmd}.md)`).join('\n')}
1069
1070
  }
1070
1071
 
1071
1072
  // Create helper scripts
1072
- const helpers = ['setup-mcp.sh', 'quick-start.sh', 'github-setup.sh'];
1073
+ const helpers = ['setup-mcp.sh', 'quick-start.sh', 'github-setup.sh', 'github-safe.js'];
1073
1074
  for (const helper of helpers) {
1074
1075
  if (!dryRun) {
1075
1076
  const content = createHelperScript(helper);
@@ -1308,17 +1309,20 @@ ${commands.map((cmd) => `- [${cmd}](./${cmd}.md)`).join('\n')}
1308
1309
  console.log('\n📚 Quick Start:');
1309
1310
  if (isClaudeCodeInstalled()) {
1310
1311
  console.log('1. View available commands: ls .claude/commands/');
1311
- console.log('2. Start a swarm: npx claude-flow swarm init');
1312
- console.log('3. Use MCP tools in Claude Code for enhanced coordination');
1312
+ console.log('2. Start a swarm: npx claude-flow@alpha swarm "your objective" --claude');
1313
+ console.log('3. Use hive-mind: npx claude-flow@alpha hive-mind spawn "command" --claude');
1314
+ console.log('4. Use MCP tools in Claude Code for enhanced coordination');
1313
1315
  } else {
1314
1316
  console.log('1. Install Claude Code: npm install -g @anthropic-ai/claude-code');
1315
1317
  console.log('2. Add MCP servers (see instructions above)');
1316
1318
  console.log('3. View available commands: ls .claude/commands/');
1317
- console.log('4. Start a swarm: npx claude-flow swarm init');
1319
+ console.log('4. Start a swarm: npx claude-flow@alpha swarm "your objective" --claude');
1320
+ console.log('5. Use hive-mind: npx claude-flow@alpha hive-mind spawn "command" --claude');
1318
1321
  }
1319
1322
  console.log('\n💡 Tips:');
1320
1323
  console.log('• Check .claude/commands/ for detailed documentation');
1321
1324
  console.log('• Use --help with any command for options');
1325
+ console.log('• Run commands with --claude flag for best Claude Code integration');
1322
1326
  console.log('• Enable GitHub integration with .claude/helpers/github-setup.sh');
1323
1327
  } catch (err) {
1324
1328
  printError(`Failed to initialize Claude Flow v2.0.0: ${err.message}`);
@@ -311,7 +311,7 @@ async function copyHelperScripts(templatesDir, targetDir, options, results) {
311
311
  await fs.mkdir(helpersDir, { recursive: true });
312
312
  }
313
313
 
314
- const helpers = ['setup-mcp.sh', 'quick-start.sh', 'github-setup.sh'];
314
+ const helpers = ['setup-mcp.sh', 'quick-start.sh', 'github-setup.sh', 'github-safe.js'];
315
315
  const { createHelperScript } = await import('./templates/enhanced-templates.js');
316
316
 
317
317
  for (const helper of helpers) {
@@ -1061,6 +1061,113 @@ echo " - npx claude-flow github swarm"
1061
1061
  echo " - npx claude-flow repo analyze"
1062
1062
  echo " - npx claude-flow pr enhance"
1063
1063
  echo " - npx claude-flow issue triage"
1064
+ `,
1065
+ 'github-safe.js': `#!/usr/bin/env node
1066
+
1067
+ /**
1068
+ * Safe GitHub CLI Helper
1069
+ * Prevents timeout issues when using gh commands with special characters
1070
+ *
1071
+ * Usage:
1072
+ * ./github-safe.js issue comment 123 "Message with \`backticks\`"
1073
+ * ./github-safe.js pr create --title "Title" --body "Complex body"
1074
+ */
1075
+
1076
+ import { execSync } from 'child_process';
1077
+ import { writeFileSync, unlinkSync } from 'fs';
1078
+ import { tmpdir } from 'os';
1079
+ import { join } from 'path';
1080
+ import { randomBytes } from 'crypto';
1081
+
1082
+ const args = process.argv.slice(2);
1083
+
1084
+ if (args.length < 2) {
1085
+ console.log(\`
1086
+ Safe GitHub CLI Helper
1087
+
1088
+ Usage:
1089
+ ./github-safe.js issue comment <number> <body>
1090
+ ./github-safe.js pr comment <number> <body>
1091
+ ./github-safe.js issue create --title <title> --body <body>
1092
+ ./github-safe.js pr create --title <title> --body <body>
1093
+
1094
+ This helper prevents timeout issues with special characters like:
1095
+ - Backticks in code examples
1096
+ - Command substitution \\$(...)
1097
+ - Directory paths
1098
+ - Special shell characters
1099
+ \`);
1100
+ process.exit(1);
1101
+ }
1102
+
1103
+ const [command, subcommand, ...restArgs] = args;
1104
+
1105
+ // Handle commands that need body content
1106
+ if ((command === 'issue' || command === 'pr') &&
1107
+ (subcommand === 'comment' || subcommand === 'create')) {
1108
+
1109
+ let bodyIndex = -1;
1110
+ let body = '';
1111
+
1112
+ if (subcommand === 'comment' && restArgs.length >= 2) {
1113
+ // Simple format: github-safe.js issue comment 123 "body"
1114
+ body = restArgs[1];
1115
+ bodyIndex = 1;
1116
+ } else {
1117
+ // Flag format: --body "content"
1118
+ bodyIndex = restArgs.indexOf('--body');
1119
+ if (bodyIndex !== -1 && bodyIndex < restArgs.length - 1) {
1120
+ body = restArgs[bodyIndex + 1];
1121
+ }
1122
+ }
1123
+
1124
+ if (body) {
1125
+ // Use temporary file for body content
1126
+ const tmpFile = join(tmpdir(), \`gh-body-\${randomBytes(8).toString('hex')}.tmp\`);
1127
+
1128
+ try {
1129
+ writeFileSync(tmpFile, body, 'utf8');
1130
+
1131
+ // Build new command with --body-file
1132
+ const newArgs = [...restArgs];
1133
+ if (subcommand === 'comment' && bodyIndex === 1) {
1134
+ // Replace body with --body-file
1135
+ newArgs[1] = '--body-file';
1136
+ newArgs.push(tmpFile);
1137
+ } else if (bodyIndex !== -1) {
1138
+ // Replace --body with --body-file
1139
+ newArgs[bodyIndex] = '--body-file';
1140
+ newArgs[bodyIndex + 1] = tmpFile;
1141
+ }
1142
+
1143
+ // Execute safely
1144
+ const ghCommand = \`gh \${command} \${subcommand} \${newArgs.join(' ')}\`;
1145
+ console.log(\`Executing: \${ghCommand}\`);
1146
+
1147
+ const result = execSync(ghCommand, {
1148
+ stdio: 'inherit',
1149
+ timeout: 30000 // 30 second timeout
1150
+ });
1151
+
1152
+ } catch (error) {
1153
+ console.error('Error:', error.message);
1154
+ process.exit(1);
1155
+ } finally {
1156
+ // Clean up
1157
+ try {
1158
+ unlinkSync(tmpFile);
1159
+ } catch (e) {
1160
+ // Ignore cleanup errors
1161
+ }
1162
+ }
1163
+ } else {
1164
+ // No body content, execute normally
1165
+ execSync(\`gh \${args.join(' ')}\`, { stdio: 'inherit' });
1166
+ }
1167
+ } else {
1168
+ // Other commands, execute normally
1169
+ execSync(\`gh \${args.join(' ')}\`, { stdio: 'inherit' });
1170
+ }
1064
1171
  `,
1065
1172
  };
1066
1173
 
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Safe GitHub CLI Helper
5
+ * Prevents timeout issues when using gh commands with special characters
6
+ *
7
+ * Usage:
8
+ * ./github-safe.js issue comment 123 "Message with `backticks`"
9
+ * ./github-safe.js pr create --title "Title" --body "Complex body"
10
+ */
11
+
12
+ import { execSync } from 'child_process';
13
+ import { writeFileSync, unlinkSync } from 'fs';
14
+ import { tmpdir } from 'os';
15
+ import { join } from 'path';
16
+ import { randomBytes } from 'crypto';
17
+
18
+ const args = process.argv.slice(2);
19
+
20
+ if (args.length < 2) {
21
+ console.log(`
22
+ Safe GitHub CLI Helper
23
+
24
+ Usage:
25
+ ./github-safe.js issue comment <number> <body>
26
+ ./github-safe.js pr comment <number> <body>
27
+ ./github-safe.js issue create --title <title> --body <body>
28
+ ./github-safe.js pr create --title <title> --body <body>
29
+
30
+ This helper prevents timeout issues with special characters like:
31
+ - Backticks in code examples
32
+ - Command substitution \$(...)
33
+ - Directory paths
34
+ - Special shell characters
35
+ `);
36
+ process.exit(1);
37
+ }
38
+
39
+ const [command, subcommand, ...restArgs] = args;
40
+
41
+ // Handle commands that need body content
42
+ if ((command === 'issue' || command === 'pr') &&
43
+ (subcommand === 'comment' || subcommand === 'create')) {
44
+
45
+ let bodyIndex = -1;
46
+ let body = '';
47
+
48
+ if (subcommand === 'comment' && restArgs.length >= 2) {
49
+ // Simple format: github-safe.js issue comment 123 "body"
50
+ body = restArgs[1];
51
+ bodyIndex = 1;
52
+ } else {
53
+ // Flag format: --body "content"
54
+ bodyIndex = restArgs.indexOf('--body');
55
+ if (bodyIndex !== -1 && bodyIndex < restArgs.length - 1) {
56
+ body = restArgs[bodyIndex + 1];
57
+ }
58
+ }
59
+
60
+ if (body) {
61
+ // Use temporary file for body content
62
+ const tmpFile = join(tmpdir(), `gh-body-${randomBytes(8).toString('hex')}.tmp`);
63
+
64
+ try {
65
+ writeFileSync(tmpFile, body, 'utf8');
66
+
67
+ // Build new command with --body-file
68
+ const newArgs = [...restArgs];
69
+ if (subcommand === 'comment' && bodyIndex === 1) {
70
+ // Replace body with --body-file
71
+ newArgs[1] = '--body-file';
72
+ newArgs.push(tmpFile);
73
+ } else if (bodyIndex !== -1) {
74
+ // Replace --body with --body-file
75
+ newArgs[bodyIndex] = '--body-file';
76
+ newArgs[bodyIndex + 1] = tmpFile;
77
+ }
78
+
79
+ // Execute safely
80
+ const ghCommand = `gh ${command} ${subcommand} ${newArgs.join(' ')}`;
81
+ console.log(`Executing: ${ghCommand}`);
82
+
83
+ const result = execSync(ghCommand, {
84
+ stdio: 'inherit',
85
+ timeout: 30000 // 30 second timeout
86
+ });
87
+
88
+ } catch (error) {
89
+ console.error('Error:', error.message);
90
+ process.exit(1);
91
+ } finally {
92
+ // Clean up
93
+ try {
94
+ unlinkSync(tmpFile);
95
+ } catch (e) {
96
+ // Ignore cleanup errors
97
+ }
98
+ }
99
+ } else {
100
+ // No body content, execute normally
101
+ execSync(`gh ${args.join(' ')}`, { stdio: 'inherit' });
102
+ }
103
+ } else {
104
+ // Other commands, execute normally
105
+ execSync(`gh ${args.join(' ')}`, { stdio: 'inherit' });
106
+ }
@@ -1,37 +1,56 @@
1
1
  /**
2
- * Central source of truth for agent types
3
- * This file ensures consistency across TypeScript types and runtime validation
2
+ * Agent Types - Dynamic loading from .claude/agents/ directory
3
+ * This file provides type-safe access to dynamically loaded agent definitions
4
4
  */
5
5
 
6
- export const AGENT_TYPES = {
7
- COORDINATOR: 'coordinator',
8
- RESEARCHER: 'researcher',
9
- CODER: 'coder',
10
- ANALYST: 'analyst',
11
- ARCHITECT: 'architect',
12
- TESTER: 'tester',
13
- REVIEWER: 'reviewer',
14
- OPTIMIZER: 'optimizer',
15
- DOCUMENTER: 'documenter',
16
- MONITOR: 'monitor',
17
- SPECIALIST: 'specialist',
6
+ import { getAvailableAgentTypes, isValidAgentType as validateAgentType } from '../agents/agent-loader.js';
7
+
8
+ // Dynamic agent type - will be a string that matches available agents
9
+ export type AgentType = string;
10
+
11
+ // Legacy agent type mapping for backward compatibility
12
+ export const LEGACY_AGENT_MAPPING = {
13
+ analyst: 'code-analyzer',
14
+ coordinator: 'task-orchestrator',
15
+ optimizer: 'perf-analyzer',
16
+ documenter: 'api-docs',
17
+ monitor: 'performance-benchmarker',
18
+ specialist: 'system-architect',
19
+ architect: 'system-architect',
18
20
  } as const;
19
21
 
20
- export type AgentType = (typeof AGENT_TYPES)[keyof typeof AGENT_TYPES];
22
+ /**
23
+ * Get all valid agent types dynamically
24
+ */
25
+ export async function getValidAgentTypes(): Promise<string[]> {
26
+ return await getAvailableAgentTypes();
27
+ }
21
28
 
22
- // Array of all valid agent types for runtime validation
23
- export const VALID_AGENT_TYPES = Object.values(AGENT_TYPES);
29
+ /**
30
+ * Helper function to validate agent type
31
+ */
32
+ export async function isValidAgentType(type: string): Promise<boolean> {
33
+ return await validateAgentType(type);
34
+ }
24
35
 
25
- // JSON Schema for agent type validation
26
- export const AGENT_TYPE_SCHEMA = {
27
- type: 'string',
28
- enum: VALID_AGENT_TYPES,
29
- description: 'Type of AI agent',
30
- };
36
+ /**
37
+ * Resolve legacy agent types to current equivalents
38
+ */
39
+ export function resolveLegacyAgentType(legacyType: string): string {
40
+ const mapped = LEGACY_AGENT_MAPPING[legacyType as keyof typeof LEGACY_AGENT_MAPPING];
41
+ return mapped || legacyType;
42
+ }
31
43
 
32
- // Helper function to validate agent type
33
- export function isValidAgentType(type: string): type is AgentType {
34
- return VALID_AGENT_TYPES.includes(type as AgentType);
44
+ /**
45
+ * Create JSON Schema for agent type validation (async)
46
+ */
47
+ export async function getAgentTypeSchema() {
48
+ const validTypes = await getValidAgentTypes();
49
+ return {
50
+ type: 'string',
51
+ enum: validTypes,
52
+ description: 'Type of specialized AI agent',
53
+ };
35
54
  }
36
55
 
37
56
  // Strategy types
@@ -4,17 +4,49 @@
4
4
 
5
5
  import type { MCPTool, MCPContext, AgentProfile, Task, MemoryEntry } from '../utils/types.js';
6
6
  import type { ILogger } from '../core/logger.js';
7
+ import { getAvailableAgentTypes, getAgentTypeSchema } from '../constants/agent-types.js';
7
8
  import type { Permissions } from './auth.js';
8
9
 
9
10
  export interface ClaudeFlowToolContext extends MCPContext {
10
11
  orchestrator?: any; // Reference to orchestrator instance
11
12
  }
12
13
 
14
+ /**
15
+ * Enhance tool schema with dynamic agent types
16
+ */
17
+ async function enhanceToolWithAgentTypes(tool: MCPTool): Promise<MCPTool> {
18
+ const availableTypes = await getAvailableAgentTypes();
19
+
20
+ // Clone the tool to avoid modifying the original
21
+ const enhancedTool = JSON.parse(JSON.stringify(tool));
22
+
23
+ // Find and populate enum fields for agent types
24
+ function addEnumToAgentTypeFields(obj: any) {
25
+ if (typeof obj !== 'object' || obj === null) return;
26
+
27
+ for (const [key, value] of Object.entries(obj)) {
28
+ if (typeof value === 'object' && value !== null) {
29
+ // Check if this is an agent type field
30
+ if (key === 'type' || key === 'filterByType' || key === 'assignToAgentType') {
31
+ const field = value as any;
32
+ if (field.type === 'string' && field.description?.includes('loaded dynamically from .claude/agents/')) {
33
+ field.enum = availableTypes;
34
+ }
35
+ }
36
+ addEnumToAgentTypeFields(value);
37
+ }
38
+ }
39
+ }
40
+
41
+ addEnumToAgentTypeFields(enhancedTool.inputSchema);
42
+ return enhancedTool;
43
+ }
44
+
13
45
  /**
14
46
  * Create all Claude-Flow specific MCP tools
15
47
  */
16
- export function createClaudeFlowTools(logger: ILogger): MCPTool[] {
17
- return [
48
+ export async function createClaudeFlowTools(logger: ILogger): Promise<MCPTool[]> {
49
+ const tools = [
18
50
  // Agent management tools
19
51
  createSpawnAgentTool(logger),
20
52
  createListAgentsTool(logger),
@@ -55,6 +87,13 @@ export function createClaudeFlowTools(logger: ILogger): MCPTool[] {
55
87
  createListTerminalsTool(logger),
56
88
  createCreateTerminalTool(logger),
57
89
  ];
90
+
91
+ // Enhance tools with dynamic agent types
92
+ const enhancedTools = await Promise.all(
93
+ tools.map(tool => enhanceToolWithAgentTypes(tool))
94
+ );
95
+
96
+ return enhancedTools;
58
97
  }
59
98
 
60
99
  function createSpawnAgentTool(logger: ILogger): MCPTool {
@@ -66,20 +105,8 @@ function createSpawnAgentTool(logger: ILogger): MCPTool {
66
105
  properties: {
67
106
  type: {
68
107
  type: 'string',
69
- enum: [
70
- 'coordinator',
71
- 'researcher',
72
- 'coder',
73
- 'analyst',
74
- 'architect',
75
- 'tester',
76
- 'reviewer',
77
- 'optimizer',
78
- 'documenter',
79
- 'monitor',
80
- 'specialist',
81
- ],
82
- description: 'Type of agent to spawn',
108
+ // Note: enum will be populated dynamically at runtime
109
+ description: 'Type of specialized agent to spawn (loaded dynamically from .claude/agents/)',
83
110
  },
84
111
  name: {
85
112
  type: 'string',
@@ -161,20 +188,8 @@ function createListAgentsTool(logger: ILogger): MCPTool {
161
188
  },
162
189
  filterByType: {
163
190
  type: 'string',
164
- enum: [
165
- 'coordinator',
166
- 'researcher',
167
- 'coder',
168
- 'analyst',
169
- 'architect',
170
- 'tester',
171
- 'reviewer',
172
- 'optimizer',
173
- 'documenter',
174
- 'monitor',
175
- 'specialist',
176
- ],
177
- description: 'Filter agents by type',
191
+ // Note: enum will be populated dynamically at runtime
192
+ description: 'Filter agents by type (loaded dynamically from .claude/agents/)',
178
193
  },
179
194
  },
180
195
  },
@@ -317,20 +332,8 @@ function createCreateTaskTool(logger: ILogger): MCPTool {
317
332
  },
318
333
  assignToAgentType: {
319
334
  type: 'string',
320
- enum: [
321
- 'coordinator',
322
- 'researcher',
323
- 'coder',
324
- 'analyst',
325
- 'architect',
326
- 'tester',
327
- 'reviewer',
328
- 'optimizer',
329
- 'documenter',
330
- 'monitor',
331
- 'specialist',
332
- ],
333
- description: 'Type of agent to assign the task to',
335
+ // Note: enum will be populated dynamically at runtime
336
+ description: 'Type of specialized agent to assign the task to (loaded dynamically from .claude/agents/)',
334
337
  },
335
338
  input: {
336
339
  type: 'object',
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Safe GitHub CLI command execution utilities
3
+ * Prevents shell injection and timeout issues with special characters
4
+ */
5
+
6
+ import { promises as fs } from 'fs';
7
+ import { tmpdir } from 'os';
8
+ import { join } from 'path';
9
+ import { randomBytes } from 'crypto';
10
+ import { spawn } from 'child_process';
11
+
12
+ /**
13
+ * Execute GitHub CLI command with safe body content handling
14
+ * @param {string} command - The gh command (e.g., 'issue comment')
15
+ * @param {string} target - Target (issue/PR number or repo)
16
+ * @param {string} body - Body content that may contain special characters
17
+ * @param {Object} options - Additional options
18
+ * @returns {Promise<string>} - Command output
19
+ */
20
+ export async function safeGhCommand(command, target, body, options = {}) {
21
+ const tmpFile = join(tmpdir(), `gh-body-${randomBytes(8).toString('hex')}.tmp`);
22
+
23
+ try {
24
+ // Write body content to temporary file
25
+ await fs.writeFile(tmpFile, body, 'utf8');
26
+
27
+ // Build command arguments
28
+ const args = command.split(' ');
29
+ args.push(target);
30
+ args.push('--body-file', tmpFile);
31
+
32
+ // Add any additional options
33
+ Object.entries(options).forEach(([key, value]) => {
34
+ args.push(`--${key}`, value);
35
+ });
36
+
37
+ // Execute command with timeout
38
+ const result = await executeWithTimeout('gh', args, options.timeout || 30000);
39
+ return result;
40
+ } finally {
41
+ // Clean up temp file
42
+ try {
43
+ await fs.unlink(tmpFile);
44
+ } catch (err) {
45
+ // Ignore cleanup errors
46
+ }
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Execute command with timeout and proper cleanup
52
+ * @param {string} command - Command to execute
53
+ * @param {string[]} args - Command arguments
54
+ * @param {number} timeout - Timeout in milliseconds
55
+ * @returns {Promise<string>} - Command output
56
+ */
57
+ function executeWithTimeout(command, args, timeout) {
58
+ return new Promise((resolve, reject) => {
59
+ const child = spawn(command, args, {
60
+ stdio: ['ignore', 'pipe', 'pipe'],
61
+ shell: false, // Important: don't use shell
62
+ });
63
+
64
+ let stdout = '';
65
+ let stderr = '';
66
+ let timedOut = false;
67
+
68
+ // Set timeout
69
+ const timer = setTimeout(() => {
70
+ timedOut = true;
71
+ child.kill('SIGTERM');
72
+ setTimeout(() => child.kill('SIGKILL'), 5000); // Force kill after 5s
73
+ }, timeout);
74
+
75
+ child.stdout.on('data', (data) => {
76
+ stdout += data.toString();
77
+ });
78
+
79
+ child.stderr.on('data', (data) => {
80
+ stderr += data.toString();
81
+ });
82
+
83
+ child.on('close', (code) => {
84
+ clearTimeout(timer);
85
+
86
+ if (timedOut) {
87
+ reject(new Error(`Command timed out after ${timeout}ms`));
88
+ } else if (code !== 0) {
89
+ reject(new Error(`Command failed with exit code ${code}: ${stderr}`));
90
+ } else {
91
+ resolve(stdout.trim());
92
+ }
93
+ });
94
+
95
+ child.on('error', (err) => {
96
+ clearTimeout(timer);
97
+ reject(err);
98
+ });
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Safe methods for common GitHub CLI operations
104
+ */
105
+ export const gh = {
106
+ /**
107
+ * Create issue comment safely
108
+ * @param {number|string} issue - Issue number
109
+ * @param {string} body - Comment body
110
+ * @param {Object} options - Additional options
111
+ */
112
+ async issueComment(issue, body, options = {}) {
113
+ return safeGhCommand('issue comment', issue.toString(), body, options);
114
+ },
115
+
116
+ /**
117
+ * Create PR comment safely
118
+ * @param {number|string} pr - PR number
119
+ * @param {string} body - Comment body
120
+ * @param {Object} options - Additional options
121
+ */
122
+ async prComment(pr, body, options = {}) {
123
+ return safeGhCommand('pr comment', pr.toString(), body, options);
124
+ },
125
+
126
+ /**
127
+ * Create issue safely
128
+ * @param {Object} params - Issue parameters
129
+ */
130
+ async createIssue({ title, body, labels = [], assignees = [] }) {
131
+ const tmpFile = join(tmpdir(), `gh-issue-${randomBytes(8).toString('hex')}.tmp`);
132
+
133
+ try {
134
+ await fs.writeFile(tmpFile, body, 'utf8');
135
+
136
+ const args = ['issue', 'create', '--title', title, '--body-file', tmpFile];
137
+
138
+ if (labels.length > 0) {
139
+ args.push('--label', labels.join(','));
140
+ }
141
+
142
+ if (assignees.length > 0) {
143
+ args.push('--assignee', assignees.join(','));
144
+ }
145
+
146
+ return await executeWithTimeout('gh', args, 30000);
147
+ } finally {
148
+ try {
149
+ await fs.unlink(tmpFile);
150
+ } catch (err) {
151
+ // Ignore cleanup errors
152
+ }
153
+ }
154
+ },
155
+
156
+ /**
157
+ * Create PR safely
158
+ * @param {Object} params - PR parameters
159
+ */
160
+ async createPR({ title, body, base = 'main', head, draft = false }) {
161
+ const tmpFile = join(tmpdir(), `gh-pr-${randomBytes(8).toString('hex')}.tmp`);
162
+
163
+ try {
164
+ await fs.writeFile(tmpFile, body, 'utf8');
165
+
166
+ const args = ['pr', 'create', '--title', title, '--body-file', tmpFile, '--base', base];
167
+
168
+ if (head) {
169
+ args.push('--head', head);
170
+ }
171
+
172
+ if (draft) {
173
+ args.push('--draft');
174
+ }
175
+
176
+ return await executeWithTimeout('gh', args, 30000);
177
+ } finally {
178
+ try {
179
+ await fs.unlink(tmpFile);
180
+ } catch (err) {
181
+ // Ignore cleanup errors
182
+ }
183
+ }
184
+ }
185
+ };
186
+
187
+ /**
188
+ * Example usage:
189
+ *
190
+ * import { gh } from './github-cli-safe.js';
191
+ *
192
+ * // Safe issue comment with special characters
193
+ * await gh.issueComment(123, 'Code: `npm install` and $(echo test)');
194
+ *
195
+ * // Safe PR creation with complex body
196
+ * await gh.createPR({
197
+ * title: 'Fix: Handle special characters',
198
+ * body: 'This fixes issues with `backticks` and .claude/agents/ paths',
199
+ * base: 'main'
200
+ * });
201
+ */