kodevu 0.1.27 → 0.1.29
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/README.md +9 -1
- package/package.json +1 -1
- package/src/config.js +55 -2
- package/src/diff-processor.js +95 -0
- package/src/index.js +1 -0
- package/src/report-generator.js +185 -0
- package/src/review-runner.js +43 -614
- package/src/reviewers.js +115 -0
- package/src/state-manager.js +61 -0
- package/src/token-usage.js +110 -0
- package/src/utils.js +34 -0
package/README.md
CHANGED
|
@@ -59,6 +59,12 @@ Run with debug logs:
|
|
|
59
59
|
npx kodevu /path/to/your/repo --debug
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
Specify the output language (e.g., Chinese):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npx kodevu /path/to/your/repo --lang zh
|
|
66
|
+
```
|
|
67
|
+
|
|
62
68
|
Use a custom config path only when needed:
|
|
63
69
|
|
|
64
70
|
```bash
|
|
@@ -77,7 +83,8 @@ npx kodevu /path/to/your/repo --config ./config.current.json
|
|
|
77
83
|
|
|
78
84
|
- `target`: required repository target; can be provided by config or as the CLI positional argument
|
|
79
85
|
- `reviewer`: `codex`, `gemini`, `copilot`, or `auto`; default `auto`
|
|
80
|
-
- `prompt`:
|
|
86
|
+
- `prompt`: additional instructions for the reviewer; usually empty by default as the core instructions are built-in
|
|
87
|
+
- `lang`: output language for the review (e.g., `zh`, `en`, `auto`); default `auto`
|
|
81
88
|
- `outputDir`: report output directory; default `~/.kodevu`
|
|
82
89
|
- `outputFormats`: report formats to generate; supports `markdown` and `json`; default `["markdown"]`
|
|
83
90
|
- `stateFilePath`: review state file path; default `~/.kodevu/state.json`
|
|
@@ -110,6 +117,7 @@ Internal defaults:
|
|
|
110
117
|
- If `outputFormats` includes `json`, matching `.json` files are generated alongside Markdown reports.
|
|
111
118
|
- `~/.kodevu/state.json` stores per-project checkpoints keyed by repository identity; only the v2 multi-project structure is supported.
|
|
112
119
|
- If the reviewer command exits non-zero or times out, the report is still written, but the state is not advanced so the change can be retried later.
|
|
120
|
+
- Review instructions are built into the tool in English to ensure consistent logic across reviewers. The `lang` setting (CLI `--lang` or config `lang`) determines the language AI uses for the resulting review. When set to `auto`, Kodevu detects the language from the system environment (`LANG`, `LC_ALL`, or system locale).
|
|
113
121
|
- Each report includes a `Token Usage` section recording token consumption for the review task. When the reviewer CLI outputs token statistics (via stderr), those are used directly (`source: "reviewer"`). Otherwise tokens are estimated at ~4 characters per token (`source: "estimate"`). The JSON report contains a `tokenUsage` object with `inputTokens`, `outputTokens`, `totalTokens`, and `source`.
|
|
114
122
|
|
|
115
123
|
## License
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -6,15 +6,16 @@ import { findCommandOnPath } from "./shell.js";
|
|
|
6
6
|
const defaultStorageDir = path.join(os.homedir(), ".kodevu");
|
|
7
7
|
const SUPPORTED_REVIEWERS = ["codex", "gemini", "copilot"];
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
const defaultConfig = {
|
|
10
11
|
reviewer: "auto",
|
|
11
12
|
target: "",
|
|
13
|
+
lang: "auto",
|
|
12
14
|
outputDir: defaultStorageDir,
|
|
13
15
|
stateFilePath: path.join(defaultStorageDir, "state.json"),
|
|
14
16
|
logsDir: path.join(defaultStorageDir, "logs"),
|
|
15
17
|
commandTimeoutMs: 600000,
|
|
16
|
-
prompt:
|
|
17
|
-
"请严格审查当前变更,优先指出 bug、回归风险、兼容性问题、安全问题、边界条件缺陷和缺失测试。请使用简体中文输出 Markdown;如果没有明确缺陷,请写“未发现明确缺陷”,并补充剩余风险。",
|
|
18
|
+
prompt: "",
|
|
18
19
|
maxRevisionsPerRun: 5,
|
|
19
20
|
outputFormats: ["markdown"]
|
|
20
21
|
};
|
|
@@ -22,6 +23,7 @@ const defaultConfig = {
|
|
|
22
23
|
const configTemplate = {
|
|
23
24
|
target: "C:/path/to/your/repository-or-subdirectory",
|
|
24
25
|
reviewer: defaultConfig.reviewer,
|
|
26
|
+
lang: defaultConfig.lang,
|
|
25
27
|
prompt: defaultConfig.prompt,
|
|
26
28
|
outputDir: "~/.kodevu",
|
|
27
29
|
stateFilePath: "~/.kodevu/state.json",
|
|
@@ -74,6 +76,38 @@ function normalizeOutputFormats(outputFormats, loadedConfigPath) {
|
|
|
74
76
|
return normalized;
|
|
75
77
|
}
|
|
76
78
|
|
|
79
|
+
function detectLanguage() {
|
|
80
|
+
const envLang = (process.env.LANG || process.env.LC_ALL || process.env.LC_MESSAGES || "").toLowerCase();
|
|
81
|
+
const intlLocale = (() => {
|
|
82
|
+
try {
|
|
83
|
+
return Intl.DateTimeFormat().resolvedOptions().locale.toLowerCase();
|
|
84
|
+
} catch {
|
|
85
|
+
return "";
|
|
86
|
+
}
|
|
87
|
+
})();
|
|
88
|
+
|
|
89
|
+
// On Windows, the LANG environment variable is often set to 'en_US.UTF-8' by default
|
|
90
|
+
// in many shells (like Git Bash/MSYS2), regardless of the actual OS display language.
|
|
91
|
+
// We prefer the system locale (Intl) on Windows if it's Chinese.
|
|
92
|
+
if (os.platform() === "win32" && intlLocale.startsWith("zh")) {
|
|
93
|
+
return "zh";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (envLang) {
|
|
97
|
+
if (envLang.startsWith("zh")) return "zh";
|
|
98
|
+
if (envLang.startsWith("en")) return "en";
|
|
99
|
+
return envLang.split(/[._-]/)[0];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (intlLocale) {
|
|
103
|
+
if (intlLocale.startsWith("zh")) return "zh";
|
|
104
|
+
if (intlLocale.startsWith("en")) return "en";
|
|
105
|
+
return intlLocale.split("-")[0];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return "en";
|
|
109
|
+
}
|
|
110
|
+
|
|
77
111
|
async function resolveAutoReviewers(debug, loadedConfigPath) {
|
|
78
112
|
const availableReviewers = [];
|
|
79
113
|
|
|
@@ -109,6 +143,7 @@ export function parseCliArgs(argv) {
|
|
|
109
143
|
debug: false,
|
|
110
144
|
help: false,
|
|
111
145
|
reviewer: "",
|
|
146
|
+
lang: "",
|
|
112
147
|
prompt: "",
|
|
113
148
|
commandExplicitlySet: false
|
|
114
149
|
};
|
|
@@ -163,6 +198,16 @@ export function parseCliArgs(argv) {
|
|
|
163
198
|
continue;
|
|
164
199
|
}
|
|
165
200
|
|
|
201
|
+
if (value === "--lang" || value === "-l") {
|
|
202
|
+
const lang = argv[index + 1];
|
|
203
|
+
if (!lang || lang.startsWith("-")) {
|
|
204
|
+
throw new Error(`Missing value for ${value}`);
|
|
205
|
+
}
|
|
206
|
+
args.lang = lang;
|
|
207
|
+
index += 1;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
166
211
|
if (!value.startsWith("-") && args.command === "run" && !args.target) {
|
|
167
212
|
args.target = value;
|
|
168
213
|
continue;
|
|
@@ -209,12 +254,19 @@ export async function loadConfig(configPath, cliArgs = {}) {
|
|
|
209
254
|
config.prompt = cliArgs.prompt;
|
|
210
255
|
}
|
|
211
256
|
|
|
257
|
+
if (cliArgs.lang) {
|
|
258
|
+
config.lang = cliArgs.lang;
|
|
259
|
+
}
|
|
260
|
+
|
|
212
261
|
if (!config.target) {
|
|
213
262
|
throw new Error('Missing target. Pass `npx kodevu <repo-path>` or set "target" in config.json.');
|
|
214
263
|
}
|
|
215
264
|
|
|
216
265
|
config.debug = Boolean(cliArgs.debug);
|
|
217
266
|
config.reviewer = String(config.reviewer || "auto").toLowerCase();
|
|
267
|
+
config.lang = String(config.lang || "auto").toLowerCase();
|
|
268
|
+
|
|
269
|
+
config.resolvedLang = config.lang === "auto" ? detectLanguage() : config.lang;
|
|
218
270
|
|
|
219
271
|
if (config.reviewer === "auto") {
|
|
220
272
|
const availableReviewers = await resolveAutoReviewers(config.debug, loadedConfigPath);
|
|
@@ -264,6 +316,7 @@ Options:
|
|
|
264
316
|
--config, -c Optional config json path. If omitted, ./config.json is loaded only when present
|
|
265
317
|
--reviewer, -r Override reviewer (codex | gemini | copilot | auto)
|
|
266
318
|
--prompt, -p Override prompt
|
|
319
|
+
--lang, -l Override output language (e.g. zh, en, auto)
|
|
267
320
|
--debug, -d Print extra debug information to the console
|
|
268
321
|
--help, -h Show help
|
|
269
322
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { countLines } from "./utils.js";
|
|
2
|
+
|
|
3
|
+
export const DIFF_LIMITS = {
|
|
4
|
+
review: {
|
|
5
|
+
maxLines: 4000,
|
|
6
|
+
maxChars: 120000
|
|
7
|
+
},
|
|
8
|
+
report: {
|
|
9
|
+
maxLines: 1500,
|
|
10
|
+
maxChars: 40000
|
|
11
|
+
},
|
|
12
|
+
tailLines: 200
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function trimBlockToChars(text, maxChars, keepTail = false) {
|
|
16
|
+
if (text.length <= maxChars) {
|
|
17
|
+
return text;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (maxChars <= 3) {
|
|
21
|
+
return ".".repeat(Math.max(maxChars, 0));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return keepTail ? `...${text.slice(-(maxChars - 3))}` : `${text.slice(0, maxChars - 3)}...`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function truncateDiffText(diffText, maxLines, maxChars, tailLines, purposeLabel) {
|
|
28
|
+
const normalizedDiff = diffText.replace(/\r\n/g, "\n");
|
|
29
|
+
const originalLineCount = countLines(normalizedDiff);
|
|
30
|
+
const originalCharCount = normalizedDiff.length;
|
|
31
|
+
|
|
32
|
+
if (originalLineCount <= maxLines && originalCharCount <= maxChars) {
|
|
33
|
+
return {
|
|
34
|
+
text: diffText,
|
|
35
|
+
wasTruncated: false,
|
|
36
|
+
originalLineCount,
|
|
37
|
+
originalCharCount,
|
|
38
|
+
outputLineCount: originalLineCount,
|
|
39
|
+
outputCharCount: originalCharCount
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const lines = normalizedDiff.split("\n");
|
|
44
|
+
const safeTailLines = Math.min(Math.max(tailLines, 0), Math.max(maxLines - 2, 0));
|
|
45
|
+
const headLineCount = Math.max(maxLines - safeTailLines - 1, 1);
|
|
46
|
+
let headBlock = lines.slice(0, headLineCount).join("\n");
|
|
47
|
+
let tailBlock = safeTailLines > 0 ? lines.slice(-safeTailLines).join("\n") : "";
|
|
48
|
+
const omittedLineCount = Math.max(originalLineCount - headLineCount - safeTailLines, 0);
|
|
49
|
+
const markerBlock = [
|
|
50
|
+
`... diff truncated for ${purposeLabel} ...`,
|
|
51
|
+
`original lines: ${originalLineCount}, original chars: ${originalCharCount}`,
|
|
52
|
+
`omitted lines: ${omittedLineCount}`
|
|
53
|
+
].join("\n");
|
|
54
|
+
|
|
55
|
+
let truncatedText = [headBlock, markerBlock, tailBlock].filter(Boolean).join("\n");
|
|
56
|
+
|
|
57
|
+
if (truncatedText.length > maxChars) {
|
|
58
|
+
const reservedChars = markerBlock.length + (tailBlock ? 2 : 1);
|
|
59
|
+
const remainingChars = Math.max(maxChars - reservedChars, 0);
|
|
60
|
+
const headBudget = tailBlock ? Math.floor(remainingChars * 0.7) : remainingChars;
|
|
61
|
+
const tailBudget = tailBlock ? Math.max(remainingChars - headBudget, 0) : 0;
|
|
62
|
+
headBlock = trimBlockToChars(headBlock, headBudget, false);
|
|
63
|
+
tailBlock = trimBlockToChars(tailBlock, tailBudget, true);
|
|
64
|
+
truncatedText = [headBlock, markerBlock, tailBlock].filter(Boolean).join("\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
text: truncatedText,
|
|
69
|
+
wasTruncated: true,
|
|
70
|
+
originalLineCount,
|
|
71
|
+
originalCharCount,
|
|
72
|
+
outputLineCount: countLines(truncatedText),
|
|
73
|
+
outputCharCount: truncatedText.length
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function prepareDiffPayloads(config, diffText) {
|
|
78
|
+
return {
|
|
79
|
+
review: truncateDiffText(
|
|
80
|
+
diffText,
|
|
81
|
+
DIFF_LIMITS.review.maxLines,
|
|
82
|
+
DIFF_LIMITS.review.maxChars,
|
|
83
|
+
DIFF_LIMITS.tailLines,
|
|
84
|
+
"reviewer input"
|
|
85
|
+
),
|
|
86
|
+
report: truncateDiffText(
|
|
87
|
+
diffText,
|
|
88
|
+
DIFF_LIMITS.report.maxLines,
|
|
89
|
+
DIFF_LIMITS.report.maxChars,
|
|
90
|
+
Math.min(DIFF_LIMITS.tailLines, DIFF_LIMITS.report.maxLines),
|
|
91
|
+
"report output"
|
|
92
|
+
)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
export const CORE_REVIEW_INSTRUCTION =
|
|
2
|
+
"Please strictly review the current changes, prioritizing bugs, regression risks, compatibility issues, security concerns, boundary condition flaws, and missing tests. Please use Markdown for your response. If no clear flaws are found, write \"No clear flaws found\" and supplement with residual risks.";
|
|
3
|
+
|
|
4
|
+
export function getReviewWorkspaceRoot(config, backend, targetInfo) {
|
|
5
|
+
if (backend.kind === "git" && targetInfo.repoRootPath) {
|
|
6
|
+
return targetInfo.repoRootPath;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (backend.kind === "svn" && targetInfo.workingCopyPath) {
|
|
10
|
+
return targetInfo.workingCopyPath;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return config.baseDir;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function buildPrompt(config, backend, targetInfo, details, reviewDiffPayload) {
|
|
17
|
+
const fileList = details.changedPaths.map((item) => `${item.action} ${item.relativePath}`).join("\n");
|
|
18
|
+
const workspaceRoot = getReviewWorkspaceRoot(config, backend, targetInfo);
|
|
19
|
+
const canReadRelatedFiles = backend.kind === "git" || Boolean(targetInfo.workingCopyPath);
|
|
20
|
+
|
|
21
|
+
const langInstruction = config.resolvedLang === "zh"
|
|
22
|
+
? "Please use Simplified Chinese for your response."
|
|
23
|
+
: `Please use ${config.resolvedLang || "English"} for your response.`;
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
CORE_REVIEW_INSTRUCTION,
|
|
27
|
+
langInstruction,
|
|
28
|
+
config.prompt,
|
|
29
|
+
canReadRelatedFiles
|
|
30
|
+
? `You are running inside a read-only workspace rooted at: ${workspaceRoot}`
|
|
31
|
+
: "No local repository workspace is available for this review run.",
|
|
32
|
+
canReadRelatedFiles
|
|
33
|
+
? "Besides the diff below, you may read other related files in the workspace when needed to understand call sites, shared utilities, configuration, tests, or data flow. Do not modify files or rely on shell commands."
|
|
34
|
+
: "Review primarily from the diff below. Do not assume access to other local files, shell commands, or repository history.",
|
|
35
|
+
"Use plain text file references like path/to/file.js:123. Do not emit clickable workspace links.",
|
|
36
|
+
`Repository Type: ${backend.displayName}`,
|
|
37
|
+
`Change ID: ${details.displayId}`,
|
|
38
|
+
`Author: ${details.author}`,
|
|
39
|
+
`Date: ${details.date || "unknown"}`,
|
|
40
|
+
`Changed files:\n${fileList || "(none)"}`,
|
|
41
|
+
`Commit message:\n${details.message || "(empty)"}`,
|
|
42
|
+
reviewDiffPayload.wasTruncated
|
|
43
|
+
? `Diff delivery note: the diff was truncated before being sent to the reviewer to stay within configured size limits. Original diff size was ${reviewDiffPayload.originalLineCount} lines / ${reviewDiffPayload.originalCharCount} chars, and the included excerpt is ${reviewDiffPayload.outputLineCount} lines / ${reviewDiffPayload.outputCharCount} chars. Use the changed file list and inspect related workspace files when needed.`
|
|
44
|
+
: `Diff delivery note: the full diff is included. Size is ${reviewDiffPayload.originalLineCount} lines / ${reviewDiffPayload.originalCharCount} chars.`
|
|
45
|
+
].filter(Boolean).join("\n\n");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function formatTokenUsage(tokenUsage) {
|
|
49
|
+
const sourceLabel = tokenUsage.source === "reviewer" ? "reviewer reported" : "estimated (~4 chars/token)";
|
|
50
|
+
return [
|
|
51
|
+
`- Input Tokens: \`${tokenUsage.inputTokens}\``,
|
|
52
|
+
`- Output Tokens: \`${tokenUsage.outputTokens}\``,
|
|
53
|
+
`- Total Tokens: \`${tokenUsage.totalTokens}\``,
|
|
54
|
+
`- Token Source: \`${sourceLabel}\``
|
|
55
|
+
].join("\n");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function formatDiffHandling(diffPayload, label) {
|
|
59
|
+
return [
|
|
60
|
+
`- ${label} Original Lines: \`${diffPayload.originalLineCount}\``,
|
|
61
|
+
`- ${label} Original Chars: \`${diffPayload.originalCharCount}\``,
|
|
62
|
+
`- ${label} Included Lines: \`${diffPayload.outputLineCount}\``,
|
|
63
|
+
`- ${label} Included Chars: \`${diffPayload.outputCharCount}\``,
|
|
64
|
+
`- ${label} Truncated: \`${diffPayload.wasTruncated ? "yes" : "no"}\``
|
|
65
|
+
].join("\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function formatChangedPaths(changedPaths) {
|
|
69
|
+
if (changedPaths.length === 0) {
|
|
70
|
+
return "_No changed files captured._";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return changedPaths
|
|
74
|
+
.map((item) => {
|
|
75
|
+
const renameSuffix = item.previousPath ? ` (from ${item.previousPath})` : "";
|
|
76
|
+
return `- \`${item.action}\` ${item.relativePath}${renameSuffix}`;
|
|
77
|
+
})
|
|
78
|
+
.join("\n");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function formatChangeList(backend, changeIds) {
|
|
82
|
+
return changeIds.map((changeId) => backend.formatChangeId(changeId)).join(", ");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function shouldWriteFormat(config, format) {
|
|
86
|
+
return Array.isArray(config.outputFormats) && config.outputFormats.includes(format);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function buildReport(config, backend, targetInfo, details, diffPayloads, reviewer, reviewerResult, tokenUsage) {
|
|
90
|
+
const lines = [
|
|
91
|
+
`# ${backend.displayName} Review Report: ${details.displayId}`,
|
|
92
|
+
"",
|
|
93
|
+
`- Repository Type: \`${backend.displayName}\``,
|
|
94
|
+
`- Target: \`${targetInfo.targetDisplay || config.target}\``,
|
|
95
|
+
`- Change ID: \`${details.displayId}\``,
|
|
96
|
+
`- Author: \`${details.author}\``,
|
|
97
|
+
`- Commit Date: \`${details.date || "unknown"}\``,
|
|
98
|
+
`- Generated At: \`${new Date().toISOString()}\``,
|
|
99
|
+
`- Reviewer: \`${reviewer.displayName}\``,
|
|
100
|
+
`- Reviewer Exit Code: \`${reviewerResult.code}\``,
|
|
101
|
+
`- Reviewer Timed Out: \`${reviewerResult.timedOut ? "yes" : "no"}\``,
|
|
102
|
+
"",
|
|
103
|
+
"## Token Usage",
|
|
104
|
+
"",
|
|
105
|
+
formatTokenUsage(tokenUsage),
|
|
106
|
+
"",
|
|
107
|
+
"## Changed Files",
|
|
108
|
+
"",
|
|
109
|
+
formatChangedPaths(details.changedPaths),
|
|
110
|
+
"",
|
|
111
|
+
"## Commit Message",
|
|
112
|
+
"",
|
|
113
|
+
details.message ? "```text\n" + details.message + "\n```" : "_Empty_",
|
|
114
|
+
"",
|
|
115
|
+
"## Review Context",
|
|
116
|
+
"",
|
|
117
|
+
"```text",
|
|
118
|
+
buildPrompt(config, backend, targetInfo, details, diffPayloads.review),
|
|
119
|
+
"```",
|
|
120
|
+
"",
|
|
121
|
+
"## Diff Handling",
|
|
122
|
+
"",
|
|
123
|
+
formatDiffHandling(diffPayloads.review, "Reviewer Input"),
|
|
124
|
+
formatDiffHandling(diffPayloads.report, "Report Diff"),
|
|
125
|
+
"",
|
|
126
|
+
"## Diff",
|
|
127
|
+
"",
|
|
128
|
+
"```diff",
|
|
129
|
+
diffPayloads.report.text.trim() || "(empty diff)",
|
|
130
|
+
"```",
|
|
131
|
+
"",
|
|
132
|
+
`## ${reviewer.responseSectionTitle}`,
|
|
133
|
+
"",
|
|
134
|
+
reviewerResult.message?.trim() ? reviewerResult.message.trim() : reviewer.emptyResponseText
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
return `${lines.join("\n")}\n`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function buildJsonReport(config, backend, targetInfo, details, diffPayloads, reviewer, reviewerResult, tokenUsage) {
|
|
141
|
+
return {
|
|
142
|
+
repositoryType: backend.displayName,
|
|
143
|
+
target: targetInfo.targetDisplay || config.target,
|
|
144
|
+
changeId: details.displayId,
|
|
145
|
+
author: details.author,
|
|
146
|
+
commitDate: details.date || "unknown",
|
|
147
|
+
generatedAt: new Date().toISOString(),
|
|
148
|
+
reviewer: {
|
|
149
|
+
name: reviewer.displayName,
|
|
150
|
+
exitCode: reviewerResult.code,
|
|
151
|
+
timedOut: Boolean(reviewerResult.timedOut)
|
|
152
|
+
},
|
|
153
|
+
tokenUsage: {
|
|
154
|
+
inputTokens: tokenUsage.inputTokens,
|
|
155
|
+
outputTokens: tokenUsage.outputTokens,
|
|
156
|
+
totalTokens: tokenUsage.totalTokens,
|
|
157
|
+
source: tokenUsage.source
|
|
158
|
+
},
|
|
159
|
+
changedFiles: details.changedPaths.map((item) => ({
|
|
160
|
+
action: item.action,
|
|
161
|
+
path: item.relativePath,
|
|
162
|
+
previousPath: item.previousPath || null
|
|
163
|
+
})),
|
|
164
|
+
commitMessage: details.message || "",
|
|
165
|
+
reviewContext: buildPrompt(config, backend, targetInfo, details, diffPayloads.review),
|
|
166
|
+
diffHandling: {
|
|
167
|
+
reviewerInput: {
|
|
168
|
+
originalLines: diffPayloads.review.originalLineCount,
|
|
169
|
+
originalChars: diffPayloads.review.originalCharCount,
|
|
170
|
+
includedLines: diffPayloads.review.outputLineCount,
|
|
171
|
+
includedChars: diffPayloads.review.outputCharCount,
|
|
172
|
+
truncated: diffPayloads.review.wasTruncated
|
|
173
|
+
},
|
|
174
|
+
reportDiff: {
|
|
175
|
+
originalLines: diffPayloads.report.originalLineCount,
|
|
176
|
+
originalChars: diffPayloads.report.originalCharCount,
|
|
177
|
+
includedLines: diffPayloads.report.outputLineCount,
|
|
178
|
+
includedChars: diffPayloads.report.outputCharCount,
|
|
179
|
+
truncated: diffPayloads.report.wasTruncated
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
diff: diffPayloads.report.text.trim(),
|
|
183
|
+
reviewerResponse: reviewerResult.message?.trim() ? reviewerResult.message.trim() : reviewer.emptyResponseText
|
|
184
|
+
};
|
|
185
|
+
}
|