@backburner/cli 0.1.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 (202) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +329 -0
  3. package/dist/src/agents/antigravity-launch.js +162 -0
  4. package/dist/src/agents/antigravity.js +251 -0
  5. package/dist/src/agents/claude-launch.js +117 -0
  6. package/dist/src/agents/claude.js +271 -0
  7. package/dist/src/agents/codex-launch.js +82 -0
  8. package/dist/src/agents/codex.js +261 -0
  9. package/dist/src/agents/composite-runner.js +13 -0
  10. package/dist/src/agents/extraction.js +442 -0
  11. package/dist/src/agents/failure-feedback.js +132 -0
  12. package/dist/src/agents/gemini-launch.js +96 -0
  13. package/dist/src/agents/gemini.js +392 -0
  14. package/dist/src/agents/job-agent-assignments.js +180 -0
  15. package/dist/src/agents/lifecycle-feedback.js +147 -0
  16. package/dist/src/agents/limit-detector.js +83 -0
  17. package/dist/src/agents/opencode-launch.js +138 -0
  18. package/dist/src/agents/opencode.js +156 -0
  19. package/dist/src/agents/plan-breakdown-feedback.js +97 -0
  20. package/dist/src/agents/pool.js +168 -0
  21. package/dist/src/agents/prompt.js +462 -0
  22. package/dist/src/agents/provider-sessions.js +105 -0
  23. package/dist/src/agents/review-feedback.js +255 -0
  24. package/dist/src/agents/selection.js +113 -0
  25. package/dist/src/agents/service.js +1087 -0
  26. package/dist/src/agents/types.js +1 -0
  27. package/dist/src/agents/wave-assessment-feedback.js +69 -0
  28. package/dist/src/attention/derive.js +337 -0
  29. package/dist/src/capabilities/index.js +2 -0
  30. package/dist/src/capabilities/projection.js +183 -0
  31. package/dist/src/capabilities/types.js +1 -0
  32. package/dist/src/capabilities/validator.js +87 -0
  33. package/dist/src/cli/commands/broker-smoke.js +96 -0
  34. package/dist/src/cli/commands/broker.js +56 -0
  35. package/dist/src/cli/commands/init.js +4 -0
  36. package/dist/src/cli/commands/journal.js +43 -0
  37. package/dist/src/cli/commands/run-loop.js +56 -0
  38. package/dist/src/cli/commands/run.js +1241 -0
  39. package/dist/src/cli/commands/tui.js +187 -0
  40. package/dist/src/cli/dispatcher.js +51 -0
  41. package/dist/src/cli/errors.js +2 -0
  42. package/dist/src/cli/init.js +92 -0
  43. package/dist/src/cli/options.js +86 -0
  44. package/dist/src/cli/output.js +231 -0
  45. package/dist/src/cli/run-journal.js +342 -0
  46. package/dist/src/cli/run-types.js +1 -0
  47. package/dist/src/cli/run.js +32 -0
  48. package/dist/src/cli/runtime/dispatch-lifecycle.js +772 -0
  49. package/dist/src/cli/runtime/provider-runtime-types.js +1 -0
  50. package/dist/src/cli/runtime/provider-runtime.js +152 -0
  51. package/dist/src/cli/runtime/provider-tools.js +73 -0
  52. package/dist/src/cli/runtime/providers/antigravity.js +26 -0
  53. package/dist/src/cli/runtime/providers/claude.js +20 -0
  54. package/dist/src/cli/runtime/providers/codex.js +20 -0
  55. package/dist/src/cli/runtime/providers/gemini.js +20 -0
  56. package/dist/src/cli/runtime/providers/opencode.js +20 -0
  57. package/dist/src/cli/runtime/run-context.js +131 -0
  58. package/dist/src/cli/tui/index.js +3 -0
  59. package/dist/src/cli/tui/screen.js +388 -0
  60. package/dist/src/cli/tui/tracker.js +558 -0
  61. package/dist/src/cli/tui/types.js +1 -0
  62. package/dist/src/cli/tui/views.js +1006 -0
  63. package/dist/src/config/loader.js +136 -0
  64. package/dist/src/config/schemas.js +403 -0
  65. package/dist/src/config/types.js +1 -0
  66. package/dist/src/context/types.js +331 -0
  67. package/dist/src/domain/entities.js +1 -0
  68. package/dist/src/git/backburner-git-tool-service.js +680 -0
  69. package/dist/src/git/gitops-lifecycle.js +56 -0
  70. package/dist/src/git/merge-prep-workspace.js +357 -0
  71. package/dist/src/git/types.js +484 -0
  72. package/dist/src/github/broker-model-content.js +215 -0
  73. package/dist/src/github/broker-server.js +237 -0
  74. package/dist/src/github/broker.js +638 -0
  75. package/dist/src/github/composed.js +53 -0
  76. package/dist/src/github/gateway.js +478 -0
  77. package/dist/src/github/normalize.js +222 -0
  78. package/dist/src/github/pr-classification.js +73 -0
  79. package/dist/src/github/scoped-broker.js +35 -0
  80. package/dist/src/github/security.js +126 -0
  81. package/dist/src/github/service.js +332 -0
  82. package/dist/src/github/types.js +1 -0
  83. package/dist/src/github/utils.js +16 -0
  84. package/dist/src/journal/index.js +4 -0
  85. package/dist/src/journal/reader.js +62 -0
  86. package/dist/src/journal/renderer.js +50 -0
  87. package/dist/src/journal/service.js +72 -0
  88. package/dist/src/journal/types.js +1 -0
  89. package/dist/src/journal/writer.js +19 -0
  90. package/dist/src/mcps/bridge.js +166 -0
  91. package/dist/src/mcps/index.js +5 -0
  92. package/dist/src/mcps/reconcile.js +113 -0
  93. package/dist/src/mcps/smithery-client.js +186 -0
  94. package/dist/src/mcps/smithery.js +164 -0
  95. package/dist/src/mcps/types.js +1 -0
  96. package/dist/src/memory/json-store.js +71 -0
  97. package/dist/src/memory/prompt.js +16 -0
  98. package/dist/src/memory/scope.js +31 -0
  99. package/dist/src/memory/store.js +1 -0
  100. package/dist/src/memory/tool-service.js +142 -0
  101. package/dist/src/memory/types.js +12 -0
  102. package/dist/src/onboarding/config-writer.js +96 -0
  103. package/dist/src/onboarding/engine.js +86 -0
  104. package/dist/src/onboarding/prompt.js +102 -0
  105. package/dist/src/onboarding/services/provider-discovery.js +58 -0
  106. package/dist/src/onboarding/services/repo-discovery.js +246 -0
  107. package/dist/src/onboarding/steps/check-github-auth.js +32 -0
  108. package/dist/src/onboarding/steps/check-requirements.js +34 -0
  109. package/dist/src/onboarding/steps/configure-models.js +105 -0
  110. package/dist/src/onboarding/steps/configure-paths.js +33 -0
  111. package/dist/src/onboarding/steps/discover-providers.js +38 -0
  112. package/dist/src/onboarding/steps/discover-repos.js +39 -0
  113. package/dist/src/onboarding/steps/generate-config.js +106 -0
  114. package/dist/src/onboarding/steps/readiness-validation.js +158 -0
  115. package/dist/src/onboarding/steps/select-repos.js +76 -0
  116. package/dist/src/onboarding/steps/show-summary.js +97 -0
  117. package/dist/src/onboarding/steps/welcome.js +27 -0
  118. package/dist/src/onboarding/types.js +1 -0
  119. package/dist/src/onboarding/validators/git-access.js +63 -0
  120. package/dist/src/onboarding/validators/github-auth.js +85 -0
  121. package/dist/src/onboarding/validators/tool-checker.js +153 -0
  122. package/dist/src/onboarding/validators/workspace.js +83 -0
  123. package/dist/src/prompts/resolver.js +106 -0
  124. package/dist/src/retrospectives/candidates.js +224 -0
  125. package/dist/src/retrospectives/derive.js +169 -0
  126. package/dist/src/retrospectives/ingest.js +321 -0
  127. package/dist/src/retrospectives/management-prs.js +380 -0
  128. package/dist/src/retrospectives/proposals.js +199 -0
  129. package/dist/src/retrospectives/scopes.js +272 -0
  130. package/dist/src/retrospectives/scoring.js +171 -0
  131. package/dist/src/retrospectives/types.js +1 -0
  132. package/dist/src/state/loader.js +473 -0
  133. package/dist/src/state/types.js +15 -0
  134. package/dist/src/tasks/derivation/builders.js +70 -0
  135. package/dist/src/tasks/derivation/handlers/comment-response.js +169 -0
  136. package/dist/src/tasks/derivation/handlers/implement-plan.js +103 -0
  137. package/dist/src/tasks/derivation/handlers/index.js +54 -0
  138. package/dist/src/tasks/derivation/handlers/packet-pr-control.js +92 -0
  139. package/dist/src/tasks/derivation/handlers/packet-worktree.js +29 -0
  140. package/dist/src/tasks/derivation/handlers/plan-breakdown.js +45 -0
  141. package/dist/src/tasks/derivation/handlers/prepare-for-merge.js +91 -0
  142. package/dist/src/tasks/derivation/handlers/product-discovery.js +36 -0
  143. package/dist/src/tasks/derivation/handlers/retry-failed-task.js +125 -0
  144. package/dist/src/tasks/derivation/handlers/review-pr.js +28 -0
  145. package/dist/src/tasks/derivation/handlers/shared.js +24 -0
  146. package/dist/src/tasks/derivation/handlers/sync-parent-branch.js +100 -0
  147. package/dist/src/tasks/derivation/handlers/wave-assessment.js +223 -0
  148. package/dist/src/tasks/derivation/handlers/write-plan.js +101 -0
  149. package/dist/src/tasks/derivation/handlers/write-product-spec.js +49 -0
  150. package/dist/src/tasks/derivation/runner.js +18 -0
  151. package/dist/src/tasks/derivation/types.js +1 -0
  152. package/dist/src/tasks/derive.js +810 -0
  153. package/dist/src/tasks/mcp-skip.js +10 -0
  154. package/dist/src/tasks/retry.js +10 -0
  155. package/dist/src/tasks/task.js +184 -0
  156. package/dist/src/tasks/types.js +1 -0
  157. package/dist/src/utils/command-runner.js +192 -0
  158. package/dist/src/utils/json.js +38 -0
  159. package/dist/src/utils/logger.js +43 -0
  160. package/dist/src/utils/paths.js +38 -0
  161. package/dist/src/utils/slug.js +10 -0
  162. package/dist/src/workflows/registry.js +174 -0
  163. package/dist/src/workflows/triage.js +90 -0
  164. package/dist/src/workflows/workstream-machine/derive-events.js +121 -0
  165. package/dist/src/workflows/workstream-machine/evaluate.js +24 -0
  166. package/dist/src/workflows/workstream-machine/events.js +1 -0
  167. package/dist/src/workflows/workstream-machine/machine.js +355 -0
  168. package/dist/src/workflows/workstream-machine/selectors.js +19 -0
  169. package/dist/src/workflows/workstream-machine/types.js +1 -0
  170. package/dist/src/workstreams/branch-sync.js +87 -0
  171. package/dist/src/workstreams/derive.js +629 -0
  172. package/dist/src/workstreams/mcp-blockers.js +420 -0
  173. package/dist/src/workstreams/packet-candidates.js +42 -0
  174. package/dist/src/workstreams/packet-lifecycle-feedback.js +60 -0
  175. package/dist/src/workstreams/packet-lifecycle-report.js +199 -0
  176. package/dist/src/workstreams/packet-plan.js +597 -0
  177. package/dist/src/workstreams/packet-projections.js +190 -0
  178. package/dist/src/workstreams/parent-branch-sync-action.js +312 -0
  179. package/dist/src/workstreams/parent-branch-sync.js +148 -0
  180. package/dist/src/workstreams/phases.js +4 -0
  181. package/dist/src/workstreams/plan-breakdown-generator/export.js +19 -0
  182. package/dist/src/workstreams/plan-breakdown-generator/index.js +6 -0
  183. package/dist/src/workstreams/plan-breakdown-generator/inspect.js +50 -0
  184. package/dist/src/workstreams/plan-breakdown-generator/patch.js +139 -0
  185. package/dist/src/workstreams/plan-breakdown-generator/session.js +19 -0
  186. package/dist/src/workstreams/plan-breakdown-generator/store.js +27 -0
  187. package/dist/src/workstreams/plan-breakdown-generator/tools.js +407 -0
  188. package/dist/src/workstreams/plan-breakdown-generator/types.js +6 -0
  189. package/dist/src/workstreams/plan-breakdown-generator/validate.js +212 -0
  190. package/dist/src/workstreams/plan-breakdown-schema.js +175 -0
  191. package/dist/src/workstreams/utils.js +15 -0
  192. package/dist/src/workstreams/wave-assessment/checkpoint.js +172 -0
  193. package/dist/src/workstreams/wave-assessment/index.js +3 -0
  194. package/dist/src/workstreams/wave-assessment/session.js +84 -0
  195. package/dist/src/workstreams/wave-assessment/store.js +51 -0
  196. package/dist/src/workstreams/wave-assessment/tools.js +419 -0
  197. package/dist/src/workstreams/wave-assessment/types.js +1 -0
  198. package/dist/src/workstreams/wave-assessment/validate.js +78 -0
  199. package/dist/src/worktrees/service.js +839 -0
  200. package/dist/src/worktrees/workspace-ownership.js +34 -0
  201. package/dist/src/worktrees/workspace-recovery.js +177 -0
  202. package/package.json +61 -0
@@ -0,0 +1,251 @@
1
+ import { mkdir, readFile, realpath, writeFile } from "node:fs/promises";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { writeJsonFileAtomic } from "../utils/json.js";
5
+ import { buildAntigravityCliArgs, ensureAntigravityMcpConfig, getAntigravityEnvOverrides, mapPermissionToAntigravityMode, prependGlobalAntigravityLaunchInstructions, writeAntigravityMcpConfig, writeAntigravitySettings } from "./antigravity-launch.js";
6
+ import { buildExecutionId, extractAndExportPlanBreakdownResult, extractAndExportWaveAssessmentResult, extractExitCode, extractReviewResult, extractStderr, extractStdout, extractTriageResult, detectProviderLimit } from "./extraction.js";
7
+ import { buildPromptPacket, renderPrompt } from "./prompt.js";
8
+ const DEFAULT_TIMEOUT_MS = 20 * 60 * 1000;
9
+ const DEFAULT_PRINT_TIMEOUT = "20m";
10
+ export class AntigravityCliAgent {
11
+ options;
12
+ now;
13
+ timeoutMs;
14
+ printTimeout;
15
+ logger;
16
+ constructor(options) {
17
+ this.options = options;
18
+ this.now = options.now ?? (() => new Date().toISOString());
19
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
20
+ this.printTimeout = options.printTimeout ?? DEFAULT_PRINT_TIMEOUT;
21
+ this.logger = options.logger;
22
+ }
23
+ async dispatch(task, context, agentConfig, _sessionDecision, promptContent) {
24
+ const promptPacket = buildPromptPacket(task, context);
25
+ const promptText = prependGlobalAntigravityLaunchInstructions(renderPrompt(task, promptPacket, {
26
+ providerIdentity: "Antigravity CLI",
27
+ customConstraints: promptContent?.content,
28
+ learningOverlays: promptContent?.learningOverlays
29
+ }), { ...(this.options.githubBroker ? { mcp: this.options.githubBroker } : {}) });
30
+ const startedAt = this.now();
31
+ const executionId = buildExecutionId(task.id, startedAt);
32
+ const artifactDir = path.join(this.options.logDir, "artifacts", executionId);
33
+ const promptPacketPath = path.join(artifactDir, "prompt-packet.json");
34
+ const promptArtifactPath = path.join(artifactDir, "prompt.md");
35
+ const stdoutArtifactPath = path.join(artifactDir, "stdout.txt");
36
+ const stderrArtifactPath = path.join(artifactDir, "stderr.txt");
37
+ const streamArtifactPath = path.join(artifactDir, "stdout-raw.txt");
38
+ const settingsArtifactPath = path.join(artifactDir, "antigravity-settings.json");
39
+ const mcpConfigArtifactPath = path.join(artifactDir, "antigravity-mcp-config.json");
40
+ const logFilePath = path.join(artifactDir, "antigravity.log");
41
+ const antigravityHome = this.options.configHome ?? path.join(os.homedir(), ".gemini", "antigravity-cli");
42
+ const legacyGeminiConfigHome = this.options.legacyGeminiConfigHome ??
43
+ (this.options.configHome ? undefined : path.join(os.homedir(), ".gemini", "config"));
44
+ const cwd = context.localRepoPath ?? process.cwd();
45
+ const permissionMode = mapPermissionToAntigravityMode(task.executionPermission);
46
+ const resumeConversationId = _sessionDecision?.mode === "native_resume_candidate"
47
+ ? _sessionDecision.record.providerSessionId
48
+ : undefined;
49
+ await mkdir(artifactDir, { recursive: true });
50
+ await Promise.all([
51
+ writeJsonFileAtomic(promptPacketPath, promptPacket),
52
+ writeFile(promptArtifactPath, promptText, "utf8"),
53
+ writeAntigravitySettings(settingsArtifactPath, {
54
+ permissionMode,
55
+ cwd,
56
+ ...(agentConfig.model ? { model: agentConfig.model } : {})
57
+ }),
58
+ writeAntigravityMcpConfig(mcpConfigArtifactPath, {
59
+ ...(this.options.githubBroker ? { mcp: this.options.githubBroker } : {})
60
+ })
61
+ ]);
62
+ const args = buildAntigravityCliArgs({
63
+ permissionMode,
64
+ ...(agentConfig.model ? { model: agentConfig.model } : {}),
65
+ ...(resumeConversationId ? { conversationId: resumeConversationId } : {}),
66
+ printTimeout: this.printTimeout,
67
+ logFilePath
68
+ });
69
+ this.logger?.info(JSON.stringify({
70
+ component: "antigravity-launch",
71
+ taskKind: task.kind,
72
+ targetRepoPath: cwd,
73
+ antigravityHome,
74
+ settingsPath: settingsArtifactPath,
75
+ mcpConfigPath: mcpConfigArtifactPath,
76
+ legacyGeminiMcpConfigPath: legacyGeminiConfigHome
77
+ ? path.join(legacyGeminiConfigHome, "mcp_config.json")
78
+ : undefined,
79
+ mcpServerUrl: this.options.githubBroker?.serverUrl
80
+ }));
81
+ let exitCode = 0;
82
+ let rawStdout = "";
83
+ let rawStderr = "";
84
+ try {
85
+ if (this.options.githubBroker) {
86
+ const setup = await ensureAntigravityMcpConfig(antigravityHome, this.options.githubBroker);
87
+ this.logger?.info(JSON.stringify({
88
+ component: "antigravity-mcp-bootstrap",
89
+ configPath: setup.configPath,
90
+ changed: setup.changed,
91
+ backupPath: setup.backupPath
92
+ }));
93
+ if (legacyGeminiConfigHome) {
94
+ const legacySetup = await ensureAntigravityMcpConfig(legacyGeminiConfigHome, this.options.githubBroker);
95
+ this.logger?.info(JSON.stringify({
96
+ component: "antigravity-legacy-gemini-mcp-bootstrap",
97
+ configPath: legacySetup.configPath,
98
+ changed: legacySetup.changed,
99
+ backupPath: legacySetup.backupPath
100
+ }));
101
+ }
102
+ }
103
+ const result = await this.options.commandRunner.run(agentConfig.command, args, {
104
+ cwd,
105
+ input: promptText,
106
+ env: getAntigravityEnvOverrides(),
107
+ timeoutMs: this.timeoutMs
108
+ });
109
+ exitCode = result.exitCode;
110
+ rawStdout = result.stdout;
111
+ rawStderr = result.stderr;
112
+ }
113
+ catch (error) {
114
+ exitCode = extractExitCode(error);
115
+ rawStdout = extractStdout(error);
116
+ rawStderr = extractStderr(error) || (error instanceof Error ? error.message : String(error));
117
+ }
118
+ const stdout = normalizeAntigravityStdout(rawStdout);
119
+ const endedAt = this.now();
120
+ const observedConversationId = await readAntigravityConversationIdForCwd(antigravityHome, cwd)
121
+ .catch((err) => {
122
+ this.logger?.warn(`[antigravity-session] Failed to read conversation id for ${cwd}: ${err instanceof Error ? err.message : String(err)}`);
123
+ return undefined;
124
+ });
125
+ const finalConversationId = observedConversationId ?? resumeConversationId;
126
+ await Promise.all([
127
+ writeFile(streamArtifactPath, rawStdout, "utf8"),
128
+ writeFile(stdoutArtifactPath, stdout, "utf8"),
129
+ writeFile(stderrArtifactPath, rawStderr, "utf8")
130
+ ]);
131
+ const triageResult = extractTriageResult(task, stdout);
132
+ const reviewResult = extractReviewResult(task, stdout);
133
+ const planBreakdownExtraction = extractAndExportPlanBreakdownResult(task, stdout, this.options.planBreakdownSessionStore);
134
+ const waveAssessmentExtraction = extractAndExportWaveAssessmentResult(task, stdout, this.options.waveAssessmentSessionStore);
135
+ let taskStatus = exitCode === 0 ? "completed" : "failed";
136
+ let errorMessage = exitCode === 0 ? undefined : rawStderr.trim() || "Antigravity CLI exited with a non-zero status.";
137
+ if (task.kind === "review_pr" && exitCode === 0 && !reviewResult) {
138
+ taskStatus = "failed";
139
+ errorMessage = "Agent output did not contain a valid structured review result.";
140
+ }
141
+ if ((task.kind === "plan_breakdown" || task.kind === "wave_plan_replanning") &&
142
+ exitCode === 0 &&
143
+ !planBreakdownExtraction.ok) {
144
+ taskStatus = "failed";
145
+ errorMessage = planBreakdownExtraction.message;
146
+ }
147
+ if (task.kind === "wave_plan_assessment" && exitCode === 0 && !waveAssessmentExtraction.ok) {
148
+ taskStatus = "failed";
149
+ errorMessage = waveAssessmentExtraction.message;
150
+ }
151
+ const execution = {
152
+ id: executionId,
153
+ taskId: task.id,
154
+ taskKind: task.kind,
155
+ taskStatus,
156
+ repoId: task.repoId,
157
+ repoSlug: task.repoSlug,
158
+ agentId: agentConfig.id,
159
+ agentProvider: "antigravity",
160
+ ...(agentConfig.model ? { agentModel: agentConfig.model } : {}),
161
+ command: {
162
+ executable: agentConfig.command,
163
+ args,
164
+ cwd,
165
+ timeoutMs: this.timeoutMs
166
+ },
167
+ promptArtifactPath,
168
+ stdoutArtifactPath,
169
+ stderrArtifactPath,
170
+ streamArtifactPath,
171
+ promptPacketPath,
172
+ settingsArtifactPath,
173
+ startedAt,
174
+ endedAt,
175
+ exitCode,
176
+ ...(errorMessage ? { errorMessage } : {}),
177
+ ...(triageResult || reviewResult || planBreakdownExtraction.ok || waveAssessmentExtraction.ok
178
+ ? {
179
+ result: {
180
+ ...(triageResult ? { triage: triageResult } : {}),
181
+ ...(reviewResult ? { review: reviewResult } : {}),
182
+ ...(planBreakdownExtraction.ok
183
+ ? {
184
+ planBreakdown: planBreakdownExtraction.planBreakdown,
185
+ planBreakdownSessionId: planBreakdownExtraction.sessionId
186
+ }
187
+ : {}),
188
+ ...(waveAssessmentExtraction.ok
189
+ ? {
190
+ waveAssessment: waveAssessmentExtraction.waveAssessment
191
+ }
192
+ : {})
193
+ }
194
+ }
195
+ : {})
196
+ };
197
+ const providerLimitSignal = detectProviderLimit("antigravity", exitCode, rawStdout, rawStderr);
198
+ return {
199
+ taskStatus: execution.taskStatus,
200
+ execution,
201
+ ...(providerLimitSignal ? { providerLimitSignal } : {}),
202
+ ...(finalConversationId
203
+ ? {
204
+ providerSession: {
205
+ providerSessionId: finalConversationId,
206
+ status: "active"
207
+ }
208
+ }
209
+ : {})
210
+ };
211
+ }
212
+ }
213
+ export function normalizeAntigravityStdout(stdout) {
214
+ return stdout
215
+ .split(/\r?\n/)
216
+ .filter((line) => line.trim() !== "--print-timeout")
217
+ .join("\n")
218
+ .trimEnd();
219
+ }
220
+ export async function readAntigravityConversationIdForCwd(antigravityHome, cwd) {
221
+ const cachePath = path.join(antigravityHome, "cache", "last_conversations.json");
222
+ let raw;
223
+ try {
224
+ raw = await readFile(cachePath, "utf8");
225
+ }
226
+ catch (err) {
227
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
228
+ return undefined;
229
+ }
230
+ throw err;
231
+ }
232
+ const parsed = JSON.parse(raw);
233
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
234
+ return undefined;
235
+ }
236
+ const conversations = parsed;
237
+ const candidates = new Set([cwd]);
238
+ try {
239
+ candidates.add(await realpath(cwd));
240
+ }
241
+ catch {
242
+ // The cwd may have been removed after the run; fall back to the literal path.
243
+ }
244
+ for (const candidate of candidates) {
245
+ const value = conversations[candidate];
246
+ if (typeof value === "string" && value.trim().length > 0) {
247
+ return value;
248
+ }
249
+ }
250
+ return undefined;
251
+ }
@@ -0,0 +1,117 @@
1
+ import * as fs from "node:fs/promises";
2
+ /**
3
+ * Maps orchestrator permissions to Claude Code CLI permission modes.
4
+ * - read-only -> dontAsk
5
+ * - workspace-write -> bypassPermissions (allows running local bash commands like npm, docker-compose)
6
+ */
7
+ export function mapPermissionToClaudePermissionMode(permission) {
8
+ return permission === "workspace-write" ? "bypassPermissions" : "dontAsk";
9
+ }
10
+ /**
11
+ * Builds CLI arguments for Claude Code CLI.
12
+ * Uses -p for print mode (non-interactive).
13
+ */
14
+ export function buildClaudeCliArgs(options) {
15
+ const args = ["-p", "--permission-mode", options.permissionMode];
16
+ if (options.resumeSessionId) {
17
+ args.push("--resume", options.resumeSessionId);
18
+ }
19
+ else if (options.plannedSessionId) {
20
+ args.push("--session-id", options.plannedSessionId);
21
+ if (options.sessionName) {
22
+ args.push("--name", options.sessionName);
23
+ }
24
+ }
25
+ if (options.model) {
26
+ args.push("--model", options.model);
27
+ }
28
+ if (options.settingsPath) {
29
+ args.push("--settings", options.settingsPath);
30
+ }
31
+ if (options.mcpConfigPath) {
32
+ args.push("--mcp-config", options.mcpConfigPath, "--strict-mcp-config");
33
+ }
34
+ if (options.allowedTools && options.allowedTools.length > 0) {
35
+ args.push("--allowedTools", options.allowedTools.join(","));
36
+ }
37
+ return args;
38
+ }
39
+ /**
40
+ * Builds the MCP configuration object for Claude.
41
+ */
42
+ export function buildClaudeMcpConfig(mcp) {
43
+ return {
44
+ mcpServers: {
45
+ [mcp.serverName]: {
46
+ type: "http",
47
+ url: mcp.serverUrl
48
+ }
49
+ }
50
+ };
51
+ }
52
+ /**
53
+ * Builds the settings configuration object for Claude.
54
+ */
55
+ export function buildClaudeSettings(options) {
56
+ if (options.workspacePath) {
57
+ return { trustedWorkspaces: [options.workspacePath] };
58
+ }
59
+ return {};
60
+ }
61
+ /**
62
+ * Writes the MCP configuration to a file.
63
+ */
64
+ export async function writeClaudeMcpConfig(path, mcp) {
65
+ const config = buildClaudeMcpConfig(mcp);
66
+ await fs.writeFile(path, JSON.stringify(config, null, 2));
67
+ }
68
+ /**
69
+ * Writes the settings configuration to a file.
70
+ */
71
+ export async function writeClaudeSettings(path, options) {
72
+ const settings = buildClaudeSettings(options);
73
+ await fs.writeFile(path, JSON.stringify(settings, null, 2));
74
+ }
75
+ /**
76
+ * Generates the allow-list of tool names for Claude's --allowedTools flag.
77
+ * Names follow the mcp__<serverName>__<toolName> pattern.
78
+ */
79
+ export function buildClaudeAllowedTools(mcp) {
80
+ return mcp.toolAllowList.map((tool) => {
81
+ const normalizedToolName = tool.replaceAll(".", "_");
82
+ return `mcp__${mcp.serverName}__${normalizedToolName}`;
83
+ });
84
+ }
85
+ /**
86
+ * Provides global launch instructions for Claude agents.
87
+ */
88
+ export function buildGlobalClaudeLaunchInstructions(input) {
89
+ const tools = buildClaudeAllowedTools(input.mcp).join(", ");
90
+ return `
91
+ # Claude Code CLI Launch Instructions
92
+
93
+ You are running inside a managed orchestrator.
94
+ A local MCP server named "${input.mcp.serverName}" is available and pre-authorized.
95
+ The following tools are allowed: ${tools}.
96
+
97
+ ## Operational Rules
98
+
99
+ 1. Use the local MCP server for all GitHub operations (issues, PRs, comments, code search).
100
+ 2. Do not use the "gh" CLI or other external GitHub tools directly for these tasks.
101
+ 3. If a required GitHub capability is missing from the MCP server, report it as a limitation rather than attempting a bypass.
102
+ 4. Adhere to the repository-local AGENTS.md instructions if present.
103
+ 5. Any message you post to GitHub (via comments or PR descriptions) MUST begin with the "🤖" emoji.
104
+
105
+ Ready for task execution.
106
+ `.trim();
107
+ }
108
+ /**
109
+ * Prepends global instructions to a prompt if MCP is configured.
110
+ */
111
+ export function prependGlobalClaudeLaunchInstructions(promptText, options) {
112
+ if (!options.mcp) {
113
+ return promptText;
114
+ }
115
+ const instructions = buildGlobalClaudeLaunchInstructions({ mcp: options.mcp });
116
+ return `${instructions}\n\n${promptText}`;
117
+ }
@@ -0,0 +1,271 @@
1
+ import { copyFile, mkdir, readdir, stat, writeFile } from "node:fs/promises";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { writeJsonFileAtomic } from "../utils/json.js";
5
+ import { buildClaudeAllowedTools, buildClaudeCliArgs, mapPermissionToClaudePermissionMode, prependGlobalClaudeLaunchInstructions, writeClaudeMcpConfig, writeClaudeSettings } from "./claude-launch.js";
6
+ import { buildExecutionId, extractExitCode, extractAndExportPlanBreakdownResult, extractAndExportWaveAssessmentResult, extractReviewResult, extractStderr, extractStdout, extractTriageResult, detectProviderLimit } from "./extraction.js";
7
+ import { buildPromptPacket, renderPrompt } from "./prompt.js";
8
+ const DEFAULT_TIMEOUT_MS = 20 * 60 * 1000;
9
+ export class ClaudeCodeCliAgent {
10
+ options;
11
+ now;
12
+ timeoutMs;
13
+ logger;
14
+ constructor(options) {
15
+ this.options = options;
16
+ this.now = options.now ?? (() => new Date().toISOString());
17
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
18
+ this.logger = options.logger;
19
+ }
20
+ async dispatch(task, context, agentConfig, sessionDecision, promptContent) {
21
+ const promptPacket = buildPromptPacket(task, context);
22
+ const promptText = prependGlobalClaudeLaunchInstructions(renderPrompt(task, promptPacket, {
23
+ providerIdentity: "Claude Code CLI",
24
+ customConstraints: promptContent?.content,
25
+ learningOverlays: promptContent?.learningOverlays
26
+ }), { ...(this.options.githubBroker ? { mcp: this.options.githubBroker } : {}) });
27
+ const startedAt = this.now();
28
+ const executionId = buildExecutionId(task.id, startedAt);
29
+ const artifactDir = path.join(this.options.logDir, "artifacts", executionId);
30
+ const promptPacketPath = path.join(artifactDir, "prompt-packet.json");
31
+ const promptArtifactPath = path.join(artifactDir, "prompt.md");
32
+ const stdoutArtifactPath = path.join(artifactDir, "stdout.txt");
33
+ const stderrArtifactPath = path.join(artifactDir, "stderr.txt");
34
+ const providerTranscriptArtifactPath = path.join(artifactDir, "claude-project-transcript.jsonl");
35
+ const mcpConfigPath = path.join(artifactDir, "claude-mcp-config.json");
36
+ const settingsArtifactPath = path.join(artifactDir, "claude-settings.json");
37
+ await mkdir(artifactDir, { recursive: true });
38
+ const cwd = context.localRepoPath ?? process.cwd();
39
+ const writeTasks = [
40
+ writeJsonFileAtomic(promptPacketPath, promptPacket),
41
+ writeFile(promptArtifactPath, promptText, "utf8")
42
+ ];
43
+ if (this.options.githubBroker) {
44
+ writeTasks.push(writeClaudeMcpConfig(mcpConfigPath, this.options.githubBroker));
45
+ writeTasks.push(writeClaudeSettings(settingsArtifactPath, {
46
+ mcp: this.options.githubBroker,
47
+ workspacePath: cwd
48
+ }));
49
+ }
50
+ await Promise.all(writeTasks);
51
+ const permissionMode = mapPermissionToClaudePermissionMode(task.executionPermission);
52
+ const resumeSessionId = sessionDecision?.mode === "native_resume_candidate"
53
+ ? sessionDecision.record.providerSessionId
54
+ : undefined;
55
+ const plannedSessionId = sessionDecision?.mode === "new_session"
56
+ ? sessionDecision.providerSessionId
57
+ : undefined;
58
+ const sessionName = sessionDecision?.mode === "new_session"
59
+ ? sessionDecision.providerSessionName
60
+ : undefined;
61
+ const args = buildClaudeCliArgs({
62
+ permissionMode,
63
+ resumeSessionId,
64
+ plannedSessionId,
65
+ sessionName,
66
+ ...(agentConfig.model ? { model: agentConfig.model } : {}),
67
+ ...(this.options.githubBroker
68
+ ? {
69
+ mcpConfigPath,
70
+ settingsPath: settingsArtifactPath,
71
+ allowedTools: buildClaudeAllowedTools(this.options.githubBroker)
72
+ }
73
+ : {})
74
+ });
75
+ this.logger?.info(JSON.stringify({
76
+ component: "claude-launch",
77
+ taskKind: task.kind,
78
+ targetRepoPath: cwd,
79
+ mcpConfigPath: this.options.githubBroker ? mcpConfigPath : undefined,
80
+ mcpServerUrl: this.options.githubBroker?.serverUrl
81
+ }));
82
+ let exitCode = 0;
83
+ let stdout = "";
84
+ let stderr = "";
85
+ try {
86
+ const result = await this.options.commandRunner.run(agentConfig.command, args, {
87
+ cwd,
88
+ input: promptText,
89
+ timeoutMs: this.timeoutMs
90
+ });
91
+ exitCode = result.exitCode;
92
+ stdout = result.stdout;
93
+ stderr = result.stderr;
94
+ }
95
+ catch (error) {
96
+ exitCode = extractExitCode(error);
97
+ stdout = extractStdout(error);
98
+ stderr = extractStderr(error);
99
+ }
100
+ const endedAt = this.now();
101
+ const originalStderr = stderr;
102
+ const transcriptCapture = await captureClaudeProjectTranscript({
103
+ cwd,
104
+ startedAt,
105
+ artifactPath: providerTranscriptArtifactPath,
106
+ ...(this.options.claudeProjectsDir ? { projectsDir: this.options.claudeProjectsDir } : {}),
107
+ ...(this.logger ? { logger: this.logger } : {})
108
+ });
109
+ if (transcriptCapture && exitCode !== 0 && stderr.trim() === "") {
110
+ stderr = [
111
+ `Claude project transcript captured at ${providerTranscriptArtifactPath}`,
112
+ `Source: ${transcriptCapture.sourcePath}`
113
+ ].join("\n");
114
+ }
115
+ await Promise.all([
116
+ writeFile(stdoutArtifactPath, stdout, "utf8"),
117
+ writeFile(stderrArtifactPath, stderr, "utf8")
118
+ ]);
119
+ const triageResult = extractTriageResult(task, stdout);
120
+ const reviewResult = extractReviewResult(task, stdout);
121
+ const planBreakdownExtraction = extractAndExportPlanBreakdownResult(task, stdout, this.options.planBreakdownSessionStore);
122
+ const waveAssessmentExtraction = extractAndExportWaveAssessmentResult(task, stdout, this.options.waveAssessmentSessionStore);
123
+ let taskStatus = exitCode === 0 ? "completed" : "failed";
124
+ let errorMessage = exitCode === 0 ? undefined : originalStderr.trim() || "Claude Code CLI exited with a non-zero status.";
125
+ if (task.kind === "review_pr" && exitCode === 0 && !reviewResult) {
126
+ taskStatus = "failed";
127
+ errorMessage = "Agent output did not contain a valid structured review result.";
128
+ }
129
+ if ((task.kind === "plan_breakdown" || task.kind === "wave_plan_replanning") && exitCode === 0) {
130
+ if (!planBreakdownExtraction.ok) {
131
+ taskStatus = "failed";
132
+ errorMessage = planBreakdownExtraction.message;
133
+ }
134
+ }
135
+ if (task.kind === "wave_plan_assessment" && exitCode === 0) {
136
+ if (!waveAssessmentExtraction.ok) {
137
+ taskStatus = "failed";
138
+ errorMessage = waveAssessmentExtraction.message;
139
+ }
140
+ }
141
+ const execution = {
142
+ id: executionId,
143
+ taskId: task.id,
144
+ taskKind: task.kind,
145
+ taskStatus,
146
+ repoId: task.repoId,
147
+ repoSlug: task.repoSlug,
148
+ agentId: agentConfig.id,
149
+ agentProvider: "claude",
150
+ ...(agentConfig.model ? { agentModel: agentConfig.model } : {}),
151
+ command: {
152
+ executable: agentConfig.command,
153
+ args,
154
+ cwd,
155
+ timeoutMs: this.timeoutMs
156
+ },
157
+ promptArtifactPath,
158
+ stdoutArtifactPath,
159
+ stderrArtifactPath,
160
+ ...(transcriptCapture ? { providerTranscriptArtifactPath } : {}),
161
+ promptPacketPath,
162
+ ...(this.options.githubBroker ? { settingsArtifactPath } : {}),
163
+ startedAt,
164
+ endedAt,
165
+ exitCode,
166
+ ...(errorMessage ? { errorMessage } : {}),
167
+ ...(triageResult || reviewResult || planBreakdownExtraction.ok || waveAssessmentExtraction.ok
168
+ ? {
169
+ result: {
170
+ ...(triageResult ? { triage: triageResult } : {}),
171
+ ...(reviewResult ? { review: reviewResult } : {}),
172
+ ...(planBreakdownExtraction.ok
173
+ ? {
174
+ planBreakdown: planBreakdownExtraction.planBreakdown,
175
+ planBreakdownSessionId: planBreakdownExtraction.sessionId
176
+ }
177
+ : {}),
178
+ ...(waveAssessmentExtraction.ok
179
+ ? {
180
+ waveAssessment: waveAssessmentExtraction.waveAssessment
181
+ }
182
+ : {})
183
+ }
184
+ }
185
+ : {})
186
+ };
187
+ const parsedSessionId = extractSessionIdFromClaudeOutput(stdout);
188
+ const finalSessionId = parsedSessionId || plannedSessionId || resumeSessionId;
189
+ const finalSessionName = parsedSessionId ? undefined : (sessionName || (sessionDecision?.mode === "native_resume_candidate" ? sessionDecision.record.providerSessionName : undefined));
190
+ const providerLimitSignal = detectProviderLimit("claude", exitCode, stdout, stderr);
191
+ return {
192
+ taskStatus: execution.taskStatus,
193
+ execution,
194
+ ...(providerLimitSignal ? { providerLimitSignal } : {}),
195
+ ...(finalSessionId
196
+ ? {
197
+ providerSession: {
198
+ providerSessionId: finalSessionId,
199
+ status: "active",
200
+ ...(finalSessionName ? { providerSessionName: finalSessionName } : {})
201
+ }
202
+ }
203
+ : {})
204
+ };
205
+ }
206
+ }
207
+ async function captureClaudeProjectTranscript(input) {
208
+ const projectsDir = input.projectsDir ?? path.join(os.homedir(), ".claude", "projects");
209
+ const projectDir = path.join(projectsDir, encodeClaudeProjectPath(input.cwd));
210
+ const startedAtMs = Date.parse(input.startedAt);
211
+ const minMtimeMs = Number.isFinite(startedAtMs) ? startedAtMs - 5_000 : 0;
212
+ try {
213
+ const entries = await readdir(projectDir, { withFileTypes: true });
214
+ const candidates = [];
215
+ for (const entry of entries) {
216
+ if (!entry.isFile() || !entry.name.endsWith(".jsonl")) {
217
+ continue;
218
+ }
219
+ const filePath = path.join(projectDir, entry.name);
220
+ const fileStat = await stat(filePath);
221
+ if (fileStat.mtimeMs >= minMtimeMs) {
222
+ candidates.push({ filePath, mtimeMs: fileStat.mtimeMs });
223
+ }
224
+ }
225
+ candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
226
+ const latest = candidates[0];
227
+ if (!latest) {
228
+ return undefined;
229
+ }
230
+ await copyFile(latest.filePath, input.artifactPath);
231
+ return { sourcePath: latest.filePath };
232
+ }
233
+ catch (error) {
234
+ const code = error.code;
235
+ if (code !== "ENOENT") {
236
+ input.logger?.warn(`[claude-transcript] Failed to capture Claude project transcript for ${input.cwd}: ${error instanceof Error ? error.message : String(error)}`);
237
+ }
238
+ return undefined;
239
+ }
240
+ }
241
+ function encodeClaudeProjectPath(cwd) {
242
+ return path.resolve(cwd).replace(/[^A-Za-z0-9]/g, "-");
243
+ }
244
+ function extractSessionIdFromClaudeOutput(stdoutText) {
245
+ if (!stdoutText.trim()) {
246
+ return undefined;
247
+ }
248
+ try {
249
+ const parsed = JSON.parse(stdoutText);
250
+ if (parsed && typeof parsed === "object" && typeof parsed.session_id === "string") {
251
+ return parsed.session_id;
252
+ }
253
+ }
254
+ catch {
255
+ // Substring parse candidate
256
+ const start = stdoutText.indexOf("{");
257
+ const end = stdoutText.lastIndexOf("}");
258
+ if (start !== -1 && end !== -1 && end > start) {
259
+ try {
260
+ const parsed = JSON.parse(stdoutText.substring(start, end + 1));
261
+ if (parsed && typeof parsed === "object" && typeof parsed.session_id === "string") {
262
+ return parsed.session_id;
263
+ }
264
+ }
265
+ catch {
266
+ // ignore
267
+ }
268
+ }
269
+ }
270
+ return undefined;
271
+ }