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.
Files changed (45) hide show
  1. package/dist/common/helper.cjs +674 -68
  2. package/dist/common/helper.cjs.map +1 -1
  3. package/dist/common/helper.d.cts +106 -4
  4. package/dist/common/helper.d.ts +106 -4
  5. package/dist/common/helper.js +659 -70
  6. package/dist/common/helper.js.map +1 -1
  7. package/dist/common/types.d.cts +24 -1
  8. package/dist/common/types.d.ts +24 -1
  9. package/dist/pr-review/claude/claude-commander.cjs +58 -10
  10. package/dist/pr-review/claude/claude-commander.cjs.map +1 -1
  11. package/dist/pr-review/claude/claude-commander.js +58 -10
  12. package/dist/pr-review/claude/claude-commander.js.map +1 -1
  13. package/dist/pr-review/claude/installation-claude.cjs +219 -13
  14. package/dist/pr-review/claude/installation-claude.cjs.map +1 -1
  15. package/dist/pr-review/claude/installation-claude.js +218 -13
  16. package/dist/pr-review/claude/installation-claude.js.map +1 -1
  17. package/dist/pr-review/codex/codex-commander.cjs +55 -9
  18. package/dist/pr-review/codex/codex-commander.cjs.map +1 -1
  19. package/dist/pr-review/codex/codex-commander.js +55 -9
  20. package/dist/pr-review/codex/codex-commander.js.map +1 -1
  21. package/dist/pr-review/codex/installation-codex.cjs +219 -13
  22. package/dist/pr-review/codex/installation-codex.cjs.map +1 -1
  23. package/dist/pr-review/codex/installation-codex.js +218 -13
  24. package/dist/pr-review/codex/installation-codex.js.map +1 -1
  25. package/dist/pr-review/gemini/gemini-commander.cjs +82 -16
  26. package/dist/pr-review/gemini/gemini-commander.cjs.map +1 -1
  27. package/dist/pr-review/gemini/gemini-commander.js +82 -16
  28. package/dist/pr-review/gemini/gemini-commander.js.map +1 -1
  29. package/dist/pr-review/gemini/installation-gemini.cjs +219 -13
  30. package/dist/pr-review/gemini/installation-gemini.cjs.map +1 -1
  31. package/dist/pr-review/gemini/installation-gemini.js +218 -13
  32. package/dist/pr-review/gemini/installation-gemini.js.map +1 -1
  33. package/dist/pr-review/review-one-by-one.cjs +838 -184
  34. package/dist/pr-review/review-one-by-one.cjs.map +1 -1
  35. package/dist/pr-review/review-one-by-one.js +839 -185
  36. package/dist/pr-review/review-one-by-one.js.map +1 -1
  37. package/dist/pr-review/review.cjs +815 -156
  38. package/dist/pr-review/review.cjs.map +1 -1
  39. package/dist/pr-review/review.js +815 -156
  40. package/dist/pr-review/review.js.map +1 -1
  41. package/package.json +4 -7
  42. package/src/common/rules/coding-convention.md +393 -0
  43. package/src/common/rules/coding-convention.pdf +0 -0
  44. package/src/common/rules/naming-rule.md +347 -0
  45. 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 fs5 = require('fs');
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 fs5__default = /*#__PURE__*/_interopDefault(fs5);
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 rulesPath = path__default.default.resolve(__dirname$1, "../../src/common/rules/review-rules.md");
21
- var namingRulesPath = path__default.default.resolve(__dirname$1, "../../src/common/rules/naming-rule.md");
22
- var codingConventionRulesPath = path__default.default.resolve(__dirname$1, "../../src/common/rules/coding-convention.md");
23
- path__default.default.resolve(__dirname$1, "../../src/common/form/review-form.md");
24
- var reviewFormOneByOnePath = path__default.default.resolve(__dirname$1, "../../src/common/form/review-form-one-by-one.md");
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 parseServiceFromArgs(args4 = process.argv.slice(2)) {
43
- const serviceIndex = args4.indexOf("--service");
44
- const rawService = serviceIndex !== -1 ? args4[serviceIndex + 1] : "";
45
- if (!rawService) {
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 normalizedService = rawService.toLowerCase();
49
- if (AIServices.includes(normalizedService)) {
50
- return normalizedService;
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
- console.error(
53
- `\u274C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC11C\uBE44\uC2A4\uC785\uB2C8\uB2E4: ${rawService}. \uC0AC\uC6A9 \uAC00\uB2A5 \uAC12: ${AIServices.join(", ")} (\uC608: --service codex)`
54
- );
55
- process.exit(1);
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 isTestMode(args4 = process.argv.slice(2)) {
58
- return args4.includes("--test");
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(`[TRACE][${scope}] ${step}${detail ? ` | ${detail}` : ""}`);
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 (!fs5__default.default.existsSync(filePath)) {
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 (fs5__default.default.existsSync(filePath)) {
81
- fs5__default.default.unlinkSync(filePath);
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 (!fs5__default.default.existsSync(REPORT_DIR)) {
89
- fs5__default.default.mkdirSync(REPORT_DIR, { recursive: true });
90
- }
91
- }
92
- function getNowString() {
93
- const now = /* @__PURE__ */ new Date();
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 getGitDiffFilter() {
103
- const includeExtensions = ["*.ts", "*.tsx", "*.js", "*.jsx"];
104
- const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);
105
- const quote = (pattern) => `"${pattern}"`;
106
- const includeParams = includeExtensions.map(quote).join(" ");
107
- const excludeParams = excludePatterns.map(quote).join(" ");
108
- return { includeParams, excludeParams };
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 (e) {
148
- console.error("\u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800 \uC5F4\uAE30 \uC2E4\uD328:", e);
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 getDiffArgs() {
154
- const args4 = process.argv.slice(2);
155
- const commitIndex = args4.indexOf("--commit");
156
- const { includeParams, excludeParams } = getGitDiffFilter();
157
- let diffArgs = "";
158
- if (commitIndex !== -1) {
159
- const commitHash = args4[commitIndex + 1];
160
- if (!commitHash) {
161
- console.error("\u274C \uCEE4\uBC0B \uD574\uC2DC\uAC00 \uC81C\uACF5\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
162
- process.exit(1);
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 nextArg = args4[commitIndex + 2];
165
- let n = 0;
166
- if (nextArg && !nextArg.startsWith("--")) {
167
- n = parseInt(nextArg, 10);
168
- if (isNaN(n)) {
169
- n = 0;
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
- console.log(`\u2139\uFE0F \uCEE4\uBC0B '${commitHash}' ${n > 0 ? ` \uD3EC\uD568 \uCD1D ${n + 1}\uAC1C\uC758 \uCEE4\uBC0B` : ""}\uC744 \uB9AC\uBDF0\uD569\uB2C8\uB2E4...`);
173
- diffArgs = `${commitHash}~${n + 1} ${commitHash}`;
174
- } else {
175
- try {
176
- const check = child_process.execSync(`git diff --name-only -- ${includeParams} ${excludeParams}`).toString();
177
- if (!check.trim()) {
178
- console.log("\u2139\uFE0F Unstaged \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uB9C8\uC9C0\uB9C9 \uCEE4\uBC0B(HEAD)\uC744 \uB9AC\uBDF0\uD569\uB2C8\uB2E4...");
179
- diffArgs = "HEAD~1 HEAD";
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
- } catch {
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 diffArgs;
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 \x1B[32m${selectedServiceFromArgs}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
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
- "\u{1F916} AI \uC11C\uBE44\uC2A4\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694 (\x1B[33m\u2191\u2193 \uBC29\uD5A5\uD0A4\x1B[0m \uC774\uB3D9, \x1B[33mEnter\x1B[0m \uC120\uD0DD):\n"
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(` \x1B[36m>\x1B[0m \x1B[36m\u25C9\x1B[0m \x1B[1m${service}\x1B[0m
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 \x1B[32m${AIServices[selectedIndex]}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
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
- console.warn("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
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
- console.warn(
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((path2) => `--append-system-prompt-file ${shellQuote(path2)}`).join(" ");
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) => fs5__default.default.existsSync(rule.path)).map((rule) => rule.path);
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 = fs5__default.default.existsSync(reviewFormPath2);
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
- console.warn(
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
- console.warn(
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", "install-start");
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", "exit(1) for login");
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
- console.error("\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).");
432
- console.error(installError);
433
- process.exit(1);
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
- console.warn(
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) => fs5__default.default.existsSync(filePath)).map((filePath) => `- ${filePath}`).join("\n");
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 = fs5__default.default.existsSync(reviewFormPath2);
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
- console.warn("\u26A0\uFE0F \uC9C0\uC815\uD55C \uBAA8\uB378\uC774 \uC5C6\uB294 \uACBD\uC6B0, \uC5D0\uB7EC\uAC00 \uBC1C\uC0DD\uD558\uB2C8 \uC8FC\uC758\uD558\uC138\uC694.");
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
- console.warn(
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", "install-start");
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", "exit(1) for login");
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
- console.error("\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).");
547
- console.error(installError);
548
- process.exit(1);
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
- console.warn(
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 validRules = rules.filter((rule) => fs5__default.default.existsSync(rule.path)).map((rule) => `@${rule.path}`).join(", ");
657
- const rulesCount = validRules ? validRules.split(",").length : 0;
658
- trace5("rules:loaded", `count=${rulesCount}`);
659
- const reviewFormRef = fs5__default.default.existsSync(reviewFormPath2) ? `@${reviewFormPath2}` : "(\uC5C6\uC74C)";
660
- trace5("reviewForm:status", reviewFormRef === "(\uC5C6\uC74C)" ? "missing" : "exists");
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 = `\uB2E4\uC74C \uADDC\uCE59\uB4E4\uC744 \uCC38\uACE0\uD574\uC11C(${validRules || "(\uC5C6\uC74C)"}) \uC774 diff(@${tempDiffPath2})\uB97C \uB9AC\uBDF0\uD574\uC918.
663
- \uB9AC\uBDF0 \uC591\uC2DD\uC740 ${reviewFormRef} \uC5D0 \uB9DE\uCDB0\uC11C \uC791\uC131\uD574\uC918.
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
- console.warn(
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
- console.warn(
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", "install-start");
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", "exit(1) for login");
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
- console.error("\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).");
712
- console.error(installError);
713
- process.exit(1);
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
- trace7("service-selection:start");
727
- const service = await showSelectionAIService();
728
- trace7("service-selection:done", `service=${service}`);
729
- switch (service) {
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("report-dir:create:start");
750
- createReportDirectory();
751
- trace7("report-dir:create:done");
752
- const { includeParams, excludeParams } = getGitDiffFilter();
753
- trace7("diff-filter:loaded", `include=${includeParams} | exclude=${excludeParams}`);
754
- const diffArgs = getDiffArgs();
755
- trace7("diff-args:build:done", `diffArgs=${diffArgs || "(default)"}`);
756
- const filesCommand = `git diff --name-only ${diffArgs} -- ${includeParams} ${excludeParams}`;
757
- trace7("files-command:run", filesCommand);
758
- const fileList = child_process.execSync(filesCommand).toString().split("\n").filter(Boolean);
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 \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
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
- const fullDiffCommand = `git diff ${diffArgs} -- ${includeParams} ${excludeParams}`;
767
- trace7("full-diff:run");
768
- const fullDiff = child_process.execSync(fullDiffCommand).toString();
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
- fs5__default.default.writeFileSync(tempDiffPath, fullDiff);
1364
+ fs__default.default.writeFileSync(tempDiffPath, fullDiff);
774
1365
  trace7("temp-diff:write:done");
775
1366
  trace7("saved-diff:copy:start");
776
- const savedDiffPath = getNextFilePath(REPORT_DIR, `${nowStr}-diff`, ".txt");
777
- fs5__default.default.copyFileSync(tempDiffPath, savedDiffPath);
1367
+ savedDiffPath = getNextFilePath(REPORT_DIR, `${nowStr}-diff`, ".txt");
1368
+ fs__default.default.copyFileSync(tempDiffPath, savedDiffPath);
778
1369
  trace7("saved-diff:copy:done", savedDiffPath);
779
- const savedReportPath = getNextFilePath(REPORT_DIR, nowStr, ".md");
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} Reviewing: ${file}...`);
785
- trace7("file-diff:run", file);
786
- const fileDiff = child_process.execSync(`git diff ${diffArgs} -- "${file}"`).toString();
787
- trace7("file-diff:done", `${file} | length=${fileDiff.length}`);
788
- const tempOneFileDiffPath = `temp_diff_${file.replace(/\//g, "_")}.txt`;
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
- fs5__default.default.writeFileSync(tempOneFileDiffPath, fileDiff);
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
- fs5__default.default.appendFileSync(savedReportPath, tempReport);
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
- fs5__default.default.appendFileSync(savedReportPath, `
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
- fs5__default.default.appendFileSync(savedReportPath, errorReport);
1463
+ fs__default.default.appendFileSync(savedReportPath, errorReport);
842
1464
  try {
843
- const tempOneFileDiffPath = `temp_diff_${file.replace(/\//g, "_")}.txt`;
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 (e) {
850
- trace7("file-temp-diff:delete:failed(catch)", file);
851
- console.error(`\u274C Error deleting temp file for ${file}:`, e);
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
- trace7("cleanup-temp-diff:start(catch)");
874
- deleteTempDiff();
875
- trace7("cleanup-temp-diff:done(catch)");
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
  }