@covibes/zeroshot 5.3.0 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +94 -14
  2. package/cli/commands/providers.js +8 -9
  3. package/cli/index.js +3032 -2409
  4. package/cli/message-formatters-normal.js +28 -6
  5. package/cluster-templates/base-templates/debug-workflow.json +1 -1
  6. package/cluster-templates/base-templates/full-workflow.json +72 -188
  7. package/cluster-templates/base-templates/worker-validator.json +59 -3
  8. package/cluster-templates/conductor-bootstrap.json +4 -4
  9. package/lib/docker-config.js +8 -0
  10. package/lib/git-remote-utils.js +165 -0
  11. package/lib/id-detector.js +10 -7
  12. package/lib/provider-defaults.js +62 -0
  13. package/lib/provider-names.js +2 -1
  14. package/lib/settings/claude-auth.js +78 -0
  15. package/lib/settings.js +161 -63
  16. package/package.json +7 -2
  17. package/scripts/setup-merge-queue.sh +170 -0
  18. package/src/agent/agent-config.js +135 -82
  19. package/src/agent/agent-context-builder.js +297 -188
  20. package/src/agent/agent-hook-executor.js +310 -113
  21. package/src/agent/agent-lifecycle.js +385 -325
  22. package/src/agent/agent-stuck-detector.js +7 -7
  23. package/src/agent/agent-task-executor.js +824 -565
  24. package/src/agent/output-extraction.js +41 -24
  25. package/src/agent/output-reformatter.js +1 -1
  26. package/src/agent/schema-utils.js +108 -73
  27. package/src/agent-wrapper.js +10 -1
  28. package/src/agents/git-pusher-template.js +285 -0
  29. package/src/claude-task-runner.js +85 -34
  30. package/src/config-validator.js +922 -657
  31. package/src/input-helpers.js +65 -0
  32. package/src/isolation-manager.js +289 -199
  33. package/src/issue-providers/README.md +305 -0
  34. package/src/issue-providers/azure-devops-provider.js +273 -0
  35. package/src/issue-providers/base-provider.js +232 -0
  36. package/src/issue-providers/github-provider.js +179 -0
  37. package/src/issue-providers/gitlab-provider.js +241 -0
  38. package/src/issue-providers/index.js +196 -0
  39. package/src/issue-providers/jira-provider.js +239 -0
  40. package/src/ledger.js +22 -5
  41. package/src/lib/safe-exec.js +88 -0
  42. package/src/orchestrator.js +1107 -811
  43. package/src/preflight.js +313 -159
  44. package/src/process-metrics.js +98 -56
  45. package/src/providers/anthropic/cli-builder.js +53 -25
  46. package/src/providers/anthropic/index.js +72 -2
  47. package/src/providers/anthropic/output-parser.js +32 -14
  48. package/src/providers/base-provider.js +70 -0
  49. package/src/providers/capabilities.js +9 -0
  50. package/src/providers/google/output-parser.js +47 -38
  51. package/src/providers/index.js +19 -3
  52. package/src/providers/openai/cli-builder.js +14 -3
  53. package/src/providers/openai/index.js +1 -0
  54. package/src/providers/openai/output-parser.js +44 -30
  55. package/src/providers/opencode/cli-builder.js +42 -0
  56. package/src/providers/opencode/index.js +103 -0
  57. package/src/providers/opencode/models.js +55 -0
  58. package/src/providers/opencode/output-parser.js +122 -0
  59. package/src/schemas/sub-cluster.js +68 -39
  60. package/src/status-footer.js +94 -48
  61. package/src/sub-cluster-wrapper.js +76 -35
  62. package/src/template-resolver.js +12 -9
  63. package/src/tui/data-poller.js +123 -99
  64. package/src/tui/formatters.js +4 -3
  65. package/src/tui/keybindings.js +259 -318
  66. package/src/tui/renderer.js +11 -21
  67. package/task-lib/attachable-watcher.js +118 -81
  68. package/task-lib/claude-recovery.js +61 -24
  69. package/task-lib/commands/episodes.js +105 -0
  70. package/task-lib/commands/list.js +2 -2
  71. package/task-lib/commands/logs.js +231 -189
  72. package/task-lib/commands/schedules.js +111 -61
  73. package/task-lib/config.js +0 -2
  74. package/task-lib/runner.js +84 -27
  75. package/task-lib/scheduler.js +1 -1
  76. package/task-lib/store.js +467 -168
  77. package/task-lib/tui/formatters.js +94 -45
  78. package/task-lib/tui/renderer.js +13 -3
  79. package/task-lib/tui.js +73 -32
  80. package/task-lib/watcher.js +128 -90
  81. package/src/agents/git-pusher-agent.json +0 -20
  82. package/src/github.js +0 -139
@@ -100,6 +100,23 @@ function truncate(str, maxLen) {
100
100
  * @returns {object|null} Parsed event with type, text, toolName, error
101
101
  */
102
102
  function parseEvent(line) {
103
+ const { trimmed, timestamp } = extractTimestamp(line);
104
+
105
+ // Keep non-JSON lines as-is
106
+ if (!trimmed.startsWith('{')) {
107
+ return trimmed ? { type: 'raw', text: trimmed, timestamp } : null;
108
+ }
109
+
110
+ // Parse JSON events
111
+ const event = safeParseEvent(trimmed);
112
+ if (!event) {
113
+ return null;
114
+ }
115
+
116
+ return parseStreamEvent(event, timestamp);
117
+ }
118
+
119
+ function extractTimestamp(line) {
103
120
  let trimmed = line.trim();
104
121
 
105
122
  // Strip timestamp prefix if present: [1234567890]{...} -> {...}
@@ -110,57 +127,89 @@ function parseEvent(line) {
110
127
  trimmed = timestampMatch[2];
111
128
  }
112
129
 
113
- // Keep non-JSON lines as-is
114
- if (!trimmed.startsWith('{')) {
115
- return trimmed ? { type: 'raw', text: trimmed, timestamp } : null;
116
- }
130
+ return { trimmed, timestamp };
131
+ }
117
132
 
118
- // Parse JSON events
133
+ function safeParseEvent(trimmed) {
119
134
  try {
120
- const event = JSON.parse(trimmed);
121
-
122
- // Text delta
123
- if (event.type === 'stream_event' && event.event?.type === 'content_block_delta') {
124
- return {
125
- type: 'text',
126
- text: event.event?.delta?.text || '',
127
- timestamp,
128
- };
129
- }
130
- // Tool use
131
- else if (event.type === 'stream_event' && event.event?.type === 'content_block_start') {
132
- const block = event.event?.content_block;
133
- if (block?.type === 'tool_use' && block?.name) {
134
- return {
135
- type: 'tool',
136
- toolName: block.name,
137
- timestamp,
138
- };
139
- }
140
- }
141
- // Assistant message
142
- else if (event.type === 'assistant' && event.message?.content) {
143
- let text = '';
144
- for (const content of event.message.content) {
145
- if (content.type === 'text') {
146
- text += content.text;
147
- }
148
- }
149
- return text ? { type: 'text', text, timestamp } : null;
150
- }
151
- // Error
152
- else if (event.type === 'result' && event.is_error) {
153
- return {
154
- type: 'error',
155
- text: event.result || 'Unknown error',
156
- timestamp,
157
- };
158
- }
135
+ return JSON.parse(trimmed);
159
136
  } catch {
160
- // Parse error - skip
137
+ return null;
138
+ }
139
+ }
140
+
141
+ function parseStreamEvent(event, timestamp) {
142
+ if (event.type === 'stream_event') {
143
+ return parseStreamDelta(event, timestamp);
144
+ }
145
+
146
+ if (event.type === 'assistant') {
147
+ return parseAssistantMessage(event, timestamp);
148
+ }
149
+
150
+ if (event.type === 'result') {
151
+ return parseResultEvent(event, timestamp);
152
+ }
153
+
154
+ return null;
155
+ }
156
+
157
+ function parseStreamDelta(event, timestamp) {
158
+ const eventType = event.event?.type;
159
+
160
+ if (eventType === 'content_block_delta') {
161
+ return {
162
+ type: 'text',
163
+ text: event.event?.delta?.text || '',
164
+ timestamp,
165
+ };
166
+ }
167
+
168
+ if (eventType === 'content_block_start') {
169
+ return parseToolUseEvent(event.event?.content_block, timestamp);
170
+ }
171
+
172
+ return null;
173
+ }
174
+
175
+ function parseToolUseEvent(block, timestamp) {
176
+ if (block?.type === 'tool_use' && block?.name) {
177
+ return {
178
+ type: 'tool',
179
+ toolName: block.name,
180
+ timestamp,
181
+ };
161
182
  }
162
183
 
163
184
  return null;
164
185
  }
165
186
 
187
+ function parseAssistantMessage(event, timestamp) {
188
+ const contentBlocks = event.message?.content;
189
+ if (!Array.isArray(contentBlocks)) {
190
+ return null;
191
+ }
192
+
193
+ let text = '';
194
+ for (const content of contentBlocks) {
195
+ if (content.type === 'text') {
196
+ text += content.text;
197
+ }
198
+ }
199
+
200
+ return text ? { type: 'text', text, timestamp } : null;
201
+ }
202
+
203
+ function parseResultEvent(event, timestamp) {
204
+ if (!event.is_error) {
205
+ return null;
206
+ }
207
+
208
+ return {
209
+ type: 'error',
210
+ text: event.result || 'Unknown error',
211
+ timestamp,
212
+ };
213
+ }
214
+
166
215
  export { formatTimestamp, formatBytes, formatCPU, stateIcon, truncate, parseEvent };
@@ -23,6 +23,18 @@ class Renderer {
23
23
  this.screen = screen;
24
24
  }
25
25
 
26
+ /**
27
+ * Format task prompt for display (truncate if needed)
28
+ * @param {string|undefined} prompt - Task prompt
29
+ * @returns {string} Formatted prompt or empty string
30
+ * @private
31
+ */
32
+ _formatTaskPrompt(prompt) {
33
+ if (!prompt) return '';
34
+ const truncated = prompt.length > 80 ? prompt.substring(0, 80) + '...' : prompt;
35
+ return `{bold}Task:{/bold} {gray-fg}${truncated}{/}`;
36
+ }
37
+
26
38
  /**
27
39
  * Render task info box
28
40
  * @param {object} taskInfo - Task metadata
@@ -40,9 +52,7 @@ class Renderer {
40
52
  `${icon} {bold}Status:{/bold} {white-fg}${taskInfo.status || 'unknown'}{/}`,
41
53
  `{bold}Runtime:{/bold} {white-fg}${runtime}{/}`,
42
54
  `{bold}CPU:{/bold} {white-fg}${cpu}{/} {bold}Memory:{/bold} {white-fg}${memory}{/}`,
43
- taskInfo.prompt
44
- ? `{bold}Task:{/bold} {gray-fg}${taskInfo.prompt.substring(0, 80)}${taskInfo.prompt.length > 80 ? '...' : ''}{/}`
45
- : '',
55
+ this._formatTaskPrompt(taskInfo.prompt),
46
56
  ]
47
57
  .filter(Boolean)
48
58
  .join(' ');
package/task-lib/tui.js CHANGED
@@ -31,38 +31,80 @@ function parseLogLine(line) {
31
31
  }
32
32
 
33
33
  // Parse JSON and extract relevant info
34
+ const event = safeParseEvent(trimmed);
35
+ if (!event) {
36
+ return '';
37
+ }
38
+
39
+ return extractEventText(event);
40
+ }
41
+
42
+ function safeParseEvent(trimmed) {
34
43
  try {
35
- const event = JSON.parse(trimmed);
44
+ return JSON.parse(trimmed);
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
36
49
 
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
- }
50
+ function extractEventText(event) {
51
+ if (event.type === 'stream_event') {
52
+ return extractStreamEventText(event);
53
+ }
54
+
55
+ if (event.type === 'assistant') {
56
+ return extractAssistantText(event);
57
+ }
58
+
59
+ if (event.type === 'result') {
60
+ return extractResultText(event);
61
+ }
62
+
63
+ return '';
64
+ }
65
+
66
+ function extractStreamEventText(event) {
67
+ const eventType = event.event?.type;
68
+
69
+ if (eventType === 'content_block_delta') {
70
+ return event.event?.delta?.text || '';
71
+ }
72
+
73
+ if (eventType === 'content_block_start') {
74
+ return extractToolUseText(event.event?.content_block);
75
+ }
76
+
77
+ return '';
78
+ }
79
+
80
+ function extractToolUseText(block) {
81
+ if (block?.type === 'tool_use' && block?.name) {
82
+ return `\n[Tool: ${block.name}]\n`;
83
+ }
84
+
85
+ return '';
86
+ }
87
+
88
+ function extractAssistantText(event) {
89
+ const contentBlocks = event.message?.content;
90
+
91
+ if (!Array.isArray(contentBlocks)) {
92
+ return '';
93
+ }
94
+
95
+ let output = '';
96
+ for (const content of contentBlocks) {
97
+ if (content.type === 'text') {
98
+ output += content.text;
63
99
  }
64
- } catch {
65
- // Not JSON or parse error - skip
100
+ }
101
+
102
+ return output;
103
+ }
104
+
105
+ function extractResultText(event) {
106
+ if (event.is_error) {
107
+ return `\n[ERROR] ${event.result || 'Unknown error'}\n`;
66
108
  }
67
109
 
68
110
  return '';
@@ -260,8 +302,8 @@ class TaskTUI {
260
302
  const tasks = loadTasks();
261
303
  this.tasks = Object.values(tasks);
262
304
 
263
- // Sort by creation date, newest first
264
- this.tasks.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
305
+ // Sort by creation date, oldest first (chronological)
306
+ this.tasks.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
265
307
 
266
308
  // Verify running status
267
309
  for (const task of this.tasks) {
@@ -334,7 +376,6 @@ class TaskTUI {
334
376
  }
335
377
 
336
378
  // Strip ANSI codes for clean display
337
- // eslint-disable-next-line no-control-regex
338
379
  content = content.replace(/\x1b\[[0-9;]*m/g, '');
339
380
  } catch (error) {
340
381
  content = `Error reading log: ${error.message}`;
@@ -44,61 +44,143 @@ let finalResultJson = null;
44
44
  let streamingModeError = null;
45
45
 
46
46
  let stdoutBuffer = '';
47
+ let stderrBuffer = '';
48
+
49
+ function splitBufferLines(buffer, chunk) {
50
+ const nextBuffer = buffer + chunk;
51
+ const lines = nextBuffer.split('\n');
52
+ const remaining = lines.pop() || '';
53
+ return { lines, remaining };
54
+ }
55
+
56
+ function captureStreamingError(line, timestamp) {
57
+ if (!enableRecovery) {
58
+ return false;
59
+ }
60
+
61
+ const detectedError = detectStreamingModeError(line);
62
+ if (!detectedError) {
63
+ return false;
64
+ }
65
+
66
+ streamingModeError = { ...detectedError, timestamp };
67
+ return true;
68
+ }
69
+
70
+ function maybeCaptureStructuredOutput(line) {
71
+ try {
72
+ const json = JSON.parse(line);
73
+ if (json.structured_output) {
74
+ finalResultJson = line;
75
+ }
76
+ } catch {
77
+ // Not JSON, skip
78
+ }
79
+ }
80
+
81
+ function handleSilentJsonLines(lines, timestamp) {
82
+ for (const line of lines) {
83
+ if (!line.trim()) continue;
84
+ if (captureStreamingError(line, timestamp)) {
85
+ continue;
86
+ }
87
+ maybeCaptureStructuredOutput(line);
88
+ }
89
+ }
90
+
91
+ function handleStreamingLines(lines, timestamp) {
92
+ for (const line of lines) {
93
+ if (captureStreamingError(line, timestamp)) {
94
+ continue;
95
+ }
96
+ log(`[${timestamp}]${line}\n`);
97
+ }
98
+ }
99
+
100
+ function flushStdoutBuffer(timestamp) {
101
+ if (!stdoutBuffer.trim()) {
102
+ return;
103
+ }
104
+
105
+ if (!enableRecovery) {
106
+ if (!silentJsonMode) {
107
+ log(`[${timestamp}]${stdoutBuffer}\n`);
108
+ }
109
+ return;
110
+ }
111
+
112
+ if (captureStreamingError(stdoutBuffer, timestamp)) {
113
+ return;
114
+ }
115
+
116
+ if (silentJsonMode) {
117
+ maybeCaptureStructuredOutput(stdoutBuffer);
118
+ return;
119
+ }
120
+
121
+ log(`[${timestamp}]${stdoutBuffer}\n`);
122
+ }
123
+
124
+ function flushStderrBuffer(timestamp) {
125
+ if (stderrBuffer.trim()) {
126
+ log(`[${timestamp}]${stderrBuffer}\n`);
127
+ }
128
+ }
129
+
130
+ function attemptRecovery(code, timestamp) {
131
+ if (!(enableRecovery && code !== 0 && streamingModeError?.sessionId)) {
132
+ return null;
133
+ }
134
+
135
+ const recovered = recoverStructuredOutput(streamingModeError.sessionId);
136
+ if (recovered?.payload) {
137
+ const recoveredLine = JSON.stringify(recovered.payload);
138
+ if (silentJsonMode) {
139
+ finalResultJson = recoveredLine;
140
+ } else {
141
+ log(`[${timestamp}]${recoveredLine}\n`);
142
+ }
143
+ } else if (streamingModeError.line) {
144
+ if (silentJsonMode) {
145
+ log(streamingModeError.line + '\n');
146
+ } else {
147
+ log(`[${streamingModeError.timestamp}]${streamingModeError.line}\n`);
148
+ }
149
+ }
150
+
151
+ return recovered;
152
+ }
153
+
154
+ function writeCompletionFooter(code, signal) {
155
+ if (config.outputFormat === 'json') {
156
+ return;
157
+ }
158
+
159
+ log(`\n${'='.repeat(50)}\n`);
160
+ log(`Finished: ${new Date().toISOString()}\n`);
161
+ log(`Exit code: ${code}, Signal: ${signal}\n`);
162
+ }
47
163
 
48
164
  child.stdout.on('data', (data) => {
49
165
  const chunk = data.toString();
50
166
  const timestamp = Date.now();
51
167
 
168
+ const { lines, remaining } = splitBufferLines(stdoutBuffer, chunk);
169
+ stdoutBuffer = remaining;
170
+
52
171
  if (silentJsonMode) {
53
- stdoutBuffer += chunk;
54
- const lines = stdoutBuffer.split('\n');
55
- stdoutBuffer = lines.pop() || '';
56
-
57
- for (const line of lines) {
58
- if (!line.trim()) continue;
59
- if (enableRecovery) {
60
- const detectedError = detectStreamingModeError(line);
61
- if (detectedError) {
62
- streamingModeError = { ...detectedError, timestamp };
63
- continue;
64
- }
65
- }
66
- try {
67
- const json = JSON.parse(line);
68
- if (json.structured_output) {
69
- finalResultJson = line;
70
- }
71
- } catch {
72
- // Not JSON, skip
73
- }
74
- }
172
+ handleSilentJsonLines(lines, timestamp);
75
173
  } else {
76
- stdoutBuffer += chunk;
77
- const lines = stdoutBuffer.split('\n');
78
- stdoutBuffer = lines.pop() || '';
79
-
80
- for (const line of lines) {
81
- if (enableRecovery) {
82
- const detectedError = detectStreamingModeError(line);
83
- if (detectedError) {
84
- streamingModeError = { ...detectedError, timestamp };
85
- continue;
86
- }
87
- }
88
- log(`[${timestamp}]${line}\n`);
89
- }
174
+ handleStreamingLines(lines, timestamp);
90
175
  }
91
176
  });
92
177
 
93
- let stderrBuffer = '';
94
-
95
178
  child.stderr.on('data', (data) => {
96
179
  const chunk = data.toString();
97
180
  const timestamp = Date.now();
98
181
 
99
- stderrBuffer += chunk;
100
- const lines = stderrBuffer.split('\n');
101
- stderrBuffer = lines.pop() || '';
182
+ const { lines, remaining } = splitBufferLines(stderrBuffer, chunk);
183
+ stderrBuffer = remaining;
102
184
 
103
185
  for (const line of lines) {
104
186
  log(`[${timestamp}]${line}\n`);
@@ -108,60 +190,16 @@ child.stderr.on('data', (data) => {
108
190
  child.on('close', async (code, signal) => {
109
191
  const timestamp = Date.now();
110
192
 
111
- if (stdoutBuffer.trim()) {
112
- if (enableRecovery) {
113
- const detectedError = detectStreamingModeError(stdoutBuffer);
114
- if (detectedError) {
115
- streamingModeError = { ...detectedError, timestamp };
116
- } else if (silentJsonMode) {
117
- try {
118
- const json = JSON.parse(stdoutBuffer);
119
- if (json.structured_output) {
120
- finalResultJson = stdoutBuffer;
121
- }
122
- } catch {
123
- // Not valid JSON
124
- }
125
- } else {
126
- log(`[${timestamp}]${stdoutBuffer}\n`);
127
- }
128
- } else if (!silentJsonMode) {
129
- log(`[${timestamp}]${stdoutBuffer}\n`);
130
- }
131
- }
132
-
133
- if (stderrBuffer.trim()) {
134
- log(`[${timestamp}]${stderrBuffer}\n`);
135
- }
193
+ flushStdoutBuffer(timestamp);
194
+ flushStderrBuffer(timestamp);
136
195
 
137
- let recovered = null;
138
- if (enableRecovery && code !== 0 && streamingModeError?.sessionId) {
139
- recovered = recoverStructuredOutput(streamingModeError.sessionId);
140
- if (recovered?.payload) {
141
- const recoveredLine = JSON.stringify(recovered.payload);
142
- if (silentJsonMode) {
143
- finalResultJson = recoveredLine;
144
- } else {
145
- log(`[${timestamp}]${recoveredLine}\n`);
146
- }
147
- } else if (streamingModeError.line) {
148
- if (silentJsonMode) {
149
- log(streamingModeError.line + '\n');
150
- } else {
151
- log(`[${streamingModeError.timestamp}]${streamingModeError.line}\n`);
152
- }
153
- }
154
- }
196
+ const recovered = attemptRecovery(code, timestamp);
155
197
 
156
198
  if (silentJsonMode && finalResultJson) {
157
199
  log(finalResultJson + '\n');
158
200
  }
159
201
 
160
- if (config.outputFormat !== 'json') {
161
- log(`\n${'='.repeat(50)}\n`);
162
- log(`Finished: ${new Date().toISOString()}\n`);
163
- log(`Exit code: ${code}, Signal: ${signal}\n`);
164
- }
202
+ writeCompletionFooter(code, signal);
165
203
 
166
204
  const resolvedCode = recovered?.payload ? 0 : code;
167
205
  const status = resolvedCode === 0 ? 'completed' : 'failed';
@@ -169,7 +207,7 @@ child.on('close', async (code, signal) => {
169
207
  await updateTask(taskId, {
170
208
  status,
171
209
  exitCode: resolvedCode,
172
- error: resolvedCode === 0 ? null : signal ? `Killed by ${signal}` : null,
210
+ error: resolvedCode !== 0 && signal ? `Killed by ${signal}` : null,
173
211
  });
174
212
  } catch (updateError) {
175
213
  log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
@@ -1,20 +0,0 @@
1
- {
2
- "id": "git-pusher",
3
- "role": "completion-detector",
4
- "modelLevel": "level2",
5
- "triggers": [
6
- {
7
- "topic": "VALIDATION_RESULT",
8
- "logic": {
9
- "engine": "javascript",
10
- "script": "const validators = cluster.getAgentsByRole('validator'); const lastPush = ledger.findLast({ topic: 'IMPLEMENTATION_READY' }); if (!lastPush) return false; if (validators.length === 0) return true; const results = ledger.query({ topic: 'VALIDATION_RESULT', since: lastPush.timestamp }); if (results.length < validators.length) return false; const allApproved = results.every(r => r.content?.data?.approved === 'true' || r.content?.data?.approved === true); if (!allApproved) return false; const hasRealEvidence = results.every(r => { const criteria = r.content?.data?.criteriaResults || []; return criteria.every(c => { return c.evidence?.command && typeof c.evidence?.exitCode === 'number' && c.evidence?.output?.length > 10; }); }); return hasRealEvidence;"
11
- },
12
- "action": "execute_task"
13
- }
14
- ],
15
- "prompt": "\ud83d\udea8 CRITICAL: ALL VALIDATORS APPROVED. YOU MUST CREATE A PR AND GET IT MERGED. DO NOT STOP UNTIL THE PR IS MERGED. \ud83d\udea8\n\n## MANDATORY STEPS - EXECUTE EACH ONE IN ORDER - DO NOT SKIP ANY STEP\n\n### STEP 1: Stage ALL changes (MANDATORY)\n```bash\ngit add -A\n```\nRun this command. Do not skip it.\n\n### STEP 2: Check what's staged\n```bash\ngit status\n```\nRun this. If nothing to commit, output JSON with pr_url: null and stop.\n\n### STEP 3: Commit the changes (MANDATORY if there are changes)\n```bash\ngit commit -m \"feat: implement #{{issue_number}} - {{issue_title}}\"\n```\nRun this command. Do not skip it.\n\n### STEP 4: Push to origin (MANDATORY)\n```bash\ngit push -u origin HEAD\n```\nRun this. If it fails, check the error and fix it.\n\n\u26a0\ufe0f AFTER PUSH YOU ARE NOT DONE! CONTINUE TO STEP 5! \u26a0\ufe0f\n\n### STEP 5: CREATE THE PR (MANDATORY - YOU MUST RUN THIS COMMAND)\n```bash\ngh pr create --title \"feat: {{issue_title}}\" --body \"Closes #{{issue_number}}\"\n```\n\ud83d\udea8 YOU MUST RUN `gh pr create`! Outputting a link is NOT creating a PR! \ud83d\udea8\nThe push output shows a \"Create a pull request\" link - IGNORE IT.\nYou MUST run the `gh pr create` command above. Save the actual PR URL from the output.\n\n\u26a0\ufe0f AFTER PR CREATION YOU ARE NOT DONE! CONTINUE TO STEP 6! \u26a0\ufe0f\n\n### STEP 6: MERGE THE PR (MANDATORY - THIS IS NOT OPTIONAL)\n```bash\ngh pr merge --merge --auto\n```\nThis sets auto-merge. If it fails (e.g., no auto-merge enabled), try:\n```bash\ngh pr merge --merge\n```\n\n\ud83d\udea8 IF MERGE FAILS DUE TO CONFLICTS - YOU MUST RESOLVE THEM:\na) Pull latest main and rebase:\n ```bash\n git fetch origin main\n git rebase origin/main\n ```\nb) If conflicts appear - RESOLVE THEM IMMEDIATELY:\n - Read the conflicting files\n - Make intelligent decisions about what code to keep\n - Edit the files to resolve conflicts\n - `git add <resolved-files>`\n - `git rebase --continue`\nc) Force push the resolved branch:\n ```bash\n git push --force-with-lease\n ```\nd) Retry merge:\n ```bash\n gh pr merge --merge\n ```\n\nREPEAT UNTIL MERGED. DO NOT GIVE UP. DO NOT SKIP. THE PR MUST BE MERGED.\nIf merge is blocked by CI, wait and retry. If blocked by reviews, set auto-merge.\n\n## CRITICAL RULES\n- Execute EVERY step in order (1, 2, 3, 4, 5, 6)\n- Do NOT skip git add -A\n- Do NOT skip git commit\n- Do NOT skip gh pr create - THE TASK IS NOT DONE UNTIL PR EXISTS\n- Do NOT skip gh pr merge - THE TASK IS NOT DONE UNTIL PR IS MERGED\n- If push fails, debug and fix it\n- If PR creation fails, debug and fix it\n- If merge fails, debug and fix it\n- DO NOT OUTPUT JSON UNTIL PR IS ACTUALLY MERGED\n- A link from git push is NOT a PR - you must run gh pr create\n\n## Final Output\nONLY after the PR is MERGED, output:\n```json\n{\"pr_url\": \"https://github.com/owner/repo/pull/123\", \"pr_number\": 123, \"merged\": true}\n```\n\nIf truly no changes exist, output:\n```json\n{\"pr_url\": null, \"pr_number\": null, \"merged\": false}\n```",
16
- "output": {
17
- "topic": "PR_CREATED",
18
- "publishAfter": "CLUSTER_COMPLETE"
19
- }
20
- }