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.
- package/.agent/config/claude.json +18 -0
- package/.agent/config/plugin.json +102 -0
- package/.agent/config/slash-commands.json +103 -0
- package/.agent/config/specialist-registry.json +415 -0
- package/.agent/config/system-prompt.md +191 -0
- package/.agent/history/architecture-graph.yaml +302 -3
- package/.agent/history/graph-cache.json +515 -45
- package/.agent/history/memory.db +0 -0
- package/.agent/history/snapshots/bin__adapter-install.js.json +13 -0
- package/.agent/history/snapshots/bin__global-store.js.json +15 -0
- package/.agent/history/snapshots/bin__mcp-server.js.json +19 -0
- package/.agent/history/snapshots/bin__proxy-server.js.json +20 -0
- package/.agent/history/snapshots/bin__spawn-agent.js.json +12 -0
- package/.agent/history/snapshots/bin__tk-proxy.js.json +21 -0
- package/.agent/history/snapshots/bin__tribunal-kit.js.json +9 -7
- package/.agent/history/snapshots/bin__wrapper.js.json +14 -0
- package/.agent/history/snapshots/eslint.config.js.json +1 -2
- package/.agent/history/snapshots/scripts__benchmark.js.json +14 -0
- package/.agent/history/snapshots/scripts__changelog.js.json +1 -2
- package/.agent/history/snapshots/scripts__fix-vbc.js.json +11 -0
- package/.agent/history/snapshots/scripts__stress_benchmark.js.json +15 -0
- package/.agent/history/snapshots/scripts__sync-version.js.json +1 -2
- package/.agent/history/snapshots/scripts__validate-payload.js.json +1 -2
- package/.agent/history/snapshots/scripts__visual_audit.js.json +11 -0
- package/.agent/history/snapshots/test__integration__bridges.test.js.json +1 -2
- package/.agent/history/snapshots/test__integration__graceful_degradation.test.js.json +12 -0
- package/.agent/history/snapshots/test__integration__init.test.js.json +1 -2
- package/.agent/history/snapshots/test__integration__minimal_change_pipeline.test.js.json +13 -0
- package/.agent/history/snapshots/test__integration__parallel_tribunal.test.js.json +12 -0
- package/.agent/history/snapshots/test__integration__routing.test.js.json +1 -2
- package/.agent/history/snapshots/test__integration__swarm_dispatcher.test.js.json +1 -2
- package/.agent/history/snapshots/test__integration__sync_status.test.js.json +13 -0
- package/.agent/history/snapshots/test__integration__wave2.test.js.json +1 -2
- package/.agent/history/snapshots/test__integration__wrapper.test.js.json +13 -0
- package/.agent/history/snapshots/test__unit__align.test.js.json +10 -0
- package/.agent/history/snapshots/test__unit__args.test.js.json +3 -3
- package/.agent/history/snapshots/test__unit__audit_release.test.js.json +16 -0
- package/.agent/history/snapshots/test__unit__case_law_manager.test.js.json +1 -2
- package/.agent/history/snapshots/test__unit__cicd_validator.test.js.json +12 -0
- package/.agent/history/snapshots/test__unit__compile.test.js.json +10 -0
- package/.agent/history/snapshots/test__unit__context_broker.test.js.json +1 -2
- package/.agent/history/snapshots/test__unit__contract_engine.test.js.json +13 -0
- package/.agent/history/snapshots/test__unit__copyDir.test.js.json +3 -3
- package/.agent/history/snapshots/test__unit__graph_tools.test.js.json +1 -2
- package/.agent/history/snapshots/test__unit__guardrail_engine.test.js.json +10 -0
- package/.agent/history/snapshots/test__unit__impact_classifier.test.js.json +12 -0
- package/.agent/history/snapshots/test__unit__init.test.js.json +14 -0
- package/.agent/history/snapshots/test__unit__inner_loop_validator.test.js.json +1 -2
- package/.agent/history/snapshots/test__unit__integrity_manifest.test.js.json +13 -0
- package/.agent/history/snapshots/test__unit__learn.test.js.json +11 -0
- package/.agent/history/snapshots/test__unit__marathon.test.js.json +22 -0
- package/.agent/history/snapshots/test__unit__mcp_server.test.js.json +16 -0
- package/.agent/history/snapshots/test__unit__memory.test.js.json +13 -0
- package/.agent/history/snapshots/test__unit__minimal_change.test.js.json +10 -0
- package/.agent/history/snapshots/test__unit__native.test.js.json +10 -0
- package/.agent/history/snapshots/test__unit__optimize.test.js.json +13 -0
- package/.agent/history/snapshots/test__unit__path_resolution.test.js.json +14 -0
- package/.agent/history/snapshots/test__unit__production_readiness_evidence.test.js.json +21 -0
- package/.agent/history/snapshots/test__unit__selfInstall.test.js.json +3 -3
- package/.agent/history/snapshots/test__unit__semver.test.js.json +3 -3
- package/.agent/history/snapshots/test__unit__skill_evolution.test.js.json +11 -0
- package/.agent/history/snapshots/test__unit__stress.test.js.json +15 -0
- package/.agent/history/snapshots/test__unit__swarm_dispatcher.test.js.json +3 -3
- package/.agent/history/snapshots/test__unit__utils.test.js.json +10 -0
- package/.agent/scripts/ast_context_loader.js +137 -0
- package/.agent/scripts/memory_engine.js +581 -0
- package/.agent/scripts/payload_schemas.js +146 -0
- package/.agent/scripts/prompt_compiler.js +59 -11
- package/.agent/scripts/swarm_dispatcher.js +295 -67
- package/.agent/scripts/token_budget_broker.js +117 -6
- package/.claude/CLAUDE.md +442 -0
- package/README.md +22 -12
- package/SECURITY.md +3 -3
- package/bin/adapter-install.js +240 -0
- package/bin/global-store.js +48 -0
- package/bin/mcp-server.js +568 -252
- package/bin/proxy-server.js +113 -0
- package/bin/spawn-agent.js +39 -0
- package/bin/tk-proxy.js +34 -0
- package/dist/commands/init.js +8 -51
- package/dist/commands/status.js +61 -36
- package/dist/commands/validate.js +22 -31
- package/dist/tui/banner.js +77 -0
- package/dist/tui/index.js +21 -0
- package/dist/tui/reviewer-grid.js +90 -0
- package/dist/tui/shimmer.js +91 -0
- package/dist/tui/theme.js +130 -0
- package/dist/tui/tree.js +93 -0
- package/dist/tui/wizard.js +148 -0
- package/dist/utils/helpers.js +5 -41
- package/package.json +27 -10
- package/scripts/build-graph.js +71 -0
- package/.agent/history/snapshots/migrate_refs.js.json +0 -11
- package/.agent/scripts/compile_router.py +0 -5
- 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
|
-
|
|
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
|
-
|
|
100
|
+
rawText = String(input || '');
|
|
72
101
|
}
|
|
73
102
|
|
|
74
|
-
//
|
|
75
|
-
const
|
|
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(
|
|
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
|
-
|
|
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 =>
|
|
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
|
|
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
|
-
|
|
353
|
-
|
|
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
|
-
|
|
578
|
+
swarmPayload = { workers: payloadData };
|
|
356
579
|
} else if (payloadData.workers && Array.isArray(payloadData.workers)) {
|
|
357
|
-
|
|
580
|
+
swarmPayload = payloadData;
|
|
358
581
|
} else {
|
|
359
|
-
|
|
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
|
-
|
|
367
|
-
if (!
|
|
368
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (allErrors.length > 0) {
|
|
626
|
+
return true;
|
|
627
|
+
} catch (error) {
|
|
394
628
|
if (!quiet) {
|
|
395
|
-
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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.');
|