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