@ruvector/ruvllm 0.2.4 → 2.4.0

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.
Files changed (100) hide show
  1. package/README.md +413 -293
  2. package/bin/cli.js +619 -2
  3. package/dist/cjs/benchmarks/embedding-benchmark.d.ts +102 -0
  4. package/dist/cjs/benchmarks/embedding-benchmark.d.ts.map +1 -0
  5. package/dist/cjs/benchmarks/embedding-benchmark.js +436 -0
  6. package/dist/cjs/benchmarks/index.d.ts +34 -0
  7. package/dist/cjs/benchmarks/index.d.ts.map +1 -0
  8. package/dist/cjs/benchmarks/index.js +132 -0
  9. package/dist/cjs/benchmarks/model-comparison.d.ts +71 -0
  10. package/dist/cjs/benchmarks/model-comparison.d.ts.map +1 -0
  11. package/dist/cjs/benchmarks/model-comparison.js +476 -0
  12. package/dist/cjs/benchmarks/routing-benchmark.d.ts +70 -0
  13. package/dist/cjs/benchmarks/routing-benchmark.d.ts.map +1 -0
  14. package/dist/cjs/benchmarks/routing-benchmark.js +289 -0
  15. package/dist/cjs/contrastive.d.ts +229 -0
  16. package/dist/cjs/contrastive.d.ts.map +1 -0
  17. package/dist/cjs/contrastive.js +589 -0
  18. package/dist/cjs/engine.d.ts +3 -0
  19. package/dist/cjs/engine.d.ts.map +1 -1
  20. package/dist/cjs/engine.js +26 -6
  21. package/dist/cjs/index.d.ts +4 -0
  22. package/dist/cjs/index.d.ts.map +1 -1
  23. package/dist/cjs/index.js +9 -1
  24. package/dist/cjs/models.d.ts +129 -0
  25. package/dist/cjs/models.d.ts.map +1 -0
  26. package/dist/cjs/models.js +323 -0
  27. package/dist/cjs/native.d.ts +3 -3
  28. package/dist/cjs/native.d.ts.map +1 -1
  29. package/dist/cjs/native.js +1 -1
  30. package/dist/cjs/rlm/controller.d.ts +245 -0
  31. package/dist/cjs/rlm/controller.d.ts.map +1 -0
  32. package/dist/cjs/rlm/controller.js +554 -0
  33. package/dist/cjs/rlm/index.d.ts +62 -0
  34. package/dist/cjs/rlm/index.d.ts.map +1 -0
  35. package/dist/cjs/rlm/index.js +97 -0
  36. package/dist/cjs/rlm/training.d.ts +333 -0
  37. package/dist/cjs/rlm/training.d.ts.map +1 -0
  38. package/dist/cjs/rlm/training.js +680 -0
  39. package/dist/cjs/rlm/types.d.ts +189 -0
  40. package/dist/cjs/rlm/types.d.ts.map +1 -0
  41. package/dist/cjs/rlm/types.js +10 -0
  42. package/dist/esm/benchmarks/embedding-benchmark.d.ts +102 -0
  43. package/dist/esm/benchmarks/embedding-benchmark.d.ts.map +1 -0
  44. package/dist/esm/benchmarks/embedding-benchmark.js +427 -0
  45. package/dist/esm/benchmarks/index.d.ts +34 -0
  46. package/dist/esm/benchmarks/index.d.ts.map +1 -0
  47. package/dist/esm/benchmarks/index.js +110 -0
  48. package/dist/esm/benchmarks/model-comparison.d.ts +71 -0
  49. package/dist/esm/benchmarks/model-comparison.d.ts.map +1 -0
  50. package/dist/esm/benchmarks/model-comparison.js +434 -0
  51. package/dist/esm/benchmarks/routing-benchmark.d.ts +70 -0
  52. package/dist/esm/benchmarks/routing-benchmark.d.ts.map +1 -0
  53. package/dist/esm/benchmarks/routing-benchmark.js +283 -0
  54. package/dist/esm/contrastive.d.ts +229 -0
  55. package/dist/esm/contrastive.d.ts.map +1 -0
  56. package/dist/esm/contrastive.js +578 -0
  57. package/dist/esm/engine.d.ts +3 -0
  58. package/dist/esm/engine.d.ts.map +1 -1
  59. package/dist/esm/engine.js +26 -6
  60. package/dist/esm/index.d.ts +4 -0
  61. package/dist/esm/index.d.ts.map +1 -1
  62. package/dist/esm/index.js +9 -1
  63. package/dist/esm/models.d.ts +129 -0
  64. package/dist/esm/models.d.ts.map +1 -0
  65. package/dist/esm/models.js +282 -0
  66. package/dist/esm/native.d.ts +3 -3
  67. package/dist/esm/native.d.ts.map +1 -1
  68. package/dist/esm/native.js +1 -1
  69. package/dist/esm/rlm/controller.d.ts +245 -0
  70. package/dist/esm/rlm/controller.d.ts.map +1 -0
  71. package/dist/esm/rlm/controller.js +550 -0
  72. package/dist/esm/rlm/index.d.ts +62 -0
  73. package/dist/esm/rlm/index.d.ts.map +1 -0
  74. package/dist/esm/rlm/index.js +71 -0
  75. package/dist/esm/rlm/training.d.ts +333 -0
  76. package/dist/esm/rlm/training.d.ts.map +1 -0
  77. package/dist/esm/rlm/training.js +672 -0
  78. package/dist/esm/rlm/types.d.ts +189 -0
  79. package/dist/esm/rlm/types.d.ts.map +1 -0
  80. package/dist/esm/rlm/types.js +9 -0
  81. package/package.json +13 -8
  82. package/scripts/ensemble-model-compare.js +287 -0
  83. package/scripts/huggingface/README.md +537 -0
  84. package/scripts/huggingface/publish.sh +112 -0
  85. package/scripts/hybrid-model-compare.js +373 -0
  86. package/scripts/improved-model-compare.js +288 -0
  87. package/scripts/optimized-model-compare.js +364 -0
  88. package/scripts/real-model-compare.js +280 -0
  89. package/scripts/training/agentic-flow-capabilities.json +329 -0
  90. package/scripts/training/claude-code-synth.js +505 -0
  91. package/scripts/training/claude-flow-capabilities.json +1322 -0
  92. package/scripts/training/claude-hard-negatives.js +222 -0
  93. package/scripts/training/contrastive-finetune.js +517 -0
  94. package/scripts/training/ecosystem-triplets.jsonl +1467 -0
  95. package/scripts/training/generate-ecosystem-triplets.js +1324 -0
  96. package/scripts/training/rlm-dataset.js +671 -0
  97. package/scripts/training/routing-dataset.js +634 -0
  98. package/scripts/training/ruvector-capabilities.json +798 -0
  99. package/scripts/training/validate-ecosystem.js +381 -0
  100. package/scripts/training/validation-results.json +23 -0
@@ -0,0 +1,505 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Claude Code Synthetic Data Generator
4
+ *
5
+ * Uses @ruvector/agentic-synth to generate high-quality
6
+ * training data for RuvLTRA routing optimization.
7
+ *
8
+ * Features:
9
+ * - Claude Code-specific task patterns
10
+ * - Hard negative mining for contrastive learning
11
+ * - Quality scoring based on task clarity
12
+ * - DSPy-based prompt optimization
13
+ */
14
+
15
+ const { GoogleGenerativeAI } = require('@google/generative-ai');
16
+ const { writeFileSync, existsSync, mkdirSync, readFileSync } = require('fs');
17
+ const { join } = require('path');
18
+ const { homedir } = require('os');
19
+
20
+ // Configuration
21
+ const OUTPUT_DIR = join(__dirname, 'generated');
22
+ const EXAMPLES_PER_AGENT = 100; // Generate 100 examples per agent
23
+ const HARD_NEGATIVES_PER_AGENT = 20;
24
+
25
+ // Agent definitions with Claude Code context
26
+ const CLAUDE_CODE_AGENTS = {
27
+ coder: {
28
+ role: 'Software developer who implements features and writes production code',
29
+ claudeCodeContext: 'Uses Edit, Write, MultiEdit tools to create and modify code files',
30
+ keywords: ['implement', 'build', 'create', 'write code', 'add feature', 'component', 'function'],
31
+ examples: [
32
+ 'Implement a binary search function in TypeScript',
33
+ 'Build a React component for user authentication',
34
+ 'Create a REST API endpoint for data retrieval',
35
+ ],
36
+ },
37
+ researcher: {
38
+ role: 'Technical researcher who investigates and analyzes',
39
+ claudeCodeContext: 'Uses Grep, Glob, Read, WebSearch tools to gather information',
40
+ keywords: ['research', 'investigate', 'explore', 'analyze', 'find', 'discover', 'study'],
41
+ examples: [
42
+ 'Research best practices for React state management',
43
+ 'Investigate why the API is returning slow responses',
44
+ 'Explore different authentication strategies',
45
+ ],
46
+ },
47
+ reviewer: {
48
+ role: 'Code reviewer who evaluates code quality',
49
+ claudeCodeContext: 'Uses Read, Grep tools to analyze existing code for quality issues',
50
+ keywords: ['review', 'check', 'evaluate', 'assess', 'inspect', 'pull request', 'PR'],
51
+ examples: [
52
+ 'Review the pull request for code quality',
53
+ 'Check the implementation for potential issues',
54
+ 'Evaluate the API design decisions',
55
+ ],
56
+ },
57
+ tester: {
58
+ role: 'QA engineer who writes and runs tests',
59
+ claudeCodeContext: 'Uses Write, Edit tools to create test files and Bash to run tests',
60
+ keywords: ['test', 'tests', 'testing', 'unit test', 'integration test', 'e2e', 'coverage', 'spec'],
61
+ examples: [
62
+ 'Write unit tests for the authentication module',
63
+ 'Add integration tests for the API endpoints',
64
+ 'Create e2e tests for the checkout flow',
65
+ ],
66
+ },
67
+ architect: {
68
+ role: 'System architect who designs software structure',
69
+ claudeCodeContext: 'Uses Read, Grep tools to understand codebase and Write to document designs',
70
+ keywords: ['design', 'architecture', 'schema', 'structure', 'system', 'diagram', 'plan'],
71
+ examples: [
72
+ 'Design the database schema for user profiles',
73
+ 'Plan the microservices architecture',
74
+ 'Create the system architecture diagram',
75
+ ],
76
+ },
77
+ 'security-architect': {
78
+ role: 'Security specialist who audits vulnerabilities',
79
+ claudeCodeContext: 'Uses Grep, Read tools to scan code for security issues',
80
+ keywords: ['security', 'vulnerability', 'xss', 'injection', 'audit', 'cve', 'exploit'],
81
+ examples: [
82
+ 'Audit the API endpoints for XSS vulnerabilities',
83
+ 'Check for SQL injection vulnerabilities',
84
+ 'Review authentication for security issues',
85
+ ],
86
+ },
87
+ debugger: {
88
+ role: 'Bug hunter who fixes errors and traces issues',
89
+ claudeCodeContext: 'Uses Read, Grep, Bash tools to trace issues and Edit to fix bugs',
90
+ keywords: ['debug', 'fix', 'bug', 'error', 'exception', 'crash', 'trace', 'issue'],
91
+ examples: [
92
+ 'Fix the null pointer exception in login',
93
+ 'Debug the memory leak in WebSocket handler',
94
+ 'Trace the source of the intermittent error',
95
+ ],
96
+ },
97
+ documenter: {
98
+ role: 'Technical writer who creates documentation',
99
+ claudeCodeContext: 'Uses Write, Edit tools to create and update documentation files',
100
+ keywords: ['document', 'jsdoc', 'readme', 'comment', 'explain', 'describe'],
101
+ examples: [
102
+ 'Write JSDoc comments for utility functions',
103
+ 'Create README for the new package',
104
+ 'Document the API endpoints',
105
+ ],
106
+ },
107
+ refactorer: {
108
+ role: 'Code modernizer who restructures without changing behavior',
109
+ claudeCodeContext: 'Uses Edit, MultiEdit tools to restructure code across files',
110
+ keywords: ['refactor', 'restructure', 'modernize', 'extract', 'consolidate', 'simplify'],
111
+ examples: [
112
+ 'Refactor the payment module to async/await',
113
+ 'Restructure the utils folder',
114
+ 'Extract common logic into shared module',
115
+ ],
116
+ },
117
+ optimizer: {
118
+ role: 'Performance engineer who speeds up slow code',
119
+ claudeCodeContext: 'Uses Bash to run profilers and Edit to optimize code',
120
+ keywords: ['optimize', 'performance', 'speed', 'cache', 'latency', 'slow', 'fast'],
121
+ examples: [
122
+ 'Optimize the database queries for dashboard',
123
+ 'Cache the frequently accessed user data',
124
+ 'Improve the API response time',
125
+ ],
126
+ },
127
+ devops: {
128
+ role: 'DevOps engineer who manages deployment and infrastructure',
129
+ claudeCodeContext: 'Uses Bash for deployment commands and Write for config files',
130
+ keywords: ['deploy', 'ci/cd', 'kubernetes', 'docker', 'pipeline', 'infrastructure'],
131
+ examples: [
132
+ 'Set up the CI/CD pipeline',
133
+ 'Configure Kubernetes deployment',
134
+ 'Deploy to production',
135
+ ],
136
+ },
137
+ 'api-docs': {
138
+ role: 'API documentation specialist who creates specs',
139
+ claudeCodeContext: 'Uses Write to generate OpenAPI/Swagger specs',
140
+ keywords: ['openapi', 'swagger', 'api spec', 'endpoint', 'rest api', 'graphql'],
141
+ examples: [
142
+ 'Generate OpenAPI documentation for REST API',
143
+ 'Create Swagger spec for the endpoints',
144
+ 'Document the API request/response formats',
145
+ ],
146
+ },
147
+ planner: {
148
+ role: 'Project planner who organizes and schedules work',
149
+ claudeCodeContext: 'Uses TodoWrite tool to create and manage task lists',
150
+ keywords: ['plan', 'sprint', 'roadmap', 'milestone', 'estimate', 'schedule', 'prioritize'],
151
+ examples: [
152
+ 'Create a sprint plan for next two weeks',
153
+ 'Estimate the feature implementation effort',
154
+ 'Plan the roadmap for Q3',
155
+ ],
156
+ },
157
+ };
158
+
159
+ // Prompt template for synthetic data generation
160
+ const GENERATION_PROMPT = `You are generating training data for an AI agent routing system used in Claude Code (an AI coding assistant).
161
+
162
+ ## Task
163
+ Generate ${EXAMPLES_PER_AGENT} diverse, realistic task descriptions that would be routed to the "${'{AGENT}'}" agent.
164
+
165
+ ## Agent Description
166
+ Role: {ROLE}
167
+ Claude Code Context: {CONTEXT}
168
+ Key Indicators: {KEYWORDS}
169
+
170
+ ## Requirements
171
+ 1. Each task should be a realistic software engineering task
172
+ 2. Tasks should clearly indicate the agent type through action verbs and context
173
+ 3. Include variety in:
174
+ - Programming languages (TypeScript, Python, Rust, Go, etc.)
175
+ - Frameworks (React, Vue, Express, Django, etc.)
176
+ - Domains (web, mobile, backend, data, ML, etc.)
177
+ - Complexity levels (simple to complex)
178
+ 4. Tasks should be 5-20 words, clear and actionable
179
+ 5. Include edge cases that might be confused with other agents
180
+
181
+ ## Examples for this agent
182
+ {EXAMPLES}
183
+
184
+ ## Output Format
185
+ Return a JSON array of objects with this structure:
186
+ [
187
+ {
188
+ "task": "The task description",
189
+ "quality": 0.8-1.0,
190
+ "difficulty": "easy|medium|hard",
191
+ "tags": ["relevant", "tags"]
192
+ }
193
+ ]
194
+
195
+ Generate exactly ${EXAMPLES_PER_AGENT} unique tasks. Be creative and diverse.`;
196
+
197
+ // Prompt for hard negatives
198
+ const HARD_NEGATIVE_PROMPT = `You are generating hard negative examples for contrastive learning in an AI agent routing system.
199
+
200
+ ## Context
201
+ We have an agent called "${'{AGENT}'}" with this role: {ROLE}
202
+
203
+ We need tasks that SEEM like they might belong to this agent but actually belong to OTHER agents.
204
+ These are "hard negatives" - confusing examples that help the model learn better boundaries.
205
+
206
+ ## Confusable Agents
207
+ {CONFUSABLE_AGENTS}
208
+
209
+ ## Requirements
210
+ 1. Generate ${HARD_NEGATIVES_PER_AGENT} tasks that might be confused with "${'{AGENT}'}"
211
+ 2. Each task should actually belong to a DIFFERENT agent
212
+ 3. The confusion should be subtle but clear upon reflection
213
+ 4. Include the correct agent label
214
+
215
+ ## Output Format
216
+ [
217
+ {
218
+ "task": "The confusing task description",
219
+ "appears_to_be": "${'{AGENT}'}",
220
+ "actually_is": "the_correct_agent",
221
+ "confusion_reason": "Why this might be confused"
222
+ }
223
+ ]`;
224
+
225
+ /**
226
+ * Initialize Gemini client
227
+ */
228
+ function getGeminiClient() {
229
+ const apiKey = process.env.GEMINI_API_KEY;
230
+ if (!apiKey) {
231
+ console.error('GEMINI_API_KEY environment variable required');
232
+ console.error('Set it with: export GEMINI_API_KEY=your_key');
233
+ process.exit(1);
234
+ }
235
+ return new GoogleGenerativeAI(apiKey);
236
+ }
237
+
238
+ /**
239
+ * Generate training data for an agent using Gemini
240
+ */
241
+ async function generateAgentData(client, agent, agentConfig) {
242
+ console.log(` Generating data for ${agent}...`);
243
+
244
+ const prompt = GENERATION_PROMPT
245
+ .replace(/\{AGENT\}/g, agent)
246
+ .replace('{ROLE}', agentConfig.role)
247
+ .replace('{CONTEXT}', agentConfig.claudeCodeContext)
248
+ .replace('{KEYWORDS}', agentConfig.keywords.join(', '))
249
+ .replace('{EXAMPLES}', agentConfig.examples.map(e => `- ${e}`).join('\n'));
250
+
251
+ try {
252
+ const model = client.getGenerativeModel({ model: 'gemini-2.0-flash-exp' });
253
+ const result = await model.generateContent(prompt);
254
+ const response = result.response.text();
255
+
256
+ // Extract JSON from response
257
+ const jsonMatch = response.match(/\[[\s\S]*\]/);
258
+ if (!jsonMatch) {
259
+ console.error(` Failed to parse JSON for ${agent}`);
260
+ return [];
261
+ }
262
+
263
+ const data = JSON.parse(jsonMatch[0]);
264
+ console.log(` Generated ${data.length} examples for ${agent}`);
265
+
266
+ return data.map(item => ({
267
+ ...item,
268
+ agent,
269
+ type: 'positive',
270
+ }));
271
+ } catch (error) {
272
+ console.error(` Error generating data for ${agent}: ${error.message}`);
273
+ return [];
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Generate hard negatives for an agent
279
+ */
280
+ async function generateHardNegatives(client, agent, agentConfig, allAgents) {
281
+ console.log(` Generating hard negatives for ${agent}...`);
282
+
283
+ // Find confusable agents
284
+ const confusableAgents = Object.entries(allAgents)
285
+ .filter(([name]) => name !== agent)
286
+ .map(([name, config]) => `- ${name}: ${config.role}`)
287
+ .join('\n');
288
+
289
+ const prompt = HARD_NEGATIVE_PROMPT
290
+ .replace(/\{AGENT\}/g, agent)
291
+ .replace('{ROLE}', agentConfig.role)
292
+ .replace('{CONFUSABLE_AGENTS}', confusableAgents);
293
+
294
+ try {
295
+ const model = client.getGenerativeModel({ model: 'gemini-2.0-flash-exp' });
296
+ const result = await model.generateContent(prompt);
297
+ const response = result.response.text();
298
+
299
+ const jsonMatch = response.match(/\[[\s\S]*\]/);
300
+ if (!jsonMatch) {
301
+ console.error(` Failed to parse hard negatives for ${agent}`);
302
+ return [];
303
+ }
304
+
305
+ const data = JSON.parse(jsonMatch[0]);
306
+ console.log(` Generated ${data.length} hard negatives for ${agent}`);
307
+
308
+ return data.map(item => ({
309
+ task: item.task,
310
+ agent: item.actually_is,
311
+ confusing_with: agent,
312
+ confusion_reason: item.confusion_reason,
313
+ type: 'hard_negative',
314
+ quality: 1.0,
315
+ }));
316
+ } catch (error) {
317
+ console.error(` Error generating hard negatives for ${agent}: ${error.message}`);
318
+ return [];
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Main generation pipeline
324
+ */
325
+ async function main() {
326
+ console.log('\n╔═══════════════════════════════════════════════════════════════════════════════════╗');
327
+ console.log('║ CLAUDE CODE SYNTHETIC TRAINING DATA GENERATOR ║');
328
+ console.log('║ Using @ruvector/agentic-synth ║');
329
+ console.log('╚═══════════════════════════════════════════════════════════════════════════════════╝\n');
330
+
331
+ // Check for API key
332
+ if (!process.env.GEMINI_API_KEY) {
333
+ console.log('GEMINI_API_KEY not set. Generating static dataset from templates...\n');
334
+ generateStaticDataset();
335
+ return;
336
+ }
337
+
338
+ const client = getGeminiClient();
339
+
340
+ // Create output directory
341
+ if (!existsSync(OUTPUT_DIR)) {
342
+ mkdirSync(OUTPUT_DIR, { recursive: true });
343
+ }
344
+
345
+ const allData = [];
346
+ const allHardNegatives = [];
347
+ const agents = Object.keys(CLAUDE_CODE_AGENTS);
348
+
349
+ console.log('─────────────────────────────────────────────────────────────────');
350
+ console.log(' GENERATING POSITIVE EXAMPLES');
351
+ console.log('─────────────────────────────────────────────────────────────────\n');
352
+
353
+ // Generate positive examples for each agent
354
+ for (const agent of agents) {
355
+ const data = await generateAgentData(client, agent, CLAUDE_CODE_AGENTS[agent]);
356
+ allData.push(...data);
357
+
358
+ // Rate limit
359
+ await new Promise(resolve => setTimeout(resolve, 1000));
360
+ }
361
+
362
+ console.log('\n─────────────────────────────────────────────────────────────────');
363
+ console.log(' GENERATING HARD NEGATIVES');
364
+ console.log('─────────────────────────────────────────────────────────────────\n');
365
+
366
+ // Generate hard negatives
367
+ for (const agent of agents) {
368
+ const negatives = await generateHardNegatives(client, agent, CLAUDE_CODE_AGENTS[agent], CLAUDE_CODE_AGENTS);
369
+ allHardNegatives.push(...negatives);
370
+
371
+ // Rate limit
372
+ await new Promise(resolve => setTimeout(resolve, 1000));
373
+ }
374
+
375
+ // Combine and save
376
+ const fullDataset = [...allData, ...allHardNegatives];
377
+
378
+ // Save full dataset
379
+ const outputPath = join(OUTPUT_DIR, 'claude-code-routing-dataset.json');
380
+ writeFileSync(outputPath, JSON.stringify(fullDataset, null, 2));
381
+
382
+ // Save training pairs (for contrastive learning)
383
+ const contrastivePairs = generateContrastivePairs(allData, allHardNegatives);
384
+ const pairsPath = join(OUTPUT_DIR, 'contrastive-pairs.json');
385
+ writeFileSync(pairsPath, JSON.stringify(contrastivePairs, null, 2));
386
+
387
+ // Print summary
388
+ console.log('\n═══════════════════════════════════════════════════════════════════════════════════');
389
+ console.log(' GENERATION COMPLETE');
390
+ console.log('═══════════════════════════════════════════════════════════════════════════════════\n');
391
+
392
+ console.log(` Positive examples: ${allData.length}`);
393
+ console.log(` Hard negatives: ${allHardNegatives.length}`);
394
+ console.log(` Contrastive pairs: ${contrastivePairs.length}`);
395
+ console.log(` Total dataset size: ${fullDataset.length}`);
396
+ console.log(`\n Output files:`);
397
+ console.log(` ${outputPath}`);
398
+ console.log(` ${pairsPath}`);
399
+ console.log('');
400
+ }
401
+
402
+ /**
403
+ * Generate contrastive pairs from data
404
+ */
405
+ function generateContrastivePairs(positives, negatives) {
406
+ const pairs = [];
407
+
408
+ // Group positives by agent
409
+ const byAgent = {};
410
+ for (const item of positives) {
411
+ if (!byAgent[item.agent]) byAgent[item.agent] = [];
412
+ byAgent[item.agent].push(item);
413
+ }
414
+
415
+ // Create positive pairs (same agent)
416
+ for (const [agent, items] of Object.entries(byAgent)) {
417
+ for (let i = 0; i < items.length - 1; i++) {
418
+ for (let j = i + 1; j < Math.min(i + 3, items.length); j++) {
419
+ pairs.push({
420
+ anchor: items[i].task,
421
+ positive: items[j].task,
422
+ agent,
423
+ type: 'positive_pair',
424
+ });
425
+ }
426
+ }
427
+ }
428
+
429
+ // Create negative pairs (different agents)
430
+ const agents = Object.keys(byAgent);
431
+ for (let i = 0; i < agents.length; i++) {
432
+ for (let j = i + 1; j < agents.length; j++) {
433
+ const agent1Items = byAgent[agents[i]];
434
+ const agent2Items = byAgent[agents[j]];
435
+
436
+ if (agent1Items && agent1Items[0] && agent2Items && agent2Items[0]) {
437
+ pairs.push({
438
+ anchor: agent1Items[0].task,
439
+ negative: agent2Items[0].task,
440
+ anchor_agent: agents[i],
441
+ negative_agent: agents[j],
442
+ type: 'negative_pair',
443
+ });
444
+ }
445
+ }
446
+ }
447
+
448
+ // Add hard negative pairs
449
+ for (const neg of negatives) {
450
+ const confusingAgent = byAgent[neg.confusing_with];
451
+ if (confusingAgent && confusingAgent[0]) {
452
+ pairs.push({
453
+ anchor: confusingAgent[0].task,
454
+ negative: neg.task,
455
+ anchor_agent: neg.confusing_with,
456
+ negative_agent: neg.agent,
457
+ type: 'hard_negative_pair',
458
+ confusion_reason: neg.confusion_reason,
459
+ });
460
+ }
461
+ }
462
+
463
+ return pairs;
464
+ }
465
+
466
+ /**
467
+ * Generate static dataset without API (fallback)
468
+ */
469
+ function generateStaticDataset() {
470
+ console.log('Generating static dataset from routing-dataset.js...\n');
471
+
472
+ // Import the static dataset
473
+ const { generateTrainingDataset, generateContrastivePairs, getDatasetStats } = require('./routing-dataset.js');
474
+
475
+ const dataset = generateTrainingDataset();
476
+ const pairs = generateContrastivePairs();
477
+ const stats = getDatasetStats();
478
+
479
+ // Create output directory
480
+ if (!existsSync(OUTPUT_DIR)) {
481
+ mkdirSync(OUTPUT_DIR, { recursive: true });
482
+ }
483
+
484
+ // Save dataset
485
+ const datasetPath = join(OUTPUT_DIR, 'claude-code-routing-dataset.json');
486
+ writeFileSync(datasetPath, JSON.stringify(dataset, null, 2));
487
+
488
+ const pairsPath = join(OUTPUT_DIR, 'contrastive-pairs.json');
489
+ writeFileSync(pairsPath, JSON.stringify(pairs, null, 2));
490
+
491
+ console.log('═══════════════════════════════════════════════════════════════');
492
+ console.log(' STATIC DATASET GENERATED');
493
+ console.log('═══════════════════════════════════════════════════════════════\n');
494
+
495
+ console.log(` Total examples: ${stats.totalExamples}`);
496
+ console.log(` Contrastive pairs: ${stats.contrastivePairs}`);
497
+ console.log(` Agent types: ${stats.agents.length}`);
498
+ console.log(`\n Output files:`);
499
+ console.log(` ${datasetPath}`);
500
+ console.log(` ${pairsPath}`);
501
+ console.log('\n To generate more data with AI, set GEMINI_API_KEY');
502
+ console.log('');
503
+ }
504
+
505
+ main().catch(console.error);