sales-frontend-gemini-cli 0.4.2 → 0.4.4

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 (47) hide show
  1. package/dist/common/helper.cjs +581 -53
  2. package/dist/common/helper.cjs.map +1 -1
  3. package/dist/common/helper.d.cts +114 -3
  4. package/dist/common/helper.d.ts +114 -3
  5. package/dist/common/helper.js +570 -54
  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 +60 -12
  10. package/dist/pr-review/claude/claude-commander.cjs.map +1 -1
  11. package/dist/pr-review/claude/claude-commander.js +60 -12
  12. package/dist/pr-review/claude/claude-commander.js.map +1 -1
  13. package/dist/pr-review/claude/installation-claude.cjs +42 -6
  14. package/dist/pr-review/claude/installation-claude.cjs.map +1 -1
  15. package/dist/pr-review/claude/installation-claude.js +42 -6
  16. package/dist/pr-review/claude/installation-claude.js.map +1 -1
  17. package/dist/pr-review/codex/codex-commander.cjs +63 -12
  18. package/dist/pr-review/codex/codex-commander.cjs.map +1 -1
  19. package/dist/pr-review/codex/codex-commander.d.cts +1 -1
  20. package/dist/pr-review/codex/codex-commander.d.ts +1 -1
  21. package/dist/pr-review/codex/codex-commander.js +63 -12
  22. package/dist/pr-review/codex/codex-commander.js.map +1 -1
  23. package/dist/pr-review/codex/installation-codex.cjs +42 -6
  24. package/dist/pr-review/codex/installation-codex.cjs.map +1 -1
  25. package/dist/pr-review/codex/installation-codex.js +42 -6
  26. package/dist/pr-review/codex/installation-codex.js.map +1 -1
  27. package/dist/pr-review/gemini/gemini-commander.cjs +76 -16
  28. package/dist/pr-review/gemini/gemini-commander.cjs.map +1 -1
  29. package/dist/pr-review/gemini/gemini-commander.js +76 -16
  30. package/dist/pr-review/gemini/gemini-commander.js.map +1 -1
  31. package/dist/pr-review/gemini/installation-gemini.cjs +42 -6
  32. package/dist/pr-review/gemini/installation-gemini.cjs.map +1 -1
  33. package/dist/pr-review/gemini/installation-gemini.js +42 -6
  34. package/dist/pr-review/gemini/installation-gemini.js.map +1 -1
  35. package/dist/pr-review/review-one-by-one.cjs +699 -106
  36. package/dist/pr-review/review-one-by-one.cjs.map +1 -1
  37. package/dist/pr-review/review-one-by-one.js +700 -107
  38. package/dist/pr-review/review-one-by-one.js.map +1 -1
  39. package/dist/pr-review/review.cjs +722 -105
  40. package/dist/pr-review/review.cjs.map +1 -1
  41. package/dist/pr-review/review.js +722 -105
  42. package/dist/pr-review/review.js.map +1 -1
  43. package/package.json +4 -7
  44. package/src/common/rules/coding-convention.md +393 -0
  45. package/src/common/rules/coding-convention.pdf +0 -0
  46. package/src/common/rules/naming-rule.md +347 -0
  47. package/src/common/rules/naming-rule.pdf +0 -0
@@ -1,8 +1,8 @@
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');
@@ -17,14 +17,52 @@ var readline__default = /*#__PURE__*/_interopDefault(readline);
17
17
 
18
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))));
19
19
  var traceMessages = [];
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
- var reviewFormPath = path__default.default.resolve(__dirname$1, "../../src/common/form/review-form.md");
24
- path__default.default.resolve(__dirname$1, "../../src/common/form/review-form-one-by-one.md");
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");
25
61
  var REPORT_DIR = ".review-report";
26
62
  var tempDiffPath = "temp_diff.txt";
27
63
  var AIServices = ["gemini", "claude", "codex"];
64
+ var COMMIT_FETCH_LIMIT = 20;
65
+ var COMMIT_SELECTION_WINDOW = 8;
28
66
  var ignoreList = [
29
67
  "package.json",
30
68
  "*.yml",
@@ -42,12 +80,224 @@ var ignoreList = [
42
80
  function isTestMode(args4 = process.argv.slice(2)) {
43
81
  return args4.includes("--test");
44
82
  }
83
+ function shouldStreamAIOutput(args4 = process.argv.slice(2)) {
84
+ return args4.includes("--stream-output");
85
+ }
45
86
  function clearTraceMessages() {
46
87
  traceMessages.length = 0;
47
88
  }
48
89
  function getTraceMessages() {
49
90
  return [...traceMessages];
50
91
  }
92
+ var ANSI = {
93
+ bold: "\x1B[1m",
94
+ cyan: "\x1B[36m",
95
+ dim: "\x1B[2m",
96
+ green: "\x1B[32m",
97
+ reset: "\x1B[0m",
98
+ yellow: "\x1B[33m"
99
+ };
100
+ var ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
101
+ var COMBINING_MARK_PATTERN = /\p{Mark}/u;
102
+ var GRAPHEME_SEGMENTER = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter("ko", { granularity: "grapheme" }) : null;
103
+ function getGitDiffPathspecs() {
104
+ return {
105
+ excludePatterns: ignoreList.map((item) => `:(exclude)${item}`),
106
+ includePatterns: ["*.ts", "*.tsx", "*.js", "*.jsx"]
107
+ };
108
+ }
109
+ function segmentGraphemes(value) {
110
+ if (!GRAPHEME_SEGMENTER) {
111
+ return [...value];
112
+ }
113
+ return [...GRAPHEME_SEGMENTER.segment(value)].map(({ segment }) => segment);
114
+ }
115
+ function isWideCodePoint(codePoint) {
116
+ 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);
117
+ }
118
+ function isEmojiCodePoint(codePoint) {
119
+ return codePoint >= 127462 && codePoint <= 127487 || codePoint >= 127744 && codePoint <= 129791 || codePoint >= 9728 && codePoint <= 10175;
120
+ }
121
+ function getGraphemeWidth(grapheme) {
122
+ let width = 0;
123
+ for (const character of grapheme) {
124
+ const codePoint = character.codePointAt(0);
125
+ if (!codePoint || COMBINING_MARK_PATTERN.test(character) || codePoint === 8205) {
126
+ continue;
127
+ }
128
+ if (codePoint >= 65024 && codePoint <= 65039 || codePoint >= 917760 && codePoint <= 917999) {
129
+ continue;
130
+ }
131
+ if (isWideCodePoint(codePoint) || isEmojiCodePoint(codePoint)) {
132
+ width = Math.max(width, 2);
133
+ continue;
134
+ }
135
+ width = Math.max(width, 1);
136
+ }
137
+ return width;
138
+ }
139
+ function tokenizePlainText(value) {
140
+ return segmentGraphemes(value).map((segment) => ({
141
+ value: segment,
142
+ visibleWidth: getGraphemeWidth(segment)
143
+ }));
144
+ }
145
+ function tokenizeVisibleText(value) {
146
+ const tokens = [];
147
+ let lastIndex = 0;
148
+ for (const match of value.matchAll(ANSI_PATTERN)) {
149
+ const index = match.index ?? 0;
150
+ if (index > lastIndex) {
151
+ tokens.push(...tokenizePlainText(value.slice(lastIndex, index)));
152
+ }
153
+ tokens.push({
154
+ value: match[0],
155
+ visibleWidth: 0
156
+ });
157
+ lastIndex = index + match[0].length;
158
+ }
159
+ if (lastIndex < value.length) {
160
+ tokens.push(...tokenizePlainText(value.slice(lastIndex)));
161
+ }
162
+ return tokens;
163
+ }
164
+ function truncateLineForTerminal(value, maxWidth) {
165
+ if (maxWidth <= 0) {
166
+ return "";
167
+ }
168
+ const tokens = tokenizeVisibleText(value);
169
+ const totalWidth = tokens.reduce((sum, token) => sum + token.visibleWidth, 0);
170
+ if (totalWidth <= maxWidth) {
171
+ return value;
172
+ }
173
+ const ellipsis = "...";
174
+ const ellipsisWidth = 3;
175
+ const targetWidth = Math.max(0, maxWidth - ellipsisWidth);
176
+ let usedWidth = 0;
177
+ let result = "";
178
+ for (const token of tokens) {
179
+ if (token.visibleWidth === 0) {
180
+ result += token.value;
181
+ continue;
182
+ }
183
+ if (usedWidth + token.visibleWidth > targetWidth) {
184
+ break;
185
+ }
186
+ result += token.value;
187
+ usedWidth += token.visibleWidth;
188
+ }
189
+ return `${result}${ellipsis}${ANSI.reset}`;
190
+ }
191
+ function fitLinesToTerminal(lines) {
192
+ const maxWidth = Math.max(20, (process.stdout.columns || 120) - 1);
193
+ return lines.map((line) => truncateLineForTerminal(line, maxWidth));
194
+ }
195
+ function runGitCommand(args4, options = {}) {
196
+ const { allowFailure = false, trimOutput = true } = options;
197
+ try {
198
+ const output = child_process.execFileSync("git", args4, {
199
+ encoding: "utf8",
200
+ maxBuffer: 1024 * 1024 * 20,
201
+ stdio: ["ignore", "pipe", "pipe"]
202
+ });
203
+ return trimOutput ? output.trim() : output;
204
+ } catch (error) {
205
+ helperTrace("git-command:failed", `${args4.join(" ")} | ${getErrorSummary(error)}`);
206
+ if (allowFailure) {
207
+ return "";
208
+ }
209
+ throw error;
210
+ }
211
+ }
212
+ async function executeShellCommandWithProgress(command, options = {}) {
213
+ const { progressIntervalMs = 1e4, progressMessage = "\u23F3 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...", streamOutput = false } = options;
214
+ const { spawn } = await import('child_process');
215
+ return new Promise((resolve, reject) => {
216
+ let stdout = "";
217
+ let stderr = "";
218
+ const startedAt = Date.now();
219
+ console.log(progressMessage);
220
+ const child = spawn("/bin/zsh", ["-lc", command], {
221
+ stdio: ["ignore", "pipe", "pipe"]
222
+ });
223
+ const progressTimer = setInterval(() => {
224
+ const elapsedSeconds = Math.max(1, Math.floor((Date.now() - startedAt) / 1e3));
225
+ console.log(`${progressMessage} (${elapsedSeconds}s \uACBD\uACFC)`);
226
+ }, progressIntervalMs);
227
+ child.stdout.on("data", (chunk) => {
228
+ const text = chunk.toString();
229
+ stdout += text;
230
+ if (streamOutput) {
231
+ process.stdout.write(text);
232
+ }
233
+ });
234
+ child.stderr.on("data", (chunk) => {
235
+ const text = chunk.toString();
236
+ stderr += text;
237
+ if (streamOutput) {
238
+ process.stderr.write(text);
239
+ }
240
+ });
241
+ child.on("error", (error) => {
242
+ clearInterval(progressTimer);
243
+ reject(error);
244
+ });
245
+ child.on("close", (code, signal) => {
246
+ clearInterval(progressTimer);
247
+ if (code === 0) {
248
+ resolve({
249
+ stderr,
250
+ stdout
251
+ });
252
+ return;
253
+ }
254
+ const exitSummary = signal ? `signal=${signal}` : `code=${String(code ?? "unknown")}`;
255
+ const failureDetails = {
256
+ code,
257
+ command,
258
+ signal,
259
+ stderr,
260
+ stdout
261
+ };
262
+ reject(createShellCommandExecutionError(failureDetails, exitSummary));
263
+ });
264
+ });
265
+ }
266
+ function getShellCommandFailurePreview(failureDetails) {
267
+ const stderrText = failureDetails.stderr.trim();
268
+ const stdoutText = failureDetails.stdout.trim();
269
+ const combinedOutput = stderrText || stdoutText;
270
+ if (!combinedOutput) {
271
+ return "";
272
+ }
273
+ const MAX_PREVIEW_LENGTH = 4e3;
274
+ if (combinedOutput.length <= MAX_PREVIEW_LENGTH) {
275
+ return combinedOutput;
276
+ }
277
+ return combinedOutput.slice(-4e3);
278
+ }
279
+ function createShellCommandExecutionError(failureDetails, exitSummary) {
280
+ const failurePreview = getShellCommandFailurePreview(failureDetails);
281
+ const error = new Error(`\uC258 \uBA85\uB839 \uC2E4\uD589 \uC2E4\uD328 (${exitSummary})${failurePreview ? `
282
+ ${failurePreview}` : ""}`);
283
+ error.code = failureDetails.code;
284
+ error.signal = failureDetails.signal;
285
+ error.stdout = failureDetails.stdout;
286
+ error.stderr = failureDetails.stderr;
287
+ error.command = failureDetails.command;
288
+ return error;
289
+ }
290
+ function formatReviewTargetFiles(files, visibleCount = 5) {
291
+ if (files.length === 0) {
292
+ return "(\uC5C6\uC74C)";
293
+ }
294
+ const visibleFiles = files.slice(0, visibleCount);
295
+ const hiddenCount = Math.max(0, files.length - visibleFiles.length);
296
+ if (hiddenCount === 0) {
297
+ return visibleFiles.join(", ");
298
+ }
299
+ return `${visibleFiles.join(", ")} \uC678 ${hiddenCount}\uAC1C`;
300
+ }
51
301
  function createTraceLogger(scope, args4 = process.argv.slice(2)) {
52
302
  const enabled = isTestMode(args4);
53
303
  return (step, detail) => {
@@ -109,7 +359,7 @@ function serializeError(error) {
109
359
  }
110
360
  if (error && typeof error === "object") {
111
361
  const errorLike = error;
112
- const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs"];
362
+ const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs", "command"];
113
363
  extraKeys.forEach((key) => {
114
364
  if (errorLike[key] !== void 0) {
115
365
  serialized[key] = errorLike[key];
@@ -215,6 +465,87 @@ ${section.markdown}`).join("\n")}
215
465
  return "";
216
466
  }
217
467
  }
468
+ function getExecutionLogSummary(status, title) {
469
+ if (title) {
470
+ return title;
471
+ }
472
+ switch (status) {
473
+ case "success":
474
+ return "\uB9AC\uBDF0 \uC2E4\uD589\uC774 \uC131\uACF5\uC801\uC73C\uB85C \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
475
+ case "failed":
476
+ return "\uB9AC\uBDF0 \uC2E4\uD589 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
477
+ case "partial_failure":
478
+ return "\uB9AC\uBDF0 \uC2E4\uD589\uC740 \uC644\uB8CC\uB418\uC5C8\uC9C0\uB9CC \uC77C\uBD80 \uB2E8\uACC4\uC5D0\uC11C \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
479
+ default:
480
+ return "\uB9AC\uBDF0 \uC2E4\uD589\uC774 \uCDE8\uC18C\uB418\uC5C8\uAC70\uB098 \uB9AC\uBDF0 \uB300\uC0C1\uC774 \uC5C6\uC5B4 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
481
+ }
482
+ }
483
+ function formatExecutionDuration(startedAt, finishedAt) {
484
+ const durationMs = Math.max(0, finishedAt.getTime() - startedAt.getTime());
485
+ if (durationMs < 1e3) {
486
+ return `${durationMs}ms`;
487
+ }
488
+ const durationSeconds = durationMs / 1e3;
489
+ if (durationSeconds < 60) {
490
+ return `${durationSeconds.toFixed(1)}s`;
491
+ }
492
+ const minutes = Math.floor(durationSeconds / 60);
493
+ const seconds = Math.round(durationSeconds % 60);
494
+ return `${minutes}m ${seconds}s`;
495
+ }
496
+ function writeExecutionLog(options = {}) {
497
+ try {
498
+ const startedAt = options.startedAt ?? /* @__PURE__ */ new Date();
499
+ const finishedAt = options.finishedAt ?? /* @__PURE__ */ new Date();
500
+ const status = options.status ?? "success";
501
+ helperTrace("execution-log:write:start", options.scope || "unknown");
502
+ createReportDirectory();
503
+ const reportPath = getAvailableFilePath(REPORT_DIR, `${getNowString(finishedAt)}-execution-log`, ".md");
504
+ const traceSnapshot = options.traceMessages ?? getTraceMessages();
505
+ const extraSections = options.extraSections || [];
506
+ const serializedError = options.error ? serializeError(options.error) : null;
507
+ const report = `# Execution Log
508
+
509
+ - \uC2DC\uC791 \uC2DC\uAC01: ${getHumanReadableNowString(startedAt)}
510
+ - \uC885\uB8CC \uC2DC\uAC01: ${getHumanReadableNowString(finishedAt)}
511
+ - \uC2E4\uD589 \uC2DC\uAC04: ${formatExecutionDuration(startedAt, finishedAt)}
512
+ - \uC0C1\uD0DC: \`${status}\`
513
+ - Scope: \`${options.scope || "unknown"}\`
514
+ - \uC791\uC5C5 \uACBD\uB85C: \`${process.cwd()}\`
515
+ - \uC2E4\uD589 \uC778\uC790: \`${JSON.stringify(options.args ?? process.argv.slice(2))}\`
516
+ - \uC2E4\uD589 \uD658\uACBD: \`${process.platform} ${process.arch} / Node ${process.version}\`
517
+
518
+ ## Summary
519
+
520
+ ${getExecutionLogSummary(status, options.title)}
521
+ ${serializedError ? `
522
+
523
+ ## Error
524
+
525
+ \`\`\`json
526
+ ${JSON.stringify(serializedError, null, 2)}
527
+ \`\`\`` : ""}
528
+
529
+ ## Trace
530
+
531
+ \`\`\`json
532
+ ${JSON.stringify(traceSnapshot, null, 2)}
533
+ \`\`\`${extraSections.length ? `
534
+ ${extraSections.map((section) => `
535
+ ## ${section.heading}
536
+
537
+ ${section.markdown}`).join("\n")}
538
+ ` : "\n"}
539
+ `;
540
+ fs__default.default.writeFileSync(reportPath, report);
541
+ helperTrace("execution-log:write:done", reportPath);
542
+ return reportPath;
543
+ } catch (writeError) {
544
+ console.error("\u26A0\uFE0F \uC2E4\uD589 \uB85C\uADF8 \uD30C\uC77C \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
545
+ console.error(writeError);
546
+ return "";
547
+ }
548
+ }
218
549
  function exitWithError(message, options = {}) {
219
550
  const reportPath = writeErrorReport(options.error || new Error(message), {
220
551
  ...options,
@@ -259,13 +590,67 @@ ${JSON.stringify(AIServices, null, 2)}
259
590
  }
260
591
  );
261
592
  }
262
- function getGitDiffFilter() {
263
- const includeExtensions = ["*.ts", "*.tsx", "*.js", "*.jsx"];
264
- const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);
265
- const quote = (pattern) => `"${pattern}"`;
266
- const includeParams = includeExtensions.map(quote).join(" ");
267
- const excludeParams = excludePatterns.map(quote).join(" ");
268
- return { includeParams, excludeParams };
593
+ function truncateCommitSubject(subject) {
594
+ if (subject.length <= 72) {
595
+ return subject;
596
+ }
597
+ return `${subject.slice(0, 69)}...`;
598
+ }
599
+ function getRecentCommitOptions() {
600
+ const output = runGitCommand(
601
+ ["log", `-${COMMIT_FETCH_LIMIT}`, "--date=relative", "--pretty=format:%h%x09%an%x09%ar%x09%s"],
602
+ { allowFailure: true }
603
+ );
604
+ if (!output) {
605
+ return [];
606
+ }
607
+ return output.split("\n").map((line) => {
608
+ const [hash = "", author = "", relativeDate = "", ...subjectParts] = line.split(" ");
609
+ const subject = subjectParts.join(" ").trim();
610
+ return {
611
+ author,
612
+ description: `${author} | ${relativeDate}`,
613
+ hash,
614
+ label: `${hash} | ${truncateCommitSubject(subject)}`,
615
+ relativeDate,
616
+ subject
617
+ };
618
+ });
619
+ }
620
+ function buildSelectedCommitSummary(commits) {
621
+ return commits.map((commit) => `- ${commit.hash} | ${commit.subject} | ${commit.author} | ${commit.relativeDate}`).join("\n");
622
+ }
623
+ function getReviewPathspecArgs() {
624
+ const { includePatterns, excludePatterns } = getGitDiffPathspecs();
625
+ return [...includePatterns, ...excludePatterns];
626
+ }
627
+ function buildSelectedCommitDiff(commits) {
628
+ const reviewPathspecArgs = getReviewPathspecArgs();
629
+ const sections = commits.map((commit) => {
630
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", ...reviewPathspecArgs], {
631
+ allowFailure: true,
632
+ trimOutput: false
633
+ }).trim();
634
+ if (!diff) {
635
+ return "";
636
+ }
637
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
638
+ }).filter(Boolean).join("\n\n");
639
+ if (!sections) {
640
+ return "";
641
+ }
642
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", "# \uB9AC\uBDF0 \uB300\uC0C1 diff", sections].join("\n");
643
+ }
644
+ function getSelectedCommitFiles(commits) {
645
+ const reviewPathspecArgs = getReviewPathspecArgs();
646
+ const files = /* @__PURE__ */ new Set();
647
+ commits.forEach((commit) => {
648
+ const output = runGitCommand(["show", "--pretty=format:", "--name-only", commit.hash, "--", ...reviewPathspecArgs], {
649
+ allowFailure: true
650
+ });
651
+ output.split("\n").map((line) => line.trim()).filter(Boolean).forEach((filePath) => files.add(filePath));
652
+ });
653
+ return [...files];
269
654
  }
270
655
  function openReport(reportPath) {
271
656
  const resolvedPath = path__default.default.resolve(reportPath);
@@ -316,59 +701,166 @@ function openReport(reportPath) {
316
701
  helperTrace("open-report:unsupported-platform", platform);
317
702
  console.error(`\u26A0\uFE0F \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD50C\uB7AB\uD3FC\uC785\uB2C8\uB2E4: ${platform}`);
318
703
  }
319
- function getDiffArgs() {
320
- const args4 = process.argv.slice(2);
321
- const commitIndex = args4.indexOf("--commit");
322
- const { includeParams, excludeParams } = getGitDiffFilter();
323
- helperTrace("diff-args:resolve:start", `args=${JSON.stringify(args4)}`);
324
- let diffArgs = "";
325
- if (commitIndex !== -1) {
326
- const commitHash = args4[commitIndex + 1];
327
- if (!commitHash) {
328
- helperTrace("diff-args:commit-hash-missing");
329
- exitWithError("\u274C \uCEE4\uBC0B \uD574\uC2DC\uAC00 \uC81C\uACF5\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.", {
330
- scope: "helper:getDiffArgs",
331
- args: args4
332
- });
704
+ function ensureInteractiveSelectionAvailable(scope, message) {
705
+ if (process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function") {
706
+ return;
707
+ }
708
+ helperTrace(`${scope}:tty-missing`);
709
+ exitWithError(message, {
710
+ scope: `helper:${scope}`
711
+ });
712
+ }
713
+ function renderSelectionBlock(lines, previousLineCount) {
714
+ if (previousLineCount > 0) {
715
+ readline__default.default.moveCursor(process.stdout, 0, -previousLineCount);
716
+ readline__default.default.clearScreenDown(process.stdout);
717
+ }
718
+ const fittedLines = fitLinesToTerminal(lines);
719
+ process.stdout.write(`${fittedLines.join("\n")}
720
+ `);
721
+ return fittedLines.length;
722
+ }
723
+ function getSelectionWindowRange(optionCount, selectedIndex, windowSize) {
724
+ if (optionCount <= windowSize) {
725
+ return {
726
+ end: optionCount,
727
+ start: 0
728
+ };
729
+ }
730
+ const halfWindow = Math.floor(windowSize / 2);
731
+ const maxStart = optionCount - windowSize;
732
+ const start = Math.max(0, Math.min(selectedIndex - halfWindow, maxStart));
733
+ return {
734
+ end: Math.min(optionCount, start + windowSize),
735
+ start
736
+ };
737
+ }
738
+ function buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize) {
739
+ const { start, end } = getSelectionWindowRange(options.length, selectedIndex, windowSize);
740
+ const lines = [
741
+ `${ANSI.bold}${question}${ANSI.reset}`,
742
+ `${ANSI.dim}\u2191\u2193 \uC774\uB3D9 | Space \uC120\uD0DD/\uD574\uC81C | Enter \uC644\uB8CC | Esc \uCDE8\uC18C${ANSI.reset}`,
743
+ `${ANSI.dim}\uC120\uD0DD\uB428: ${toggled.size}\uAC1C / \uC804\uCCB4: ${options.length}\uAC1C${ANSI.reset}`
744
+ ];
745
+ for (let index = start; index < end; index += 1) {
746
+ const option = options[index];
747
+ if (!option) {
748
+ continue;
333
749
  }
334
- const nextArg = args4[commitIndex + 2];
335
- let n = 0;
336
- if (nextArg && !nextArg.startsWith("--")) {
337
- n = parseInt(nextArg, 10);
338
- if (isNaN(n)) {
339
- n = 0;
340
- }
750
+ const cursor = index === selectedIndex ? `${ANSI.cyan}>${ANSI.reset}` : " ";
751
+ const checked = toggled.has(index) ? `${ANSI.green}\u2611${ANSI.reset}` : "\u2610";
752
+ const description = option.description ? ` ${ANSI.dim}${option.description}${ANSI.reset}` : "";
753
+ lines.push(`${cursor} ${checked} ${option.label}${description}`);
754
+ }
755
+ if (options.length > windowSize) {
756
+ lines.push(`${ANSI.dim}\uD45C\uC2DC \uBC94\uC704: ${start + 1}-${end} / ${options.length}${ANSI.reset}`);
757
+ }
758
+ return lines;
759
+ }
760
+ async function showMultiSelect(question, options, windowSize = COMMIT_SELECTION_WINDOW) {
761
+ ensureInteractiveSelectionAvailable("showMultiSelect", "\u274C \uCEE4\uBC0B \uC120\uD0DD \uBAA8\uB2EC\uC740 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
762
+ let selectedIndex = 0;
763
+ let renderedLineCount = 0;
764
+ const toggled = /* @__PURE__ */ new Set();
765
+ const rl = readline__default.default.createInterface({
766
+ input: process.stdin,
767
+ output: process.stdout,
768
+ terminal: true
769
+ });
770
+ process.stdout.write("\x1B[?25l");
771
+ const cleanup = () => {
772
+ if (renderedLineCount > 0) {
773
+ readline__default.default.moveCursor(process.stdout, 0, -renderedLineCount);
774
+ readline__default.default.clearScreenDown(process.stdout);
775
+ renderedLineCount = 0;
341
776
  }
342
- helperTrace("diff-args:commit-mode", `${commitHash}~${n + 1} ${commitHash}`);
343
- console.log(`\u2139\uFE0F \uCEE4\uBC0B '${commitHash}' ${n > 0 ? ` \uD3EC\uD568 \uCD1D ${n + 1}\uAC1C\uC758 \uCEE4\uBC0B` : ""}\uC744 \uB9AC\uBDF0\uD569\uB2C8\uB2E4...`);
344
- diffArgs = `${commitHash}~${n + 1} ${commitHash}`;
345
- } else {
346
- try {
347
- helperTrace("diff-args:unstaged-check:start");
348
- const check = child_process.execSync(`git diff --name-only -- ${includeParams} ${excludeParams}`).toString();
349
- if (!check.trim()) {
350
- helperTrace("diff-args:unstaged-check:empty", "use HEAD~1 HEAD");
351
- 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...");
352
- diffArgs = "HEAD~1 HEAD";
353
- } else {
354
- helperTrace("diff-args:unstaged-check:has-changes", `length=${check.length}`);
777
+ process.stdin.removeListener("data", onData);
778
+ process.stdin.setRawMode(false);
779
+ process.stdin.pause();
780
+ rl.close();
781
+ process.stdout.write("\x1B[?25h");
782
+ };
783
+ const render = () => {
784
+ const lines = buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize);
785
+ renderedLineCount = renderSelectionBlock(lines, renderedLineCount);
786
+ };
787
+ const confirmSelection = (resolve) => {
788
+ const values = [...toggled].sort((left, right) => left - right).map((index) => options[index]?.value).filter((value) => value !== void 0);
789
+ cleanup();
790
+ resolve(values);
791
+ };
792
+ const cancelSelection = (resolve) => {
793
+ cleanup();
794
+ resolve([]);
795
+ };
796
+ let onData = (_data) => {
797
+ };
798
+ render();
799
+ return new Promise((resolve) => {
800
+ onData = (data) => {
801
+ const key = data.toString();
802
+ if (key === "") {
803
+ cleanup();
804
+ process.exit(0);
355
805
  }
356
- } catch (error) {
357
- helperTrace("diff-args:unstaged-check:failed", getErrorSummary(error));
358
- }
806
+ if (key === "\x1B") {
807
+ cancelSelection(resolve);
808
+ return;
809
+ }
810
+ if (key === "\x1B[A") {
811
+ selectedIndex = (selectedIndex - 1 + options.length) % options.length;
812
+ render();
813
+ return;
814
+ }
815
+ if (key === "\x1B[B") {
816
+ selectedIndex = (selectedIndex + 1) % options.length;
817
+ render();
818
+ return;
819
+ }
820
+ if (key === " ") {
821
+ if (toggled.has(selectedIndex)) {
822
+ toggled.delete(selectedIndex);
823
+ } else {
824
+ toggled.add(selectedIndex);
825
+ }
826
+ render();
827
+ return;
828
+ }
829
+ if (key === "\r" || key === "\n") {
830
+ confirmSelection(resolve);
831
+ }
832
+ };
833
+ process.stdin.setRawMode(true);
834
+ process.stdin.resume();
835
+ process.stdin.on("data", onData);
836
+ });
837
+ }
838
+ async function selectReviewCommits() {
839
+ const commits = getRecentCommitOptions();
840
+ if (commits.length === 0) {
841
+ console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uCD5C\uADFC \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
842
+ return [];
359
843
  }
360
- helperTrace("diff-args:resolve:done", diffArgs || "(default)");
361
- return diffArgs;
844
+ return showMultiSelect(
845
+ "\uB9AC\uBDF0\uD560 \uCEE4\uBC0B\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.",
846
+ commits.map((commit) => ({
847
+ description: commit.description,
848
+ label: commit.label,
849
+ value: commit
850
+ })),
851
+ COMMIT_SELECTION_WINDOW
852
+ );
362
853
  }
363
854
  async function showSelectionAIService() {
364
855
  const selectedServiceFromArgs = parseServiceFromArgs();
365
856
  if (selectedServiceFromArgs) {
366
857
  helperTrace("show-selection:from-args", selectedServiceFromArgs);
367
858
  console.log(`
368
- \u2705 \x1B[32m${selectedServiceFromArgs}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
859
+ \u2705 ${ANSI.green}${selectedServiceFromArgs}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
369
860
  `);
370
861
  return selectedServiceFromArgs;
371
862
  }
863
+ 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.");
372
864
  helperTrace("show-selection:interactive:start");
373
865
  let selectedIndex = 0;
374
866
  const rl = readline__default.default.createInterface({
@@ -386,11 +878,12 @@ async function showSelectionAIService() {
386
878
  helperTrace("show-selection:interactive:render", AIServices[selectedIndex] || "unknown");
387
879
  readline__default.default.clearScreenDown(process.stdout);
388
880
  process.stdout.write(
389
- "\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"
881
+ `\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):
882
+ `
390
883
  );
391
884
  AIServices.forEach((service, index) => {
392
885
  if (index === selectedIndex) {
393
- process.stdout.write(` \x1B[36m>\x1B[0m \x1B[36m\u25C9\x1B[0m \x1B[1m${service}\x1B[0m
886
+ process.stdout.write(` ${ANSI.cyan}>${ANSI.reset} ${ANSI.cyan}\u25C9${ANSI.reset} ${ANSI.bold}${service}${ANSI.reset}
394
887
  `);
395
888
  } else {
396
889
  process.stdout.write(` \u25EF ${service}
@@ -420,7 +913,7 @@ async function showSelectionAIService() {
420
913
  rl.close();
421
914
  process.stdout.write("\x1B[?25h");
422
915
  console.log(`
423
- \u2705 \x1B[32m${AIServices[selectedIndex]}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
916
+ \u2705 ${ANSI.green}${AIServices[selectedIndex]}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
424
917
  `);
425
918
  const result = AIServices[selectedIndex];
426
919
  if (result) {
@@ -440,6 +933,13 @@ var ALLOWED_REASONING_EFFORTS = ["minimal", "low", "medium", "high"];
440
933
  function shellQuote(value) {
441
934
  return `'${value.replace(/'/g, `'\\''`)}'`;
442
935
  }
936
+ function toShellOptionToken(value) {
937
+ const SIMPLE_SHELL_TOKEN_PATTERN = /^[A-Za-z0-9._:/=-]+$/;
938
+ if (SIMPLE_SHELL_TOKEN_PATTERN.test(value)) {
939
+ return value;
940
+ }
941
+ return shellQuote(value);
942
+ }
443
943
  function getArgValue(flag) {
444
944
  const index = args.indexOf(flag);
445
945
  if (index === -1 || !args[index + 1]) {
@@ -447,6 +947,11 @@ function getArgValue(flag) {
447
947
  }
448
948
  return args[index + 1];
449
949
  }
950
+ function printNotice(message) {
951
+ if (args.includes("--test")) {
952
+ console.warn(message);
953
+ }
954
+ }
450
955
  function toUnique(values) {
451
956
  const seen = /* @__PURE__ */ new Set();
452
957
  return values.filter((value) => {
@@ -470,11 +975,11 @@ function resolveReasoningEffort() {
470
975
  const normalized = normalizeEffort(customReasoningEffort);
471
976
  trace("reasoning:custom", `${customReasoningEffort} -> ${normalized}`);
472
977
  if (customReasoningEffort === "minimal") {
473
- console.warn("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
978
+ printNotice("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
474
979
  }
475
980
  return normalized;
476
981
  }
477
- console.warn(
982
+ printNotice(
478
983
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS.join(
479
984
  ", "
480
985
  )}`
@@ -514,10 +1019,10 @@ function getAliasFallbacks(primaryAlias) {
514
1019
  }
515
1020
  function buildClaudeExecCommand(options) {
516
1021
  const { tempDiffPath: tempDiffPath2, prompt, systemPromptFiles, effort, model, fallbackModel } = options;
517
- const modelOption = model ? `--model ${shellQuote(model)}` : "";
518
- const fallbackOption = model && fallbackModel ? `--fallback-model ${shellQuote(fallbackModel)}` : "";
519
- const effortOption = `--effort ${shellQuote(effort)}`;
520
- const appendedPromptFiles = systemPromptFiles.map((path2) => `--append-system-prompt-file ${shellQuote(path2)}`).join(" ");
1022
+ const modelOption = model ? `--model ${toShellOptionToken(model)}` : "";
1023
+ const fallbackOption = model && fallbackModel ? `--fallback-model ${toShellOptionToken(fallbackModel)}` : "";
1024
+ const effortOption = `--effort ${toShellOptionToken(effort)}`;
1025
+ const appendedPromptFiles = systemPromptFiles.map((path3) => `--append-system-prompt-file ${shellQuote(path3)}`).join(" ");
521
1026
  return `cat ${shellQuote(tempDiffPath2)} | claude ${[
522
1027
  modelOption,
523
1028
  fallbackOption,
@@ -550,13 +1055,13 @@ var createClaudeCommand = (tempDiffPath2, reviewFormPath2) => {
550
1055
  trace("model:candidates", modelCandidates.join(", "));
551
1056
  trace("command:candidates:count", String(modelCandidates.length + 1));
552
1057
  if (customModel) {
553
- console.warn(
1058
+ printNotice(
554
1059
  `\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
555
1060
  " -> "
556
1061
  )}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
557
1062
  );
558
1063
  } else {
559
- console.warn(
1064
+ printNotice(
560
1065
  `\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.`
561
1066
  );
562
1067
  }
@@ -636,22 +1141,37 @@ function getArgValue2(flag) {
636
1141
  }
637
1142
  return args2[index + 1];
638
1143
  }
1144
+ function printNotice2(message) {
1145
+ if (args2.includes("--test")) {
1146
+ console.warn(message);
1147
+ }
1148
+ }
1149
+ function normalizeEffort2(level) {
1150
+ if (level === "minimal") {
1151
+ return "low";
1152
+ }
1153
+ return level;
1154
+ }
639
1155
  function resolveReasoningEffort2() {
640
1156
  const customReasoningEffort = getArgValue2("--reasoning-effort");
641
1157
  if (customReasoningEffort) {
642
1158
  if (ALLOWED_REASONING_EFFORTS2.includes(customReasoningEffort)) {
643
- trace3("reasoning:custom", customReasoningEffort);
644
- return customReasoningEffort;
1159
+ const normalized = normalizeEffort2(customReasoningEffort);
1160
+ trace3("reasoning:custom", `${customReasoningEffort} -> ${normalized}`);
1161
+ if (customReasoningEffort === "minimal") {
1162
+ printNotice2("\u26A0\uFE0F Codex\uB294 minimal\uC774 web_search \uB3C4\uAD6C\uC640 \uCDA9\uB3CC\uD560 \uC218 \uC788\uC5B4 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
1163
+ }
1164
+ return normalized;
645
1165
  }
646
- console.warn(
1166
+ printNotice2(
647
1167
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS2.join(
648
1168
  ", "
649
1169
  )}`
650
1170
  );
651
1171
  }
652
1172
  if (args2.includes("--flash")) {
653
- trace3("reasoning:flash-default", "minimal");
654
- return "minimal";
1173
+ trace3("reasoning:flash-default", "low");
1174
+ return "low";
655
1175
  }
656
1176
  if (args2.includes("--review")) {
657
1177
  trace3("reasoning:review-default", "high");
@@ -687,14 +1207,14 @@ ${reviewFormLine || "- (\uC5C6\uC74C)"}
687
1207
  trace3("prompt:prepared", `length=${prompt.length}`);
688
1208
  let command = "";
689
1209
  if (customModel) {
690
- 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.");
1210
+ 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.");
691
1211
  trace3("model:custom", customModel);
692
1212
  command = buildCodexExecCommand(prompt, reasoningEffort, customModel);
693
1213
  } else {
694
1214
  const preferredModelAlias = "gpt-5";
695
1215
  const aliasCommand = buildCodexExecCommand(prompt, reasoningEffort, preferredModelAlias);
696
1216
  const fallbackCommand = buildCodexExecCommand(prompt, reasoningEffort);
697
- console.warn(
1217
+ printNotice2(
698
1218
  `\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.`
699
1219
  );
700
1220
  trace3("model:alias-first", preferredModelAlias);
@@ -755,6 +1275,11 @@ function getArgValue3(flag) {
755
1275
  }
756
1276
  return args3[index + 1];
757
1277
  }
1278
+ function printNotice3(message) {
1279
+ if (args3.includes("--test")) {
1280
+ console.warn(message);
1281
+ }
1282
+ }
758
1283
  function toUnique2(values) {
759
1284
  const seen = /* @__PURE__ */ new Set();
760
1285
  return values.filter((value) => {
@@ -772,7 +1297,7 @@ function resolveReasoningEffort3() {
772
1297
  trace5("reasoning:custom", customReasoningEffort);
773
1298
  return customReasoningEffort;
774
1299
  }
775
- console.warn(
1300
+ printNotice3(
776
1301
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS3.join(
777
1302
  ", "
778
1303
  )}`
@@ -834,38 +1359,57 @@ function buildGeminiExecCommand(prompt, model) {
834
1359
  const modelOption = model ? `--model ${shellQuote3(model)}` : "";
835
1360
  return `gemini ${[modelOption, "-p", shellQuote3(prompt)].filter(Boolean).join(" ")}`;
836
1361
  }
1362
+ function toGeminiFileReference(filePath) {
1363
+ return `@${filePath}`;
1364
+ }
1365
+ function buildGeminiFileReferenceSection(files) {
1366
+ const existingFiles = files.filter((file) => fs__default.default.existsSync(file.path));
1367
+ return {
1368
+ count: existingFiles.length,
1369
+ items: existingFiles.map((file) => `${file.display} ${toGeminiFileReference(file.path)}`)
1370
+ };
1371
+ }
837
1372
  var createGeminiCommand = (tempDiffPath2, reviewFormPath2) => {
838
1373
  trace5("createGeminiCommand:start", `tempDiffPath=${tempDiffPath2}, reviewFormPath=${reviewFormPath2}`);
839
1374
  const customModel = getArgValue3("--model");
840
1375
  const reasoningEffort = resolveReasoningEffort3();
841
1376
  const primaryAlias = resolvePrimaryAlias2(reasoningEffort);
842
1377
  const aliasFallbacks = toUnique2(getAliasFallbacks2(primaryAlias));
1378
+ const resolvedTempDiffPath = path__default.default.resolve(tempDiffPath2);
1379
+ const resolvedReviewFormPath = path__default.default.resolve(reviewFormPath2);
843
1380
  const rules = [
844
1381
  { path: rulesPath, display: "\uB8F0\uC14B" },
845
1382
  { path: namingRulesPath, display: "\uB124\uC774\uBC0D \uADDC\uCE59" },
846
1383
  { path: codingConventionRulesPath, display: "\uCF54\uB529 \uCEE8\uBCA4\uC158" }
847
1384
  ];
848
- const validRules = rules.filter((rule) => fs__default.default.existsSync(rule.path)).map((rule) => `@${rule.path}`).join(", ");
849
- const rulesCount = validRules ? validRules.split(",").length : 0;
850
- trace5("rules:loaded", `count=${rulesCount}`);
851
- const reviewFormRef = fs__default.default.existsSync(reviewFormPath2) ? `@${reviewFormPath2}` : "(\uC5C6\uC74C)";
852
- trace5("reviewForm:status", reviewFormRef === "(\uC5C6\uC74C)" ? "missing" : "exists");
1385
+ const ruleSection = buildGeminiFileReferenceSection(rules);
1386
+ trace5("rules:loaded", `count=${ruleSection.count}`);
1387
+ const reviewFormExists = fs__default.default.existsSync(resolvedReviewFormPath);
1388
+ const reviewFormText = reviewFormExists ? `\uB9AC\uBDF0 \uC591\uC2DD ${toGeminiFileReference(resolvedReviewFormPath)}` : "\uB9AC\uBDF0 \uC591\uC2DD (\uC5C6\uC74C)";
1389
+ trace5("reviewForm:status", reviewFormExists ? "exists" : "missing");
853
1390
  const reasoningInstruction = getReasoningInstruction(reasoningEffort);
854
- const prompt = `\uB2E4\uC74C \uADDC\uCE59\uB4E4\uC744 \uCC38\uACE0\uD574\uC11C(${validRules || "(\uC5C6\uC74C)"}) \uC774 diff(@${tempDiffPath2})\uB97C \uB9AC\uBDF0\uD574\uC918.
855
- \uB9AC\uBDF0 \uC591\uC2DD\uC740 ${reviewFormRef} \uC5D0 \uB9DE\uCDB0\uC11C \uC791\uC131\uD574\uC918.
856
- \uCD94\uB860 \uAC15\uB3C4 \uC9C0\uCE68: ${reasoningInstruction}`;
1391
+ const ruleText = ruleSection.items.join(" ") || "(\uC5C6\uC74C)";
1392
+ const diffText = `\uB9AC\uBDF0 \uB300\uC0C1 diff ${toGeminiFileReference(resolvedTempDiffPath)}`;
1393
+ const prompt = [
1394
+ "\uC544\uB798 \uD30C\uC77C\uB4E4\uC744 \uC21C\uC11C\uB300\uB85C \uC77D\uACE0 \uCF54\uB4DC \uB9AC\uBDF0\uB97C \uC9C4\uD589\uD574\uC918.",
1395
+ `\uADDC\uCE59 \uD30C\uC77C: ${ruleText}`,
1396
+ `\uB9AC\uBDF0 \uC591\uC2DD \uD30C\uC77C: ${reviewFormText}`,
1397
+ `\uB9AC\uBDF0 \uB300\uC0C1 diff \uD30C\uC77C: ${diffText}`,
1398
+ "\uBC18\uB4DC\uC2DC \uB9AC\uBDF0 \uC591\uC2DD\uC5D0 \uB9DE\uCDB0 \uC791\uC131\uD574\uC918.",
1399
+ `\uCD94\uB860 \uAC15\uB3C4 \uC9C0\uCE68: ${reasoningInstruction}`
1400
+ ].join(" ");
857
1401
  trace5("prompt:prepared", `length=${prompt.length}`);
858
1402
  const modelCandidates = toUnique2(customModel ? [customModel, ...aliasFallbacks] : aliasFallbacks);
859
1403
  trace5("model:candidates", modelCandidates.join(", "));
860
1404
  trace5("command:candidates:count", String(modelCandidates.length + 1));
861
1405
  if (customModel) {
862
- console.warn(
1406
+ printNotice3(
863
1407
  `\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
864
1408
  " -> "
865
1409
  )}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
866
1410
  );
867
1411
  } else {
868
- console.warn(
1412
+ printNotice3(
869
1413
  `\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.`
870
1414
  );
871
1415
  }
@@ -916,14 +1460,24 @@ function checkGeminiCliInstalled() {
916
1460
  // src/pr-review/review.ts
917
1461
  async function main() {
918
1462
  const args4 = process.argv.slice(2);
1463
+ const startedAt = /* @__PURE__ */ new Date();
919
1464
  clearTraceMessages();
920
1465
  const isTest = isTestMode(args4);
1466
+ const shouldStreamOutput = shouldStreamAIOutput(args4);
921
1467
  const trace7 = createTraceLogger("review", args4);
922
1468
  trace7("main:start", `args=${JSON.stringify(args4)}`);
923
1469
  let command = "";
924
1470
  let savedDiffPath = "";
925
1471
  let savedReportPath = "";
926
1472
  let service = "";
1473
+ let selectedCommitSummary = "";
1474
+ let reviewTargetFiles = [];
1475
+ let executionLogPath = "";
1476
+ let executionStatus = "cancelled";
1477
+ let executionTitle = "\uB9AC\uBDF0 \uC2E4\uD589\uC774 \uCDE8\uC18C\uB418\uC5C8\uAC70\uB098 \uB9AC\uBDF0 \uB300\uC0C1\uC774 \uC5C6\uC5B4 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
1478
+ let executionError = null;
1479
+ let resultLength = 0;
1480
+ let exitCode = 0;
927
1481
  try {
928
1482
  trace7("service-selection:start");
929
1483
  service = await showSelectionAIService();
@@ -947,30 +1501,39 @@ async function main() {
947
1501
  }
948
1502
  trace7("review-flow:start");
949
1503
  console.log("\u{1F680} AI Code Review\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4...");
950
- const nowStr = getNowString();
951
- trace7("timestamp:created", nowStr);
952
- trace7("report-dir:create:start");
953
- createReportDirectory();
954
- trace7("report-dir:create:done");
955
- trace7("diff-args:build:start");
956
- const diffArgs = getDiffArgs();
957
- trace7("diff-args:build:done", `diffArgs=${diffArgs || "(default)"}`);
958
- let diff = "";
959
- const { includeParams, excludeParams } = getGitDiffFilter();
960
- trace7("diff-filter:loaded", `include=${includeParams} | exclude=${excludeParams}`);
961
- try {
962
- trace7("git-diff:run");
963
- diff = child_process.execSync(`git diff ${diffArgs} -- ${includeParams} ${excludeParams}`).toString();
964
- trace7("git-diff:done", `length=${diff.length}`);
965
- } catch (error) {
966
- trace7("git-diff:error", getErrorSummary(error));
1504
+ if (shouldStreamOutput) {
1505
+ console.log("\u2139\uFE0F AI \uC2E4\uC2DC\uAC04 \uC751\uB2F5 \uD45C\uC2DC \uBAA8\uB4DC\uAC00 \uD65C\uC131\uD654\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
967
1506
  }
1507
+ trace7("commit-selection:start");
1508
+ const selectedCommits = await selectReviewCommits();
1509
+ trace7("commit-selection:done", `count=${selectedCommits.length}`);
1510
+ if (selectedCommits.length === 0) {
1511
+ trace7("commit-selection:empty");
1512
+ executionTitle = "\uC120\uD0DD\uB41C \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.";
1513
+ console.log("\u2139\uFE0F \uC120\uD0DD\uB41C \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1514
+ deleteTempDiff();
1515
+ return;
1516
+ }
1517
+ selectedCommitSummary = buildSelectedCommitSummary(selectedCommits);
1518
+ trace7("commit-summary:prepared", selectedCommitSummary);
1519
+ reviewTargetFiles = getSelectedCommitFiles(selectedCommits);
1520
+ console.log(`\u{1F4C2} \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C(${reviewTargetFiles.length}\uAC1C): ${formatReviewTargetFiles(reviewTargetFiles)}`);
1521
+ console.log("\u23F3 \uC120\uD0DD\uD55C \uCEE4\uBC0B diff\uB97C \uC815\uB9AC\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...");
1522
+ trace7("git-diff:build:start");
1523
+ const diff = buildSelectedCommitDiff(selectedCommits);
1524
+ trace7("git-diff:build:done", `length=${diff.length}`);
968
1525
  if (!diff.trim() && !isTest) {
969
1526
  trace7("empty-diff:exit");
970
- console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (Unstaged Empty & HEAD Empty).");
1527
+ executionTitle = "\uC120\uD0DD\uD55C \uCEE4\uBC0B\uC5D0\uC11C \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C \uBCC0\uACBD\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.";
1528
+ 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.");
971
1529
  deleteTempDiff();
972
- process.exit(0);
1530
+ return;
973
1531
  }
1532
+ const nowStr = getNowString();
1533
+ trace7("timestamp:created", nowStr);
1534
+ trace7("report-dir:create:start");
1535
+ createReportDirectory();
1536
+ trace7("report-dir:create:done");
974
1537
  trace7("temp-diff:write:start", tempDiffPath);
975
1538
  fs__default.default.writeFileSync(tempDiffPath, diff);
976
1539
  trace7("temp-diff:write:done");
@@ -992,9 +1555,12 @@ async function main() {
992
1555
  }
993
1556
  trace7("command:create:done");
994
1557
  trace7("command:exec:start");
995
- const result = child_process.execSync(command).toString();
1558
+ const result = (await executeShellCommandWithProgress(command, {
1559
+ progressMessage: `\u23F3 [\uB9AC\uBDF0 \uC9C4\uD589] ${service} | \uB300\uC0C1 ${reviewTargetFiles.length}\uAC1C \uD30C\uC77C`,
1560
+ streamOutput: isTest || shouldStreamOutput
1561
+ })).stdout;
1562
+ resultLength = result.length;
996
1563
  trace7("command:exec:done", `resultLength=${result.length}`);
997
- console.log(result);
998
1564
  trace7("report:write:start");
999
1565
  savedReportPath = getNextFilePath(REPORT_DIR, nowStr, ".md");
1000
1566
  fs__default.default.writeFileSync(savedReportPath, result);
@@ -1019,9 +1585,15 @@ ${command}`);
1019
1585
  deleteTempDiff();
1020
1586
  trace7("cleanup-temp-diff:done");
1021
1587
  trace7("review-flow:end");
1588
+ executionStatus = "success";
1589
+ executionTitle = "\uB9AC\uBDF0\uAC00 \uC131\uACF5\uC801\uC73C\uB85C \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
1022
1590
  } catch (error) {
1023
1591
  trace7("review-flow:catch", getErrorSummary(error));
1024
1592
  let errorReportPath = "";
1593
+ executionStatus = "failed";
1594
+ executionTitle = "\uB9AC\uBDF0 \uC2E4\uD589 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
1595
+ executionError = error;
1596
+ exitCode = 1;
1025
1597
  trace7("cleanup-temp-diff:start(catch)");
1026
1598
  try {
1027
1599
  deleteTempDiff();
@@ -1042,6 +1614,8 @@ ${command}`);
1042
1614
  ${JSON.stringify(
1043
1615
  {
1044
1616
  service,
1617
+ selectedCommitSummary: selectedCommitSummary || null,
1618
+ reviewTargetFiles,
1045
1619
  command: command || null,
1046
1620
  tempDiffPath,
1047
1621
  savedDiffPath: savedDiffPath || null,
@@ -1059,7 +1633,50 @@ ${JSON.stringify(
1059
1633
  if (errorReportPath) {
1060
1634
  console.error(`\u{1F4C4} \uC5D0\uB7EC \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${errorReportPath}`);
1061
1635
  }
1062
- process.exit(1);
1636
+ } finally {
1637
+ executionLogPath = writeExecutionLog({
1638
+ scope: "review",
1639
+ status: executionStatus,
1640
+ title: executionTitle,
1641
+ args: args4,
1642
+ startedAt,
1643
+ error: executionError,
1644
+ extraSections: [
1645
+ {
1646
+ heading: "Execution Context",
1647
+ markdown: `\`\`\`json
1648
+ ${JSON.stringify(
1649
+ {
1650
+ service: service || null,
1651
+ selectedCommitSummary: selectedCommitSummary || null,
1652
+ reviewTargetFiles,
1653
+ command: command || null,
1654
+ tempDiffPath,
1655
+ savedDiffPath: savedDiffPath || null,
1656
+ savedReportPath: savedReportPath || null,
1657
+ shouldStreamOutput,
1658
+ resultLength
1659
+ },
1660
+ null,
1661
+ 2
1662
+ )}
1663
+ \`\`\``
1664
+ },
1665
+ {
1666
+ heading: "Generated Command",
1667
+ markdown: command ? `\`\`\`sh
1668
+ ${command}
1669
+ \`\`\`` : "(\uC5C6\uC74C)"
1670
+ }
1671
+ ]
1672
+ });
1673
+ if (executionLogPath) {
1674
+ const writeLog = executionStatus === "failed" ? console.error : console.log;
1675
+ writeLog(`\u{1F4DD} \uC2E4\uD589 \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${executionLogPath}`);
1676
+ }
1677
+ }
1678
+ if (exitCode !== 0) {
1679
+ process.exit(exitCode);
1063
1680
  }
1064
1681
  }
1065
1682
  main();