@krotovm/gitlab-ai-review 1.0.30 → 1.0.31
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 +18 -4
- package/dist/prompt/index.js +33 -14
- 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 { buildAnswer, buildConsolidatePrompt, buildFileReviewPrompt, buildPrompt, buildTriagePrompt, buildVerificationPrompt, extractCompletionText, parseTriageResponse, } from "../prompt/index.js";
|
|
3
|
+
import { buildAnswer, buildConsolidatePrompt, buildFileReviewPrompt, buildPrompt, buildTriagePrompt, buildVerificationPrompt, extractCompletionText, parseTriageResponseDetailed, 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, MAX_VERIFICATION_TOOL_ROUNDS, TOOL_NAME_GET_FILE, TOOL_NAME_GREP, } from "./tooling.js";
|
|
6
6
|
async function appendDebugDump(_debugDumpFile, debugRecordWriter, record) {
|
|
@@ -164,6 +164,7 @@ async function mapWithConcurrency(items, concurrency, fn) {
|
|
|
164
164
|
export async function reviewMergeRequestWithTools(params) {
|
|
165
165
|
const { openaiInstance, aiModel, promptLimits, changes, refs, gitLabProjectApiUrl, projectId, headers, forceTools, loggers, debugDumpFile, debugRecordWriter, } = params;
|
|
166
166
|
const { logDebug, logStep } = loggers;
|
|
167
|
+
logStep(`Single-pass review started for ${changes.length} file(s) (fallback mode).`);
|
|
167
168
|
const messages = buildPrompt({
|
|
168
169
|
changes: changes.map((change) => ({ diff: change.diff })),
|
|
169
170
|
limits: promptLimits,
|
|
@@ -217,6 +218,7 @@ export async function reviewMergeRequestWithTools(params) {
|
|
|
217
218
|
},
|
|
218
219
|
];
|
|
219
220
|
for (let round = 0; round < MAX_TOOL_ROUNDS; round += 1) {
|
|
221
|
+
logStep(`Single-pass round ${round + 1}/${MAX_TOOL_ROUNDS}: requesting model response...`);
|
|
220
222
|
const completion = await createCompletionWithDebug({
|
|
221
223
|
openaiInstance,
|
|
222
224
|
requestLabel: `main_review_round_${round + 1}`,
|
|
@@ -236,6 +238,7 @@ export async function reviewMergeRequestWithTools(params) {
|
|
|
236
238
|
return buildAnswer(completion);
|
|
237
239
|
const toolCalls = message.tool_calls ?? [];
|
|
238
240
|
logDebug(`main-review round=${round + 1} tool_calls=${toolCalls.length} finish_reason=${completion.choices[0]?.finish_reason ?? "unknown"}`);
|
|
241
|
+
logStep(`Single-pass round ${round + 1}: model returned ${toolCalls.length} tool call(s).`);
|
|
239
242
|
if (toolCalls.length === 0)
|
|
240
243
|
return buildAnswer(completion);
|
|
241
244
|
messages.push({
|
|
@@ -290,6 +293,7 @@ export async function reviewMergeRequestWithTools(params) {
|
|
|
290
293
|
role: "user",
|
|
291
294
|
content: `Tool-call limit reached (${MAX_TOOL_ROUNDS}). Do not call any tools. Provide your best-effort final review now, strictly following the required output format. If confidence is low, return the exact no-issues sentence.`,
|
|
292
295
|
});
|
|
296
|
+
logStep("Single-pass tool-call limit reached. Requesting final answer.");
|
|
293
297
|
const finalCompletion = await createCompletionWithDebug({
|
|
294
298
|
openaiInstance,
|
|
295
299
|
requestLabel: "main_review_final_after_tool_limit",
|
|
@@ -599,6 +603,8 @@ export async function reviewMergeRequestMultiPass(params) {
|
|
|
599
603
|
const triageMessages = buildTriagePrompt(triageInputs);
|
|
600
604
|
let triageResult = null;
|
|
601
605
|
let triageText = null;
|
|
606
|
+
let triageParseReason = null;
|
|
607
|
+
let triageParseError = null;
|
|
602
608
|
try {
|
|
603
609
|
const triageCompletion = await createCompletionWithDebug({
|
|
604
610
|
openaiInstance,
|
|
@@ -614,8 +620,12 @@ export async function reviewMergeRequestMultiPass(params) {
|
|
|
614
620
|
},
|
|
615
621
|
});
|
|
616
622
|
triageText = extractCompletionText(triageCompletion);
|
|
617
|
-
if (triageText != null)
|
|
618
|
-
|
|
623
|
+
if (triageText != null) {
|
|
624
|
+
const triageParse = parseTriageResponseDetailed(triageText);
|
|
625
|
+
triageResult = triageParse.result;
|
|
626
|
+
triageParseReason = triageParse.reason;
|
|
627
|
+
triageParseError = triageParse.parseError;
|
|
628
|
+
}
|
|
619
629
|
}
|
|
620
630
|
catch (error) {
|
|
621
631
|
logStep(`Triage pass failed: ${error?.message ?? error}. Falling back to single-pass.`);
|
|
@@ -624,7 +634,11 @@ export async function reviewMergeRequestMultiPass(params) {
|
|
|
624
634
|
if (triageText != null) {
|
|
625
635
|
const triagePreview = triageText.replace(/\s+/g, " ").trim().slice(0, 200);
|
|
626
636
|
const looksLikeHtml = /<html|<!doctype html/i.test(triageText);
|
|
627
|
-
|
|
637
|
+
const parseReasonText = triageParseReason != null
|
|
638
|
+
? `reason=${triageParseReason}`
|
|
639
|
+
: `reason=${looksLikeHtml ? "html_response" : "unknown_non_json"}`;
|
|
640
|
+
const parseErrorText = triageParseError != null ? ` parse_error=${triageParseError}` : "";
|
|
641
|
+
logStep(`Triage parse failed: ${parseReasonText}.${parseErrorText} Preview: ${triagePreview || "<empty>"}`);
|
|
628
642
|
}
|
|
629
643
|
else {
|
|
630
644
|
logStep("Triage parse failed: model returned empty response body.");
|
package/dist/prompt/index.js
CHANGED
|
@@ -58,29 +58,48 @@ export function buildTriagePrompt(changes) {
|
|
|
58
58
|
];
|
|
59
59
|
}
|
|
60
60
|
export function parseTriageResponse(text) {
|
|
61
|
+
return parseTriageResponseDetailed(text).result;
|
|
62
|
+
}
|
|
63
|
+
export function parseTriageResponseDetailed(text) {
|
|
64
|
+
const cleaned = text
|
|
65
|
+
.replace(/```json?\s*/g, "")
|
|
66
|
+
.replace(/```\s*/g, "")
|
|
67
|
+
.trim();
|
|
68
|
+
if (cleaned === "") {
|
|
69
|
+
return { result: null, reason: "empty_response", parseError: null };
|
|
70
|
+
}
|
|
61
71
|
try {
|
|
62
|
-
const cleaned = text
|
|
63
|
-
.replace(/```json?\s*/g, "")
|
|
64
|
-
.replace(/```\s*/g, "")
|
|
65
|
-
.trim();
|
|
66
72
|
const parsed = JSON.parse(cleaned);
|
|
67
73
|
if (typeof parsed.summary === "string" && Array.isArray(parsed.files)) {
|
|
68
74
|
return {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
result: {
|
|
76
|
+
summary: parsed.summary,
|
|
77
|
+
files: parsed.files
|
|
78
|
+
.filter((f) => typeof f.path === "string" &&
|
|
79
|
+
(f.verdict === "NEEDS_REVIEW" || f.verdict === "SKIP"))
|
|
80
|
+
.map((f) => ({
|
|
81
|
+
path: f.path,
|
|
82
|
+
verdict: f.verdict,
|
|
83
|
+
})),
|
|
84
|
+
},
|
|
85
|
+
reason: null,
|
|
86
|
+
parseError: null,
|
|
77
87
|
};
|
|
78
88
|
}
|
|
89
|
+
return { result: null, reason: "invalid_schema", parseError: null };
|
|
79
90
|
}
|
|
80
|
-
catch {
|
|
91
|
+
catch (error) {
|
|
81
92
|
// JSON parse failure — caller will fall back to single-pass.
|
|
93
|
+
return {
|
|
94
|
+
result: null,
|
|
95
|
+
reason: "invalid_json",
|
|
96
|
+
parseError: error instanceof Error
|
|
97
|
+
? error.message
|
|
98
|
+
: typeof error === "string"
|
|
99
|
+
? error
|
|
100
|
+
: JSON.stringify(error),
|
|
101
|
+
};
|
|
82
102
|
}
|
|
83
|
-
return null;
|
|
84
103
|
}
|
|
85
104
|
export function buildFileReviewPrompt(params) {
|
|
86
105
|
const { filePath, fileDiff, summary, otherChangedFiles, allowTools = false, } = params;
|