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.
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Gemini Prompt Templates
3
+ * System prompts and prompt builders for code review
4
+ */
5
+
6
+ import {
7
+ combinePresets,
8
+ getPreset,
9
+ getAllPresetKeys,
10
+ type ReviewPreset,
11
+ } from "./presets.js";
12
+
13
+ export const CODE_REVIEW_SYSTEM_PROMPT = `You are an expert code reviewer assistant integrated with Bitbucket. Your role is to provide helpful, actionable feedback on pull requests.
14
+
15
+ ## Your Capabilities
16
+ - Analyze code changes for bugs, security issues, and best practices
17
+ - Suggest improvements for code quality and maintainability
18
+ - Identify potential performance issues
19
+ - Check for proper error handling
20
+ - Verify code follows project conventions
21
+
22
+ ## Guidelines
23
+ 1. **Be Constructive**: Focus on helping improve the code, not criticizing the author
24
+ 2. **Be Specific**: Point to exact lines and provide concrete suggestions
25
+ 3. **Be Concise**: Keep comments focused and actionable
26
+ 4. **Prioritize**: Focus on important issues first (bugs > security > style)
27
+ 5. **Explain Why**: Always explain the reasoning behind suggestions
28
+ 6. **Suggest Alternatives**: When pointing out issues, suggest better approaches
29
+
30
+ ## Comment Format
31
+ - Use markdown for formatting
32
+ - For code suggestions, use fenced code blocks with language hints
33
+ - Keep inline comments short (1-3 sentences)
34
+ - Use the general PR comment for summaries and overall feedback
35
+
36
+ ## What to Look For
37
+ - Logic errors and potential bugs
38
+ - Security vulnerabilities (SQL injection, XSS, etc.)
39
+ - Null/undefined handling
40
+ - Error handling completeness
41
+ - Type safety issues
42
+ - Performance concerns
43
+ - Code duplication
44
+ - Naming clarity
45
+ - Documentation gaps (for public APIs)
46
+
47
+ ## What NOT to Do
48
+ - Don't nitpick style issues covered by linters
49
+ - Don't request changes for subjective preferences
50
+ - Don't leave comments without actionable suggestions
51
+ - Don't focus on trivial issues when there are bigger concerns`;
52
+
53
+ export const TAG_MODE_SYSTEM_PROMPT = `${CODE_REVIEW_SYSTEM_PROMPT}
54
+
55
+ ## Tag Mode Behavior
56
+ You were triggered by a user mentioning @gemini in a comment. Respond directly to their question or request. Focus your response on what they specifically asked for.
57
+
58
+ If they asked a question, answer it.
59
+ If they requested a review, review the relevant code.
60
+ If they asked for clarification, provide context.`;
61
+
62
+ export const AGENT_MODE_SYSTEM_PROMPT = `${CODE_REVIEW_SYSTEM_PROMPT}
63
+
64
+ ## Agent Mode Behavior
65
+ You are running in automated mode, triggered by a pipeline or automation. Execute the provided prompt completely and thoroughly. Be systematic and cover all aspects mentioned in the prompt.`;
66
+
67
+ export interface PRContext {
68
+ title: string;
69
+ description: string;
70
+ author: string;
71
+ sourceBranch: string;
72
+ targetBranch: string;
73
+ files: Array<{
74
+ path: string;
75
+ status: string;
76
+ additions: number;
77
+ deletions: number;
78
+ }>;
79
+ diff: string;
80
+ comments?: Array<{
81
+ author: string;
82
+ content: string;
83
+ path?: string;
84
+ line?: number;
85
+ createdAt: string;
86
+ }>;
87
+ }
88
+
89
+ export function buildPRReviewPrompt(context: PRContext): string {
90
+ const filesSummary = context.files
91
+ .map(
92
+ (f) =>
93
+ `- ${f.path} (${f.status}): +${f.additions}/-${f.deletions}`
94
+ )
95
+ .join("\n");
96
+
97
+ return `## Pull Request Review Request
98
+
99
+ ### PR Details
100
+ - **Title**: ${context.title}
101
+ - **Author**: ${context.author}
102
+ - **Branch**: ${context.sourceBranch} → ${context.targetBranch}
103
+
104
+ ### Description
105
+ ${context.description || "_No description provided_"}
106
+
107
+ ### Changed Files
108
+ ${filesSummary}
109
+
110
+ ### Diff
111
+ \`\`\`diff
112
+ ${context.diff}
113
+ \`\`\`
114
+
115
+ ${context.comments?.length ? `### Existing Comments\n${formatComments(context.comments)}` : ""}
116
+
117
+ ---
118
+
119
+ Please review this pull request and provide feedback. Focus on:
120
+ 1. Any bugs or logic errors
121
+ 2. Security concerns
122
+ 3. Code quality improvements
123
+ 4. Missing error handling
124
+ 5. Performance considerations
125
+
126
+ Use the available tools to leave inline comments on specific lines and a summary comment for overall feedback.`;
127
+ }
128
+
129
+ export function buildTagModePrompt(
130
+ context: PRContext,
131
+ userMessage: string,
132
+ mentionAuthor: string
133
+ ): string {
134
+ const filesSummary = context.files
135
+ .map(
136
+ (f) =>
137
+ `- ${f.path} (${f.status}): +${f.additions}/-${f.deletions}`
138
+ )
139
+ .join("\n");
140
+
141
+ return `## User Request
142
+
143
+ **@${mentionAuthor}** mentioned you with the following message:
144
+
145
+ > ${userMessage}
146
+
147
+ ---
148
+
149
+ ### PR Context
150
+ - **Title**: ${context.title}
151
+ - **Author**: ${context.author}
152
+ - **Branch**: ${context.sourceBranch} → ${context.targetBranch}
153
+
154
+ ### Description
155
+ ${context.description || "_No description provided_"}
156
+
157
+ ### Changed Files
158
+ ${filesSummary}
159
+
160
+ ### Diff
161
+ \`\`\`diff
162
+ ${context.diff}
163
+ \`\`\`
164
+
165
+ ${context.comments?.length ? `### Recent Comments\n${formatComments(context.comments)}` : ""}
166
+
167
+ ---
168
+
169
+ Please respond to the user's request. Address their specific question or concern.`;
170
+ }
171
+
172
+ export function buildAgentModePrompt(
173
+ context: PRContext,
174
+ agentPrompt: string
175
+ ): string {
176
+ const filesSummary = context.files
177
+ .map(
178
+ (f) =>
179
+ `- ${f.path} (${f.status}): +${f.additions}/-${f.deletions}`
180
+ )
181
+ .join("\n");
182
+
183
+ return `## Automated Task
184
+
185
+ Execute the following task:
186
+
187
+ ${agentPrompt}
188
+
189
+ ---
190
+
191
+ ### PR Context
192
+ - **Title**: ${context.title}
193
+ - **Author**: ${context.author}
194
+ - **Branch**: ${context.sourceBranch} → ${context.targetBranch}
195
+
196
+ ### Description
197
+ ${context.description || "_No description provided_"}
198
+
199
+ ### Changed Files
200
+ ${filesSummary}
201
+
202
+ ### Diff
203
+ \`\`\`diff
204
+ ${context.diff}
205
+ \`\`\`
206
+
207
+ ---
208
+
209
+ Complete the requested task using the available tools.`;
210
+ }
211
+
212
+ function formatComments(
213
+ comments: NonNullable<PRContext["comments"]>
214
+ ): string {
215
+ return comments
216
+ .map((c) => {
217
+ const location = c.path
218
+ ? `on \`${c.path}${c.line ? `:${c.line}` : ""}\``
219
+ : "";
220
+ return `- **${c.author}** ${location} (${c.createdAt}):\n > ${c.content.split("\n").join("\n > ")}`;
221
+ })
222
+ .join("\n\n");
223
+ }
224
+
225
+ /**
226
+ * Build system prompt with presets
227
+ */
228
+ export function buildSystemPromptWithPresets(
229
+ baseSystemPrompt: string,
230
+ presetKeys: string[],
231
+ customPrompt?: string
232
+ ): string {
233
+ // If no presets or custom prompt, return base
234
+ if (presetKeys.length === 0 && !customPrompt) {
235
+ return baseSystemPrompt;
236
+ }
237
+
238
+ // Validate preset keys
239
+ const validKeys = getAllPresetKeys();
240
+ const invalidKeys = presetKeys.filter((key) => !validKeys.includes(key));
241
+ if (invalidKeys.length > 0) {
242
+ console.warn(`⚠️ Unknown preset keys: ${invalidKeys.join(", ")}`);
243
+ }
244
+
245
+ const validPresetKeys = presetKeys.filter((key) => validKeys.includes(key));
246
+
247
+ // Combine presets
248
+ const presetPrompt = combinePresets(validPresetKeys, customPrompt);
249
+
250
+ if (!presetPrompt) {
251
+ return baseSystemPrompt;
252
+ }
253
+
254
+ return `${baseSystemPrompt}
255
+
256
+ ---
257
+
258
+ ${presetPrompt}`;
259
+ }
260
+
261
+ /**
262
+ * Build prompt with presets for PR review
263
+ */
264
+ export function buildPRReviewPromptWithPresets(
265
+ context: PRContext,
266
+ presetKeys: string[],
267
+ customPrompt?: string
268
+ ): string {
269
+ const basePrompt = buildPRReviewPrompt(context);
270
+ const presetPrompt = combinePresets(presetKeys, customPrompt);
271
+
272
+ if (!presetPrompt) {
273
+ return basePrompt;
274
+ }
275
+
276
+ return `${basePrompt}
277
+
278
+ ---
279
+
280
+ ${presetPrompt}`;
281
+ }
282
+
283
+ /**
284
+ * Build agent mode prompt with presets
285
+ */
286
+ export function buildAgentModePromptWithPresets(
287
+ context: PRContext,
288
+ agentPrompt: string,
289
+ presetKeys: string[],
290
+ customPrompt?: string
291
+ ): string {
292
+ const basePrompt = buildAgentModePrompt(context, agentPrompt);
293
+ const presetPrompt = combinePresets(presetKeys, customPrompt);
294
+
295
+ if (!presetPrompt) {
296
+ return basePrompt;
297
+ }
298
+
299
+ return `${basePrompt}
300
+
301
+ ---
302
+
303
+ ${presetPrompt}`;
304
+ }
305
+
306
+ /**
307
+ * Build tag mode prompt with presets
308
+ */
309
+ export function buildTagModePromptWithPresets(
310
+ context: PRContext,
311
+ userMessage: string,
312
+ mentionAuthor: string,
313
+ presetKeys: string[],
314
+ customPrompt?: string
315
+ ): string {
316
+ const basePrompt = buildTagModePrompt(context, userMessage, mentionAuthor);
317
+ const presetPrompt = combinePresets(presetKeys, customPrompt);
318
+
319
+ if (!presetPrompt) {
320
+ return basePrompt;
321
+ }
322
+
323
+ return `${basePrompt}
324
+
325
+ ---
326
+
327
+ ${presetPrompt}`;
328
+ }
329
+
330
+ // Re-export preset utilities for convenience
331
+ export { combinePresets, getPreset, getAllPresetKeys, type ReviewPreset };
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Gemini Function Calling Tools Definition
3
+ * Tools for code review and PR interaction
4
+ */
5
+
6
+ import { SchemaType, type FunctionDeclaration } from "@google/generative-ai";
7
+
8
+ /**
9
+ * Tool for creating inline comments on specific code lines
10
+ */
11
+ export const createInlineCommentTool: FunctionDeclaration = {
12
+ name: "create_inline_comment",
13
+ description:
14
+ "Create an inline comment on a specific line of code in the pull request. Use this to provide targeted feedback on specific code changes.",
15
+ parameters: {
16
+ type: SchemaType.OBJECT,
17
+ properties: {
18
+ path: {
19
+ type: SchemaType.STRING,
20
+ description: "The file path relative to the repository root",
21
+ },
22
+ line: {
23
+ type: SchemaType.NUMBER,
24
+ description: "The line number to comment on (1-indexed)",
25
+ },
26
+ content: {
27
+ type: SchemaType.STRING,
28
+ description:
29
+ "The comment content in markdown format. Be specific and actionable.",
30
+ },
31
+ },
32
+ required: ["path", "line", "content"],
33
+ },
34
+ };
35
+
36
+ /**
37
+ * Tool for creating a general PR comment
38
+ */
39
+ export const createPRCommentTool: FunctionDeclaration = {
40
+ name: "create_pr_comment",
41
+ description:
42
+ "Create a general comment on the pull request. Use this for overall feedback, summaries, or questions that are not specific to a particular line of code.",
43
+ parameters: {
44
+ type: SchemaType.OBJECT,
45
+ properties: {
46
+ content: {
47
+ type: SchemaType.STRING,
48
+ description:
49
+ "The comment content in markdown format. Structure your feedback clearly.",
50
+ },
51
+ },
52
+ required: ["content"],
53
+ },
54
+ };
55
+
56
+ /**
57
+ * Tool for updating the tracking comment
58
+ */
59
+ export const updateTrackingCommentTool: FunctionDeclaration = {
60
+ name: "update_tracking_comment",
61
+ description:
62
+ "Update the AI assistant's tracking comment to show progress or final results.",
63
+ parameters: {
64
+ type: SchemaType.OBJECT,
65
+ properties: {
66
+ content: {
67
+ type: SchemaType.STRING,
68
+ description: "The updated content for the tracking comment in markdown",
69
+ },
70
+ status: {
71
+ type: SchemaType.STRING,
72
+ enum: ["in_progress", "completed", "failed"],
73
+ description: "The current status of the review",
74
+ },
75
+ },
76
+ required: ["content", "status"],
77
+ },
78
+ };
79
+
80
+ /**
81
+ * Tool for reading file contents
82
+ */
83
+ export const readFileTool: FunctionDeclaration = {
84
+ name: "read_file",
85
+ description:
86
+ "Read the contents of a file from the repository. Use this to understand the full context of code changes.",
87
+ parameters: {
88
+ type: SchemaType.OBJECT,
89
+ properties: {
90
+ path: {
91
+ type: SchemaType.STRING,
92
+ description: "The file path relative to the repository root",
93
+ },
94
+ startLine: {
95
+ type: SchemaType.NUMBER,
96
+ description: "Optional starting line number (1-indexed)",
97
+ },
98
+ endLine: {
99
+ type: SchemaType.NUMBER,
100
+ description: "Optional ending line number (1-indexed)",
101
+ },
102
+ },
103
+ required: ["path"],
104
+ },
105
+ };
106
+
107
+ /**
108
+ * Tool for searching code in the repository
109
+ */
110
+ export const searchCodeTool: FunctionDeclaration = {
111
+ name: "search_code",
112
+ description:
113
+ "Search for code patterns or text in the repository. Use this to find related code, usages, or definitions.",
114
+ parameters: {
115
+ type: SchemaType.OBJECT,
116
+ properties: {
117
+ query: {
118
+ type: SchemaType.STRING,
119
+ description: "The search query (supports regex)",
120
+ },
121
+ filePattern: {
122
+ type: SchemaType.STRING,
123
+ description: "Optional glob pattern to filter files (e.g., '*.ts')",
124
+ },
125
+ maxResults: {
126
+ type: SchemaType.NUMBER,
127
+ description: "Maximum number of results to return (default: 20)",
128
+ },
129
+ },
130
+ required: ["query"],
131
+ },
132
+ };
133
+
134
+ /**
135
+ * Tool for getting pipeline/CI status
136
+ */
137
+ export const getPipelineStatusTool: FunctionDeclaration = {
138
+ name: "get_pipeline_status",
139
+ description:
140
+ "Get the current status of the pipeline builds for this PR or branch.",
141
+ parameters: {
142
+ type: SchemaType.OBJECT,
143
+ properties: {
144
+ includeHistory: {
145
+ type: SchemaType.BOOLEAN,
146
+ description: "Include recent build history (default: false)",
147
+ },
148
+ },
149
+ required: [],
150
+ },
151
+ };
152
+
153
+ /**
154
+ * Tool for suggesting code fixes
155
+ */
156
+ export const suggestFixTool: FunctionDeclaration = {
157
+ name: "suggest_fix",
158
+ description:
159
+ "Suggest a code fix for a specific issue. This creates a code suggestion that can be applied directly.",
160
+ parameters: {
161
+ type: SchemaType.OBJECT,
162
+ properties: {
163
+ path: {
164
+ type: SchemaType.STRING,
165
+ description: "The file path relative to the repository root",
166
+ },
167
+ startLine: {
168
+ type: SchemaType.NUMBER,
169
+ description: "The starting line number of the code to replace",
170
+ },
171
+ endLine: {
172
+ type: SchemaType.NUMBER,
173
+ description: "The ending line number of the code to replace",
174
+ },
175
+ suggestedCode: {
176
+ type: SchemaType.STRING,
177
+ description: "The suggested replacement code",
178
+ },
179
+ explanation: {
180
+ type: SchemaType.STRING,
181
+ description: "Brief explanation of why this change is suggested",
182
+ },
183
+ },
184
+ required: ["path", "startLine", "endLine", "suggestedCode", "explanation"],
185
+ },
186
+ };
187
+
188
+ /**
189
+ * All available tools for code review
190
+ */
191
+ export const codeReviewTools: FunctionDeclaration[] = [
192
+ createInlineCommentTool,
193
+ createPRCommentTool,
194
+ updateTrackingCommentTool,
195
+ readFileTool,
196
+ searchCodeTool,
197
+ getPipelineStatusTool,
198
+ suggestFixTool,
199
+ ];
200
+
201
+ /**
202
+ * Subset of tools for lightweight reviews
203
+ */
204
+ export const lightweightReviewTools: FunctionDeclaration[] = [
205
+ createInlineCommentTool,
206
+ createPRCommentTool,
207
+ readFileTool,
208
+ ];
209
+
210
+ /**
211
+ * Get tools based on mode
212
+ */
213
+ export function getToolsForMode(
214
+ mode: "full" | "lightweight" | "agent"
215
+ ): FunctionDeclaration[] {
216
+ switch (mode) {
217
+ case "full":
218
+ return codeReviewTools;
219
+ case "lightweight":
220
+ return lightweightReviewTools;
221
+ case "agent":
222
+ return codeReviewTools;
223
+ default:
224
+ return lightweightReviewTools;
225
+ }
226
+ }
package/src/index.ts ADDED
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Bitbucket Gemini Action
3
+ * Main entry point for the action
4
+ */
5
+
6
+ export { prepare } from "./entrypoints/prepare.js";
7
+ export { execute } from "./entrypoints/execute.js";
8
+
9
+ // Bitbucket API
10
+ export { BitbucketClient } from "./bitbucket/api/client.js";
11
+ export { parseBitbucketContext } from "./bitbucket/context.js";
12
+ export type {
13
+ BitbucketPullRequest,
14
+ BitbucketComment,
15
+ BitbucketUser,
16
+ ParsedBitbucketContext,
17
+ } from "./bitbucket/types.js";
18
+
19
+ // Gemini API
20
+ export { GeminiClient } from "./gemini/client.js";
21
+ export { codeReviewTools, getToolsForMode } from "./gemini/tools.js";
22
+ export {
23
+ buildPRReviewPrompt,
24
+ buildTagModePrompt,
25
+ buildAgentModePrompt,
26
+ } from "./gemini/prompts.js";
27
+
28
+ // Modes
29
+ export { detectMode, getMode, shouldAnyModeTrigger } from "./modes/registry.js";
30
+ export type { Mode, ModeName, ModeContext } from "./modes/types.js";
31
+
32
+ // Utils
33
+ export { getEnvConfig, getGeminiApiKey } from "./utils/env.js";
34
+ export { withRetry } from "./utils/retry.js";
35
+
36
+ /**
37
+ * Run the full action (prepare + execute)
38
+ */
39
+ export async function run(): Promise<void> {
40
+ const { prepare } = await import("./entrypoints/prepare.js");
41
+ const { execute } = await import("./entrypoints/execute.js");
42
+
43
+ const prepareResult = await prepare();
44
+
45
+ if (!prepareResult.success) {
46
+ console.error("Prepare phase failed:", prepareResult.error);
47
+ process.exit(1);
48
+ }
49
+
50
+ if (!prepareResult.shouldContinue) {
51
+ console.log("No action needed:", prepareResult.error || "No trigger");
52
+ process.exit(0);
53
+ }
54
+
55
+ const executeResult = await execute();
56
+
57
+ if (!executeResult.success) {
58
+ console.error("Execute phase failed:", executeResult.error);
59
+ process.exit(1);
60
+ }
61
+
62
+ console.log("Action completed successfully");
63
+ }
64
+
65
+ // Run if executed directly
66
+ if (import.meta.main) {
67
+ run().catch((error) => {
68
+ console.error("Fatal error:", error);
69
+ process.exit(1);
70
+ });
71
+ }