@covibes/zeroshot 5.2.1 → 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 (98) hide show
  1. package/CHANGELOG.md +174 -189
  2. package/README.md +226 -195
  3. package/cli/commands/providers.js +149 -0
  4. package/cli/index.js +3145 -2366
  5. package/cli/lib/first-run.js +40 -3
  6. package/cli/message-formatters-normal.js +28 -6
  7. package/cluster-templates/base-templates/debug-workflow.json +24 -78
  8. package/cluster-templates/base-templates/full-workflow.json +99 -316
  9. package/cluster-templates/base-templates/single-worker.json +23 -15
  10. package/cluster-templates/base-templates/worker-validator.json +105 -36
  11. package/cluster-templates/conductor-bootstrap.json +9 -7
  12. package/lib/docker-config.js +14 -1
  13. package/lib/git-remote-utils.js +165 -0
  14. package/lib/id-detector.js +10 -7
  15. package/lib/provider-defaults.js +62 -0
  16. package/lib/provider-detection.js +59 -0
  17. package/lib/provider-names.js +57 -0
  18. package/lib/settings/claude-auth.js +78 -0
  19. package/lib/settings.js +298 -15
  20. package/lib/stream-json-parser.js +4 -238
  21. package/package.json +27 -6
  22. package/scripts/setup-merge-queue.sh +170 -0
  23. package/scripts/validate-templates.js +100 -0
  24. package/src/agent/agent-config.js +140 -63
  25. package/src/agent/agent-context-builder.js +336 -165
  26. package/src/agent/agent-hook-executor.js +337 -67
  27. package/src/agent/agent-lifecycle.js +386 -287
  28. package/src/agent/agent-stuck-detector.js +7 -7
  29. package/src/agent/agent-task-executor.js +944 -683
  30. package/src/agent/output-extraction.js +217 -0
  31. package/src/agent/output-reformatter.js +175 -0
  32. package/src/agent/schema-utils.js +146 -0
  33. package/src/agent-wrapper.js +112 -31
  34. package/src/agents/git-pusher-template.js +285 -0
  35. package/src/claude-task-runner.js +145 -44
  36. package/src/config-router.js +13 -13
  37. package/src/config-validator.js +1049 -563
  38. package/src/input-helpers.js +65 -0
  39. package/src/isolation-manager.js +499 -320
  40. package/src/issue-providers/README.md +305 -0
  41. package/src/issue-providers/azure-devops-provider.js +273 -0
  42. package/src/issue-providers/base-provider.js +232 -0
  43. package/src/issue-providers/github-provider.js +179 -0
  44. package/src/issue-providers/gitlab-provider.js +241 -0
  45. package/src/issue-providers/index.js +196 -0
  46. package/src/issue-providers/jira-provider.js +239 -0
  47. package/src/ledger.js +50 -11
  48. package/src/lib/safe-exec.js +88 -0
  49. package/src/orchestrator.js +1348 -757
  50. package/src/preflight.js +306 -149
  51. package/src/process-metrics.js +98 -56
  52. package/src/providers/anthropic/cli-builder.js +73 -0
  53. package/src/providers/anthropic/index.js +204 -0
  54. package/src/providers/anthropic/models.js +23 -0
  55. package/src/providers/anthropic/output-parser.js +177 -0
  56. package/src/providers/base-provider.js +251 -0
  57. package/src/providers/capabilities.js +60 -0
  58. package/src/providers/google/cli-builder.js +55 -0
  59. package/src/providers/google/index.js +116 -0
  60. package/src/providers/google/models.js +24 -0
  61. package/src/providers/google/output-parser.js +101 -0
  62. package/src/providers/index.js +91 -0
  63. package/src/providers/openai/cli-builder.js +133 -0
  64. package/src/providers/openai/index.js +136 -0
  65. package/src/providers/openai/models.js +21 -0
  66. package/src/providers/openai/output-parser.js +143 -0
  67. package/src/providers/opencode/cli-builder.js +42 -0
  68. package/src/providers/opencode/index.js +103 -0
  69. package/src/providers/opencode/models.js +55 -0
  70. package/src/providers/opencode/output-parser.js +122 -0
  71. package/src/schemas/sub-cluster.js +68 -39
  72. package/src/status-footer.js +94 -48
  73. package/src/sub-cluster-wrapper.js +92 -36
  74. package/src/task-runner.js +8 -6
  75. package/src/template-resolver.js +12 -9
  76. package/src/tui/data-poller.js +123 -99
  77. package/src/tui/formatters.js +4 -3
  78. package/src/tui/keybindings.js +259 -318
  79. package/src/tui/layout.js +20 -3
  80. package/src/tui/renderer.js +11 -21
  81. package/task-lib/attachable-watcher.js +150 -111
  82. package/task-lib/claude-recovery.js +156 -0
  83. package/task-lib/commands/episodes.js +105 -0
  84. package/task-lib/commands/list.js +3 -3
  85. package/task-lib/commands/logs.js +231 -189
  86. package/task-lib/commands/resume.js +3 -2
  87. package/task-lib/commands/run.js +12 -3
  88. package/task-lib/commands/schedules.js +111 -61
  89. package/task-lib/config.js +0 -2
  90. package/task-lib/runner.js +128 -50
  91. package/task-lib/scheduler.js +3 -3
  92. package/task-lib/store.js +464 -152
  93. package/task-lib/tui/formatters.js +94 -45
  94. package/task-lib/tui/renderer.js +13 -3
  95. package/task-lib/tui.js +73 -32
  96. package/task-lib/watcher.js +157 -100
  97. package/src/agents/git-pusher-agent.json +0 -20
  98. package/src/github.js +0 -103
@@ -13,6 +13,8 @@
13
13
  const LogicEngine = require('./logic-engine');
14
14
  const { validateAgentConfig } = require('./agent/agent-config');
15
15
  const { loadSettings, validateModelAgainstMax } = require('../lib/settings');
16
+ const { normalizeProviderName } = require('../lib/provider-names');
17
+ const { getProvider } = require('./providers');
16
18
  const { buildContext } = require('./agent/agent-context-builder');
17
19
  const { findMatchingTrigger, evaluateTrigger } = require('./agent/agent-trigger-evaluator');
18
20
  const { executeHook } = require('./agent/agent-hook-executor');
@@ -69,6 +71,8 @@ class AgentWrapper {
69
71
  this.unsubscribe = null;
70
72
  /** @type {number | null} */
71
73
  this.lastTaskEndTime = null; // Track when last task completed (for context filtering)
74
+ /** @type {number | null} */
75
+ this.lastAgentStartTime = null; // Track when agent last began executing (for context filtering)
72
76
 
73
77
  // LIVENESS DETECTION - Track output freshness to detect stuck agents
74
78
  /** @type {number | null} */
@@ -87,9 +91,12 @@ class AgentWrapper {
87
91
  // TaskRunner DI - create mockSpawnFn wrapper
88
92
  const taskRunner = options.taskRunner;
89
93
  this.mockSpawnFn = (args, { context }) => {
94
+ const spec = this._resolveModelSpec();
90
95
  return taskRunner.run(context, {
91
96
  agentId: this.id,
92
97
  model: this._selectModel(),
98
+ modelSpec: spec,
99
+ provider: this._resolveProvider(),
93
100
  });
94
101
  };
95
102
  } else {
@@ -116,6 +123,90 @@ class AgentWrapper {
116
123
  }
117
124
  }
118
125
 
126
+ _resolveProvider() {
127
+ const settings = loadSettings();
128
+ const clusterConfig = this.cluster?.config || {};
129
+
130
+ const resolved =
131
+ clusterConfig.forceProvider ||
132
+ this.config.provider ||
133
+ clusterConfig.defaultProvider ||
134
+ settings.defaultProvider ||
135
+ 'claude';
136
+
137
+ return normalizeProviderName(resolved) || 'claude';
138
+ }
139
+
140
+ _resolveModelSpec() {
141
+ const settings = loadSettings();
142
+ const providerName = this._resolveProvider();
143
+ const provider = getProvider(providerName);
144
+ const clusterConfig = this.cluster?.config || {};
145
+ const providerSettings = settings.providerSettings?.[providerName] || {};
146
+ const levelOverrides = providerSettings.levelOverrides || {};
147
+ const minLevel = providerSettings.minLevel;
148
+ const maxLevel = providerSettings.maxLevel;
149
+ const forcedLevel =
150
+ clusterConfig.forceProvider === providerName ? clusterConfig.forceLevel : null;
151
+
152
+ const applyReasoningOverride = (spec, override) => {
153
+ if (!override) return spec;
154
+ return { ...spec, reasoningEffort: override };
155
+ };
156
+
157
+ if (this.modelConfig.type === 'rules') {
158
+ for (const rule of this.modelConfig.rules) {
159
+ if (this._matchesIterationRange(rule.iterations)) {
160
+ if (rule.model) {
161
+ return {
162
+ level: 'custom',
163
+ model: rule.model,
164
+ reasoningEffort: rule.reasoningEffort || this.config.reasoningEffort,
165
+ };
166
+ }
167
+ if (rule.modelLevel) {
168
+ const level = provider.validateLevel(rule.modelLevel, minLevel, maxLevel);
169
+ const spec = provider.resolveModelSpec(level, levelOverrides);
170
+ return applyReasoningOverride(
171
+ { ...spec, level },
172
+ rule.reasoningEffort || this.config.reasoningEffort
173
+ );
174
+ }
175
+ }
176
+ }
177
+
178
+ throw new Error(
179
+ `Agent ${this.id}: No model rule matched iteration ${this.iteration}. ` +
180
+ `Add a catch-all rule like { "iterations": "all", "modelLevel": "level2" }`
181
+ );
182
+ }
183
+
184
+ if (this.modelConfig.model) {
185
+ return {
186
+ level: 'custom',
187
+ model: this.modelConfig.model,
188
+ reasoningEffort: this.config.reasoningEffort,
189
+ };
190
+ }
191
+
192
+ if (forcedLevel) {
193
+ const level = provider.validateLevel(forcedLevel, minLevel, maxLevel);
194
+ const spec = provider.resolveModelSpec(level, levelOverrides);
195
+ return applyReasoningOverride({ ...spec, level }, this.config.reasoningEffort);
196
+ }
197
+
198
+ if (this.modelConfig.modelLevel) {
199
+ const level = provider.validateLevel(this.modelConfig.modelLevel, minLevel, maxLevel);
200
+ const spec = provider.resolveModelSpec(level, levelOverrides);
201
+ return applyReasoningOverride({ ...spec, level }, this.config.reasoningEffort);
202
+ }
203
+
204
+ const defaultLevel = providerSettings.defaultLevel || provider.getDefaultLevel();
205
+ const level = provider.validateLevel(defaultLevel, minLevel, maxLevel);
206
+ const spec = provider.resolveModelSpec(level, levelOverrides);
207
+ return applyReasoningOverride({ ...spec, level }, this.config.reasoningEffort);
208
+ }
209
+
119
210
  /**
120
211
  * Publish a message to the message bus, always including sender_model
121
212
  * @private
@@ -126,6 +217,7 @@ class AgentWrapper {
126
217
  cluster_id: this.cluster.id,
127
218
  sender: this.id,
128
219
  sender_model: this._selectModel(),
220
+ sender_provider: this._resolveProvider(),
129
221
  });
130
222
  }
131
223
 
@@ -145,6 +237,7 @@ class AgentWrapper {
145
237
  role: this.role,
146
238
  state: this.state,
147
239
  model: this._selectModel(),
240
+ provider: this._resolveProvider(),
148
241
  ...details,
149
242
  },
150
243
  },
@@ -153,44 +246,21 @@ class AgentWrapper {
153
246
 
154
247
  /**
155
248
  * Select model based on current iteration and agent config
156
- * Enforces maxModel ceiling from settings
157
- * @returns {string} Model name ('sonnet', 'opus', 'haiku')
249
+ * Enforces legacy maxModel/minModel for Claude's haiku/sonnet/opus
250
+ * @returns {string|null}
158
251
  * @private
159
252
  */
160
253
  _selectModel() {
254
+ const spec = this._resolveModelSpec();
161
255
  const settings = loadSettings();
162
256
  const maxModel = settings.maxModel || 'sonnet';
257
+ const minModel = settings.minModel || null;
163
258
 
164
- let requestedModel = null;
165
-
166
- // Get requested model from config
167
- if (this.modelConfig.type === 'static') {
168
- requestedModel = this.modelConfig.model;
169
- } else if (this.modelConfig.type === 'rules') {
170
- // Dynamic rules: evaluate based on iteration
171
- for (const rule of this.modelConfig.rules) {
172
- if (this._matchesIterationRange(rule.iterations)) {
173
- requestedModel = rule.model;
174
- break;
175
- }
176
- }
177
-
178
- // No match for rules: fail fast (config error)
179
- if (!requestedModel) {
180
- throw new Error(
181
- `Agent ${this.id}: No model rule matched iteration ${this.iteration}. ` +
182
- `Add a catch-all rule like { "iterations": "all", "model": "sonnet" }`
183
- );
184
- }
259
+ if (spec.model && ['opus', 'sonnet', 'haiku'].includes(spec.model)) {
260
+ return validateModelAgainstMax(spec.model, maxModel, minModel);
185
261
  }
186
262
 
187
- // If no model specified (neither static nor rules), use maxModel as default
188
- if (!requestedModel) {
189
- return maxModel;
190
- }
191
-
192
- // Enforce ceiling - will throw if requestedModel > maxModel
193
- return validateModelAgainstMax(requestedModel, maxModel);
263
+ return spec.model || null;
194
264
  }
195
265
 
196
266
  /**
@@ -336,7 +406,8 @@ class AgentWrapper {
336
406
  * @private
337
407
  */
338
408
  _buildContext(triggeringMessage) {
339
- return buildContext({
409
+ const previousAgentStart = this.lastAgentStartTime;
410
+ const context = buildContext({
340
411
  id: this.id,
341
412
  role: this.role,
342
413
  iteration: this.iteration,
@@ -344,12 +415,18 @@ class AgentWrapper {
344
415
  messageBus: this.messageBus,
345
416
  cluster: this.cluster,
346
417
  lastTaskEndTime: this.lastTaskEndTime,
418
+ lastAgentStartTime: previousAgentStart,
347
419
  triggeringMessage,
348
420
  selectedPrompt: this._selectPrompt(),
349
421
  // Pass isolation state for conditional git restriction
350
422
  worktree: this.worktree,
351
423
  isolation: this.isolation,
352
424
  });
425
+
426
+ // Record when this iteration started so future "since: last_agent_start" filters work
427
+ this.lastAgentStartTime = Date.now();
428
+
429
+ return context;
353
430
  }
354
431
 
355
432
  /**
@@ -424,6 +501,7 @@ class AgentWrapper {
424
501
  * Parse agent output to extract structured result data
425
502
  * GENERIC - returns whatever structured output the agent provides
426
503
  * Works with any agent schema (planner, validator, worker, etc.)
504
+ * Falls back to reformatting if extraction fails
427
505
  * @private
428
506
  */
429
507
  _parseResultOutput(output) {
@@ -464,10 +542,13 @@ class AgentWrapper {
464
542
  * Get current agent state
465
543
  */
466
544
  getState() {
545
+ const modelSpec = this._resolveModelSpec();
467
546
  return {
468
547
  id: this.id,
469
548
  role: this.role,
470
549
  model: this._selectModel(),
550
+ provider: this._resolveProvider(),
551
+ modelSpec,
471
552
  state: this.state,
472
553
  iteration: this.iteration,
473
554
  maxIterations: this.maxIterations,
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Git Pusher Agent Template
3
+ *
4
+ * Generates platform-specific git-pusher agent configurations.
5
+ * Eliminates duplication across github/gitlab/azure JSON files.
6
+ *
7
+ * Single source of truth for:
8
+ * - Trigger logic (validation consensus detection)
9
+ * - Agent structure (id, role, modelLevel, output)
10
+ * - Prompt template with platform-specific commands
11
+ */
12
+
13
+ /**
14
+ * Shared trigger logic for detecting when all validators have approved.
15
+ * This is the SINGLE source of truth - no more duplicating across 3 JSON files.
16
+ */
17
+ const SHARED_TRIGGER_SCRIPT = `const validators = cluster.getAgentsByRole('validator');
18
+ const lastPush = ledger.findLast({ topic: 'IMPLEMENTATION_READY' });
19
+ if (!lastPush) return false;
20
+ if (validators.length === 0) return true;
21
+ const results = ledger.query({ topic: 'VALIDATION_RESULT', since: lastPush.timestamp });
22
+ if (results.length < validators.length) return false;
23
+ const allApproved = results.every(r => r.content?.data?.approved === 'true' || r.content?.data?.approved === true);
24
+ if (!allApproved) return false;
25
+ const hasRealEvidence = results.every(r => {
26
+ const criteria = r.content?.data?.criteriaResults || [];
27
+ return criteria.every(c => {
28
+ return c.evidence?.command && typeof c.evidence?.exitCode === 'number' && c.evidence?.output?.length > 10;
29
+ });
30
+ });
31
+ return hasRealEvidence;`;
32
+
33
+ /**
34
+ * Platform-specific CLI commands and terminology
35
+ */
36
+ const PLATFORM_CONFIGS = {
37
+ github: {
38
+ prName: 'PR',
39
+ prNameLower: 'pull request',
40
+ createCmd: 'gh pr create --title "feat: {{issue_title}}" --body "Closes #{{issue_number}}"',
41
+ mergeCmd: 'gh pr merge --merge --auto',
42
+ mergeFallbackCmd: 'gh pr merge --merge',
43
+ prUrlExample: 'https://github.com/owner/repo/pull/123',
44
+ outputFields: { urlField: 'pr_url', numberField: 'pr_number', mergedField: 'merged' },
45
+ },
46
+ gitlab: {
47
+ prName: 'MR',
48
+ prNameLower: 'merge request',
49
+ createCmd:
50
+ 'glab mr create --title "feat: {{issue_title}}" --description "Closes #{{issue_number}}"',
51
+ mergeCmd: 'glab mr merge --auto-merge',
52
+ mergeFallbackCmd: 'glab mr merge',
53
+ prUrlExample: 'https://gitlab.com/owner/repo/-/merge_requests/123',
54
+ outputFields: { urlField: 'mr_url', numberField: 'mr_number', mergedField: 'merged' },
55
+ },
56
+ 'azure-devops': {
57
+ prName: 'PR',
58
+ prNameLower: 'pull request',
59
+ createCmd:
60
+ 'az repos pr create --title "feat: {{issue_title}}" --description "Closes #{{issue_number}}"',
61
+ mergeCmd: 'az repos pr update --id <PR_ID> --auto-complete true',
62
+ mergeFallbackCmd: 'az repos pr update --id <PR_ID> --status completed',
63
+ prUrlExample: 'https://dev.azure.com/org/project/_git/repo/pullrequest/123',
64
+ outputFields: {
65
+ urlField: 'pr_url',
66
+ numberField: 'pr_number',
67
+ mergedField: 'merged',
68
+ autoCompleteField: 'auto_complete',
69
+ },
70
+ // Azure requires extracting PR ID from create output
71
+ requiresPrIdExtraction: true,
72
+ },
73
+ };
74
+
75
+ /**
76
+ * Generate the prompt for a specific platform
77
+ * @param {Object} config - Platform configuration from PLATFORM_CONFIGS
78
+ * @returns {string} The complete prompt with platform-specific commands
79
+ */
80
+ function generatePrompt(config) {
81
+ const {
82
+ prName,
83
+ prNameLower,
84
+ createCmd,
85
+ mergeCmd,
86
+ mergeFallbackCmd,
87
+ prUrlExample,
88
+ outputFields,
89
+ requiresPrIdExtraction,
90
+ } = config;
91
+
92
+ // Azure-specific instructions for PR ID extraction
93
+ const azurePrIdNote = requiresPrIdExtraction
94
+ ? `\n\n💡 IMPORTANT: The output will contain the PR ID. You MUST extract it for the next step.
95
+ Look for output like: "Created PR 123" or parse the URL for the PR number.
96
+ Save the PR ID to a variable for step 6.`
97
+ : '';
98
+
99
+ // Azure uses different merge terminology
100
+ const mergeDescription = requiresPrIdExtraction
101
+ ? 'SET AUTO-COMPLETE (MANDATORY - THIS IS NOT OPTIONAL)'
102
+ : `MERGE THE ${prName} (MANDATORY - THIS IS NOT OPTIONAL)`;
103
+
104
+ const mergeExplanation = requiresPrIdExtraction
105
+ ? `Replace <PR_ID> with the actual PR number from step 5.
106
+ This enables auto-complete (auto-merge when CI passes).
107
+
108
+ If auto-complete is not available or you need to merge immediately:`
109
+ : `This sets auto-merge. If it fails (e.g., no auto-merge enabled), try:`;
110
+
111
+ const postMergeStatus = requiresPrIdExtraction
112
+ ? 'PR IS CREATED AND AUTO-COMPLETE IS SET'
113
+ : `${prName} IS MERGED`;
114
+
115
+ const finalOutputNote = requiresPrIdExtraction
116
+ ? `ONLY after the PR is created and auto-complete is set, output:
117
+ \`\`\`json
118
+ {"${outputFields.urlField}": "${prUrlExample}", "${outputFields.numberField}": 123, "merged": false, "auto_complete": true}
119
+ \`\`\`
120
+
121
+ If truly no changes exist, output:
122
+ \`\`\`json
123
+ {"${outputFields.urlField}": null, "${outputFields.numberField}": null, "merged": false, "auto_complete": false}
124
+ \`\`\``
125
+ : `ONLY after the ${prName} is MERGED, output:
126
+ \`\`\`json
127
+ {"${outputFields.urlField}": "${prUrlExample}", "${outputFields.numberField}": 123, "merged": true}
128
+ \`\`\`
129
+
130
+ If truly no changes exist, output:
131
+ \`\`\`json
132
+ {"${outputFields.urlField}": null, "${outputFields.numberField}": null, "merged": false}
133
+ \`\`\``;
134
+
135
+ return `🚨 CRITICAL: ALL VALIDATORS APPROVED. YOU MUST CREATE A ${prName} AND GET IT MERGED. DO NOT STOP UNTIL THE ${prName} IS MERGED. 🚨
136
+
137
+ ## MANDATORY STEPS - EXECUTE EACH ONE IN ORDER - DO NOT SKIP ANY STEP
138
+
139
+ ### STEP 1: Stage ALL changes (MANDATORY)
140
+ \`\`\`bash
141
+ git add -A
142
+ \`\`\`
143
+ Run this command. Do not skip it.
144
+
145
+ ### STEP 2: Check what's staged
146
+ \`\`\`bash
147
+ git status
148
+ \`\`\`
149
+ Run this. If nothing to commit, output JSON with ${outputFields.urlField}: null and stop.
150
+
151
+ ### STEP 3: Commit the changes (MANDATORY if there are changes)
152
+ \`\`\`bash
153
+ git commit -m "feat: implement #{{issue_number}} - {{issue_title}}"
154
+ \`\`\`
155
+ Run this command. Do not skip it.
156
+
157
+ ### STEP 4: Push to origin (MANDATORY)
158
+ \`\`\`bash
159
+ git push -u origin HEAD
160
+ \`\`\`
161
+ Run this. If it fails, check the error and fix it.
162
+
163
+ ⚠️ AFTER PUSH YOU ARE NOT DONE! CONTINUE TO STEP 5! ⚠️
164
+
165
+ ### STEP 5: CREATE THE ${prName.toUpperCase()} (MANDATORY - YOU MUST RUN THIS COMMAND)
166
+ \`\`\`bash
167
+ ${createCmd}
168
+ \`\`\`
169
+ 🚨 YOU MUST RUN \`${createCmd.split(' ').slice(0, 3).join(' ')}\`! Outputting a link is NOT creating a ${prName}! 🚨
170
+ The push output shows a "Create a ${prNameLower}" link - IGNORE IT.
171
+ You MUST run the \`${createCmd.split(' ').slice(0, 3).join(' ')}\` command above.${requiresPrIdExtraction ? '' : ` Save the actual ${prName} URL from the output.`}${azurePrIdNote}
172
+
173
+ ⚠️ AFTER ${prName} CREATION YOU ARE NOT DONE! CONTINUE TO STEP 6! ⚠️
174
+
175
+ ### STEP 6: ${mergeDescription}
176
+ \`\`\`bash
177
+ ${mergeCmd}
178
+ \`\`\`
179
+ ${mergeExplanation}
180
+ \`\`\`bash
181
+ ${mergeFallbackCmd}
182
+ \`\`\`
183
+
184
+ 🚨 IF MERGE FAILS DUE TO CONFLICTS - YOU MUST RESOLVE THEM:
185
+ a) Pull latest main and rebase:
186
+ \`\`\`bash
187
+ git fetch origin main
188
+ git rebase origin/main
189
+ \`\`\`
190
+ b) If conflicts appear - RESOLVE THEM IMMEDIATELY:
191
+ - Read the conflicting files
192
+ - Make intelligent decisions about what code to keep
193
+ - Edit the files to resolve conflicts
194
+ - \`git add <resolved-files>\`
195
+ - \`git rebase --continue\`
196
+ c) Force push the resolved branch:
197
+ \`\`\`bash
198
+ git push --force-with-lease
199
+ \`\`\`
200
+ d) Retry merge:
201
+ \`\`\`bash
202
+ ${mergeFallbackCmd}
203
+ \`\`\`
204
+
205
+ REPEAT UNTIL MERGED. DO NOT GIVE UP. DO NOT SKIP. THE ${prName} MUST BE ${requiresPrIdExtraction ? 'SET TO AUTO-COMPLETE' : 'MERGED'}.
206
+ If merge is blocked by CI, wait and retry. ${requiresPrIdExtraction ? 'The auto-complete will merge when CI passes.' : 'If blocked by reviews, set auto-merge.'}
207
+
208
+ ## CRITICAL RULES
209
+ - Execute EVERY step in order (1, 2, 3, 4, 5, 6)
210
+ - Do NOT skip git add -A
211
+ - Do NOT skip git commit
212
+ - Do NOT skip ${createCmd.split(' ').slice(0, 3).join(' ')} - THE TASK IS NOT DONE UNTIL ${prName} EXISTS
213
+ - Do NOT skip ${mergeCmd.split(' ').slice(0, 4).join(' ')} - THE TASK IS NOT DONE UNTIL ${postMergeStatus}${requiresPrIdExtraction ? '\n- MUST extract PR ID from step 5 output to use in step 6' : ''}
214
+ - If push fails, debug and fix it
215
+ - If ${prName} creation fails, debug and fix it
216
+ - If ${requiresPrIdExtraction ? 'auto-complete' : 'merge'} fails, debug and fix it
217
+ - DO NOT OUTPUT JSON UNTIL ${postMergeStatus}
218
+ - A link from git push is NOT a ${prName} - you must run ${createCmd.split(' ').slice(0, 3).join(' ')}
219
+
220
+ ## Final Output
221
+ ${finalOutputNote}`;
222
+ }
223
+
224
+ /**
225
+ * Generate a git-pusher agent configuration for a specific platform
226
+ *
227
+ * @param {string} platform - Platform ID ('github', 'gitlab', 'azure-devops')
228
+ * @returns {Object} Agent configuration object
229
+ * @throws {Error} If platform is not supported
230
+ */
231
+ function generateGitPusherAgent(platform) {
232
+ const config = PLATFORM_CONFIGS[platform];
233
+
234
+ if (!config) {
235
+ const supported = Object.keys(PLATFORM_CONFIGS).join(', ');
236
+ throw new Error(`Unsupported platform '${platform}'. Supported: ${supported}`);
237
+ }
238
+
239
+ return {
240
+ id: 'git-pusher',
241
+ role: 'completion-detector',
242
+ modelLevel: 'level2',
243
+ triggers: [
244
+ {
245
+ topic: 'VALIDATION_RESULT',
246
+ logic: {
247
+ engine: 'javascript',
248
+ script: SHARED_TRIGGER_SCRIPT,
249
+ },
250
+ action: 'execute_task',
251
+ },
252
+ ],
253
+ prompt: generatePrompt(config),
254
+ output: {
255
+ topic: 'PR_CREATED',
256
+ publishAfter: 'CLUSTER_COMPLETE',
257
+ },
258
+ };
259
+ }
260
+
261
+ /**
262
+ * Get list of supported platforms for git-pusher
263
+ * @returns {string[]} Array of platform IDs
264
+ */
265
+ function getSupportedPlatforms() {
266
+ return Object.keys(PLATFORM_CONFIGS);
267
+ }
268
+
269
+ /**
270
+ * Check if a platform supports git-pusher (PR/MR creation)
271
+ * @param {string} platform - Platform ID
272
+ * @returns {boolean}
273
+ */
274
+ function isPlatformSupported(platform) {
275
+ return platform in PLATFORM_CONFIGS;
276
+ }
277
+
278
+ module.exports = {
279
+ generateGitPusherAgent,
280
+ getSupportedPlatforms,
281
+ isPlatformSupported,
282
+ // Export for testing
283
+ SHARED_TRIGGER_SCRIPT,
284
+ PLATFORM_CONFIGS,
285
+ };