@covibes/zeroshot 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/LICENSE +21 -0
  3. package/README.md +364 -0
  4. package/cli/index.js +3990 -0
  5. package/cluster-templates/base-templates/debug-workflow.json +181 -0
  6. package/cluster-templates/base-templates/full-workflow.json +455 -0
  7. package/cluster-templates/base-templates/single-worker.json +48 -0
  8. package/cluster-templates/base-templates/worker-validator.json +131 -0
  9. package/cluster-templates/conductor-bootstrap.json +122 -0
  10. package/cluster-templates/conductor-junior-bootstrap.json +69 -0
  11. package/docker/zeroshot-cluster/Dockerfile +132 -0
  12. package/lib/completion.js +174 -0
  13. package/lib/id-detector.js +53 -0
  14. package/lib/settings.js +97 -0
  15. package/lib/stream-json-parser.js +236 -0
  16. package/package.json +121 -0
  17. package/src/agent/agent-config.js +121 -0
  18. package/src/agent/agent-context-builder.js +241 -0
  19. package/src/agent/agent-hook-executor.js +329 -0
  20. package/src/agent/agent-lifecycle.js +555 -0
  21. package/src/agent/agent-stuck-detector.js +256 -0
  22. package/src/agent/agent-task-executor.js +1034 -0
  23. package/src/agent/agent-trigger-evaluator.js +67 -0
  24. package/src/agent-wrapper.js +459 -0
  25. package/src/agents/git-pusher-agent.json +20 -0
  26. package/src/attach/attach-client.js +438 -0
  27. package/src/attach/attach-server.js +543 -0
  28. package/src/attach/index.js +35 -0
  29. package/src/attach/protocol.js +220 -0
  30. package/src/attach/ring-buffer.js +121 -0
  31. package/src/attach/socket-discovery.js +242 -0
  32. package/src/claude-task-runner.js +468 -0
  33. package/src/config-router.js +80 -0
  34. package/src/config-validator.js +598 -0
  35. package/src/github.js +103 -0
  36. package/src/isolation-manager.js +1042 -0
  37. package/src/ledger.js +429 -0
  38. package/src/logic-engine.js +223 -0
  39. package/src/message-bus-bridge.js +139 -0
  40. package/src/message-bus.js +202 -0
  41. package/src/name-generator.js +232 -0
  42. package/src/orchestrator.js +1938 -0
  43. package/src/schemas/sub-cluster.js +156 -0
  44. package/src/sub-cluster-wrapper.js +545 -0
  45. package/src/task-runner.js +28 -0
  46. package/src/template-resolver.js +347 -0
  47. package/src/tui/CHANGES.txt +133 -0
  48. package/src/tui/LAYOUT.md +261 -0
  49. package/src/tui/README.txt +192 -0
  50. package/src/tui/TWO-LEVEL-NAVIGATION.md +186 -0
  51. package/src/tui/data-poller.js +325 -0
  52. package/src/tui/demo.js +208 -0
  53. package/src/tui/formatters.js +123 -0
  54. package/src/tui/index.js +193 -0
  55. package/src/tui/keybindings.js +383 -0
  56. package/src/tui/layout.js +317 -0
  57. package/src/tui/renderer.js +194 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * AgentTriggerEvaluator - Trigger matching and logic evaluation
3
+ *
4
+ * Provides:
5
+ * - Trigger matching based on message topics
6
+ * - Logic evaluation via LogicEngine
7
+ * - Trigger action determination
8
+ */
9
+
10
+ /**
11
+ * Find trigger matching the message topic
12
+ * @param {Object} params - Evaluation parameters
13
+ * @param {Array} params.triggers - Agent triggers configuration
14
+ * @param {Object} params.message - Message to match against
15
+ * @returns {Object|null} Matching trigger or null
16
+ */
17
+ function findMatchingTrigger({ triggers, message }) {
18
+ if (!triggers) {
19
+ return null;
20
+ }
21
+
22
+ return triggers.find((trigger) => {
23
+ // Match exact topic or wildcard
24
+ if (trigger.topic === '*' || trigger.topic === message.topic) {
25
+ return true;
26
+ }
27
+ // Match topic prefix (e.g., "VALIDATION_*")
28
+ if (trigger.topic.endsWith('*')) {
29
+ const prefix = trigger.topic.slice(0, -1);
30
+ return message.topic.startsWith(prefix);
31
+ }
32
+ return false;
33
+ });
34
+ }
35
+
36
+ /**
37
+ * Evaluate trigger logic
38
+ * @param {Object} params - Evaluation parameters
39
+ * @param {Object} params.trigger - Trigger to evaluate
40
+ * @param {Object} params.message - Triggering message
41
+ * @param {Object} params.agent - Agent context (id, role, iteration, cluster_id)
42
+ * @param {Object} params.logicEngine - LogicEngine instance
43
+ * @returns {boolean} Whether trigger logic passed
44
+ */
45
+ function evaluateTrigger({ trigger, message, agent, logicEngine }) {
46
+ if (!trigger.logic || !trigger.logic.script) {
47
+ return true; // No logic = always true
48
+ }
49
+
50
+ // NO TRY/CATCH - let errors propagate and crash
51
+ return logicEngine.evaluate(trigger.logic.script, agent, message);
52
+ }
53
+
54
+ /**
55
+ * Get trigger action type
56
+ * @param {Object} trigger - Trigger object
57
+ * @returns {string} Action type ('execute_task' or 'stop_cluster')
58
+ */
59
+ function getTriggerAction(trigger) {
60
+ return trigger.action || 'execute_task';
61
+ }
62
+
63
+ module.exports = {
64
+ findMatchingTrigger,
65
+ evaluateTrigger,
66
+ getTriggerAction,
67
+ };
@@ -0,0 +1,459 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * AgentWrapper - Manages agent lifecycle and claude-zeroshots execution
4
+ *
5
+ * Provides:
6
+ * - Agent state machine (idle -> evaluating -> building context -> executing -> idle)
7
+ * - Trigger evaluation via LogicEngine
8
+ * - Context building from ledger
9
+ * - claude-zeroshots spawning and monitoring
10
+ * - Hook execution (onStart, onComplete, onError)
11
+ */
12
+
13
+ const LogicEngine = require('./logic-engine');
14
+ const { validateAgentConfig } = require('./agent/agent-config');
15
+ const { buildContext } = require('./agent/agent-context-builder');
16
+ const { findMatchingTrigger, evaluateTrigger } = require('./agent/agent-trigger-evaluator');
17
+ const { executeHook } = require('./agent/agent-hook-executor');
18
+ const {
19
+ spawnClaudeTask,
20
+ followClaudeTaskLogs,
21
+ waitForTaskReady,
22
+ spawnClaudeTaskIsolated,
23
+ getClaudeTasksPath,
24
+ parseResultOutput,
25
+ killTask,
26
+ } = require('./agent/agent-task-executor');
27
+ const {
28
+ start: lifecycleStart,
29
+ stop: lifecycleStop,
30
+ handleMessage: lifecycleHandleMessage,
31
+ executeTriggerAction: lifecycleExecuteTriggerAction,
32
+ executeTask: lifecycleExecuteTask,
33
+ startLivenessCheck: lifecycleStartLivenessCheck,
34
+ stopLivenessCheck: lifecycleStopLivenessCheck,
35
+ } = require('./agent/agent-lifecycle');
36
+
37
+ class AgentWrapper {
38
+ /**
39
+ * @param {any} config - Agent configuration
40
+ * @param {any} messageBus - Message bus instance
41
+ * @param {any} cluster - Cluster instance
42
+ * @param {any} options - Options
43
+ */
44
+ constructor(config, messageBus, cluster, options = {}) {
45
+ // Validate and normalize configuration
46
+ const normalizedConfig = validateAgentConfig(config, options);
47
+
48
+ this.id = normalizedConfig.id;
49
+ this.role = normalizedConfig.role;
50
+ this.modelConfig = normalizedConfig.modelConfig;
51
+ this.config = normalizedConfig;
52
+ this.messageBus = messageBus;
53
+ this.cluster = cluster;
54
+ this.logicEngine = new LogicEngine(messageBus, cluster);
55
+
56
+ this.state = 'idle';
57
+ this.iteration = 0;
58
+ this.maxIterations = normalizedConfig.maxIterations;
59
+ this.timeout = normalizedConfig.timeout;
60
+ /** @type {any} */
61
+ this.currentTask = null;
62
+ /** @type {string | null} */
63
+ this.currentTaskId = null; // Track spawned task ID for resume capability
64
+ /** @type {number | null} */
65
+ this.processPid = null; // Track process PID for resource monitoring
66
+ this.running = false;
67
+ /** @type {Function | null} */
68
+ this.unsubscribe = null;
69
+ /** @type {number | null} */
70
+ this.lastTaskEndTime = null; // Track when last task completed (for context filtering)
71
+
72
+ // LIVENESS DETECTION - Track output freshness to detect stuck agents
73
+ /** @type {number | null} */
74
+ this.lastOutputTime = null; // Timestamp of last output received
75
+ /** @type {NodeJS.Timeout | null} */
76
+ this.livenessCheckInterval = null; // Interval for health checks
77
+ this.staleDuration = normalizedConfig.staleDuration;
78
+ this.enableLivenessCheck = normalizedConfig.enableLivenessCheck;
79
+
80
+ // MOCK SUPPORT - Inject mock spawn function for testing
81
+ // When set, _spawnClaudeTask uses this instead of real ct CLI
82
+ this.mockSpawnFn = options.mockSpawnFn || null;
83
+ this.testMode = options.testMode || false;
84
+ this.quiet = options.quiet || false;
85
+
86
+ // ISOLATION SUPPORT - Run tasks inside Docker container
87
+ this.isolation = options.isolation || null;
88
+ }
89
+
90
+ /**
91
+ * Log message (respects quiet mode)
92
+ * @private
93
+ */
94
+ _log(...args) {
95
+ if (!this.quiet) {
96
+ console.log(...args);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Publish a message to the message bus, always including sender_model
102
+ * @private
103
+ */
104
+ _publish(message) {
105
+ this.messageBus.publish({
106
+ ...message,
107
+ cluster_id: this.cluster.id,
108
+ sender: this.id,
109
+ sender_model: this._selectModel(),
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Publish agent lifecycle event to message bus (visible in zeroshot logs)
115
+ * @private
116
+ */
117
+ _publishLifecycle(event, details = {}) {
118
+ this._publish({
119
+ topic: 'AGENT_LIFECYCLE',
120
+ receiver: 'system',
121
+ content: {
122
+ text: `${this.id}: ${event}`,
123
+ data: {
124
+ event,
125
+ agent: this.id,
126
+ role: this.role,
127
+ state: this.state,
128
+ model: this._selectModel(),
129
+ ...details,
130
+ },
131
+ },
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Select model based on current iteration and agent config
137
+ * @returns {string} Model name ('sonnet', 'opus', 'haiku')
138
+ * @private
139
+ */
140
+ _selectModel() {
141
+ // Backward compatibility: static model
142
+ if (this.modelConfig.type === 'static') {
143
+ return this.modelConfig.model;
144
+ }
145
+
146
+ // Dynamic rules: evaluate based on iteration
147
+ for (const rule of this.modelConfig.rules) {
148
+ if (this._matchesIterationRange(rule.iterations)) {
149
+ return rule.model;
150
+ }
151
+ }
152
+
153
+ // No match: fail fast
154
+ throw new Error(
155
+ `Agent ${this.id}: No model rule matched iteration ${this.iteration}. ` +
156
+ `Add a catch-all rule like { "iterations": "all", "model": "sonnet" }`
157
+ );
158
+ }
159
+
160
+ /**
161
+ * Check if current iteration matches the range pattern
162
+ * @param {string} pattern - e.g., "1", "1-3", "5+", "all"
163
+ * @returns {boolean}
164
+ * @private
165
+ */
166
+ _matchesIterationRange(pattern) {
167
+ if (pattern === 'all') return true;
168
+
169
+ const current = this.iteration;
170
+
171
+ // Exact match: "3"
172
+ if (/^\d+$/.test(pattern)) {
173
+ return current === parseInt(pattern);
174
+ }
175
+
176
+ // Range: "1-3"
177
+ const rangeMatch = pattern.match(/^(\d+)-(\d+)$/);
178
+ if (rangeMatch) {
179
+ const [, start, end] = rangeMatch;
180
+ return current >= parseInt(start) && current <= parseInt(end);
181
+ }
182
+
183
+ // Open-ended: "5+"
184
+ const openMatch = pattern.match(/^(\d+)\+$/);
185
+ if (openMatch) {
186
+ const [, start] = openMatch;
187
+ return current >= parseInt(start);
188
+ }
189
+
190
+ throw new Error(
191
+ `Agent ${this.id}: Invalid iteration pattern '${pattern}'. ` +
192
+ `Valid formats: "1", "1-3", "5+", "all"`
193
+ );
194
+ }
195
+
196
+ /**
197
+ * Select prompt based on current iteration and agent config
198
+ * @returns {string|null} System prompt string, or null if no prompt configured
199
+ * @private
200
+ */
201
+ _selectPrompt() {
202
+ const promptConfig = this.config.promptConfig;
203
+
204
+ // No prompt configured
205
+ if (!promptConfig) {
206
+ return null;
207
+ }
208
+
209
+ // Backward compatibility: static prompt
210
+ if (promptConfig.type === 'static') {
211
+ return promptConfig.system;
212
+ }
213
+
214
+ // Dynamic rules: evaluate based on iteration
215
+ for (const rule of promptConfig.rules) {
216
+ if (this._matchesIterationRange(rule.match)) {
217
+ return rule.system;
218
+ }
219
+ }
220
+
221
+ // No match: fail fast
222
+ throw new Error(
223
+ `Agent ${this.id}: No prompt rule matched iteration ${this.iteration}. ` +
224
+ `Add a catch-all rule like { "match": "all", "system": "..." }`
225
+ );
226
+ }
227
+
228
+ /**
229
+ * Start the agent (begin listening for triggers)
230
+ */
231
+ start() {
232
+ return lifecycleStart(this);
233
+ }
234
+
235
+ /**
236
+ * Stop the agent
237
+ */
238
+ stop() {
239
+ return lifecycleStop(this);
240
+ }
241
+
242
+ /**
243
+ * Handle incoming message
244
+ * @private
245
+ */
246
+ _handleMessage(message) {
247
+ return lifecycleHandleMessage(this, message);
248
+ }
249
+
250
+ /**
251
+ * Find trigger matching the message topic
252
+ * @private
253
+ */
254
+ _findMatchingTrigger(message) {
255
+ return findMatchingTrigger({
256
+ triggers: this.config.triggers,
257
+ message,
258
+ });
259
+ }
260
+
261
+ /**
262
+ * Evaluate trigger logic
263
+ * @private
264
+ */
265
+ _evaluateTrigger(trigger, message) {
266
+ const agent = {
267
+ id: this.id,
268
+ role: this.role,
269
+ iteration: this.iteration,
270
+ cluster_id: this.cluster.id,
271
+ };
272
+
273
+ return evaluateTrigger({
274
+ trigger,
275
+ message,
276
+ agent,
277
+ logicEngine: this.logicEngine,
278
+ });
279
+ }
280
+
281
+ /**
282
+ * Execute trigger action
283
+ * @private
284
+ */
285
+ _executeTriggerAction(trigger, message) {
286
+ return lifecycleExecuteTriggerAction(this, trigger, message);
287
+ }
288
+
289
+ /**
290
+ * Execute claude-zeroshots with built context
291
+ * Retries disabled by default. Set agent config `maxRetries` to enable (e.g., 3).
292
+ * @private
293
+ */
294
+ _executeTask(triggeringMessage) {
295
+ return lifecycleExecuteTask(this, triggeringMessage);
296
+ }
297
+
298
+ /**
299
+ * Build context from ledger based on contextStrategy
300
+ * @private
301
+ */
302
+ _buildContext(triggeringMessage) {
303
+ return buildContext({
304
+ id: this.id,
305
+ role: this.role,
306
+ iteration: this.iteration,
307
+ config: this.config,
308
+ messageBus: this.messageBus,
309
+ cluster: this.cluster,
310
+ lastTaskEndTime: this.lastTaskEndTime,
311
+ triggeringMessage,
312
+ selectedPrompt: this._selectPrompt(),
313
+ });
314
+ }
315
+
316
+ /**
317
+ * Spawn claude-zeroshots process and stream output via message bus
318
+ * @private
319
+ */
320
+ _spawnClaudeTask(context) {
321
+ return spawnClaudeTask(this, context);
322
+ }
323
+
324
+ /**
325
+ * Wait for task to be registered in ct storage
326
+ * @private
327
+ */
328
+ _waitForTaskReady(taskId, maxRetries = 10, delayMs = 200) {
329
+ return waitForTaskReady(this, taskId, maxRetries, delayMs);
330
+ }
331
+
332
+ /**
333
+ * Follow claude-zeroshots logs until completion, streaming to message bus
334
+ * Reads log file directly for reliable streaming
335
+ * @private
336
+ */
337
+ _followClaudeTaskLogs(taskId) {
338
+ return followClaudeTaskLogs(this, taskId);
339
+ }
340
+
341
+ /**
342
+ * Get path to claude-zeroshots executable
343
+ * @private
344
+ */
345
+ _getClaudeTasksPath() {
346
+ return getClaudeTasksPath();
347
+ }
348
+
349
+ /**
350
+ * Spawn claude-zeroshots inside Docker container (isolation mode)
351
+ * Runs Claude CLI inside the container for full isolation
352
+ * @private
353
+ */
354
+ _spawnClaudeTaskIsolated(context) {
355
+ return spawnClaudeTaskIsolated(this, context);
356
+ }
357
+
358
+ /**
359
+ * Kill current task
360
+ * @private
361
+ */
362
+ _killTask() {
363
+ return killTask(this);
364
+ }
365
+
366
+ /**
367
+ * Execute a hook
368
+ * THROWS on failure - no silent errors
369
+ * @private
370
+ */
371
+ _executeHook(hookName, context) {
372
+ const hook = this.config.hooks?.[hookName];
373
+ return executeHook({
374
+ hook,
375
+ agent: this,
376
+ message: context.triggeringMessage,
377
+ result: context.result,
378
+ messageBus: this.messageBus,
379
+ cluster: this.cluster,
380
+ orchestrator: this.orchestrator,
381
+ });
382
+ }
383
+
384
+ /**
385
+ * Parse agent output to extract structured result data
386
+ * GENERIC - returns whatever structured output the agent provides
387
+ * Works with any agent schema (planner, validator, worker, etc.)
388
+ * @private
389
+ */
390
+ _parseResultOutput(output) {
391
+ return parseResultOutput(this, output);
392
+ }
393
+
394
+ /**
395
+ * Resume agent task with context from previous failure
396
+ * Called by Orchestrator.resume() to continue where we left off
397
+ * @param {String} resumeContext - Context describing what to resume
398
+ */
399
+ async resume(resumeContext) {
400
+ if (!this.running) {
401
+ throw new Error(`Agent ${this.id} is not running. Start it first.`);
402
+ }
403
+
404
+ if (this.state !== 'idle') {
405
+ throw new Error(`Agent ${this.id} is busy (state: ${this.state}). Wait for current task.`);
406
+ }
407
+
408
+ this._log(`[${this.id}] Resuming task...`);
409
+
410
+ // Create a synthetic triggering message for resume
411
+ const triggeringMessage = {
412
+ cluster_id: this.cluster.id,
413
+ topic: 'AGENT_RESUME',
414
+ sender: 'system',
415
+ content: {
416
+ text: resumeContext,
417
+ },
418
+ };
419
+
420
+ // Execute the task with resume context
421
+ await this._executeTask(triggeringMessage);
422
+ }
423
+
424
+ /**
425
+ * Get current agent state
426
+ */
427
+ getState() {
428
+ return {
429
+ id: this.id,
430
+ role: this.role,
431
+ model: this._selectModel(),
432
+ state: this.state,
433
+ iteration: this.iteration,
434
+ maxIterations: this.maxIterations,
435
+ currentTask: this.currentTask ? true : false,
436
+ currentTaskId: this.currentTaskId,
437
+ pid: this.processPid,
438
+ };
439
+ }
440
+
441
+ /**
442
+ * Start monitoring agent output liveness
443
+ * Detects when agent produces no output for configured staleDuration
444
+ * @private
445
+ */
446
+ _startLivenessCheck() {
447
+ return lifecycleStartLivenessCheck(this);
448
+ }
449
+
450
+ /**
451
+ * Stop liveness monitoring
452
+ * @private
453
+ */
454
+ _stopLivenessCheck() {
455
+ return lifecycleStopLivenessCheck(this);
456
+ }
457
+ }
458
+
459
+ module.exports = AgentWrapper;
@@ -0,0 +1,20 @@
1
+ {
2
+ "id": "git-pusher",
3
+ "role": "completion-detector",
4
+ "model": "sonnet",
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; const results = ledger.query({ topic: 'VALIDATION_RESULT', since: lastPush.timestamp }); if (results.length < validators.length) return false; return results.every(r => r.content?.data?.approved === 'true' || r.content?.data?.approved === true);"
11
+ },
12
+ "action": "execute_task"
13
+ }
14
+ ],
15
+ "prompt": "🚨 CRITICAL: ALL VALIDATORS APPROVED. YOU MUST CREATE A PR AND GET IT MERGED. DO NOT STOP UNTIL THE PR IS MERGED. 🚨\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⚠️ AFTER PUSH YOU ARE NOT DONE! CONTINUE TO STEP 5! ⚠️\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🚨 YOU MUST RUN `gh pr create`! Outputting a link is NOT creating a PR! 🚨\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⚠️ AFTER PR CREATION YOU ARE NOT DONE! CONTINUE TO STEP 6! ⚠️\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🚨 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
+ }