@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.
@@ -1,6 +1,6 @@
1
1
  /** @format */
2
2
  import OpenAI from "openai";
3
- import { AI_MAX_OUTPUT_TOKENS, buildAnswer, buildConsolidatePrompt, buildFileReviewPrompt, buildPrompt, buildTriagePrompt, buildVerificationPrompt, extractCompletionText, parseTriageResponse, } from "../prompt/index.js";
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
  });
@@ -1,6 +1,6 @@
1
1
  /** @format */
2
2
  import OpenAI from "openai";
3
- import { AI_MAX_OUTPUT_TOKENS, AI_MODEL_TEMPERATURE } from "../prompt/index.js";
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 { url: url.toString(), error: { name: res.name, message: res.message } };
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 { url: url.toString(), status: res.status, statusText: res.statusText, body: bodyText.slice(0, 1000) };
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",
@@ -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 safe = sanitizeGitLabMarkdown(content);
276
+ const normalizedFindings = normalizeReviewFindingsMarkdown(content);
277
+ const safe = sanitizeGitLabMarkdown(normalizedFindings);
277
278
  return `${safe}\n\n---\n_${DISCLAIMER}_`;
278
279
  };
@@ -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
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@krotovm/gitlab-ai-review",
4
- "version": "1.0.21",
4
+ "version": "1.0.22",
5
5
  "description": "CLI tool to generate AI code reviews for GitLab merge requests.",
6
6
  "main": "dist/cli.js",
7
7
  "bin": {