@covibes/zeroshot 1.0.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/LICENSE +21 -0
  3. package/README.md +364 -0
  4. package/cli/index.js +3990 -0
  5. package/cluster-templates/base-templates/debug-workflow.json +181 -0
  6. package/cluster-templates/base-templates/full-workflow.json +455 -0
  7. package/cluster-templates/base-templates/single-worker.json +48 -0
  8. package/cluster-templates/base-templates/worker-validator.json +131 -0
  9. package/cluster-templates/conductor-bootstrap.json +122 -0
  10. package/cluster-templates/conductor-junior-bootstrap.json +69 -0
  11. package/docker/zeroshot-cluster/Dockerfile +132 -0
  12. package/lib/completion.js +174 -0
  13. package/lib/id-detector.js +53 -0
  14. package/lib/settings.js +97 -0
  15. package/lib/stream-json-parser.js +236 -0
  16. package/package.json +121 -0
  17. package/src/agent/agent-config.js +121 -0
  18. package/src/agent/agent-context-builder.js +241 -0
  19. package/src/agent/agent-hook-executor.js +329 -0
  20. package/src/agent/agent-lifecycle.js +555 -0
  21. package/src/agent/agent-stuck-detector.js +256 -0
  22. package/src/agent/agent-task-executor.js +1034 -0
  23. package/src/agent/agent-trigger-evaluator.js +67 -0
  24. package/src/agent-wrapper.js +459 -0
  25. package/src/agents/git-pusher-agent.json +20 -0
  26. package/src/attach/attach-client.js +438 -0
  27. package/src/attach/attach-server.js +543 -0
  28. package/src/attach/index.js +35 -0
  29. package/src/attach/protocol.js +220 -0
  30. package/src/attach/ring-buffer.js +121 -0
  31. package/src/attach/socket-discovery.js +242 -0
  32. package/src/claude-task-runner.js +468 -0
  33. package/src/config-router.js +80 -0
  34. package/src/config-validator.js +598 -0
  35. package/src/github.js +103 -0
  36. package/src/isolation-manager.js +1042 -0
  37. package/src/ledger.js +429 -0
  38. package/src/logic-engine.js +223 -0
  39. package/src/message-bus-bridge.js +139 -0
  40. package/src/message-bus.js +202 -0
  41. package/src/name-generator.js +232 -0
  42. package/src/orchestrator.js +1938 -0
  43. package/src/schemas/sub-cluster.js +156 -0
  44. package/src/sub-cluster-wrapper.js +545 -0
  45. package/src/task-runner.js +28 -0
  46. package/src/template-resolver.js +347 -0
  47. package/src/tui/CHANGES.txt +133 -0
  48. package/src/tui/LAYOUT.md +261 -0
  49. package/src/tui/README.txt +192 -0
  50. package/src/tui/TWO-LEVEL-NAVIGATION.md +186 -0
  51. package/src/tui/data-poller.js +325 -0
  52. package/src/tui/demo.js +208 -0
  53. package/src/tui/formatters.js +123 -0
  54. package/src/tui/index.js +193 -0
  55. package/src/tui/keybindings.js +383 -0
  56. package/src/tui/layout.js +317 -0
  57. package/src/tui/renderer.js +194 -0
@@ -0,0 +1,598 @@
1
+ /**
2
+ * Config Validator - Static analysis for zeroshot cluster configurations
3
+ *
4
+ * Catches logical failures that would cause clusters to:
5
+ * - Never start (no bootstrap trigger)
6
+ * - Never complete (no path to completion)
7
+ * - Loop infinitely (circular dependencies)
8
+ * - Deadlock (impossible consensus)
9
+ * - Waste compute (orchestrator executing tasks)
10
+ *
11
+ * Run at config load time to fail fast before spawning agents.
12
+ */
13
+
14
+ /**
15
+ * Check if config is a conductor-bootstrap style config
16
+ * Conductor configs dynamically spawn agents via CLUSTER_OPERATIONS
17
+ * @param {Object} config - Cluster configuration
18
+ * @returns {boolean}
19
+ */
20
+ function isConductorConfig(config) {
21
+ return config.agents?.some(
22
+ (a) =>
23
+ a.role === 'conductor' &&
24
+ // Old style: static topic in config
25
+ (a.hooks?.onComplete?.config?.topic === 'CLUSTER_OPERATIONS' ||
26
+ // New style: topic set in transform script (check for CLUSTER_OPERATIONS in script)
27
+ a.hooks?.onComplete?.transform?.script?.includes('CLUSTER_OPERATIONS'))
28
+ );
29
+ }
30
+
31
+ /**
32
+ * Validate a cluster configuration for structural correctness
33
+ * @param {Object} config - Cluster configuration
34
+ * @param {Number} depth - Current nesting depth (for subcluster validation)
35
+ * @returns {{ valid: boolean, errors: string[], warnings: string[] }}
36
+ */
37
+ function validateConfig(config, depth = 0) {
38
+ const errors = [];
39
+ const warnings = [];
40
+
41
+ // Max nesting depth check
42
+ const MAX_DEPTH = 5;
43
+ if (depth > MAX_DEPTH) {
44
+ errors.push(`Cluster nesting exceeds max depth (${MAX_DEPTH})`);
45
+ return { valid: false, errors, warnings };
46
+ }
47
+
48
+ // === PHASE 1: Basic structure validation ===
49
+ const basicResult = validateBasicStructure(config, depth);
50
+ errors.push(...basicResult.errors);
51
+ warnings.push(...basicResult.warnings);
52
+ if (basicResult.errors.length > 0) {
53
+ // Can't proceed with flow analysis if basic structure is broken
54
+ return { valid: false, errors, warnings };
55
+ }
56
+
57
+ // Conductor configs dynamically spawn agents - skip message flow analysis
58
+ // The orchestrator validates the spawned config at CLUSTER_OPERATIONS execution time
59
+ const conductorMode = isConductorConfig(config);
60
+
61
+ // === PHASE 2: Message flow analysis (skip for conductor configs) ===
62
+ if (!conductorMode) {
63
+ const flowResult = analyzeMessageFlow(config);
64
+ errors.push(...flowResult.errors);
65
+ warnings.push(...flowResult.warnings);
66
+ }
67
+
68
+ // === PHASE 3: Agent-specific validation ===
69
+ const agentResult = validateAgents(config);
70
+ errors.push(...agentResult.errors);
71
+ warnings.push(...agentResult.warnings);
72
+
73
+ // === PHASE 4: Logic script validation ===
74
+ const logicResult = validateLogicScripts(config);
75
+ errors.push(...logicResult.errors);
76
+ warnings.push(...logicResult.warnings);
77
+
78
+ return {
79
+ valid: errors.length === 0,
80
+ errors,
81
+ warnings,
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Phase 1: Validate basic structure (fields, types, duplicates)
87
+ */
88
+ function validateBasicStructure(config, depth = 0) {
89
+ const errors = [];
90
+ const warnings = [];
91
+
92
+ if (!config.agents || !Array.isArray(config.agents)) {
93
+ errors.push('agents array is required');
94
+ return { errors, warnings };
95
+ }
96
+
97
+ if (config.agents.length === 0) {
98
+ errors.push('agents array cannot be empty');
99
+ return { errors, warnings };
100
+ }
101
+
102
+ const seenIds = new Set();
103
+
104
+ for (let i = 0; i < config.agents.length; i++) {
105
+ const agent = config.agents[i];
106
+ const prefix = `agents[${i}]`;
107
+
108
+ // Check if this is a subcluster
109
+ const isSubCluster = agent.type === 'subcluster';
110
+
111
+ // Required fields
112
+ if (!agent.id) {
113
+ errors.push(`${prefix}.id is required`);
114
+ } else if (typeof agent.id !== 'string') {
115
+ errors.push(`${prefix}.id must be a string`);
116
+ } else if (seenIds.has(agent.id)) {
117
+ errors.push(`Duplicate agent id: "${agent.id}"`);
118
+ } else {
119
+ seenIds.add(agent.id);
120
+ }
121
+
122
+ if (!agent.role) {
123
+ errors.push(`${prefix}.role is required`);
124
+ }
125
+
126
+ // Validate subclusters
127
+ if (isSubCluster) {
128
+ const subClusterSchema = require('./schemas/sub-cluster');
129
+ const subResult = subClusterSchema.validateSubCluster(agent, depth);
130
+ errors.push(...subResult.errors);
131
+ warnings.push(...subResult.warnings);
132
+ continue; // Skip regular agent validation
133
+ }
134
+
135
+ // Regular agent validation
136
+ if (!agent.triggers || !Array.isArray(agent.triggers)) {
137
+ errors.push(`${prefix}.triggers array is required`);
138
+ } else if (agent.triggers.length === 0) {
139
+ errors.push(`${prefix}.triggers cannot be empty (agent would never activate)`);
140
+ }
141
+
142
+ // Validate triggers structure
143
+ if (agent.triggers) {
144
+ for (let j = 0; j < agent.triggers.length; j++) {
145
+ const trigger = agent.triggers[j];
146
+ const triggerPrefix = `${prefix}.triggers[${j}]`;
147
+
148
+ if (!trigger.topic) {
149
+ errors.push(`${triggerPrefix}.topic is required`);
150
+ }
151
+
152
+ if (trigger.action && !['execute_task', 'stop_cluster'].includes(trigger.action)) {
153
+ errors.push(
154
+ `${triggerPrefix}.action must be 'execute_task' or 'stop_cluster', got '${trigger.action}'`
155
+ );
156
+ }
157
+
158
+ if (trigger.logic) {
159
+ if (!trigger.logic.script) {
160
+ errors.push(`${triggerPrefix}.logic.script is required when logic is specified`);
161
+ }
162
+ if (trigger.logic.engine && trigger.logic.engine !== 'javascript') {
163
+ errors.push(
164
+ `${triggerPrefix}.logic.engine must be 'javascript', got '${trigger.logic.engine}'`
165
+ );
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ // Validate model rules if present
172
+ if (agent.modelRules) {
173
+ if (!Array.isArray(agent.modelRules)) {
174
+ errors.push(`${prefix}.modelRules must be an array`);
175
+ } else {
176
+ for (let j = 0; j < agent.modelRules.length; j++) {
177
+ const rule = agent.modelRules[j];
178
+ const rulePrefix = `${prefix}.modelRules[${j}]`;
179
+
180
+ if (!rule.iterations) {
181
+ errors.push(`${rulePrefix}.iterations is required`);
182
+ } else if (!isValidIterationPattern(rule.iterations)) {
183
+ errors.push(
184
+ `${rulePrefix}.iterations '${rule.iterations}' is invalid. Valid: "1", "1-3", "5+", "all"`
185
+ );
186
+ }
187
+
188
+ if (!rule.model) {
189
+ errors.push(`${rulePrefix}.model is required`);
190
+ } else if (!['opus', 'sonnet', 'haiku'].includes(rule.model)) {
191
+ errors.push(
192
+ `${rulePrefix}.model must be 'opus', 'sonnet', or 'haiku', got '${rule.model}'`
193
+ );
194
+ }
195
+ }
196
+
197
+ // Check for coverage gap (no catch-all rule)
198
+ const hasCatchAll = agent.modelRules.some(
199
+ (r) => r.iterations === 'all' || /^\d+\+$/.test(r.iterations)
200
+ );
201
+ if (!hasCatchAll) {
202
+ errors.push(
203
+ `${prefix}.modelRules has no catch-all rule (e.g., "all" or "5+"). High iterations will fail.`
204
+ );
205
+ }
206
+ }
207
+ }
208
+ }
209
+
210
+ return { errors, warnings };
211
+ }
212
+
213
+ /**
214
+ * Phase 2: Analyze message flow for structural problems
215
+ */
216
+ function analyzeMessageFlow(config) {
217
+ const errors = [];
218
+ const warnings = [];
219
+
220
+ // Build topic graph
221
+ const topicProducers = new Map(); // topic -> [agentIds that produce it]
222
+ const topicConsumers = new Map(); // topic -> [agentIds that consume it]
223
+ const agentOutputTopics = new Map(); // agentId -> [topics it produces]
224
+ const agentInputTopics = new Map(); // agentId -> [topics it consumes]
225
+
226
+ // System always produces ISSUE_OPENED
227
+ topicProducers.set('ISSUE_OPENED', ['system']);
228
+
229
+ for (const agent of config.agents) {
230
+ agentInputTopics.set(agent.id, []);
231
+ agentOutputTopics.set(agent.id, []);
232
+
233
+ // Track what topics this agent consumes (triggers)
234
+ for (const trigger of agent.triggers || []) {
235
+ const topic = trigger.topic;
236
+ if (!topicConsumers.has(topic)) {
237
+ topicConsumers.set(topic, []);
238
+ }
239
+ topicConsumers.get(topic).push(agent.id);
240
+ agentInputTopics.get(agent.id).push(topic);
241
+ }
242
+
243
+ // Track what topics this agent produces (hooks)
244
+ const outputTopic = agent.hooks?.onComplete?.config?.topic;
245
+ if (outputTopic) {
246
+ if (!topicProducers.has(outputTopic)) {
247
+ topicProducers.set(outputTopic, []);
248
+ }
249
+ topicProducers.get(outputTopic).push(agent.id);
250
+ agentOutputTopics.get(agent.id).push(outputTopic);
251
+ }
252
+ }
253
+
254
+ // === CHECK 1: No bootstrap trigger ===
255
+ const issueOpenedConsumers = topicConsumers.get('ISSUE_OPENED') || [];
256
+ if (issueOpenedConsumers.length === 0) {
257
+ errors.push(
258
+ 'No agent triggers on ISSUE_OPENED. Cluster will never start. ' +
259
+ 'Add a trigger: { "topic": "ISSUE_OPENED", "action": "execute_task" }'
260
+ );
261
+ }
262
+
263
+ // === CHECK 2: No completion handler ===
264
+ const completionHandlers = config.agents.filter(
265
+ (a) =>
266
+ a.triggers?.some((t) => t.action === 'stop_cluster') ||
267
+ a.id === 'completion-detector' ||
268
+ a.id === 'git-pusher' ||
269
+ a.hooks?.onComplete?.config?.topic === 'CLUSTER_COMPLETE'
270
+ );
271
+
272
+ if (completionHandlers.length === 0) {
273
+ errors.push(
274
+ 'No completion handler found. Cluster will run until idle timeout (2 min). ' +
275
+ 'Add an agent with trigger action: "stop_cluster"'
276
+ );
277
+ } else if (completionHandlers.length > 1) {
278
+ errors.push(
279
+ `Multiple completion handlers: [${completionHandlers.map((a) => a.id).join(', ')}]. ` +
280
+ 'This causes race conditions. Keep only one.'
281
+ );
282
+ }
283
+
284
+ // === CHECK 3: Orphan topics (produced but never consumed) ===
285
+ for (const [topic, producers] of topicProducers) {
286
+ if (topic === 'CLUSTER_COMPLETE') continue; // System handles this
287
+ const consumers = topicConsumers.get(topic) || [];
288
+ if (consumers.length === 0) {
289
+ warnings.push(
290
+ `Topic '${topic}' is produced by [${producers.join(', ')}] but never consumed. Dead end.`
291
+ );
292
+ }
293
+ }
294
+
295
+ // === CHECK 4: Waiting for topics that are never produced ===
296
+ for (const [topic, consumers] of topicConsumers) {
297
+ if (topic === 'ISSUE_OPENED' || topic === 'CLUSTER_RESUMED') continue; // System produces
298
+ if (topic.endsWith('*')) continue; // Wildcard pattern
299
+ const producers = topicProducers.get(topic) || [];
300
+ if (producers.length === 0) {
301
+ errors.push(
302
+ `Topic '${topic}' consumed by [${consumers.join(', ')}] but never produced. ` +
303
+ 'These agents will never trigger.'
304
+ );
305
+ }
306
+ }
307
+
308
+ // === CHECK 5: Self-triggering agents (instant infinite loop) ===
309
+ for (const agent of config.agents) {
310
+ const inputs = agentInputTopics.get(agent.id) || [];
311
+ const outputs = agentOutputTopics.get(agent.id) || [];
312
+ const selfTrigger = inputs.find((t) => outputs.includes(t));
313
+ if (selfTrigger) {
314
+ errors.push(
315
+ `Agent '${agent.id}' triggers on '${selfTrigger}' and produces '${selfTrigger}'. ` +
316
+ 'Instant infinite loop.'
317
+ );
318
+ }
319
+ }
320
+
321
+ // === CHECK 6: Two-agent circular dependency ===
322
+ for (const agentA of config.agents) {
323
+ const outputsA = agentOutputTopics.get(agentA.id) || [];
324
+ for (const agentB of config.agents) {
325
+ if (agentA.id === agentB.id) continue;
326
+ const inputsB = agentInputTopics.get(agentB.id) || [];
327
+ const outputsB = agentOutputTopics.get(agentB.id) || [];
328
+ const inputsA = agentInputTopics.get(agentA.id) || [];
329
+
330
+ // A produces what B consumes, AND B produces what A consumes
331
+ const aToB = outputsA.some((t) => inputsB.includes(t));
332
+ const bToA = outputsB.some((t) => inputsA.includes(t));
333
+
334
+ if (aToB && bToA) {
335
+ // This might be intentional (rejection loop), check if there's an escape
336
+ const hasEscapeLogic =
337
+ agentA.triggers?.some((t) => t.logic) || agentB.triggers?.some((t) => t.logic);
338
+ if (!hasEscapeLogic) {
339
+ warnings.push(
340
+ `Circular dependency: '${agentA.id}' ↔ '${agentB.id}'. ` +
341
+ 'Add logic conditions to prevent infinite loop, or ensure maxIterations is set.'
342
+ );
343
+ }
344
+ }
345
+ }
346
+ }
347
+
348
+ // === CHECK 7: Validator without worker re-trigger ===
349
+ const validators = config.agents.filter((a) => a.role === 'validator');
350
+ const workers = config.agents.filter((a) => a.role === 'implementation');
351
+
352
+ if (validators.length > 0 && workers.length > 0) {
353
+ for (const worker of workers) {
354
+ const triggersOnValidation = worker.triggers?.some(
355
+ (t) => t.topic === 'VALIDATION_RESULT' || t.topic.includes('VALIDATION')
356
+ );
357
+ if (!triggersOnValidation) {
358
+ errors.push(
359
+ `Worker '${worker.id}' has validators but doesn't trigger on VALIDATION_RESULT. ` +
360
+ 'Rejections will be ignored. Add trigger: { "topic": "VALIDATION_RESULT", "logic": {...} }'
361
+ );
362
+ }
363
+ }
364
+ }
365
+
366
+ // === CHECK 8: Context strategy missing trigger topics ===
367
+ for (const agent of config.agents) {
368
+ if (!agent.contextStrategy?.sources) continue;
369
+
370
+ const triggerTopics = (agent.triggers || []).map((t) => t.topic);
371
+ const contextTopics = agent.contextStrategy.sources.map((s) => s.topic);
372
+
373
+ for (const triggerTopic of triggerTopics) {
374
+ if (triggerTopic === 'ISSUE_OPENED' || triggerTopic === 'CLUSTER_RESUMED') continue;
375
+ if (triggerTopic.endsWith('*')) continue;
376
+
377
+ if (!contextTopics.includes(triggerTopic)) {
378
+ warnings.push(
379
+ `Agent '${agent.id}' triggers on '${triggerTopic}' but doesn't include it in contextStrategy. ` +
380
+ 'Agent may not see what triggered it.'
381
+ );
382
+ }
383
+ }
384
+ }
385
+
386
+ return { errors, warnings };
387
+ }
388
+
389
+ /**
390
+ * Phase 3: Validate agent-specific configurations
391
+ */
392
+ function validateAgents(config) {
393
+ const errors = [];
394
+ const warnings = [];
395
+
396
+ const roles = new Map(); // role -> [agentIds]
397
+
398
+ for (const agent of config.agents) {
399
+ // Track roles
400
+ if (!roles.has(agent.role)) {
401
+ roles.set(agent.role, []);
402
+ }
403
+ roles.get(agent.role).push(agent.id);
404
+
405
+ // Orchestrator should not execute tasks
406
+ if (agent.role === 'orchestrator') {
407
+ const executesTask = agent.triggers?.some(
408
+ (t) => t.action === 'execute_task' || (!t.action && !t.logic)
409
+ );
410
+ if (executesTask) {
411
+ warnings.push(
412
+ `Orchestrator '${agent.id}' has execute_task triggers. ` +
413
+ 'Orchestrators typically use action: "stop_cluster". This may waste API calls.'
414
+ );
415
+ }
416
+ }
417
+
418
+ // JSON output without schema
419
+ if (agent.outputFormat === 'json' && !agent.jsonSchema) {
420
+ warnings.push(
421
+ `Agent '${agent.id}' has outputFormat: 'json' but no jsonSchema. ` +
422
+ 'Output parsing may be unreliable.'
423
+ );
424
+ }
425
+
426
+ // Very high maxIterations
427
+ if (agent.maxIterations && agent.maxIterations > 50) {
428
+ warnings.push(
429
+ `Agent '${agent.id}' has maxIterations: ${agent.maxIterations}. ` +
430
+ 'This may consume significant API credits if stuck in a loop.'
431
+ );
432
+ }
433
+
434
+ // No maxIterations on implementation agent (unbounded retries)
435
+ if (agent.role === 'implementation' && !agent.maxIterations) {
436
+ warnings.push(
437
+ `Implementation agent '${agent.id}' has no maxIterations. ` +
438
+ 'Defaults to 30, but consider setting explicitly.'
439
+ );
440
+ }
441
+ }
442
+
443
+ // Check for role references in logic scripts
444
+ // IMPORTANT: Changed from error to warning because some triggers are designed to be
445
+ // no-ops when the referenced role doesn't exist (e.g., worker's VALIDATION_RESULT
446
+ // trigger returns false when validators.length === 0)
447
+ for (const agent of config.agents) {
448
+ for (const trigger of agent.triggers || []) {
449
+ if (trigger.logic?.script) {
450
+ const script = trigger.logic.script;
451
+ const roleMatch = script.match(/getAgentsByRole\(['"](\w+)['"]\)/g);
452
+ if (roleMatch) {
453
+ for (const match of roleMatch) {
454
+ const role = match.match(/['"](\w+)['"]/)[1];
455
+ if (!roles.has(role)) {
456
+ warnings.push(
457
+ `Agent '${agent.id}' logic references role '${role}' but no agent has that role. ` +
458
+ `Trigger may be a no-op. Available roles: [${Array.from(roles.keys()).join(', ')}]`
459
+ );
460
+ }
461
+ }
462
+ }
463
+ }
464
+ }
465
+ }
466
+
467
+ return { errors, warnings };
468
+ }
469
+
470
+ /**
471
+ * Phase 4: Validate logic scripts (syntax only, not semantics)
472
+ */
473
+ function validateLogicScripts(config) {
474
+ const errors = [];
475
+ const warnings = [];
476
+
477
+ const vm = require('vm');
478
+
479
+ for (const agent of config.agents) {
480
+ for (const trigger of agent.triggers || []) {
481
+ if (!trigger.logic?.script) continue;
482
+
483
+ const script = trigger.logic.script;
484
+
485
+ // Syntax check
486
+ try {
487
+ const wrappedScript = `(function() { ${script} })()`;
488
+ new vm.Script(wrappedScript);
489
+ } catch (syntaxError) {
490
+ errors.push(`Agent '${agent.id}' has invalid logic script: ${syntaxError.message}`);
491
+ continue;
492
+ }
493
+
494
+ // Check for common mistakes - only flag if script is JUST "return false" or "return true"
495
+ // Complex scripts with conditionals should not trigger this
496
+ const trimmedScript = script.trim().replace(/\s+/g, ' ');
497
+ const isSimpleReturnFalse = /^return\s+false;?$/.test(trimmedScript);
498
+ const isSimpleReturnTrue = /^return\s+true;?$/.test(trimmedScript);
499
+
500
+ if (isSimpleReturnFalse) {
501
+ warnings.push(
502
+ `Agent '${agent.id}' logic is just 'return false'. Agent will never trigger.`
503
+ );
504
+ }
505
+
506
+ if (isSimpleReturnTrue) {
507
+ warnings.push(
508
+ `Agent '${agent.id}' logic is just 'return true'. Consider adding conditions or removing the logic block.`
509
+ );
510
+ }
511
+
512
+ // Check for undefined variable access (common typos)
513
+ const knownVars = [
514
+ 'ledger',
515
+ 'cluster',
516
+ 'message',
517
+ 'agent',
518
+ 'helpers',
519
+ 'Set',
520
+ 'Map',
521
+ 'Array',
522
+ 'Object',
523
+ 'JSON',
524
+ 'Date',
525
+ 'Math',
526
+ ];
527
+ const varPattern = /\b([a-zA-Z_]\w*)\s*\./g;
528
+ let match;
529
+ while ((match = varPattern.exec(script)) !== null) {
530
+ const varName = match[1];
531
+ if (
532
+ !knownVars.includes(varName) &&
533
+ !script.includes(`const ${varName}`) &&
534
+ !script.includes(`let ${varName}`)
535
+ ) {
536
+ warnings.push(
537
+ `Agent '${agent.id}' logic uses '${varName}' which may be undefined. ` +
538
+ `Available: [${knownVars.join(', ')}]`
539
+ );
540
+ break; // Only warn once per agent
541
+ }
542
+ }
543
+ }
544
+ }
545
+
546
+ return { errors, warnings };
547
+ }
548
+
549
+ /**
550
+ * Check if iteration pattern is valid
551
+ */
552
+ function isValidIterationPattern(pattern) {
553
+ if (pattern === 'all') return true;
554
+ if (/^\d+$/.test(pattern)) return true; // "1"
555
+ if (/^\d+-\d+$/.test(pattern)) return true; // "1-3"
556
+ if (/^\d+\+$/.test(pattern)) return true; // "5+"
557
+ return false;
558
+ }
559
+
560
+ /**
561
+ * Format validation result for CLI output
562
+ */
563
+ function formatValidationResult(result) {
564
+ const lines = [];
565
+
566
+ if (result.valid) {
567
+ lines.push('✅ Configuration is valid');
568
+ } else {
569
+ lines.push('❌ Configuration has errors');
570
+ }
571
+
572
+ if (result.errors.length > 0) {
573
+ lines.push('\nErrors:');
574
+ for (const error of result.errors) {
575
+ lines.push(` ❌ ${error}`);
576
+ }
577
+ }
578
+
579
+ if (result.warnings.length > 0) {
580
+ lines.push('\nWarnings:');
581
+ for (const warning of result.warnings) {
582
+ lines.push(` ⚠️ ${warning}`);
583
+ }
584
+ }
585
+
586
+ return lines.join('\n');
587
+ }
588
+
589
+ module.exports = {
590
+ validateConfig,
591
+ isConductorConfig,
592
+ validateBasicStructure,
593
+ analyzeMessageFlow,
594
+ validateAgents,
595
+ validateLogicScripts,
596
+ isValidIterationPattern,
597
+ formatValidationResult,
598
+ };
package/src/github.js ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * GitHub - Fetch and parse GitHub issues
3
+ *
4
+ * Provides:
5
+ * - Issue fetching via gh CLI
6
+ * - Parsing of issue data into context
7
+ * - Fallback to plain text input
8
+ */
9
+
10
+ const { execSync } = require('child_process');
11
+
12
+ class GitHub {
13
+ /**
14
+ * Fetch GitHub issue by URL or number
15
+ * @param {String} issueRef - Issue URL or number
16
+ * @returns {Object} Parsed issue data
17
+ */
18
+ static fetchIssue(issueRef) {
19
+ try {
20
+ // Extract issue number from URL if needed
21
+ const issueNumber = this._extractIssueNumber(issueRef);
22
+
23
+ // Fetch issue using gh CLI
24
+ const cmd = `gh issue view ${issueNumber} --json number,title,body,labels,assignees,comments,url`;
25
+ const output = execSync(cmd, { encoding: 'utf8' });
26
+ const issue = JSON.parse(output);
27
+
28
+ return this._parseIssue(issue);
29
+ } catch (error) {
30
+ throw new Error(`Failed to fetch GitHub issue: ${error.message}`);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Extract issue number from URL or return as-is
36
+ * @private
37
+ */
38
+ static _extractIssueNumber(issueRef) {
39
+ // If it's a URL, extract the number
40
+ const urlMatch = issueRef.match(/\/issues\/(\d+)/);
41
+ if (urlMatch) {
42
+ return urlMatch[1];
43
+ }
44
+
45
+ // Otherwise assume it's already a number
46
+ return issueRef;
47
+ }
48
+
49
+ /**
50
+ * Parse issue into structured context
51
+ * @private
52
+ */
53
+ static _parseIssue(issue) {
54
+ let context = `# GitHub Issue #${issue.number}\n\n`;
55
+ context += `## Title\n${issue.title}\n\n`;
56
+
57
+ if (issue.body) {
58
+ context += `## Description\n${issue.body}\n\n`;
59
+ }
60
+
61
+ if (issue.labels && issue.labels.length > 0) {
62
+ context += `## Labels\n`;
63
+ context += issue.labels.map((l) => `- ${l.name}`).join('\n');
64
+ context += '\n\n';
65
+ }
66
+
67
+ if (issue.comments && issue.comments.length > 0) {
68
+ context += `## Comments\n\n`;
69
+ for (const comment of issue.comments) {
70
+ context += `### ${comment.author.login} (${new Date(comment.createdAt).toISOString()})\n`;
71
+ context += `${comment.body}\n\n`;
72
+ }
73
+ }
74
+
75
+ return {
76
+ number: issue.number,
77
+ title: issue.title,
78
+ body: issue.body,
79
+ labels: issue.labels || [],
80
+ comments: issue.comments || [],
81
+ url: issue.url || null,
82
+ context,
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Create a plain text input wrapper
88
+ * @param {String} text - Plain text input
89
+ * @returns {Object} Structured context
90
+ */
91
+ static createTextInput(text) {
92
+ return {
93
+ number: null,
94
+ title: 'Manual Input',
95
+ body: text,
96
+ labels: [],
97
+ comments: [],
98
+ context: `# Manual Input\n\n${text}\n`,
99
+ };
100
+ }
101
+ }
102
+
103
+ module.exports = GitHub;