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