sales-frontend-gemini-cli 0.4.2 → 0.4.3
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/common/helper.cjs +464 -52
- package/dist/common/helper.cjs.map +1 -1
- package/dist/common/helper.d.cts +85 -3
- package/dist/common/helper.d.ts +85 -3
- package/dist/common/helper.js +455 -53
- package/dist/common/helper.js.map +1 -1
- package/dist/common/types.d.cts +24 -1
- package/dist/common/types.d.ts +24 -1
- package/dist/pr-review/claude/claude-commander.cjs +50 -9
- package/dist/pr-review/claude/claude-commander.cjs.map +1 -1
- package/dist/pr-review/claude/claude-commander.js +50 -9
- package/dist/pr-review/claude/claude-commander.js.map +1 -1
- package/dist/pr-review/claude/installation-claude.cjs +41 -5
- package/dist/pr-review/claude/installation-claude.cjs.map +1 -1
- package/dist/pr-review/claude/installation-claude.js +41 -5
- package/dist/pr-review/claude/installation-claude.js.map +1 -1
- package/dist/pr-review/codex/codex-commander.cjs +49 -8
- package/dist/pr-review/codex/codex-commander.cjs.map +1 -1
- package/dist/pr-review/codex/codex-commander.js +49 -8
- package/dist/pr-review/codex/codex-commander.js.map +1 -1
- package/dist/pr-review/codex/installation-codex.cjs +41 -5
- package/dist/pr-review/codex/installation-codex.cjs.map +1 -1
- package/dist/pr-review/codex/installation-codex.js +41 -5
- package/dist/pr-review/codex/installation-codex.js.map +1 -1
- package/dist/pr-review/gemini/gemini-commander.cjs +75 -15
- package/dist/pr-review/gemini/gemini-commander.cjs.map +1 -1
- package/dist/pr-review/gemini/gemini-commander.js +75 -15
- package/dist/pr-review/gemini/gemini-commander.js.map +1 -1
- package/dist/pr-review/gemini/installation-gemini.cjs +41 -5
- package/dist/pr-review/gemini/installation-gemini.cjs.map +1 -1
- package/dist/pr-review/gemini/installation-gemini.js +41 -5
- package/dist/pr-review/gemini/installation-gemini.js.map +1 -1
- package/dist/pr-review/review-one-by-one.cjs +489 -95
- package/dist/pr-review/review-one-by-one.cjs.map +1 -1
- package/dist/pr-review/review-one-by-one.js +490 -96
- package/dist/pr-review/review-one-by-one.js.map +1 -1
- package/dist/pr-review/review.cjs +517 -94
- package/dist/pr-review/review.cjs.map +1 -1
- package/dist/pr-review/review.js +517 -94
- package/dist/pr-review/review.js.map +1 -1
- package/package.json +4 -7
- package/src/common/rules/coding-convention.md +393 -0
- package/src/common/rules/coding-convention.pdf +0 -0
- package/src/common/rules/naming-rule.md +347 -0
- package/src/common/rules/naming-rule.pdf +0 -0
package/dist/pr-review/review.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
2
|
import fs from 'fs';
|
|
3
|
+
import { execSync, execFileSync } from 'child_process';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import readline from 'readline';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
@@ -8,14 +8,52 @@ import { inspect } from 'util';
|
|
|
8
8
|
|
|
9
9
|
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
var traceMessages = [];
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
var GEMINI_CLI_PACKAGE_NAME = "sales-frontend-gemini-cli";
|
|
12
|
+
var cachedPackageRootPath = "";
|
|
13
|
+
function isGeminiCliPackageRoot(directory) {
|
|
14
|
+
const packageJsonPath = path.join(directory, "package.json");
|
|
15
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
20
|
+
return packageJson.name === GEMINI_CLI_PACKAGE_NAME;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function resolveGeminiCliPackageRoot(startDirectory = __dirname) {
|
|
26
|
+
if (cachedPackageRootPath) {
|
|
27
|
+
return cachedPackageRootPath;
|
|
28
|
+
}
|
|
29
|
+
let currentDirectory = startDirectory;
|
|
30
|
+
while (true) {
|
|
31
|
+
if (isGeminiCliPackageRoot(currentDirectory)) {
|
|
32
|
+
cachedPackageRootPath = currentDirectory;
|
|
33
|
+
return cachedPackageRootPath;
|
|
34
|
+
}
|
|
35
|
+
const parentDirectory = path.dirname(currentDirectory);
|
|
36
|
+
if (parentDirectory === currentDirectory) {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
currentDirectory = parentDirectory;
|
|
40
|
+
}
|
|
41
|
+
cachedPackageRootPath = path.resolve(startDirectory, "../..");
|
|
42
|
+
return cachedPackageRootPath;
|
|
43
|
+
}
|
|
44
|
+
function resolvePackageAssetPath(relativeFilePath) {
|
|
45
|
+
return path.resolve(resolveGeminiCliPackageRoot(), relativeFilePath);
|
|
46
|
+
}
|
|
47
|
+
var rulesPath = resolvePackageAssetPath("src/common/rules/review-rules.md");
|
|
48
|
+
var namingRulesPath = resolvePackageAssetPath("src/common/rules/naming-rule.md");
|
|
49
|
+
var codingConventionRulesPath = resolvePackageAssetPath("src/common/rules/coding-convention.md");
|
|
50
|
+
var reviewFormPath = resolvePackageAssetPath("src/common/form/review-form.md");
|
|
51
|
+
resolvePackageAssetPath("src/common/form/review-form-one-by-one.md");
|
|
16
52
|
var REPORT_DIR = ".review-report";
|
|
17
53
|
var tempDiffPath = "temp_diff.txt";
|
|
18
54
|
var AIServices = ["gemini", "claude", "codex"];
|
|
55
|
+
var COMMIT_FETCH_LIMIT = 20;
|
|
56
|
+
var COMMIT_SELECTION_WINDOW = 8;
|
|
19
57
|
var ignoreList = [
|
|
20
58
|
"package.json",
|
|
21
59
|
"*.yml",
|
|
@@ -39,6 +77,185 @@ function clearTraceMessages() {
|
|
|
39
77
|
function getTraceMessages() {
|
|
40
78
|
return [...traceMessages];
|
|
41
79
|
}
|
|
80
|
+
var ANSI = {
|
|
81
|
+
bold: "\x1B[1m",
|
|
82
|
+
cyan: "\x1B[36m",
|
|
83
|
+
dim: "\x1B[2m",
|
|
84
|
+
green: "\x1B[32m",
|
|
85
|
+
reset: "\x1B[0m",
|
|
86
|
+
yellow: "\x1B[33m"
|
|
87
|
+
};
|
|
88
|
+
var ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
89
|
+
var COMBINING_MARK_PATTERN = /\p{Mark}/u;
|
|
90
|
+
var GRAPHEME_SEGMENTER = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter("ko", { granularity: "grapheme" }) : null;
|
|
91
|
+
function getGitDiffPathspecs() {
|
|
92
|
+
return {
|
|
93
|
+
excludePatterns: ignoreList.map((item) => `:(exclude)${item}`),
|
|
94
|
+
includePatterns: ["*.ts", "*.tsx", "*.js", "*.jsx"]
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function segmentGraphemes(value) {
|
|
98
|
+
if (!GRAPHEME_SEGMENTER) {
|
|
99
|
+
return [...value];
|
|
100
|
+
}
|
|
101
|
+
return [...GRAPHEME_SEGMENTER.segment(value)].map(({ segment }) => segment);
|
|
102
|
+
}
|
|
103
|
+
function isWideCodePoint(codePoint) {
|
|
104
|
+
return codePoint >= 4352 && (codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 12871 && codePoint !== 12351 || codePoint >= 12880 && codePoint <= 19903 || codePoint >= 19968 && codePoint <= 42182 || codePoint >= 43360 && codePoint <= 43388 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65131 || codePoint >= 65281 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 127488 && codePoint <= 127569 || codePoint >= 131072 && codePoint <= 262141);
|
|
105
|
+
}
|
|
106
|
+
function isEmojiCodePoint(codePoint) {
|
|
107
|
+
return codePoint >= 127462 && codePoint <= 127487 || codePoint >= 127744 && codePoint <= 129791 || codePoint >= 9728 && codePoint <= 10175;
|
|
108
|
+
}
|
|
109
|
+
function getGraphemeWidth(grapheme) {
|
|
110
|
+
let width = 0;
|
|
111
|
+
for (const character of grapheme) {
|
|
112
|
+
const codePoint = character.codePointAt(0);
|
|
113
|
+
if (!codePoint || COMBINING_MARK_PATTERN.test(character) || codePoint === 8205) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (codePoint >= 65024 && codePoint <= 65039 || codePoint >= 917760 && codePoint <= 917999) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (isWideCodePoint(codePoint) || isEmojiCodePoint(codePoint)) {
|
|
120
|
+
width = Math.max(width, 2);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
width = Math.max(width, 1);
|
|
124
|
+
}
|
|
125
|
+
return width;
|
|
126
|
+
}
|
|
127
|
+
function tokenizePlainText(value) {
|
|
128
|
+
return segmentGraphemes(value).map((segment) => ({
|
|
129
|
+
value: segment,
|
|
130
|
+
visibleWidth: getGraphemeWidth(segment)
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
function tokenizeVisibleText(value) {
|
|
134
|
+
const tokens = [];
|
|
135
|
+
let lastIndex = 0;
|
|
136
|
+
for (const match of value.matchAll(ANSI_PATTERN)) {
|
|
137
|
+
const index = match.index ?? 0;
|
|
138
|
+
if (index > lastIndex) {
|
|
139
|
+
tokens.push(...tokenizePlainText(value.slice(lastIndex, index)));
|
|
140
|
+
}
|
|
141
|
+
tokens.push({
|
|
142
|
+
value: match[0],
|
|
143
|
+
visibleWidth: 0
|
|
144
|
+
});
|
|
145
|
+
lastIndex = index + match[0].length;
|
|
146
|
+
}
|
|
147
|
+
if (lastIndex < value.length) {
|
|
148
|
+
tokens.push(...tokenizePlainText(value.slice(lastIndex)));
|
|
149
|
+
}
|
|
150
|
+
return tokens;
|
|
151
|
+
}
|
|
152
|
+
function truncateLineForTerminal(value, maxWidth) {
|
|
153
|
+
if (maxWidth <= 0) {
|
|
154
|
+
return "";
|
|
155
|
+
}
|
|
156
|
+
const tokens = tokenizeVisibleText(value);
|
|
157
|
+
const totalWidth = tokens.reduce((sum, token) => sum + token.visibleWidth, 0);
|
|
158
|
+
if (totalWidth <= maxWidth) {
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
const ellipsis = "...";
|
|
162
|
+
const ellipsisWidth = 3;
|
|
163
|
+
const targetWidth = Math.max(0, maxWidth - ellipsisWidth);
|
|
164
|
+
let usedWidth = 0;
|
|
165
|
+
let result = "";
|
|
166
|
+
for (const token of tokens) {
|
|
167
|
+
if (token.visibleWidth === 0) {
|
|
168
|
+
result += token.value;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (usedWidth + token.visibleWidth > targetWidth) {
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
result += token.value;
|
|
175
|
+
usedWidth += token.visibleWidth;
|
|
176
|
+
}
|
|
177
|
+
return `${result}${ellipsis}${ANSI.reset}`;
|
|
178
|
+
}
|
|
179
|
+
function fitLinesToTerminal(lines) {
|
|
180
|
+
const maxWidth = Math.max(20, (process.stdout.columns || 120) - 1);
|
|
181
|
+
return lines.map((line) => truncateLineForTerminal(line, maxWidth));
|
|
182
|
+
}
|
|
183
|
+
function runGitCommand(args4, options = {}) {
|
|
184
|
+
const { allowFailure = false, trimOutput = true } = options;
|
|
185
|
+
try {
|
|
186
|
+
const output = execFileSync("git", args4, {
|
|
187
|
+
encoding: "utf8",
|
|
188
|
+
maxBuffer: 1024 * 1024 * 20,
|
|
189
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
190
|
+
});
|
|
191
|
+
return trimOutput ? output.trim() : output;
|
|
192
|
+
} catch (error) {
|
|
193
|
+
helperTrace("git-command:failed", `${args4.join(" ")} | ${getErrorSummary(error)}`);
|
|
194
|
+
if (allowFailure) {
|
|
195
|
+
return "";
|
|
196
|
+
}
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async function executeShellCommandWithProgress(command, options = {}) {
|
|
201
|
+
const { progressIntervalMs = 1e4, progressMessage = "\u23F3 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...", streamOutput = false } = options;
|
|
202
|
+
const { spawn } = await import('child_process');
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
let stdout = "";
|
|
205
|
+
let stderr = "";
|
|
206
|
+
const startedAt = Date.now();
|
|
207
|
+
console.log(progressMessage);
|
|
208
|
+
const child = spawn("/bin/zsh", ["-lc", command], {
|
|
209
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
210
|
+
});
|
|
211
|
+
const progressTimer = setInterval(() => {
|
|
212
|
+
const elapsedSeconds = Math.max(1, Math.floor((Date.now() - startedAt) / 1e3));
|
|
213
|
+
console.log(`${progressMessage} (${elapsedSeconds}s \uACBD\uACFC)`);
|
|
214
|
+
}, progressIntervalMs);
|
|
215
|
+
child.stdout.on("data", (chunk) => {
|
|
216
|
+
const text = chunk.toString();
|
|
217
|
+
stdout += text;
|
|
218
|
+
if (streamOutput) {
|
|
219
|
+
process.stdout.write(text);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
child.stderr.on("data", (chunk) => {
|
|
223
|
+
const text = chunk.toString();
|
|
224
|
+
stderr += text;
|
|
225
|
+
if (streamOutput) {
|
|
226
|
+
process.stderr.write(text);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
child.on("error", (error) => {
|
|
230
|
+
clearInterval(progressTimer);
|
|
231
|
+
reject(error);
|
|
232
|
+
});
|
|
233
|
+
child.on("close", (code, signal) => {
|
|
234
|
+
clearInterval(progressTimer);
|
|
235
|
+
if (code === 0) {
|
|
236
|
+
resolve({
|
|
237
|
+
stderr,
|
|
238
|
+
stdout
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const exitSummary = signal ? `signal=${signal}` : `code=${String(code ?? "unknown")}`;
|
|
243
|
+
reject(new Error(`\uC258 \uBA85\uB839 \uC2E4\uD589 \uC2E4\uD328 (${exitSummary})${stderr.trim() ? `
|
|
244
|
+
${stderr.trim()}` : ""}`));
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function formatReviewTargetFiles(files, visibleCount = 5) {
|
|
249
|
+
if (files.length === 0) {
|
|
250
|
+
return "(\uC5C6\uC74C)";
|
|
251
|
+
}
|
|
252
|
+
const visibleFiles = files.slice(0, visibleCount);
|
|
253
|
+
const hiddenCount = Math.max(0, files.length - visibleFiles.length);
|
|
254
|
+
if (hiddenCount === 0) {
|
|
255
|
+
return visibleFiles.join(", ");
|
|
256
|
+
}
|
|
257
|
+
return `${visibleFiles.join(", ")} \uC678 ${hiddenCount}\uAC1C`;
|
|
258
|
+
}
|
|
42
259
|
function createTraceLogger(scope, args4 = process.argv.slice(2)) {
|
|
43
260
|
const enabled = isTestMode(args4);
|
|
44
261
|
return (step, detail) => {
|
|
@@ -250,13 +467,67 @@ ${JSON.stringify(AIServices, null, 2)}
|
|
|
250
467
|
}
|
|
251
468
|
);
|
|
252
469
|
}
|
|
253
|
-
function
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
470
|
+
function truncateCommitSubject(subject) {
|
|
471
|
+
if (subject.length <= 72) {
|
|
472
|
+
return subject;
|
|
473
|
+
}
|
|
474
|
+
return `${subject.slice(0, 69)}...`;
|
|
475
|
+
}
|
|
476
|
+
function getRecentCommitOptions() {
|
|
477
|
+
const output = runGitCommand(
|
|
478
|
+
["log", `-${COMMIT_FETCH_LIMIT}`, "--date=relative", "--pretty=format:%h%x09%an%x09%ar%x09%s"],
|
|
479
|
+
{ allowFailure: true }
|
|
480
|
+
);
|
|
481
|
+
if (!output) {
|
|
482
|
+
return [];
|
|
483
|
+
}
|
|
484
|
+
return output.split("\n").map((line) => {
|
|
485
|
+
const [hash = "", author = "", relativeDate = "", ...subjectParts] = line.split(" ");
|
|
486
|
+
const subject = subjectParts.join(" ").trim();
|
|
487
|
+
return {
|
|
488
|
+
author,
|
|
489
|
+
description: `${author} | ${relativeDate}`,
|
|
490
|
+
hash,
|
|
491
|
+
label: `${hash} | ${truncateCommitSubject(subject)}`,
|
|
492
|
+
relativeDate,
|
|
493
|
+
subject
|
|
494
|
+
};
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
function buildSelectedCommitSummary(commits) {
|
|
498
|
+
return commits.map((commit) => `- ${commit.hash} | ${commit.subject} | ${commit.author} | ${commit.relativeDate}`).join("\n");
|
|
499
|
+
}
|
|
500
|
+
function getReviewPathspecArgs() {
|
|
501
|
+
const { includePatterns, excludePatterns } = getGitDiffPathspecs();
|
|
502
|
+
return [...includePatterns, ...excludePatterns];
|
|
503
|
+
}
|
|
504
|
+
function buildSelectedCommitDiff(commits) {
|
|
505
|
+
const reviewPathspecArgs = getReviewPathspecArgs();
|
|
506
|
+
const sections = commits.map((commit) => {
|
|
507
|
+
const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", ...reviewPathspecArgs], {
|
|
508
|
+
allowFailure: true,
|
|
509
|
+
trimOutput: false
|
|
510
|
+
}).trim();
|
|
511
|
+
if (!diff) {
|
|
512
|
+
return "";
|
|
513
|
+
}
|
|
514
|
+
return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
|
|
515
|
+
}).filter(Boolean).join("\n\n");
|
|
516
|
+
if (!sections) {
|
|
517
|
+
return "";
|
|
518
|
+
}
|
|
519
|
+
return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", "# \uB9AC\uBDF0 \uB300\uC0C1 diff", sections].join("\n");
|
|
520
|
+
}
|
|
521
|
+
function getSelectedCommitFiles(commits) {
|
|
522
|
+
const reviewPathspecArgs = getReviewPathspecArgs();
|
|
523
|
+
const files = /* @__PURE__ */ new Set();
|
|
524
|
+
commits.forEach((commit) => {
|
|
525
|
+
const output = runGitCommand(["show", "--pretty=format:", "--name-only", commit.hash, "--", ...reviewPathspecArgs], {
|
|
526
|
+
allowFailure: true
|
|
527
|
+
});
|
|
528
|
+
output.split("\n").map((line) => line.trim()).filter(Boolean).forEach((filePath) => files.add(filePath));
|
|
529
|
+
});
|
|
530
|
+
return [...files];
|
|
260
531
|
}
|
|
261
532
|
function openReport(reportPath) {
|
|
262
533
|
const resolvedPath = path.resolve(reportPath);
|
|
@@ -307,59 +578,166 @@ function openReport(reportPath) {
|
|
|
307
578
|
helperTrace("open-report:unsupported-platform", platform);
|
|
308
579
|
console.error(`\u26A0\uFE0F \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD50C\uB7AB\uD3FC\uC785\uB2C8\uB2E4: ${platform}`);
|
|
309
580
|
}
|
|
310
|
-
function
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
helperTrace(
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
581
|
+
function ensureInteractiveSelectionAvailable(scope, message) {
|
|
582
|
+
if (process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
helperTrace(`${scope}:tty-missing`);
|
|
586
|
+
exitWithError(message, {
|
|
587
|
+
scope: `helper:${scope}`
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
function renderSelectionBlock(lines, previousLineCount) {
|
|
591
|
+
if (previousLineCount > 0) {
|
|
592
|
+
readline.moveCursor(process.stdout, 0, -previousLineCount);
|
|
593
|
+
readline.clearScreenDown(process.stdout);
|
|
594
|
+
}
|
|
595
|
+
const fittedLines = fitLinesToTerminal(lines);
|
|
596
|
+
process.stdout.write(`${fittedLines.join("\n")}
|
|
597
|
+
`);
|
|
598
|
+
return fittedLines.length;
|
|
599
|
+
}
|
|
600
|
+
function getSelectionWindowRange(optionCount, selectedIndex, windowSize) {
|
|
601
|
+
if (optionCount <= windowSize) {
|
|
602
|
+
return {
|
|
603
|
+
end: optionCount,
|
|
604
|
+
start: 0
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
const halfWindow = Math.floor(windowSize / 2);
|
|
608
|
+
const maxStart = optionCount - windowSize;
|
|
609
|
+
const start = Math.max(0, Math.min(selectedIndex - halfWindow, maxStart));
|
|
610
|
+
return {
|
|
611
|
+
end: Math.min(optionCount, start + windowSize),
|
|
612
|
+
start
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
function buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize) {
|
|
616
|
+
const { start, end } = getSelectionWindowRange(options.length, selectedIndex, windowSize);
|
|
617
|
+
const lines = [
|
|
618
|
+
`${ANSI.bold}${question}${ANSI.reset}`,
|
|
619
|
+
`${ANSI.dim}\u2191\u2193 \uC774\uB3D9 | Space \uC120\uD0DD/\uD574\uC81C | Enter \uC644\uB8CC | Esc \uCDE8\uC18C${ANSI.reset}`,
|
|
620
|
+
`${ANSI.dim}\uC120\uD0DD\uB428: ${toggled.size}\uAC1C / \uC804\uCCB4: ${options.length}\uAC1C${ANSI.reset}`
|
|
621
|
+
];
|
|
622
|
+
for (let index = start; index < end; index += 1) {
|
|
623
|
+
const option = options[index];
|
|
624
|
+
if (!option) {
|
|
625
|
+
continue;
|
|
324
626
|
}
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
627
|
+
const cursor = index === selectedIndex ? `${ANSI.cyan}>${ANSI.reset}` : " ";
|
|
628
|
+
const checked = toggled.has(index) ? `${ANSI.green}\u2611${ANSI.reset}` : "\u2610";
|
|
629
|
+
const description = option.description ? ` ${ANSI.dim}${option.description}${ANSI.reset}` : "";
|
|
630
|
+
lines.push(`${cursor} ${checked} ${option.label}${description}`);
|
|
631
|
+
}
|
|
632
|
+
if (options.length > windowSize) {
|
|
633
|
+
lines.push(`${ANSI.dim}\uD45C\uC2DC \uBC94\uC704: ${start + 1}-${end} / ${options.length}${ANSI.reset}`);
|
|
634
|
+
}
|
|
635
|
+
return lines;
|
|
636
|
+
}
|
|
637
|
+
async function showMultiSelect(question, options, windowSize = COMMIT_SELECTION_WINDOW) {
|
|
638
|
+
ensureInteractiveSelectionAvailable("showMultiSelect", "\u274C \uCEE4\uBC0B \uC120\uD0DD \uBAA8\uB2EC\uC740 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
639
|
+
let selectedIndex = 0;
|
|
640
|
+
let renderedLineCount = 0;
|
|
641
|
+
const toggled = /* @__PURE__ */ new Set();
|
|
642
|
+
const rl = readline.createInterface({
|
|
643
|
+
input: process.stdin,
|
|
644
|
+
output: process.stdout,
|
|
645
|
+
terminal: true
|
|
646
|
+
});
|
|
647
|
+
process.stdout.write("\x1B[?25l");
|
|
648
|
+
const cleanup = () => {
|
|
649
|
+
if (renderedLineCount > 0) {
|
|
650
|
+
readline.moveCursor(process.stdout, 0, -renderedLineCount);
|
|
651
|
+
readline.clearScreenDown(process.stdout);
|
|
652
|
+
renderedLineCount = 0;
|
|
332
653
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
654
|
+
process.stdin.removeListener("data", onData);
|
|
655
|
+
process.stdin.setRawMode(false);
|
|
656
|
+
process.stdin.pause();
|
|
657
|
+
rl.close();
|
|
658
|
+
process.stdout.write("\x1B[?25h");
|
|
659
|
+
};
|
|
660
|
+
const render = () => {
|
|
661
|
+
const lines = buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize);
|
|
662
|
+
renderedLineCount = renderSelectionBlock(lines, renderedLineCount);
|
|
663
|
+
};
|
|
664
|
+
const confirmSelection = (resolve) => {
|
|
665
|
+
const values = [...toggled].sort((left, right) => left - right).map((index) => options[index]?.value).filter((value) => value !== void 0);
|
|
666
|
+
cleanup();
|
|
667
|
+
resolve(values);
|
|
668
|
+
};
|
|
669
|
+
const cancelSelection = (resolve) => {
|
|
670
|
+
cleanup();
|
|
671
|
+
resolve([]);
|
|
672
|
+
};
|
|
673
|
+
let onData = (_data) => {
|
|
674
|
+
};
|
|
675
|
+
render();
|
|
676
|
+
return new Promise((resolve) => {
|
|
677
|
+
onData = (data) => {
|
|
678
|
+
const key = data.toString();
|
|
679
|
+
if (key === "") {
|
|
680
|
+
cleanup();
|
|
681
|
+
process.exit(0);
|
|
346
682
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
683
|
+
if (key === "\x1B") {
|
|
684
|
+
cancelSelection(resolve);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (key === "\x1B[A") {
|
|
688
|
+
selectedIndex = (selectedIndex - 1 + options.length) % options.length;
|
|
689
|
+
render();
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (key === "\x1B[B") {
|
|
693
|
+
selectedIndex = (selectedIndex + 1) % options.length;
|
|
694
|
+
render();
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (key === " ") {
|
|
698
|
+
if (toggled.has(selectedIndex)) {
|
|
699
|
+
toggled.delete(selectedIndex);
|
|
700
|
+
} else {
|
|
701
|
+
toggled.add(selectedIndex);
|
|
702
|
+
}
|
|
703
|
+
render();
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (key === "\r" || key === "\n") {
|
|
707
|
+
confirmSelection(resolve);
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
process.stdin.setRawMode(true);
|
|
711
|
+
process.stdin.resume();
|
|
712
|
+
process.stdin.on("data", onData);
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
async function selectReviewCommits() {
|
|
716
|
+
const commits = getRecentCommitOptions();
|
|
717
|
+
if (commits.length === 0) {
|
|
718
|
+
console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uCD5C\uADFC \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
719
|
+
return [];
|
|
350
720
|
}
|
|
351
|
-
|
|
352
|
-
|
|
721
|
+
return showMultiSelect(
|
|
722
|
+
"\uB9AC\uBDF0\uD560 \uCEE4\uBC0B\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.",
|
|
723
|
+
commits.map((commit) => ({
|
|
724
|
+
description: commit.description,
|
|
725
|
+
label: commit.label,
|
|
726
|
+
value: commit
|
|
727
|
+
})),
|
|
728
|
+
COMMIT_SELECTION_WINDOW
|
|
729
|
+
);
|
|
353
730
|
}
|
|
354
731
|
async function showSelectionAIService() {
|
|
355
732
|
const selectedServiceFromArgs = parseServiceFromArgs();
|
|
356
733
|
if (selectedServiceFromArgs) {
|
|
357
734
|
helperTrace("show-selection:from-args", selectedServiceFromArgs);
|
|
358
735
|
console.log(`
|
|
359
|
-
\u2705
|
|
736
|
+
\u2705 ${ANSI.green}${selectedServiceFromArgs}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
|
|
360
737
|
`);
|
|
361
738
|
return selectedServiceFromArgs;
|
|
362
739
|
}
|
|
740
|
+
ensureInteractiveSelectionAvailable("showSelectionAIService", "\u274C AI \uC11C\uBE44\uC2A4 \uC120\uD0DD UI\uB294 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
363
741
|
helperTrace("show-selection:interactive:start");
|
|
364
742
|
let selectedIndex = 0;
|
|
365
743
|
const rl = readline.createInterface({
|
|
@@ -377,11 +755,12 @@ async function showSelectionAIService() {
|
|
|
377
755
|
helperTrace("show-selection:interactive:render", AIServices[selectedIndex] || "unknown");
|
|
378
756
|
readline.clearScreenDown(process.stdout);
|
|
379
757
|
process.stdout.write(
|
|
380
|
-
|
|
758
|
+
`\u{1F916} AI \uC11C\uBE44\uC2A4\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694 (${ANSI.yellow}\u2191\u2193 \uBC29\uD5A5\uD0A4${ANSI.reset} \uC774\uB3D9, ${ANSI.yellow}Enter${ANSI.reset} \uC120\uD0DD):
|
|
759
|
+
`
|
|
381
760
|
);
|
|
382
761
|
AIServices.forEach((service, index) => {
|
|
383
762
|
if (index === selectedIndex) {
|
|
384
|
-
process.stdout.write(`
|
|
763
|
+
process.stdout.write(` ${ANSI.cyan}>${ANSI.reset} ${ANSI.cyan}\u25C9${ANSI.reset} ${ANSI.bold}${service}${ANSI.reset}
|
|
385
764
|
`);
|
|
386
765
|
} else {
|
|
387
766
|
process.stdout.write(` \u25EF ${service}
|
|
@@ -411,7 +790,7 @@ async function showSelectionAIService() {
|
|
|
411
790
|
rl.close();
|
|
412
791
|
process.stdout.write("\x1B[?25h");
|
|
413
792
|
console.log(`
|
|
414
|
-
\u2705
|
|
793
|
+
\u2705 ${ANSI.green}${AIServices[selectedIndex]}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
|
|
415
794
|
`);
|
|
416
795
|
const result = AIServices[selectedIndex];
|
|
417
796
|
if (result) {
|
|
@@ -438,6 +817,11 @@ function getArgValue(flag) {
|
|
|
438
817
|
}
|
|
439
818
|
return args[index + 1];
|
|
440
819
|
}
|
|
820
|
+
function printNotice(message) {
|
|
821
|
+
if (args.includes("--test")) {
|
|
822
|
+
console.warn(message);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
441
825
|
function toUnique(values) {
|
|
442
826
|
const seen = /* @__PURE__ */ new Set();
|
|
443
827
|
return values.filter((value) => {
|
|
@@ -461,11 +845,11 @@ function resolveReasoningEffort() {
|
|
|
461
845
|
const normalized = normalizeEffort(customReasoningEffort);
|
|
462
846
|
trace("reasoning:custom", `${customReasoningEffort} -> ${normalized}`);
|
|
463
847
|
if (customReasoningEffort === "minimal") {
|
|
464
|
-
|
|
848
|
+
printNotice("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
|
|
465
849
|
}
|
|
466
850
|
return normalized;
|
|
467
851
|
}
|
|
468
|
-
|
|
852
|
+
printNotice(
|
|
469
853
|
`\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS.join(
|
|
470
854
|
", "
|
|
471
855
|
)}`
|
|
@@ -508,7 +892,7 @@ function buildClaudeExecCommand(options) {
|
|
|
508
892
|
const modelOption = model ? `--model ${shellQuote(model)}` : "";
|
|
509
893
|
const fallbackOption = model && fallbackModel ? `--fallback-model ${shellQuote(fallbackModel)}` : "";
|
|
510
894
|
const effortOption = `--effort ${shellQuote(effort)}`;
|
|
511
|
-
const appendedPromptFiles = systemPromptFiles.map((
|
|
895
|
+
const appendedPromptFiles = systemPromptFiles.map((path3) => `--append-system-prompt-file ${shellQuote(path3)}`).join(" ");
|
|
512
896
|
return `cat ${shellQuote(tempDiffPath2)} | claude ${[
|
|
513
897
|
modelOption,
|
|
514
898
|
fallbackOption,
|
|
@@ -541,13 +925,13 @@ var createClaudeCommand = (tempDiffPath2, reviewFormPath2) => {
|
|
|
541
925
|
trace("model:candidates", modelCandidates.join(", "));
|
|
542
926
|
trace("command:candidates:count", String(modelCandidates.length + 1));
|
|
543
927
|
if (customModel) {
|
|
544
|
-
|
|
928
|
+
printNotice(
|
|
545
929
|
`\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
|
|
546
930
|
" -> "
|
|
547
931
|
)}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
|
|
548
932
|
);
|
|
549
933
|
} else {
|
|
550
|
-
|
|
934
|
+
printNotice(
|
|
551
935
|
`\u26A0\uFE0F \uBAA8\uB378\uC774 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. alias(${aliasFallbacks.join(" -> ")})\uB97C \uC21C\uCC28 \uC2DC\uB3C4\uD558\uACE0 \uB9C8\uC9C0\uB9C9\uC5D0 \uAE30\uBCF8 \uBAA8\uB378\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
|
|
552
936
|
);
|
|
553
937
|
}
|
|
@@ -627,6 +1011,11 @@ function getArgValue2(flag) {
|
|
|
627
1011
|
}
|
|
628
1012
|
return args2[index + 1];
|
|
629
1013
|
}
|
|
1014
|
+
function printNotice2(message) {
|
|
1015
|
+
if (args2.includes("--test")) {
|
|
1016
|
+
console.warn(message);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
630
1019
|
function resolveReasoningEffort2() {
|
|
631
1020
|
const customReasoningEffort = getArgValue2("--reasoning-effort");
|
|
632
1021
|
if (customReasoningEffort) {
|
|
@@ -634,7 +1023,7 @@ function resolveReasoningEffort2() {
|
|
|
634
1023
|
trace3("reasoning:custom", customReasoningEffort);
|
|
635
1024
|
return customReasoningEffort;
|
|
636
1025
|
}
|
|
637
|
-
|
|
1026
|
+
printNotice2(
|
|
638
1027
|
`\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS2.join(
|
|
639
1028
|
", "
|
|
640
1029
|
)}`
|
|
@@ -678,14 +1067,14 @@ ${reviewFormLine || "- (\uC5C6\uC74C)"}
|
|
|
678
1067
|
trace3("prompt:prepared", `length=${prompt.length}`);
|
|
679
1068
|
let command = "";
|
|
680
1069
|
if (customModel) {
|
|
681
|
-
|
|
1070
|
+
printNotice2("\u26A0\uFE0F \uC9C0\uC815\uD55C \uBAA8\uB378\uC774 \uC5C6\uB294 \uACBD\uC6B0, \uC5D0\uB7EC\uAC00 \uBC1C\uC0DD\uD558\uB2C8 \uC8FC\uC758\uD558\uC138\uC694.");
|
|
682
1071
|
trace3("model:custom", customModel);
|
|
683
1072
|
command = buildCodexExecCommand(prompt, reasoningEffort, customModel);
|
|
684
1073
|
} else {
|
|
685
1074
|
const preferredModelAlias = "gpt-5";
|
|
686
1075
|
const aliasCommand = buildCodexExecCommand(prompt, reasoningEffort, preferredModelAlias);
|
|
687
1076
|
const fallbackCommand = buildCodexExecCommand(prompt, reasoningEffort);
|
|
688
|
-
|
|
1077
|
+
printNotice2(
|
|
689
1078
|
`\u26A0\uFE0F \uBAA8\uB378\uC774 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. alias(${preferredModelAlias})\uB97C \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 \uACC4\uC815 \uAE30\uBCF8 \uBAA8\uB378\uB85C \uC790\uB3D9 \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
|
|
690
1079
|
);
|
|
691
1080
|
trace3("model:alias-first", preferredModelAlias);
|
|
@@ -746,6 +1135,11 @@ function getArgValue3(flag) {
|
|
|
746
1135
|
}
|
|
747
1136
|
return args3[index + 1];
|
|
748
1137
|
}
|
|
1138
|
+
function printNotice3(message) {
|
|
1139
|
+
if (args3.includes("--test")) {
|
|
1140
|
+
console.warn(message);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
749
1143
|
function toUnique2(values) {
|
|
750
1144
|
const seen = /* @__PURE__ */ new Set();
|
|
751
1145
|
return values.filter((value) => {
|
|
@@ -763,7 +1157,7 @@ function resolveReasoningEffort3() {
|
|
|
763
1157
|
trace5("reasoning:custom", customReasoningEffort);
|
|
764
1158
|
return customReasoningEffort;
|
|
765
1159
|
}
|
|
766
|
-
|
|
1160
|
+
printNotice3(
|
|
767
1161
|
`\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS3.join(
|
|
768
1162
|
", "
|
|
769
1163
|
)}`
|
|
@@ -825,38 +1219,57 @@ function buildGeminiExecCommand(prompt, model) {
|
|
|
825
1219
|
const modelOption = model ? `--model ${shellQuote3(model)}` : "";
|
|
826
1220
|
return `gemini ${[modelOption, "-p", shellQuote3(prompt)].filter(Boolean).join(" ")}`;
|
|
827
1221
|
}
|
|
1222
|
+
function toGeminiFileReference(filePath) {
|
|
1223
|
+
return `@${filePath}`;
|
|
1224
|
+
}
|
|
1225
|
+
function buildGeminiFileReferenceSection(files) {
|
|
1226
|
+
const existingFiles = files.filter((file) => fs.existsSync(file.path));
|
|
1227
|
+
return {
|
|
1228
|
+
count: existingFiles.length,
|
|
1229
|
+
lines: existingFiles.map((file) => `- ${file.display}: ${toGeminiFileReference(file.path)}`)
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
828
1232
|
var createGeminiCommand = (tempDiffPath2, reviewFormPath2) => {
|
|
829
1233
|
trace5("createGeminiCommand:start", `tempDiffPath=${tempDiffPath2}, reviewFormPath=${reviewFormPath2}`);
|
|
830
1234
|
const customModel = getArgValue3("--model");
|
|
831
1235
|
const reasoningEffort = resolveReasoningEffort3();
|
|
832
1236
|
const primaryAlias = resolvePrimaryAlias2(reasoningEffort);
|
|
833
1237
|
const aliasFallbacks = toUnique2(getAliasFallbacks2(primaryAlias));
|
|
1238
|
+
const resolvedTempDiffPath = path.resolve(tempDiffPath2);
|
|
1239
|
+
const resolvedReviewFormPath = path.resolve(reviewFormPath2);
|
|
834
1240
|
const rules = [
|
|
835
1241
|
{ path: rulesPath, display: "\uB8F0\uC14B" },
|
|
836
1242
|
{ path: namingRulesPath, display: "\uB124\uC774\uBC0D \uADDC\uCE59" },
|
|
837
1243
|
{ path: codingConventionRulesPath, display: "\uCF54\uB529 \uCEE8\uBCA4\uC158" }
|
|
838
1244
|
];
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const
|
|
843
|
-
trace5("reviewForm:status",
|
|
1245
|
+
const ruleSection = buildGeminiFileReferenceSection(rules);
|
|
1246
|
+
trace5("rules:loaded", `count=${ruleSection.count}`);
|
|
1247
|
+
const reviewFormExists = fs.existsSync(resolvedReviewFormPath);
|
|
1248
|
+
const reviewFormLine = reviewFormExists ? `- \uB9AC\uBDF0 \uC591\uC2DD: ${toGeminiFileReference(resolvedReviewFormPath)}` : "- \uB9AC\uBDF0 \uC591\uC2DD: (\uC5C6\uC74C)";
|
|
1249
|
+
trace5("reviewForm:status", reviewFormExists ? "exists" : "missing");
|
|
844
1250
|
const reasoningInstruction = getReasoningInstruction(reasoningEffort);
|
|
845
|
-
const prompt = `\
|
|
846
|
-
\
|
|
1251
|
+
const prompt = `\uC544\uB798 \uD30C\uC77C\uB4E4\uC744 \uC21C\uC11C\uB300\uB85C \uC77D\uACE0 \uCF54\uB4DC \uB9AC\uBDF0\uB97C \uC9C4\uD589\uD574\uC918.
|
|
1252
|
+
\uADDC\uCE59 \uD30C\uC77C:
|
|
1253
|
+
${ruleSection.lines.join("\n") || "- (\uC5C6\uC74C)"}
|
|
1254
|
+
\uB9AC\uBDF0 \uC591\uC2DD \uD30C\uC77C:
|
|
1255
|
+
${reviewFormLine}
|
|
1256
|
+
\uB9AC\uBDF0 \uB300\uC0C1 diff \uD30C\uC77C:
|
|
1257
|
+
- \uB9AC\uBDF0 \uB300\uC0C1 diff: ${toGeminiFileReference(resolvedTempDiffPath)}
|
|
1258
|
+
|
|
1259
|
+
\uBC18\uB4DC\uC2DC \uB9AC\uBDF0 \uC591\uC2DD\uC5D0 \uB9DE\uCDB0 \uC791\uC131\uD574\uC918.
|
|
847
1260
|
\uCD94\uB860 \uAC15\uB3C4 \uC9C0\uCE68: ${reasoningInstruction}`;
|
|
848
1261
|
trace5("prompt:prepared", `length=${prompt.length}`);
|
|
849
1262
|
const modelCandidates = toUnique2(customModel ? [customModel, ...aliasFallbacks] : aliasFallbacks);
|
|
850
1263
|
trace5("model:candidates", modelCandidates.join(", "));
|
|
851
1264
|
trace5("command:candidates:count", String(modelCandidates.length + 1));
|
|
852
1265
|
if (customModel) {
|
|
853
|
-
|
|
1266
|
+
printNotice3(
|
|
854
1267
|
`\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
|
|
855
1268
|
" -> "
|
|
856
1269
|
)}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
|
|
857
1270
|
);
|
|
858
1271
|
} else {
|
|
859
|
-
|
|
1272
|
+
printNotice3(
|
|
860
1273
|
`\u26A0\uFE0F \uBAA8\uB378\uC774 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. alias(${aliasFallbacks.join(" -> ")})\uB97C \uC21C\uCC28 \uC2DC\uB3C4\uD558\uACE0 \uB9C8\uC9C0\uB9C9\uC5D0 \uAE30\uBCF8 \uBAA8\uB378\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
|
|
861
1274
|
);
|
|
862
1275
|
}
|
|
@@ -915,6 +1328,8 @@ async function main() {
|
|
|
915
1328
|
let savedDiffPath = "";
|
|
916
1329
|
let savedReportPath = "";
|
|
917
1330
|
let service = "";
|
|
1331
|
+
let selectedCommitSummary = "";
|
|
1332
|
+
let reviewTargetFiles = [];
|
|
918
1333
|
try {
|
|
919
1334
|
trace7("service-selection:start");
|
|
920
1335
|
service = await showSelectionAIService();
|
|
@@ -938,30 +1353,34 @@ async function main() {
|
|
|
938
1353
|
}
|
|
939
1354
|
trace7("review-flow:start");
|
|
940
1355
|
console.log("\u{1F680} AI Code Review\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4...");
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
trace7("
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
let diff = "";
|
|
950
|
-
const { includeParams, excludeParams } = getGitDiffFilter();
|
|
951
|
-
trace7("diff-filter:loaded", `include=${includeParams} | exclude=${excludeParams}`);
|
|
952
|
-
try {
|
|
953
|
-
trace7("git-diff:run");
|
|
954
|
-
diff = execSync(`git diff ${diffArgs} -- ${includeParams} ${excludeParams}`).toString();
|
|
955
|
-
trace7("git-diff:done", `length=${diff.length}`);
|
|
956
|
-
} catch (error) {
|
|
957
|
-
trace7("git-diff:error", getErrorSummary(error));
|
|
1356
|
+
trace7("commit-selection:start");
|
|
1357
|
+
const selectedCommits = await selectReviewCommits();
|
|
1358
|
+
trace7("commit-selection:done", `count=${selectedCommits.length}`);
|
|
1359
|
+
if (selectedCommits.length === 0) {
|
|
1360
|
+
trace7("commit-selection:empty");
|
|
1361
|
+
console.log("\u2139\uFE0F \uC120\uD0DD\uB41C \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
1362
|
+
deleteTempDiff();
|
|
1363
|
+
process.exit(0);
|
|
958
1364
|
}
|
|
1365
|
+
selectedCommitSummary = buildSelectedCommitSummary(selectedCommits);
|
|
1366
|
+
trace7("commit-summary:prepared", selectedCommitSummary);
|
|
1367
|
+
reviewTargetFiles = getSelectedCommitFiles(selectedCommits);
|
|
1368
|
+
console.log(`\u{1F4C2} \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C(${reviewTargetFiles.length}\uAC1C): ${formatReviewTargetFiles(reviewTargetFiles)}`);
|
|
1369
|
+
console.log("\u23F3 \uC120\uD0DD\uD55C \uCEE4\uBC0B diff\uB97C \uC815\uB9AC\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...");
|
|
1370
|
+
trace7("git-diff:build:start");
|
|
1371
|
+
const diff = buildSelectedCommitDiff(selectedCommits);
|
|
1372
|
+
trace7("git-diff:build:done", `length=${diff.length}`);
|
|
959
1373
|
if (!diff.trim() && !isTest) {
|
|
960
1374
|
trace7("empty-diff:exit");
|
|
961
|
-
console.log("\u2139\uFE0F \uB9AC\uBDF0\
|
|
1375
|
+
console.log("\u2139\uFE0F \uC120\uD0DD\uD55C \uCEE4\uBC0B\uC5D0\uC11C \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C \uBCC0\uACBD\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
|
|
962
1376
|
deleteTempDiff();
|
|
963
1377
|
process.exit(0);
|
|
964
1378
|
}
|
|
1379
|
+
const nowStr = getNowString();
|
|
1380
|
+
trace7("timestamp:created", nowStr);
|
|
1381
|
+
trace7("report-dir:create:start");
|
|
1382
|
+
createReportDirectory();
|
|
1383
|
+
trace7("report-dir:create:done");
|
|
965
1384
|
trace7("temp-diff:write:start", tempDiffPath);
|
|
966
1385
|
fs.writeFileSync(tempDiffPath, diff);
|
|
967
1386
|
trace7("temp-diff:write:done");
|
|
@@ -983,9 +1402,11 @@ async function main() {
|
|
|
983
1402
|
}
|
|
984
1403
|
trace7("command:create:done");
|
|
985
1404
|
trace7("command:exec:start");
|
|
986
|
-
const result =
|
|
1405
|
+
const result = (await executeShellCommandWithProgress(command, {
|
|
1406
|
+
progressMessage: `\u23F3 [\uB9AC\uBDF0 \uC9C4\uD589] ${service} | \uB300\uC0C1 ${reviewTargetFiles.length}\uAC1C \uD30C\uC77C`,
|
|
1407
|
+
streamOutput: isTest
|
|
1408
|
+
})).stdout;
|
|
987
1409
|
trace7("command:exec:done", `resultLength=${result.length}`);
|
|
988
|
-
console.log(result);
|
|
989
1410
|
trace7("report:write:start");
|
|
990
1411
|
savedReportPath = getNextFilePath(REPORT_DIR, nowStr, ".md");
|
|
991
1412
|
fs.writeFileSync(savedReportPath, result);
|
|
@@ -1033,6 +1454,8 @@ ${command}`);
|
|
|
1033
1454
|
${JSON.stringify(
|
|
1034
1455
|
{
|
|
1035
1456
|
service,
|
|
1457
|
+
selectedCommitSummary: selectedCommitSummary || null,
|
|
1458
|
+
reviewTargetFiles,
|
|
1036
1459
|
command: command || null,
|
|
1037
1460
|
tempDiffPath,
|
|
1038
1461
|
savedDiffPath: savedDiffPath || null,
|