sales-frontend-gemini-cli 0.4.1 → 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 +674 -68
- package/dist/common/helper.cjs.map +1 -1
- package/dist/common/helper.d.cts +106 -4
- package/dist/common/helper.d.ts +106 -4
- package/dist/common/helper.js +659 -70
- 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 +58 -10
- package/dist/pr-review/claude/claude-commander.cjs.map +1 -1
- package/dist/pr-review/claude/claude-commander.js +58 -10
- package/dist/pr-review/claude/claude-commander.js.map +1 -1
- package/dist/pr-review/claude/installation-claude.cjs +219 -13
- package/dist/pr-review/claude/installation-claude.cjs.map +1 -1
- package/dist/pr-review/claude/installation-claude.js +218 -13
- package/dist/pr-review/claude/installation-claude.js.map +1 -1
- package/dist/pr-review/codex/codex-commander.cjs +55 -9
- package/dist/pr-review/codex/codex-commander.cjs.map +1 -1
- package/dist/pr-review/codex/codex-commander.js +55 -9
- package/dist/pr-review/codex/codex-commander.js.map +1 -1
- package/dist/pr-review/codex/installation-codex.cjs +219 -13
- package/dist/pr-review/codex/installation-codex.cjs.map +1 -1
- package/dist/pr-review/codex/installation-codex.js +218 -13
- package/dist/pr-review/codex/installation-codex.js.map +1 -1
- package/dist/pr-review/gemini/gemini-commander.cjs +82 -16
- package/dist/pr-review/gemini/gemini-commander.cjs.map +1 -1
- package/dist/pr-review/gemini/gemini-commander.js +82 -16
- package/dist/pr-review/gemini/gemini-commander.js.map +1 -1
- package/dist/pr-review/gemini/installation-gemini.cjs +219 -13
- package/dist/pr-review/gemini/installation-gemini.cjs.map +1 -1
- package/dist/pr-review/gemini/installation-gemini.js +218 -13
- package/dist/pr-review/gemini/installation-gemini.js.map +1 -1
- package/dist/pr-review/review-one-by-one.cjs +838 -184
- package/dist/pr-review/review-one-by-one.cjs.map +1 -1
- package/dist/pr-review/review-one-by-one.js +839 -185
- package/dist/pr-review/review-one-by-one.js.map +1 -1
- package/dist/pr-review/review.cjs +815 -156
- package/dist/pr-review/review.cjs.map +1 -1
- package/dist/pr-review/review.js +815 -156
- 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
|
@@ -1,20 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { exec, execSync } from 'child_process';
|
|
3
|
-
import
|
|
4
|
-
import util from 'util';
|
|
2
|
+
import { exec, execSync, execFileSync } from 'child_process';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import util, { inspect } from 'util';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import readline from 'readline';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
|
|
9
9
|
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
var
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
var traceMessages = [];
|
|
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
|
+
resolvePackageAssetPath("src/common/form/review-form.md");
|
|
51
|
+
var reviewFormOneByOnePath = resolvePackageAssetPath("src/common/form/review-form-one-by-one.md");
|
|
15
52
|
var REPORT_DIR = ".review-report";
|
|
16
53
|
var tempDiffPath = "temp_diff.txt";
|
|
17
54
|
var AIServices = ["gemini", "claude", "codex"];
|
|
55
|
+
var COMMIT_FETCH_LIMIT = 20;
|
|
56
|
+
var COMMIT_SELECTION_WINDOW = 8;
|
|
18
57
|
var ignoreList = [
|
|
19
58
|
"package.json",
|
|
20
59
|
"*.yml",
|
|
@@ -29,77 +68,439 @@ var ignoreList = [
|
|
|
29
68
|
".review-report/"
|
|
30
69
|
// 생성되는 리포트 폴더도 제외
|
|
31
70
|
];
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
71
|
+
function isTestMode(args4 = process.argv.slice(2)) {
|
|
72
|
+
return args4.includes("--test");
|
|
73
|
+
}
|
|
74
|
+
function clearTraceMessages() {
|
|
75
|
+
traceMessages.length = 0;
|
|
76
|
+
}
|
|
77
|
+
function getTraceMessages() {
|
|
78
|
+
return [...traceMessages];
|
|
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) {
|
|
36
154
|
return "";
|
|
37
155
|
}
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
156
|
+
const tokens = tokenizeVisibleText(value);
|
|
157
|
+
const totalWidth = tokens.reduce((sum, token) => sum + token.visibleWidth, 0);
|
|
158
|
+
if (totalWidth <= maxWidth) {
|
|
159
|
+
return value;
|
|
41
160
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
);
|
|
45
|
-
|
|
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}`;
|
|
46
178
|
}
|
|
47
|
-
function
|
|
48
|
-
|
|
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
|
+
function formatReviewTargetFiles(files, visibleCount = 5) {
|
|
201
|
+
if (files.length === 0) {
|
|
202
|
+
return "(\uC5C6\uC74C)";
|
|
203
|
+
}
|
|
204
|
+
const visibleFiles = files.slice(0, visibleCount);
|
|
205
|
+
const hiddenCount = Math.max(0, files.length - visibleFiles.length);
|
|
206
|
+
if (hiddenCount === 0) {
|
|
207
|
+
return visibleFiles.join(", ");
|
|
208
|
+
}
|
|
209
|
+
return `${visibleFiles.join(", ")} \uC678 ${hiddenCount}\uAC1C`;
|
|
49
210
|
}
|
|
50
211
|
function createTraceLogger(scope, args4 = process.argv.slice(2)) {
|
|
51
212
|
const enabled = isTestMode(args4);
|
|
52
213
|
return (step, detail) => {
|
|
214
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
215
|
+
const message = `[${timestamp}][TRACE][${scope}] ${step}${detail ? ` | ${detail}` : ""}`;
|
|
216
|
+
traceMessages.push(message);
|
|
53
217
|
if (!enabled) {
|
|
54
218
|
return;
|
|
55
219
|
}
|
|
56
|
-
console.log(
|
|
220
|
+
console.log(message);
|
|
57
221
|
};
|
|
58
222
|
}
|
|
223
|
+
var helperTrace = createTraceLogger("helper");
|
|
224
|
+
function getTimestampParts(now = /* @__PURE__ */ new Date()) {
|
|
225
|
+
return {
|
|
226
|
+
YYYY: now.getFullYear(),
|
|
227
|
+
MM: String(now.getMonth() + 1).padStart(2, "0"),
|
|
228
|
+
DD: String(now.getDate()).padStart(2, "0"),
|
|
229
|
+
HH: String(now.getHours()).padStart(2, "0"),
|
|
230
|
+
mm: String(now.getMinutes()).padStart(2, "0"),
|
|
231
|
+
ss: String(now.getSeconds()).padStart(2, "0")
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function getHumanReadableNowString(now = /* @__PURE__ */ new Date()) {
|
|
235
|
+
const { YYYY, MM, DD, HH, mm, ss } = getTimestampParts(now);
|
|
236
|
+
return `${YYYY}-${MM}-${DD} ${HH}:${mm}:${ss}`;
|
|
237
|
+
}
|
|
238
|
+
function stringifyUnknown(value) {
|
|
239
|
+
if (value === void 0 || value === null) {
|
|
240
|
+
return "";
|
|
241
|
+
}
|
|
242
|
+
if (typeof value === "string") {
|
|
243
|
+
return value;
|
|
244
|
+
}
|
|
245
|
+
if (Buffer.isBuffer(value)) {
|
|
246
|
+
return value.toString();
|
|
247
|
+
}
|
|
248
|
+
if (value instanceof Error) {
|
|
249
|
+
return value.stack || value.message;
|
|
250
|
+
}
|
|
251
|
+
return inspect(value, { depth: 5, breakLength: 120 });
|
|
252
|
+
}
|
|
253
|
+
function getErrorSummary(error) {
|
|
254
|
+
if (error instanceof Error) {
|
|
255
|
+
return `${error.name}: ${error.message}`;
|
|
256
|
+
}
|
|
257
|
+
return stringifyUnknown(error) || "Unknown error";
|
|
258
|
+
}
|
|
259
|
+
function serializeError(error) {
|
|
260
|
+
const serialized = {
|
|
261
|
+
summary: getErrorSummary(error)
|
|
262
|
+
};
|
|
263
|
+
if (error instanceof Error) {
|
|
264
|
+
serialized.name = error.name;
|
|
265
|
+
serialized.message = error.message;
|
|
266
|
+
serialized.stack = error.stack;
|
|
267
|
+
} else {
|
|
268
|
+
serialized.value = stringifyUnknown(error);
|
|
269
|
+
}
|
|
270
|
+
if (error && typeof error === "object") {
|
|
271
|
+
const errorLike = error;
|
|
272
|
+
const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs"];
|
|
273
|
+
extraKeys.forEach((key) => {
|
|
274
|
+
if (errorLike[key] !== void 0) {
|
|
275
|
+
serialized[key] = errorLike[key];
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
const stdout = stringifyUnknown(errorLike.stdout);
|
|
279
|
+
if (stdout) {
|
|
280
|
+
serialized.stdout = stdout;
|
|
281
|
+
}
|
|
282
|
+
const stderr = stringifyUnknown(errorLike.stderr);
|
|
283
|
+
if (stderr) {
|
|
284
|
+
serialized.stderr = stderr;
|
|
285
|
+
}
|
|
286
|
+
const cause = stringifyUnknown(errorLike.cause);
|
|
287
|
+
if (cause) {
|
|
288
|
+
serialized.cause = cause;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return serialized;
|
|
292
|
+
}
|
|
59
293
|
function getNextFilePath(dir, baseName, extension) {
|
|
60
294
|
let counter = 1;
|
|
61
295
|
while (true) {
|
|
62
296
|
const filePath = path.join(dir, `${baseName}-${counter}${extension}`);
|
|
63
|
-
if (!
|
|
297
|
+
if (!fs.existsSync(filePath)) {
|
|
64
298
|
return filePath;
|
|
65
299
|
}
|
|
66
300
|
counter++;
|
|
67
301
|
}
|
|
68
302
|
}
|
|
303
|
+
function getAvailableFilePath(dir, baseName, extension) {
|
|
304
|
+
const firstFilePath = path.join(dir, `${baseName}${extension}`);
|
|
305
|
+
if (!fs.existsSync(firstFilePath)) {
|
|
306
|
+
return firstFilePath;
|
|
307
|
+
}
|
|
308
|
+
return getNextFilePath(dir, baseName, extension);
|
|
309
|
+
}
|
|
69
310
|
function deleteFile(filePath) {
|
|
70
|
-
if (
|
|
71
|
-
|
|
311
|
+
if (fs.existsSync(filePath)) {
|
|
312
|
+
fs.unlinkSync(filePath);
|
|
72
313
|
}
|
|
73
314
|
}
|
|
74
315
|
function deleteTempDiff() {
|
|
75
316
|
deleteFile(tempDiffPath);
|
|
76
317
|
}
|
|
77
318
|
function createReportDirectory() {
|
|
78
|
-
if (!
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
function getNowString() {
|
|
83
|
-
const
|
|
84
|
-
const YYYY = now.getFullYear();
|
|
85
|
-
const MM = String(now.getMonth() + 1).padStart(2, "0");
|
|
86
|
-
const DD = String(now.getDate()).padStart(2, "0");
|
|
87
|
-
const HH = String(now.getHours()).padStart(2, "0");
|
|
88
|
-
const mm = String(now.getMinutes()).padStart(2, "0");
|
|
89
|
-
const ss = String(now.getSeconds()).padStart(2, "0");
|
|
319
|
+
if (!fs.existsSync(REPORT_DIR)) {
|
|
320
|
+
fs.mkdirSync(REPORT_DIR, { recursive: true });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function getNowString(now = /* @__PURE__ */ new Date()) {
|
|
324
|
+
const { YYYY, MM, DD, HH, mm, ss } = getTimestampParts(now);
|
|
90
325
|
return `${YYYY}-${MM}-${DD}_${HH}-${mm}-${ss}`;
|
|
91
326
|
}
|
|
92
|
-
function
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
327
|
+
function getErrorLogTimestamp(now = /* @__PURE__ */ new Date()) {
|
|
328
|
+
const { YYYY, MM, DD, HH, mm, ss } = getTimestampParts(now);
|
|
329
|
+
return `${YYYY}-${MM}-${DD}-${HH}\uC2DC-${mm}\uBD84-${ss}\uCD08`;
|
|
330
|
+
}
|
|
331
|
+
function writeErrorReport(error, options = {}) {
|
|
332
|
+
try {
|
|
333
|
+
const now = /* @__PURE__ */ new Date();
|
|
334
|
+
helperTrace("error-report:write:start", options.scope || "unknown");
|
|
335
|
+
createReportDirectory();
|
|
336
|
+
const reportPath = getAvailableFilePath(REPORT_DIR, `error-log-${getErrorLogTimestamp(now)}`, ".md");
|
|
337
|
+
const serializedError = serializeError(error);
|
|
338
|
+
const traceSnapshot = options.traceMessages ?? getTraceMessages();
|
|
339
|
+
const extraSections = options.extraSections || [];
|
|
340
|
+
const report = `# Error Log
|
|
341
|
+
|
|
342
|
+
- \uBC1C\uC0DD \uC2DC\uAC01: ${getHumanReadableNowString(now)}
|
|
343
|
+
- Scope: \`${options.scope || "unknown"}\`
|
|
344
|
+
- \uC791\uC5C5 \uACBD\uB85C: \`${process.cwd()}\`
|
|
345
|
+
- \uC2E4\uD589 \uC778\uC790: \`${JSON.stringify(options.args ?? process.argv.slice(2))}\`
|
|
346
|
+
- \uC2E4\uD589 \uD658\uACBD: \`${process.platform} ${process.arch} / Node ${process.version}\`
|
|
347
|
+
|
|
348
|
+
## Summary
|
|
349
|
+
|
|
350
|
+
${options.title || serializedError.summary || "Unknown error"}
|
|
351
|
+
|
|
352
|
+
## Error
|
|
353
|
+
|
|
354
|
+
\`\`\`json
|
|
355
|
+
${JSON.stringify(serializedError, null, 2)}
|
|
356
|
+
\`\`\`
|
|
357
|
+
|
|
358
|
+
## Trace
|
|
359
|
+
|
|
360
|
+
\`\`\`json
|
|
361
|
+
${JSON.stringify(traceSnapshot, null, 2)}
|
|
362
|
+
\`\`\`${extraSections.length ? `
|
|
363
|
+
${extraSections.map((section) => `
|
|
364
|
+
## ${section.heading}
|
|
365
|
+
|
|
366
|
+
${section.markdown}`).join("\n")}
|
|
367
|
+
` : "\n"}
|
|
368
|
+
`;
|
|
369
|
+
fs.writeFileSync(reportPath, report);
|
|
370
|
+
helperTrace("error-report:write:done", reportPath);
|
|
371
|
+
return reportPath;
|
|
372
|
+
} catch (writeError) {
|
|
373
|
+
console.error("\u26A0\uFE0F \uC5D0\uB7EC \uB85C\uADF8 \uD30C\uC77C \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
|
|
374
|
+
console.error(writeError);
|
|
375
|
+
return "";
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
function exitWithError(message, options = {}) {
|
|
379
|
+
const reportPath = writeErrorReport(options.error || new Error(message), {
|
|
380
|
+
...options,
|
|
381
|
+
title: message
|
|
382
|
+
});
|
|
383
|
+
console.error(message);
|
|
384
|
+
if (options.error) {
|
|
385
|
+
console.error(options.error);
|
|
386
|
+
}
|
|
387
|
+
if (reportPath) {
|
|
388
|
+
console.error(`\u{1F4C4} \uC5D0\uB7EC \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${reportPath}`);
|
|
389
|
+
}
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
function parseServiceFromArgs(args4 = process.argv.slice(2)) {
|
|
393
|
+
helperTrace("parse-service:start", `args=${JSON.stringify(args4)}`);
|
|
394
|
+
const serviceIndex = args4.indexOf("--service");
|
|
395
|
+
const rawService = serviceIndex !== -1 ? args4[serviceIndex + 1] : "";
|
|
396
|
+
if (!rawService) {
|
|
397
|
+
helperTrace("parse-service:empty");
|
|
398
|
+
return "";
|
|
399
|
+
}
|
|
400
|
+
const normalizedService = rawService.toLowerCase();
|
|
401
|
+
if (AIServices.includes(normalizedService)) {
|
|
402
|
+
helperTrace("parse-service:resolved", normalizedService);
|
|
403
|
+
return normalizedService;
|
|
404
|
+
}
|
|
405
|
+
helperTrace("parse-service:invalid", rawService);
|
|
406
|
+
exitWithError(
|
|
407
|
+
`\u274C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC11C\uBE44\uC2A4\uC785\uB2C8\uB2E4: ${rawService}. \uC0AC\uC6A9 \uAC00\uB2A5 \uAC12: ${AIServices.join(", ")} (\uC608: --service codex)`,
|
|
408
|
+
{
|
|
409
|
+
scope: "helper:parseServiceFromArgs",
|
|
410
|
+
args: args4,
|
|
411
|
+
extraSections: [
|
|
412
|
+
{
|
|
413
|
+
heading: "Allowed Services",
|
|
414
|
+
markdown: `\`\`\`json
|
|
415
|
+
${JSON.stringify(AIServices, null, 2)}
|
|
416
|
+
\`\`\``
|
|
417
|
+
}
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
function truncateCommitSubject(subject) {
|
|
423
|
+
if (subject.length <= 72) {
|
|
424
|
+
return subject;
|
|
425
|
+
}
|
|
426
|
+
return `${subject.slice(0, 69)}...`;
|
|
427
|
+
}
|
|
428
|
+
function getRecentCommitOptions() {
|
|
429
|
+
const output = runGitCommand(
|
|
430
|
+
["log", `-${COMMIT_FETCH_LIMIT}`, "--date=relative", "--pretty=format:%h%x09%an%x09%ar%x09%s"],
|
|
431
|
+
{ allowFailure: true }
|
|
432
|
+
);
|
|
433
|
+
if (!output) {
|
|
434
|
+
return [];
|
|
435
|
+
}
|
|
436
|
+
return output.split("\n").map((line) => {
|
|
437
|
+
const [hash = "", author = "", relativeDate = "", ...subjectParts] = line.split(" ");
|
|
438
|
+
const subject = subjectParts.join(" ").trim();
|
|
439
|
+
return {
|
|
440
|
+
author,
|
|
441
|
+
description: `${author} | ${relativeDate}`,
|
|
442
|
+
hash,
|
|
443
|
+
label: `${hash} | ${truncateCommitSubject(subject)}`,
|
|
444
|
+
relativeDate,
|
|
445
|
+
subject
|
|
446
|
+
};
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
function buildSelectedCommitSummary(commits) {
|
|
450
|
+
return commits.map((commit) => `- ${commit.hash} | ${commit.subject} | ${commit.author} | ${commit.relativeDate}`).join("\n");
|
|
451
|
+
}
|
|
452
|
+
function getReviewPathspecArgs() {
|
|
453
|
+
const { includePatterns, excludePatterns } = getGitDiffPathspecs();
|
|
454
|
+
return [...includePatterns, ...excludePatterns];
|
|
455
|
+
}
|
|
456
|
+
function buildSelectedCommitDiff(commits) {
|
|
457
|
+
const reviewPathspecArgs = getReviewPathspecArgs();
|
|
458
|
+
const sections = commits.map((commit) => {
|
|
459
|
+
const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", ...reviewPathspecArgs], {
|
|
460
|
+
allowFailure: true,
|
|
461
|
+
trimOutput: false
|
|
462
|
+
}).trim();
|
|
463
|
+
if (!diff) {
|
|
464
|
+
return "";
|
|
465
|
+
}
|
|
466
|
+
return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
|
|
467
|
+
}).filter(Boolean).join("\n\n");
|
|
468
|
+
if (!sections) {
|
|
469
|
+
return "";
|
|
470
|
+
}
|
|
471
|
+
return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", "# \uB9AC\uBDF0 \uB300\uC0C1 diff", sections].join("\n");
|
|
472
|
+
}
|
|
473
|
+
function getSelectedCommitFiles(commits) {
|
|
474
|
+
const reviewPathspecArgs = getReviewPathspecArgs();
|
|
475
|
+
const files = /* @__PURE__ */ new Set();
|
|
476
|
+
commits.forEach((commit) => {
|
|
477
|
+
const output = runGitCommand(["show", "--pretty=format:", "--name-only", commit.hash, "--", ...reviewPathspecArgs], {
|
|
478
|
+
allowFailure: true
|
|
479
|
+
});
|
|
480
|
+
output.split("\n").map((line) => line.trim()).filter(Boolean).forEach((filePath) => files.add(filePath));
|
|
481
|
+
});
|
|
482
|
+
return [...files];
|
|
483
|
+
}
|
|
484
|
+
function buildSelectedFileDiff(commits, filePath) {
|
|
485
|
+
const sections = commits.map((commit) => {
|
|
486
|
+
const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", filePath], {
|
|
487
|
+
allowFailure: true,
|
|
488
|
+
trimOutput: false
|
|
489
|
+
}).trim();
|
|
490
|
+
if (!diff) {
|
|
491
|
+
return "";
|
|
492
|
+
}
|
|
493
|
+
return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
|
|
494
|
+
}).filter(Boolean).join("\n\n");
|
|
495
|
+
if (!sections) {
|
|
496
|
+
return "";
|
|
497
|
+
}
|
|
498
|
+
return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", `# \uD30C\uC77C: ${filePath}`, sections].join("\n\n");
|
|
99
499
|
}
|
|
100
500
|
function openReport(reportPath) {
|
|
101
501
|
const resolvedPath = path.resolve(reportPath);
|
|
102
502
|
const { platform } = process;
|
|
503
|
+
helperTrace("open-report:start", resolvedPath);
|
|
103
504
|
const openWithChrome = () => {
|
|
104
505
|
if (platform === "darwin") {
|
|
105
506
|
execSync(`open -a "Google Chrome" "${resolvedPath}"`, { stdio: "ignore" });
|
|
@@ -124,63 +525,188 @@ function openReport(reportPath) {
|
|
|
124
525
|
};
|
|
125
526
|
try {
|
|
126
527
|
if (openWithChrome()) {
|
|
528
|
+
helperTrace("open-report:chrome:success", platform);
|
|
127
529
|
console.log("\u{1F680} Google Chrome\uC5D0\uC11C \uB9AC\uD3EC\uD2B8\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
128
530
|
return;
|
|
129
531
|
}
|
|
130
|
-
} catch {
|
|
532
|
+
} catch (error) {
|
|
533
|
+
helperTrace("open-report:chrome:failed", getErrorSummary(error));
|
|
131
534
|
}
|
|
132
535
|
try {
|
|
133
536
|
if (openWithDefaultBrowser()) {
|
|
537
|
+
helperTrace("open-report:default-browser:success", platform);
|
|
134
538
|
console.log("\u{1F680} \uAE30\uBCF8 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB9AC\uD3EC\uD2B8\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
135
539
|
return;
|
|
136
540
|
}
|
|
137
|
-
} catch (
|
|
138
|
-
|
|
541
|
+
} catch (error) {
|
|
542
|
+
helperTrace("open-report:default-browser:failed", getErrorSummary(error));
|
|
543
|
+
console.error("\u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800 \uC5F4\uAE30 \uC2E4\uD328:", error);
|
|
139
544
|
return;
|
|
140
545
|
}
|
|
546
|
+
helperTrace("open-report:unsupported-platform", platform);
|
|
141
547
|
console.error(`\u26A0\uFE0F \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD50C\uB7AB\uD3FC\uC785\uB2C8\uB2E4: ${platform}`);
|
|
142
548
|
}
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
549
|
+
function ensureInteractiveSelectionAvailable(scope, message) {
|
|
550
|
+
if (process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
helperTrace(`${scope}:tty-missing`);
|
|
554
|
+
exitWithError(message, {
|
|
555
|
+
scope: `helper:${scope}`
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
function renderSelectionBlock(lines, previousLineCount) {
|
|
559
|
+
if (previousLineCount > 0) {
|
|
560
|
+
readline.moveCursor(process.stdout, 0, -previousLineCount);
|
|
561
|
+
readline.clearScreenDown(process.stdout);
|
|
562
|
+
}
|
|
563
|
+
const fittedLines = fitLinesToTerminal(lines);
|
|
564
|
+
process.stdout.write(`${fittedLines.join("\n")}
|
|
565
|
+
`);
|
|
566
|
+
return fittedLines.length;
|
|
567
|
+
}
|
|
568
|
+
function getSelectionWindowRange(optionCount, selectedIndex, windowSize) {
|
|
569
|
+
if (optionCount <= windowSize) {
|
|
570
|
+
return {
|
|
571
|
+
end: optionCount,
|
|
572
|
+
start: 0
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
const halfWindow = Math.floor(windowSize / 2);
|
|
576
|
+
const maxStart = optionCount - windowSize;
|
|
577
|
+
const start = Math.max(0, Math.min(selectedIndex - halfWindow, maxStart));
|
|
578
|
+
return {
|
|
579
|
+
end: Math.min(optionCount, start + windowSize),
|
|
580
|
+
start
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
function buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize) {
|
|
584
|
+
const { start, end } = getSelectionWindowRange(options.length, selectedIndex, windowSize);
|
|
585
|
+
const lines = [
|
|
586
|
+
`${ANSI.bold}${question}${ANSI.reset}`,
|
|
587
|
+
`${ANSI.dim}\u2191\u2193 \uC774\uB3D9 | Space \uC120\uD0DD/\uD574\uC81C | Enter \uC644\uB8CC | Esc \uCDE8\uC18C${ANSI.reset}`,
|
|
588
|
+
`${ANSI.dim}\uC120\uD0DD\uB428: ${toggled.size}\uAC1C / \uC804\uCCB4: ${options.length}\uAC1C${ANSI.reset}`
|
|
589
|
+
];
|
|
590
|
+
for (let index = start; index < end; index += 1) {
|
|
591
|
+
const option = options[index];
|
|
592
|
+
if (!option) {
|
|
593
|
+
continue;
|
|
153
594
|
}
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
595
|
+
const cursor = index === selectedIndex ? `${ANSI.cyan}>${ANSI.reset}` : " ";
|
|
596
|
+
const checked = toggled.has(index) ? `${ANSI.green}\u2611${ANSI.reset}` : "\u2610";
|
|
597
|
+
const description = option.description ? ` ${ANSI.dim}${option.description}${ANSI.reset}` : "";
|
|
598
|
+
lines.push(`${cursor} ${checked} ${option.label}${description}`);
|
|
599
|
+
}
|
|
600
|
+
if (options.length > windowSize) {
|
|
601
|
+
lines.push(`${ANSI.dim}\uD45C\uC2DC \uBC94\uC704: ${start + 1}-${end} / ${options.length}${ANSI.reset}`);
|
|
602
|
+
}
|
|
603
|
+
return lines;
|
|
604
|
+
}
|
|
605
|
+
async function showMultiSelect(question, options, windowSize = COMMIT_SELECTION_WINDOW) {
|
|
606
|
+
ensureInteractiveSelectionAvailable("showMultiSelect", "\u274C \uCEE4\uBC0B \uC120\uD0DD \uBAA8\uB2EC\uC740 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
607
|
+
let selectedIndex = 0;
|
|
608
|
+
let renderedLineCount = 0;
|
|
609
|
+
const toggled = /* @__PURE__ */ new Set();
|
|
610
|
+
const rl = readline.createInterface({
|
|
611
|
+
input: process.stdin,
|
|
612
|
+
output: process.stdout,
|
|
613
|
+
terminal: true
|
|
614
|
+
});
|
|
615
|
+
process.stdout.write("\x1B[?25l");
|
|
616
|
+
const cleanup = () => {
|
|
617
|
+
if (renderedLineCount > 0) {
|
|
618
|
+
readline.moveCursor(process.stdout, 0, -renderedLineCount);
|
|
619
|
+
readline.clearScreenDown(process.stdout);
|
|
620
|
+
renderedLineCount = 0;
|
|
161
621
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
622
|
+
process.stdin.removeListener("data", onData);
|
|
623
|
+
process.stdin.setRawMode(false);
|
|
624
|
+
process.stdin.pause();
|
|
625
|
+
rl.close();
|
|
626
|
+
process.stdout.write("\x1B[?25h");
|
|
627
|
+
};
|
|
628
|
+
const render = () => {
|
|
629
|
+
const lines = buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize);
|
|
630
|
+
renderedLineCount = renderSelectionBlock(lines, renderedLineCount);
|
|
631
|
+
};
|
|
632
|
+
const confirmSelection = (resolve) => {
|
|
633
|
+
const values = [...toggled].sort((left, right) => left - right).map((index) => options[index]?.value).filter((value) => value !== void 0);
|
|
634
|
+
cleanup();
|
|
635
|
+
resolve(values);
|
|
636
|
+
};
|
|
637
|
+
const cancelSelection = (resolve) => {
|
|
638
|
+
cleanup();
|
|
639
|
+
resolve([]);
|
|
640
|
+
};
|
|
641
|
+
let onData = (_data) => {
|
|
642
|
+
};
|
|
643
|
+
render();
|
|
644
|
+
return new Promise((resolve) => {
|
|
645
|
+
onData = (data) => {
|
|
646
|
+
const key = data.toString();
|
|
647
|
+
if (key === "") {
|
|
648
|
+
cleanup();
|
|
649
|
+
process.exit(0);
|
|
170
650
|
}
|
|
171
|
-
|
|
172
|
-
|
|
651
|
+
if (key === "\x1B") {
|
|
652
|
+
cancelSelection(resolve);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
if (key === "\x1B[A") {
|
|
656
|
+
selectedIndex = (selectedIndex - 1 + options.length) % options.length;
|
|
657
|
+
render();
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
if (key === "\x1B[B") {
|
|
661
|
+
selectedIndex = (selectedIndex + 1) % options.length;
|
|
662
|
+
render();
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (key === " ") {
|
|
666
|
+
if (toggled.has(selectedIndex)) {
|
|
667
|
+
toggled.delete(selectedIndex);
|
|
668
|
+
} else {
|
|
669
|
+
toggled.add(selectedIndex);
|
|
670
|
+
}
|
|
671
|
+
render();
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (key === "\r" || key === "\n") {
|
|
675
|
+
confirmSelection(resolve);
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
process.stdin.setRawMode(true);
|
|
679
|
+
process.stdin.resume();
|
|
680
|
+
process.stdin.on("data", onData);
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
async function selectReviewCommits() {
|
|
684
|
+
const commits = getRecentCommitOptions();
|
|
685
|
+
if (commits.length === 0) {
|
|
686
|
+
console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uCD5C\uADFC \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
687
|
+
return [];
|
|
173
688
|
}
|
|
174
|
-
return
|
|
689
|
+
return showMultiSelect(
|
|
690
|
+
"\uB9AC\uBDF0\uD560 \uCEE4\uBC0B\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.",
|
|
691
|
+
commits.map((commit) => ({
|
|
692
|
+
description: commit.description,
|
|
693
|
+
label: commit.label,
|
|
694
|
+
value: commit
|
|
695
|
+
})),
|
|
696
|
+
COMMIT_SELECTION_WINDOW
|
|
697
|
+
);
|
|
175
698
|
}
|
|
176
699
|
async function showSelectionAIService() {
|
|
177
700
|
const selectedServiceFromArgs = parseServiceFromArgs();
|
|
178
701
|
if (selectedServiceFromArgs) {
|
|
702
|
+
helperTrace("show-selection:from-args", selectedServiceFromArgs);
|
|
179
703
|
console.log(`
|
|
180
|
-
\u2705
|
|
704
|
+
\u2705 ${ANSI.green}${selectedServiceFromArgs}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
|
|
181
705
|
`);
|
|
182
706
|
return selectedServiceFromArgs;
|
|
183
707
|
}
|
|
708
|
+
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.");
|
|
709
|
+
helperTrace("show-selection:interactive:start");
|
|
184
710
|
let selectedIndex = 0;
|
|
185
711
|
const rl = readline.createInterface({
|
|
186
712
|
input: process.stdin,
|
|
@@ -194,13 +720,15 @@ async function showSelectionAIService() {
|
|
|
194
720
|
readline.moveCursor(process.stdout, 0, -(AIServices.length + 1));
|
|
195
721
|
}
|
|
196
722
|
firstRender = false;
|
|
723
|
+
helperTrace("show-selection:interactive:render", AIServices[selectedIndex] || "unknown");
|
|
197
724
|
readline.clearScreenDown(process.stdout);
|
|
198
725
|
process.stdout.write(
|
|
199
|
-
|
|
726
|
+
`\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):
|
|
727
|
+
`
|
|
200
728
|
);
|
|
201
729
|
AIServices.forEach((service, index) => {
|
|
202
730
|
if (index === selectedIndex) {
|
|
203
|
-
process.stdout.write(`
|
|
731
|
+
process.stdout.write(` ${ANSI.cyan}>${ANSI.reset} ${ANSI.cyan}\u25C9${ANSI.reset} ${ANSI.bold}${service}${ANSI.reset}
|
|
204
732
|
`);
|
|
205
733
|
} else {
|
|
206
734
|
process.stdout.write(` \u25EF ${service}
|
|
@@ -213,6 +741,7 @@ async function showSelectionAIService() {
|
|
|
213
741
|
const onData = (data) => {
|
|
214
742
|
const key = data.toString();
|
|
215
743
|
if (key === "") {
|
|
744
|
+
helperTrace("show-selection:interactive:ctrl-c");
|
|
216
745
|
process.stdout.write("\x1B[?25h");
|
|
217
746
|
process.exit(0);
|
|
218
747
|
}
|
|
@@ -229,10 +758,11 @@ async function showSelectionAIService() {
|
|
|
229
758
|
rl.close();
|
|
230
759
|
process.stdout.write("\x1B[?25h");
|
|
231
760
|
console.log(`
|
|
232
|
-
\u2705
|
|
761
|
+
\u2705 ${ANSI.green}${AIServices[selectedIndex]}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
|
|
233
762
|
`);
|
|
234
763
|
const result = AIServices[selectedIndex];
|
|
235
764
|
if (result) {
|
|
765
|
+
helperTrace("show-selection:interactive:confirmed", result);
|
|
236
766
|
resolve(result);
|
|
237
767
|
}
|
|
238
768
|
}
|
|
@@ -255,6 +785,11 @@ function getArgValue(flag) {
|
|
|
255
785
|
}
|
|
256
786
|
return args[index + 1];
|
|
257
787
|
}
|
|
788
|
+
function printNotice(message) {
|
|
789
|
+
if (args.includes("--test")) {
|
|
790
|
+
console.warn(message);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
258
793
|
function toUnique(values) {
|
|
259
794
|
const seen = /* @__PURE__ */ new Set();
|
|
260
795
|
return values.filter((value) => {
|
|
@@ -278,11 +813,11 @@ function resolveReasoningEffort() {
|
|
|
278
813
|
const normalized = normalizeEffort(customReasoningEffort);
|
|
279
814
|
trace("reasoning:custom", `${customReasoningEffort} -> ${normalized}`);
|
|
280
815
|
if (customReasoningEffort === "minimal") {
|
|
281
|
-
|
|
816
|
+
printNotice("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
|
|
282
817
|
}
|
|
283
818
|
return normalized;
|
|
284
819
|
}
|
|
285
|
-
|
|
820
|
+
printNotice(
|
|
286
821
|
`\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS.join(
|
|
287
822
|
", "
|
|
288
823
|
)}`
|
|
@@ -325,7 +860,7 @@ function buildClaudeExecCommand(options) {
|
|
|
325
860
|
const modelOption = model ? `--model ${shellQuote(model)}` : "";
|
|
326
861
|
const fallbackOption = model && fallbackModel ? `--fallback-model ${shellQuote(fallbackModel)}` : "";
|
|
327
862
|
const effortOption = `--effort ${shellQuote(effort)}`;
|
|
328
|
-
const appendedPromptFiles = systemPromptFiles.map((
|
|
863
|
+
const appendedPromptFiles = systemPromptFiles.map((path3) => `--append-system-prompt-file ${shellQuote(path3)}`).join(" ");
|
|
329
864
|
return `cat ${shellQuote(tempDiffPath2)} | claude ${[
|
|
330
865
|
modelOption,
|
|
331
866
|
fallbackOption,
|
|
@@ -346,22 +881,25 @@ var createClaudeCommand = (tempDiffPath2, reviewFormPath2) => {
|
|
|
346
881
|
{ path: namingRulesPath, display: "\uB124\uC774\uBC0D \uADDC\uCE59" },
|
|
347
882
|
{ path: codingConventionRulesPath, display: "\uCF54\uB529 \uCEE8\uBCA4\uC158" }
|
|
348
883
|
];
|
|
349
|
-
const existingRuleFiles = rules.filter((rule) =>
|
|
884
|
+
const existingRuleFiles = rules.filter((rule) => fs.existsSync(rule.path)).map((rule) => rule.path);
|
|
350
885
|
trace("rules:loaded", `count=${existingRuleFiles.length}`);
|
|
351
|
-
const reviewFormExists =
|
|
886
|
+
const reviewFormExists = fs.existsSync(reviewFormPath2);
|
|
352
887
|
trace("reviewForm:status", reviewFormExists ? "exists" : "missing");
|
|
353
888
|
const systemPromptFiles = reviewFormExists ? [...existingRuleFiles, reviewFormPath2] : existingRuleFiles;
|
|
354
889
|
const prompt = "\uC704 \uADDC\uCE59\uB4E4\uC744 \uCC38\uACE0\uD558\uC5EC \uC774 diff\uB97C \uCF54\uB4DC\uB9AC\uBDF0\uD574\uC918. \uB9AC\uBDF0\uC591\uC2DD\uC5D0 \uB9DE\uCDB0\uC11C \uC791\uC131\uD574\uC918.";
|
|
890
|
+
trace("prompt:prepared", `length=${prompt.length}`);
|
|
891
|
+
trace("system-prompt-files", `count=${systemPromptFiles.length}`);
|
|
355
892
|
const modelCandidates = toUnique(customModel ? [customModel, ...aliasFallbacks] : aliasFallbacks);
|
|
356
893
|
trace("model:candidates", modelCandidates.join(", "));
|
|
894
|
+
trace("command:candidates:count", String(modelCandidates.length + 1));
|
|
357
895
|
if (customModel) {
|
|
358
|
-
|
|
896
|
+
printNotice(
|
|
359
897
|
`\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
|
|
360
898
|
" -> "
|
|
361
899
|
)}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
|
|
362
900
|
);
|
|
363
901
|
} else {
|
|
364
|
-
|
|
902
|
+
printNotice(
|
|
365
903
|
`\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.`
|
|
366
904
|
);
|
|
367
905
|
}
|
|
@@ -394,6 +932,7 @@ var createClaudeCommand = (tempDiffPath2, reviewFormPath2) => {
|
|
|
394
932
|
\uC0DD\uC131\uB420 \uBA85\uB839\uC5B4 \uBBF8\uB9AC\uBCF4\uAE30:
|
|
395
933
|
${safeCommand}"`;
|
|
396
934
|
}
|
|
935
|
+
trace("command:mode", "execute");
|
|
397
936
|
trace("createClaudeCommand:end");
|
|
398
937
|
return command;
|
|
399
938
|
};
|
|
@@ -404,23 +943,25 @@ function checkClaudeCliInstalled() {
|
|
|
404
943
|
trace2("version-check:run", "claude --version");
|
|
405
944
|
execSync("claude --version", { stdio: "ignore" });
|
|
406
945
|
trace2("version-check:ok");
|
|
407
|
-
} catch {
|
|
408
|
-
trace2("version-check:failed",
|
|
946
|
+
} catch (error) {
|
|
947
|
+
trace2("version-check:failed", getErrorSummary(error));
|
|
948
|
+
trace2("install:start", "@anthropic-ai/claude-code");
|
|
409
949
|
console.log(
|
|
410
950
|
"\u2139\uFE0F claude-cli\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC124\uCE58\uB97C \uC9C4\uD589\uD569\uB2C8\uB2E4... npm install -g @anthropic-ai/claude-code"
|
|
411
951
|
);
|
|
412
952
|
try {
|
|
413
953
|
execSync("npm install -g @anthropic-ai/claude-code", { stdio: "inherit" });
|
|
414
|
-
trace2("install:ok", "
|
|
954
|
+
trace2("install:ok", "login-required");
|
|
415
955
|
console.log("\u2705 claude-cli \uC124\uCE58\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
416
956
|
console.log("\u26A0\uFE0F claude-cli \uC0AC\uC6A9\uC744 \uC704\uD574 \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
417
957
|
console.log(' \uD130\uBBF8\uB110\uC5D0\uC11C "claude" \uB97C \uC785\uB825\uD558\uC5EC \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC744 \uC644\uB8CC\uD55C \uD6C4, \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.');
|
|
418
958
|
process.exit(1);
|
|
419
959
|
} catch (installError) {
|
|
420
|
-
trace2("install:failed");
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
960
|
+
trace2("install:failed", getErrorSummary(installError));
|
|
961
|
+
exitWithError("\u274C claude-cli \uC124\uCE58 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4. \uAD8C\uD55C \uBB38\uC81C\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4 (sudo \uD544\uC694).", {
|
|
962
|
+
scope: "installation-claude",
|
|
963
|
+
error: installError
|
|
964
|
+
});
|
|
424
965
|
}
|
|
425
966
|
}
|
|
426
967
|
trace2("checkClaudeCliInstalled:end");
|
|
@@ -438,6 +979,11 @@ function getArgValue2(flag) {
|
|
|
438
979
|
}
|
|
439
980
|
return args2[index + 1];
|
|
440
981
|
}
|
|
982
|
+
function printNotice2(message) {
|
|
983
|
+
if (args2.includes("--test")) {
|
|
984
|
+
console.warn(message);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
441
987
|
function resolveReasoningEffort2() {
|
|
442
988
|
const customReasoningEffort = getArgValue2("--reasoning-effort");
|
|
443
989
|
if (customReasoningEffort) {
|
|
@@ -445,7 +991,7 @@ function resolveReasoningEffort2() {
|
|
|
445
991
|
trace3("reasoning:custom", customReasoningEffort);
|
|
446
992
|
return customReasoningEffort;
|
|
447
993
|
}
|
|
448
|
-
|
|
994
|
+
printNotice2(
|
|
449
995
|
`\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS2.join(
|
|
450
996
|
", "
|
|
451
997
|
)}`
|
|
@@ -471,10 +1017,10 @@ var createCodexCommand = (tempDiffPath2, reviewFormPath2) => {
|
|
|
471
1017
|
trace3("createCodexCommand:start", `tempDiffPath=${tempDiffPath2}, reviewFormPath=${reviewFormPath2}`);
|
|
472
1018
|
const customModel = getArgValue2("--model");
|
|
473
1019
|
const reasoningEffort = resolveReasoningEffort2();
|
|
474
|
-
const rules = [rulesPath, namingRulesPath, codingConventionRulesPath].filter((filePath) =>
|
|
1020
|
+
const rules = [rulesPath, namingRulesPath, codingConventionRulesPath].filter((filePath) => fs.existsSync(filePath)).map((filePath) => `- ${filePath}`).join("\n");
|
|
475
1021
|
const rulesCount = rules ? rules.split("\n").length : 0;
|
|
476
1022
|
trace3("rules:loaded", `count=${rulesCount}`);
|
|
477
|
-
const hasReviewForm =
|
|
1023
|
+
const hasReviewForm = fs.existsSync(reviewFormPath2);
|
|
478
1024
|
const reviewFormLine = hasReviewForm ? `- ${reviewFormPath2}` : "";
|
|
479
1025
|
trace3("reviewForm:status", reviewFormLine ? "exists" : "missing");
|
|
480
1026
|
const prompt = `\uC544\uB798 \uD30C\uC77C\uB4E4\uC744 \uCC38\uACE0\uD574\uC11C \uCF54\uB4DC \uB9AC\uBDF0\uB97C \uC9C4\uD589\uD574\uC918.
|
|
@@ -486,16 +1032,17 @@ ${reviewFormLine || "- (\uC5C6\uC74C)"}
|
|
|
486
1032
|
- ${tempDiffPath2}
|
|
487
1033
|
|
|
488
1034
|
\uBC18\uB4DC\uC2DC \uB9AC\uBDF0 \uC591\uC2DD\uC5D0 \uB9DE\uCDB0 \uC791\uC131\uD574\uC918.`;
|
|
1035
|
+
trace3("prompt:prepared", `length=${prompt.length}`);
|
|
489
1036
|
let command = "";
|
|
490
1037
|
if (customModel) {
|
|
491
|
-
|
|
1038
|
+
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.");
|
|
492
1039
|
trace3("model:custom", customModel);
|
|
493
1040
|
command = buildCodexExecCommand(prompt, reasoningEffort, customModel);
|
|
494
1041
|
} else {
|
|
495
1042
|
const preferredModelAlias = "gpt-5";
|
|
496
1043
|
const aliasCommand = buildCodexExecCommand(prompt, reasoningEffort, preferredModelAlias);
|
|
497
1044
|
const fallbackCommand = buildCodexExecCommand(prompt, reasoningEffort);
|
|
498
|
-
|
|
1045
|
+
printNotice2(
|
|
499
1046
|
`\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.`
|
|
500
1047
|
);
|
|
501
1048
|
trace3("model:alias-first", preferredModelAlias);
|
|
@@ -511,6 +1058,7 @@ ${reviewFormLine || "- (\uC5C6\uC74C)"}
|
|
|
511
1058
|
\uC0DD\uC131\uB420 \uBA85\uB839\uC5B4 \uBBF8\uB9AC\uBCF4\uAE30:
|
|
512
1059
|
${safeCommand}"`;
|
|
513
1060
|
}
|
|
1061
|
+
trace3("command:mode", "execute");
|
|
514
1062
|
trace3("createCodexCommand:end");
|
|
515
1063
|
return command;
|
|
516
1064
|
};
|
|
@@ -521,21 +1069,23 @@ function checkCodexCliInstalled() {
|
|
|
521
1069
|
trace4("version-check:run", "codex --version");
|
|
522
1070
|
execSync("codex --version", { stdio: "ignore" });
|
|
523
1071
|
trace4("version-check:ok");
|
|
524
|
-
} catch {
|
|
525
|
-
trace4("version-check:failed",
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
trace4("version-check:failed", getErrorSummary(error));
|
|
1074
|
+
trace4("install:start", "@openai/codex");
|
|
526
1075
|
console.log("\u2139\uFE0F codex-cli\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC124\uCE58\uB97C \uC9C4\uD589\uD569\uB2C8\uB2E4... npm install -g @openai/codex");
|
|
527
1076
|
try {
|
|
528
1077
|
execSync("npm install -g @openai/codex", { stdio: "inherit" });
|
|
529
|
-
trace4("install:ok", "
|
|
1078
|
+
trace4("install:ok", "login-required");
|
|
530
1079
|
console.log("\u2705 codex-cli \uC124\uCE58\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
531
1080
|
console.log("\u26A0\uFE0F codex-cli \uC0AC\uC6A9\uC744 \uC704\uD574 \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
532
1081
|
console.log(' \uD130\uBBF8\uB110\uC5D0\uC11C "codex login" \uC744 \uC785\uB825\uD558\uC5EC \uB85C\uADF8\uC778\uC744 \uC644\uB8CC\uD55C \uD6C4, \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.');
|
|
533
1082
|
process.exit(1);
|
|
534
1083
|
} catch (installError) {
|
|
535
|
-
trace4("install:failed");
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
1084
|
+
trace4("install:failed", getErrorSummary(installError));
|
|
1085
|
+
exitWithError("\u274C codex-cli \uC124\uCE58 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4. \uAD8C\uD55C \uBB38\uC81C\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4 (sudo \uD544\uC694).", {
|
|
1086
|
+
scope: "installation-codex",
|
|
1087
|
+
error: installError
|
|
1088
|
+
});
|
|
539
1089
|
}
|
|
540
1090
|
}
|
|
541
1091
|
trace4("checkCodexCliInstalled:end");
|
|
@@ -553,6 +1103,11 @@ function getArgValue3(flag) {
|
|
|
553
1103
|
}
|
|
554
1104
|
return args3[index + 1];
|
|
555
1105
|
}
|
|
1106
|
+
function printNotice3(message) {
|
|
1107
|
+
if (args3.includes("--test")) {
|
|
1108
|
+
console.warn(message);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
556
1111
|
function toUnique2(values) {
|
|
557
1112
|
const seen = /* @__PURE__ */ new Set();
|
|
558
1113
|
return values.filter((value) => {
|
|
@@ -570,7 +1125,7 @@ function resolveReasoningEffort3() {
|
|
|
570
1125
|
trace5("reasoning:custom", customReasoningEffort);
|
|
571
1126
|
return customReasoningEffort;
|
|
572
1127
|
}
|
|
573
|
-
|
|
1128
|
+
printNotice3(
|
|
574
1129
|
`\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS3.join(
|
|
575
1130
|
", "
|
|
576
1131
|
)}`
|
|
@@ -632,36 +1187,57 @@ function buildGeminiExecCommand(prompt, model) {
|
|
|
632
1187
|
const modelOption = model ? `--model ${shellQuote3(model)}` : "";
|
|
633
1188
|
return `gemini ${[modelOption, "-p", shellQuote3(prompt)].filter(Boolean).join(" ")}`;
|
|
634
1189
|
}
|
|
1190
|
+
function toGeminiFileReference(filePath) {
|
|
1191
|
+
return `@${filePath}`;
|
|
1192
|
+
}
|
|
1193
|
+
function buildGeminiFileReferenceSection(files) {
|
|
1194
|
+
const existingFiles = files.filter((file) => fs.existsSync(file.path));
|
|
1195
|
+
return {
|
|
1196
|
+
count: existingFiles.length,
|
|
1197
|
+
lines: existingFiles.map((file) => `- ${file.display}: ${toGeminiFileReference(file.path)}`)
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
635
1200
|
var createGeminiCommand = (tempDiffPath2, reviewFormPath2) => {
|
|
636
1201
|
trace5("createGeminiCommand:start", `tempDiffPath=${tempDiffPath2}, reviewFormPath=${reviewFormPath2}`);
|
|
637
1202
|
const customModel = getArgValue3("--model");
|
|
638
1203
|
const reasoningEffort = resolveReasoningEffort3();
|
|
639
1204
|
const primaryAlias = resolvePrimaryAlias2(reasoningEffort);
|
|
640
1205
|
const aliasFallbacks = toUnique2(getAliasFallbacks2(primaryAlias));
|
|
1206
|
+
const resolvedTempDiffPath = path.resolve(tempDiffPath2);
|
|
1207
|
+
const resolvedReviewFormPath = path.resolve(reviewFormPath2);
|
|
641
1208
|
const rules = [
|
|
642
1209
|
{ path: rulesPath, display: "\uB8F0\uC14B" },
|
|
643
1210
|
{ path: namingRulesPath, display: "\uB124\uC774\uBC0D \uADDC\uCE59" },
|
|
644
1211
|
{ path: codingConventionRulesPath, display: "\uCF54\uB529 \uCEE8\uBCA4\uC158" }
|
|
645
1212
|
];
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const
|
|
650
|
-
trace5("reviewForm:status",
|
|
1213
|
+
const ruleSection = buildGeminiFileReferenceSection(rules);
|
|
1214
|
+
trace5("rules:loaded", `count=${ruleSection.count}`);
|
|
1215
|
+
const reviewFormExists = fs.existsSync(resolvedReviewFormPath);
|
|
1216
|
+
const reviewFormLine = reviewFormExists ? `- \uB9AC\uBDF0 \uC591\uC2DD: ${toGeminiFileReference(resolvedReviewFormPath)}` : "- \uB9AC\uBDF0 \uC591\uC2DD: (\uC5C6\uC74C)";
|
|
1217
|
+
trace5("reviewForm:status", reviewFormExists ? "exists" : "missing");
|
|
651
1218
|
const reasoningInstruction = getReasoningInstruction(reasoningEffort);
|
|
652
|
-
const prompt = `\
|
|
653
|
-
\
|
|
1219
|
+
const prompt = `\uC544\uB798 \uD30C\uC77C\uB4E4\uC744 \uC21C\uC11C\uB300\uB85C \uC77D\uACE0 \uCF54\uB4DC \uB9AC\uBDF0\uB97C \uC9C4\uD589\uD574\uC918.
|
|
1220
|
+
\uADDC\uCE59 \uD30C\uC77C:
|
|
1221
|
+
${ruleSection.lines.join("\n") || "- (\uC5C6\uC74C)"}
|
|
1222
|
+
\uB9AC\uBDF0 \uC591\uC2DD \uD30C\uC77C:
|
|
1223
|
+
${reviewFormLine}
|
|
1224
|
+
\uB9AC\uBDF0 \uB300\uC0C1 diff \uD30C\uC77C:
|
|
1225
|
+
- \uB9AC\uBDF0 \uB300\uC0C1 diff: ${toGeminiFileReference(resolvedTempDiffPath)}
|
|
1226
|
+
|
|
1227
|
+
\uBC18\uB4DC\uC2DC \uB9AC\uBDF0 \uC591\uC2DD\uC5D0 \uB9DE\uCDB0 \uC791\uC131\uD574\uC918.
|
|
654
1228
|
\uCD94\uB860 \uAC15\uB3C4 \uC9C0\uCE68: ${reasoningInstruction}`;
|
|
1229
|
+
trace5("prompt:prepared", `length=${prompt.length}`);
|
|
655
1230
|
const modelCandidates = toUnique2(customModel ? [customModel, ...aliasFallbacks] : aliasFallbacks);
|
|
656
1231
|
trace5("model:candidates", modelCandidates.join(", "));
|
|
1232
|
+
trace5("command:candidates:count", String(modelCandidates.length + 1));
|
|
657
1233
|
if (customModel) {
|
|
658
|
-
|
|
1234
|
+
printNotice3(
|
|
659
1235
|
`\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
|
|
660
1236
|
" -> "
|
|
661
1237
|
)}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
|
|
662
1238
|
);
|
|
663
1239
|
} else {
|
|
664
|
-
|
|
1240
|
+
printNotice3(
|
|
665
1241
|
`\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.`
|
|
666
1242
|
);
|
|
667
1243
|
}
|
|
@@ -676,6 +1252,7 @@ var createGeminiCommand = (tempDiffPath2, reviewFormPath2) => {
|
|
|
676
1252
|
\uC0DD\uC131\uB420 \uBA85\uB839\uC5B4 \uBBF8\uB9AC\uBCF4\uAE30:
|
|
677
1253
|
${safeCommand}"`;
|
|
678
1254
|
}
|
|
1255
|
+
trace5("command:mode", "execute");
|
|
679
1256
|
trace5("createGeminiCommand:end");
|
|
680
1257
|
return command;
|
|
681
1258
|
};
|
|
@@ -686,21 +1263,23 @@ function checkGeminiCliInstalled() {
|
|
|
686
1263
|
trace6("version-check:run", "gemini --version");
|
|
687
1264
|
execSync("gemini --version", { stdio: "ignore" });
|
|
688
1265
|
trace6("version-check:ok");
|
|
689
|
-
} catch {
|
|
690
|
-
trace6("version-check:failed",
|
|
1266
|
+
} catch (error) {
|
|
1267
|
+
trace6("version-check:failed", getErrorSummary(error));
|
|
1268
|
+
trace6("install:start", "@google/gemini-cli");
|
|
691
1269
|
console.log("\u2139\uFE0F gemini-cli\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC124\uCE58\uB97C \uC9C4\uD589\uD569\uB2C8\uB2E4... npm install -g @google/gemini-cli");
|
|
692
1270
|
try {
|
|
693
1271
|
execSync("npm install -g @google/gemini-cli", { stdio: "inherit" });
|
|
694
|
-
trace6("install:ok", "
|
|
1272
|
+
trace6("install:ok", "login-required");
|
|
695
1273
|
console.log("\u2705 gemini-cli \uC124\uCE58\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
696
1274
|
console.log("\u26A0\uFE0F Gemini API \uC0AC\uC6A9\uC744 \uC704\uD574 \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
697
1275
|
console.log(' \uD130\uBBF8\uB110\uC5D0\uC11C "gemini" \uB97C \uC785\uB825\uD558\uC5EC \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC744 \uC644\uB8CC\uD55C \uD6C4, \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.');
|
|
698
1276
|
process.exit(1);
|
|
699
1277
|
} catch (installError) {
|
|
700
|
-
trace6("install:failed");
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1278
|
+
trace6("install:failed", getErrorSummary(installError));
|
|
1279
|
+
exitWithError("\u274C gemini-cli \uC124\uCE58 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4. \uAD8C\uD55C \uBB38\uC81C\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4 (sudo \uD544\uC694).", {
|
|
1280
|
+
scope: "installation-gemini",
|
|
1281
|
+
error: installError
|
|
1282
|
+
});
|
|
704
1283
|
}
|
|
705
1284
|
}
|
|
706
1285
|
trace6("checkGeminiCliInstalled:end");
|
|
@@ -710,76 +1289,92 @@ function checkGeminiCliInstalled() {
|
|
|
710
1289
|
var execAsync = util.promisify(exec);
|
|
711
1290
|
async function main() {
|
|
712
1291
|
const args4 = process.argv.slice(2);
|
|
1292
|
+
clearTraceMessages();
|
|
713
1293
|
const isTest = isTestMode(args4);
|
|
714
1294
|
const trace7 = createTraceLogger("review-one-by-one", args4);
|
|
715
1295
|
trace7("main:start", `args=${JSON.stringify(args4)}`);
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
case "gemini":
|
|
721
|
-
trace7("install-check:start", "service=gemini");
|
|
722
|
-
checkGeminiCliInstalled();
|
|
723
|
-
trace7("install-check:done", "service=gemini");
|
|
724
|
-
break;
|
|
725
|
-
case "claude":
|
|
726
|
-
trace7("install-check:start", "service=claude");
|
|
727
|
-
checkClaudeCliInstalled();
|
|
728
|
-
trace7("install-check:done", "service=claude");
|
|
729
|
-
break;
|
|
730
|
-
case "codex":
|
|
731
|
-
trace7("install-check:start", "service=codex");
|
|
732
|
-
checkCodexCliInstalled();
|
|
733
|
-
trace7("install-check:done", "service=codex");
|
|
734
|
-
break;
|
|
735
|
-
}
|
|
1296
|
+
let service = "";
|
|
1297
|
+
let savedDiffPath = "";
|
|
1298
|
+
let savedReportPath = "";
|
|
1299
|
+
let selectedCommitSummary = "";
|
|
736
1300
|
try {
|
|
1301
|
+
trace7("service-selection:start");
|
|
1302
|
+
service = await showSelectionAIService();
|
|
1303
|
+
trace7("service-selection:done", `service=${service}`);
|
|
1304
|
+
switch (service) {
|
|
1305
|
+
case "gemini":
|
|
1306
|
+
trace7("install-check:start", "service=gemini");
|
|
1307
|
+
checkGeminiCliInstalled();
|
|
1308
|
+
trace7("install-check:done", "service=gemini");
|
|
1309
|
+
break;
|
|
1310
|
+
case "claude":
|
|
1311
|
+
trace7("install-check:start", "service=claude");
|
|
1312
|
+
checkClaudeCliInstalled();
|
|
1313
|
+
trace7("install-check:done", "service=claude");
|
|
1314
|
+
break;
|
|
1315
|
+
case "codex":
|
|
1316
|
+
trace7("install-check:start", "service=codex");
|
|
1317
|
+
checkCodexCliInstalled();
|
|
1318
|
+
trace7("install-check:done", "service=codex");
|
|
1319
|
+
break;
|
|
1320
|
+
}
|
|
737
1321
|
trace7("review-flow:start");
|
|
738
1322
|
console.log("\u{1F680} AI Code Review\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4...");
|
|
739
|
-
trace7("
|
|
740
|
-
|
|
741
|
-
trace7("
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
1323
|
+
trace7("commit-selection:start");
|
|
1324
|
+
const selectedCommits = await selectReviewCommits();
|
|
1325
|
+
trace7("commit-selection:done", `count=${selectedCommits.length}`);
|
|
1326
|
+
if (selectedCommits.length === 0) {
|
|
1327
|
+
trace7("commit-selection:empty");
|
|
1328
|
+
console.log("\u2139\uFE0F \uC120\uD0DD\uB41C \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
1329
|
+
deleteTempDiff();
|
|
1330
|
+
process.exit(0);
|
|
1331
|
+
}
|
|
1332
|
+
selectedCommitSummary = buildSelectedCommitSummary(selectedCommits);
|
|
1333
|
+
trace7("commit-summary:prepared", selectedCommitSummary);
|
|
1334
|
+
console.log("\u23F3 \uC120\uD0DD\uD55C \uCEE4\uBC0B\uC758 \uD30C\uC77C \uBAA9\uB85D\uACFC diff\uB97C \uC815\uB9AC\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...");
|
|
1335
|
+
trace7("files-command:run");
|
|
1336
|
+
const fileList = getSelectedCommitFiles(selectedCommits);
|
|
749
1337
|
trace7("files-command:done", `fileCount=${fileList.length}`);
|
|
1338
|
+
console.log(`\u{1F4C2} \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C(${fileList.length}\uAC1C): ${formatReviewTargetFiles(fileList)}`);
|
|
750
1339
|
if (fileList.length === 0) {
|
|
751
1340
|
trace7("empty-file-list:exit");
|
|
752
|
-
console.log("\u2139\uFE0F \uBCC0\uACBD \
|
|
1341
|
+
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.");
|
|
753
1342
|
deleteTempDiff();
|
|
754
1343
|
process.exit(0);
|
|
755
1344
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1345
|
+
trace7("report-dir:create:start");
|
|
1346
|
+
createReportDirectory();
|
|
1347
|
+
trace7("report-dir:create:done");
|
|
1348
|
+
trace7("full-diff:build:start");
|
|
1349
|
+
const fullDiff = buildSelectedCommitDiff(selectedCommits);
|
|
759
1350
|
const nowStr = getNowString();
|
|
760
|
-
trace7("full-diff:done", `length=${fullDiff.length}`);
|
|
1351
|
+
trace7("full-diff:build:done", `length=${fullDiff.length}`);
|
|
761
1352
|
trace7("timestamp:created", nowStr);
|
|
762
1353
|
trace7("temp-diff:write:start", tempDiffPath);
|
|
763
|
-
|
|
1354
|
+
fs.writeFileSync(tempDiffPath, fullDiff);
|
|
764
1355
|
trace7("temp-diff:write:done");
|
|
765
1356
|
trace7("saved-diff:copy:start");
|
|
766
|
-
|
|
767
|
-
|
|
1357
|
+
savedDiffPath = getNextFilePath(REPORT_DIR, `${nowStr}-diff`, ".txt");
|
|
1358
|
+
fs.copyFileSync(tempDiffPath, savedDiffPath);
|
|
768
1359
|
trace7("saved-diff:copy:done", savedDiffPath);
|
|
769
|
-
|
|
1360
|
+
savedReportPath = getNextFilePath(REPORT_DIR, nowStr, ".md");
|
|
770
1361
|
trace7("saved-report:path", savedReportPath);
|
|
771
|
-
const promises = fileList.map(async (file) => {
|
|
1362
|
+
const promises = fileList.map(async (file, index) => {
|
|
1363
|
+
const tempOneFileDiffPath = `temp_diff_${file.replace(/\//g, "_")}.txt`;
|
|
1364
|
+
let command = "";
|
|
772
1365
|
try {
|
|
773
1366
|
trace7("file-review:start", file);
|
|
774
|
-
console.log(`\u{1F50D}
|
|
775
|
-
trace7("file-diff:
|
|
776
|
-
const fileDiff =
|
|
777
|
-
trace7("file-diff:done", `${file} | length=${fileDiff.length}`);
|
|
778
|
-
|
|
1367
|
+
console.log(`\u{1F50D} [${index + 1}/${fileList.length}] \uB9AC\uBDF0 \uC900\uBE44: ${file}`);
|
|
1368
|
+
trace7("file-diff:build:start", file);
|
|
1369
|
+
const fileDiff = buildSelectedFileDiff(selectedCommits, file);
|
|
1370
|
+
trace7("file-diff:build:done", `${file} | length=${fileDiff.length}`);
|
|
1371
|
+
if (!fileDiff.trim()) {
|
|
1372
|
+
trace7("file-diff:empty", file);
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
779
1375
|
trace7("file-temp-diff:write:start", tempOneFileDiffPath);
|
|
780
|
-
|
|
1376
|
+
fs.writeFileSync(tempOneFileDiffPath, fileDiff);
|
|
781
1377
|
trace7("file-temp-diff:write:done", tempOneFileDiffPath);
|
|
782
|
-
let command = "";
|
|
783
1378
|
trace7("file-command:create:start", file);
|
|
784
1379
|
switch (service) {
|
|
785
1380
|
case "gemini":
|
|
@@ -792,7 +1387,8 @@ async function main() {
|
|
|
792
1387
|
command = createCodexCommand(tempOneFileDiffPath, reviewFormOneByOnePath);
|
|
793
1388
|
break;
|
|
794
1389
|
}
|
|
795
|
-
trace7("file-command:create:done", file);
|
|
1390
|
+
trace7("file-command:create:done", `${file} | commandLength=${command.length}`);
|
|
1391
|
+
console.log(`\u23F3 [${index + 1}/${fileList.length}] \uB9AC\uBDF0 \uC9C4\uD589: ${file}`);
|
|
796
1392
|
trace7("file-command:exec:start", file);
|
|
797
1393
|
const { stdout } = await execAsync(command, { maxBuffer: 1024 * 1024 * 20 });
|
|
798
1394
|
const result = stdout.toString();
|
|
@@ -804,13 +1400,12 @@ async function main() {
|
|
|
804
1400
|
${result}
|
|
805
1401
|
|
|
806
1402
|
`;
|
|
807
|
-
console.log(tempReport);
|
|
808
1403
|
trace7("file-report:append:start", file);
|
|
809
|
-
|
|
1404
|
+
fs.appendFileSync(savedReportPath, tempReport);
|
|
810
1405
|
trace7("file-report:append:done", file);
|
|
811
1406
|
if (isTest) {
|
|
812
1407
|
trace7("file-test-command:append:start", file);
|
|
813
|
-
|
|
1408
|
+
fs.appendFileSync(savedReportPath, `
|
|
814
1409
|
|
|
815
1410
|
## \uC0AC\uC6A9\uB41C \uBA85\uB839\uC5B4
|
|
816
1411
|
|
|
@@ -819,26 +1414,52 @@ ${command}`);
|
|
|
819
1414
|
}
|
|
820
1415
|
trace7("file-review:end", file);
|
|
821
1416
|
} catch (err) {
|
|
822
|
-
trace7("file-review:catch", file);
|
|
1417
|
+
trace7("file-review:catch", `${file} | ${getErrorSummary(err)}`);
|
|
1418
|
+
const errorLogPath = writeErrorReport(err, {
|
|
1419
|
+
scope: "review-one-by-one:file",
|
|
1420
|
+
args: args4,
|
|
1421
|
+
extraSections: [
|
|
1422
|
+
{
|
|
1423
|
+
heading: "Execution Context",
|
|
1424
|
+
markdown: `\`\`\`json
|
|
1425
|
+
${JSON.stringify(
|
|
1426
|
+
{
|
|
1427
|
+
service,
|
|
1428
|
+
selectedCommitSummary: selectedCommitSummary || null,
|
|
1429
|
+
file,
|
|
1430
|
+
command: command || null,
|
|
1431
|
+
tempOneFileDiffPath,
|
|
1432
|
+
savedDiffPath: savedDiffPath || null,
|
|
1433
|
+
savedReportPath: savedReportPath || null
|
|
1434
|
+
},
|
|
1435
|
+
null,
|
|
1436
|
+
2
|
|
1437
|
+
)}
|
|
1438
|
+
\`\`\``
|
|
1439
|
+
}
|
|
1440
|
+
]
|
|
1441
|
+
});
|
|
823
1442
|
console.error(`\u274C Error reviewing file ${file}:`, err);
|
|
1443
|
+
if (errorLogPath) {
|
|
1444
|
+
console.error(`\u{1F4C4} \uC5D0\uB7EC \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${errorLogPath}`);
|
|
1445
|
+
}
|
|
824
1446
|
const errorReport = `### File: ${file}
|
|
825
1447
|
\u274C Review Failed
|
|
826
1448
|
\`\`\`
|
|
827
|
-
${err}
|
|
1449
|
+
${getErrorSummary(err)}
|
|
828
1450
|
\`\`\`
|
|
829
1451
|
|
|
830
1452
|
`;
|
|
831
|
-
|
|
1453
|
+
fs.appendFileSync(savedReportPath, errorReport);
|
|
832
1454
|
try {
|
|
833
|
-
|
|
834
|
-
if (fs5.existsSync(tempOneFileDiffPath)) {
|
|
1455
|
+
if (fs.existsSync(tempOneFileDiffPath)) {
|
|
835
1456
|
trace7("file-temp-diff:delete:start(catch)", tempOneFileDiffPath);
|
|
836
1457
|
deleteFile(tempOneFileDiffPath);
|
|
837
1458
|
trace7("file-temp-diff:delete:done(catch)", tempOneFileDiffPath);
|
|
838
1459
|
}
|
|
839
|
-
} catch (
|
|
840
|
-
trace7("file-temp-diff:delete:failed(catch)", file);
|
|
841
|
-
console.error(`\u274C Error deleting temp file for ${file}:`,
|
|
1460
|
+
} catch (cleanupError) {
|
|
1461
|
+
trace7("file-temp-diff:delete:failed(catch)", `${file} | ${getErrorSummary(cleanupError)}`);
|
|
1462
|
+
console.error(`\u274C Error deleting temp file for ${file}:`, cleanupError);
|
|
842
1463
|
}
|
|
843
1464
|
}
|
|
844
1465
|
});
|
|
@@ -857,12 +1478,45 @@ ${err}
|
|
|
857
1478
|
trace7("cleanup-temp-diff:done");
|
|
858
1479
|
trace7("review-flow:end");
|
|
859
1480
|
} catch (error) {
|
|
860
|
-
trace7("review-flow:catch");
|
|
1481
|
+
trace7("review-flow:catch", getErrorSummary(error));
|
|
1482
|
+
let errorReportPath = "";
|
|
1483
|
+
trace7("cleanup-temp-diff:start(catch)");
|
|
1484
|
+
try {
|
|
1485
|
+
deleteTempDiff();
|
|
1486
|
+
trace7("cleanup-temp-diff:done(catch)");
|
|
1487
|
+
} catch (cleanupError) {
|
|
1488
|
+
trace7("cleanup-temp-diff:failed(catch)", getErrorSummary(cleanupError));
|
|
1489
|
+
console.error("\u26A0\uFE0F \uC784\uC2DC diff \uC815\uB9AC \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
1490
|
+
console.error(cleanupError);
|
|
1491
|
+
}
|
|
1492
|
+
trace7("error-report:prepare", `service=${service || "unknown"}`);
|
|
1493
|
+
errorReportPath = writeErrorReport(error, {
|
|
1494
|
+
scope: "review-one-by-one",
|
|
1495
|
+
args: args4,
|
|
1496
|
+
extraSections: [
|
|
1497
|
+
{
|
|
1498
|
+
heading: "Execution Context",
|
|
1499
|
+
markdown: `\`\`\`json
|
|
1500
|
+
${JSON.stringify(
|
|
1501
|
+
{
|
|
1502
|
+
service: service || null,
|
|
1503
|
+
selectedCommitSummary: selectedCommitSummary || null,
|
|
1504
|
+
tempDiffPath,
|
|
1505
|
+
savedDiffPath: savedDiffPath || null,
|
|
1506
|
+
savedReportPath: savedReportPath || null
|
|
1507
|
+
},
|
|
1508
|
+
null,
|
|
1509
|
+
2
|
|
1510
|
+
)}
|
|
1511
|
+
\`\`\``
|
|
1512
|
+
}
|
|
1513
|
+
]
|
|
1514
|
+
});
|
|
861
1515
|
console.error("\u274C \uB9AC\uBDF0 \uB3C4\uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
862
1516
|
console.error(error);
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1517
|
+
if (errorReportPath) {
|
|
1518
|
+
console.error(`\u{1F4C4} \uC5D0\uB7EC \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${errorReportPath}`);
|
|
1519
|
+
}
|
|
866
1520
|
process.exit(1);
|
|
867
1521
|
}
|
|
868
1522
|
}
|