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
package/dist/pr-review/review.js
CHANGED
|
@@ -1,19 +1,59 @@
|
|
|
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';
|
|
7
|
+
import { inspect } from 'util';
|
|
7
8
|
|
|
8
9
|
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
-
var
|
|
12
|
-
|
|
13
|
-
path.
|
|
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
|
+
var reviewFormPath = resolvePackageAssetPath("src/common/form/review-form.md");
|
|
51
|
+
resolvePackageAssetPath("src/common/form/review-form-one-by-one.md");
|
|
14
52
|
var REPORT_DIR = ".review-report";
|
|
15
53
|
var tempDiffPath = "temp_diff.txt";
|
|
16
54
|
var AIServices = ["gemini", "claude", "codex"];
|
|
55
|
+
var COMMIT_FETCH_LIMIT = 20;
|
|
56
|
+
var COMMIT_SELECTION_WINDOW = 8;
|
|
17
57
|
var ignoreList = [
|
|
18
58
|
"package.json",
|
|
19
59
|
"*.yml",
|
|
@@ -28,33 +68,276 @@ var ignoreList = [
|
|
|
28
68
|
".review-report/"
|
|
29
69
|
// 생성되는 리포트 폴더도 제외
|
|
30
70
|
];
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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) {
|
|
35
154
|
return "";
|
|
36
155
|
}
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
156
|
+
const tokens = tokenizeVisibleText(value);
|
|
157
|
+
const totalWidth = tokens.reduce((sum, token) => sum + token.visibleWidth, 0);
|
|
158
|
+
if (totalWidth <= maxWidth) {
|
|
159
|
+
return value;
|
|
40
160
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
);
|
|
44
|
-
|
|
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}`;
|
|
45
178
|
}
|
|
46
|
-
function
|
|
47
|
-
|
|
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`;
|
|
48
258
|
}
|
|
49
259
|
function createTraceLogger(scope, args4 = process.argv.slice(2)) {
|
|
50
260
|
const enabled = isTestMode(args4);
|
|
51
261
|
return (step, detail) => {
|
|
262
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
263
|
+
const message = `[${timestamp}][TRACE][${scope}] ${step}${detail ? ` | ${detail}` : ""}`;
|
|
264
|
+
traceMessages.push(message);
|
|
52
265
|
if (!enabled) {
|
|
53
266
|
return;
|
|
54
267
|
}
|
|
55
|
-
console.log(
|
|
268
|
+
console.log(message);
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
var helperTrace = createTraceLogger("helper");
|
|
272
|
+
function getTimestampParts(now = /* @__PURE__ */ new Date()) {
|
|
273
|
+
return {
|
|
274
|
+
YYYY: now.getFullYear(),
|
|
275
|
+
MM: String(now.getMonth() + 1).padStart(2, "0"),
|
|
276
|
+
DD: String(now.getDate()).padStart(2, "0"),
|
|
277
|
+
HH: String(now.getHours()).padStart(2, "0"),
|
|
278
|
+
mm: String(now.getMinutes()).padStart(2, "0"),
|
|
279
|
+
ss: String(now.getSeconds()).padStart(2, "0")
|
|
56
280
|
};
|
|
57
281
|
}
|
|
282
|
+
function getHumanReadableNowString(now = /* @__PURE__ */ new Date()) {
|
|
283
|
+
const { YYYY, MM, DD, HH, mm, ss } = getTimestampParts(now);
|
|
284
|
+
return `${YYYY}-${MM}-${DD} ${HH}:${mm}:${ss}`;
|
|
285
|
+
}
|
|
286
|
+
function stringifyUnknown(value) {
|
|
287
|
+
if (value === void 0 || value === null) {
|
|
288
|
+
return "";
|
|
289
|
+
}
|
|
290
|
+
if (typeof value === "string") {
|
|
291
|
+
return value;
|
|
292
|
+
}
|
|
293
|
+
if (Buffer.isBuffer(value)) {
|
|
294
|
+
return value.toString();
|
|
295
|
+
}
|
|
296
|
+
if (value instanceof Error) {
|
|
297
|
+
return value.stack || value.message;
|
|
298
|
+
}
|
|
299
|
+
return inspect(value, { depth: 5, breakLength: 120 });
|
|
300
|
+
}
|
|
301
|
+
function getErrorSummary(error) {
|
|
302
|
+
if (error instanceof Error) {
|
|
303
|
+
return `${error.name}: ${error.message}`;
|
|
304
|
+
}
|
|
305
|
+
return stringifyUnknown(error) || "Unknown error";
|
|
306
|
+
}
|
|
307
|
+
function serializeError(error) {
|
|
308
|
+
const serialized = {
|
|
309
|
+
summary: getErrorSummary(error)
|
|
310
|
+
};
|
|
311
|
+
if (error instanceof Error) {
|
|
312
|
+
serialized.name = error.name;
|
|
313
|
+
serialized.message = error.message;
|
|
314
|
+
serialized.stack = error.stack;
|
|
315
|
+
} else {
|
|
316
|
+
serialized.value = stringifyUnknown(error);
|
|
317
|
+
}
|
|
318
|
+
if (error && typeof error === "object") {
|
|
319
|
+
const errorLike = error;
|
|
320
|
+
const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs"];
|
|
321
|
+
extraKeys.forEach((key) => {
|
|
322
|
+
if (errorLike[key] !== void 0) {
|
|
323
|
+
serialized[key] = errorLike[key];
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
const stdout = stringifyUnknown(errorLike.stdout);
|
|
327
|
+
if (stdout) {
|
|
328
|
+
serialized.stdout = stdout;
|
|
329
|
+
}
|
|
330
|
+
const stderr = stringifyUnknown(errorLike.stderr);
|
|
331
|
+
if (stderr) {
|
|
332
|
+
serialized.stderr = stderr;
|
|
333
|
+
}
|
|
334
|
+
const cause = stringifyUnknown(errorLike.cause);
|
|
335
|
+
if (cause) {
|
|
336
|
+
serialized.cause = cause;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return serialized;
|
|
340
|
+
}
|
|
58
341
|
function getNextFilePath(dir, baseName, extension) {
|
|
59
342
|
let counter = 1;
|
|
60
343
|
while (true) {
|
|
@@ -65,6 +348,13 @@ function getNextFilePath(dir, baseName, extension) {
|
|
|
65
348
|
counter++;
|
|
66
349
|
}
|
|
67
350
|
}
|
|
351
|
+
function getAvailableFilePath(dir, baseName, extension) {
|
|
352
|
+
const firstFilePath = path.join(dir, `${baseName}${extension}`);
|
|
353
|
+
if (!fs.existsSync(firstFilePath)) {
|
|
354
|
+
return firstFilePath;
|
|
355
|
+
}
|
|
356
|
+
return getNextFilePath(dir, baseName, extension);
|
|
357
|
+
}
|
|
68
358
|
function deleteFile(filePath) {
|
|
69
359
|
if (fs.existsSync(filePath)) {
|
|
70
360
|
fs.unlinkSync(filePath);
|
|
@@ -78,27 +368,171 @@ function createReportDirectory() {
|
|
|
78
368
|
fs.mkdirSync(REPORT_DIR, { recursive: true });
|
|
79
369
|
}
|
|
80
370
|
}
|
|
81
|
-
function getNowString() {
|
|
82
|
-
const
|
|
83
|
-
const YYYY = now.getFullYear();
|
|
84
|
-
const MM = String(now.getMonth() + 1).padStart(2, "0");
|
|
85
|
-
const DD = String(now.getDate()).padStart(2, "0");
|
|
86
|
-
const HH = String(now.getHours()).padStart(2, "0");
|
|
87
|
-
const mm = String(now.getMinutes()).padStart(2, "0");
|
|
88
|
-
const ss = String(now.getSeconds()).padStart(2, "0");
|
|
371
|
+
function getNowString(now = /* @__PURE__ */ new Date()) {
|
|
372
|
+
const { YYYY, MM, DD, HH, mm, ss } = getTimestampParts(now);
|
|
89
373
|
return `${YYYY}-${MM}-${DD}_${HH}-${mm}-${ss}`;
|
|
90
374
|
}
|
|
91
|
-
function
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
375
|
+
function getErrorLogTimestamp(now = /* @__PURE__ */ new Date()) {
|
|
376
|
+
const { YYYY, MM, DD, HH, mm, ss } = getTimestampParts(now);
|
|
377
|
+
return `${YYYY}-${MM}-${DD}-${HH}\uC2DC-${mm}\uBD84-${ss}\uCD08`;
|
|
378
|
+
}
|
|
379
|
+
function writeErrorReport(error, options = {}) {
|
|
380
|
+
try {
|
|
381
|
+
const now = /* @__PURE__ */ new Date();
|
|
382
|
+
helperTrace("error-report:write:start", options.scope || "unknown");
|
|
383
|
+
createReportDirectory();
|
|
384
|
+
const reportPath = getAvailableFilePath(REPORT_DIR, `error-log-${getErrorLogTimestamp(now)}`, ".md");
|
|
385
|
+
const serializedError = serializeError(error);
|
|
386
|
+
const traceSnapshot = options.traceMessages ?? getTraceMessages();
|
|
387
|
+
const extraSections = options.extraSections || [];
|
|
388
|
+
const report = `# Error Log
|
|
389
|
+
|
|
390
|
+
- \uBC1C\uC0DD \uC2DC\uAC01: ${getHumanReadableNowString(now)}
|
|
391
|
+
- Scope: \`${options.scope || "unknown"}\`
|
|
392
|
+
- \uC791\uC5C5 \uACBD\uB85C: \`${process.cwd()}\`
|
|
393
|
+
- \uC2E4\uD589 \uC778\uC790: \`${JSON.stringify(options.args ?? process.argv.slice(2))}\`
|
|
394
|
+
- \uC2E4\uD589 \uD658\uACBD: \`${process.platform} ${process.arch} / Node ${process.version}\`
|
|
395
|
+
|
|
396
|
+
## Summary
|
|
397
|
+
|
|
398
|
+
${options.title || serializedError.summary || "Unknown error"}
|
|
399
|
+
|
|
400
|
+
## Error
|
|
401
|
+
|
|
402
|
+
\`\`\`json
|
|
403
|
+
${JSON.stringify(serializedError, null, 2)}
|
|
404
|
+
\`\`\`
|
|
405
|
+
|
|
406
|
+
## Trace
|
|
407
|
+
|
|
408
|
+
\`\`\`json
|
|
409
|
+
${JSON.stringify(traceSnapshot, null, 2)}
|
|
410
|
+
\`\`\`${extraSections.length ? `
|
|
411
|
+
${extraSections.map((section) => `
|
|
412
|
+
## ${section.heading}
|
|
413
|
+
|
|
414
|
+
${section.markdown}`).join("\n")}
|
|
415
|
+
` : "\n"}
|
|
416
|
+
`;
|
|
417
|
+
fs.writeFileSync(reportPath, report);
|
|
418
|
+
helperTrace("error-report:write:done", reportPath);
|
|
419
|
+
return reportPath;
|
|
420
|
+
} catch (writeError) {
|
|
421
|
+
console.error("\u26A0\uFE0F \uC5D0\uB7EC \uB85C\uADF8 \uD30C\uC77C \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
|
|
422
|
+
console.error(writeError);
|
|
423
|
+
return "";
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function exitWithError(message, options = {}) {
|
|
427
|
+
const reportPath = writeErrorReport(options.error || new Error(message), {
|
|
428
|
+
...options,
|
|
429
|
+
title: message
|
|
430
|
+
});
|
|
431
|
+
console.error(message);
|
|
432
|
+
if (options.error) {
|
|
433
|
+
console.error(options.error);
|
|
434
|
+
}
|
|
435
|
+
if (reportPath) {
|
|
436
|
+
console.error(`\u{1F4C4} \uC5D0\uB7EC \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${reportPath}`);
|
|
437
|
+
}
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
440
|
+
function parseServiceFromArgs(args4 = process.argv.slice(2)) {
|
|
441
|
+
helperTrace("parse-service:start", `args=${JSON.stringify(args4)}`);
|
|
442
|
+
const serviceIndex = args4.indexOf("--service");
|
|
443
|
+
const rawService = serviceIndex !== -1 ? args4[serviceIndex + 1] : "";
|
|
444
|
+
if (!rawService) {
|
|
445
|
+
helperTrace("parse-service:empty");
|
|
446
|
+
return "";
|
|
447
|
+
}
|
|
448
|
+
const normalizedService = rawService.toLowerCase();
|
|
449
|
+
if (AIServices.includes(normalizedService)) {
|
|
450
|
+
helperTrace("parse-service:resolved", normalizedService);
|
|
451
|
+
return normalizedService;
|
|
452
|
+
}
|
|
453
|
+
helperTrace("parse-service:invalid", rawService);
|
|
454
|
+
exitWithError(
|
|
455
|
+
`\u274C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC11C\uBE44\uC2A4\uC785\uB2C8\uB2E4: ${rawService}. \uC0AC\uC6A9 \uAC00\uB2A5 \uAC12: ${AIServices.join(", ")} (\uC608: --service codex)`,
|
|
456
|
+
{
|
|
457
|
+
scope: "helper:parseServiceFromArgs",
|
|
458
|
+
args: args4,
|
|
459
|
+
extraSections: [
|
|
460
|
+
{
|
|
461
|
+
heading: "Allowed Services",
|
|
462
|
+
markdown: `\`\`\`json
|
|
463
|
+
${JSON.stringify(AIServices, null, 2)}
|
|
464
|
+
\`\`\``
|
|
465
|
+
}
|
|
466
|
+
]
|
|
467
|
+
}
|
|
468
|
+
);
|
|
469
|
+
}
|
|
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];
|
|
98
531
|
}
|
|
99
532
|
function openReport(reportPath) {
|
|
100
533
|
const resolvedPath = path.resolve(reportPath);
|
|
101
534
|
const { platform } = process;
|
|
535
|
+
helperTrace("open-report:start", resolvedPath);
|
|
102
536
|
const openWithChrome = () => {
|
|
103
537
|
if (platform === "darwin") {
|
|
104
538
|
execSync(`open -a "Google Chrome" "${resolvedPath}"`, { stdio: "ignore" });
|
|
@@ -123,63 +557,188 @@ function openReport(reportPath) {
|
|
|
123
557
|
};
|
|
124
558
|
try {
|
|
125
559
|
if (openWithChrome()) {
|
|
560
|
+
helperTrace("open-report:chrome:success", platform);
|
|
126
561
|
console.log("\u{1F680} Google Chrome\uC5D0\uC11C \uB9AC\uD3EC\uD2B8\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
127
562
|
return;
|
|
128
563
|
}
|
|
129
|
-
} catch {
|
|
564
|
+
} catch (error) {
|
|
565
|
+
helperTrace("open-report:chrome:failed", getErrorSummary(error));
|
|
130
566
|
}
|
|
131
567
|
try {
|
|
132
568
|
if (openWithDefaultBrowser()) {
|
|
569
|
+
helperTrace("open-report:default-browser:success", platform);
|
|
133
570
|
console.log("\u{1F680} \uAE30\uBCF8 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB9AC\uD3EC\uD2B8\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
134
571
|
return;
|
|
135
572
|
}
|
|
136
|
-
} catch (
|
|
137
|
-
|
|
573
|
+
} catch (error) {
|
|
574
|
+
helperTrace("open-report:default-browser:failed", getErrorSummary(error));
|
|
575
|
+
console.error("\u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800 \uC5F4\uAE30 \uC2E4\uD328:", error);
|
|
138
576
|
return;
|
|
139
577
|
}
|
|
578
|
+
helperTrace("open-report:unsupported-platform", platform);
|
|
140
579
|
console.error(`\u26A0\uFE0F \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD50C\uB7AB\uD3FC\uC785\uB2C8\uB2E4: ${platform}`);
|
|
141
580
|
}
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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;
|
|
152
626
|
}
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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;
|
|
160
653
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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);
|
|
169
682
|
}
|
|
170
|
-
|
|
171
|
-
|
|
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 [];
|
|
172
720
|
}
|
|
173
|
-
return
|
|
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
|
+
);
|
|
174
730
|
}
|
|
175
731
|
async function showSelectionAIService() {
|
|
176
732
|
const selectedServiceFromArgs = parseServiceFromArgs();
|
|
177
733
|
if (selectedServiceFromArgs) {
|
|
734
|
+
helperTrace("show-selection:from-args", selectedServiceFromArgs);
|
|
178
735
|
console.log(`
|
|
179
|
-
\u2705
|
|
736
|
+
\u2705 ${ANSI.green}${selectedServiceFromArgs}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
|
|
180
737
|
`);
|
|
181
738
|
return selectedServiceFromArgs;
|
|
182
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.");
|
|
741
|
+
helperTrace("show-selection:interactive:start");
|
|
183
742
|
let selectedIndex = 0;
|
|
184
743
|
const rl = readline.createInterface({
|
|
185
744
|
input: process.stdin,
|
|
@@ -193,13 +752,15 @@ async function showSelectionAIService() {
|
|
|
193
752
|
readline.moveCursor(process.stdout, 0, -(AIServices.length + 1));
|
|
194
753
|
}
|
|
195
754
|
firstRender = false;
|
|
755
|
+
helperTrace("show-selection:interactive:render", AIServices[selectedIndex] || "unknown");
|
|
196
756
|
readline.clearScreenDown(process.stdout);
|
|
197
757
|
process.stdout.write(
|
|
198
|
-
|
|
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
|
+
`
|
|
199
760
|
);
|
|
200
761
|
AIServices.forEach((service, index) => {
|
|
201
762
|
if (index === selectedIndex) {
|
|
202
|
-
process.stdout.write(`
|
|
763
|
+
process.stdout.write(` ${ANSI.cyan}>${ANSI.reset} ${ANSI.cyan}\u25C9${ANSI.reset} ${ANSI.bold}${service}${ANSI.reset}
|
|
203
764
|
`);
|
|
204
765
|
} else {
|
|
205
766
|
process.stdout.write(` \u25EF ${service}
|
|
@@ -212,6 +773,7 @@ async function showSelectionAIService() {
|
|
|
212
773
|
const onData = (data) => {
|
|
213
774
|
const key = data.toString();
|
|
214
775
|
if (key === "") {
|
|
776
|
+
helperTrace("show-selection:interactive:ctrl-c");
|
|
215
777
|
process.stdout.write("\x1B[?25h");
|
|
216
778
|
process.exit(0);
|
|
217
779
|
}
|
|
@@ -228,10 +790,11 @@ async function showSelectionAIService() {
|
|
|
228
790
|
rl.close();
|
|
229
791
|
process.stdout.write("\x1B[?25h");
|
|
230
792
|
console.log(`
|
|
231
|
-
\u2705
|
|
793
|
+
\u2705 ${ANSI.green}${AIServices[selectedIndex]}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
|
|
232
794
|
`);
|
|
233
795
|
const result = AIServices[selectedIndex];
|
|
234
796
|
if (result) {
|
|
797
|
+
helperTrace("show-selection:interactive:confirmed", result);
|
|
235
798
|
resolve(result);
|
|
236
799
|
}
|
|
237
800
|
}
|
|
@@ -254,6 +817,11 @@ function getArgValue(flag) {
|
|
|
254
817
|
}
|
|
255
818
|
return args[index + 1];
|
|
256
819
|
}
|
|
820
|
+
function printNotice(message) {
|
|
821
|
+
if (args.includes("--test")) {
|
|
822
|
+
console.warn(message);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
257
825
|
function toUnique(values) {
|
|
258
826
|
const seen = /* @__PURE__ */ new Set();
|
|
259
827
|
return values.filter((value) => {
|
|
@@ -277,11 +845,11 @@ function resolveReasoningEffort() {
|
|
|
277
845
|
const normalized = normalizeEffort(customReasoningEffort);
|
|
278
846
|
trace("reasoning:custom", `${customReasoningEffort} -> ${normalized}`);
|
|
279
847
|
if (customReasoningEffort === "minimal") {
|
|
280
|
-
|
|
848
|
+
printNotice("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
|
|
281
849
|
}
|
|
282
850
|
return normalized;
|
|
283
851
|
}
|
|
284
|
-
|
|
852
|
+
printNotice(
|
|
285
853
|
`\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS.join(
|
|
286
854
|
", "
|
|
287
855
|
)}`
|
|
@@ -324,7 +892,7 @@ function buildClaudeExecCommand(options) {
|
|
|
324
892
|
const modelOption = model ? `--model ${shellQuote(model)}` : "";
|
|
325
893
|
const fallbackOption = model && fallbackModel ? `--fallback-model ${shellQuote(fallbackModel)}` : "";
|
|
326
894
|
const effortOption = `--effort ${shellQuote(effort)}`;
|
|
327
|
-
const appendedPromptFiles = systemPromptFiles.map((
|
|
895
|
+
const appendedPromptFiles = systemPromptFiles.map((path3) => `--append-system-prompt-file ${shellQuote(path3)}`).join(" ");
|
|
328
896
|
return `cat ${shellQuote(tempDiffPath2)} | claude ${[
|
|
329
897
|
modelOption,
|
|
330
898
|
fallbackOption,
|
|
@@ -351,16 +919,19 @@ var createClaudeCommand = (tempDiffPath2, reviewFormPath2) => {
|
|
|
351
919
|
trace("reviewForm:status", reviewFormExists ? "exists" : "missing");
|
|
352
920
|
const systemPromptFiles = reviewFormExists ? [...existingRuleFiles, reviewFormPath2] : existingRuleFiles;
|
|
353
921
|
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.";
|
|
922
|
+
trace("prompt:prepared", `length=${prompt.length}`);
|
|
923
|
+
trace("system-prompt-files", `count=${systemPromptFiles.length}`);
|
|
354
924
|
const modelCandidates = toUnique(customModel ? [customModel, ...aliasFallbacks] : aliasFallbacks);
|
|
355
925
|
trace("model:candidates", modelCandidates.join(", "));
|
|
926
|
+
trace("command:candidates:count", String(modelCandidates.length + 1));
|
|
356
927
|
if (customModel) {
|
|
357
|
-
|
|
928
|
+
printNotice(
|
|
358
929
|
`\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
|
|
359
930
|
" -> "
|
|
360
931
|
)}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
|
|
361
932
|
);
|
|
362
933
|
} else {
|
|
363
|
-
|
|
934
|
+
printNotice(
|
|
364
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.`
|
|
365
936
|
);
|
|
366
937
|
}
|
|
@@ -393,6 +964,7 @@ var createClaudeCommand = (tempDiffPath2, reviewFormPath2) => {
|
|
|
393
964
|
\uC0DD\uC131\uB420 \uBA85\uB839\uC5B4 \uBBF8\uB9AC\uBCF4\uAE30:
|
|
394
965
|
${safeCommand}"`;
|
|
395
966
|
}
|
|
967
|
+
trace("command:mode", "execute");
|
|
396
968
|
trace("createClaudeCommand:end");
|
|
397
969
|
return command;
|
|
398
970
|
};
|
|
@@ -403,23 +975,25 @@ function checkClaudeCliInstalled() {
|
|
|
403
975
|
trace2("version-check:run", "claude --version");
|
|
404
976
|
execSync("claude --version", { stdio: "ignore" });
|
|
405
977
|
trace2("version-check:ok");
|
|
406
|
-
} catch {
|
|
407
|
-
trace2("version-check:failed",
|
|
978
|
+
} catch (error) {
|
|
979
|
+
trace2("version-check:failed", getErrorSummary(error));
|
|
980
|
+
trace2("install:start", "@anthropic-ai/claude-code");
|
|
408
981
|
console.log(
|
|
409
982
|
"\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"
|
|
410
983
|
);
|
|
411
984
|
try {
|
|
412
985
|
execSync("npm install -g @anthropic-ai/claude-code", { stdio: "inherit" });
|
|
413
|
-
trace2("install:ok", "
|
|
986
|
+
trace2("install:ok", "login-required");
|
|
414
987
|
console.log("\u2705 claude-cli \uC124\uCE58\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
415
988
|
console.log("\u26A0\uFE0F claude-cli \uC0AC\uC6A9\uC744 \uC704\uD574 \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
416
989
|
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.');
|
|
417
990
|
process.exit(1);
|
|
418
991
|
} catch (installError) {
|
|
419
|
-
trace2("install:failed");
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
992
|
+
trace2("install:failed", getErrorSummary(installError));
|
|
993
|
+
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).", {
|
|
994
|
+
scope: "installation-claude",
|
|
995
|
+
error: installError
|
|
996
|
+
});
|
|
423
997
|
}
|
|
424
998
|
}
|
|
425
999
|
trace2("checkClaudeCliInstalled:end");
|
|
@@ -437,6 +1011,11 @@ function getArgValue2(flag) {
|
|
|
437
1011
|
}
|
|
438
1012
|
return args2[index + 1];
|
|
439
1013
|
}
|
|
1014
|
+
function printNotice2(message) {
|
|
1015
|
+
if (args2.includes("--test")) {
|
|
1016
|
+
console.warn(message);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
440
1019
|
function resolveReasoningEffort2() {
|
|
441
1020
|
const customReasoningEffort = getArgValue2("--reasoning-effort");
|
|
442
1021
|
if (customReasoningEffort) {
|
|
@@ -444,7 +1023,7 @@ function resolveReasoningEffort2() {
|
|
|
444
1023
|
trace3("reasoning:custom", customReasoningEffort);
|
|
445
1024
|
return customReasoningEffort;
|
|
446
1025
|
}
|
|
447
|
-
|
|
1026
|
+
printNotice2(
|
|
448
1027
|
`\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS2.join(
|
|
449
1028
|
", "
|
|
450
1029
|
)}`
|
|
@@ -485,16 +1064,17 @@ ${reviewFormLine || "- (\uC5C6\uC74C)"}
|
|
|
485
1064
|
- ${tempDiffPath2}
|
|
486
1065
|
|
|
487
1066
|
\uBC18\uB4DC\uC2DC \uB9AC\uBDF0 \uC591\uC2DD\uC5D0 \uB9DE\uCDB0 \uC791\uC131\uD574\uC918.`;
|
|
1067
|
+
trace3("prompt:prepared", `length=${prompt.length}`);
|
|
488
1068
|
let command = "";
|
|
489
1069
|
if (customModel) {
|
|
490
|
-
|
|
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.");
|
|
491
1071
|
trace3("model:custom", customModel);
|
|
492
1072
|
command = buildCodexExecCommand(prompt, reasoningEffort, customModel);
|
|
493
1073
|
} else {
|
|
494
1074
|
const preferredModelAlias = "gpt-5";
|
|
495
1075
|
const aliasCommand = buildCodexExecCommand(prompt, reasoningEffort, preferredModelAlias);
|
|
496
1076
|
const fallbackCommand = buildCodexExecCommand(prompt, reasoningEffort);
|
|
497
|
-
|
|
1077
|
+
printNotice2(
|
|
498
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.`
|
|
499
1079
|
);
|
|
500
1080
|
trace3("model:alias-first", preferredModelAlias);
|
|
@@ -510,6 +1090,7 @@ ${reviewFormLine || "- (\uC5C6\uC74C)"}
|
|
|
510
1090
|
\uC0DD\uC131\uB420 \uBA85\uB839\uC5B4 \uBBF8\uB9AC\uBCF4\uAE30:
|
|
511
1091
|
${safeCommand}"`;
|
|
512
1092
|
}
|
|
1093
|
+
trace3("command:mode", "execute");
|
|
513
1094
|
trace3("createCodexCommand:end");
|
|
514
1095
|
return command;
|
|
515
1096
|
};
|
|
@@ -520,21 +1101,23 @@ function checkCodexCliInstalled() {
|
|
|
520
1101
|
trace4("version-check:run", "codex --version");
|
|
521
1102
|
execSync("codex --version", { stdio: "ignore" });
|
|
522
1103
|
trace4("version-check:ok");
|
|
523
|
-
} catch {
|
|
524
|
-
trace4("version-check:failed",
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
trace4("version-check:failed", getErrorSummary(error));
|
|
1106
|
+
trace4("install:start", "@openai/codex");
|
|
525
1107
|
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");
|
|
526
1108
|
try {
|
|
527
1109
|
execSync("npm install -g @openai/codex", { stdio: "inherit" });
|
|
528
|
-
trace4("install:ok", "
|
|
1110
|
+
trace4("install:ok", "login-required");
|
|
529
1111
|
console.log("\u2705 codex-cli \uC124\uCE58\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
530
1112
|
console.log("\u26A0\uFE0F codex-cli \uC0AC\uC6A9\uC744 \uC704\uD574 \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
531
1113
|
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.');
|
|
532
1114
|
process.exit(1);
|
|
533
1115
|
} catch (installError) {
|
|
534
|
-
trace4("install:failed");
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
1116
|
+
trace4("install:failed", getErrorSummary(installError));
|
|
1117
|
+
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).", {
|
|
1118
|
+
scope: "installation-codex",
|
|
1119
|
+
error: installError
|
|
1120
|
+
});
|
|
538
1121
|
}
|
|
539
1122
|
}
|
|
540
1123
|
trace4("checkCodexCliInstalled:end");
|
|
@@ -552,6 +1135,11 @@ function getArgValue3(flag) {
|
|
|
552
1135
|
}
|
|
553
1136
|
return args3[index + 1];
|
|
554
1137
|
}
|
|
1138
|
+
function printNotice3(message) {
|
|
1139
|
+
if (args3.includes("--test")) {
|
|
1140
|
+
console.warn(message);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
555
1143
|
function toUnique2(values) {
|
|
556
1144
|
const seen = /* @__PURE__ */ new Set();
|
|
557
1145
|
return values.filter((value) => {
|
|
@@ -569,7 +1157,7 @@ function resolveReasoningEffort3() {
|
|
|
569
1157
|
trace5("reasoning:custom", customReasoningEffort);
|
|
570
1158
|
return customReasoningEffort;
|
|
571
1159
|
}
|
|
572
|
-
|
|
1160
|
+
printNotice3(
|
|
573
1161
|
`\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS3.join(
|
|
574
1162
|
", "
|
|
575
1163
|
)}`
|
|
@@ -631,36 +1219,57 @@ function buildGeminiExecCommand(prompt, model) {
|
|
|
631
1219
|
const modelOption = model ? `--model ${shellQuote3(model)}` : "";
|
|
632
1220
|
return `gemini ${[modelOption, "-p", shellQuote3(prompt)].filter(Boolean).join(" ")}`;
|
|
633
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
|
+
}
|
|
634
1232
|
var createGeminiCommand = (tempDiffPath2, reviewFormPath2) => {
|
|
635
1233
|
trace5("createGeminiCommand:start", `tempDiffPath=${tempDiffPath2}, reviewFormPath=${reviewFormPath2}`);
|
|
636
1234
|
const customModel = getArgValue3("--model");
|
|
637
1235
|
const reasoningEffort = resolveReasoningEffort3();
|
|
638
1236
|
const primaryAlias = resolvePrimaryAlias2(reasoningEffort);
|
|
639
1237
|
const aliasFallbacks = toUnique2(getAliasFallbacks2(primaryAlias));
|
|
1238
|
+
const resolvedTempDiffPath = path.resolve(tempDiffPath2);
|
|
1239
|
+
const resolvedReviewFormPath = path.resolve(reviewFormPath2);
|
|
640
1240
|
const rules = [
|
|
641
1241
|
{ path: rulesPath, display: "\uB8F0\uC14B" },
|
|
642
1242
|
{ path: namingRulesPath, display: "\uB124\uC774\uBC0D \uADDC\uCE59" },
|
|
643
1243
|
{ path: codingConventionRulesPath, display: "\uCF54\uB529 \uCEE8\uBCA4\uC158" }
|
|
644
1244
|
];
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
const
|
|
649
|
-
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");
|
|
650
1250
|
const reasoningInstruction = getReasoningInstruction(reasoningEffort);
|
|
651
|
-
const prompt = `\
|
|
652
|
-
\
|
|
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.
|
|
653
1260
|
\uCD94\uB860 \uAC15\uB3C4 \uC9C0\uCE68: ${reasoningInstruction}`;
|
|
1261
|
+
trace5("prompt:prepared", `length=${prompt.length}`);
|
|
654
1262
|
const modelCandidates = toUnique2(customModel ? [customModel, ...aliasFallbacks] : aliasFallbacks);
|
|
655
1263
|
trace5("model:candidates", modelCandidates.join(", "));
|
|
1264
|
+
trace5("command:candidates:count", String(modelCandidates.length + 1));
|
|
656
1265
|
if (customModel) {
|
|
657
|
-
|
|
1266
|
+
printNotice3(
|
|
658
1267
|
`\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
|
|
659
1268
|
" -> "
|
|
660
1269
|
)}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
|
|
661
1270
|
);
|
|
662
1271
|
} else {
|
|
663
|
-
|
|
1272
|
+
printNotice3(
|
|
664
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.`
|
|
665
1274
|
);
|
|
666
1275
|
}
|
|
@@ -675,6 +1284,7 @@ var createGeminiCommand = (tempDiffPath2, reviewFormPath2) => {
|
|
|
675
1284
|
\uC0DD\uC131\uB420 \uBA85\uB839\uC5B4 \uBBF8\uB9AC\uBCF4\uAE30:
|
|
676
1285
|
${safeCommand}"`;
|
|
677
1286
|
}
|
|
1287
|
+
trace5("command:mode", "execute");
|
|
678
1288
|
trace5("createGeminiCommand:end");
|
|
679
1289
|
return command;
|
|
680
1290
|
};
|
|
@@ -685,21 +1295,23 @@ function checkGeminiCliInstalled() {
|
|
|
685
1295
|
trace6("version-check:run", "gemini --version");
|
|
686
1296
|
execSync("gemini --version", { stdio: "ignore" });
|
|
687
1297
|
trace6("version-check:ok");
|
|
688
|
-
} catch {
|
|
689
|
-
trace6("version-check:failed",
|
|
1298
|
+
} catch (error) {
|
|
1299
|
+
trace6("version-check:failed", getErrorSummary(error));
|
|
1300
|
+
trace6("install:start", "@google/gemini-cli");
|
|
690
1301
|
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");
|
|
691
1302
|
try {
|
|
692
1303
|
execSync("npm install -g @google/gemini-cli", { stdio: "inherit" });
|
|
693
|
-
trace6("install:ok", "
|
|
1304
|
+
trace6("install:ok", "login-required");
|
|
694
1305
|
console.log("\u2705 gemini-cli \uC124\uCE58\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
695
1306
|
console.log("\u26A0\uFE0F Gemini API \uC0AC\uC6A9\uC744 \uC704\uD574 \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
696
1307
|
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.');
|
|
697
1308
|
process.exit(1);
|
|
698
1309
|
} catch (installError) {
|
|
699
|
-
trace6("install:failed");
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
1310
|
+
trace6("install:failed", getErrorSummary(installError));
|
|
1311
|
+
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).", {
|
|
1312
|
+
scope: "installation-gemini",
|
|
1313
|
+
error: installError
|
|
1314
|
+
});
|
|
703
1315
|
}
|
|
704
1316
|
}
|
|
705
1317
|
trace6("checkGeminiCliInstalled:end");
|
|
@@ -708,64 +1320,74 @@ function checkGeminiCliInstalled() {
|
|
|
708
1320
|
// src/pr-review/review.ts
|
|
709
1321
|
async function main() {
|
|
710
1322
|
const args4 = process.argv.slice(2);
|
|
1323
|
+
clearTraceMessages();
|
|
711
1324
|
const isTest = isTestMode(args4);
|
|
712
1325
|
const trace7 = createTraceLogger("review", args4);
|
|
713
1326
|
trace7("main:start", `args=${JSON.stringify(args4)}`);
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
checkGeminiCliInstalled();
|
|
721
|
-
trace7("install-check:done", "service=gemini");
|
|
722
|
-
break;
|
|
723
|
-
case "claude":
|
|
724
|
-
trace7("install-check:start", "service=claude");
|
|
725
|
-
checkClaudeCliInstalled();
|
|
726
|
-
trace7("install-check:done", "service=claude");
|
|
727
|
-
break;
|
|
728
|
-
case "codex":
|
|
729
|
-
trace7("install-check:start", "service=codex");
|
|
730
|
-
checkCodexCliInstalled();
|
|
731
|
-
trace7("install-check:done", "service=codex");
|
|
732
|
-
break;
|
|
733
|
-
}
|
|
1327
|
+
let command = "";
|
|
1328
|
+
let savedDiffPath = "";
|
|
1329
|
+
let savedReportPath = "";
|
|
1330
|
+
let service = "";
|
|
1331
|
+
let selectedCommitSummary = "";
|
|
1332
|
+
let reviewTargetFiles = [];
|
|
734
1333
|
try {
|
|
1334
|
+
trace7("service-selection:start");
|
|
1335
|
+
service = await showSelectionAIService();
|
|
1336
|
+
trace7("service-selection:done", `service=${service}`);
|
|
1337
|
+
switch (service) {
|
|
1338
|
+
case "gemini":
|
|
1339
|
+
trace7("install-check:start", "service=gemini");
|
|
1340
|
+
checkGeminiCliInstalled();
|
|
1341
|
+
trace7("install-check:done", "service=gemini");
|
|
1342
|
+
break;
|
|
1343
|
+
case "claude":
|
|
1344
|
+
trace7("install-check:start", "service=claude");
|
|
1345
|
+
checkClaudeCliInstalled();
|
|
1346
|
+
trace7("install-check:done", "service=claude");
|
|
1347
|
+
break;
|
|
1348
|
+
case "codex":
|
|
1349
|
+
trace7("install-check:start", "service=codex");
|
|
1350
|
+
checkCodexCliInstalled();
|
|
1351
|
+
trace7("install-check:done", "service=codex");
|
|
1352
|
+
break;
|
|
1353
|
+
}
|
|
735
1354
|
trace7("review-flow:start");
|
|
736
1355
|
console.log("\u{1F680} AI Code Review\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4...");
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
trace7("
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
let diff = "";
|
|
746
|
-
const { includeParams, excludeParams } = getGitDiffFilter();
|
|
747
|
-
trace7("diff-filter:loaded", `include=${includeParams} | exclude=${excludeParams}`);
|
|
748
|
-
try {
|
|
749
|
-
trace7("git-diff:run");
|
|
750
|
-
diff = execSync(`git diff ${diffArgs} -- ${includeParams} ${excludeParams}`).toString();
|
|
751
|
-
trace7("git-diff:done", `length=${diff.length}`);
|
|
752
|
-
} catch {
|
|
753
|
-
trace7("git-diff:error", "fallback-empty-diff");
|
|
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);
|
|
754
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}`);
|
|
755
1373
|
if (!diff.trim() && !isTest) {
|
|
756
1374
|
trace7("empty-diff:exit");
|
|
757
|
-
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.");
|
|
758
1376
|
deleteTempDiff();
|
|
759
1377
|
process.exit(0);
|
|
760
1378
|
}
|
|
1379
|
+
const nowStr = getNowString();
|
|
1380
|
+
trace7("timestamp:created", nowStr);
|
|
1381
|
+
trace7("report-dir:create:start");
|
|
1382
|
+
createReportDirectory();
|
|
1383
|
+
trace7("report-dir:create:done");
|
|
761
1384
|
trace7("temp-diff:write:start", tempDiffPath);
|
|
762
1385
|
fs.writeFileSync(tempDiffPath, diff);
|
|
763
1386
|
trace7("temp-diff:write:done");
|
|
764
1387
|
trace7("saved-diff:copy:start");
|
|
765
|
-
|
|
1388
|
+
savedDiffPath = getNextFilePath(REPORT_DIR, `${nowStr}-diff`, ".txt");
|
|
766
1389
|
fs.copyFileSync(tempDiffPath, savedDiffPath);
|
|
767
1390
|
trace7("saved-diff:copy:done", savedDiffPath);
|
|
768
|
-
let command = "";
|
|
769
1391
|
trace7("command:create:start", `service=${service}`);
|
|
770
1392
|
switch (service) {
|
|
771
1393
|
case "gemini":
|
|
@@ -780,11 +1402,13 @@ async function main() {
|
|
|
780
1402
|
}
|
|
781
1403
|
trace7("command:create:done");
|
|
782
1404
|
trace7("command:exec:start");
|
|
783
|
-
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;
|
|
784
1409
|
trace7("command:exec:done", `resultLength=${result.length}`);
|
|
785
|
-
console.log(result);
|
|
786
1410
|
trace7("report:write:start");
|
|
787
|
-
|
|
1411
|
+
savedReportPath = getNextFilePath(REPORT_DIR, nowStr, ".md");
|
|
788
1412
|
fs.writeFileSync(savedReportPath, result);
|
|
789
1413
|
trace7("report:write:done", savedReportPath);
|
|
790
1414
|
if (isTest) {
|
|
@@ -808,12 +1432,47 @@ ${command}`);
|
|
|
808
1432
|
trace7("cleanup-temp-diff:done");
|
|
809
1433
|
trace7("review-flow:end");
|
|
810
1434
|
} catch (error) {
|
|
811
|
-
trace7("review-flow:catch");
|
|
1435
|
+
trace7("review-flow:catch", getErrorSummary(error));
|
|
1436
|
+
let errorReportPath = "";
|
|
1437
|
+
trace7("cleanup-temp-diff:start(catch)");
|
|
1438
|
+
try {
|
|
1439
|
+
deleteTempDiff();
|
|
1440
|
+
trace7("cleanup-temp-diff:done(catch)");
|
|
1441
|
+
} catch (cleanupError) {
|
|
1442
|
+
trace7("cleanup-temp-diff:failed(catch)", getErrorSummary(cleanupError));
|
|
1443
|
+
console.error("\u26A0\uFE0F \uC784\uC2DC diff \uC815\uB9AC \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
1444
|
+
console.error(cleanupError);
|
|
1445
|
+
}
|
|
1446
|
+
trace7("error-report:prepare", `service=${service}`);
|
|
1447
|
+
errorReportPath = writeErrorReport(error, {
|
|
1448
|
+
scope: "review",
|
|
1449
|
+
args: args4,
|
|
1450
|
+
extraSections: [
|
|
1451
|
+
{
|
|
1452
|
+
heading: "Execution Context",
|
|
1453
|
+
markdown: `\`\`\`json
|
|
1454
|
+
${JSON.stringify(
|
|
1455
|
+
{
|
|
1456
|
+
service,
|
|
1457
|
+
selectedCommitSummary: selectedCommitSummary || null,
|
|
1458
|
+
reviewTargetFiles,
|
|
1459
|
+
command: command || null,
|
|
1460
|
+
tempDiffPath,
|
|
1461
|
+
savedDiffPath: savedDiffPath || null,
|
|
1462
|
+
savedReportPath: savedReportPath || null
|
|
1463
|
+
},
|
|
1464
|
+
null,
|
|
1465
|
+
2
|
|
1466
|
+
)}
|
|
1467
|
+
\`\`\``
|
|
1468
|
+
}
|
|
1469
|
+
]
|
|
1470
|
+
});
|
|
812
1471
|
console.error("\u274C \uB9AC\uBDF0 \uB3C4\uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
813
1472
|
console.error(error);
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1473
|
+
if (errorReportPath) {
|
|
1474
|
+
console.error(`\u{1F4C4} \uC5D0\uB7EC \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${errorReportPath}`);
|
|
1475
|
+
}
|
|
817
1476
|
process.exit(1);
|
|
818
1477
|
}
|
|
819
1478
|
}
|