@the-open-engine/zeroshot 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 (295) hide show
  1. package/CHANGELOG.md +539 -0
  2. package/LICENSE +21 -0
  3. package/README.md +508 -0
  4. package/cli/commands/inspect-render.js +135 -0
  5. package/cli/commands/inspect.js +294 -0
  6. package/cli/commands/providers.js +149 -0
  7. package/cli/index.js +5431 -0
  8. package/cli/lib/first-run.js +211 -0
  9. package/cli/lib/update-checker.js +281 -0
  10. package/cli/message-formatter-utils.js +75 -0
  11. package/cli/message-formatters-normal.js +275 -0
  12. package/cli/message-formatters-watch.js +185 -0
  13. package/cluster-templates/base-templates/debug-workflow.json +422 -0
  14. package/cluster-templates/base-templates/full-workflow.json +727 -0
  15. package/cluster-templates/base-templates/heavy-validation.json +272 -0
  16. package/cluster-templates/base-templates/quick-validation.json +285 -0
  17. package/cluster-templates/base-templates/single-worker.json +71 -0
  18. package/cluster-templates/base-templates/worker-validator.json +230 -0
  19. package/cluster-templates/conductor-bootstrap.json +130 -0
  20. package/docker/zeroshot-cluster/Dockerfile +153 -0
  21. package/docker/zeroshot-cluster/pre-baked-deps.json +28 -0
  22. package/lib/agent-cli-provider/adapters/claude-parser.d.ts +3 -0
  23. package/lib/agent-cli-provider/adapters/claude-parser.d.ts.map +1 -0
  24. package/lib/agent-cli-provider/adapters/claude-parser.js +122 -0
  25. package/lib/agent-cli-provider/adapters/claude-parser.js.map +1 -0
  26. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts +15 -0
  27. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts.map +1 -0
  28. package/lib/agent-cli-provider/adapters/claude-recovery.js +165 -0
  29. package/lib/agent-cli-provider/adapters/claude-recovery.js.map +1 -0
  30. package/lib/agent-cli-provider/adapters/claude.d.ts +3 -0
  31. package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -0
  32. package/lib/agent-cli-provider/adapters/claude.js +181 -0
  33. package/lib/agent-cli-provider/adapters/claude.js.map +1 -0
  34. package/lib/agent-cli-provider/adapters/codex-parser.d.ts +3 -0
  35. package/lib/agent-cli-provider/adapters/codex-parser.d.ts.map +1 -0
  36. package/lib/agent-cli-provider/adapters/codex-parser.js +192 -0
  37. package/lib/agent-cli-provider/adapters/codex-parser.js.map +1 -0
  38. package/lib/agent-cli-provider/adapters/codex.d.ts +3 -0
  39. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -0
  40. package/lib/agent-cli-provider/adapters/codex.js +151 -0
  41. package/lib/agent-cli-provider/adapters/codex.js.map +1 -0
  42. package/lib/agent-cli-provider/adapters/common.d.ts +28 -0
  43. package/lib/agent-cli-provider/adapters/common.d.ts.map +1 -0
  44. package/lib/agent-cli-provider/adapters/common.js +71 -0
  45. package/lib/agent-cli-provider/adapters/common.js.map +1 -0
  46. package/lib/agent-cli-provider/adapters/gemini.d.ts +3 -0
  47. package/lib/agent-cli-provider/adapters/gemini.d.ts.map +1 -0
  48. package/lib/agent-cli-provider/adapters/gemini.js +176 -0
  49. package/lib/agent-cli-provider/adapters/gemini.js.map +1 -0
  50. package/lib/agent-cli-provider/adapters/index.d.ts +9 -0
  51. package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -0
  52. package/lib/agent-cli-provider/adapters/index.js +95 -0
  53. package/lib/agent-cli-provider/adapters/index.js.map +1 -0
  54. package/lib/agent-cli-provider/adapters/opencode.d.ts +3 -0
  55. package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -0
  56. package/lib/agent-cli-provider/adapters/opencode.js +228 -0
  57. package/lib/agent-cli-provider/adapters/opencode.js.map +1 -0
  58. package/lib/agent-cli-provider/contract-actions.d.ts +5 -0
  59. package/lib/agent-cli-provider/contract-actions.d.ts.map +1 -0
  60. package/lib/agent-cli-provider/contract-actions.js +140 -0
  61. package/lib/agent-cli-provider/contract-actions.js.map +1 -0
  62. package/lib/agent-cli-provider/contract-env.d.ts +11 -0
  63. package/lib/agent-cli-provider/contract-env.d.ts.map +1 -0
  64. package/lib/agent-cli-provider/contract-env.js +113 -0
  65. package/lib/agent-cli-provider/contract-env.js.map +1 -0
  66. package/lib/agent-cli-provider/contract-envelope.d.ts +49 -0
  67. package/lib/agent-cli-provider/contract-envelope.d.ts.map +1 -0
  68. package/lib/agent-cli-provider/contract-envelope.js +52 -0
  69. package/lib/agent-cli-provider/contract-envelope.js.map +1 -0
  70. package/lib/agent-cli-provider/contract-errors.d.ts +22 -0
  71. package/lib/agent-cli-provider/contract-errors.d.ts.map +1 -0
  72. package/lib/agent-cli-provider/contract-errors.js +71 -0
  73. package/lib/agent-cli-provider/contract-errors.js.map +1 -0
  74. package/lib/agent-cli-provider/contract-fallback.d.ts +7 -0
  75. package/lib/agent-cli-provider/contract-fallback.d.ts.map +1 -0
  76. package/lib/agent-cli-provider/contract-fallback.js +102 -0
  77. package/lib/agent-cli-provider/contract-fallback.js.map +1 -0
  78. package/lib/agent-cli-provider/contract-invoke.d.ts +5 -0
  79. package/lib/agent-cli-provider/contract-invoke.d.ts.map +1 -0
  80. package/lib/agent-cli-provider/contract-invoke.js +89 -0
  81. package/lib/agent-cli-provider/contract-invoke.js.map +1 -0
  82. package/lib/agent-cli-provider/contract-options.d.ts +3 -0
  83. package/lib/agent-cli-provider/contract-options.d.ts.map +1 -0
  84. package/lib/agent-cli-provider/contract-options.js +140 -0
  85. package/lib/agent-cli-provider/contract-options.js.map +1 -0
  86. package/lib/agent-cli-provider/contract-parse.d.ts +15 -0
  87. package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -0
  88. package/lib/agent-cli-provider/contract-parse.js +81 -0
  89. package/lib/agent-cli-provider/contract-parse.js.map +1 -0
  90. package/lib/agent-cli-provider/contract-support.d.ts +18 -0
  91. package/lib/agent-cli-provider/contract-support.d.ts.map +1 -0
  92. package/lib/agent-cli-provider/contract-support.js +154 -0
  93. package/lib/agent-cli-provider/contract-support.js.map +1 -0
  94. package/lib/agent-cli-provider/contract.d.ts +13 -0
  95. package/lib/agent-cli-provider/contract.d.ts.map +1 -0
  96. package/lib/agent-cli-provider/contract.js +30 -0
  97. package/lib/agent-cli-provider/contract.js.map +1 -0
  98. package/lib/agent-cli-provider/env-safety.d.ts +11 -0
  99. package/lib/agent-cli-provider/env-safety.d.ts.map +1 -0
  100. package/lib/agent-cli-provider/env-safety.js +83 -0
  101. package/lib/agent-cli-provider/env-safety.js.map +1 -0
  102. package/lib/agent-cli-provider/errors.d.ts +5 -0
  103. package/lib/agent-cli-provider/errors.d.ts.map +1 -0
  104. package/lib/agent-cli-provider/errors.js +115 -0
  105. package/lib/agent-cli-provider/errors.js.map +1 -0
  106. package/lib/agent-cli-provider/executable.d.ts +3 -0
  107. package/lib/agent-cli-provider/executable.d.ts.map +1 -0
  108. package/lib/agent-cli-provider/executable.js +24 -0
  109. package/lib/agent-cli-provider/executable.js.map +1 -0
  110. package/lib/agent-cli-provider/index.d.ts +8 -0
  111. package/lib/agent-cli-provider/index.d.ts.map +1 -0
  112. package/lib/agent-cli-provider/index.js +31 -0
  113. package/lib/agent-cli-provider/index.js.map +1 -0
  114. package/lib/agent-cli-provider/invoke-evidence.d.ts +4 -0
  115. package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -0
  116. package/lib/agent-cli-provider/invoke-evidence.js +17 -0
  117. package/lib/agent-cli-provider/invoke-evidence.js.map +1 -0
  118. package/lib/agent-cli-provider/json.d.ts +16 -0
  119. package/lib/agent-cli-provider/json.d.ts.map +1 -0
  120. package/lib/agent-cli-provider/json.js +110 -0
  121. package/lib/agent-cli-provider/json.js.map +1 -0
  122. package/lib/agent-cli-provider/log-prefix.d.ts +2 -0
  123. package/lib/agent-cli-provider/log-prefix.d.ts.map +1 -0
  124. package/lib/agent-cli-provider/log-prefix.js +22 -0
  125. package/lib/agent-cli-provider/log-prefix.js.map +1 -0
  126. package/lib/agent-cli-provider/process-runner.d.ts +17 -0
  127. package/lib/agent-cli-provider/process-runner.d.ts.map +1 -0
  128. package/lib/agent-cli-provider/process-runner.js +89 -0
  129. package/lib/agent-cli-provider/process-runner.js.map +1 -0
  130. package/lib/agent-cli-provider/redaction.d.ts +9 -0
  131. package/lib/agent-cli-provider/redaction.d.ts.map +1 -0
  132. package/lib/agent-cli-provider/redaction.js +227 -0
  133. package/lib/agent-cli-provider/redaction.js.map +1 -0
  134. package/lib/agent-cli-provider/schema.d.ts +5 -0
  135. package/lib/agent-cli-provider/schema.d.ts.map +1 -0
  136. package/lib/agent-cli-provider/schema.js +136 -0
  137. package/lib/agent-cli-provider/schema.js.map +1 -0
  138. package/lib/agent-cli-provider/single-agent-runtime.d.ts +15 -0
  139. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -0
  140. package/lib/agent-cli-provider/single-agent-runtime.js +228 -0
  141. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -0
  142. package/lib/agent-cli-provider/types.d.ts +194 -0
  143. package/lib/agent-cli-provider/types.d.ts.map +1 -0
  144. package/lib/agent-cli-provider/types.js +12 -0
  145. package/lib/agent-cli-provider/types.js.map +1 -0
  146. package/lib/completion.js +174 -0
  147. package/lib/detached-startup.js +220 -0
  148. package/lib/docker-config.js +220 -0
  149. package/lib/git-remote-utils.js +165 -0
  150. package/lib/id-detector.js +55 -0
  151. package/lib/provider-defaults.js +62 -0
  152. package/lib/provider-detection.js +59 -0
  153. package/lib/provider-names.js +57 -0
  154. package/lib/repo-settings.js +69 -0
  155. package/lib/settings/claude-auth.js +78 -0
  156. package/lib/settings.js +542 -0
  157. package/lib/start-cluster.js +321 -0
  158. package/lib/stream-json-parser.js +67 -0
  159. package/package.json +162 -0
  160. package/scripts/fix-node-pty-permissions.js +75 -0
  161. package/scripts/record-demo.sh +279 -0
  162. package/scripts/setup-merge-queue.sh +170 -0
  163. package/scripts/test-install.sh +40 -0
  164. package/scripts/validate-templates.js +107 -0
  165. package/src/agent/agent-config.js +266 -0
  166. package/src/agent/agent-context-builder.js +189 -0
  167. package/src/agent/agent-context-sections.js +338 -0
  168. package/src/agent/agent-context-sources.js +147 -0
  169. package/src/agent/agent-hook-executor.js +721 -0
  170. package/src/agent/agent-input-injector.js +141 -0
  171. package/src/agent/agent-lifecycle.js +982 -0
  172. package/src/agent/agent-quality-gate-schema.js +93 -0
  173. package/src/agent/agent-quality-gates-context.js +51 -0
  174. package/src/agent/agent-stuck-detector.js +256 -0
  175. package/src/agent/agent-task-executor.js +2028 -0
  176. package/src/agent/agent-trigger-evaluator.js +67 -0
  177. package/src/agent/context-metrics.js +160 -0
  178. package/src/agent/context-pack-builder.js +367 -0
  179. package/src/agent/context-replay-policy.js +51 -0
  180. package/src/agent/guidance-queue.js +77 -0
  181. package/src/agent/output-extraction.js +367 -0
  182. package/src/agent/output-reformatter.js +175 -0
  183. package/src/agent/pr-verification.js +653 -0
  184. package/src/agent/rate-limit-backoff.js +82 -0
  185. package/src/agent/schema-utils.js +146 -0
  186. package/src/agent/validation-platform.js +35 -0
  187. package/src/agent-cli-provider/adapters/claude-parser.ts +133 -0
  188. package/src/agent-cli-provider/adapters/claude-recovery.ts +203 -0
  189. package/src/agent-cli-provider/adapters/claude.ts +247 -0
  190. package/src/agent-cli-provider/adapters/codex-parser.ts +211 -0
  191. package/src/agent-cli-provider/adapters/codex.ts +217 -0
  192. package/src/agent-cli-provider/adapters/common.ts +124 -0
  193. package/src/agent-cli-provider/adapters/gemini.ts +243 -0
  194. package/src/agent-cli-provider/adapters/index.ts +126 -0
  195. package/src/agent-cli-provider/adapters/opencode.ts +286 -0
  196. package/src/agent-cli-provider/contract-actions.ts +150 -0
  197. package/src/agent-cli-provider/contract-env.ts +111 -0
  198. package/src/agent-cli-provider/contract-envelope.ts +110 -0
  199. package/src/agent-cli-provider/contract-errors.ts +66 -0
  200. package/src/agent-cli-provider/contract-fallback.ts +121 -0
  201. package/src/agent-cli-provider/contract-invoke.ts +104 -0
  202. package/src/agent-cli-provider/contract-options.ts +173 -0
  203. package/src/agent-cli-provider/contract-parse.ts +94 -0
  204. package/src/agent-cli-provider/contract-support.ts +167 -0
  205. package/src/agent-cli-provider/contract.ts +56 -0
  206. package/src/agent-cli-provider/env-safety.ts +82 -0
  207. package/src/agent-cli-provider/errors.ts +122 -0
  208. package/src/agent-cli-provider/executable.ts +24 -0
  209. package/src/agent-cli-provider/index.ts +83 -0
  210. package/src/agent-cli-provider/invoke-evidence.ts +18 -0
  211. package/src/agent-cli-provider/json.ts +114 -0
  212. package/src/agent-cli-provider/log-prefix.ts +20 -0
  213. package/src/agent-cli-provider/process-runner.ts +145 -0
  214. package/src/agent-cli-provider/redaction.ts +282 -0
  215. package/src/agent-cli-provider/schema.ts +115 -0
  216. package/src/agent-cli-provider/single-agent-runtime.ts +311 -0
  217. package/src/agent-cli-provider/types.ts +237 -0
  218. package/src/agent-wrapper.js +615 -0
  219. package/src/agents/git-pusher-template.js +705 -0
  220. package/src/attach/attach-client.js +438 -0
  221. package/src/attach/attach-server.js +543 -0
  222. package/src/attach/index.js +37 -0
  223. package/src/attach/protocol.js +220 -0
  224. package/src/attach/ring-buffer.js +121 -0
  225. package/src/attach/send-input.js +88 -0
  226. package/src/attach/socket-discovery.js +267 -0
  227. package/src/claude-task-runner.js +661 -0
  228. package/src/config-router.js +89 -0
  229. package/src/config-validator.js +2202 -0
  230. package/src/copy-worker.js +43 -0
  231. package/src/guidance-topics.js +10 -0
  232. package/src/input-helpers.js +65 -0
  233. package/src/isolation-manager.js +1734 -0
  234. package/src/issue-providers/README.md +305 -0
  235. package/src/issue-providers/azure-devops-provider.js +307 -0
  236. package/src/issue-providers/base-provider.js +232 -0
  237. package/src/issue-providers/github-provider.js +210 -0
  238. package/src/issue-providers/gitlab-provider.js +262 -0
  239. package/src/issue-providers/index.js +196 -0
  240. package/src/issue-providers/jira-provider.js +260 -0
  241. package/src/ledger.js +692 -0
  242. package/src/lib/gc.js +232 -0
  243. package/src/lib/safe-exec.js +88 -0
  244. package/src/logic-engine.js +201 -0
  245. package/src/message-buffer.js +81 -0
  246. package/src/message-bus-bridge.js +144 -0
  247. package/src/message-bus.js +256 -0
  248. package/src/name-generator.js +232 -0
  249. package/src/orchestrator.js +3924 -0
  250. package/src/preflight.js +712 -0
  251. package/src/process-metrics.js +608 -0
  252. package/src/providers/anthropic/index.js +3 -0
  253. package/src/providers/base-provider.js +355 -0
  254. package/src/providers/capabilities.js +60 -0
  255. package/src/providers/google/index.js +3 -0
  256. package/src/providers/index.js +293 -0
  257. package/src/providers/openai/index.js +3 -0
  258. package/src/providers/opencode/index.js +3 -0
  259. package/src/quality-gates.js +143 -0
  260. package/src/schemas/sub-cluster.js +208 -0
  261. package/src/state-snapshot.js +398 -0
  262. package/src/state-snapshotter.js +142 -0
  263. package/src/status-footer.js +1026 -0
  264. package/src/sub-cluster-wrapper.js +693 -0
  265. package/src/task-runner.js +30 -0
  266. package/src/template-resolver.js +425 -0
  267. package/src/template-validation/index.js +338 -0
  268. package/src/template-validation/simulate-consensus-gates.js +324 -0
  269. package/src/template-validation/simulate-random-topology.js +541 -0
  270. package/src/template-validation/simulate-two-stage-validation.js +270 -0
  271. package/src/worktree-claude-config.js +135 -0
  272. package/src/worktree-tooling-env.js +150 -0
  273. package/task-lib/attachable-watcher.js +381 -0
  274. package/task-lib/commands/clean.js +50 -0
  275. package/task-lib/commands/episodes.js +105 -0
  276. package/task-lib/commands/get-log-path.js +23 -0
  277. package/task-lib/commands/kill.js +32 -0
  278. package/task-lib/commands/list.js +105 -0
  279. package/task-lib/commands/logs.js +439 -0
  280. package/task-lib/commands/resume.js +42 -0
  281. package/task-lib/commands/run.js +57 -0
  282. package/task-lib/commands/schedule.js +105 -0
  283. package/task-lib/commands/scheduler-cmd.js +96 -0
  284. package/task-lib/commands/schedules.js +148 -0
  285. package/task-lib/commands/status.js +44 -0
  286. package/task-lib/commands/unschedule.js +16 -0
  287. package/task-lib/completion.js +9 -0
  288. package/task-lib/config.js +11 -0
  289. package/task-lib/name-generator.js +230 -0
  290. package/task-lib/package.json +3 -0
  291. package/task-lib/provider-helper-runtime.js +29 -0
  292. package/task-lib/runner.js +190 -0
  293. package/task-lib/scheduler.js +252 -0
  294. package/task-lib/store.js +529 -0
  295. package/task-lib/watcher.js +305 -0
@@ -0,0 +1,705 @@
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
+
21
+ function isApproved(value) {
22
+ return value === true || value === 'true';
23
+ }
24
+
25
+ function getPayload(msg) {
26
+ return msg?.content?.['data'] || {};
27
+ }
28
+
29
+ function getEvidence(gate) {
30
+ return gate?.evidence && typeof gate.evidence === 'object' ? gate.evidence : {};
31
+ }
32
+
33
+ function getGateId(gate) {
34
+ if (typeof gate?.id === 'string' && gate.id.trim() !== '') return gate.id.trim();
35
+ const gateName = gate?.['name'];
36
+ if (typeof gateName === 'string' && gateName.trim() !== '') return gateName.trim();
37
+ return null;
38
+ }
39
+
40
+ function normalizeGateRequirements(value) {
41
+ if (!Array.isArray(value)) return [];
42
+ const normalized = [];
43
+ for (const gate of value) {
44
+ if (typeof gate === 'string') {
45
+ const id = gate.trim();
46
+ if (id) normalized.push({ id });
47
+ continue;
48
+ }
49
+ if (!gate || typeof gate !== 'object') continue;
50
+ const id = getGateId(gate);
51
+ if (!id) continue;
52
+ const required = { id };
53
+ if (typeof gate.scope === 'string' && gate.scope.trim() !== '') {
54
+ required.scope = gate.scope.trim();
55
+ }
56
+ normalized.push(required);
57
+ }
58
+ return normalized;
59
+ }
60
+
61
+ function getRequiredQualityGates() {
62
+ const currentAgent =
63
+ typeof cluster.getAgent === 'function' ? cluster.getAgent(agent.id) : null;
64
+ const sources = [
65
+ agent.requiredQualityGates,
66
+ currentAgent?.requiredQualityGates,
67
+ currentAgent?.config?.requiredQualityGates,
68
+ ];
69
+ for (const source of sources) {
70
+ const gates = normalizeGateRequirements(source);
71
+ if (gates.length > 0) return gates;
72
+ }
73
+ return [];
74
+ }
75
+
76
+ function collectQualityGates(msg) {
77
+ const gateData = getPayload(msg);
78
+ return Array.isArray(gateData.qualityGates) ? gateData.qualityGates : [];
79
+ }
80
+
81
+ function toTimestamp(timestampInput) {
82
+ const value = timestampInput;
83
+ if (typeof value === 'number' && isFinite(value)) return value;
84
+ if (typeof value === 'string' && value.trim() !== '') {
85
+ const numeric = Number(value);
86
+ if (isFinite(numeric)) return numeric;
87
+ const parsed = Date.parse(value);
88
+ if (isFinite(parsed)) return parsed;
89
+ }
90
+ return null;
91
+ }
92
+
93
+ function qualityGateTimestamp(gate, msg) {
94
+ const evidence = getEvidence(gate);
95
+ return (
96
+ toTimestamp(gate?.timestamp) ||
97
+ toTimestamp(gate?.validatedAt) ||
98
+ toTimestamp(gate?.completedAt) ||
99
+ toTimestamp(evidence.timestamp) ||
100
+ toTimestamp(evidence.validatedAt) ||
101
+ toTimestamp(evidence.completedAt)
102
+ );
103
+ }
104
+
105
+ function describeQualityGate(gate, msg, required) {
106
+ const evidence = getEvidence(gate);
107
+ const parts = [];
108
+ const gateId = getGateId(gate) || required?.id;
109
+ const scope = gate?.scope || evidence.scope || required?.scope;
110
+ const status = gate?.status;
111
+ const exitCode = evidence.exitCode;
112
+ const command = evidence.command;
113
+ const output = evidence.output || gate?.reason || evidence.reason;
114
+ if (gateId) parts.push('gate=' + gateId);
115
+ if (scope) parts.push('scope=' + scope);
116
+ if (status) parts.push('status=' + status);
117
+ if (exitCode !== undefined) parts.push('exitCode=' + exitCode);
118
+ if (command) parts.push('command=' + JSON.stringify(String(command).slice(0, 160)));
119
+ if (msg?.sender) parts.push('sender=' + msg.sender);
120
+ if (output) parts.push('output=' + JSON.stringify(String(output).slice(0, 240)));
121
+ if (!output && (gate?.reason || evidence.reason)) {
122
+ parts.push('reason=' + JSON.stringify(String(gate?.reason || evidence.reason).slice(0, 240)));
123
+ }
124
+ return parts.join(' ');
125
+ }
126
+
127
+ function exitCodePasses(evidence) {
128
+ const exitCode = evidence.exitCode;
129
+ return (
130
+ exitCode === 0 ||
131
+ exitCode === '0' ||
132
+ (typeof exitCode === 'string' && exitCode.trim() !== '' && Number(exitCode) === 0)
133
+ );
134
+ }
135
+
136
+ function qualityGateMatches(gate, required) {
137
+ if (getGateId(gate) !== required.id) return false;
138
+ if (required.scope && gate?.scope !== required.scope && getEvidence(gate).scope !== required.scope) {
139
+ return false;
140
+ }
141
+ return true;
142
+ }
143
+
144
+ function compareGateEvidence(left, right) {
145
+ const leftTimestamp = qualityGateTimestamp(left.gate, left.msg) || 0;
146
+ const rightTimestamp = qualityGateTimestamp(right.gate, right.msg) || 0;
147
+ return leftTimestamp - rightTimestamp;
148
+ }
149
+
150
+ function findLatestQualityGate(messages, required) {
151
+ let latest = null;
152
+ for (const msg of messages) {
153
+ for (const gate of collectQualityGates(msg)) {
154
+ if (!qualityGateMatches(gate, required)) continue;
155
+ const candidate = { gate, msg };
156
+ if (!latest || compareGateEvidence(candidate, latest) >= 0) {
157
+ latest = candidate;
158
+ }
159
+ }
160
+ }
161
+ return latest;
162
+ }
163
+
164
+ function getQualityGateBlockingReasons(gate, msg) {
165
+ const reasons = [];
166
+ const status = String(gate?.status || '').toUpperCase();
167
+ if (status !== 'PASS') reasons.push('status=' + (gate?.status || 'missing'));
168
+
169
+ const evidence = getEvidence(gate);
170
+ if (typeof evidence.command !== 'string' || evidence.command.trim() === '') {
171
+ reasons.push('missing evidence.command');
172
+ }
173
+ if (!exitCodePasses(evidence)) {
174
+ reasons.push('evidence.exitCode=' + evidence.exitCode);
175
+ }
176
+ if (typeof evidence.output !== 'string') {
177
+ reasons.push('missing evidence.output');
178
+ }
179
+ if (gate?.stale === true || evidence.stale === true) {
180
+ reasons.push('stale=true');
181
+ }
182
+ const completedAt = qualityGateTimestamp(gate, msg);
183
+ if (completedAt === null) {
184
+ reasons.push('missing completedAt');
185
+ } else if (completedAt < lastPush.timestamp) {
186
+ reasons.push('completed before IMPLEMENTATION_READY');
187
+ }
188
+ return reasons;
189
+ }
190
+
191
+ function assertRequiredQualityGatesPass(messages) {
192
+ if (requiredQualityGatesForHandoff.length === 0) return true;
193
+
194
+ for (const required of requiredQualityGatesForHandoff) {
195
+ const found = findLatestQualityGate(messages, required);
196
+ if (!found) {
197
+ throw new Error(
198
+ 'Required quality gate missing for git-pusher handoff: gate=' + required.id
199
+ );
200
+ }
201
+
202
+ const reasons = getQualityGateBlockingReasons(found.gate, found.msg);
203
+ if (reasons.length > 0) {
204
+ throw new Error(
205
+ 'Required quality gate blocked git-pusher handoff: ' +
206
+ describeQualityGate(found.gate, found.msg, required) +
207
+ ' reason=' +
208
+ JSON.stringify(reasons.join(', '))
209
+ );
210
+ }
211
+ }
212
+
213
+ return true;
214
+ }
215
+
216
+ const requiredQualityGatesForHandoff = getRequiredQualityGates();
217
+ if (validators.length === 0 && requiredQualityGatesForHandoff.length === 0) return true;
218
+
219
+ const results = ledger.query({ topic: 'VALIDATION_RESULT', since: lastPush.timestamp });
220
+ if (results.length === 0) return false;
221
+
222
+ const validatorIds = new Set(validators.map((v) => v.id));
223
+ const validatorResults = results.filter((r) => validatorIds.has(r.sender));
224
+
225
+ // Two supported patterns:
226
+ // 1) Per-validator VALIDATION_RESULT (sender is a validator) → require all validators approve.
227
+ // 2) Consensus-only VALIDATION_RESULT (sender is coordinator) -> use latest result.
228
+ if (validatorResults.length === 0) {
229
+ let latest = null;
230
+ for (const msg of results) {
231
+ if (!latest || (typeof msg.timestamp === 'number' && msg.timestamp > latest.timestamp)) {
232
+ latest = msg;
233
+ }
234
+ }
235
+ const approved = getPayload(latest).approved;
236
+ if (!isApproved(approved)) return false;
237
+ assertRequiredQualityGatesPass([latest]);
238
+ return true;
239
+ }
240
+
241
+ const latestByValidator = new Map();
242
+ for (const msg of validatorResults) {
243
+ latestByValidator.set(msg.sender, msg);
244
+ }
245
+ if (latestByValidator.size < validators.length) return false;
246
+
247
+ for (const validator of validators) {
248
+ const msg = latestByValidator.get(validator.id);
249
+ const approved = getPayload(msg).approved;
250
+ if (!isApproved(approved)) return false;
251
+ }
252
+
253
+ const latestValidatorMessages = Array.from(latestByValidator.values());
254
+ assertRequiredQualityGatesPass(latestValidatorMessages);
255
+
256
+ const hasSufficientEvidence = latestValidatorMessages.every((r) => {
257
+ const criteria = getPayload(r).criteriaResults;
258
+ if (!Array.isArray(criteria) || criteria.length === 0) return true;
259
+ return criteria.every((c) => {
260
+ const status = String(c.status || '').toUpperCase();
261
+ if (status === 'CANNOT_VALIDATE') return true;
262
+ if (status === 'SKIPPED') return true;
263
+ if (status === 'CANNOT_VALIDATE_YET') return false;
264
+ const evidence = c.evidence || {};
265
+ const hasCommand = typeof evidence.command === 'string' && evidence.command.trim().length > 0;
266
+ const exitCode = evidence.exitCode;
267
+ const hasExitCode =
268
+ typeof exitCode === 'number' ||
269
+ (typeof exitCode === 'string' && exitCode.trim() !== '' && isFinite(Number(exitCode)));
270
+ const hasOutput = evidence.output === undefined || typeof evidence.output === 'string';
271
+ return hasCommand && hasExitCode && hasOutput;
272
+ });
273
+ });
274
+
275
+ return hasSufficientEvidence;`;
276
+
277
+ const { readRepoSettings } = require('../../lib/repo-settings');
278
+ const { resolveRequiredQualityGates } = require('../quality-gates');
279
+
280
+ function getSafeBranchName(value) {
281
+ if (typeof value !== 'string') {
282
+ return null;
283
+ }
284
+
285
+ const trimmed = value.trim();
286
+ if (trimmed.length === 0) {
287
+ return null;
288
+ }
289
+
290
+ // Conservative allowlist to avoid shell injection in generated CLI commands.
291
+ if (!/^[A-Za-z0-9._/-]+$/.test(trimmed)) {
292
+ return null;
293
+ }
294
+
295
+ return trimmed;
296
+ }
297
+
298
+ function parseBool(value) {
299
+ if (typeof value === 'boolean') return value;
300
+ if (typeof value !== 'string') return null;
301
+ const trimmed = value.trim().toLowerCase();
302
+ if (trimmed === '1' || trimmed === 'true' || trimmed === 'yes') return true;
303
+ if (trimmed === '0' || trimmed === 'false' || trimmed === 'no') return false;
304
+ return null;
305
+ }
306
+
307
+ function normalizeCloseIssueMode(value) {
308
+ if (typeof value !== 'string') return null;
309
+ const trimmed = value.trim().toLowerCase();
310
+ if (trimmed === 'auto') return 'auto';
311
+ if (trimmed === 'always') return 'always';
312
+ if (trimmed === 'never') return 'never';
313
+ return null;
314
+ }
315
+
316
+ /**
317
+ * Resolve GitHub configuration from CLI options and repo settings.
318
+ * Priority: CLI options > repo settings (.zeroshot/settings.json) > defaults
319
+ *
320
+ * @param {Object} options - CLI options
321
+ * @param {string} [options.prBase] - Target branch for PRs
322
+ * @param {boolean} [options.mergeQueue] - Use GitHub merge queue
323
+ * @param {string} [options.closeIssue] - When to close issue: auto|always|never
324
+ * @returns {Object} Resolved configuration
325
+ */
326
+ function resolveGitHubConfig(options = {}) {
327
+ const repoSettingsResult = readRepoSettings(options.cwd || process.cwd());
328
+ const repoSettings = repoSettingsResult.settings || {};
329
+ const repoGithub = repoSettings.github || {};
330
+
331
+ // CLI options override repo settings
332
+ const prBase = getSafeBranchName(options.prBase) || getSafeBranchName(repoGithub.prBase);
333
+
334
+ const useMergeQueue =
335
+ options.mergeQueue === true ||
336
+ (options.mergeQueue !== false && parseBool(repoGithub.useMergeQueue) === true);
337
+
338
+ const closeIssueMode =
339
+ normalizeCloseIssueMode(options.closeIssue) ||
340
+ normalizeCloseIssueMode(repoGithub.closeIssue) ||
341
+ (parseBool(repoGithub.closeIssue) === true ? 'always' : null) ||
342
+ 'never';
343
+
344
+ return { prBase, useMergeQueue, closeIssueMode };
345
+ }
346
+
347
+ /**
348
+ * Generate platform-specific configuration based on resolved GitHub config.
349
+ *
350
+ * @param {string} platform - Platform ID ('github', 'gitlab', 'azure-devops')
351
+ * @param {Object} config - Resolved GitHub config from resolveGitHubConfig()
352
+ * @returns {Object|null} Platform configuration or null if unsupported
353
+ */
354
+ function getPlatformConfig(platform, config = {}) {
355
+ const { prBase, useMergeQueue, closeIssueMode } = config;
356
+
357
+ const PLATFORM_CONFIGS = {
358
+ github: {
359
+ prName: 'PR',
360
+ prNameLower: 'pull request',
361
+ createCmd: `gh pr create${prBase ? ` --base ${prBase}` : ''} --title "feat: {{issue_title}}" --body "Closes #{{issue_number}}"`,
362
+ mergeCmd: useMergeQueue
363
+ ? `PR_ID="$(timeout 30 gh pr view --json id --jq .id)"
364
+ gh api graphql -f query='mutation($id:ID!){enqueuePullRequest(input:{pullRequestId:$id}){mergeQueueEntry{state}}}' -f id="$PR_ID"
365
+ echo "Waiting for merge..."
366
+ for i in $(seq 1 90); do if timeout 30 gh pr view --json mergedAt --jq .mergedAt | grep -q .; then break; fi; sleep 20; done`
367
+ : 'gh pr merge --merge --delete-branch',
368
+ mergeFallbackCmd: useMergeQueue
369
+ ? 'gh pr merge --merge --delete-branch'
370
+ : 'gh pr merge --merge',
371
+ prUrlExample: 'https://github.com/owner/repo/pull/123',
372
+ outputFields: { urlField: 'pr_url', numberField: 'pr_number', mergedField: 'merged' },
373
+ rebaseBranch: prBase || 'main',
374
+ usesMergeQueue: useMergeQueue,
375
+ closeIssueMode: closeIssueMode || 'never',
376
+ },
377
+ gitlab: {
378
+ prName: 'MR',
379
+ prNameLower: 'merge request',
380
+ createCmd:
381
+ 'glab mr create --title "feat: {{issue_title}}" --description "Closes #{{issue_number}}"',
382
+ mergeCmd: 'glab mr merge --auto-merge',
383
+ mergeFallbackCmd: 'glab mr merge',
384
+ prUrlExample: 'https://gitlab.com/owner/repo/-/merge_requests/123',
385
+ outputFields: { urlField: 'mr_url', numberField: 'mr_number', mergedField: 'merged' },
386
+ closeIssueMode: closeIssueMode || 'never',
387
+ },
388
+ 'azure-devops': {
389
+ prName: 'PR',
390
+ prNameLower: 'pull request',
391
+ createCmd:
392
+ 'az repos pr create --title "feat: {{issue_title}}" --description "Closes #{{issue_number}}"',
393
+ mergeCmd: 'az repos pr update --id <PR_ID> --auto-complete true',
394
+ mergeFallbackCmd: 'az repos pr update --id <PR_ID> --status completed',
395
+ prUrlExample: 'https://dev.azure.com/org/project/_git/repo/pullrequest/123',
396
+ outputFields: {
397
+ urlField: 'pr_url',
398
+ numberField: 'pr_number',
399
+ mergedField: 'merged',
400
+ autoCompleteField: 'auto_complete',
401
+ },
402
+ // Azure requires extracting PR ID from create output
403
+ requiresPrIdExtraction: true,
404
+ closeIssueMode: closeIssueMode || 'never',
405
+ },
406
+ };
407
+
408
+ return PLATFORM_CONFIGS[platform] || null;
409
+ }
410
+
411
+ /**
412
+ * Get list of supported platforms for git-pusher
413
+ * @returns {string[]} Array of platform IDs
414
+ */
415
+ const SUPPORTED_PLATFORMS = ['github', 'gitlab', 'azure-devops'];
416
+
417
+ /**
418
+ * Generate the prompt for a specific platform
419
+ * @param {Object} config - Platform configuration from PLATFORM_CONFIGS
420
+ * @returns {string} The complete prompt with platform-specific commands
421
+ */
422
+ function generatePrompt(config) {
423
+ const {
424
+ prName,
425
+ prNameLower,
426
+ createCmd,
427
+ mergeCmd,
428
+ mergeFallbackCmd,
429
+ prUrlExample,
430
+ outputFields,
431
+ requiresPrIdExtraction,
432
+ rebaseBranch,
433
+ usesMergeQueue,
434
+ closeIssueMode,
435
+ } = config;
436
+
437
+ // Azure-specific instructions for PR ID extraction
438
+ const azurePrIdNote = requiresPrIdExtraction
439
+ ? `\n\n💡 IMPORTANT: The output will contain the PR ID. You MUST extract it for the next step.
440
+ Look for output like: "Created PR 123" or parse the URL for the PR number.
441
+ Save the PR ID to a variable for step 6.`
442
+ : '';
443
+
444
+ // Azure uses different merge terminology
445
+ const mergeDescription = requiresPrIdExtraction
446
+ ? 'SET AUTO-COMPLETE (MANDATORY - THIS IS NOT OPTIONAL)'
447
+ : usesMergeQueue
448
+ ? `ENQUEUE INTO MERGE QUEUE AND WAIT UNTIL THE ${prName} IS MERGED (MANDATORY - THIS IS NOT OPTIONAL)`
449
+ : `MERGE THE ${prName} (MANDATORY - THIS IS NOT OPTIONAL)`;
450
+
451
+ const mergeExplanation = requiresPrIdExtraction
452
+ ? `Replace <PR_ID> with the actual PR number from step 5.
453
+ This enables auto-complete (auto-merge when CI passes).
454
+
455
+ If auto-complete is not available or you need to merge immediately:`
456
+ : usesMergeQueue
457
+ ? `This enqueues the ${prName} into GitHub's merge queue and waits until it is merged.
458
+
459
+ If enqueue fails (merge queue not enabled, missing permissions, etc.), fall back to auto-merge:`
460
+ : `This merges the ${prName} directly and deletes the remote branch. If it fails, try without branch deletion:`;
461
+
462
+ const finalOutputNote = requiresPrIdExtraction
463
+ ? `ONLY after the PR is created and auto-complete is set, output:
464
+ \`\`\`json
465
+ {"${outputFields.urlField}": "${prUrlExample}", "${outputFields.numberField}": 123, "merged": false, "auto_complete": true}
466
+ \`\`\`
467
+
468
+ If truly no changes exist, output:
469
+ \`\`\`json
470
+ {"${outputFields.urlField}": null, "${outputFields.numberField}": null, "merged": false, "auto_complete": false}
471
+ \`\`\``
472
+ : `ONLY after the ${prName} is MERGED, output:
473
+ \`\`\`json
474
+ {"${outputFields.urlField}": "${prUrlExample}", "${outputFields.numberField}": 123, "merged": true}
475
+ \`\`\`
476
+
477
+ If truly no changes exist, output:
478
+ \`\`\`json
479
+ {"${outputFields.urlField}": null, "${outputFields.numberField}": null, "merged": false}
480
+ \`\`\``;
481
+
482
+ return `CRITICAL: ALL VALIDATORS APPROVED. YOU ARE A TRANSPORT-ONLY GIT PUSHER.
483
+
484
+ Your job is to preserve validator ownership: stage, commit, push, create the ${prName}, then merge or enable auto-merge when possible.
485
+
486
+ Do NOT edit source files, tests, configs, generated artifacts, or lockfiles.
487
+ Do NOT inspect CI logs to debug product code.
488
+ Do NOT resolve merge conflicts or rebase conflicts.
489
+ Do NOT run implementation/debugging workflows after validators hand off.
490
+
491
+ Allowed after validation:
492
+ - git add/status/commit/push
493
+ - ${createCmd.split(' ').slice(0, 3).join(' ')}
494
+ - ${mergeCmd.split(' ').slice(0, 4).join(' ')} or auto-merge/auto-complete commands
495
+ - status-only commands such as ${prName === 'PR' ? 'gh pr view/gh pr checks' : 'the platform PR/MR status command'}
496
+
497
+ If commit hooks, push, ${prName} creation, merge, CI, or conflict handling requires code changes, STOP and report the blocked state in JSON. The implementation and validator agents must fix code and rerun quality gates.
498
+
499
+ ## MANDATORY STEPS - EXECUTE EACH ONE IN ORDER - DO NOT SKIP ANY STEP
500
+
501
+ ### STEP 1: Stage ALL changes (MANDATORY)
502
+ \`\`\`bash
503
+ git add -A
504
+ \`\`\`
505
+ Run this command. Do not skip it. If commit fails because hooks/checks fail, do not edit files. Output blocked JSON with the failure summary.
506
+
507
+ ### STEP 2: Check what's staged
508
+ \`\`\`bash
509
+ git status
510
+ \`\`\`
511
+ Run this. If nothing to commit, output JSON with ${outputFields.urlField}: null and stop.
512
+
513
+ ### STEP 3: Commit the changes (MANDATORY if there are changes)
514
+ \`\`\`bash
515
+ git commit -m "feat: implement #{{issue_number}} - {{issue_title}}"
516
+ \`\`\`
517
+ Run this command. Do not skip it.
518
+
519
+ ### STEP 4: Push to origin (MANDATORY)
520
+ \`\`\`bash
521
+ git push -u origin HEAD
522
+ \`\`\`
523
+ Run this. If it fails, do not edit files, rebase, or resolve conflicts. Output blocked JSON with the failure summary.
524
+
525
+ ⚠️ AFTER PUSH YOU ARE NOT DONE! CONTINUE TO STEP 5! ⚠️
526
+
527
+ ### STEP 5: CREATE THE ${prName.toUpperCase()} (MANDATORY - YOU MUST RUN THIS COMMAND)
528
+ \`\`\`bash
529
+ ${createCmd}
530
+ \`\`\`
531
+ 🚨 YOU MUST RUN \`${createCmd.split(' ').slice(0, 3).join(' ')}\`! Outputting a link is NOT creating a ${prName}! 🚨
532
+ The push output shows a "Create a ${prNameLower}" link - IGNORE IT.
533
+ You MUST run the \`${createCmd.split(' ').slice(0, 3).join(' ')}\` command above.${requiresPrIdExtraction ? '' : ` Save the actual ${prName} URL from the output.`}${azurePrIdNote}
534
+
535
+ ⚠️ AFTER ${prName} CREATION YOU ARE NOT DONE! CONTINUE TO STEP 6! ⚠️
536
+
537
+ ### STEP 6: ${mergeDescription}
538
+ \`\`\`bash
539
+ ${mergeCmd}
540
+ \`\`\`
541
+ ${mergeExplanation}
542
+ \`\`\`bash
543
+ ${mergeFallbackCmd}
544
+ \`\`\`
545
+
546
+ If direct merge is blocked by pending CI or required review, set auto-merge/auto-complete when the platform supports it and output status-only JSON without \`blocked: true\`.
547
+ If merge is blocked by failed CI, merge conflicts, rejected hooks, or any condition requiring code changes, do not debug or edit code. Output blocked JSON with the ${prName} details and failure summary.
548
+
549
+ ${
550
+ closeIssueMode !== 'never'
551
+ ? `### STEP 7: Close the issue (MANDATORY)
552
+ \`\`\`bash
553
+ if [ "{{issue_number}}" != "unknown" ]; then
554
+ ISSUE_STATE="$(gh issue view {{issue_number}} --json state --jq .state 2>/dev/null || true)"
555
+ if [ "$ISSUE_STATE" = "OPEN" ]; then
556
+ BASE_BRANCH="${rebaseBranch || 'main'}"
557
+ DEFAULT_BRANCH="$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name 2>/dev/null || true)"
558
+ SHOULD_CLOSE="0"
559
+ if [ "${closeIssueMode}" = "always" ]; then
560
+ SHOULD_CLOSE="1"
561
+ elif [ "${closeIssueMode}" = "auto" ]; then
562
+ if [ -z "$DEFAULT_BRANCH" ] || [ "$BASE_BRANCH" != "$DEFAULT_BRANCH" ]; then
563
+ SHOULD_CLOSE="1"
564
+ fi
565
+ fi
566
+
567
+ if [ "$SHOULD_CLOSE" = "1" ]; then
568
+ PR_URL="$(gh pr view --json url --jq .url 2>/dev/null || true)"
569
+ if [ -n "$PR_URL" ]; then
570
+ gh issue close {{issue_number}} --comment "Implemented in $PR_URL"
571
+ else
572
+ gh issue close {{issue_number}} --comment "Implemented"
573
+ fi
574
+ fi
575
+ fi
576
+ fi
577
+ \`\`\`
578
+ Only do this AFTER the ${prName} is merged.`
579
+ : ''
580
+ }
581
+
582
+ ## CRITICAL RULES
583
+ - Execute EVERY step in order (1, 2, 3, 4, 5, 6)
584
+ - Do NOT skip git add -A
585
+ - Do NOT skip git commit
586
+ - Do NOT skip ${createCmd.split(' ').slice(0, 3).join(' ')} - THE TASK IS NOT DONE UNTIL ${prName} EXISTS
587
+ - Do NOT skip ${mergeCmd.split(' ').slice(0, 4).join(' ')} - attempt merge or auto-merge before reporting blocked${requiresPrIdExtraction ? '\n- MUST extract PR ID from step 5 output to use in step 6' : ''}
588
+ - Do NOT edit files after validator handoff
589
+ - Do NOT debug product failures after validator handoff
590
+ - If push, ${prName} creation, CI, or ${requiresPrIdExtraction ? 'auto-complete' : 'merge'} fails, report it instead of fixing code
591
+ - Output JSON only after the ${prName} is merged, auto-merge is enabled/pending, or a non-code transport failure blocks progress
592
+ - A link from git push is NOT a ${prName} - you must run ${createCmd.split(' ').slice(0, 3).join(' ')}
593
+
594
+ ## Final Output
595
+ ${finalOutputNote}
596
+
597
+ If blocked after creating a ${prName}, output:
598
+ \`\`\`json
599
+ {"${outputFields.urlField}": "${prUrlExample}", "${outputFields.numberField}": 123, "merged": false, "blocked": true, "blocked_reason": "ci_failed: test job failed"}
600
+ \`\`\`
601
+
602
+ If blocked before creating a ${prName}, output:
603
+ \`\`\`json
604
+ {"${outputFields.urlField}": null, "${outputFields.numberField}": null, "merged": false, "blocked": true, "blocked_reason": "commit_failed: pre-commit hook failed"}
605
+ \`\`\``;
606
+ }
607
+
608
+ /**
609
+ * Generate a git-pusher agent configuration for a specific platform
610
+ *
611
+ * @param {string} platform - Platform ID ('github', 'gitlab', 'azure-devops')
612
+ * @param {Object} [options] - CLI options for GitHub configuration
613
+ * @param {string} [options.prBase] - Target branch for PRs
614
+ * @param {boolean} [options.mergeQueue] - Use GitHub merge queue
615
+ * @param {string} [options.closeIssue] - When to close issue: auto|always|never
616
+ * @param {Array} [options.requiredQualityGates] - Required handoff quality gates
617
+ * @returns {Object} Agent configuration object
618
+ * @throws {Error} If platform is not supported
619
+ */
620
+ function generateGitPusherAgent(platform, options = {}) {
621
+ // Resolve config from CLI options and repo settings
622
+ const resolvedConfig = resolveGitHubConfig(options);
623
+ const platformConfig = getPlatformConfig(platform, resolvedConfig);
624
+ const requiredQualityGates = resolveRequiredQualityGates(options);
625
+
626
+ if (!platformConfig) {
627
+ const supported = SUPPORTED_PLATFORMS.join(', ');
628
+ throw new Error(`Unsupported platform '${platform}'. Supported: ${supported}`);
629
+ }
630
+
631
+ return {
632
+ id: 'git-pusher',
633
+ role: 'completion-detector',
634
+ modelLevel: 'level2',
635
+ ...(requiredQualityGates.length > 0 ? { requiredQualityGates } : {}),
636
+ triggers: [
637
+ {
638
+ topic: 'VALIDATION_RESULT',
639
+ logic: {
640
+ engine: 'javascript',
641
+ script: SHARED_TRIGGER_SCRIPT,
642
+ },
643
+ action: 'execute_task',
644
+ },
645
+ ],
646
+ prompt: generatePrompt(platformConfig),
647
+ hooks: {
648
+ onComplete: {
649
+ action: 'verify_pull_request',
650
+ // No config needed - verification reads from result.structured_output
651
+ // and publishes CLUSTER_COMPLETE only if verification passes
652
+ },
653
+ },
654
+ output: {
655
+ topic: 'PR_CREATED',
656
+ publishAfter: 'CLUSTER_COMPLETE',
657
+ },
658
+ structuredOutput: {
659
+ type: 'object',
660
+ properties: {
661
+ pr_number: {
662
+ type: 'number',
663
+ description: 'MUST extract from gh pr create output - NOT from git push link',
664
+ },
665
+ pr_url: { type: 'string' },
666
+ merged: { type: 'boolean' },
667
+ merge_commit_sha: {
668
+ type: 'string',
669
+ description: 'MUST extract from gh pr merge output',
670
+ },
671
+ blocked: { type: 'boolean' },
672
+ blocked_reason: { type: 'string' },
673
+ },
674
+ required: ['pr_number', 'pr_url', 'merged'],
675
+ },
676
+ };
677
+ }
678
+
679
+ /**
680
+ * Get list of supported platforms for git-pusher
681
+ * @returns {string[]} Array of platform IDs
682
+ */
683
+ function getSupportedPlatforms() {
684
+ return SUPPORTED_PLATFORMS;
685
+ }
686
+
687
+ /**
688
+ * Check if a platform supports git-pusher (PR/MR creation)
689
+ * @param {string} platform - Platform ID
690
+ * @returns {boolean}
691
+ */
692
+ function isPlatformSupported(platform) {
693
+ return SUPPORTED_PLATFORMS.includes(platform);
694
+ }
695
+
696
+ module.exports = {
697
+ generateGitPusherAgent,
698
+ getSupportedPlatforms,
699
+ isPlatformSupported,
700
+ // Export for testing
701
+ SHARED_TRIGGER_SCRIPT,
702
+ SUPPORTED_PLATFORMS,
703
+ resolveGitHubConfig,
704
+ getPlatformConfig,
705
+ };