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,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var child_process = require('child_process');
|
|
5
4
|
var fs = require('fs');
|
|
5
|
+
var child_process = require('child_process');
|
|
6
6
|
var path = require('path');
|
|
7
7
|
var readline = require('readline');
|
|
8
8
|
var url = require('url');
|
|
9
|
+
var util = require('util');
|
|
9
10
|
|
|
10
11
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
11
12
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -15,14 +16,53 @@ var path__default = /*#__PURE__*/_interopDefault(path);
|
|
|
15
16
|
var readline__default = /*#__PURE__*/_interopDefault(readline);
|
|
16
17
|
|
|
17
18
|
var __dirname$1 = path__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('review.cjs', document.baseURI).href))));
|
|
18
|
-
var
|
|
19
|
-
var
|
|
20
|
-
var
|
|
21
|
-
|
|
22
|
-
path__default.default.
|
|
19
|
+
var traceMessages = [];
|
|
20
|
+
var GEMINI_CLI_PACKAGE_NAME = "sales-frontend-gemini-cli";
|
|
21
|
+
var cachedPackageRootPath = "";
|
|
22
|
+
function isGeminiCliPackageRoot(directory) {
|
|
23
|
+
const packageJsonPath = path__default.default.join(directory, "package.json");
|
|
24
|
+
if (!fs__default.default.existsSync(packageJsonPath)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const packageJson = JSON.parse(fs__default.default.readFileSync(packageJsonPath, "utf8"));
|
|
29
|
+
return packageJson.name === GEMINI_CLI_PACKAGE_NAME;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function resolveGeminiCliPackageRoot(startDirectory = __dirname$1) {
|
|
35
|
+
if (cachedPackageRootPath) {
|
|
36
|
+
return cachedPackageRootPath;
|
|
37
|
+
}
|
|
38
|
+
let currentDirectory = startDirectory;
|
|
39
|
+
while (true) {
|
|
40
|
+
if (isGeminiCliPackageRoot(currentDirectory)) {
|
|
41
|
+
cachedPackageRootPath = currentDirectory;
|
|
42
|
+
return cachedPackageRootPath;
|
|
43
|
+
}
|
|
44
|
+
const parentDirectory = path__default.default.dirname(currentDirectory);
|
|
45
|
+
if (parentDirectory === currentDirectory) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
currentDirectory = parentDirectory;
|
|
49
|
+
}
|
|
50
|
+
cachedPackageRootPath = path__default.default.resolve(startDirectory, "../..");
|
|
51
|
+
return cachedPackageRootPath;
|
|
52
|
+
}
|
|
53
|
+
function resolvePackageAssetPath(relativeFilePath) {
|
|
54
|
+
return path__default.default.resolve(resolveGeminiCliPackageRoot(), relativeFilePath);
|
|
55
|
+
}
|
|
56
|
+
var rulesPath = resolvePackageAssetPath("src/common/rules/review-rules.md");
|
|
57
|
+
var namingRulesPath = resolvePackageAssetPath("src/common/rules/naming-rule.md");
|
|
58
|
+
var codingConventionRulesPath = resolvePackageAssetPath("src/common/rules/coding-convention.md");
|
|
59
|
+
var reviewFormPath = resolvePackageAssetPath("src/common/form/review-form.md");
|
|
60
|
+
resolvePackageAssetPath("src/common/form/review-form-one-by-one.md");
|
|
23
61
|
var REPORT_DIR = ".review-report";
|
|
24
62
|
var tempDiffPath = "temp_diff.txt";
|
|
25
63
|
var AIServices = ["gemini", "claude", "codex"];
|
|
64
|
+
var COMMIT_FETCH_LIMIT = 20;
|
|
65
|
+
var COMMIT_SELECTION_WINDOW = 8;
|
|
26
66
|
var ignoreList = [
|
|
27
67
|
"package.json",
|
|
28
68
|
"*.yml",
|
|
@@ -37,33 +77,276 @@ var ignoreList = [
|
|
|
37
77
|
".review-report/"
|
|
38
78
|
// 생성되는 리포트 폴더도 제외
|
|
39
79
|
];
|
|
40
|
-
function
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
80
|
+
function isTestMode(args4 = process.argv.slice(2)) {
|
|
81
|
+
return args4.includes("--test");
|
|
82
|
+
}
|
|
83
|
+
function clearTraceMessages() {
|
|
84
|
+
traceMessages.length = 0;
|
|
85
|
+
}
|
|
86
|
+
function getTraceMessages() {
|
|
87
|
+
return [...traceMessages];
|
|
88
|
+
}
|
|
89
|
+
var ANSI = {
|
|
90
|
+
bold: "\x1B[1m",
|
|
91
|
+
cyan: "\x1B[36m",
|
|
92
|
+
dim: "\x1B[2m",
|
|
93
|
+
green: "\x1B[32m",
|
|
94
|
+
reset: "\x1B[0m",
|
|
95
|
+
yellow: "\x1B[33m"
|
|
96
|
+
};
|
|
97
|
+
var ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
98
|
+
var COMBINING_MARK_PATTERN = /\p{Mark}/u;
|
|
99
|
+
var GRAPHEME_SEGMENTER = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter("ko", { granularity: "grapheme" }) : null;
|
|
100
|
+
function getGitDiffPathspecs() {
|
|
101
|
+
return {
|
|
102
|
+
excludePatterns: ignoreList.map((item) => `:(exclude)${item}`),
|
|
103
|
+
includePatterns: ["*.ts", "*.tsx", "*.js", "*.jsx"]
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function segmentGraphemes(value) {
|
|
107
|
+
if (!GRAPHEME_SEGMENTER) {
|
|
108
|
+
return [...value];
|
|
109
|
+
}
|
|
110
|
+
return [...GRAPHEME_SEGMENTER.segment(value)].map(({ segment }) => segment);
|
|
111
|
+
}
|
|
112
|
+
function isWideCodePoint(codePoint) {
|
|
113
|
+
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);
|
|
114
|
+
}
|
|
115
|
+
function isEmojiCodePoint(codePoint) {
|
|
116
|
+
return codePoint >= 127462 && codePoint <= 127487 || codePoint >= 127744 && codePoint <= 129791 || codePoint >= 9728 && codePoint <= 10175;
|
|
117
|
+
}
|
|
118
|
+
function getGraphemeWidth(grapheme) {
|
|
119
|
+
let width = 0;
|
|
120
|
+
for (const character of grapheme) {
|
|
121
|
+
const codePoint = character.codePointAt(0);
|
|
122
|
+
if (!codePoint || COMBINING_MARK_PATTERN.test(character) || codePoint === 8205) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (codePoint >= 65024 && codePoint <= 65039 || codePoint >= 917760 && codePoint <= 917999) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (isWideCodePoint(codePoint) || isEmojiCodePoint(codePoint)) {
|
|
129
|
+
width = Math.max(width, 2);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
width = Math.max(width, 1);
|
|
133
|
+
}
|
|
134
|
+
return width;
|
|
135
|
+
}
|
|
136
|
+
function tokenizePlainText(value) {
|
|
137
|
+
return segmentGraphemes(value).map((segment) => ({
|
|
138
|
+
value: segment,
|
|
139
|
+
visibleWidth: getGraphemeWidth(segment)
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
function tokenizeVisibleText(value) {
|
|
143
|
+
const tokens = [];
|
|
144
|
+
let lastIndex = 0;
|
|
145
|
+
for (const match of value.matchAll(ANSI_PATTERN)) {
|
|
146
|
+
const index = match.index ?? 0;
|
|
147
|
+
if (index > lastIndex) {
|
|
148
|
+
tokens.push(...tokenizePlainText(value.slice(lastIndex, index)));
|
|
149
|
+
}
|
|
150
|
+
tokens.push({
|
|
151
|
+
value: match[0],
|
|
152
|
+
visibleWidth: 0
|
|
153
|
+
});
|
|
154
|
+
lastIndex = index + match[0].length;
|
|
155
|
+
}
|
|
156
|
+
if (lastIndex < value.length) {
|
|
157
|
+
tokens.push(...tokenizePlainText(value.slice(lastIndex)));
|
|
158
|
+
}
|
|
159
|
+
return tokens;
|
|
160
|
+
}
|
|
161
|
+
function truncateLineForTerminal(value, maxWidth) {
|
|
162
|
+
if (maxWidth <= 0) {
|
|
44
163
|
return "";
|
|
45
164
|
}
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
165
|
+
const tokens = tokenizeVisibleText(value);
|
|
166
|
+
const totalWidth = tokens.reduce((sum, token) => sum + token.visibleWidth, 0);
|
|
167
|
+
if (totalWidth <= maxWidth) {
|
|
168
|
+
return value;
|
|
49
169
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
);
|
|
53
|
-
|
|
170
|
+
const ellipsis = "...";
|
|
171
|
+
const ellipsisWidth = 3;
|
|
172
|
+
const targetWidth = Math.max(0, maxWidth - ellipsisWidth);
|
|
173
|
+
let usedWidth = 0;
|
|
174
|
+
let result = "";
|
|
175
|
+
for (const token of tokens) {
|
|
176
|
+
if (token.visibleWidth === 0) {
|
|
177
|
+
result += token.value;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (usedWidth + token.visibleWidth > targetWidth) {
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
result += token.value;
|
|
184
|
+
usedWidth += token.visibleWidth;
|
|
185
|
+
}
|
|
186
|
+
return `${result}${ellipsis}${ANSI.reset}`;
|
|
54
187
|
}
|
|
55
|
-
function
|
|
56
|
-
|
|
188
|
+
function fitLinesToTerminal(lines) {
|
|
189
|
+
const maxWidth = Math.max(20, (process.stdout.columns || 120) - 1);
|
|
190
|
+
return lines.map((line) => truncateLineForTerminal(line, maxWidth));
|
|
191
|
+
}
|
|
192
|
+
function runGitCommand(args4, options = {}) {
|
|
193
|
+
const { allowFailure = false, trimOutput = true } = options;
|
|
194
|
+
try {
|
|
195
|
+
const output = child_process.execFileSync("git", args4, {
|
|
196
|
+
encoding: "utf8",
|
|
197
|
+
maxBuffer: 1024 * 1024 * 20,
|
|
198
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
199
|
+
});
|
|
200
|
+
return trimOutput ? output.trim() : output;
|
|
201
|
+
} catch (error) {
|
|
202
|
+
helperTrace("git-command:failed", `${args4.join(" ")} | ${getErrorSummary(error)}`);
|
|
203
|
+
if (allowFailure) {
|
|
204
|
+
return "";
|
|
205
|
+
}
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async function executeShellCommandWithProgress(command, options = {}) {
|
|
210
|
+
const { progressIntervalMs = 1e4, progressMessage = "\u23F3 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...", streamOutput = false } = options;
|
|
211
|
+
const { spawn } = await import('child_process');
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
let stdout = "";
|
|
214
|
+
let stderr = "";
|
|
215
|
+
const startedAt = Date.now();
|
|
216
|
+
console.log(progressMessage);
|
|
217
|
+
const child = spawn("/bin/zsh", ["-lc", command], {
|
|
218
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
219
|
+
});
|
|
220
|
+
const progressTimer = setInterval(() => {
|
|
221
|
+
const elapsedSeconds = Math.max(1, Math.floor((Date.now() - startedAt) / 1e3));
|
|
222
|
+
console.log(`${progressMessage} (${elapsedSeconds}s \uACBD\uACFC)`);
|
|
223
|
+
}, progressIntervalMs);
|
|
224
|
+
child.stdout.on("data", (chunk) => {
|
|
225
|
+
const text = chunk.toString();
|
|
226
|
+
stdout += text;
|
|
227
|
+
if (streamOutput) {
|
|
228
|
+
process.stdout.write(text);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
child.stderr.on("data", (chunk) => {
|
|
232
|
+
const text = chunk.toString();
|
|
233
|
+
stderr += text;
|
|
234
|
+
if (streamOutput) {
|
|
235
|
+
process.stderr.write(text);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
child.on("error", (error) => {
|
|
239
|
+
clearInterval(progressTimer);
|
|
240
|
+
reject(error);
|
|
241
|
+
});
|
|
242
|
+
child.on("close", (code, signal) => {
|
|
243
|
+
clearInterval(progressTimer);
|
|
244
|
+
if (code === 0) {
|
|
245
|
+
resolve({
|
|
246
|
+
stderr,
|
|
247
|
+
stdout
|
|
248
|
+
});
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const exitSummary = signal ? `signal=${signal}` : `code=${String(code ?? "unknown")}`;
|
|
252
|
+
reject(new Error(`\uC258 \uBA85\uB839 \uC2E4\uD589 \uC2E4\uD328 (${exitSummary})${stderr.trim() ? `
|
|
253
|
+
${stderr.trim()}` : ""}`));
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function formatReviewTargetFiles(files, visibleCount = 5) {
|
|
258
|
+
if (files.length === 0) {
|
|
259
|
+
return "(\uC5C6\uC74C)";
|
|
260
|
+
}
|
|
261
|
+
const visibleFiles = files.slice(0, visibleCount);
|
|
262
|
+
const hiddenCount = Math.max(0, files.length - visibleFiles.length);
|
|
263
|
+
if (hiddenCount === 0) {
|
|
264
|
+
return visibleFiles.join(", ");
|
|
265
|
+
}
|
|
266
|
+
return `${visibleFiles.join(", ")} \uC678 ${hiddenCount}\uAC1C`;
|
|
57
267
|
}
|
|
58
268
|
function createTraceLogger(scope, args4 = process.argv.slice(2)) {
|
|
59
269
|
const enabled = isTestMode(args4);
|
|
60
270
|
return (step, detail) => {
|
|
271
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
272
|
+
const message = `[${timestamp}][TRACE][${scope}] ${step}${detail ? ` | ${detail}` : ""}`;
|
|
273
|
+
traceMessages.push(message);
|
|
61
274
|
if (!enabled) {
|
|
62
275
|
return;
|
|
63
276
|
}
|
|
64
|
-
console.log(
|
|
277
|
+
console.log(message);
|
|
65
278
|
};
|
|
66
279
|
}
|
|
280
|
+
var helperTrace = createTraceLogger("helper");
|
|
281
|
+
function getTimestampParts(now = /* @__PURE__ */ new Date()) {
|
|
282
|
+
return {
|
|
283
|
+
YYYY: now.getFullYear(),
|
|
284
|
+
MM: String(now.getMonth() + 1).padStart(2, "0"),
|
|
285
|
+
DD: String(now.getDate()).padStart(2, "0"),
|
|
286
|
+
HH: String(now.getHours()).padStart(2, "0"),
|
|
287
|
+
mm: String(now.getMinutes()).padStart(2, "0"),
|
|
288
|
+
ss: String(now.getSeconds()).padStart(2, "0")
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function getHumanReadableNowString(now = /* @__PURE__ */ new Date()) {
|
|
292
|
+
const { YYYY, MM, DD, HH, mm, ss } = getTimestampParts(now);
|
|
293
|
+
return `${YYYY}-${MM}-${DD} ${HH}:${mm}:${ss}`;
|
|
294
|
+
}
|
|
295
|
+
function stringifyUnknown(value) {
|
|
296
|
+
if (value === void 0 || value === null) {
|
|
297
|
+
return "";
|
|
298
|
+
}
|
|
299
|
+
if (typeof value === "string") {
|
|
300
|
+
return value;
|
|
301
|
+
}
|
|
302
|
+
if (Buffer.isBuffer(value)) {
|
|
303
|
+
return value.toString();
|
|
304
|
+
}
|
|
305
|
+
if (value instanceof Error) {
|
|
306
|
+
return value.stack || value.message;
|
|
307
|
+
}
|
|
308
|
+
return util.inspect(value, { depth: 5, breakLength: 120 });
|
|
309
|
+
}
|
|
310
|
+
function getErrorSummary(error) {
|
|
311
|
+
if (error instanceof Error) {
|
|
312
|
+
return `${error.name}: ${error.message}`;
|
|
313
|
+
}
|
|
314
|
+
return stringifyUnknown(error) || "Unknown error";
|
|
315
|
+
}
|
|
316
|
+
function serializeError(error) {
|
|
317
|
+
const serialized = {
|
|
318
|
+
summary: getErrorSummary(error)
|
|
319
|
+
};
|
|
320
|
+
if (error instanceof Error) {
|
|
321
|
+
serialized.name = error.name;
|
|
322
|
+
serialized.message = error.message;
|
|
323
|
+
serialized.stack = error.stack;
|
|
324
|
+
} else {
|
|
325
|
+
serialized.value = stringifyUnknown(error);
|
|
326
|
+
}
|
|
327
|
+
if (error && typeof error === "object") {
|
|
328
|
+
const errorLike = error;
|
|
329
|
+
const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs"];
|
|
330
|
+
extraKeys.forEach((key) => {
|
|
331
|
+
if (errorLike[key] !== void 0) {
|
|
332
|
+
serialized[key] = errorLike[key];
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
const stdout = stringifyUnknown(errorLike.stdout);
|
|
336
|
+
if (stdout) {
|
|
337
|
+
serialized.stdout = stdout;
|
|
338
|
+
}
|
|
339
|
+
const stderr = stringifyUnknown(errorLike.stderr);
|
|
340
|
+
if (stderr) {
|
|
341
|
+
serialized.stderr = stderr;
|
|
342
|
+
}
|
|
343
|
+
const cause = stringifyUnknown(errorLike.cause);
|
|
344
|
+
if (cause) {
|
|
345
|
+
serialized.cause = cause;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return serialized;
|
|
349
|
+
}
|
|
67
350
|
function getNextFilePath(dir, baseName, extension) {
|
|
68
351
|
let counter = 1;
|
|
69
352
|
while (true) {
|
|
@@ -74,6 +357,13 @@ function getNextFilePath(dir, baseName, extension) {
|
|
|
74
357
|
counter++;
|
|
75
358
|
}
|
|
76
359
|
}
|
|
360
|
+
function getAvailableFilePath(dir, baseName, extension) {
|
|
361
|
+
const firstFilePath = path__default.default.join(dir, `${baseName}${extension}`);
|
|
362
|
+
if (!fs__default.default.existsSync(firstFilePath)) {
|
|
363
|
+
return firstFilePath;
|
|
364
|
+
}
|
|
365
|
+
return getNextFilePath(dir, baseName, extension);
|
|
366
|
+
}
|
|
77
367
|
function deleteFile(filePath) {
|
|
78
368
|
if (fs__default.default.existsSync(filePath)) {
|
|
79
369
|
fs__default.default.unlinkSync(filePath);
|
|
@@ -87,27 +377,171 @@ function createReportDirectory() {
|
|
|
87
377
|
fs__default.default.mkdirSync(REPORT_DIR, { recursive: true });
|
|
88
378
|
}
|
|
89
379
|
}
|
|
90
|
-
function getNowString() {
|
|
91
|
-
const
|
|
92
|
-
const YYYY = now.getFullYear();
|
|
93
|
-
const MM = String(now.getMonth() + 1).padStart(2, "0");
|
|
94
|
-
const DD = String(now.getDate()).padStart(2, "0");
|
|
95
|
-
const HH = String(now.getHours()).padStart(2, "0");
|
|
96
|
-
const mm = String(now.getMinutes()).padStart(2, "0");
|
|
97
|
-
const ss = String(now.getSeconds()).padStart(2, "0");
|
|
380
|
+
function getNowString(now = /* @__PURE__ */ new Date()) {
|
|
381
|
+
const { YYYY, MM, DD, HH, mm, ss } = getTimestampParts(now);
|
|
98
382
|
return `${YYYY}-${MM}-${DD}_${HH}-${mm}-${ss}`;
|
|
99
383
|
}
|
|
100
|
-
function
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
384
|
+
function getErrorLogTimestamp(now = /* @__PURE__ */ new Date()) {
|
|
385
|
+
const { YYYY, MM, DD, HH, mm, ss } = getTimestampParts(now);
|
|
386
|
+
return `${YYYY}-${MM}-${DD}-${HH}\uC2DC-${mm}\uBD84-${ss}\uCD08`;
|
|
387
|
+
}
|
|
388
|
+
function writeErrorReport(error, options = {}) {
|
|
389
|
+
try {
|
|
390
|
+
const now = /* @__PURE__ */ new Date();
|
|
391
|
+
helperTrace("error-report:write:start", options.scope || "unknown");
|
|
392
|
+
createReportDirectory();
|
|
393
|
+
const reportPath = getAvailableFilePath(REPORT_DIR, `error-log-${getErrorLogTimestamp(now)}`, ".md");
|
|
394
|
+
const serializedError = serializeError(error);
|
|
395
|
+
const traceSnapshot = options.traceMessages ?? getTraceMessages();
|
|
396
|
+
const extraSections = options.extraSections || [];
|
|
397
|
+
const report = `# Error Log
|
|
398
|
+
|
|
399
|
+
- \uBC1C\uC0DD \uC2DC\uAC01: ${getHumanReadableNowString(now)}
|
|
400
|
+
- Scope: \`${options.scope || "unknown"}\`
|
|
401
|
+
- \uC791\uC5C5 \uACBD\uB85C: \`${process.cwd()}\`
|
|
402
|
+
- \uC2E4\uD589 \uC778\uC790: \`${JSON.stringify(options.args ?? process.argv.slice(2))}\`
|
|
403
|
+
- \uC2E4\uD589 \uD658\uACBD: \`${process.platform} ${process.arch} / Node ${process.version}\`
|
|
404
|
+
|
|
405
|
+
## Summary
|
|
406
|
+
|
|
407
|
+
${options.title || serializedError.summary || "Unknown error"}
|
|
408
|
+
|
|
409
|
+
## Error
|
|
410
|
+
|
|
411
|
+
\`\`\`json
|
|
412
|
+
${JSON.stringify(serializedError, null, 2)}
|
|
413
|
+
\`\`\`
|
|
414
|
+
|
|
415
|
+
## Trace
|
|
416
|
+
|
|
417
|
+
\`\`\`json
|
|
418
|
+
${JSON.stringify(traceSnapshot, null, 2)}
|
|
419
|
+
\`\`\`${extraSections.length ? `
|
|
420
|
+
${extraSections.map((section) => `
|
|
421
|
+
## ${section.heading}
|
|
422
|
+
|
|
423
|
+
${section.markdown}`).join("\n")}
|
|
424
|
+
` : "\n"}
|
|
425
|
+
`;
|
|
426
|
+
fs__default.default.writeFileSync(reportPath, report);
|
|
427
|
+
helperTrace("error-report:write:done", reportPath);
|
|
428
|
+
return reportPath;
|
|
429
|
+
} catch (writeError) {
|
|
430
|
+
console.error("\u26A0\uFE0F \uC5D0\uB7EC \uB85C\uADF8 \uD30C\uC77C \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
|
|
431
|
+
console.error(writeError);
|
|
432
|
+
return "";
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
function exitWithError(message, options = {}) {
|
|
436
|
+
const reportPath = writeErrorReport(options.error || new Error(message), {
|
|
437
|
+
...options,
|
|
438
|
+
title: message
|
|
439
|
+
});
|
|
440
|
+
console.error(message);
|
|
441
|
+
if (options.error) {
|
|
442
|
+
console.error(options.error);
|
|
443
|
+
}
|
|
444
|
+
if (reportPath) {
|
|
445
|
+
console.error(`\u{1F4C4} \uC5D0\uB7EC \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${reportPath}`);
|
|
446
|
+
}
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
449
|
+
function parseServiceFromArgs(args4 = process.argv.slice(2)) {
|
|
450
|
+
helperTrace("parse-service:start", `args=${JSON.stringify(args4)}`);
|
|
451
|
+
const serviceIndex = args4.indexOf("--service");
|
|
452
|
+
const rawService = serviceIndex !== -1 ? args4[serviceIndex + 1] : "";
|
|
453
|
+
if (!rawService) {
|
|
454
|
+
helperTrace("parse-service:empty");
|
|
455
|
+
return "";
|
|
456
|
+
}
|
|
457
|
+
const normalizedService = rawService.toLowerCase();
|
|
458
|
+
if (AIServices.includes(normalizedService)) {
|
|
459
|
+
helperTrace("parse-service:resolved", normalizedService);
|
|
460
|
+
return normalizedService;
|
|
461
|
+
}
|
|
462
|
+
helperTrace("parse-service:invalid", rawService);
|
|
463
|
+
exitWithError(
|
|
464
|
+
`\u274C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC11C\uBE44\uC2A4\uC785\uB2C8\uB2E4: ${rawService}. \uC0AC\uC6A9 \uAC00\uB2A5 \uAC12: ${AIServices.join(", ")} (\uC608: --service codex)`,
|
|
465
|
+
{
|
|
466
|
+
scope: "helper:parseServiceFromArgs",
|
|
467
|
+
args: args4,
|
|
468
|
+
extraSections: [
|
|
469
|
+
{
|
|
470
|
+
heading: "Allowed Services",
|
|
471
|
+
markdown: `\`\`\`json
|
|
472
|
+
${JSON.stringify(AIServices, null, 2)}
|
|
473
|
+
\`\`\``
|
|
474
|
+
}
|
|
475
|
+
]
|
|
476
|
+
}
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
function truncateCommitSubject(subject) {
|
|
480
|
+
if (subject.length <= 72) {
|
|
481
|
+
return subject;
|
|
482
|
+
}
|
|
483
|
+
return `${subject.slice(0, 69)}...`;
|
|
484
|
+
}
|
|
485
|
+
function getRecentCommitOptions() {
|
|
486
|
+
const output = runGitCommand(
|
|
487
|
+
["log", `-${COMMIT_FETCH_LIMIT}`, "--date=relative", "--pretty=format:%h%x09%an%x09%ar%x09%s"],
|
|
488
|
+
{ allowFailure: true }
|
|
489
|
+
);
|
|
490
|
+
if (!output) {
|
|
491
|
+
return [];
|
|
492
|
+
}
|
|
493
|
+
return output.split("\n").map((line) => {
|
|
494
|
+
const [hash = "", author = "", relativeDate = "", ...subjectParts] = line.split(" ");
|
|
495
|
+
const subject = subjectParts.join(" ").trim();
|
|
496
|
+
return {
|
|
497
|
+
author,
|
|
498
|
+
description: `${author} | ${relativeDate}`,
|
|
499
|
+
hash,
|
|
500
|
+
label: `${hash} | ${truncateCommitSubject(subject)}`,
|
|
501
|
+
relativeDate,
|
|
502
|
+
subject
|
|
503
|
+
};
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
function buildSelectedCommitSummary(commits) {
|
|
507
|
+
return commits.map((commit) => `- ${commit.hash} | ${commit.subject} | ${commit.author} | ${commit.relativeDate}`).join("\n");
|
|
508
|
+
}
|
|
509
|
+
function getReviewPathspecArgs() {
|
|
510
|
+
const { includePatterns, excludePatterns } = getGitDiffPathspecs();
|
|
511
|
+
return [...includePatterns, ...excludePatterns];
|
|
512
|
+
}
|
|
513
|
+
function buildSelectedCommitDiff(commits) {
|
|
514
|
+
const reviewPathspecArgs = getReviewPathspecArgs();
|
|
515
|
+
const sections = commits.map((commit) => {
|
|
516
|
+
const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", ...reviewPathspecArgs], {
|
|
517
|
+
allowFailure: true,
|
|
518
|
+
trimOutput: false
|
|
519
|
+
}).trim();
|
|
520
|
+
if (!diff) {
|
|
521
|
+
return "";
|
|
522
|
+
}
|
|
523
|
+
return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
|
|
524
|
+
}).filter(Boolean).join("\n\n");
|
|
525
|
+
if (!sections) {
|
|
526
|
+
return "";
|
|
527
|
+
}
|
|
528
|
+
return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", "# \uB9AC\uBDF0 \uB300\uC0C1 diff", sections].join("\n");
|
|
529
|
+
}
|
|
530
|
+
function getSelectedCommitFiles(commits) {
|
|
531
|
+
const reviewPathspecArgs = getReviewPathspecArgs();
|
|
532
|
+
const files = /* @__PURE__ */ new Set();
|
|
533
|
+
commits.forEach((commit) => {
|
|
534
|
+
const output = runGitCommand(["show", "--pretty=format:", "--name-only", commit.hash, "--", ...reviewPathspecArgs], {
|
|
535
|
+
allowFailure: true
|
|
536
|
+
});
|
|
537
|
+
output.split("\n").map((line) => line.trim()).filter(Boolean).forEach((filePath) => files.add(filePath));
|
|
538
|
+
});
|
|
539
|
+
return [...files];
|
|
107
540
|
}
|
|
108
541
|
function openReport(reportPath) {
|
|
109
542
|
const resolvedPath = path__default.default.resolve(reportPath);
|
|
110
543
|
const { platform } = process;
|
|
544
|
+
helperTrace("open-report:start", resolvedPath);
|
|
111
545
|
const openWithChrome = () => {
|
|
112
546
|
if (platform === "darwin") {
|
|
113
547
|
child_process.execSync(`open -a "Google Chrome" "${resolvedPath}"`, { stdio: "ignore" });
|
|
@@ -132,63 +566,188 @@ function openReport(reportPath) {
|
|
|
132
566
|
};
|
|
133
567
|
try {
|
|
134
568
|
if (openWithChrome()) {
|
|
569
|
+
helperTrace("open-report:chrome:success", platform);
|
|
135
570
|
console.log("\u{1F680} Google Chrome\uC5D0\uC11C \uB9AC\uD3EC\uD2B8\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
136
571
|
return;
|
|
137
572
|
}
|
|
138
|
-
} catch {
|
|
573
|
+
} catch (error) {
|
|
574
|
+
helperTrace("open-report:chrome:failed", getErrorSummary(error));
|
|
139
575
|
}
|
|
140
576
|
try {
|
|
141
577
|
if (openWithDefaultBrowser()) {
|
|
578
|
+
helperTrace("open-report:default-browser:success", platform);
|
|
142
579
|
console.log("\u{1F680} \uAE30\uBCF8 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB9AC\uD3EC\uD2B8\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
143
580
|
return;
|
|
144
581
|
}
|
|
145
|
-
} catch (
|
|
146
|
-
|
|
582
|
+
} catch (error) {
|
|
583
|
+
helperTrace("open-report:default-browser:failed", getErrorSummary(error));
|
|
584
|
+
console.error("\u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800 \uC5F4\uAE30 \uC2E4\uD328:", error);
|
|
147
585
|
return;
|
|
148
586
|
}
|
|
587
|
+
helperTrace("open-report:unsupported-platform", platform);
|
|
149
588
|
console.error(`\u26A0\uFE0F \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD50C\uB7AB\uD3FC\uC785\uB2C8\uB2E4: ${platform}`);
|
|
150
589
|
}
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
590
|
+
function ensureInteractiveSelectionAvailable(scope, message) {
|
|
591
|
+
if (process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
helperTrace(`${scope}:tty-missing`);
|
|
595
|
+
exitWithError(message, {
|
|
596
|
+
scope: `helper:${scope}`
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
function renderSelectionBlock(lines, previousLineCount) {
|
|
600
|
+
if (previousLineCount > 0) {
|
|
601
|
+
readline__default.default.moveCursor(process.stdout, 0, -previousLineCount);
|
|
602
|
+
readline__default.default.clearScreenDown(process.stdout);
|
|
603
|
+
}
|
|
604
|
+
const fittedLines = fitLinesToTerminal(lines);
|
|
605
|
+
process.stdout.write(`${fittedLines.join("\n")}
|
|
606
|
+
`);
|
|
607
|
+
return fittedLines.length;
|
|
608
|
+
}
|
|
609
|
+
function getSelectionWindowRange(optionCount, selectedIndex, windowSize) {
|
|
610
|
+
if (optionCount <= windowSize) {
|
|
611
|
+
return {
|
|
612
|
+
end: optionCount,
|
|
613
|
+
start: 0
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
const halfWindow = Math.floor(windowSize / 2);
|
|
617
|
+
const maxStart = optionCount - windowSize;
|
|
618
|
+
const start = Math.max(0, Math.min(selectedIndex - halfWindow, maxStart));
|
|
619
|
+
return {
|
|
620
|
+
end: Math.min(optionCount, start + windowSize),
|
|
621
|
+
start
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
function buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize) {
|
|
625
|
+
const { start, end } = getSelectionWindowRange(options.length, selectedIndex, windowSize);
|
|
626
|
+
const lines = [
|
|
627
|
+
`${ANSI.bold}${question}${ANSI.reset}`,
|
|
628
|
+
`${ANSI.dim}\u2191\u2193 \uC774\uB3D9 | Space \uC120\uD0DD/\uD574\uC81C | Enter \uC644\uB8CC | Esc \uCDE8\uC18C${ANSI.reset}`,
|
|
629
|
+
`${ANSI.dim}\uC120\uD0DD\uB428: ${toggled.size}\uAC1C / \uC804\uCCB4: ${options.length}\uAC1C${ANSI.reset}`
|
|
630
|
+
];
|
|
631
|
+
for (let index = start; index < end; index += 1) {
|
|
632
|
+
const option = options[index];
|
|
633
|
+
if (!option) {
|
|
634
|
+
continue;
|
|
161
635
|
}
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
636
|
+
const cursor = index === selectedIndex ? `${ANSI.cyan}>${ANSI.reset}` : " ";
|
|
637
|
+
const checked = toggled.has(index) ? `${ANSI.green}\u2611${ANSI.reset}` : "\u2610";
|
|
638
|
+
const description = option.description ? ` ${ANSI.dim}${option.description}${ANSI.reset}` : "";
|
|
639
|
+
lines.push(`${cursor} ${checked} ${option.label}${description}`);
|
|
640
|
+
}
|
|
641
|
+
if (options.length > windowSize) {
|
|
642
|
+
lines.push(`${ANSI.dim}\uD45C\uC2DC \uBC94\uC704: ${start + 1}-${end} / ${options.length}${ANSI.reset}`);
|
|
643
|
+
}
|
|
644
|
+
return lines;
|
|
645
|
+
}
|
|
646
|
+
async function showMultiSelect(question, options, windowSize = COMMIT_SELECTION_WINDOW) {
|
|
647
|
+
ensureInteractiveSelectionAvailable("showMultiSelect", "\u274C \uCEE4\uBC0B \uC120\uD0DD \uBAA8\uB2EC\uC740 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
648
|
+
let selectedIndex = 0;
|
|
649
|
+
let renderedLineCount = 0;
|
|
650
|
+
const toggled = /* @__PURE__ */ new Set();
|
|
651
|
+
const rl = readline__default.default.createInterface({
|
|
652
|
+
input: process.stdin,
|
|
653
|
+
output: process.stdout,
|
|
654
|
+
terminal: true
|
|
655
|
+
});
|
|
656
|
+
process.stdout.write("\x1B[?25l");
|
|
657
|
+
const cleanup = () => {
|
|
658
|
+
if (renderedLineCount > 0) {
|
|
659
|
+
readline__default.default.moveCursor(process.stdout, 0, -renderedLineCount);
|
|
660
|
+
readline__default.default.clearScreenDown(process.stdout);
|
|
661
|
+
renderedLineCount = 0;
|
|
169
662
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
663
|
+
process.stdin.removeListener("data", onData);
|
|
664
|
+
process.stdin.setRawMode(false);
|
|
665
|
+
process.stdin.pause();
|
|
666
|
+
rl.close();
|
|
667
|
+
process.stdout.write("\x1B[?25h");
|
|
668
|
+
};
|
|
669
|
+
const render = () => {
|
|
670
|
+
const lines = buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize);
|
|
671
|
+
renderedLineCount = renderSelectionBlock(lines, renderedLineCount);
|
|
672
|
+
};
|
|
673
|
+
const confirmSelection = (resolve) => {
|
|
674
|
+
const values = [...toggled].sort((left, right) => left - right).map((index) => options[index]?.value).filter((value) => value !== void 0);
|
|
675
|
+
cleanup();
|
|
676
|
+
resolve(values);
|
|
677
|
+
};
|
|
678
|
+
const cancelSelection = (resolve) => {
|
|
679
|
+
cleanup();
|
|
680
|
+
resolve([]);
|
|
681
|
+
};
|
|
682
|
+
let onData = (_data) => {
|
|
683
|
+
};
|
|
684
|
+
render();
|
|
685
|
+
return new Promise((resolve) => {
|
|
686
|
+
onData = (data) => {
|
|
687
|
+
const key = data.toString();
|
|
688
|
+
if (key === "") {
|
|
689
|
+
cleanup();
|
|
690
|
+
process.exit(0);
|
|
178
691
|
}
|
|
179
|
-
|
|
180
|
-
|
|
692
|
+
if (key === "\x1B") {
|
|
693
|
+
cancelSelection(resolve);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (key === "\x1B[A") {
|
|
697
|
+
selectedIndex = (selectedIndex - 1 + options.length) % options.length;
|
|
698
|
+
render();
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if (key === "\x1B[B") {
|
|
702
|
+
selectedIndex = (selectedIndex + 1) % options.length;
|
|
703
|
+
render();
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (key === " ") {
|
|
707
|
+
if (toggled.has(selectedIndex)) {
|
|
708
|
+
toggled.delete(selectedIndex);
|
|
709
|
+
} else {
|
|
710
|
+
toggled.add(selectedIndex);
|
|
711
|
+
}
|
|
712
|
+
render();
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (key === "\r" || key === "\n") {
|
|
716
|
+
confirmSelection(resolve);
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
process.stdin.setRawMode(true);
|
|
720
|
+
process.stdin.resume();
|
|
721
|
+
process.stdin.on("data", onData);
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
async function selectReviewCommits() {
|
|
725
|
+
const commits = getRecentCommitOptions();
|
|
726
|
+
if (commits.length === 0) {
|
|
727
|
+
console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uCD5C\uADFC \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
728
|
+
return [];
|
|
181
729
|
}
|
|
182
|
-
return
|
|
730
|
+
return showMultiSelect(
|
|
731
|
+
"\uB9AC\uBDF0\uD560 \uCEE4\uBC0B\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.",
|
|
732
|
+
commits.map((commit) => ({
|
|
733
|
+
description: commit.description,
|
|
734
|
+
label: commit.label,
|
|
735
|
+
value: commit
|
|
736
|
+
})),
|
|
737
|
+
COMMIT_SELECTION_WINDOW
|
|
738
|
+
);
|
|
183
739
|
}
|
|
184
740
|
async function showSelectionAIService() {
|
|
185
741
|
const selectedServiceFromArgs = parseServiceFromArgs();
|
|
186
742
|
if (selectedServiceFromArgs) {
|
|
743
|
+
helperTrace("show-selection:from-args", selectedServiceFromArgs);
|
|
187
744
|
console.log(`
|
|
188
|
-
\u2705
|
|
745
|
+
\u2705 ${ANSI.green}${selectedServiceFromArgs}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
|
|
189
746
|
`);
|
|
190
747
|
return selectedServiceFromArgs;
|
|
191
748
|
}
|
|
749
|
+
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.");
|
|
750
|
+
helperTrace("show-selection:interactive:start");
|
|
192
751
|
let selectedIndex = 0;
|
|
193
752
|
const rl = readline__default.default.createInterface({
|
|
194
753
|
input: process.stdin,
|
|
@@ -202,13 +761,15 @@ async function showSelectionAIService() {
|
|
|
202
761
|
readline__default.default.moveCursor(process.stdout, 0, -(AIServices.length + 1));
|
|
203
762
|
}
|
|
204
763
|
firstRender = false;
|
|
764
|
+
helperTrace("show-selection:interactive:render", AIServices[selectedIndex] || "unknown");
|
|
205
765
|
readline__default.default.clearScreenDown(process.stdout);
|
|
206
766
|
process.stdout.write(
|
|
207
|
-
|
|
767
|
+
`\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):
|
|
768
|
+
`
|
|
208
769
|
);
|
|
209
770
|
AIServices.forEach((service, index) => {
|
|
210
771
|
if (index === selectedIndex) {
|
|
211
|
-
process.stdout.write(`
|
|
772
|
+
process.stdout.write(` ${ANSI.cyan}>${ANSI.reset} ${ANSI.cyan}\u25C9${ANSI.reset} ${ANSI.bold}${service}${ANSI.reset}
|
|
212
773
|
`);
|
|
213
774
|
} else {
|
|
214
775
|
process.stdout.write(` \u25EF ${service}
|
|
@@ -221,6 +782,7 @@ async function showSelectionAIService() {
|
|
|
221
782
|
const onData = (data) => {
|
|
222
783
|
const key = data.toString();
|
|
223
784
|
if (key === "") {
|
|
785
|
+
helperTrace("show-selection:interactive:ctrl-c");
|
|
224
786
|
process.stdout.write("\x1B[?25h");
|
|
225
787
|
process.exit(0);
|
|
226
788
|
}
|
|
@@ -237,10 +799,11 @@ async function showSelectionAIService() {
|
|
|
237
799
|
rl.close();
|
|
238
800
|
process.stdout.write("\x1B[?25h");
|
|
239
801
|
console.log(`
|
|
240
|
-
\u2705
|
|
802
|
+
\u2705 ${ANSI.green}${AIServices[selectedIndex]}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
|
|
241
803
|
`);
|
|
242
804
|
const result = AIServices[selectedIndex];
|
|
243
805
|
if (result) {
|
|
806
|
+
helperTrace("show-selection:interactive:confirmed", result);
|
|
244
807
|
resolve(result);
|
|
245
808
|
}
|
|
246
809
|
}
|
|
@@ -263,6 +826,11 @@ function getArgValue(flag) {
|
|
|
263
826
|
}
|
|
264
827
|
return args[index + 1];
|
|
265
828
|
}
|
|
829
|
+
function printNotice(message) {
|
|
830
|
+
if (args.includes("--test")) {
|
|
831
|
+
console.warn(message);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
266
834
|
function toUnique(values) {
|
|
267
835
|
const seen = /* @__PURE__ */ new Set();
|
|
268
836
|
return values.filter((value) => {
|
|
@@ -286,11 +854,11 @@ function resolveReasoningEffort() {
|
|
|
286
854
|
const normalized = normalizeEffort(customReasoningEffort);
|
|
287
855
|
trace("reasoning:custom", `${customReasoningEffort} -> ${normalized}`);
|
|
288
856
|
if (customReasoningEffort === "minimal") {
|
|
289
|
-
|
|
857
|
+
printNotice("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
|
|
290
858
|
}
|
|
291
859
|
return normalized;
|
|
292
860
|
}
|
|
293
|
-
|
|
861
|
+
printNotice(
|
|
294
862
|
`\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS.join(
|
|
295
863
|
", "
|
|
296
864
|
)}`
|
|
@@ -333,7 +901,7 @@ function buildClaudeExecCommand(options) {
|
|
|
333
901
|
const modelOption = model ? `--model ${shellQuote(model)}` : "";
|
|
334
902
|
const fallbackOption = model && fallbackModel ? `--fallback-model ${shellQuote(fallbackModel)}` : "";
|
|
335
903
|
const effortOption = `--effort ${shellQuote(effort)}`;
|
|
336
|
-
const appendedPromptFiles = systemPromptFiles.map((
|
|
904
|
+
const appendedPromptFiles = systemPromptFiles.map((path3) => `--append-system-prompt-file ${shellQuote(path3)}`).join(" ");
|
|
337
905
|
return `cat ${shellQuote(tempDiffPath2)} | claude ${[
|
|
338
906
|
modelOption,
|
|
339
907
|
fallbackOption,
|
|
@@ -360,16 +928,19 @@ var createClaudeCommand = (tempDiffPath2, reviewFormPath2) => {
|
|
|
360
928
|
trace("reviewForm:status", reviewFormExists ? "exists" : "missing");
|
|
361
929
|
const systemPromptFiles = reviewFormExists ? [...existingRuleFiles, reviewFormPath2] : existingRuleFiles;
|
|
362
930
|
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.";
|
|
931
|
+
trace("prompt:prepared", `length=${prompt.length}`);
|
|
932
|
+
trace("system-prompt-files", `count=${systemPromptFiles.length}`);
|
|
363
933
|
const modelCandidates = toUnique(customModel ? [customModel, ...aliasFallbacks] : aliasFallbacks);
|
|
364
934
|
trace("model:candidates", modelCandidates.join(", "));
|
|
935
|
+
trace("command:candidates:count", String(modelCandidates.length + 1));
|
|
365
936
|
if (customModel) {
|
|
366
|
-
|
|
937
|
+
printNotice(
|
|
367
938
|
`\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
|
|
368
939
|
" -> "
|
|
369
940
|
)}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
|
|
370
941
|
);
|
|
371
942
|
} else {
|
|
372
|
-
|
|
943
|
+
printNotice(
|
|
373
944
|
`\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.`
|
|
374
945
|
);
|
|
375
946
|
}
|
|
@@ -402,6 +973,7 @@ var createClaudeCommand = (tempDiffPath2, reviewFormPath2) => {
|
|
|
402
973
|
\uC0DD\uC131\uB420 \uBA85\uB839\uC5B4 \uBBF8\uB9AC\uBCF4\uAE30:
|
|
403
974
|
${safeCommand}"`;
|
|
404
975
|
}
|
|
976
|
+
trace("command:mode", "execute");
|
|
405
977
|
trace("createClaudeCommand:end");
|
|
406
978
|
return command;
|
|
407
979
|
};
|
|
@@ -412,23 +984,25 @@ function checkClaudeCliInstalled() {
|
|
|
412
984
|
trace2("version-check:run", "claude --version");
|
|
413
985
|
child_process.execSync("claude --version", { stdio: "ignore" });
|
|
414
986
|
trace2("version-check:ok");
|
|
415
|
-
} catch {
|
|
416
|
-
trace2("version-check:failed",
|
|
987
|
+
} catch (error) {
|
|
988
|
+
trace2("version-check:failed", getErrorSummary(error));
|
|
989
|
+
trace2("install:start", "@anthropic-ai/claude-code");
|
|
417
990
|
console.log(
|
|
418
991
|
"\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"
|
|
419
992
|
);
|
|
420
993
|
try {
|
|
421
994
|
child_process.execSync("npm install -g @anthropic-ai/claude-code", { stdio: "inherit" });
|
|
422
|
-
trace2("install:ok", "
|
|
995
|
+
trace2("install:ok", "login-required");
|
|
423
996
|
console.log("\u2705 claude-cli \uC124\uCE58\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
424
997
|
console.log("\u26A0\uFE0F claude-cli \uC0AC\uC6A9\uC744 \uC704\uD574 \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
425
998
|
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.');
|
|
426
999
|
process.exit(1);
|
|
427
1000
|
} catch (installError) {
|
|
428
|
-
trace2("install:failed");
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
1001
|
+
trace2("install:failed", getErrorSummary(installError));
|
|
1002
|
+
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).", {
|
|
1003
|
+
scope: "installation-claude",
|
|
1004
|
+
error: installError
|
|
1005
|
+
});
|
|
432
1006
|
}
|
|
433
1007
|
}
|
|
434
1008
|
trace2("checkClaudeCliInstalled:end");
|
|
@@ -446,6 +1020,11 @@ function getArgValue2(flag) {
|
|
|
446
1020
|
}
|
|
447
1021
|
return args2[index + 1];
|
|
448
1022
|
}
|
|
1023
|
+
function printNotice2(message) {
|
|
1024
|
+
if (args2.includes("--test")) {
|
|
1025
|
+
console.warn(message);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
449
1028
|
function resolveReasoningEffort2() {
|
|
450
1029
|
const customReasoningEffort = getArgValue2("--reasoning-effort");
|
|
451
1030
|
if (customReasoningEffort) {
|
|
@@ -453,7 +1032,7 @@ function resolveReasoningEffort2() {
|
|
|
453
1032
|
trace3("reasoning:custom", customReasoningEffort);
|
|
454
1033
|
return customReasoningEffort;
|
|
455
1034
|
}
|
|
456
|
-
|
|
1035
|
+
printNotice2(
|
|
457
1036
|
`\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS2.join(
|
|
458
1037
|
", "
|
|
459
1038
|
)}`
|
|
@@ -494,16 +1073,17 @@ ${reviewFormLine || "- (\uC5C6\uC74C)"}
|
|
|
494
1073
|
- ${tempDiffPath2}
|
|
495
1074
|
|
|
496
1075
|
\uBC18\uB4DC\uC2DC \uB9AC\uBDF0 \uC591\uC2DD\uC5D0 \uB9DE\uCDB0 \uC791\uC131\uD574\uC918.`;
|
|
1076
|
+
trace3("prompt:prepared", `length=${prompt.length}`);
|
|
497
1077
|
let command = "";
|
|
498
1078
|
if (customModel) {
|
|
499
|
-
|
|
1079
|
+
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.");
|
|
500
1080
|
trace3("model:custom", customModel);
|
|
501
1081
|
command = buildCodexExecCommand(prompt, reasoningEffort, customModel);
|
|
502
1082
|
} else {
|
|
503
1083
|
const preferredModelAlias = "gpt-5";
|
|
504
1084
|
const aliasCommand = buildCodexExecCommand(prompt, reasoningEffort, preferredModelAlias);
|
|
505
1085
|
const fallbackCommand = buildCodexExecCommand(prompt, reasoningEffort);
|
|
506
|
-
|
|
1086
|
+
printNotice2(
|
|
507
1087
|
`\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.`
|
|
508
1088
|
);
|
|
509
1089
|
trace3("model:alias-first", preferredModelAlias);
|
|
@@ -519,6 +1099,7 @@ ${reviewFormLine || "- (\uC5C6\uC74C)"}
|
|
|
519
1099
|
\uC0DD\uC131\uB420 \uBA85\uB839\uC5B4 \uBBF8\uB9AC\uBCF4\uAE30:
|
|
520
1100
|
${safeCommand}"`;
|
|
521
1101
|
}
|
|
1102
|
+
trace3("command:mode", "execute");
|
|
522
1103
|
trace3("createCodexCommand:end");
|
|
523
1104
|
return command;
|
|
524
1105
|
};
|
|
@@ -529,21 +1110,23 @@ function checkCodexCliInstalled() {
|
|
|
529
1110
|
trace4("version-check:run", "codex --version");
|
|
530
1111
|
child_process.execSync("codex --version", { stdio: "ignore" });
|
|
531
1112
|
trace4("version-check:ok");
|
|
532
|
-
} catch {
|
|
533
|
-
trace4("version-check:failed",
|
|
1113
|
+
} catch (error) {
|
|
1114
|
+
trace4("version-check:failed", getErrorSummary(error));
|
|
1115
|
+
trace4("install:start", "@openai/codex");
|
|
534
1116
|
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");
|
|
535
1117
|
try {
|
|
536
1118
|
child_process.execSync("npm install -g @openai/codex", { stdio: "inherit" });
|
|
537
|
-
trace4("install:ok", "
|
|
1119
|
+
trace4("install:ok", "login-required");
|
|
538
1120
|
console.log("\u2705 codex-cli \uC124\uCE58\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
539
1121
|
console.log("\u26A0\uFE0F codex-cli \uC0AC\uC6A9\uC744 \uC704\uD574 \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
540
1122
|
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.');
|
|
541
1123
|
process.exit(1);
|
|
542
1124
|
} catch (installError) {
|
|
543
|
-
trace4("install:failed");
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
1125
|
+
trace4("install:failed", getErrorSummary(installError));
|
|
1126
|
+
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).", {
|
|
1127
|
+
scope: "installation-codex",
|
|
1128
|
+
error: installError
|
|
1129
|
+
});
|
|
547
1130
|
}
|
|
548
1131
|
}
|
|
549
1132
|
trace4("checkCodexCliInstalled:end");
|
|
@@ -561,6 +1144,11 @@ function getArgValue3(flag) {
|
|
|
561
1144
|
}
|
|
562
1145
|
return args3[index + 1];
|
|
563
1146
|
}
|
|
1147
|
+
function printNotice3(message) {
|
|
1148
|
+
if (args3.includes("--test")) {
|
|
1149
|
+
console.warn(message);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
564
1152
|
function toUnique2(values) {
|
|
565
1153
|
const seen = /* @__PURE__ */ new Set();
|
|
566
1154
|
return values.filter((value) => {
|
|
@@ -578,7 +1166,7 @@ function resolveReasoningEffort3() {
|
|
|
578
1166
|
trace5("reasoning:custom", customReasoningEffort);
|
|
579
1167
|
return customReasoningEffort;
|
|
580
1168
|
}
|
|
581
|
-
|
|
1169
|
+
printNotice3(
|
|
582
1170
|
`\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS3.join(
|
|
583
1171
|
", "
|
|
584
1172
|
)}`
|
|
@@ -640,36 +1228,57 @@ function buildGeminiExecCommand(prompt, model) {
|
|
|
640
1228
|
const modelOption = model ? `--model ${shellQuote3(model)}` : "";
|
|
641
1229
|
return `gemini ${[modelOption, "-p", shellQuote3(prompt)].filter(Boolean).join(" ")}`;
|
|
642
1230
|
}
|
|
1231
|
+
function toGeminiFileReference(filePath) {
|
|
1232
|
+
return `@${filePath}`;
|
|
1233
|
+
}
|
|
1234
|
+
function buildGeminiFileReferenceSection(files) {
|
|
1235
|
+
const existingFiles = files.filter((file) => fs__default.default.existsSync(file.path));
|
|
1236
|
+
return {
|
|
1237
|
+
count: existingFiles.length,
|
|
1238
|
+
lines: existingFiles.map((file) => `- ${file.display}: ${toGeminiFileReference(file.path)}`)
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
643
1241
|
var createGeminiCommand = (tempDiffPath2, reviewFormPath2) => {
|
|
644
1242
|
trace5("createGeminiCommand:start", `tempDiffPath=${tempDiffPath2}, reviewFormPath=${reviewFormPath2}`);
|
|
645
1243
|
const customModel = getArgValue3("--model");
|
|
646
1244
|
const reasoningEffort = resolveReasoningEffort3();
|
|
647
1245
|
const primaryAlias = resolvePrimaryAlias2(reasoningEffort);
|
|
648
1246
|
const aliasFallbacks = toUnique2(getAliasFallbacks2(primaryAlias));
|
|
1247
|
+
const resolvedTempDiffPath = path__default.default.resolve(tempDiffPath2);
|
|
1248
|
+
const resolvedReviewFormPath = path__default.default.resolve(reviewFormPath2);
|
|
649
1249
|
const rules = [
|
|
650
1250
|
{ path: rulesPath, display: "\uB8F0\uC14B" },
|
|
651
1251
|
{ path: namingRulesPath, display: "\uB124\uC774\uBC0D \uADDC\uCE59" },
|
|
652
1252
|
{ path: codingConventionRulesPath, display: "\uCF54\uB529 \uCEE8\uBCA4\uC158" }
|
|
653
1253
|
];
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
const
|
|
658
|
-
trace5("reviewForm:status",
|
|
1254
|
+
const ruleSection = buildGeminiFileReferenceSection(rules);
|
|
1255
|
+
trace5("rules:loaded", `count=${ruleSection.count}`);
|
|
1256
|
+
const reviewFormExists = fs__default.default.existsSync(resolvedReviewFormPath);
|
|
1257
|
+
const reviewFormLine = reviewFormExists ? `- \uB9AC\uBDF0 \uC591\uC2DD: ${toGeminiFileReference(resolvedReviewFormPath)}` : "- \uB9AC\uBDF0 \uC591\uC2DD: (\uC5C6\uC74C)";
|
|
1258
|
+
trace5("reviewForm:status", reviewFormExists ? "exists" : "missing");
|
|
659
1259
|
const reasoningInstruction = getReasoningInstruction(reasoningEffort);
|
|
660
|
-
const prompt = `\
|
|
661
|
-
\
|
|
1260
|
+
const prompt = `\uC544\uB798 \uD30C\uC77C\uB4E4\uC744 \uC21C\uC11C\uB300\uB85C \uC77D\uACE0 \uCF54\uB4DC \uB9AC\uBDF0\uB97C \uC9C4\uD589\uD574\uC918.
|
|
1261
|
+
\uADDC\uCE59 \uD30C\uC77C:
|
|
1262
|
+
${ruleSection.lines.join("\n") || "- (\uC5C6\uC74C)"}
|
|
1263
|
+
\uB9AC\uBDF0 \uC591\uC2DD \uD30C\uC77C:
|
|
1264
|
+
${reviewFormLine}
|
|
1265
|
+
\uB9AC\uBDF0 \uB300\uC0C1 diff \uD30C\uC77C:
|
|
1266
|
+
- \uB9AC\uBDF0 \uB300\uC0C1 diff: ${toGeminiFileReference(resolvedTempDiffPath)}
|
|
1267
|
+
|
|
1268
|
+
\uBC18\uB4DC\uC2DC \uB9AC\uBDF0 \uC591\uC2DD\uC5D0 \uB9DE\uCDB0 \uC791\uC131\uD574\uC918.
|
|
662
1269
|
\uCD94\uB860 \uAC15\uB3C4 \uC9C0\uCE68: ${reasoningInstruction}`;
|
|
1270
|
+
trace5("prompt:prepared", `length=${prompt.length}`);
|
|
663
1271
|
const modelCandidates = toUnique2(customModel ? [customModel, ...aliasFallbacks] : aliasFallbacks);
|
|
664
1272
|
trace5("model:candidates", modelCandidates.join(", "));
|
|
1273
|
+
trace5("command:candidates:count", String(modelCandidates.length + 1));
|
|
665
1274
|
if (customModel) {
|
|
666
|
-
|
|
1275
|
+
printNotice3(
|
|
667
1276
|
`\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
|
|
668
1277
|
" -> "
|
|
669
1278
|
)}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
|
|
670
1279
|
);
|
|
671
1280
|
} else {
|
|
672
|
-
|
|
1281
|
+
printNotice3(
|
|
673
1282
|
`\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.`
|
|
674
1283
|
);
|
|
675
1284
|
}
|
|
@@ -684,6 +1293,7 @@ var createGeminiCommand = (tempDiffPath2, reviewFormPath2) => {
|
|
|
684
1293
|
\uC0DD\uC131\uB420 \uBA85\uB839\uC5B4 \uBBF8\uB9AC\uBCF4\uAE30:
|
|
685
1294
|
${safeCommand}"`;
|
|
686
1295
|
}
|
|
1296
|
+
trace5("command:mode", "execute");
|
|
687
1297
|
trace5("createGeminiCommand:end");
|
|
688
1298
|
return command;
|
|
689
1299
|
};
|
|
@@ -694,21 +1304,23 @@ function checkGeminiCliInstalled() {
|
|
|
694
1304
|
trace6("version-check:run", "gemini --version");
|
|
695
1305
|
child_process.execSync("gemini --version", { stdio: "ignore" });
|
|
696
1306
|
trace6("version-check:ok");
|
|
697
|
-
} catch {
|
|
698
|
-
trace6("version-check:failed",
|
|
1307
|
+
} catch (error) {
|
|
1308
|
+
trace6("version-check:failed", getErrorSummary(error));
|
|
1309
|
+
trace6("install:start", "@google/gemini-cli");
|
|
699
1310
|
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");
|
|
700
1311
|
try {
|
|
701
1312
|
child_process.execSync("npm install -g @google/gemini-cli", { stdio: "inherit" });
|
|
702
|
-
trace6("install:ok", "
|
|
1313
|
+
trace6("install:ok", "login-required");
|
|
703
1314
|
console.log("\u2705 gemini-cli \uC124\uCE58\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
704
1315
|
console.log("\u26A0\uFE0F Gemini API \uC0AC\uC6A9\uC744 \uC704\uD574 \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
705
1316
|
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.');
|
|
706
1317
|
process.exit(1);
|
|
707
1318
|
} catch (installError) {
|
|
708
|
-
trace6("install:failed");
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1319
|
+
trace6("install:failed", getErrorSummary(installError));
|
|
1320
|
+
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).", {
|
|
1321
|
+
scope: "installation-gemini",
|
|
1322
|
+
error: installError
|
|
1323
|
+
});
|
|
712
1324
|
}
|
|
713
1325
|
}
|
|
714
1326
|
trace6("checkGeminiCliInstalled:end");
|
|
@@ -717,64 +1329,74 @@ function checkGeminiCliInstalled() {
|
|
|
717
1329
|
// src/pr-review/review.ts
|
|
718
1330
|
async function main() {
|
|
719
1331
|
const args4 = process.argv.slice(2);
|
|
1332
|
+
clearTraceMessages();
|
|
720
1333
|
const isTest = isTestMode(args4);
|
|
721
1334
|
const trace7 = createTraceLogger("review", args4);
|
|
722
1335
|
trace7("main:start", `args=${JSON.stringify(args4)}`);
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
checkGeminiCliInstalled();
|
|
730
|
-
trace7("install-check:done", "service=gemini");
|
|
731
|
-
break;
|
|
732
|
-
case "claude":
|
|
733
|
-
trace7("install-check:start", "service=claude");
|
|
734
|
-
checkClaudeCliInstalled();
|
|
735
|
-
trace7("install-check:done", "service=claude");
|
|
736
|
-
break;
|
|
737
|
-
case "codex":
|
|
738
|
-
trace7("install-check:start", "service=codex");
|
|
739
|
-
checkCodexCliInstalled();
|
|
740
|
-
trace7("install-check:done", "service=codex");
|
|
741
|
-
break;
|
|
742
|
-
}
|
|
1336
|
+
let command = "";
|
|
1337
|
+
let savedDiffPath = "";
|
|
1338
|
+
let savedReportPath = "";
|
|
1339
|
+
let service = "";
|
|
1340
|
+
let selectedCommitSummary = "";
|
|
1341
|
+
let reviewTargetFiles = [];
|
|
743
1342
|
try {
|
|
1343
|
+
trace7("service-selection:start");
|
|
1344
|
+
service = await showSelectionAIService();
|
|
1345
|
+
trace7("service-selection:done", `service=${service}`);
|
|
1346
|
+
switch (service) {
|
|
1347
|
+
case "gemini":
|
|
1348
|
+
trace7("install-check:start", "service=gemini");
|
|
1349
|
+
checkGeminiCliInstalled();
|
|
1350
|
+
trace7("install-check:done", "service=gemini");
|
|
1351
|
+
break;
|
|
1352
|
+
case "claude":
|
|
1353
|
+
trace7("install-check:start", "service=claude");
|
|
1354
|
+
checkClaudeCliInstalled();
|
|
1355
|
+
trace7("install-check:done", "service=claude");
|
|
1356
|
+
break;
|
|
1357
|
+
case "codex":
|
|
1358
|
+
trace7("install-check:start", "service=codex");
|
|
1359
|
+
checkCodexCliInstalled();
|
|
1360
|
+
trace7("install-check:done", "service=codex");
|
|
1361
|
+
break;
|
|
1362
|
+
}
|
|
744
1363
|
trace7("review-flow:start");
|
|
745
1364
|
console.log("\u{1F680} AI Code Review\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4...");
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
trace7("
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
let diff = "";
|
|
755
|
-
const { includeParams, excludeParams } = getGitDiffFilter();
|
|
756
|
-
trace7("diff-filter:loaded", `include=${includeParams} | exclude=${excludeParams}`);
|
|
757
|
-
try {
|
|
758
|
-
trace7("git-diff:run");
|
|
759
|
-
diff = child_process.execSync(`git diff ${diffArgs} -- ${includeParams} ${excludeParams}`).toString();
|
|
760
|
-
trace7("git-diff:done", `length=${diff.length}`);
|
|
761
|
-
} catch {
|
|
762
|
-
trace7("git-diff:error", "fallback-empty-diff");
|
|
1365
|
+
trace7("commit-selection:start");
|
|
1366
|
+
const selectedCommits = await selectReviewCommits();
|
|
1367
|
+
trace7("commit-selection:done", `count=${selectedCommits.length}`);
|
|
1368
|
+
if (selectedCommits.length === 0) {
|
|
1369
|
+
trace7("commit-selection:empty");
|
|
1370
|
+
console.log("\u2139\uFE0F \uC120\uD0DD\uB41C \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
1371
|
+
deleteTempDiff();
|
|
1372
|
+
process.exit(0);
|
|
763
1373
|
}
|
|
1374
|
+
selectedCommitSummary = buildSelectedCommitSummary(selectedCommits);
|
|
1375
|
+
trace7("commit-summary:prepared", selectedCommitSummary);
|
|
1376
|
+
reviewTargetFiles = getSelectedCommitFiles(selectedCommits);
|
|
1377
|
+
console.log(`\u{1F4C2} \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C(${reviewTargetFiles.length}\uAC1C): ${formatReviewTargetFiles(reviewTargetFiles)}`);
|
|
1378
|
+
console.log("\u23F3 \uC120\uD0DD\uD55C \uCEE4\uBC0B diff\uB97C \uC815\uB9AC\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...");
|
|
1379
|
+
trace7("git-diff:build:start");
|
|
1380
|
+
const diff = buildSelectedCommitDiff(selectedCommits);
|
|
1381
|
+
trace7("git-diff:build:done", `length=${diff.length}`);
|
|
764
1382
|
if (!diff.trim() && !isTest) {
|
|
765
1383
|
trace7("empty-diff:exit");
|
|
766
|
-
console.log("\u2139\uFE0F \uB9AC\uBDF0\
|
|
1384
|
+
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.");
|
|
767
1385
|
deleteTempDiff();
|
|
768
1386
|
process.exit(0);
|
|
769
1387
|
}
|
|
1388
|
+
const nowStr = getNowString();
|
|
1389
|
+
trace7("timestamp:created", nowStr);
|
|
1390
|
+
trace7("report-dir:create:start");
|
|
1391
|
+
createReportDirectory();
|
|
1392
|
+
trace7("report-dir:create:done");
|
|
770
1393
|
trace7("temp-diff:write:start", tempDiffPath);
|
|
771
1394
|
fs__default.default.writeFileSync(tempDiffPath, diff);
|
|
772
1395
|
trace7("temp-diff:write:done");
|
|
773
1396
|
trace7("saved-diff:copy:start");
|
|
774
|
-
|
|
1397
|
+
savedDiffPath = getNextFilePath(REPORT_DIR, `${nowStr}-diff`, ".txt");
|
|
775
1398
|
fs__default.default.copyFileSync(tempDiffPath, savedDiffPath);
|
|
776
1399
|
trace7("saved-diff:copy:done", savedDiffPath);
|
|
777
|
-
let command = "";
|
|
778
1400
|
trace7("command:create:start", `service=${service}`);
|
|
779
1401
|
switch (service) {
|
|
780
1402
|
case "gemini":
|
|
@@ -789,11 +1411,13 @@ async function main() {
|
|
|
789
1411
|
}
|
|
790
1412
|
trace7("command:create:done");
|
|
791
1413
|
trace7("command:exec:start");
|
|
792
|
-
const result =
|
|
1414
|
+
const result = (await executeShellCommandWithProgress(command, {
|
|
1415
|
+
progressMessage: `\u23F3 [\uB9AC\uBDF0 \uC9C4\uD589] ${service} | \uB300\uC0C1 ${reviewTargetFiles.length}\uAC1C \uD30C\uC77C`,
|
|
1416
|
+
streamOutput: isTest
|
|
1417
|
+
})).stdout;
|
|
793
1418
|
trace7("command:exec:done", `resultLength=${result.length}`);
|
|
794
|
-
console.log(result);
|
|
795
1419
|
trace7("report:write:start");
|
|
796
|
-
|
|
1420
|
+
savedReportPath = getNextFilePath(REPORT_DIR, nowStr, ".md");
|
|
797
1421
|
fs__default.default.writeFileSync(savedReportPath, result);
|
|
798
1422
|
trace7("report:write:done", savedReportPath);
|
|
799
1423
|
if (isTest) {
|
|
@@ -817,12 +1441,47 @@ ${command}`);
|
|
|
817
1441
|
trace7("cleanup-temp-diff:done");
|
|
818
1442
|
trace7("review-flow:end");
|
|
819
1443
|
} catch (error) {
|
|
820
|
-
trace7("review-flow:catch");
|
|
1444
|
+
trace7("review-flow:catch", getErrorSummary(error));
|
|
1445
|
+
let errorReportPath = "";
|
|
1446
|
+
trace7("cleanup-temp-diff:start(catch)");
|
|
1447
|
+
try {
|
|
1448
|
+
deleteTempDiff();
|
|
1449
|
+
trace7("cleanup-temp-diff:done(catch)");
|
|
1450
|
+
} catch (cleanupError) {
|
|
1451
|
+
trace7("cleanup-temp-diff:failed(catch)", getErrorSummary(cleanupError));
|
|
1452
|
+
console.error("\u26A0\uFE0F \uC784\uC2DC diff \uC815\uB9AC \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
1453
|
+
console.error(cleanupError);
|
|
1454
|
+
}
|
|
1455
|
+
trace7("error-report:prepare", `service=${service}`);
|
|
1456
|
+
errorReportPath = writeErrorReport(error, {
|
|
1457
|
+
scope: "review",
|
|
1458
|
+
args: args4,
|
|
1459
|
+
extraSections: [
|
|
1460
|
+
{
|
|
1461
|
+
heading: "Execution Context",
|
|
1462
|
+
markdown: `\`\`\`json
|
|
1463
|
+
${JSON.stringify(
|
|
1464
|
+
{
|
|
1465
|
+
service,
|
|
1466
|
+
selectedCommitSummary: selectedCommitSummary || null,
|
|
1467
|
+
reviewTargetFiles,
|
|
1468
|
+
command: command || null,
|
|
1469
|
+
tempDiffPath,
|
|
1470
|
+
savedDiffPath: savedDiffPath || null,
|
|
1471
|
+
savedReportPath: savedReportPath || null
|
|
1472
|
+
},
|
|
1473
|
+
null,
|
|
1474
|
+
2
|
|
1475
|
+
)}
|
|
1476
|
+
\`\`\``
|
|
1477
|
+
}
|
|
1478
|
+
]
|
|
1479
|
+
});
|
|
821
1480
|
console.error("\u274C \uB9AC\uBDF0 \uB3C4\uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
822
1481
|
console.error(error);
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1482
|
+
if (errorReportPath) {
|
|
1483
|
+
console.error(`\u{1F4C4} \uC5D0\uB7EC \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${errorReportPath}`);
|
|
1484
|
+
}
|
|
826
1485
|
process.exit(1);
|
|
827
1486
|
}
|
|
828
1487
|
}
|