bitbucket-gemini-action 1.0.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.
- package/.claude/settings.local.json +8 -0
- package/.prettierrc +8 -0
- package/CLAUDE.md +150 -0
- package/README.md +375 -0
- package/bitbucket-pipelines.yml +95 -0
- package/bun.lock +227 -0
- package/dist/prepare.js +7111 -0
- package/examples/bitbucket-pipelines-full.yml +157 -0
- package/examples/bitbucket-pipelines-minimal.yml +22 -0
- package/package.json +33 -0
- package/src/bitbucket/api/client.ts +406 -0
- package/src/bitbucket/context.ts +196 -0
- package/src/bitbucket/data/fetcher.ts +195 -0
- package/src/bitbucket/data/formatter.ts +221 -0
- package/src/bitbucket/operations/comments.ts +236 -0
- package/src/bitbucket/types.ts +262 -0
- package/src/bitbucket/validation/permissions.ts +154 -0
- package/src/bitbucket/validation/trigger.ts +175 -0
- package/src/entrypoints/execute.ts +349 -0
- package/src/entrypoints/prepare.ts +216 -0
- package/src/gemini/client.ts +263 -0
- package/src/gemini/presets.ts +2130 -0
- package/src/gemini/prompts.ts +331 -0
- package/src/gemini/tools.ts +226 -0
- package/src/index.ts +71 -0
- package/src/modes/agent/index.ts +119 -0
- package/src/modes/registry.ts +118 -0
- package/src/modes/tag/index.ts +172 -0
- package/src/modes/types.ts +95 -0
- package/src/utils/env.ts +190 -0
- package/src/utils/retry.ts +149 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trigger Validation
|
|
3
|
+
* Validates if a trigger should activate the action
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ParsedBitbucketContext, BitbucketComment } from "../types.js";
|
|
7
|
+
|
|
8
|
+
export interface TriggerValidationResult {
|
|
9
|
+
shouldTrigger: boolean;
|
|
10
|
+
reason: string;
|
|
11
|
+
triggerType?: "mention" | "label" | "assignee" | "automation" | "manual";
|
|
12
|
+
userMessage?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TriggerOptions {
|
|
16
|
+
triggerPhrase: string;
|
|
17
|
+
labelTrigger?: string;
|
|
18
|
+
assigneeTrigger?: string;
|
|
19
|
+
prompt?: string;
|
|
20
|
+
botAccountId?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validate if the context should trigger the action
|
|
25
|
+
*/
|
|
26
|
+
export function validateTrigger(
|
|
27
|
+
context: ParsedBitbucketContext,
|
|
28
|
+
options: TriggerOptions
|
|
29
|
+
): TriggerValidationResult {
|
|
30
|
+
// Explicit prompt always triggers (agent mode)
|
|
31
|
+
if (options.prompt) {
|
|
32
|
+
return {
|
|
33
|
+
shouldTrigger: true,
|
|
34
|
+
reason: "Explicit prompt provided",
|
|
35
|
+
triggerType: "automation",
|
|
36
|
+
userMessage: options.prompt,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check for manual/schedule triggers
|
|
41
|
+
if (
|
|
42
|
+
context.eventType === "manual" ||
|
|
43
|
+
context.eventType === "schedule"
|
|
44
|
+
) {
|
|
45
|
+
return {
|
|
46
|
+
shouldTrigger: true,
|
|
47
|
+
reason: `Triggered by ${context.eventType} event`,
|
|
48
|
+
triggerType: "manual",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check comment-based triggers
|
|
53
|
+
if (
|
|
54
|
+
context.eventType === "pullrequest:comment_created" ||
|
|
55
|
+
context.eventType === "pullrequest:comment_updated"
|
|
56
|
+
) {
|
|
57
|
+
return validateCommentTrigger(context.comment, options);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// No trigger matched
|
|
61
|
+
return {
|
|
62
|
+
shouldTrigger: false,
|
|
63
|
+
reason: `Event type ${context.eventType} does not match any trigger`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Validate comment-based trigger
|
|
69
|
+
*/
|
|
70
|
+
function validateCommentTrigger(
|
|
71
|
+
comment: BitbucketComment | undefined,
|
|
72
|
+
options: TriggerOptions
|
|
73
|
+
): TriggerValidationResult {
|
|
74
|
+
if (!comment) {
|
|
75
|
+
return {
|
|
76
|
+
shouldTrigger: false,
|
|
77
|
+
reason: "No comment in context",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Don't respond to own comments (prevent infinite loops)
|
|
82
|
+
if (options.botAccountId && comment.user.account_id === options.botAccountId) {
|
|
83
|
+
return {
|
|
84
|
+
shouldTrigger: false,
|
|
85
|
+
reason: "Comment is from the bot itself",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check for trigger phrase
|
|
90
|
+
const content = comment.content.raw;
|
|
91
|
+
const triggerPhrase = options.triggerPhrase.toLowerCase();
|
|
92
|
+
|
|
93
|
+
if (!content.toLowerCase().includes(triggerPhrase)) {
|
|
94
|
+
return {
|
|
95
|
+
shouldTrigger: false,
|
|
96
|
+
reason: `Comment does not contain trigger phrase: ${options.triggerPhrase}`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Extract user message (remove trigger phrase)
|
|
101
|
+
const userMessage = extractUserMessage(content, options.triggerPhrase);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
shouldTrigger: true,
|
|
105
|
+
reason: "Trigger phrase found in comment",
|
|
106
|
+
triggerType: "mention",
|
|
107
|
+
userMessage,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Extract user message from comment content
|
|
113
|
+
*/
|
|
114
|
+
function extractUserMessage(content: string, triggerPhrase: string): string {
|
|
115
|
+
// Remove trigger phrase (case insensitive)
|
|
116
|
+
const regex = new RegExp(escapeRegex(triggerPhrase), "gi");
|
|
117
|
+
const cleaned = content.replace(regex, "").trim();
|
|
118
|
+
|
|
119
|
+
// Remove leading/trailing whitespace and newlines
|
|
120
|
+
return cleaned.replace(/^\s+|\s+$/g, "");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Escape special regex characters
|
|
125
|
+
*/
|
|
126
|
+
function escapeRegex(str: string): string {
|
|
127
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Check if comment was modified after trigger time
|
|
132
|
+
*/
|
|
133
|
+
export function isCommentModifiedAfterTrigger(
|
|
134
|
+
comment: BitbucketComment,
|
|
135
|
+
triggerTimestamp: string
|
|
136
|
+
): boolean {
|
|
137
|
+
const triggerTime = new Date(triggerTimestamp).getTime();
|
|
138
|
+
const updatedTime = new Date(comment.updated_on).getTime();
|
|
139
|
+
const createdTime = new Date(comment.created_on).getTime();
|
|
140
|
+
|
|
141
|
+
// If updated after created and after trigger, it was modified
|
|
142
|
+
return updatedTime > createdTime && updatedTime > triggerTime;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Validate comment content for safety
|
|
147
|
+
*/
|
|
148
|
+
export function validateCommentContent(content: string): {
|
|
149
|
+
safe: boolean;
|
|
150
|
+
warnings: string[];
|
|
151
|
+
} {
|
|
152
|
+
const warnings: string[] = [];
|
|
153
|
+
|
|
154
|
+
// Check for potential injection attempts
|
|
155
|
+
const suspiciousPatterns = [
|
|
156
|
+
/```system/i,
|
|
157
|
+
/\[INST\]/i,
|
|
158
|
+
/<\|im_start\|>/i,
|
|
159
|
+
/<\|im_end\|>/i,
|
|
160
|
+
/You are now/i,
|
|
161
|
+
/Ignore previous/i,
|
|
162
|
+
/Forget all/i,
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
for (const pattern of suspiciousPatterns) {
|
|
166
|
+
if (pattern.test(content)) {
|
|
167
|
+
warnings.push(`Suspicious pattern detected: ${pattern.toString()}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
safe: warnings.length === 0,
|
|
173
|
+
warnings,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execute Entrypoint
|
|
3
|
+
* Phase 2: Run Gemini and process results
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createGeminiClientFromEnv } from "../gemini/client.js";
|
|
7
|
+
import { createBitbucketClientFromEnv } from "../bitbucket/api/client.js";
|
|
8
|
+
import { parseBitbucketContext } from "../bitbucket/context.js";
|
|
9
|
+
import { fetchPRData } from "../bitbucket/data/fetcher.js";
|
|
10
|
+
import { formatPRContext } from "../bitbucket/data/formatter.js";
|
|
11
|
+
import {
|
|
12
|
+
buildPRReviewPrompt,
|
|
13
|
+
buildTagModePrompt,
|
|
14
|
+
buildAgentModePrompt,
|
|
15
|
+
buildPRReviewPromptWithPresets,
|
|
16
|
+
buildTagModePromptWithPresets,
|
|
17
|
+
buildAgentModePromptWithPresets,
|
|
18
|
+
buildSystemPromptWithPresets,
|
|
19
|
+
} from "../gemini/prompts.js";
|
|
20
|
+
import { getToolsForMode } from "../gemini/tools.js";
|
|
21
|
+
import {
|
|
22
|
+
updateTrackingComment,
|
|
23
|
+
createInlineComment,
|
|
24
|
+
createPRComment,
|
|
25
|
+
} from "../bitbucket/operations/comments.js";
|
|
26
|
+
import { getReviewPresets, getCustomPrompt } from "../utils/env.js";
|
|
27
|
+
import { withRetry, GEMINI_RETRY_OPTIONS } from "../utils/retry.js";
|
|
28
|
+
import type { GeminiToolCall } from "../gemini/client.js";
|
|
29
|
+
|
|
30
|
+
interface ExecuteOptions {
|
|
31
|
+
mode: "tag" | "agent";
|
|
32
|
+
prompt: string;
|
|
33
|
+
systemPrompt: string;
|
|
34
|
+
trackingCommentId?: number;
|
|
35
|
+
userMessage?: string;
|
|
36
|
+
mentionAuthor?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ExecuteResult {
|
|
40
|
+
success: boolean;
|
|
41
|
+
response?: string;
|
|
42
|
+
toolCalls?: GeminiToolCall[];
|
|
43
|
+
inlineCommentsCreated: number;
|
|
44
|
+
error?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Main execute function
|
|
49
|
+
*/
|
|
50
|
+
export async function execute(options?: ExecuteOptions): Promise<ExecuteResult> {
|
|
51
|
+
console.log("🚀 Starting Bitbucket Gemini Action - Execute Phase");
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// Load options from prepare phase if not provided
|
|
55
|
+
const opts = options || loadPrepareOutput();
|
|
56
|
+
console.log(`📋 Mode: ${opts.mode}`);
|
|
57
|
+
|
|
58
|
+
// Create clients
|
|
59
|
+
const geminiClient = createGeminiClientFromEnv();
|
|
60
|
+
const bitbucketClient = createBitbucketClientFromEnv();
|
|
61
|
+
|
|
62
|
+
// Parse context
|
|
63
|
+
const context = parseBitbucketContext();
|
|
64
|
+
|
|
65
|
+
// Update tracking comment to show progress
|
|
66
|
+
if (opts.trackingCommentId && context.entityNumber) {
|
|
67
|
+
await updateTrackingComment(
|
|
68
|
+
bitbucketClient,
|
|
69
|
+
context.workspace,
|
|
70
|
+
context.repoSlug,
|
|
71
|
+
context.entityNumber,
|
|
72
|
+
opts.trackingCommentId,
|
|
73
|
+
{
|
|
74
|
+
status: "in_progress",
|
|
75
|
+
message: "Analyzing code changes...",
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Fetch PR data
|
|
81
|
+
let prContext;
|
|
82
|
+
if (context.isPR && context.entityNumber) {
|
|
83
|
+
console.log("📥 Fetching PR data...");
|
|
84
|
+
const prData = await fetchPRData(bitbucketClient, context);
|
|
85
|
+
prContext = formatPRContext(prData);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Get review presets and custom prompt
|
|
89
|
+
const presetKeys = getReviewPresets();
|
|
90
|
+
const customPrompt = getCustomPrompt();
|
|
91
|
+
|
|
92
|
+
if (presetKeys.length > 0) {
|
|
93
|
+
console.log(`📋 Review Presets: ${presetKeys.join(", ")}`);
|
|
94
|
+
}
|
|
95
|
+
if (customPrompt) {
|
|
96
|
+
console.log(`📋 Custom Prompt: ${customPrompt.substring(0, 50)}...`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Build prompt based on mode (with presets if configured)
|
|
100
|
+
let fullPrompt: string;
|
|
101
|
+
if (opts.mode === "tag" && prContext) {
|
|
102
|
+
fullPrompt = presetKeys.length > 0 || customPrompt
|
|
103
|
+
? buildTagModePromptWithPresets(
|
|
104
|
+
prContext,
|
|
105
|
+
opts.userMessage || opts.prompt,
|
|
106
|
+
opts.mentionAuthor || "user",
|
|
107
|
+
presetKeys,
|
|
108
|
+
customPrompt
|
|
109
|
+
)
|
|
110
|
+
: buildTagModePrompt(
|
|
111
|
+
prContext,
|
|
112
|
+
opts.userMessage || opts.prompt,
|
|
113
|
+
opts.mentionAuthor || "user"
|
|
114
|
+
);
|
|
115
|
+
} else if (opts.mode === "agent" && prContext) {
|
|
116
|
+
fullPrompt = presetKeys.length > 0 || customPrompt
|
|
117
|
+
? buildAgentModePromptWithPresets(prContext, opts.prompt, presetKeys, customPrompt)
|
|
118
|
+
: buildAgentModePrompt(prContext, opts.prompt);
|
|
119
|
+
} else if (prContext) {
|
|
120
|
+
fullPrompt = presetKeys.length > 0 || customPrompt
|
|
121
|
+
? buildPRReviewPromptWithPresets(prContext, presetKeys, customPrompt)
|
|
122
|
+
: buildPRReviewPrompt(prContext);
|
|
123
|
+
} else {
|
|
124
|
+
fullPrompt = opts.prompt;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Apply presets to system prompt if configured
|
|
128
|
+
const systemPrompt = presetKeys.length > 0 || customPrompt
|
|
129
|
+
? buildSystemPromptWithPresets(opts.systemPrompt, presetKeys, customPrompt)
|
|
130
|
+
: opts.systemPrompt;
|
|
131
|
+
|
|
132
|
+
// Get tools for mode
|
|
133
|
+
const tools = getToolsForMode(opts.mode === "agent" ? "full" : "lightweight");
|
|
134
|
+
|
|
135
|
+
console.log("🤖 Calling Gemini API...");
|
|
136
|
+
|
|
137
|
+
// Call Gemini with retry
|
|
138
|
+
const response = await withRetry(
|
|
139
|
+
() =>
|
|
140
|
+
geminiClient.generateWithTools(fullPrompt, tools, systemPrompt),
|
|
141
|
+
GEMINI_RETRY_OPTIONS
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
console.log(`✅ Gemini response received (${response.finishReason})`);
|
|
145
|
+
|
|
146
|
+
// Process tool calls
|
|
147
|
+
let inlineCommentsCreated = 0;
|
|
148
|
+
if (response.toolCalls && context.entityNumber) {
|
|
149
|
+
inlineCommentsCreated = await processToolCalls(
|
|
150
|
+
bitbucketClient,
|
|
151
|
+
context.workspace,
|
|
152
|
+
context.repoSlug,
|
|
153
|
+
context.entityNumber,
|
|
154
|
+
response.toolCalls,
|
|
155
|
+
opts.trackingCommentId
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// If no tool calls but we have a text response, post it as a comment
|
|
160
|
+
if (
|
|
161
|
+
!response.toolCalls?.length &&
|
|
162
|
+
response.text &&
|
|
163
|
+
context.entityNumber
|
|
164
|
+
) {
|
|
165
|
+
await createPRComment(
|
|
166
|
+
bitbucketClient,
|
|
167
|
+
context.workspace,
|
|
168
|
+
context.repoSlug,
|
|
169
|
+
context.entityNumber,
|
|
170
|
+
response.text
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Update tracking comment with final status
|
|
175
|
+
if (opts.trackingCommentId && context.entityNumber) {
|
|
176
|
+
await updateTrackingComment(
|
|
177
|
+
bitbucketClient,
|
|
178
|
+
context.workspace,
|
|
179
|
+
context.repoSlug,
|
|
180
|
+
context.entityNumber,
|
|
181
|
+
opts.trackingCommentId,
|
|
182
|
+
{
|
|
183
|
+
status: "completed",
|
|
184
|
+
message: "Review completed successfully.",
|
|
185
|
+
summary: response.text.substring(0, 500),
|
|
186
|
+
inlineCommentsCount: inlineCommentsCreated,
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log("✅ Execute phase completed successfully");
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
success: true,
|
|
195
|
+
response: response.text,
|
|
196
|
+
toolCalls: response.toolCalls,
|
|
197
|
+
inlineCommentsCreated,
|
|
198
|
+
};
|
|
199
|
+
} catch (error) {
|
|
200
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
201
|
+
console.error(`❌ Execute phase failed: ${message}`);
|
|
202
|
+
|
|
203
|
+
// Try to update tracking comment with error
|
|
204
|
+
try {
|
|
205
|
+
const context = parseBitbucketContext();
|
|
206
|
+
const bitbucketClient = createBitbucketClientFromEnv();
|
|
207
|
+
const opts = options || loadPrepareOutput();
|
|
208
|
+
|
|
209
|
+
if (opts.trackingCommentId && context.entityNumber) {
|
|
210
|
+
await updateTrackingComment(
|
|
211
|
+
bitbucketClient,
|
|
212
|
+
context.workspace,
|
|
213
|
+
context.repoSlug,
|
|
214
|
+
context.entityNumber,
|
|
215
|
+
opts.trackingCommentId,
|
|
216
|
+
{
|
|
217
|
+
status: "failed",
|
|
218
|
+
error: message,
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
} catch {
|
|
223
|
+
// Ignore errors updating tracking comment
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
success: false,
|
|
228
|
+
inlineCommentsCreated: 0,
|
|
229
|
+
error: message,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Process Gemini tool calls
|
|
236
|
+
*/
|
|
237
|
+
async function processToolCalls(
|
|
238
|
+
client: ReturnType<typeof createBitbucketClientFromEnv>,
|
|
239
|
+
workspace: string,
|
|
240
|
+
repoSlug: string,
|
|
241
|
+
prId: number,
|
|
242
|
+
toolCalls: GeminiToolCall[],
|
|
243
|
+
trackingCommentId?: number
|
|
244
|
+
): Promise<number> {
|
|
245
|
+
let inlineCommentsCreated = 0;
|
|
246
|
+
|
|
247
|
+
for (const toolCall of toolCalls) {
|
|
248
|
+
console.log(`🔧 Processing tool call: ${toolCall.name}`);
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
switch (toolCall.name) {
|
|
252
|
+
case "create_inline_comment": {
|
|
253
|
+
const args = toolCall.args as {
|
|
254
|
+
path: string;
|
|
255
|
+
line: number;
|
|
256
|
+
content: string;
|
|
257
|
+
};
|
|
258
|
+
await createInlineComment(
|
|
259
|
+
client,
|
|
260
|
+
workspace,
|
|
261
|
+
repoSlug,
|
|
262
|
+
prId,
|
|
263
|
+
args.path,
|
|
264
|
+
args.line,
|
|
265
|
+
args.content
|
|
266
|
+
);
|
|
267
|
+
inlineCommentsCreated++;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
case "create_pr_comment": {
|
|
272
|
+
const args = toolCall.args as { content: string };
|
|
273
|
+
await createPRComment(client, workspace, repoSlug, prId, args.content);
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
case "update_tracking_comment": {
|
|
278
|
+
if (trackingCommentId) {
|
|
279
|
+
const args = toolCall.args as {
|
|
280
|
+
content: string;
|
|
281
|
+
status: "in_progress" | "completed" | "failed";
|
|
282
|
+
};
|
|
283
|
+
await updateTrackingComment(
|
|
284
|
+
client,
|
|
285
|
+
workspace,
|
|
286
|
+
repoSlug,
|
|
287
|
+
prId,
|
|
288
|
+
trackingCommentId,
|
|
289
|
+
{
|
|
290
|
+
status: args.status,
|
|
291
|
+
message: args.content,
|
|
292
|
+
}
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
default:
|
|
299
|
+
console.warn(`Unknown tool call: ${toolCall.name}`);
|
|
300
|
+
}
|
|
301
|
+
} catch (error) {
|
|
302
|
+
console.error(
|
|
303
|
+
`Error processing tool call ${toolCall.name}:`,
|
|
304
|
+
error instanceof Error ? error.message : error
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return inlineCommentsCreated;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Load output from prepare phase
|
|
314
|
+
*/
|
|
315
|
+
function loadPrepareOutput(): ExecuteOptions {
|
|
316
|
+
const fs = require("fs");
|
|
317
|
+
const outputDir = process.env.BITBUCKET_CLONE_DIR || ".";
|
|
318
|
+
const outputPath = `${outputDir}/.gemini-action-output.json`;
|
|
319
|
+
|
|
320
|
+
if (!fs.existsSync(outputPath)) {
|
|
321
|
+
throw new Error(
|
|
322
|
+
"Prepare phase output not found. Run prepare phase first."
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const data = JSON.parse(fs.readFileSync(outputPath, "utf-8"));
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
mode: data.mode || "tag",
|
|
330
|
+
prompt: data.prompt || "",
|
|
331
|
+
systemPrompt: data.systemPrompt || "",
|
|
332
|
+
trackingCommentId: data.trackingCommentId,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Run if executed directly
|
|
337
|
+
if (import.meta.main) {
|
|
338
|
+
execute()
|
|
339
|
+
.then((result) => {
|
|
340
|
+
if (!result.success) {
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
console.log(`📊 Inline comments created: ${result.inlineCommentsCreated}`);
|
|
344
|
+
})
|
|
345
|
+
.catch((error) => {
|
|
346
|
+
console.error("Fatal error:", error);
|
|
347
|
+
process.exit(1);
|
|
348
|
+
});
|
|
349
|
+
}
|