@covibes/zeroshot 1.0.1 → 1.1.3

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 (42) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +2 -0
  3. package/cli/index.js +151 -208
  4. package/cli/message-formatter-utils.js +75 -0
  5. package/cli/message-formatters-normal.js +214 -0
  6. package/cli/message-formatters-watch.js +181 -0
  7. package/cluster-templates/base-templates/full-workflow.json +10 -5
  8. package/docker/zeroshot-cluster/Dockerfile +6 -0
  9. package/package.json +5 -2
  10. package/src/agent/agent-task-executor.js +237 -112
  11. package/src/isolation-manager.js +94 -51
  12. package/src/orchestrator.js +45 -10
  13. package/src/preflight.js +383 -0
  14. package/src/process-metrics.js +546 -0
  15. package/src/status-footer.js +543 -0
  16. package/task-lib/attachable-watcher.js +202 -0
  17. package/task-lib/commands/clean.js +50 -0
  18. package/task-lib/commands/get-log-path.js +23 -0
  19. package/task-lib/commands/kill.js +32 -0
  20. package/task-lib/commands/list.js +105 -0
  21. package/task-lib/commands/logs.js +411 -0
  22. package/task-lib/commands/resume.js +41 -0
  23. package/task-lib/commands/run.js +48 -0
  24. package/task-lib/commands/schedule.js +105 -0
  25. package/task-lib/commands/scheduler-cmd.js +96 -0
  26. package/task-lib/commands/schedules.js +98 -0
  27. package/task-lib/commands/status.js +44 -0
  28. package/task-lib/commands/unschedule.js +16 -0
  29. package/task-lib/completion.js +9 -0
  30. package/task-lib/config.js +10 -0
  31. package/task-lib/name-generator.js +230 -0
  32. package/task-lib/package.json +3 -0
  33. package/task-lib/runner.js +123 -0
  34. package/task-lib/scheduler.js +252 -0
  35. package/task-lib/store.js +217 -0
  36. package/task-lib/tui/formatters.js +166 -0
  37. package/task-lib/tui/index.js +197 -0
  38. package/task-lib/tui/layout.js +111 -0
  39. package/task-lib/tui/renderer.js +119 -0
  40. package/task-lib/tui.js +384 -0
  41. package/task-lib/watcher.js +162 -0
  42. package/cluster-templates/conductor-junior-bootstrap.json +0 -69
@@ -0,0 +1,384 @@
1
+ /**
2
+ * Task TUI - Interactive task viewer
3
+ *
4
+ * Features:
5
+ * - List all tasks
6
+ * - Navigate with arrow keys
7
+ * - Press Enter to view logs
8
+ * - Press Esc to go back to list
9
+ */
10
+
11
+ import blessed from 'blessed';
12
+ import { loadTasks } from './store.js';
13
+ import { isProcessRunning } from './runner.js';
14
+ import fs from 'fs';
15
+ import path from 'path';
16
+ import os from 'os';
17
+
18
+ // Parse a single log line from JSON stream format
19
+ function parseLogLine(line) {
20
+ let trimmed = line.trim();
21
+
22
+ // Strip timestamp prefix if present: [1234567890]{...} -> {...}
23
+ const timestampMatch = trimmed.match(/^\[\d+\](.*)$/);
24
+ if (timestampMatch) {
25
+ trimmed = timestampMatch[1];
26
+ }
27
+
28
+ // Keep non-JSON lines
29
+ if (!trimmed.startsWith('{')) {
30
+ return trimmed ? trimmed + '\n' : '';
31
+ }
32
+
33
+ // Parse JSON and extract relevant info
34
+ try {
35
+ const event = JSON.parse(trimmed);
36
+
37
+ // Extract text from content_block_delta
38
+ if (event.type === 'stream_event' && event.event?.type === 'content_block_delta') {
39
+ return event.event?.delta?.text || '';
40
+ }
41
+ // Extract tool use info
42
+ else if (event.type === 'stream_event' && event.event?.type === 'content_block_start') {
43
+ const block = event.event?.content_block;
44
+ if (block?.type === 'tool_use' && block?.name) {
45
+ return `\n[Tool: ${block.name}]\n`;
46
+ }
47
+ }
48
+ // Extract assistant messages
49
+ else if (event.type === 'assistant' && event.message?.content) {
50
+ let output = '';
51
+ for (const content of event.message.content) {
52
+ if (content.type === 'text') {
53
+ output += content.text;
54
+ }
55
+ }
56
+ return output;
57
+ }
58
+ // Extract final result
59
+ else if (event.type === 'result') {
60
+ if (event.is_error) {
61
+ return `\n[ERROR] ${event.result || 'Unknown error'}\n`;
62
+ }
63
+ }
64
+ } catch {
65
+ // Not JSON or parse error - skip
66
+ }
67
+
68
+ return '';
69
+ }
70
+
71
+ class TaskTUI {
72
+ constructor(options = {}) {
73
+ this.tasks = [];
74
+ this.selectedIndex = 0;
75
+ this.viewMode = 'list'; // 'list' or 'detail'
76
+ this.selectedTask = null;
77
+ this.initialScrollDone = false;
78
+ this.refreshRate = options.refreshRate || 1000;
79
+ }
80
+
81
+ start() {
82
+ // Create screen
83
+ this.screen = blessed.screen({
84
+ smartCSR: true,
85
+ title: 'Vibe Task Watch',
86
+ dockBorders: true,
87
+ fullUnicode: true,
88
+ });
89
+
90
+ // Create main list view
91
+ this.listBox = blessed.list({
92
+ parent: this.screen,
93
+ top: 0,
94
+ left: 0,
95
+ width: '100%',
96
+ height: '100%-2',
97
+ keys: true,
98
+ vi: true,
99
+ mouse: true,
100
+ border: {
101
+ type: 'line',
102
+ },
103
+ style: {
104
+ selected: {
105
+ bg: 'blue',
106
+ fg: 'white',
107
+ bold: true,
108
+ },
109
+ border: {
110
+ fg: 'cyan',
111
+ },
112
+ },
113
+ tags: true,
114
+ });
115
+
116
+ // Create detail view (hidden initially)
117
+ this.detailBox = blessed.box({
118
+ parent: this.screen,
119
+ top: 0,
120
+ left: 0,
121
+ width: '100%',
122
+ height: '100%-2',
123
+ border: {
124
+ type: 'line',
125
+ },
126
+ style: {
127
+ border: {
128
+ fg: 'cyan',
129
+ },
130
+ },
131
+ scrollable: true,
132
+ alwaysScroll: true,
133
+ keys: true,
134
+ vi: true,
135
+ mouse: true,
136
+ scrollbar: {
137
+ ch: '│',
138
+ style: {
139
+ bg: 'cyan',
140
+ },
141
+ },
142
+ tags: false,
143
+ hidden: true,
144
+ });
145
+
146
+ // Status bar
147
+ this.statusBar = blessed.box({
148
+ parent: this.screen,
149
+ bottom: 0,
150
+ left: 0,
151
+ width: '100%',
152
+ height: 2,
153
+ content: '',
154
+ tags: true,
155
+ style: {
156
+ bg: 'blue',
157
+ fg: 'white',
158
+ },
159
+ });
160
+
161
+ // Setup keybindings
162
+ this.setupKeybindings();
163
+
164
+ // Initial render
165
+ this.refreshData();
166
+ this.render();
167
+
168
+ // Start polling
169
+ this.pollInterval = setInterval(() => {
170
+ this.refreshData();
171
+ this.render();
172
+ }, this.refreshRate);
173
+
174
+ // Render screen
175
+ this.screen.render();
176
+
177
+ // Focus on list after layout is established
178
+ this.listBox.focus();
179
+ }
180
+
181
+ setupKeybindings() {
182
+ // Quit
183
+ this.screen.key(['q', 'C-c'], () => {
184
+ if (this.pollInterval) {
185
+ clearInterval(this.pollInterval);
186
+ }
187
+ process.exit(0);
188
+ });
189
+
190
+ // Enter - view details
191
+ this.listBox.key(['enter', 'space'], () => {
192
+ if (this.tasks.length > 0) {
193
+ this.selectedTask = this.tasks[this.selectedIndex];
194
+ this.viewMode = 'detail';
195
+ this.initialScrollDone = false;
196
+ this.listBox.hide();
197
+ this.detailBox.show();
198
+ this.screen.render();
199
+ setImmediate(() => {
200
+ this.detailBox.focus();
201
+ this.render();
202
+ });
203
+ }
204
+ });
205
+
206
+ // Escape - back to list
207
+ this.screen.key(['escape'], () => {
208
+ if (this.viewMode === 'detail') {
209
+ this.viewMode = 'list';
210
+ this.detailBox.hide();
211
+ this.listBox.show();
212
+ this.screen.render();
213
+ setImmediate(() => {
214
+ this.listBox.focus();
215
+ this.render();
216
+ });
217
+ }
218
+ });
219
+
220
+ // Arrow keys for list
221
+ this.listBox.key(['up', 'k'], () => {
222
+ if (this.selectedIndex > 0) {
223
+ this.selectedIndex--;
224
+ this.listBox.select(this.selectedIndex);
225
+ this.render();
226
+ }
227
+ });
228
+
229
+ this.listBox.key(['down', 'j'], () => {
230
+ if (this.selectedIndex < this.tasks.length - 1) {
231
+ this.selectedIndex++;
232
+ this.listBox.select(this.selectedIndex);
233
+ this.render();
234
+ }
235
+ });
236
+
237
+ // Scroll in detail view
238
+ this.detailBox.key(['up', 'k'], () => {
239
+ this.detailBox.scroll(-1);
240
+ this.screen.render();
241
+ });
242
+
243
+ this.detailBox.key(['down', 'j'], () => {
244
+ this.detailBox.scroll(1);
245
+ this.screen.render();
246
+ });
247
+
248
+ this.detailBox.key(['pageup', 'u'], () => {
249
+ this.detailBox.scroll(-10);
250
+ this.screen.render();
251
+ });
252
+
253
+ this.detailBox.key(['pagedown', 'd'], () => {
254
+ this.detailBox.scroll(10);
255
+ this.screen.render();
256
+ });
257
+ }
258
+
259
+ refreshData() {
260
+ const tasks = loadTasks();
261
+ this.tasks = Object.values(tasks);
262
+
263
+ // Sort by creation date, newest first
264
+ this.tasks.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
265
+
266
+ // Verify running status
267
+ for (const task of this.tasks) {
268
+ if (task.status === 'running' && !isProcessRunning(task.pid)) {
269
+ task.status = 'stale';
270
+ }
271
+ }
272
+ }
273
+
274
+ render() {
275
+ if (this.viewMode === 'list') {
276
+ this.renderList();
277
+ } else {
278
+ this.renderDetail();
279
+ }
280
+ }
281
+
282
+ renderList() {
283
+ const items = this.tasks.map((task) => {
284
+ const statusIcon =
285
+ {
286
+ running: '{blue-fg}●{/}',
287
+ completed: '{green-fg}●{/}',
288
+ failed: '{red-fg}●{/}',
289
+ stale: '{yellow-fg}●{/}',
290
+ }[task.status] || '{gray-fg}●{/}';
291
+
292
+ const age = this.getAge(task.createdAt);
293
+ const cwd = task.cwd.replace(os.homedir(), '~');
294
+
295
+ return `${statusIcon} {cyan-fg}${task.id.padEnd(25)}{/} {gray-fg}${task.status.padEnd(10)} ${age.padEnd(10)} ${cwd}{/}`;
296
+ });
297
+
298
+ this.listBox.setItems(items);
299
+ this.listBox.setLabel(
300
+ ` Tasks (${this.tasks.length}) - ↑↓ navigate, Enter to view logs, q to quit `
301
+ );
302
+
303
+ // Update status bar
304
+ if (this.tasks.length > 0) {
305
+ const task = this.tasks[this.selectedIndex];
306
+ this.statusBar.setContent(
307
+ ` Selected: {cyan-fg}${task.id}{/} | Status: ${this.getStatusColor(task.status)} | Press Enter to view logs`
308
+ );
309
+ } else {
310
+ this.statusBar.setContent(' No tasks found');
311
+ }
312
+
313
+ this.screen.render();
314
+ }
315
+
316
+ renderDetail() {
317
+ if (!this.selectedTask) return;
318
+
319
+ const task = this.selectedTask;
320
+
321
+ // Load and parse log file
322
+ const logPath = path.join(os.homedir(), '.claude-zeroshot', 'logs', `${task.id}.log`);
323
+ let content = '';
324
+
325
+ if (fs.existsSync(logPath)) {
326
+ try {
327
+ const rawContent = fs.readFileSync(logPath, 'utf8');
328
+ const lines = rawContent.split('\n');
329
+
330
+ // Parse JSON stream and extract human-readable content
331
+ for (const line of lines) {
332
+ const parsed = parseLogLine(line);
333
+ if (parsed) content += parsed;
334
+ }
335
+
336
+ // Strip ANSI codes for clean display
337
+ // eslint-disable-next-line no-control-regex
338
+ content = content.replace(/\x1b\[[0-9;]*m/g, '');
339
+ } catch (error) {
340
+ content = `Error reading log: ${error.message}`;
341
+ }
342
+ } else {
343
+ content = 'No log file found';
344
+ }
345
+
346
+ this.detailBox.setContent(content);
347
+ this.detailBox.setLabel(` ${task.id} | ${task.status} | ↑↓ scroll, Esc back, q quit `);
348
+
349
+ // Update status bar
350
+ this.statusBar.setContent(` ${task.id} | Esc to go back`);
351
+
352
+ // Scroll to bottom only on first view
353
+ if (!this.initialScrollDone) {
354
+ this.detailBox.setScrollPerc(100);
355
+ this.initialScrollDone = true;
356
+ }
357
+
358
+ this.screen.render();
359
+ }
360
+
361
+ getAge(dateStr) {
362
+ const diff = Date.now() - new Date(dateStr).getTime();
363
+ const mins = Math.floor(diff / 60000);
364
+ const hours = Math.floor(mins / 60);
365
+ const days = Math.floor(hours / 24);
366
+
367
+ if (days > 0) return `${days}d ago`;
368
+ if (hours > 0) return `${hours}h ago`;
369
+ if (mins > 0) return `${mins}m ago`;
370
+ return 'just now';
371
+ }
372
+
373
+ getStatusColor(status) {
374
+ const colors = {
375
+ running: '{blue-fg}running{/}',
376
+ completed: '{green-fg}completed{/}',
377
+ failed: '{red-fg}failed{/}',
378
+ stale: '{yellow-fg}stale{/}',
379
+ };
380
+ return colors[status] || status;
381
+ }
382
+ }
383
+
384
+ export default TaskTUI;
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Watcher process - spawns and monitors a claude process
5
+ * Runs detached from parent, updates task status on completion
6
+ *
7
+ * Uses regular spawn (not PTY) - Claude CLI with --print is non-interactive
8
+ * PTY causes EIO errors when processes are killed/OOM'd
9
+ */
10
+
11
+ import { spawn } from 'child_process';
12
+ import { appendFileSync } from 'fs';
13
+ import { dirname } from 'path';
14
+ import { fileURLToPath } from 'url';
15
+ import { updateTask } from './store.js';
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+
19
+ const [, , taskId, cwd, logFile, argsJson, configJson] = process.argv;
20
+ const args = JSON.parse(argsJson);
21
+ const config = configJson ? JSON.parse(configJson) : {};
22
+
23
+ function log(msg) {
24
+ appendFileSync(logFile, msg);
25
+ }
26
+
27
+ // Build environment - remove API key to force subscription credentials
28
+ const env = { ...process.env };
29
+ delete env.ANTHROPIC_API_KEY;
30
+
31
+ // Add model flag - priority: config.model > ANTHROPIC_MODEL env var
32
+ const claudeArgs = [...args];
33
+ const model = config.model || env.ANTHROPIC_MODEL;
34
+ if (model && !claudeArgs.includes('--model')) {
35
+ claudeArgs.unshift('--model', model);
36
+ }
37
+
38
+ // Spawn claude using regular child_process (not PTY)
39
+ // --print mode is non-interactive, PTY adds overhead and causes EIO on OOM
40
+ const child = spawn('claude', claudeArgs, {
41
+ cwd,
42
+ env,
43
+ stdio: ['ignore', 'pipe', 'pipe'],
44
+ });
45
+
46
+ // Update task with PID
47
+ updateTask(taskId, { pid: child.pid });
48
+
49
+ // For JSON schema output with silent mode, capture ONLY the structured_output JSON
50
+ const silentJsonMode =
51
+ config.outputFormat === 'json' && config.jsonSchema && config.silentJsonOutput;
52
+ let finalResultJson = null;
53
+
54
+ // Buffer for incomplete lines (need complete lines to add timestamps)
55
+ let stdoutBuffer = '';
56
+
57
+ // Process stdout data
58
+ // CRITICAL: Prepend timestamp to each line for real-time tracking in cluster
59
+ // Format: [1733301234567]{json...} - consumers parse timestamp for accurate timing
60
+ child.stdout.on('data', (data) => {
61
+ const chunk = data.toString();
62
+ const timestamp = Date.now();
63
+
64
+ if (silentJsonMode) {
65
+ // Parse each line to find the one with structured_output
66
+ stdoutBuffer += chunk;
67
+ const lines = stdoutBuffer.split('\n');
68
+ stdoutBuffer = lines.pop() || ''; // Keep incomplete line in buffer
69
+
70
+ for (const line of lines) {
71
+ if (!line.trim()) continue;
72
+ try {
73
+ const json = JSON.parse(line);
74
+ if (json.structured_output) {
75
+ finalResultJson = line;
76
+ }
77
+ } catch {
78
+ // Not JSON or incomplete, skip
79
+ }
80
+ }
81
+ } else {
82
+ // Normal mode - stream with timestamps on each complete line
83
+ stdoutBuffer += chunk;
84
+ const lines = stdoutBuffer.split('\n');
85
+ stdoutBuffer = lines.pop() || ''; // Keep incomplete line in buffer
86
+
87
+ for (const line of lines) {
88
+ // Timestamp each line: [epochMs]originalContent
89
+ log(`[${timestamp}]${line}\n`);
90
+ }
91
+ }
92
+ });
93
+
94
+ // Buffer for stderr incomplete lines
95
+ let stderrBuffer = '';
96
+
97
+ // Stream stderr to log with timestamps
98
+ child.stderr.on('data', (data) => {
99
+ const chunk = data.toString();
100
+ const timestamp = Date.now();
101
+
102
+ stderrBuffer += chunk;
103
+ const lines = stderrBuffer.split('\n');
104
+ stderrBuffer = lines.pop() || '';
105
+
106
+ for (const line of lines) {
107
+ log(`[${timestamp}]${line}\n`);
108
+ }
109
+ });
110
+
111
+ // Handle process exit
112
+ child.on('close', (code, signal) => {
113
+ const timestamp = Date.now();
114
+
115
+ // Flush any remaining buffered stdout
116
+ if (stdoutBuffer.trim()) {
117
+ if (silentJsonMode) {
118
+ try {
119
+ const json = JSON.parse(stdoutBuffer);
120
+ if (json.structured_output) {
121
+ finalResultJson = stdoutBuffer;
122
+ }
123
+ } catch {
124
+ // Not valid JSON
125
+ }
126
+ } else {
127
+ log(`[${timestamp}]${stdoutBuffer}\n`);
128
+ }
129
+ }
130
+
131
+ // Flush any remaining buffered stderr
132
+ if (stderrBuffer.trim()) {
133
+ log(`[${timestamp}]${stderrBuffer}\n`);
134
+ }
135
+
136
+ // In silent JSON mode, log ONLY the final structured_output JSON
137
+ if (silentJsonMode && finalResultJson) {
138
+ log(finalResultJson + '\n');
139
+ }
140
+
141
+ // Skip footer for pure JSON output
142
+ if (config.outputFormat !== 'json') {
143
+ log(`\n${'='.repeat(50)}\n`);
144
+ log(`Finished: ${new Date().toISOString()}\n`);
145
+ log(`Exit code: ${code}, Signal: ${signal}\n`);
146
+ }
147
+
148
+ // Simple status: completed if exit 0, failed otherwise
149
+ const status = code === 0 ? 'completed' : 'failed';
150
+ updateTask(taskId, {
151
+ status,
152
+ exitCode: code,
153
+ error: signal ? `Killed by ${signal}` : null,
154
+ });
155
+ process.exit(0);
156
+ });
157
+
158
+ child.on('error', (err) => {
159
+ log(`\nError: ${err.message}\n`);
160
+ updateTask(taskId, { status: 'failed', error: err.message });
161
+ process.exit(1);
162
+ });
@@ -1,69 +0,0 @@
1
- {
2
- "name": "Junior Conductor Bootstrap",
3
- "description": "Cost-optimized haiku conductor for fast task classification - spawns agents directly for TRIVIAL/SIMPLE/STANDARD/CRITICAL, escalates only for UNCERTAIN tasks",
4
- "agents": [
5
- {
6
- "id": "junior-conductor",
7
- "role": "conductor",
8
- "model": "haiku",
9
- "outputFormat": "json",
10
- "jsonSchema": {
11
- "type": "object",
12
- "properties": {
13
- "classification": {
14
- "type": "string",
15
- "enum": ["TRIVIAL", "SIMPLE", "STANDARD", "CRITICAL", "UNCERTAIN"],
16
- "description": "Task complexity category"
17
- },
18
- "reasoning": {
19
- "type": "string",
20
- "description": "Why this classification (1-2 sentences)"
21
- },
22
- "operations": {
23
- "type": "array",
24
- "description": "CLUSTER_OPERATIONS to spawn agents (empty if UNCERTAIN)"
25
- }
26
- },
27
- "required": ["classification", "reasoning", "operations"]
28
- },
29
- "prompt": {
30
- "system": "You are the JUNIOR CONDUCTOR (Haiku) - fast, cost-efficient task classification.\n\n## Your Job\nClassify tasks into complexity categories and spawn appropriate agent clusters.\nYou handle 95% of tasks. Only escalate when the DECISION ITSELF is hard.\n\n## Categories\n\n**TRIVIAL** (haiku worker only):\n- Typos, docs, simple renames, config changes\n- Simple scripts (hello world, count to 100)\n- No planning needed, mechanical implementation\n- Examples: \"fix typo in README\", \"create Python script that counts to 100\"\n\n**SIMPLE** (sonnet worker + haiku validator):\n- Single-file bug fixes\n- Small feature additions\n- Basic refactoring (one function)\n- Examples: \"fix null pointer in user service\", \"add validation to form\"\n\n**STANDARD** (planner + worker + 2 validators):\n- Multi-file features\n- Cross-component changes\n- Non-trivial refactoring\n- Examples: \"add rate limiting to API\", \"implement search filtering\"\n\n**CRITICAL** (planner(opus) + worker + security + reviewer + tester):\n- Auth, payments, security\n- Data migrations\n- Production-critical changes\n- Examples: \"implement OAuth2 login\", \"add payment processing\"\n\n**UNCERTAIN** (escalate to senior conductor):\n- Ambiguous requirements (can't determine scope)\n- Unknown unknowns (\"refactor auth\" - how big?)\n- Edge cases not covered by examples\n- Contradictory requirements\n\n## Output Format\n\n```json\n{\n \"classification\": \"TRIVIAL|SIMPLE|STANDARD|CRITICAL|UNCERTAIN\",\n \"reasoning\": \"Brief explanation (1-2 sentences)\",\n \"operations\": [/* CLUSTER_OPERATIONS array or empty if UNCERTAIN */]\n}\n```\n\n## Agent Primitives - Copy these exactly\n\n### WORKER\n{\"id\":\"worker\",\"role\":\"implementation\",\"model\":\"haiku or sonnet\",\"verify\":true,\"maxIterations\":30,\"prompt\":{\"system\":\"Implement the task. On iteration 1: follow the plan or issue. On iteration 2+: fix ALL issues from validators.\"},\"contextStrategy\":{\"sources\":[{\"topic\":\"ISSUE_OPENED\",\"limit\":1},{\"topic\":\"PLAN_READY\",\"limit\":1},{\"topic\":\"VALIDATION_RESULT\",\"since\":\"last_task_end\",\"limit\":10}],\"format\":\"chronological\",\"maxTokens\":100000},\"triggers\":[{\"topic\":\"PLAN_READY\",\"action\":\"execute_task\"},{\"topic\":\"ISSUE_OPENED\",\"action\":\"execute_task\"},{\"topic\":\"VALIDATION_RESULT\",\"logic\":{\"engine\":\"javascript\",\"script\":\"const validators = cluster.getAgentsByRole('validator');\\nconst lastPush = ledger.findLast({ topic: 'IMPLEMENTATION_READY' });\\nif (!lastPush) return false;\\nconst responses = ledger.query({ topic: 'VALIDATION_RESULT', since: lastPush.timestamp });\\nif (responses.length < validators.length) return false;\\nreturn responses.some(r => r.content?.data?.approved === false || r.content?.data?.approved === 'false');\"},\"action\":\"execute_task\"},{\"topic\":\"CLUSTER_RESUMED\",\"action\":\"execute_task\"}],\"hooks\":{\"onComplete\":{\"action\":\"publish_message\",\"config\":{\"topic\":\"IMPLEMENTATION_READY\",\"content\":{\"text\":\"Implementation complete.\"}}}}}\n\n### PLANNER\n{\"id\":\"planner\",\"role\":\"planning\",\"model\":\"sonnet\",\"outputFormat\":\"json\",\"jsonSchema\":{\"type\":\"object\",\"properties\":{\"plan\":{\"type\":\"string\"},\"summary\":{\"type\":\"string\"},\"filesAffected\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"plan\",\"summary\",\"filesAffected\"]},\"prompt\":{\"system\":\"Create a clear implementation plan. List files to modify and concrete steps.\"},\"contextStrategy\":{\"sources\":[{\"topic\":\"ISSUE_OPENED\",\"limit\":1}],\"format\":\"chronological\",\"maxTokens\":100000},\"triggers\":[{\"topic\":\"ISSUE_OPENED\",\"action\":\"execute_task\"}],\"hooks\":{\"onComplete\":{\"action\":\"publish_message\",\"config\":{\"topic\":\"PLAN_READY\",\"content\":{\"text\":\"{{result.plan}}\",\"data\":{\"summary\":\"{{result.summary}}\",\"filesAffected\":\"{{result.filesAffected}}\"}}}}}} \n\n### VALIDATOR\n{\"id\":\"validator\",\"role\":\"validator\",\"model\":\"haiku or sonnet\",\"outputFormat\":\"json\",\"jsonSchema\":{\"type\":\"object\",\"properties\":{\"approved\":{\"type\":\"boolean\"},\"summary\":{\"type\":\"string\"},\"errors\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"approved\",\"summary\",\"errors\"]},\"prompt\":{\"system\":\"Verify implementation meets requirements. APPROVE if core functionality works. REJECT only for missing/broken functionality.\"},\"contextStrategy\":{\"sources\":[{\"topic\":\"ISSUE_OPENED\",\"limit\":1},{\"topic\":\"PLAN_READY\",\"limit\":1},{\"topic\":\"IMPLEMENTATION_READY\",\"limit\":1}],\"format\":\"chronological\",\"maxTokens\":50000},\"triggers\":[{\"topic\":\"IMPLEMENTATION_READY\",\"action\":\"execute_task\"}],\"hooks\":{\"onComplete\":{\"action\":\"publish_message\",\"config\":{\"topic\":\"VALIDATION_RESULT\",\"content\":{\"text\":\"{{result.summary}}\",\"data\":{\"approved\":\"{{result.approved}}\",\"errors\":\"{{result.errors}}\"}}}}}}\n\n### CODE REVIEWER\n{\"id\":\"reviewer\",\"role\":\"validator\",\"model\":\"sonnet\",\"outputFormat\":\"json\",\"jsonSchema\":{\"type\":\"object\",\"properties\":{\"approved\":{\"type\":\"boolean\"},\"summary\":{\"type\":\"string\"},\"issues\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"approved\",\"summary\",\"issues\"]},\"prompt\":{\"system\":\"Code review for security and quality. REJECT for: SQL injection, XSS, auth bypass, exposed secrets, silent error swallowing. APPROVE if code is functional and safe.\"},\"contextStrategy\":{\"sources\":[{\"topic\":\"ISSUE_OPENED\",\"limit\":1},{\"topic\":\"PLAN_READY\",\"limit\":1},{\"topic\":\"IMPLEMENTATION_READY\",\"limit\":1}],\"format\":\"chronological\",\"maxTokens\":50000},\"triggers\":[{\"topic\":\"IMPLEMENTATION_READY\",\"action\":\"execute_task\"}],\"hooks\":{\"onComplete\":{\"action\":\"publish_message\",\"config\":{\"topic\":\"VALIDATION_RESULT\",\"content\":{\"text\":\"{{result.summary}}\",\"data\":{\"approved\":\"{{result.approved}}\",\"issues\":\"{{result.issues}}\"}}}}}}\n\n### SECURITY VALIDATOR\n{\"id\":\"security-validator\",\"role\":\"validator\",\"model\":\"sonnet\",\"outputFormat\":\"json\",\"jsonSchema\":{\"type\":\"object\",\"properties\":{\"approved\":{\"type\":\"boolean\"},\"summary\":{\"type\":\"string\"},\"vulnerabilities\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"approved\",\"summary\",\"vulnerabilities\"]},\"prompt\":{\"system\":\"Deep security review. Check for: SQL injection, XSS, CSRF, auth bypass, insecure crypto, path traversal, exposed secrets. REJECT if any vulnerability found.\"},\"contextStrategy\":{\"sources\":[{\"topic\":\"ISSUE_OPENED\",\"limit\":1},{\"topic\":\"PLAN_READY\",\"limit\":1},{\"topic\":\"IMPLEMENTATION_READY\",\"limit\":1}],\"format\":\"chronological\",\"maxTokens\":50000},\"triggers\":[{\"topic\":\"IMPLEMENTATION_READY\",\"action\":\"execute_task\"}],\"hooks\":{\"onComplete\":{\"action\":\"publish_message\",\"config\":{\"topic\":\"VALIDATION_RESULT\",\"content\":{\"text\":\"{{result.summary}}\",\"data\":{\"approved\":\"{{result.approved}}\",\"vulnerabilities\":\"{{result.vulnerabilities}}\"}}}}}}\n\n### TESTER\n{\"id\":\"tester\",\"role\":\"validator\",\"model\":\"sonnet\",\"outputFormat\":\"json\",\"jsonSchema\":{\"type\":\"object\",\"properties\":{\"approved\":{\"type\":\"boolean\"},\"summary\":{\"type\":\"string\"},\"bugs\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"approved\",\"summary\",\"bugs\"]},\"prompt\":{\"system\":\"QA testing. Test basic usage and common edge cases. REJECT if core functionality broken or crashes. APPROVE if it basically works.\"},\"contextStrategy\":{\"sources\":[{\"topic\":\"ISSUE_OPENED\",\"limit\":1},{\"topic\":\"PLAN_READY\",\"limit\":1},{\"topic\":\"IMPLEMENTATION_READY\",\"limit\":1}],\"format\":\"chronological\",\"maxTokens\":50000},\"triggers\":[{\"topic\":\"IMPLEMENTATION_READY\",\"action\":\"execute_task\"}],\"hooks\":{\"onComplete\":{\"action\":\"publish_message\",\"config\":{\"topic\":\"VALIDATION_RESULT\",\"content\":{\"text\":\"{{result.summary}}\",\"data\":{\"approved\":\"{{result.approved}}\",\"bugs\":\"{{result.bugs}}\"}}}}}}\n\n### COMPLETION DETECTOR (with validators)\n{\"id\":\"completion-detector\",\"role\":\"orchestrator\",\"triggers\":[{\"topic\":\"VALIDATION_RESULT\",\"logic\":{\"engine\":\"javascript\",\"script\":\"const validators = cluster.getAgentsByRole('validator');\\nconst lastPush = ledger.findLast({ topic: 'IMPLEMENTATION_READY' });\\nif (!lastPush) return false;\\nconst responses = ledger.query({ topic: 'VALIDATION_RESULT', since: lastPush.timestamp });\\nif (responses.length < validators.length) return false;\\nreturn responses.every(r => r.content?.data?.approved === true || r.content?.data?.approved === 'true');\"},\"action\":\"stop_cluster\"}]}\n\n### SIMPLE COMPLETION DETECTOR (no validators)\n{\"id\":\"completion-detector\",\"role\":\"orchestrator\",\"triggers\":[{\"topic\":\"IMPLEMENTATION_READY\",\"action\":\"stop_cluster\"}]}\n\n## Critical Rules\n\n1. Use categorical classification - NO confidence scores\n2. Only output UNCERTAIN when decision itself is hard\n3. Include full operations array for TRIVIAL/SIMPLE/STANDARD/CRITICAL\n4. Fail fast - if task is malformed/unclear, classify as UNCERTAIN\n5. Follow exact patterns above for operations\n6. For TRIVIAL: use haiku worker + simple completion detector\n7. For SIMPLE: use sonnet worker + haiku validator + full completion detector\n8. For STANDARD: use sonnet planner + sonnet worker + sonnet validator + sonnet reviewer + full completion detector\n9. For CRITICAL: use opus planner + sonnet worker + sonnet security-validator + sonnet reviewer + sonnet tester + full completion detector\n10. ALWAYS republish ISSUE_OPENED as last operation with full task text\n\nTask: {{ISSUE_OPENED.content.text}}"
31
- },
32
- "contextStrategy": {
33
- "sources": [
34
- {
35
- "topic": "ISSUE_OPENED",
36
- "limit": 1
37
- }
38
- ],
39
- "format": "chronological",
40
- "maxTokens": 100000
41
- },
42
- "triggers": [
43
- {
44
- "topic": "ISSUE_OPENED",
45
- "logic": {
46
- "engine": "javascript",
47
- "script": "return message.sender === 'system';"
48
- },
49
- "action": "execute_task"
50
- }
51
- ],
52
- "hooks": {
53
- "onComplete": {
54
- "action": "publish_message",
55
- "config": {
56
- "topic": "JUNIOR_CLASSIFICATION",
57
- "content": {
58
- "text": "{{result.reasoning}}",
59
- "data": {
60
- "classification": "{{result.classification}}",
61
- "operations": "{{result.operations}}"
62
- }
63
- }
64
- }
65
- }
66
- }
67
- }
68
- ]
69
- }