tribunal-kit 7.0.0 → 8.0.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 (95) hide show
  1. package/.agent/config/claude.json +18 -0
  2. package/.agent/config/plugin.json +102 -0
  3. package/.agent/config/slash-commands.json +103 -0
  4. package/.agent/config/specialist-registry.json +415 -0
  5. package/.agent/config/system-prompt.md +191 -0
  6. package/.agent/history/architecture-graph.yaml +302 -3
  7. package/.agent/history/graph-cache.json +515 -45
  8. package/.agent/history/memory.db +0 -0
  9. package/.agent/history/snapshots/bin__adapter-install.js.json +13 -0
  10. package/.agent/history/snapshots/bin__global-store.js.json +15 -0
  11. package/.agent/history/snapshots/bin__mcp-server.js.json +19 -0
  12. package/.agent/history/snapshots/bin__proxy-server.js.json +20 -0
  13. package/.agent/history/snapshots/bin__spawn-agent.js.json +12 -0
  14. package/.agent/history/snapshots/bin__tk-proxy.js.json +21 -0
  15. package/.agent/history/snapshots/bin__tribunal-kit.js.json +9 -7
  16. package/.agent/history/snapshots/bin__wrapper.js.json +14 -0
  17. package/.agent/history/snapshots/eslint.config.js.json +1 -2
  18. package/.agent/history/snapshots/scripts__benchmark.js.json +14 -0
  19. package/.agent/history/snapshots/scripts__changelog.js.json +1 -2
  20. package/.agent/history/snapshots/scripts__fix-vbc.js.json +11 -0
  21. package/.agent/history/snapshots/scripts__stress_benchmark.js.json +15 -0
  22. package/.agent/history/snapshots/scripts__sync-version.js.json +1 -2
  23. package/.agent/history/snapshots/scripts__validate-payload.js.json +1 -2
  24. package/.agent/history/snapshots/scripts__visual_audit.js.json +11 -0
  25. package/.agent/history/snapshots/test__integration__bridges.test.js.json +1 -2
  26. package/.agent/history/snapshots/test__integration__graceful_degradation.test.js.json +12 -0
  27. package/.agent/history/snapshots/test__integration__init.test.js.json +1 -2
  28. package/.agent/history/snapshots/test__integration__minimal_change_pipeline.test.js.json +13 -0
  29. package/.agent/history/snapshots/test__integration__parallel_tribunal.test.js.json +12 -0
  30. package/.agent/history/snapshots/test__integration__routing.test.js.json +1 -2
  31. package/.agent/history/snapshots/test__integration__swarm_dispatcher.test.js.json +1 -2
  32. package/.agent/history/snapshots/test__integration__sync_status.test.js.json +13 -0
  33. package/.agent/history/snapshots/test__integration__wave2.test.js.json +1 -2
  34. package/.agent/history/snapshots/test__integration__wrapper.test.js.json +13 -0
  35. package/.agent/history/snapshots/test__unit__align.test.js.json +10 -0
  36. package/.agent/history/snapshots/test__unit__args.test.js.json +3 -3
  37. package/.agent/history/snapshots/test__unit__audit_release.test.js.json +16 -0
  38. package/.agent/history/snapshots/test__unit__case_law_manager.test.js.json +1 -2
  39. package/.agent/history/snapshots/test__unit__cicd_validator.test.js.json +12 -0
  40. package/.agent/history/snapshots/test__unit__compile.test.js.json +10 -0
  41. package/.agent/history/snapshots/test__unit__context_broker.test.js.json +1 -2
  42. package/.agent/history/snapshots/test__unit__contract_engine.test.js.json +13 -0
  43. package/.agent/history/snapshots/test__unit__copyDir.test.js.json +3 -3
  44. package/.agent/history/snapshots/test__unit__graph_tools.test.js.json +1 -2
  45. package/.agent/history/snapshots/test__unit__guardrail_engine.test.js.json +10 -0
  46. package/.agent/history/snapshots/test__unit__impact_classifier.test.js.json +12 -0
  47. package/.agent/history/snapshots/test__unit__init.test.js.json +14 -0
  48. package/.agent/history/snapshots/test__unit__inner_loop_validator.test.js.json +1 -2
  49. package/.agent/history/snapshots/test__unit__integrity_manifest.test.js.json +13 -0
  50. package/.agent/history/snapshots/test__unit__learn.test.js.json +11 -0
  51. package/.agent/history/snapshots/test__unit__marathon.test.js.json +22 -0
  52. package/.agent/history/snapshots/test__unit__mcp_server.test.js.json +16 -0
  53. package/.agent/history/snapshots/test__unit__memory.test.js.json +13 -0
  54. package/.agent/history/snapshots/test__unit__minimal_change.test.js.json +10 -0
  55. package/.agent/history/snapshots/test__unit__native.test.js.json +10 -0
  56. package/.agent/history/snapshots/test__unit__optimize.test.js.json +13 -0
  57. package/.agent/history/snapshots/test__unit__path_resolution.test.js.json +14 -0
  58. package/.agent/history/snapshots/test__unit__production_readiness_evidence.test.js.json +21 -0
  59. package/.agent/history/snapshots/test__unit__selfInstall.test.js.json +3 -3
  60. package/.agent/history/snapshots/test__unit__semver.test.js.json +3 -3
  61. package/.agent/history/snapshots/test__unit__skill_evolution.test.js.json +11 -0
  62. package/.agent/history/snapshots/test__unit__stress.test.js.json +15 -0
  63. package/.agent/history/snapshots/test__unit__swarm_dispatcher.test.js.json +3 -3
  64. package/.agent/history/snapshots/test__unit__utils.test.js.json +10 -0
  65. package/.agent/scripts/ast_context_loader.js +137 -0
  66. package/.agent/scripts/memory_engine.js +581 -0
  67. package/.agent/scripts/payload_schemas.js +146 -0
  68. package/.agent/scripts/prompt_compiler.js +59 -11
  69. package/.agent/scripts/swarm_dispatcher.js +295 -67
  70. package/.agent/scripts/token_budget_broker.js +117 -6
  71. package/.claude/CLAUDE.md +442 -0
  72. package/README.md +22 -12
  73. package/SECURITY.md +3 -3
  74. package/bin/adapter-install.js +240 -0
  75. package/bin/global-store.js +48 -0
  76. package/bin/mcp-server.js +568 -252
  77. package/bin/proxy-server.js +113 -0
  78. package/bin/spawn-agent.js +39 -0
  79. package/bin/tk-proxy.js +34 -0
  80. package/dist/commands/init.js +8 -51
  81. package/dist/commands/status.js +61 -36
  82. package/dist/commands/validate.js +22 -31
  83. package/dist/tui/banner.js +77 -0
  84. package/dist/tui/index.js +21 -0
  85. package/dist/tui/reviewer-grid.js +90 -0
  86. package/dist/tui/shimmer.js +91 -0
  87. package/dist/tui/theme.js +130 -0
  88. package/dist/tui/tree.js +93 -0
  89. package/dist/tui/wizard.js +148 -0
  90. package/dist/utils/helpers.js +5 -41
  91. package/package.json +27 -10
  92. package/scripts/build-graph.js +71 -0
  93. package/.agent/history/snapshots/migrate_refs.js.json +0 -11
  94. package/.agent/scripts/compile_router.py +0 -5
  95. package/.agent/scripts/migrate_skills_frontmatter.py +0 -5
@@ -0,0 +1,146 @@
1
+ const { z } = require('zod');
2
+
3
+ // Worker Request Schema
4
+ const WorkerRequestSchema = z.object({
5
+ task_id: z.string().min(1, 'task_id must be a non-empty string'),
6
+ type: z.enum(
7
+ [
8
+ 'research',
9
+ 'generate_code',
10
+ 'review_code',
11
+ 'debug',
12
+ 'plan',
13
+ 'design_schema',
14
+ 'write_docs',
15
+ 'security_audit',
16
+ 'optimize',
17
+ 'test',
18
+ ],
19
+ 'type must be a valid worker type',
20
+ ),
21
+ agent: z.string().min(1, 'agent must be a non-empty string'),
22
+ goal: z
23
+ .string()
24
+ .min(1, 'goal must be a non-empty string')
25
+ .max(200, 'goal must not exceed 200 characters (keep it to a single, focused sentence)'),
26
+ context: z
27
+ .string()
28
+ .min(1, 'context must be a non-empty string')
29
+ .max(800, 'context must not exceed 800 characters (trim to minimal required context only)'),
30
+ max_retries: z
31
+ .number()
32
+ .int('max_retries must be an integer')
33
+ .min(1, 'max_retries must be at least 1')
34
+ .max(3, 'max_retries must be at most 3')
35
+ .optional()
36
+ .default(3),
37
+ files_attached: z
38
+ .array(z.string())
39
+ .optional()
40
+ .default([])
41
+ .describe('List of file paths to attach to the worker'),
42
+ priority: z
43
+ .enum(['low', 'medium', 'high'])
44
+ .optional()
45
+ .default('medium')
46
+ .describe('Task priority level'),
47
+ metadata: z.record(z.unknown()).optional().describe('Additional metadata for the worker'),
48
+ });
49
+
50
+ // Worker Result Schema
51
+ const WorkerResultSchema = z.object({
52
+ task_id: z.string().min(1, 'task_id must be a non-empty string'),
53
+ agent: z.string().min(1, 'agent must be a non-empty string'),
54
+ status: z.enum(
55
+ ['success', 'failure', 'escalate'],
56
+ 'status must be one of: success, failure, escalate',
57
+ ),
58
+ output: z
59
+ .string()
60
+ .optional()
61
+ .describe('Output generated by the worker (required for success status)'),
62
+ error: z.string().optional().describe('Error message (required for failure or escalate status)'),
63
+ attempts: z
64
+ .number()
65
+ .int('attempts must be an integer')
66
+ .min(1, 'attempts must be at least 1')
67
+ .optional(),
68
+ duration: z.number().int('duration must be an integer (milliseconds)').optional(),
69
+ findings: z
70
+ .array(z.string())
71
+ .optional()
72
+ .default([])
73
+ .describe('List of findings or issues discovered'),
74
+ suggestions: z
75
+ .array(z.string())
76
+ .optional()
77
+ .default([])
78
+ .describe('List of suggestions or recommendations'),
79
+ });
80
+
81
+ // Swarm Payload Schema
82
+ const SwarmPayloadSchema = z.object({
83
+ workers: z
84
+ .array(WorkerRequestSchema)
85
+ .min(1, 'Must have at least one worker')
86
+ .max(28, 'Maximum 28 workers allowed per swarm (for full tribunal)'),
87
+ wave: z
88
+ .enum(['wave-1-core', 'wave-2-security', 'wave-3-domain', 'full'])
89
+ .optional()
90
+ .default('full')
91
+ .describe('Which tribunal wave to execute'),
92
+ shared_context: z
93
+ .record(z.unknown())
94
+ .optional()
95
+ .describe('Context shared between all workers in the swarm'),
96
+ metadata: z
97
+ .record(z.unknown())
98
+ .optional()
99
+ .describe('Additional metadata for the swarm execution'),
100
+ });
101
+
102
+ /**
103
+ * Validate a payload against a Zod schema
104
+ * @param {Object} payload - The payload to validate
105
+ * @param {z.ZodSchema} schema - The Zod schema to validate against
106
+ * @returns {Object} Validation result with { valid: boolean, data?: Object, errors?: Array }
107
+ */
108
+ function validatePayload(payload, schema) {
109
+ const result = schema.safeParse(payload);
110
+ if (!result.success) {
111
+ return {
112
+ valid: false,
113
+ errors: result.error.issues.map(issue => ({
114
+ path: issue.path.join('.'),
115
+ message: issue.message,
116
+ code: issue.code,
117
+ })),
118
+ };
119
+ }
120
+ return { valid: true, data: result.data };
121
+ }
122
+
123
+ /**
124
+ * Validate a payload and throw an error if invalid
125
+ * @param {Object} payload - The payload to validate
126
+ * @param {z.ZodSchema} schema - The Zod schema to validate against
127
+ * @throws {Error} If validation fails
128
+ */
129
+ function validatePayloadOrThrow(payload, schema) {
130
+ const validationResult = validatePayload(payload, schema);
131
+ if (!validationResult.valid) {
132
+ const errorMessages = validationResult.errors
133
+ .map(err => `${err.path}: ${err.message}`)
134
+ .join('; ');
135
+ throw new Error(`Payload validation failed: ${errorMessages}`);
136
+ }
137
+ return validationResult.data;
138
+ }
139
+
140
+ module.exports = {
141
+ WorkerRequestSchema,
142
+ WorkerResultSchema,
143
+ SwarmPayloadSchema,
144
+ validatePayload,
145
+ validatePayloadOrThrow,
146
+ };
@@ -53,35 +53,69 @@ const ACTION_ROUTER = {
53
53
  audit: ['vulnerability-scanner', 'lint-and-validate'],
54
54
  };
55
55
 
56
+ /**
57
+ * Sanitizes user input to prevent prompt injection attacks.
58
+ * @param {string} text
59
+ * @returns {string} Sanitized text
60
+ */
61
+ function sanitizeUserInput(text) {
62
+ // 1. Strip XML/HTML tags
63
+ const sanitized = text.replace(/<[^>]+>/g, '');
64
+
65
+ // 2. Check for injection patterns
66
+ const injectionPatterns = [
67
+ /ignore\s+(all\s+)?previous\s+instructions/i,
68
+ /you\s+are\s+now\s+a?\s+/i,
69
+ /system\s*:\s*/i,
70
+ /assistant\s*:\s*/i,
71
+ /\[\[INST\]\]/i,
72
+ /<<SYS>>/i,
73
+ ];
74
+
75
+ for (const pattern of injectionPatterns) {
76
+ if (pattern.test(sanitized)) {
77
+ // Wrap in explicit delimiter if injection is detected
78
+ return `USER_INPUT_START\n${sanitized}\nUSER_INPUT_END`;
79
+ }
80
+ }
81
+
82
+ return sanitized;
83
+ }
84
+
56
85
  /**
57
86
  * Programmatically compile a user request or spec object into a hyper-dense super-prompt string.
58
87
  * @param {string|object} input
59
88
  * @returns {string} Highly compressed YAML string
60
89
  */
61
90
  function compileSuperPrompt(input) {
62
- let cleanInput = '';
63
91
  let preStack = [];
64
92
  let preRules = [];
65
93
 
94
+ let rawText = '';
66
95
  if (typeof input === 'object' && input !== null) {
67
- cleanInput = (input.task || input.spec || '').trim();
96
+ rawText = input.task || input.spec || '';
68
97
  if (Array.isArray(input.stack)) preStack = input.stack;
69
98
  if (Array.isArray(input.rules)) preRules = input.rules;
70
99
  } else {
71
- cleanInput = String(input || '').trim();
100
+ rawText = String(input || '');
72
101
  }
73
102
 
74
- // 1. Extract Action (Intent mapping)
75
- const actionMatch = cleanInput.match(
103
+ // Sanitize the input to prevent prompt injection
104
+ const originalInput = sanitizeUserInput(rawText);
105
+ const trimmedInput = originalInput.trim();
106
+ const _cleanInput = trimmedInput;
107
+
108
+ // 1. Extract Action (Intent mapping) using trimmed input
109
+ const actionMatch = trimmedInput.match(
76
110
  /^(?:(?:hey,?\s*|please\s+|can you\s+|could you\s+|would you\s+|i need you to\s+|i want to\s+)*)(build|create|fix|debug|refactor|update|write|design|audit)\b/i,
77
111
  );
78
112
  const action = actionMatch ? actionMatch[1].toLowerCase() : 'execute';
79
113
 
80
- // 2. Extract Tech Stack
114
+ // 2. Extract Tech Stack using trimmed input
81
115
  const stackSet = new Set(preStack.map(s => s.toLowerCase()));
82
116
  TECH_KEYWORDS.forEach(tech => {
83
117
  const regex = new RegExp(`\\b${tech.replace('.', '\\.')}\\b`, 'i');
84
- if (regex.test(cleanInput)) {
118
+ if (regex.test(trimmedInput)) {
85
119
  stackSet.add(tech.toLowerCase());
86
120
  }
87
121
  });
@@ -101,10 +135,25 @@ function compileSuperPrompt(input) {
101
135
 
102
136
  const finalSkills = Array.from(recommendedSkills).slice(0, 4);
103
137
 
104
- // 4. Compact YAML Generation
105
- const indentedTarget = cleanInput
138
+ // 4. Compact YAML Generation with prompt injection defense
139
+ // Preserve leading whitespace and escape leading "---" or "..." to prevent YAML document start
140
+ const indentedTarget = originalInput
106
141
  .split('\n')
107
- .map(line => ' ' + line)
142
+ .map(line => {
143
+ const match = line.match(/^(\s*)/);
144
+ const leadingSpaces = match[1];
145
+ const content = line.substring(match[1].length);
146
+
147
+ let escapedContent = content;
148
+ if (content.startsWith('---')) {
149
+ escapedContent = '- --' + content.substring(3);
150
+ } else if (content.startsWith('...')) {
151
+ escapedContent = '- ...' + content.substring(3);
152
+ }
153
+
154
+ const withIndent = ' ' + leadingSpaces + escapedContent; // 3 spaces for YAML indentation
155
+ return withIndent;
156
+ })
108
157
  .join('\n');
109
158
 
110
159
  return [
@@ -114,7 +163,6 @@ function compileSuperPrompt(input) {
114
163
  indentedTarget,
115
164
  `stack: [${stack.join(', ')}]`,
116
165
  `recommended_skills: [${finalSkills.join(', ')}]`,
117
- '---',
118
166
  ].join('\n');
119
167
  }
120
168
 
@@ -9,6 +9,12 @@
9
9
  const fs = require('fs');
10
10
  const path = require('path');
11
11
  const { execSync } = require('child_process');
12
+ const {
13
+ WorkerRequestSchema: _WorkerRequestSchema,
14
+ WorkerResultSchema: _WorkerResultSchema,
15
+ SwarmPayloadSchema,
16
+ validatePayloadOrThrow,
17
+ } = require('./payload_schemas');
12
18
 
13
19
  // ─── ANSI TUI Renderer ────────────────────────────────────────────────────────
14
20
  class SwarmDashboard {
@@ -85,7 +91,7 @@ const VALID_RESULT_STATUSES = new Set(['success', 'failure', 'escalate']);
85
91
 
86
92
  const MAX_GOAL_LENGTH = 200;
87
93
  const MAX_CONTEXT_LENGTH = 800;
88
- const MAX_WORKERS_PER_SWARM = 5;
94
+ const _MAX_WORKERS_PER_SWARM = 10;
89
95
 
90
96
  function getBinaryPath() {
91
97
  if (process.env.TRIBUNAL_FORCE_JS === '1') return null;
@@ -134,6 +140,42 @@ function findAgentDir(startPath) {
134
140
  return null;
135
141
  }
136
142
 
143
+ // ─── Retry Policy Configuration ───────────────────────────────────────────────
144
+ const MAX_RETRIES = 3;
145
+ const RETRY_DELAYS = [1000, 3000, 10000]; // Exponential backoff: 1s, 3s, 10s
146
+
147
+ /**
148
+ * Execute a function with 3-strike retry policy
149
+ * @param {Function} fn - Function to execute
150
+ * @param {Object} context - Context for error reporting
151
+ * @returns {Promise<any>} Result of the function or throws error after max retries
152
+ */
153
+ async function executeWithRetry(fn, _context = {}) {
154
+ let lastError;
155
+
156
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
157
+ try {
158
+ return await fn();
159
+ } catch (error) {
160
+ lastError = error;
161
+
162
+ // If this was the last attempt, don't delay
163
+ if (attempt === MAX_RETRIES) {
164
+ break;
165
+ }
166
+
167
+ // Wait before retrying (exponential backoff)
168
+ const delay = RETRY_DELAYS[attempt - 1];
169
+ await new Promise(resolve => setTimeout(resolve, delay));
170
+ }
171
+ }
172
+
173
+ // If we got here, all retries failed
174
+ throw new Error(
175
+ `Operation failed after ${MAX_RETRIES} attempts. Last error: ${lastError.message}`,
176
+ );
177
+ }
178
+
137
179
  // ─── Legacy mode: validate orchestrator micro-worker payloads ──────────────────
138
180
 
139
181
  function validatePayload(payloadData, workspaceRoot, agentsDir, payloadFile = null) {
@@ -227,6 +269,185 @@ function buildWorkerPrompts(payloadData, workspaceRoot) {
227
269
  return prompts;
228
270
  }
229
271
 
272
+ // ─── Swarm Orchestrator (Wave-Based Execution) ───────────────────────────────
273
+
274
+ const TRIBUNAL_WAVES = {
275
+ 'wave-1-core': {
276
+ reviewers: ['precedence-reviewer', 'logic-reviewer', 'schema-reviewer', 'resilience-reviewer'],
277
+ maxParallel: 4,
278
+ timeout: 30000,
279
+ failureMode: 'halt',
280
+ },
281
+ 'wave-2-security': {
282
+ reviewers: [
283
+ 'security-auditor',
284
+ 'dependency-reviewer',
285
+ 'type-safety-reviewer',
286
+ 'complexity-reviewer',
287
+ 'sql-reviewer',
288
+ 'pipeline-reviewer',
289
+ ],
290
+ maxParallel: 6,
291
+ timeout: 45000,
292
+ failureMode: 'continue-warn',
293
+ },
294
+ 'wave-3-domain': {
295
+ reviewers: [
296
+ 'frontend-reviewer',
297
+ 'performance-reviewer',
298
+ 'mobile-reviewer',
299
+ 'ai-code-reviewer',
300
+ 'test-coverage-reviewer',
301
+ 'accessibility-reviewer',
302
+ 'ui-ux-auditor',
303
+ 'review-animations',
304
+ 'vitals-reviewer',
305
+ 'db-latency-auditor',
306
+ 'throughput-optimizer',
307
+ ],
308
+ maxParallel: 8,
309
+ timeout: 60000,
310
+ failureMode: 'continue',
311
+ },
312
+ };
313
+
314
+ class SwarmOrchestrator {
315
+ constructor(dashboard = null) {
316
+ this.dashboard = dashboard;
317
+ this.workers = [];
318
+ this.results = [];
319
+ }
320
+
321
+ async executeTribunal(payload, waveName = 'full') {
322
+ const wavesToRun =
323
+ waveName === 'full' ? ['wave-1-core', 'wave-2-security', 'wave-3-domain'] : [waveName];
324
+
325
+ const workers =
326
+ typeof payload === 'object' && payload !== null && payload.workers
327
+ ? payload.workers
328
+ : Array.isArray(payload)
329
+ ? payload
330
+ : [payload];
331
+
332
+ this.workers = workers;
333
+ this.results = [];
334
+
335
+ if (this.dashboard) {
336
+ this.dashboard.workers = this.workers.map(w => ({
337
+ name: w.target_agent || w.agent || 'Worker',
338
+ task: (w.task_description || w.goal || '').slice(0, 40) + '...',
339
+ status: '⏳ Pending',
340
+ color: '\x1b[33m', // Yellow
341
+ }));
342
+ this.dashboard.start();
343
+ }
344
+
345
+ for (const wave of wavesToRun) {
346
+ const config = TRIBUNAL_WAVES[wave] || {
347
+ maxParallel: 4,
348
+ timeout: 30000,
349
+ failureMode: 'continue',
350
+ };
351
+ // Filter workers belonging to this wave
352
+ let waveWorkers = [];
353
+ if (waveName === 'full') {
354
+ // If it's a full run, we only pick the reviewers designated for this wave
355
+ const allowedReviewers = new Set(config.reviewers || []);
356
+ waveWorkers = this.workers.filter(w => allowedReviewers.has(w.agent || w.target_agent));
357
+ } else {
358
+ waveWorkers = this.workers;
359
+ }
360
+
361
+ if (waveWorkers.length === 0) continue;
362
+
363
+ const waveResults = await this.executeWave(wave, config, waveWorkers);
364
+ this.results.push(...waveResults);
365
+
366
+ if (config.failureMode === 'halt' && waveResults.some(r => r.status === 'failure')) {
367
+ if (this.dashboard) this.dashboard.stop();
368
+ console.error(
369
+ `\n\x1b[31m✖ HALT: Core wave '${wave}' encountered a failure. Aborting subsequent waves.\x1b[0m`,
370
+ );
371
+ return this.results;
372
+ }
373
+ }
374
+
375
+ if (this.dashboard) {
376
+ this.dashboard.stop();
377
+ }
378
+ return this.results;
379
+ }
380
+
381
+ async executeWave(waveName, config, workers) {
382
+ const results = [];
383
+ const { maxParallel } = config;
384
+
385
+ // Batch workers by maxParallel
386
+ for (let i = 0; i < workers.length; i += maxParallel) {
387
+ const batch = workers.slice(i, i + maxParallel);
388
+ const batchPromises = batch.map(worker => this.executeWorker(worker, config.timeout));
389
+ const batchResults = await Promise.allSettled(batchPromises);
390
+ results.push(...batchResults.map(r => r.value || r.reason));
391
+ }
392
+ return results;
393
+ }
394
+
395
+ async executeWorker(worker, timeout) {
396
+ const workerIndex = this.workers.indexOf(worker);
397
+ if (this.dashboard && workerIndex !== -1) {
398
+ this.dashboard.updateStatus(workerIndex, 'Working', '\x1b[36m'); // Cyan
399
+ }
400
+
401
+ const agentName = worker.agent || worker.target_agent || 'Unknown';
402
+
403
+ try {
404
+ const result = await executeWithRetry(
405
+ async () => {
406
+ return this.invokeReviewerMock(worker, timeout);
407
+ },
408
+ { agentName },
409
+ );
410
+
411
+ if (this.dashboard && workerIndex !== -1) {
412
+ this.dashboard.updateStatus(workerIndex, '✔ Success', '\x1b[32m'); // Green
413
+ }
414
+ return {
415
+ agent: agentName,
416
+ status: 'success',
417
+ output: result,
418
+ };
419
+ } catch (error) {
420
+ if (this.dashboard && workerIndex !== -1) {
421
+ this.dashboard.updateStatus(workerIndex, '✖ Failed', '\x1b[31m'); // Red
422
+ }
423
+ return {
424
+ agent: agentName,
425
+ status: 'failure',
426
+ error: error.message,
427
+ };
428
+ }
429
+ }
430
+
431
+ // Since this script primarily validates and outputs JSON, we simulate execution
432
+ // taking between 1-3 seconds for demo purposes.
433
+ async invokeReviewerMock(worker, _timeout) {
434
+ return new Promise((resolve, reject) => {
435
+ const delay = Math.floor(Math.random() * 2000) + 1000;
436
+ // 5% chance to simulate a transient failure for retry logic testing
437
+ const shouldFail = Math.random() < 0.05;
438
+
439
+ setTimeout(() => {
440
+ if (shouldFail) {
441
+ reject(new Error('Transient API failure simulated.'));
442
+ } else {
443
+ resolve(`Completed processing for ${worker.agent || worker.target_agent}`);
444
+ }
445
+ }, delay);
446
+ });
447
+ }
448
+ }
449
+ // ─────────────────────────────────────────────────────────────────────────────
450
+
230
451
  // ─── Swarm mode: validate WorkerRequest / WorkerResult payloads ───────────────
231
452
 
232
453
  function validateWorkerRequest(req, index, agentsDir) {
@@ -349,57 +570,66 @@ function validateSwarmPayload(payloadData, agentsDir, payloadFile = null, opts =
349
570
  }
350
571
  }
351
572
 
352
- let items;
353
- if (typeof payloadData === 'object' && payloadData !== null) {
573
+ // Validate using Zod schema
574
+ try {
575
+ // Convert payloadData to the expected format for SwarmPayloadSchema
576
+ let swarmPayload;
354
577
  if (Array.isArray(payloadData)) {
355
- items = payloadData;
578
+ swarmPayload = { workers: payloadData };
356
579
  } else if (payloadData.workers && Array.isArray(payloadData.workers)) {
357
- items = payloadData.workers;
580
+ swarmPayload = payloadData;
358
581
  } else {
359
- items = [payloadData];
582
+ swarmPayload = { workers: [payloadData] };
360
583
  }
361
- } else {
362
- if (!quiet) console.error('ERROR: Swarm payload must be a JSON object or array.');
363
- return false;
364
- }
365
584
 
366
- if (items.length > MAX_WORKERS_PER_SWARM) {
367
- if (!quiet) {
368
- console.error(
369
- `ERROR: Swarm payload contains ${items.length} workers, exceeding the maximum of ${MAX_WORKERS_PER_SWARM}.`,
370
- );
585
+ // Add default wave if not provided
586
+ if (!swarmPayload.wave) {
587
+ swarmPayload.wave = 'full';
371
588
  }
372
- return false;
373
- }
374
589
 
375
- const allErrors = [];
376
- for (let i = 0; i < items.length; i++) {
377
- const item = items[i];
378
- if (typeof item !== 'object' || item === null) {
379
- allErrors.push(`Item[${i}]: must be a JSON object.`);
380
- continue;
381
- }
590
+ // Validate using Zod schema
591
+ validatePayloadOrThrow(swarmPayload, SwarmPayloadSchema);
592
+
593
+ // Additional validation: check that agents exist
594
+ const workers = swarmPayload.workers;
595
+ for (let i = 0; i < workers.length; i++) {
596
+ const worker = workers[i];
597
+ const agent = worker.agent;
598
+ if (agent && typeof agent === 'string') {
599
+ const agentFile = path.join(resolvedAgentsDir, `${agent}.md`);
600
+ if (!fs.existsSync(agentFile)) {
601
+ if (!quiet) {
602
+ console.error(
603
+ `ERROR: Worker ${i}: agent '${agent}' not found at ${agentFile}. Only agents that exist in .agent/agents/ are valid.`,
604
+ );
605
+ }
606
+ return false;
607
+ }
608
+ }
382
609
 
383
- let errors;
384
- if ('status' in item && 'output' in item) {
385
- errors = validateWorkerResult(item, i);
386
- } else {
387
- errors = validateWorkerRequest(item, i, resolvedAgentsDir);
610
+ // Validate attached files exist (warning only, not error)
611
+ const filesAttached = worker.files_attached || [];
612
+ if (Array.isArray(filesAttached)) {
613
+ for (const f of filesAttached) {
614
+ const filePath = path.resolve(process.cwd(), f);
615
+ if (!fs.existsSync(filePath)) {
616
+ if (!quiet) {
617
+ console.warn(
618
+ `WARN: Worker ${i}: attached file '${f}' does not exist (might be a new file to create).`,
619
+ );
620
+ }
621
+ }
622
+ }
623
+ }
388
624
  }
389
625
 
390
- allErrors.push(...errors);
391
- }
392
-
393
- if (allErrors.length > 0) {
626
+ return true;
627
+ } catch (error) {
394
628
  if (!quiet) {
395
- for (const err of allErrors) {
396
- console.error(`ERROR: ${err}`);
397
- }
629
+ console.error(`ERROR: Swarm payload validation failed: ${error.message}`);
398
630
  }
399
631
  return false;
400
632
  }
401
-
402
- return true;
403
633
  }
404
634
 
405
635
  // ─── Main ─────────────────────────────────────────────────────────────────────
@@ -499,35 +729,33 @@ function main() {
499
729
  }
500
730
  }
501
731
 
502
- if (useTui) {
503
- const workers =
504
- typeof payloadData === 'object' && payloadData !== null && payloadData.workers
505
- ? payloadData.workers
506
- : Array.isArray(payloadData)
507
- ? payloadData
508
- : [payloadData];
509
-
510
- const dashboard = new SwarmDashboard(workers);
511
- dashboard.start();
512
-
513
- // Simulate parallel execution for demo/UX purposes
514
- setTimeout(() => dashboard.updateStatus(0, 'Researching', '\x1b[36m'), 1000);
515
- setTimeout(() => {
516
- if (workers.length > 1) dashboard.updateStatus(1, 'Generating', '\x1b[35m');
517
- }, 1500);
518
-
519
- setTimeout(() => {
520
- workers.forEach((w, i) => dashboard.updateStatus(i, '✔ Done', '\x1b[32m'));
521
- dashboard.stop();
522
- console.log('\n\x1b[32m✔ Swarm validation complete. Ready for dispatch.\x1b[0m\n');
523
- }, 3000);
524
- } else {
525
- console.log('INFO: Swarm payload validation successful.');
526
- if (astContext) {
527
- console.log('--- ENRICHED SWARM PAYLOAD ---');
528
- console.log(JSON.stringify(payloadData, null, 2));
529
- }
530
- }
732
+ const orchestrator = new SwarmOrchestrator(useTui ? new SwarmDashboard([]) : null);
733
+
734
+ // We must run in an async context since SwarmOrchestrator uses async/await.
735
+ const wave =
736
+ typeof payloadData === 'object' && payloadData !== null && payloadData.wave
737
+ ? payloadData.wave
738
+ : 'full';
739
+
740
+ orchestrator
741
+ .executeTribunal(payloadData, wave)
742
+ .then(results => {
743
+ if (!useTui) {
744
+ console.log('INFO: Swarm payload validation and orchestration successful.');
745
+ if (astContext) {
746
+ console.log('--- ENRICHED SWARM PAYLOAD ---');
747
+ console.log(JSON.stringify(payloadData, null, 2));
748
+ }
749
+ console.log('--- EXECUTION PLAN (MOCK RESULTS) ---');
750
+ console.log(JSON.stringify(results, null, 2));
751
+ } else {
752
+ console.log('\n\x1b[32m✔ Swarm orchestration complete.\x1b[0m\n');
753
+ }
754
+ })
755
+ .catch(err => {
756
+ console.error(`ERROR: Swarm orchestration failed: ${err.message}`);
757
+ process.exit(1);
758
+ });
531
759
  } else {
532
760
  if (!validatePayload(payloadData, workspaceRoot, agentsDir, file)) {
533
761
  console.error('ERROR: Payload validation failed.');