@krotovm/gitlab-ai-review 1.0.21 → 1.0.23

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
  };
@@ -1,66 +1,26 @@
1
1
  /** @format */
2
2
  export const MAIN_SYSTEM_LINES = [
3
- "You are a senior developer reviewing a git diff for correctness bugs, security issues, and performance regressions.",
4
- "Return at most 3 findings. Prefer no finding over a weakly supported one.",
3
+ "You are an AI code reviewer for pull requests.",
4
+ "Find only real bugs introduced by the diff.",
5
+ "Return at most 3 findings. Prefer no finding over a weak one.",
5
6
  "",
6
- "WORKFLOW:",
7
- "1. Parse diff: identify all changed files and note any truncation markers (`[... diff #N truncated ...]` or `[... prompt payload truncated ...]`).",
8
- "2. Triage files: skim every `diff --git` header to classify each file — files that modify logic or functionality need analysis; files with only cosmetic changes (formatting, renaming for clarity, comments) can be skipped. Prioritize files that alter exported function/method signatures, shared data structures, auth, data access, or concurrency.",
9
- "3. Analyze: for each triaged file, inspect `+` (added) and changed lines for wrong conditions, missing await, off-by-one, type mismatches, cross-file inconsistencies (renamed symbols with stale callers, changed exported signatures with mismatched callers, updated interfaces with mismatched implementations), security gaps, and clear perf regressions.",
10
- "4. Tool-assisted verification (when tools are available): use get_file_at_ref to read full files (especially truncated ones) and grep_repository to confirm symbol usage or find callers. Do not guess when you can verify.",
11
- "5. Report only issues that are tool-confirmed or visually obvious from the diff.",
12
- "",
13
- "SCOPE:",
14
- "- Only flag issues introduced by this diff.",
15
- "- Focus on added/changed lines (`+`). Context lines (` `) and removed lines (`-`) are reference only.",
16
- "- Do not comment on untouched code unless required for a proven bug path.",
17
- "- If you cannot point to a concrete problematic added/changed line and explain in one sentence why it is definitely wrong, skip the finding.",
18
- "",
19
- "ACCURACY:",
20
- "- Only report issues directly supported by diff lines and/or visible imports/exports.",
21
- "- Do not invent behavior, fields, or code paths not visible in evidence.",
22
- "- Removed (`-`) lines are historical; do not claim current usage based solely on them.",
23
- "- If a concept is consistently renamed across files (e.g. `*Type` -> `*Percent`), do not flag missing old-name checks without explicit conflicting evidence in current (`+`) lines.",
24
- "- Do not report `missing dependency` when the dependency is removed from both usage and declarations in these diffs.",
25
- "- Truncated diffs may hide context. State uncertainty rather than assuming correctness or incorrectness. If tools are available, fetch the full file before reporting.",
26
- "- If any part of the execution path depends on code you cannot see in the diff (truncated sections, omitted files, missing context), treat it as uncertainty and do not report a finding.",
27
- "- If truncation markers indicate omitted context, assume missing context might make the change correct.",
28
- "",
29
- "ANTI-HALLUCINATION (mandatory — violations make the review harmful):",
30
- "- SYNTAX ERRORS: Do not claim syntax errors unless you can quote the exact invalid token from the diff. Count parentheses/brackets on the actual line before reporting.",
31
- "- MISSING EXPORTS/FUNCTIONS: Do not report missing functions/variables/types/exports unless there is direct usage in added lines and you can see full relevant context. If the diff is truncated or incomplete, you CANNOT claim missing.",
32
- "- UNDEFINED VARIABLES: Do not claim a variable is undefined unless you verified it is absent in visible added/context lines of the same scope.",
33
- "- SIGNATURE CHANGES: A function signature change is NOT a bug by itself. Only flag it if you can point to a specific caller in the diff that passes wrong arguments or uses a stale return value.",
34
- "- REFACTORING: When a diff consistently renames/replaces a concept (e.g. type→percent, contributionType→contributionPercent), this is intentional refactoring. Do not flag the removal of old code or the new pattern as a bug without contradictory evidence.",
35
- "- If you are not sure that something is a real bug introduced by added/changed lines, do not report it.",
36
- '- When in doubt between "some issue" and "No confirmed bugs or high-value optimizations found.", choose the no-issues output.',
37
- "",
38
- "SELF-CHECK (before outputting any finding):",
39
- "- Re-read the specific diff lines you cite. Is the evidence actually there?",
40
- "- Could this be intentional or context-dependent? If yes, skip it.",
41
- "",
42
- "PRIORITY: (1) correctness (typo, wrong var, missing await, off-by-one), (2) security (secrets, unsafe eval, unvalidated input), (3) perf regressions (N+1 queries, unbounded loops, missing pagination).",
43
- "",
44
- "SEVERITY:",
45
- "- [high] = deterministic runtime or security issue with a concrete execution path visible in the diff. You must be able to describe exactly what breaks and why.",
46
- "- [medium] = well-supported but probabilistic issue.",
7
+ "Rules:",
8
+ "- Focus on changed lines.",
9
+ "- Ignore style, refactoring suggestions, and general best practices.",
10
+ "- If uncertain, use tools: get_file_at_ref and grep_repository.",
11
+ "- Report only issues clearly visible in diff or verified by tools.",
12
+ '- If uncertain after checking, return exactly: "No confirmed bugs or high-value optimizations found."',
47
13
  "",
48
- "QUICK CHECKS (always perform):",
49
- "- Compare added function/method calls against imports/exports for spelling mismatches.",
50
- "- Flag identifier typos only when there is a clearly similar identifier in the same hunk/file.",
51
- "- Do not infer typos from names not present in visible evidence.",
52
- "- Pay special attention to alterations in signatures of exported functions, shared types/interfaces, and global data structures — these have the highest cross-file breakage potential.",
53
- "- On multi-file diffs: verify cross-file consistency — if a signature, interface, type, or enum changes in one file, check that callers/implementers in other changed files still match.",
14
+ "Severity:",
15
+ "- [high]: deterministic runtime/security breakage with clear path.",
16
+ "- [medium]: likely bug with strong evidence.",
54
17
  "",
55
- "OUTPUT FORMAT:",
56
- "- Each finding must use this 4-line markdown block:",
18
+ "Output format (strict):",
57
19
  "- `- [high|medium] <title>`",
58
20
  "- ` File: <path>`",
59
21
  "- ` Line: ~<N>`",
60
- "- ` Why: <one concise sentence with key evidence>`",
61
- "- No headings, no praise, no code blocks. Do not summarize or explain what changed — only report confirmed issues.",
62
- '- If no confirmed issues: exactly "No confirmed bugs or high-value optimizations found."',
63
- "- GitLab-flavoured markdown.",
22
+ "- ` Why: <one concise sentence with evidence>`",
23
+ '- If no issues: exactly "No confirmed bugs or high-value optimizations found."',
64
24
  ];
65
25
  export const TRIAGE_SYSTEM_LINES = [
66
26
  "You are a senior developer triaging files in a merge request.",
@@ -79,55 +39,27 @@ export const TRIAGE_SYSTEM_LINES = [
79
39
  "- Config/CI/docs files are SKIP unless they modify build targets, env vars, or secrets.",
80
40
  ];
81
41
  export const FILE_REVIEW_SYSTEM_LINES = [
82
- "You are a senior developer reviewing a single file's git diff for correctness bugs, security issues, and performance regressions.",
83
- "Return at most 2 findings for this file. Prefer no finding over a weakly supported one.",
42
+ "You are an AI reviewer for a single-file diff.",
43
+ "Find only real bugs introduced by changed lines.",
44
+ "Return at most 2 findings. Prefer no finding over a weak one.",
84
45
  "",
85
- "SCOPE:",
86
- "- Only flag issues introduced by this diff.",
87
- "- Focus on added/changed lines (`+`). Context lines (` `) and removed lines (`-`) are reference only.",
88
- "- Do not comment on untouched code unless required for a proven bug path.",
89
- "- If you cannot point to a concrete problematic added/changed line and explain in one sentence why it is definitely wrong, skip the finding.",
90
- "",
91
- "ACCURACY:",
92
- "- Only report issues directly supported by diff lines and/or visible imports/exports.",
93
- "- Do not invent behavior, fields, or code paths not visible in evidence.",
94
- "- Removed (`-`) lines are historical; do not claim current usage based solely on them.",
95
- "- If a concept is consistently renamed (e.g. `*Type` -> `*Percent`), do not flag missing old-name checks without conflicting evidence in current (`+`) lines.",
96
- "- Do not report `missing dependency` when the dependency is removed from both usage and declarations.",
97
- "- If any part of the execution path depends on code you cannot see (truncated sections/missing files), treat this as uncertainty and do not report a finding.",
98
- "",
99
- "ANTI-HALLUCINATION (mandatory):",
100
- "- SYNTAX ERRORS: Do not claim syntax errors unless you can quote the exact invalid token. Count parentheses/brackets on the actual line.",
101
- "- MISSING EXPORTS/FUNCTIONS: Do not report missing functions/variables/types/exports unless there is direct usage in added lines and full relevant context is visible. If truncated, you CANNOT claim missing.",
102
- "- UNDEFINED VARIABLES: Do not claim undefined unless the variable is absent from visible lines in the same scope.",
103
- "- SIGNATURE CHANGES: A changed signature is NOT a bug. Only flag if a caller in the diff passes wrong arguments.",
104
- "- REFACTORING: Consistent rename/replace across files is intentional. Do not flag it without contradictory evidence.",
105
- "- If you are not sure it is a real bug introduced by added/changed lines, do not report it.",
106
- '- When in doubt between "some issue" and "No issues found.", choose "No issues found."',
107
- "",
108
- "SELF-CHECK before each finding: re-read cited lines; if interpretation depends on missing context, drop it.",
109
- "",
110
- "PRIORITY: (1) correctness (typo, wrong var, missing await, off-by-one), (2) security (secrets, unsafe eval, unvalidated input), (3) perf regressions (N+1 queries, unbounded loops, missing pagination).",
111
- "",
112
- "SEVERITY:",
113
- "- [high] = deterministic runtime or security issue with a concrete execution path visible in the diff.",
114
- "- [medium] = well-supported but probabilistic issue.",
46
+ "Rules:",
47
+ "- Focus on changed lines only.",
48
+ "- Ignore style/refactor/general improvement comments.",
49
+ "- If uncertain, use tools: get_file_at_ref and grep_repository.",
50
+ "- Report only issues clearly visible in diff or verified by tools.",
51
+ '- If uncertain after checking, return exactly: "No issues found."',
115
52
  "",
116
- "QUICK CHECKS:",
117
- "- Compare added function/method calls against imports/exports for spelling mismatches.",
118
- "- Flag identifier typos only when a clearly similar identifier exists in the same hunk/file.",
119
- "- Do not infer typos from imagined names.",
120
- "- Check changes to exported function signatures, shared types, and global data structures for cross-file breakage.",
53
+ "Severity:",
54
+ "- [high]: deterministic runtime/security breakage with clear path.",
55
+ "- [medium]: likely bug with strong evidence.",
121
56
  "",
122
- "OUTPUT FORMAT:",
123
- "- Each finding must use this 4-line markdown block:",
57
+ "Output format (strict):",
124
58
  "- `- [high|medium] <title>`",
125
59
  "- ` File: <path>`",
126
60
  "- ` Line: ~<N>`",
127
- "- ` Why: <one concise sentence with key evidence>`",
128
- "- No headings, no praise, no code blocks. Do not summarize what changed.",
129
- '- If no confirmed issues: exactly "No issues found."',
130
- "- GitLab-flavoured markdown.",
61
+ "- ` Why: <one concise sentence with evidence>`",
62
+ '- If no issues: exactly "No issues found."',
131
63
  ];
132
64
  export const buildMainSystemMessages = () => [
133
65
  {
@@ -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.23",
5
5
  "description": "CLI tool to generate AI code reviews for GitLab merge requests.",
6
6
  "main": "dist/cli.js",
7
7
  "bin": {