@krotovm/gitlab-ai-review 1.0.21 → 1.0.22
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/dist/cli/ci-review.js +1 -5
- package/dist/gitlab/services.js +11 -4
- package/dist/prompt/index.js +3 -2
- package/dist/prompt/utils.js +62 -0
- package/package.json +1 -1
package/dist/cli/ci-review.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @format */
|
|
2
2
|
import OpenAI from "openai";
|
|
3
|
-
import {
|
|
3
|
+
import { buildAnswer, buildConsolidatePrompt, buildFileReviewPrompt, buildPrompt, buildTriagePrompt, buildVerificationPrompt, extractCompletionText, parseTriageResponse, } from "../prompt/index.js";
|
|
4
4
|
import { fetchFileAtRef, searchRepository, } from "../gitlab/services.js";
|
|
5
5
|
import { logToolUsageMinimal, MAX_FILE_TOOL_ROUNDS, MAX_TOOL_ROUNDS, TOOL_NAME_GET_FILE, TOOL_NAME_GREP, } from "./tooling.js";
|
|
6
6
|
function buildReviewMetadata(changes, refs) {
|
|
@@ -219,7 +219,6 @@ export async function reviewMergeRequestWithTools(params) {
|
|
|
219
219
|
const finalCompletion = await openaiInstance.chat.completions.create({
|
|
220
220
|
model: aiModel,
|
|
221
221
|
temperature: 0.2,
|
|
222
|
-
max_tokens: AI_MAX_OUTPUT_TOKENS,
|
|
223
222
|
stream: false,
|
|
224
223
|
messages,
|
|
225
224
|
});
|
|
@@ -330,7 +329,6 @@ async function runFileReviewWithTools(params) {
|
|
|
330
329
|
const final = await openaiInstance.chat.completions.create({
|
|
331
330
|
model: aiModel,
|
|
332
331
|
temperature: 0.2,
|
|
333
|
-
max_tokens: AI_MAX_OUTPUT_TOKENS,
|
|
334
332
|
stream: false,
|
|
335
333
|
messages,
|
|
336
334
|
});
|
|
@@ -421,7 +419,6 @@ export async function reviewMergeRequestMultiPass(params) {
|
|
|
421
419
|
const consolidateCompletion = await openaiInstance.chat.completions.create({
|
|
422
420
|
model: aiModel,
|
|
423
421
|
temperature: 0.1,
|
|
424
|
-
max_tokens: AI_MAX_OUTPUT_TOKENS,
|
|
425
422
|
stream: false,
|
|
426
423
|
messages: consolidateMessages,
|
|
427
424
|
});
|
|
@@ -440,7 +437,6 @@ export async function reviewMergeRequestMultiPass(params) {
|
|
|
440
437
|
const verificationCompletion = await openaiInstance.chat.completions.create({
|
|
441
438
|
model: aiModel,
|
|
442
439
|
temperature: 0.0,
|
|
443
|
-
max_tokens: AI_MAX_OUTPUT_TOKENS,
|
|
444
440
|
stream: false,
|
|
445
441
|
messages: verificationMessages,
|
|
446
442
|
});
|
package/dist/gitlab/services.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @format */
|
|
2
2
|
import OpenAI from "openai";
|
|
3
|
-
import {
|
|
3
|
+
import { AI_MODEL_TEMPERATURE } from "../prompt/index.js";
|
|
4
4
|
import { GitLabError, OpenAIError, } from "./types.js";
|
|
5
5
|
export const fetchPreEditFiles = async ({ gitLabBaseUrl, headers, changesOldPaths, ref }) => {
|
|
6
6
|
const oldFilesRequestUrls = changesOldPaths.map((filePath) => {
|
|
@@ -87,7 +87,6 @@ export async function generateAICompletion(messages, openaiInstance, aiModel) {
|
|
|
87
87
|
completion = await openaiInstance.chat.completions.create({
|
|
88
88
|
model: aiModel,
|
|
89
89
|
temperature: AI_MODEL_TEMPERATURE,
|
|
90
|
-
max_tokens: AI_MAX_OUTPUT_TOKENS,
|
|
91
90
|
stream: false,
|
|
92
91
|
messages,
|
|
93
92
|
});
|
|
@@ -164,10 +163,18 @@ export const searchRepository = async ({ gitLabBaseUrl, headers, query, ref, pro
|
|
|
164
163
|
if (res instanceof Error || !res.ok) {
|
|
165
164
|
const responseDetails = await (async () => {
|
|
166
165
|
if (res instanceof Error) {
|
|
167
|
-
return {
|
|
166
|
+
return {
|
|
167
|
+
url: url.toString(),
|
|
168
|
+
error: { name: res.name, message: res.message },
|
|
169
|
+
};
|
|
168
170
|
}
|
|
169
171
|
const bodyText = await res.text().catch(() => "");
|
|
170
|
-
return {
|
|
172
|
+
return {
|
|
173
|
+
url: url.toString(),
|
|
174
|
+
status: res.status,
|
|
175
|
+
statusText: res.statusText,
|
|
176
|
+
body: bodyText.slice(0, 1000),
|
|
177
|
+
};
|
|
171
178
|
})();
|
|
172
179
|
return new GitLabError({
|
|
173
180
|
name: "SEARCH_FAILED",
|
package/dist/prompt/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @format */
|
|
2
2
|
import { buildMainSystemMessages, FILE_REVIEW_SYSTEM, TRIAGE_SYSTEM, } from "./messages.js";
|
|
3
|
-
import { sanitizeGitLabMarkdown, truncateWithMarker } from "./utils.js";
|
|
3
|
+
import { normalizeReviewFindingsMarkdown, sanitizeGitLabMarkdown, truncateWithMarker, } from "./utils.js";
|
|
4
4
|
export const DEFAULT_PROMPT_LIMITS = {
|
|
5
5
|
maxDiffs: 50,
|
|
6
6
|
maxDiffChars: 16000,
|
|
@@ -273,6 +273,7 @@ export const buildAnswer = (completion) => {
|
|
|
273
273
|
if (content === "") {
|
|
274
274
|
return `${ERROR_ANSWER}\n\nError: Model returned an empty response body. Try another model (for example, gpt-4o-mini) or a different provider endpoint.\n\n---\n_${DISCLAIMER}_`;
|
|
275
275
|
}
|
|
276
|
-
const
|
|
276
|
+
const normalizedFindings = normalizeReviewFindingsMarkdown(content);
|
|
277
|
+
const safe = sanitizeGitLabMarkdown(normalizedFindings);
|
|
277
278
|
return `${safe}\n\n---\n_${DISCLAIMER}_`;
|
|
278
279
|
};
|
package/dist/prompt/utils.js
CHANGED
|
@@ -12,3 +12,65 @@ export function sanitizeGitLabMarkdown(input) {
|
|
|
12
12
|
const withClosedFence = fenceCount % 2 === 1 ? `${normalized}\n\`\`\`` : normalized;
|
|
13
13
|
return withClosedFence;
|
|
14
14
|
}
|
|
15
|
+
const NO_ISSUES_SENTENCE = "No confirmed bugs or high-value optimizations found.";
|
|
16
|
+
export function normalizeReviewFindingsMarkdown(input) {
|
|
17
|
+
const normalized = input.replace(/\r\n/g, "\n").trim();
|
|
18
|
+
if (normalized === "" || normalized === NO_ISSUES_SENTENCE)
|
|
19
|
+
return normalized;
|
|
20
|
+
const lines = normalized.split("\n");
|
|
21
|
+
const findings = [];
|
|
22
|
+
const headerRe = /^\s*-?\s*\[(high|medium)\]\s+(.+?)\s*$/i;
|
|
23
|
+
const fileRe = /^\s*[-*]?\s*File:\s*(.+?)\s*$/i;
|
|
24
|
+
const lineRe = /^\s*[-*]?\s*Line:\s*(.+?)\s*$/i;
|
|
25
|
+
const whyRe = /^\s*[-*]?\s*Why:\s*(.+?)\s*$/i;
|
|
26
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
27
|
+
const headerMatch = lines[i].match(headerRe);
|
|
28
|
+
if (headerMatch == null)
|
|
29
|
+
continue;
|
|
30
|
+
const severity = headerMatch[1].toLowerCase();
|
|
31
|
+
const title = headerMatch[2].trim();
|
|
32
|
+
let file = null;
|
|
33
|
+
let line = null;
|
|
34
|
+
let why = null;
|
|
35
|
+
let j = i + 1;
|
|
36
|
+
while (j < lines.length) {
|
|
37
|
+
const nextHeader = lines[j].match(headerRe);
|
|
38
|
+
if (nextHeader != null)
|
|
39
|
+
break;
|
|
40
|
+
if (file == null) {
|
|
41
|
+
const m = lines[j].match(fileRe);
|
|
42
|
+
if (m != null) {
|
|
43
|
+
file = m[1].trim();
|
|
44
|
+
j += 1;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (line == null) {
|
|
49
|
+
const m = lines[j].match(lineRe);
|
|
50
|
+
if (m != null) {
|
|
51
|
+
line = m[1].trim();
|
|
52
|
+
j += 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (why == null) {
|
|
57
|
+
const m = lines[j].match(whyRe);
|
|
58
|
+
if (m != null) {
|
|
59
|
+
why = m[1].trim();
|
|
60
|
+
j += 1;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
j += 1;
|
|
65
|
+
}
|
|
66
|
+
if (file != null && line != null && why != null) {
|
|
67
|
+
findings.push({ severity, title, file, line, why });
|
|
68
|
+
i = j - 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (findings.length === 0)
|
|
72
|
+
return normalized;
|
|
73
|
+
return findings
|
|
74
|
+
.map((f) => `- [${f.severity}] ${f.title}\n File: ${f.file}\n Line: ${f.line}\n Why: ${f.why}`)
|
|
75
|
+
.join("\n\n");
|
|
76
|
+
}
|