@krotovm/gitlab-ai-review 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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 KrotovM
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # AI Code Reviewer
2
+
3
+ Gitlab AI Code Review is a CLI tool that leverages OpenAI models to automatically review code changes and output a Markdown review (either to GitLab merge requests via CI, or to your local console).
4
+
5
+ ## Features
6
+
7
+ - Automatically reviews code changes in GitLab repositories
8
+ - Provides feedback on code clarity, simplicity, bugs, and security issues
9
+ - Generates Markdown-formatted responses for easy readability in GitLab
10
+
11
+ ## GitLab CI usage (recommended)
12
+
13
+ This repo now includes a CLI you can run in a dedicated GitLab CI job. It is designed to run in **Merge Request pipelines** and will post a **new comment** on the MR with the AI review.
14
+
15
+ ### Required CI variables
16
+
17
+ - `OPENAI_API_KEY`
18
+ - `AI_MODEL` (optional, default: `gpt-4o-mini`; examples: `gpt-4o`)
19
+
20
+ GitLab provides these automatically in CI:
21
+
22
+ - `CI_API_V4_URL`
23
+ - `CI_PROJECT_ID`
24
+ - `CI_MERGE_REQUEST_IID`
25
+ - `CI_JOB_TOKEN`
26
+
27
+ ### Example `.gitlab-ci.yml`
28
+
29
+ ```yaml
30
+ stages:
31
+ - review
32
+
33
+ ai_review:
34
+ stage: review
35
+ image: node:20
36
+ rules:
37
+ - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
38
+ script:
39
+ - corepack enable
40
+ - pnpm install --frozen-lockfile
41
+ - pnpm build:ts
42
+ - node dist/cli.js
43
+ ```
44
+
45
+ ## Local usage
46
+
47
+ You can also run the CLI locally to review diffs and print the review to the console.
48
+
49
+ ### Review uncommitted changes (staged + unstaged)
50
+
51
+ ```bash
52
+ OPENAI_API_KEY=... AI_MODEL=gpt-4o node dist/cli.js --worktree
53
+ ```
54
+
55
+ ### Review last commit (HEAD)
56
+
57
+ ```bash
58
+ OPENAI_API_KEY=... AI_MODEL=gpt-4o node dist/cli.js --last-commit
59
+ ```
60
+
61
+ ## Installation
62
+
63
+ ```bash
64
+ pnpm install
65
+ pnpm build:ts
66
+ ```
package/dist/cli.js ADDED
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ import OpenAI from 'openai';
3
+ import { buildAnswer, buildPrompt } from './prompt/index.js';
4
+ import { fetchMergeRequestChanges, fetchPreEditFiles, generateAICompletion, postMergeRequestNote } from './gitlab/services.js';
5
+ function printHelp() {
6
+ process.stdout.write([
7
+ 'gitlab-ai-review',
8
+ '',
9
+ 'Usage:',
10
+ ' gitlab-ai-review --ci',
11
+ ' gitlab-ai-review --worktree',
12
+ ' gitlab-ai-review --last-commit',
13
+ ' gitlab-ai-review --help',
14
+ ' gitlab-ai-review --debug',
15
+ '',
16
+ 'Modes (choose one):',
17
+ ' --ci Run in GitLab MR pipeline: fetch MR changes and post a new MR note.',
18
+ ' --worktree Review local uncommitted changes (staged + unstaged) and print to stdout.',
19
+ ' --last-commit Review last commit (HEAD) and print to stdout.',
20
+ '',
21
+ 'Debug:',
22
+ ' --debug Print full error details (stack, API error fields).',
23
+ '',
24
+ 'Env vars:',
25
+ ' OPENAI_API_KEY (required) OpenAI API key.',
26
+ ' AI_MODEL (optional) OpenAI chat model, e.g. gpt-4o. Default: gpt-4o-mini.',
27
+ '',
28
+ 'CI-only env vars (provided by GitLab):',
29
+ ' CI_API_V4_URL, CI_PROJECT_ID, CI_MERGE_REQUEST_IID, CI_JOB_TOKEN',
30
+ '',
31
+ 'Notes:',
32
+ ' - If no mode is specified, it defaults to --ci when CI_MERGE_REQUEST_IID is set, otherwise --worktree.',
33
+ ''
34
+ ].join('\n'));
35
+ }
36
+ function requiredEnv(name) {
37
+ const value = process.env[name];
38
+ if (value == null || value.trim() === '') {
39
+ throw new Error(`Missing required env var: ${name}`);
40
+ }
41
+ return value;
42
+ }
43
+ function envOrDefault(name, defaultValue) {
44
+ const value = process.env[name];
45
+ if (value == null)
46
+ return defaultValue;
47
+ const trimmed = value.trim();
48
+ return trimmed === '' ? defaultValue : trimmed;
49
+ }
50
+ function hasDebugFlag(argv) {
51
+ const args = new Set(argv.slice(2));
52
+ return args.has('--debug');
53
+ }
54
+ function parseMode(argv) {
55
+ const args = new Set(argv.slice(2));
56
+ if (args.has('--help') || args.has('-h')) {
57
+ printHelp();
58
+ process.exit(0);
59
+ }
60
+ const hasCi = args.has('--ci');
61
+ const hasWorktree = args.has('--worktree');
62
+ const hasLastCommit = args.has('--last-commit');
63
+ const count = Number(hasCi) + Number(hasWorktree) + Number(hasLastCommit);
64
+ if (count > 1) {
65
+ throw new Error('Choose only one mode: --ci, --worktree, or --last-commit');
66
+ }
67
+ if (hasCi)
68
+ return 'ci';
69
+ if (hasWorktree)
70
+ return 'worktree';
71
+ if (hasLastCommit)
72
+ return 'last-commit';
73
+ if (process.env.CI_MERGE_REQUEST_IID != null)
74
+ return 'ci';
75
+ return 'worktree';
76
+ }
77
+ async function runGit(args) {
78
+ const { spawn } = await import('node:child_process');
79
+ return await new Promise((resolve, reject) => {
80
+ const child = spawn('git', args, { stdio: ['ignore', 'pipe', 'pipe'] });
81
+ let stdout = '';
82
+ let stderr = '';
83
+ child.stdout.on('data', (d) => { stdout += String(d); });
84
+ child.stderr.on('data', (d) => { stderr += String(d); });
85
+ child.on('error', reject);
86
+ child.on('close', (code) => {
87
+ if (code === 0)
88
+ return resolve(stdout);
89
+ reject(new Error(stderr.trim() || `git ${args.join(' ')} failed with exit code ${code}`));
90
+ });
91
+ });
92
+ }
93
+ async function localDiffWorktree() {
94
+ const unstaged = await runGit(['diff']);
95
+ const staged = await runGit(['diff', '--staged']);
96
+ const combined = [staged.trim(), unstaged.trim()].filter(Boolean).join('\n\n');
97
+ return combined;
98
+ }
99
+ async function localDiffLastCommit() {
100
+ // show patch for HEAD, but avoid commit message metadata
101
+ return await runGit(['show', '--format=', 'HEAD']);
102
+ }
103
+ async function reviewDiffToConsole(diff, openaiApiKey, aiModel) {
104
+ if (diff.trim() === '') {
105
+ process.stdout.write('No diff found. Skipping review.\n');
106
+ return;
107
+ }
108
+ const messageParams = buildPrompt({
109
+ changes: [{ diff }]
110
+ });
111
+ const openaiInstance = new OpenAI({ apiKey: openaiApiKey });
112
+ const completion = await generateAICompletion(messageParams, openaiInstance, aiModel);
113
+ const answer = buildAnswer(completion);
114
+ process.stdout.write(`${answer}\n`);
115
+ }
116
+ async function main() {
117
+ const mode = parseMode(process.argv);
118
+ const openaiApiKey = requiredEnv('OPENAI_API_KEY');
119
+ const aiModel = envOrDefault('AI_MODEL', 'gpt-4o-mini');
120
+ if (mode === 'worktree') {
121
+ const diff = await localDiffWorktree();
122
+ await reviewDiffToConsole(diff, openaiApiKey, aiModel);
123
+ return;
124
+ }
125
+ if (mode === 'last-commit') {
126
+ const diff = await localDiffLastCommit();
127
+ await reviewDiffToConsole(diff, openaiApiKey, aiModel);
128
+ return;
129
+ }
130
+ const ciApiV4Url = requiredEnv('CI_API_V4_URL');
131
+ const projectId = requiredEnv('CI_PROJECT_ID');
132
+ const mergeRequestIid = requiredEnv('CI_MERGE_REQUEST_IID');
133
+ const jobToken = requiredEnv('CI_JOB_TOKEN');
134
+ const gitLabBaseUrl = new URL(ciApiV4Url);
135
+ const headers = { 'JOB-TOKEN': jobToken };
136
+ const mrChanges = await fetchMergeRequestChanges({
137
+ gitLabBaseUrl,
138
+ headers,
139
+ projectId,
140
+ mergeRequestIid
141
+ });
142
+ if (mrChanges instanceof Error)
143
+ throw mrChanges;
144
+ const changes = mrChanges.changes ?? [];
145
+ if (changes.length === 0) {
146
+ process.stdout.write('No changes found in merge request. Skipping review.\n');
147
+ return;
148
+ }
149
+ const baseSha = mrChanges.diff_refs?.base_sha;
150
+ const ref = baseSha ?? 'HEAD';
151
+ const changesOldPaths = changes.map(c => c.old_path);
152
+ const oldFiles = await fetchPreEditFiles({
153
+ gitLabBaseUrl: new URL(`${ciApiV4Url}/projects/${projectId}`),
154
+ headers,
155
+ changesOldPaths,
156
+ ref
157
+ });
158
+ if (oldFiles instanceof Error)
159
+ throw oldFiles;
160
+ const messageParams = buildPrompt({
161
+ oldFiles,
162
+ changes: changes.map(c => ({ diff: c.diff }))
163
+ });
164
+ const openaiInstance = new OpenAI({ apiKey: openaiApiKey });
165
+ const completion = await generateAICompletion(messageParams, openaiInstance, aiModel);
166
+ const answer = buildAnswer(completion);
167
+ const noteRes = await postMergeRequestNote({
168
+ gitLabBaseUrl: new URL(`${ciApiV4Url}/projects/${projectId}`),
169
+ headers,
170
+ mergeRequestIid
171
+ }, { body: answer });
172
+ if (noteRes instanceof Error)
173
+ throw noteRes;
174
+ process.stdout.write('Posted AI review comment to merge request.\n');
175
+ }
176
+ main().catch((err) => {
177
+ const message = err instanceof Error ? err.message : String(err);
178
+ process.stderr.write(`${message}\n`);
179
+ if (hasDebugFlag(process.argv)) {
180
+ if (err instanceof Error && err.stack != null) {
181
+ process.stderr.write(`\nStack:\n${err.stack}\n`);
182
+ }
183
+ const anyErr = err;
184
+ const debugInfo = {
185
+ name: anyErr?.name,
186
+ message: anyErr?.message,
187
+ status: anyErr?.status,
188
+ code: anyErr?.code,
189
+ type: anyErr?.type,
190
+ request_id: anyErr?.request_id,
191
+ headers: anyErr?.headers,
192
+ error: anyErr?.error,
193
+ cause: anyErr?.cause instanceof Error ? { name: anyErr.cause.name, message: anyErr.cause.message, stack: anyErr.cause.stack } : anyErr?.cause
194
+ };
195
+ process.stderr.write(`\nDebug:\n${JSON.stringify(debugInfo, null, 2)}\n`);
196
+ }
197
+ process.exitCode = 1;
198
+ });
199
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export class BaseError extends Error {
2
+ name;
3
+ message;
4
+ cause;
5
+ statusCode;
6
+ constructor({ name, message, cause, statusCode = 500 }) {
7
+ super();
8
+ this.name = name;
9
+ this.message = message;
10
+ this.cause = cause;
11
+ this.statusCode = statusCode;
12
+ }
13
+ }
14
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/config/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,SAA4B,SAAQ,KAAK;IAC3C,IAAI,CAAG;IACP,OAAO,CAAQ;IACf,KAAK,CAAK;IACnB,UAAU,CAAQ;IAElB,YAAa,EACX,IAAI,EACJ,OAAO,EACP,KAAK,EACL,UAAU,GAAG,GAAG,EAMjB;QACC,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;IAC9B,CAAC;CACF"}
@@ -0,0 +1,24 @@
1
+ export const AI_MODELS = [
2
+ 'gpt-4o',
3
+ 'gpt-4o-2024-05-13',
4
+ 'gpt-4-turbo',
5
+ 'gpt-4-turbo-2024-04-09',
6
+ 'gpt-4-0125-preview',
7
+ 'gpt-4-turbo-preview',
8
+ 'gpt-4-1106-preview',
9
+ 'gpt-4-vision-preview',
10
+ 'gpt-4',
11
+ 'gpt-4-0314',
12
+ 'gpt-4-0613',
13
+ 'gpt-4-32k',
14
+ 'gpt-4-32k-0314',
15
+ 'gpt-4-32k-0613',
16
+ 'gpt-3.5-turbo',
17
+ 'gpt-3.5-turbo-16k',
18
+ 'gpt-3.5-turbo-0301',
19
+ 'gpt-3.5-turbo-0613',
20
+ 'gpt-3.5-turbo-1106',
21
+ 'gpt-3.5-turbo-0125',
22
+ 'gpt-3.5-turbo-16k-0613'
23
+ ];
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,SAAS,GAAgB;IACpC,QAAQ;IACR,mBAAmB;IACnB,aAAa;IACb,wBAAwB;IACxB,oBAAoB;IACpB,qBAAqB;IACrB,oBAAoB;IACpB,sBAAsB;IACtB,OAAO;IACP,YAAY;IACZ,YAAY;IACZ,WAAW;IACX,gBAAgB;IAChB,gBAAgB;IAChB,eAAe;IACf,mBAAmB;IACnB,oBAAoB;IACpB,oBAAoB;IACpB,oBAAoB;IACpB,oBAAoB;IACpB,wBAAwB;CACzB,CAAA"}
package/dist/errors.js ADDED
@@ -0,0 +1,14 @@
1
+ export class BaseError extends Error {
2
+ name;
3
+ message;
4
+ cause;
5
+ statusCode;
6
+ constructor({ name, message, cause, statusCode = 500 }) {
7
+ super();
8
+ this.name = name;
9
+ this.message = message;
10
+ this.cause = cause;
11
+ this.statusCode = statusCode;
12
+ }
13
+ }
14
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,SAA4B,SAAQ,KAAK;IAC3C,IAAI,CAAG;IACP,OAAO,CAAQ;IACf,KAAK,CAAS;IACvB,UAAU,CAAQ;IAElB,YAAa,EACX,IAAI,EACJ,OAAO,EACP,KAAK,EACL,UAAU,GAAG,GAAG,EAMjB;QACC,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;IAC9B,CAAC;CACF"}
@@ -0,0 +1,98 @@
1
+ import OpenAI from 'openai';
2
+ import { AI_MODEL_TEMPERATURE } from '../prompt/index.js';
3
+ import { GitLabError, OpenAIError } from './types.js';
4
+ export const fetchPreEditFiles = async ({ gitLabBaseUrl, headers, changesOldPaths, ref }) => {
5
+ const oldFilesRequestUrls = changesOldPaths.map(filePath => {
6
+ const url = new URL(`${gitLabBaseUrl}/repository/files/${encodeURIComponent(filePath)}/raw`);
7
+ url.searchParams.set('ref', ref);
8
+ return url;
9
+ });
10
+ let oldFiles;
11
+ try {
12
+ oldFiles = await Promise.allSettled(oldFilesRequestUrls.map(async (url) => {
13
+ const file = await (await fetch(url, { headers: { ...headers } })).text();
14
+ return file;
15
+ }));
16
+ }
17
+ catch (error) {
18
+ oldFiles = error;
19
+ }
20
+ if (oldFiles instanceof Error) {
21
+ return new GitLabError({
22
+ name: 'MISSING_OLD_FILES',
23
+ message: 'Failed to fetch old files'
24
+ });
25
+ }
26
+ return oldFiles.reduce((acc, file, index) => {
27
+ if (file.status === 'fulfilled') {
28
+ acc.push({
29
+ fileName: changesOldPaths[index],
30
+ fileContent: file.value
31
+ });
32
+ }
33
+ return acc;
34
+ }, []);
35
+ };
36
+ export async function generateAICompletion(messages, openaiInstance, aiModel) {
37
+ let completion;
38
+ try {
39
+ completion = await openaiInstance.chat.completions.create({
40
+ model: aiModel,
41
+ temperature: AI_MODEL_TEMPERATURE,
42
+ stream: false,
43
+ messages
44
+ });
45
+ }
46
+ catch (error) {
47
+ completion = error;
48
+ }
49
+ if (completion instanceof Error) {
50
+ return new OpenAIError({
51
+ name: 'MISSING_AI_COMPLETION',
52
+ message: 'Failed to generate AI completion',
53
+ cause: completion
54
+ });
55
+ }
56
+ return completion;
57
+ }
58
+ export const postMergeRequestNote = async ({ gitLabBaseUrl, headers, mergeRequestIid }, commentPayload) => {
59
+ const commentUrl = new URL(`${gitLabBaseUrl}/merge_requests/${mergeRequestIid}/notes`);
60
+ let aiComment;
61
+ try {
62
+ aiComment = await fetch(commentUrl, {
63
+ method: 'POST',
64
+ headers: {
65
+ ...headers,
66
+ 'Content-Type': 'application/json'
67
+ },
68
+ body: JSON.stringify(commentPayload)
69
+ });
70
+ }
71
+ catch (error) {
72
+ aiComment = error;
73
+ }
74
+ if (aiComment instanceof Error || !aiComment.ok) {
75
+ return new GitLabError({
76
+ name: 'FAILED_TO_POST_COMMENT',
77
+ message: 'Failed to post AI comment'
78
+ });
79
+ }
80
+ };
81
+ export const fetchMergeRequestChanges = async ({ gitLabBaseUrl, headers, projectId, mergeRequestIid }) => {
82
+ const url = new URL(`${gitLabBaseUrl}/projects/${projectId}/merge_requests/${mergeRequestIid}/changes`);
83
+ let res;
84
+ try {
85
+ res = await fetch(url, { headers: { ...headers } });
86
+ }
87
+ catch (error) {
88
+ res = error;
89
+ }
90
+ if (res instanceof Error || !res.ok) {
91
+ return new GitLabError({
92
+ name: 'MISSING_DIFF',
93
+ message: 'Failed to fetch merge request changes'
94
+ });
95
+ }
96
+ return await res.json();
97
+ };
98
+ //# sourceMappingURL=services.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services.js","sourceRoot":"","sources":["../../src/gitlab/services.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { BaseError } from '../errors.js';
2
+ export class GitLabError extends BaseError {
3
+ }
4
+ export class OpenAIError extends BaseError {
5
+ }
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/gitlab/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAiBxC,MAAM,OAAO,WAAY,SAAQ,SAA0B;CAAI;AAC/D,MAAM,OAAO,WAAY,SAAQ,SAA0B;CAAI"}
@@ -0,0 +1,46 @@
1
+ const QUESTIONS = `\n\nQuestions:\n
2
+ 1. Can you summarize the changes in a succinct bullet point list\n
3
+ 2. In the diff, are the added or changed code written in a clear and easy to understand way?\n
4
+ 3. Does the code use comments, or descriptive function and variables names that explain what they mean?\n
5
+ 4. based on the code complexity of the changes, could the code be simplified without breaking its functionality? if so can you give example snippets?\n
6
+ 5. Can you find any bugs, if so please explain and reference line numbers?\n
7
+ 6. Do you see any code that could induce security issues?\n\n`;
8
+ const MESSAGES = [{
9
+ role: 'system',
10
+ content: 'You are a senior developer reviewing code changes.'
11
+ },
12
+ {
13
+ role: 'assistant',
14
+ content: 'Format the response so it renders nicely in GitLab, with nice and organized markdown (use code blocks if needed), and send just the response no comments on the request, when answering include a short version of the question, so we know what it is.'
15
+ }];
16
+ export const AI_MODEL_TEMPERATURE = 0.2;
17
+ export const buildPrompt = ({ changes, oldFiles }) => {
18
+ const hasOldFiles = oldFiles != null && oldFiles.length > 0;
19
+ const content = `
20
+ As a senior developer, review the following code changes and answer code review questions about them. The code changes are provided as git diff strings.
21
+ ${hasOldFiles ? 'The entire file before the change is provided for context. Make sure to keep it as a reference when reviewing the changes.' : 'No full pre-change file context is available; review based on the diff only.'}
22
+
23
+ Files before changes:
24
+ ${hasOldFiles ? oldFiles.map(oldFile => JSON.stringify(oldFile)).join('\n\n') : '(not provided)'}
25
+
26
+ Changes:
27
+ ${changes.map(change => change.diff).join('\n\n')}
28
+
29
+ ${QUESTIONS}
30
+ `;
31
+ return [...MESSAGES, { role: 'user', content }];
32
+ };
33
+ const ERROR_ANSWER = 'I\'m sorry, I\'m not feeling well today. Please ask a human to review this code change.';
34
+ const DISCLAIMER = 'This comment was generated by an artificial intelligence duck.';
35
+ export const buildAnswer = (completion) => {
36
+ if (completion instanceof Error) {
37
+ const maybeCause = completion.cause;
38
+ const causeMessage = maybeCause instanceof Error ? maybeCause.message : undefined;
39
+ return `${ERROR_ANSWER}\n\nError: ${completion.message}${causeMessage != null ? `\nCause: ${causeMessage}` : ''}`;
40
+ }
41
+ if ((completion == null) || (completion.choices.length === 0)) {
42
+ return `${ERROR_ANSWER}\n\n${DISCLAIMER}`;
43
+ }
44
+ return `${completion.choices[0].message.content}\n\n${DISCLAIMER}`;
45
+ };
46
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/prompt/index.ts"],"names":[],"mappings":"AAGA,MAAM,SAAS,GAAG;;;;;;8DAM4C,CAAA;AAE9D,MAAM,QAAQ,GAAiC,CAAC;QAC9C,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,oDAAoD;KAC9D;IACD;QACE,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,yPAAyP;KACnQ,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAA;AAOvC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAyB,EAAgC,EAAE;IACxG,MAAM,WAAW,GAAG,QAAQ,IAAI,IAAI,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAA;IAC3D,MAAM,OAAO,GAAG;;MAEZ,WAAW,CAAC,CAAC,CAAC,4HAA4H,CAAC,CAAC,CAAC,8EAA8E;;;MAG3N,WAAW,CAAC,CAAC,CAAC,QAAS,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,gBAAgB;;;MAG/F,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;;MAE/C,SAAS;KACV,CAAA;IAEH,OAAO,CAAC,GAAG,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;AACjD,CAAC,CAAA;AAED,MAAM,YAAY,GAAG,yFAAyF,CAAA;AAE9G,MAAM,UAAU,GAAG,gEAAgE,CAAA;AAEnF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,UAA8C,EAAU,EAAE;IACpF,IAAI,UAAU,YAAY,KAAK,EAAE,CAAC;QAChC,MAAM,UAAU,GAAI,UAAkB,CAAC,KAAK,CAAA;QAC5C,MAAM,YAAY,GAAG,UAAU,YAAY,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;QACjF,OAAO,GAAG,YAAY,cAAc,UAAU,CAAC,OAAO,GAAG,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,YAAY,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;IACnH,CAAC;IACD,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;QAC9D,OAAO,GAAG,YAAY,OAAO,UAAU,EAAE,CAAA;IAC3C,CAAC;IACD,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,OAAO,OAAO,UAAU,EAAE,CAAA;AACrE,CAAC,CAAA"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "type": "module",
3
+ "name": "@krotovm/gitlab-ai-review",
4
+ "version": "1.0.0",
5
+ "description": "CLI tool to generate AI code reviews for GitLab merge requests and local diffs.",
6
+ "main": "dist/cli.js",
7
+ "bin": {
8
+ "gitlab-ai-review": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "build:ts": "tsc",
17
+ "prepublishOnly": "pnpm build:ts"
18
+ },
19
+ "keywords": [
20
+ "gitlab",
21
+ "merge-request",
22
+ "code-review",
23
+ "openai",
24
+ "cli"
25
+ ],
26
+ "author": "KrotovM",
27
+ "license": "MIT",
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/KrotovM/gitlab-ai-mr-reviewer.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/KrotovM/gitlab-ai-mr-reviewer/issues"
37
+ },
38
+ "homepage": "https://github.com/KrotovM/gitlab-ai-mr-reviewer#readme",
39
+ "dependencies": {
40
+ "openai": "^4.47.2"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^20.4.4",
44
+ "typescript": "^5.2.2"
45
+ },
46
+ "packageManager": "pnpm@8.15.4+sha256.cea6d0bdf2de3a0549582da3983c70c92ffc577ff4410cbf190817ddc35137c2"
47
+ }