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,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { execSync } from 'child_process';
3
2
  import fs from 'fs';
3
+ import { execSync, execFileSync } from 'child_process';
4
4
  import path from 'path';
5
5
  import readline from 'readline';
6
6
  import { fileURLToPath } from 'url';
@@ -8,14 +8,52 @@ import { inspect } from 'util';
8
8
 
9
9
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
10
10
  var traceMessages = [];
11
- var rulesPath = path.resolve(__dirname, "../../src/common/rules/review-rules.md");
12
- var namingRulesPath = path.resolve(__dirname, "../../src/common/rules/naming-rule.md");
13
- var codingConventionRulesPath = path.resolve(__dirname, "../../src/common/rules/coding-convention.md");
14
- var reviewFormPath = path.resolve(__dirname, "../../src/common/form/review-form.md");
15
- path.resolve(__dirname, "../../src/common/form/review-form-one-by-one.md");
11
+ var GEMINI_CLI_PACKAGE_NAME = "sales-frontend-gemini-cli";
12
+ var cachedPackageRootPath = "";
13
+ function isGeminiCliPackageRoot(directory) {
14
+ const packageJsonPath = path.join(directory, "package.json");
15
+ if (!fs.existsSync(packageJsonPath)) {
16
+ return false;
17
+ }
18
+ try {
19
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
20
+ return packageJson.name === GEMINI_CLI_PACKAGE_NAME;
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+ function resolveGeminiCliPackageRoot(startDirectory = __dirname) {
26
+ if (cachedPackageRootPath) {
27
+ return cachedPackageRootPath;
28
+ }
29
+ let currentDirectory = startDirectory;
30
+ while (true) {
31
+ if (isGeminiCliPackageRoot(currentDirectory)) {
32
+ cachedPackageRootPath = currentDirectory;
33
+ return cachedPackageRootPath;
34
+ }
35
+ const parentDirectory = path.dirname(currentDirectory);
36
+ if (parentDirectory === currentDirectory) {
37
+ break;
38
+ }
39
+ currentDirectory = parentDirectory;
40
+ }
41
+ cachedPackageRootPath = path.resolve(startDirectory, "../..");
42
+ return cachedPackageRootPath;
43
+ }
44
+ function resolvePackageAssetPath(relativeFilePath) {
45
+ return path.resolve(resolveGeminiCliPackageRoot(), relativeFilePath);
46
+ }
47
+ var rulesPath = resolvePackageAssetPath("src/common/rules/review-rules.md");
48
+ var namingRulesPath = resolvePackageAssetPath("src/common/rules/naming-rule.md");
49
+ var codingConventionRulesPath = resolvePackageAssetPath("src/common/rules/coding-convention.md");
50
+ var reviewFormPath = resolvePackageAssetPath("src/common/form/review-form.md");
51
+ resolvePackageAssetPath("src/common/form/review-form-one-by-one.md");
16
52
  var REPORT_DIR = ".review-report";
17
53
  var tempDiffPath = "temp_diff.txt";
18
54
  var AIServices = ["gemini", "claude", "codex"];
55
+ var COMMIT_FETCH_LIMIT = 20;
56
+ var COMMIT_SELECTION_WINDOW = 8;
19
57
  var ignoreList = [
20
58
  "package.json",
21
59
  "*.yml",
@@ -33,12 +71,224 @@ var ignoreList = [
33
71
  function isTestMode(args4 = process.argv.slice(2)) {
34
72
  return args4.includes("--test");
35
73
  }
74
+ function shouldStreamAIOutput(args4 = process.argv.slice(2)) {
75
+ return args4.includes("--stream-output");
76
+ }
36
77
  function clearTraceMessages() {
37
78
  traceMessages.length = 0;
38
79
  }
39
80
  function getTraceMessages() {
40
81
  return [...traceMessages];
41
82
  }
83
+ var ANSI = {
84
+ bold: "\x1B[1m",
85
+ cyan: "\x1B[36m",
86
+ dim: "\x1B[2m",
87
+ green: "\x1B[32m",
88
+ reset: "\x1B[0m",
89
+ yellow: "\x1B[33m"
90
+ };
91
+ var ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
92
+ var COMBINING_MARK_PATTERN = /\p{Mark}/u;
93
+ var GRAPHEME_SEGMENTER = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter("ko", { granularity: "grapheme" }) : null;
94
+ function getGitDiffPathspecs() {
95
+ return {
96
+ excludePatterns: ignoreList.map((item) => `:(exclude)${item}`),
97
+ includePatterns: ["*.ts", "*.tsx", "*.js", "*.jsx"]
98
+ };
99
+ }
100
+ function segmentGraphemes(value) {
101
+ if (!GRAPHEME_SEGMENTER) {
102
+ return [...value];
103
+ }
104
+ return [...GRAPHEME_SEGMENTER.segment(value)].map(({ segment }) => segment);
105
+ }
106
+ function isWideCodePoint(codePoint) {
107
+ 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);
108
+ }
109
+ function isEmojiCodePoint(codePoint) {
110
+ return codePoint >= 127462 && codePoint <= 127487 || codePoint >= 127744 && codePoint <= 129791 || codePoint >= 9728 && codePoint <= 10175;
111
+ }
112
+ function getGraphemeWidth(grapheme) {
113
+ let width = 0;
114
+ for (const character of grapheme) {
115
+ const codePoint = character.codePointAt(0);
116
+ if (!codePoint || COMBINING_MARK_PATTERN.test(character) || codePoint === 8205) {
117
+ continue;
118
+ }
119
+ if (codePoint >= 65024 && codePoint <= 65039 || codePoint >= 917760 && codePoint <= 917999) {
120
+ continue;
121
+ }
122
+ if (isWideCodePoint(codePoint) || isEmojiCodePoint(codePoint)) {
123
+ width = Math.max(width, 2);
124
+ continue;
125
+ }
126
+ width = Math.max(width, 1);
127
+ }
128
+ return width;
129
+ }
130
+ function tokenizePlainText(value) {
131
+ return segmentGraphemes(value).map((segment) => ({
132
+ value: segment,
133
+ visibleWidth: getGraphemeWidth(segment)
134
+ }));
135
+ }
136
+ function tokenizeVisibleText(value) {
137
+ const tokens = [];
138
+ let lastIndex = 0;
139
+ for (const match of value.matchAll(ANSI_PATTERN)) {
140
+ const index = match.index ?? 0;
141
+ if (index > lastIndex) {
142
+ tokens.push(...tokenizePlainText(value.slice(lastIndex, index)));
143
+ }
144
+ tokens.push({
145
+ value: match[0],
146
+ visibleWidth: 0
147
+ });
148
+ lastIndex = index + match[0].length;
149
+ }
150
+ if (lastIndex < value.length) {
151
+ tokens.push(...tokenizePlainText(value.slice(lastIndex)));
152
+ }
153
+ return tokens;
154
+ }
155
+ function truncateLineForTerminal(value, maxWidth) {
156
+ if (maxWidth <= 0) {
157
+ return "";
158
+ }
159
+ const tokens = tokenizeVisibleText(value);
160
+ const totalWidth = tokens.reduce((sum, token) => sum + token.visibleWidth, 0);
161
+ if (totalWidth <= maxWidth) {
162
+ return value;
163
+ }
164
+ const ellipsis = "...";
165
+ const ellipsisWidth = 3;
166
+ const targetWidth = Math.max(0, maxWidth - ellipsisWidth);
167
+ let usedWidth = 0;
168
+ let result = "";
169
+ for (const token of tokens) {
170
+ if (token.visibleWidth === 0) {
171
+ result += token.value;
172
+ continue;
173
+ }
174
+ if (usedWidth + token.visibleWidth > targetWidth) {
175
+ break;
176
+ }
177
+ result += token.value;
178
+ usedWidth += token.visibleWidth;
179
+ }
180
+ return `${result}${ellipsis}${ANSI.reset}`;
181
+ }
182
+ function fitLinesToTerminal(lines) {
183
+ const maxWidth = Math.max(20, (process.stdout.columns || 120) - 1);
184
+ return lines.map((line) => truncateLineForTerminal(line, maxWidth));
185
+ }
186
+ function runGitCommand(args4, options = {}) {
187
+ const { allowFailure = false, trimOutput = true } = options;
188
+ try {
189
+ const output = execFileSync("git", args4, {
190
+ encoding: "utf8",
191
+ maxBuffer: 1024 * 1024 * 20,
192
+ stdio: ["ignore", "pipe", "pipe"]
193
+ });
194
+ return trimOutput ? output.trim() : output;
195
+ } catch (error) {
196
+ helperTrace("git-command:failed", `${args4.join(" ")} | ${getErrorSummary(error)}`);
197
+ if (allowFailure) {
198
+ return "";
199
+ }
200
+ throw error;
201
+ }
202
+ }
203
+ async function executeShellCommandWithProgress(command, options = {}) {
204
+ const { progressIntervalMs = 1e4, progressMessage = "\u23F3 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...", streamOutput = false } = options;
205
+ const { spawn } = await import('child_process');
206
+ return new Promise((resolve, reject) => {
207
+ let stdout = "";
208
+ let stderr = "";
209
+ const startedAt = Date.now();
210
+ console.log(progressMessage);
211
+ const child = spawn("/bin/zsh", ["-lc", command], {
212
+ stdio: ["ignore", "pipe", "pipe"]
213
+ });
214
+ const progressTimer = setInterval(() => {
215
+ const elapsedSeconds = Math.max(1, Math.floor((Date.now() - startedAt) / 1e3));
216
+ console.log(`${progressMessage} (${elapsedSeconds}s \uACBD\uACFC)`);
217
+ }, progressIntervalMs);
218
+ child.stdout.on("data", (chunk) => {
219
+ const text = chunk.toString();
220
+ stdout += text;
221
+ if (streamOutput) {
222
+ process.stdout.write(text);
223
+ }
224
+ });
225
+ child.stderr.on("data", (chunk) => {
226
+ const text = chunk.toString();
227
+ stderr += text;
228
+ if (streamOutput) {
229
+ process.stderr.write(text);
230
+ }
231
+ });
232
+ child.on("error", (error) => {
233
+ clearInterval(progressTimer);
234
+ reject(error);
235
+ });
236
+ child.on("close", (code, signal) => {
237
+ clearInterval(progressTimer);
238
+ if (code === 0) {
239
+ resolve({
240
+ stderr,
241
+ stdout
242
+ });
243
+ return;
244
+ }
245
+ const exitSummary = signal ? `signal=${signal}` : `code=${String(code ?? "unknown")}`;
246
+ const failureDetails = {
247
+ code,
248
+ command,
249
+ signal,
250
+ stderr,
251
+ stdout
252
+ };
253
+ reject(createShellCommandExecutionError(failureDetails, exitSummary));
254
+ });
255
+ });
256
+ }
257
+ function getShellCommandFailurePreview(failureDetails) {
258
+ const stderrText = failureDetails.stderr.trim();
259
+ const stdoutText = failureDetails.stdout.trim();
260
+ const combinedOutput = stderrText || stdoutText;
261
+ if (!combinedOutput) {
262
+ return "";
263
+ }
264
+ const MAX_PREVIEW_LENGTH = 4e3;
265
+ if (combinedOutput.length <= MAX_PREVIEW_LENGTH) {
266
+ return combinedOutput;
267
+ }
268
+ return combinedOutput.slice(-4e3);
269
+ }
270
+ function createShellCommandExecutionError(failureDetails, exitSummary) {
271
+ const failurePreview = getShellCommandFailurePreview(failureDetails);
272
+ const error = new Error(`\uC258 \uBA85\uB839 \uC2E4\uD589 \uC2E4\uD328 (${exitSummary})${failurePreview ? `
273
+ ${failurePreview}` : ""}`);
274
+ error.code = failureDetails.code;
275
+ error.signal = failureDetails.signal;
276
+ error.stdout = failureDetails.stdout;
277
+ error.stderr = failureDetails.stderr;
278
+ error.command = failureDetails.command;
279
+ return error;
280
+ }
281
+ function formatReviewTargetFiles(files, visibleCount = 5) {
282
+ if (files.length === 0) {
283
+ return "(\uC5C6\uC74C)";
284
+ }
285
+ const visibleFiles = files.slice(0, visibleCount);
286
+ const hiddenCount = Math.max(0, files.length - visibleFiles.length);
287
+ if (hiddenCount === 0) {
288
+ return visibleFiles.join(", ");
289
+ }
290
+ return `${visibleFiles.join(", ")} \uC678 ${hiddenCount}\uAC1C`;
291
+ }
42
292
  function createTraceLogger(scope, args4 = process.argv.slice(2)) {
43
293
  const enabled = isTestMode(args4);
44
294
  return (step, detail) => {
@@ -100,7 +350,7 @@ function serializeError(error) {
100
350
  }
101
351
  if (error && typeof error === "object") {
102
352
  const errorLike = error;
103
- const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs"];
353
+ const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs", "command"];
104
354
  extraKeys.forEach((key) => {
105
355
  if (errorLike[key] !== void 0) {
106
356
  serialized[key] = errorLike[key];
@@ -206,6 +456,87 @@ ${section.markdown}`).join("\n")}
206
456
  return "";
207
457
  }
208
458
  }
459
+ function getExecutionLogSummary(status, title) {
460
+ if (title) {
461
+ return title;
462
+ }
463
+ switch (status) {
464
+ case "success":
465
+ return "\uB9AC\uBDF0 \uC2E4\uD589\uC774 \uC131\uACF5\uC801\uC73C\uB85C \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
466
+ case "failed":
467
+ return "\uB9AC\uBDF0 \uC2E4\uD589 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
468
+ case "partial_failure":
469
+ 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.";
470
+ default:
471
+ 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.";
472
+ }
473
+ }
474
+ function formatExecutionDuration(startedAt, finishedAt) {
475
+ const durationMs = Math.max(0, finishedAt.getTime() - startedAt.getTime());
476
+ if (durationMs < 1e3) {
477
+ return `${durationMs}ms`;
478
+ }
479
+ const durationSeconds = durationMs / 1e3;
480
+ if (durationSeconds < 60) {
481
+ return `${durationSeconds.toFixed(1)}s`;
482
+ }
483
+ const minutes = Math.floor(durationSeconds / 60);
484
+ const seconds = Math.round(durationSeconds % 60);
485
+ return `${minutes}m ${seconds}s`;
486
+ }
487
+ function writeExecutionLog(options = {}) {
488
+ try {
489
+ const startedAt = options.startedAt ?? /* @__PURE__ */ new Date();
490
+ const finishedAt = options.finishedAt ?? /* @__PURE__ */ new Date();
491
+ const status = options.status ?? "success";
492
+ helperTrace("execution-log:write:start", options.scope || "unknown");
493
+ createReportDirectory();
494
+ const reportPath = getAvailableFilePath(REPORT_DIR, `${getNowString(finishedAt)}-execution-log`, ".md");
495
+ const traceSnapshot = options.traceMessages ?? getTraceMessages();
496
+ const extraSections = options.extraSections || [];
497
+ const serializedError = options.error ? serializeError(options.error) : null;
498
+ const report = `# Execution Log
499
+
500
+ - \uC2DC\uC791 \uC2DC\uAC01: ${getHumanReadableNowString(startedAt)}
501
+ - \uC885\uB8CC \uC2DC\uAC01: ${getHumanReadableNowString(finishedAt)}
502
+ - \uC2E4\uD589 \uC2DC\uAC04: ${formatExecutionDuration(startedAt, finishedAt)}
503
+ - \uC0C1\uD0DC: \`${status}\`
504
+ - Scope: \`${options.scope || "unknown"}\`
505
+ - \uC791\uC5C5 \uACBD\uB85C: \`${process.cwd()}\`
506
+ - \uC2E4\uD589 \uC778\uC790: \`${JSON.stringify(options.args ?? process.argv.slice(2))}\`
507
+ - \uC2E4\uD589 \uD658\uACBD: \`${process.platform} ${process.arch} / Node ${process.version}\`
508
+
509
+ ## Summary
510
+
511
+ ${getExecutionLogSummary(status, options.title)}
512
+ ${serializedError ? `
513
+
514
+ ## Error
515
+
516
+ \`\`\`json
517
+ ${JSON.stringify(serializedError, null, 2)}
518
+ \`\`\`` : ""}
519
+
520
+ ## Trace
521
+
522
+ \`\`\`json
523
+ ${JSON.stringify(traceSnapshot, null, 2)}
524
+ \`\`\`${extraSections.length ? `
525
+ ${extraSections.map((section) => `
526
+ ## ${section.heading}
527
+
528
+ ${section.markdown}`).join("\n")}
529
+ ` : "\n"}
530
+ `;
531
+ fs.writeFileSync(reportPath, report);
532
+ helperTrace("execution-log:write:done", reportPath);
533
+ return reportPath;
534
+ } catch (writeError) {
535
+ console.error("\u26A0\uFE0F \uC2E4\uD589 \uB85C\uADF8 \uD30C\uC77C \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
536
+ console.error(writeError);
537
+ return "";
538
+ }
539
+ }
209
540
  function exitWithError(message, options = {}) {
210
541
  const reportPath = writeErrorReport(options.error || new Error(message), {
211
542
  ...options,
@@ -250,13 +581,67 @@ ${JSON.stringify(AIServices, null, 2)}
250
581
  }
251
582
  );
252
583
  }
253
- function getGitDiffFilter() {
254
- const includeExtensions = ["*.ts", "*.tsx", "*.js", "*.jsx"];
255
- const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);
256
- const quote = (pattern) => `"${pattern}"`;
257
- const includeParams = includeExtensions.map(quote).join(" ");
258
- const excludeParams = excludePatterns.map(quote).join(" ");
259
- return { includeParams, excludeParams };
584
+ function truncateCommitSubject(subject) {
585
+ if (subject.length <= 72) {
586
+ return subject;
587
+ }
588
+ return `${subject.slice(0, 69)}...`;
589
+ }
590
+ function getRecentCommitOptions() {
591
+ const output = runGitCommand(
592
+ ["log", `-${COMMIT_FETCH_LIMIT}`, "--date=relative", "--pretty=format:%h%x09%an%x09%ar%x09%s"],
593
+ { allowFailure: true }
594
+ );
595
+ if (!output) {
596
+ return [];
597
+ }
598
+ return output.split("\n").map((line) => {
599
+ const [hash = "", author = "", relativeDate = "", ...subjectParts] = line.split(" ");
600
+ const subject = subjectParts.join(" ").trim();
601
+ return {
602
+ author,
603
+ description: `${author} | ${relativeDate}`,
604
+ hash,
605
+ label: `${hash} | ${truncateCommitSubject(subject)}`,
606
+ relativeDate,
607
+ subject
608
+ };
609
+ });
610
+ }
611
+ function buildSelectedCommitSummary(commits) {
612
+ return commits.map((commit) => `- ${commit.hash} | ${commit.subject} | ${commit.author} | ${commit.relativeDate}`).join("\n");
613
+ }
614
+ function getReviewPathspecArgs() {
615
+ const { includePatterns, excludePatterns } = getGitDiffPathspecs();
616
+ return [...includePatterns, ...excludePatterns];
617
+ }
618
+ function buildSelectedCommitDiff(commits) {
619
+ const reviewPathspecArgs = getReviewPathspecArgs();
620
+ const sections = commits.map((commit) => {
621
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", ...reviewPathspecArgs], {
622
+ allowFailure: true,
623
+ trimOutput: false
624
+ }).trim();
625
+ if (!diff) {
626
+ return "";
627
+ }
628
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
629
+ }).filter(Boolean).join("\n\n");
630
+ if (!sections) {
631
+ return "";
632
+ }
633
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", "# \uB9AC\uBDF0 \uB300\uC0C1 diff", sections].join("\n");
634
+ }
635
+ function getSelectedCommitFiles(commits) {
636
+ const reviewPathspecArgs = getReviewPathspecArgs();
637
+ const files = /* @__PURE__ */ new Set();
638
+ commits.forEach((commit) => {
639
+ const output = runGitCommand(["show", "--pretty=format:", "--name-only", commit.hash, "--", ...reviewPathspecArgs], {
640
+ allowFailure: true
641
+ });
642
+ output.split("\n").map((line) => line.trim()).filter(Boolean).forEach((filePath) => files.add(filePath));
643
+ });
644
+ return [...files];
260
645
  }
261
646
  function openReport(reportPath) {
262
647
  const resolvedPath = path.resolve(reportPath);
@@ -307,59 +692,166 @@ function openReport(reportPath) {
307
692
  helperTrace("open-report:unsupported-platform", platform);
308
693
  console.error(`\u26A0\uFE0F \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD50C\uB7AB\uD3FC\uC785\uB2C8\uB2E4: ${platform}`);
309
694
  }
310
- function getDiffArgs() {
311
- const args4 = process.argv.slice(2);
312
- const commitIndex = args4.indexOf("--commit");
313
- const { includeParams, excludeParams } = getGitDiffFilter();
314
- helperTrace("diff-args:resolve:start", `args=${JSON.stringify(args4)}`);
315
- let diffArgs = "";
316
- if (commitIndex !== -1) {
317
- const commitHash = args4[commitIndex + 1];
318
- if (!commitHash) {
319
- helperTrace("diff-args:commit-hash-missing");
320
- exitWithError("\u274C \uCEE4\uBC0B \uD574\uC2DC\uAC00 \uC81C\uACF5\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.", {
321
- scope: "helper:getDiffArgs",
322
- args: args4
323
- });
695
+ function ensureInteractiveSelectionAvailable(scope, message) {
696
+ if (process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function") {
697
+ return;
698
+ }
699
+ helperTrace(`${scope}:tty-missing`);
700
+ exitWithError(message, {
701
+ scope: `helper:${scope}`
702
+ });
703
+ }
704
+ function renderSelectionBlock(lines, previousLineCount) {
705
+ if (previousLineCount > 0) {
706
+ readline.moveCursor(process.stdout, 0, -previousLineCount);
707
+ readline.clearScreenDown(process.stdout);
708
+ }
709
+ const fittedLines = fitLinesToTerminal(lines);
710
+ process.stdout.write(`${fittedLines.join("\n")}
711
+ `);
712
+ return fittedLines.length;
713
+ }
714
+ function getSelectionWindowRange(optionCount, selectedIndex, windowSize) {
715
+ if (optionCount <= windowSize) {
716
+ return {
717
+ end: optionCount,
718
+ start: 0
719
+ };
720
+ }
721
+ const halfWindow = Math.floor(windowSize / 2);
722
+ const maxStart = optionCount - windowSize;
723
+ const start = Math.max(0, Math.min(selectedIndex - halfWindow, maxStart));
724
+ return {
725
+ end: Math.min(optionCount, start + windowSize),
726
+ start
727
+ };
728
+ }
729
+ function buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize) {
730
+ const { start, end } = getSelectionWindowRange(options.length, selectedIndex, windowSize);
731
+ const lines = [
732
+ `${ANSI.bold}${question}${ANSI.reset}`,
733
+ `${ANSI.dim}\u2191\u2193 \uC774\uB3D9 | Space \uC120\uD0DD/\uD574\uC81C | Enter \uC644\uB8CC | Esc \uCDE8\uC18C${ANSI.reset}`,
734
+ `${ANSI.dim}\uC120\uD0DD\uB428: ${toggled.size}\uAC1C / \uC804\uCCB4: ${options.length}\uAC1C${ANSI.reset}`
735
+ ];
736
+ for (let index = start; index < end; index += 1) {
737
+ const option = options[index];
738
+ if (!option) {
739
+ continue;
324
740
  }
325
- const nextArg = args4[commitIndex + 2];
326
- let n = 0;
327
- if (nextArg && !nextArg.startsWith("--")) {
328
- n = parseInt(nextArg, 10);
329
- if (isNaN(n)) {
330
- n = 0;
331
- }
741
+ const cursor = index === selectedIndex ? `${ANSI.cyan}>${ANSI.reset}` : " ";
742
+ const checked = toggled.has(index) ? `${ANSI.green}\u2611${ANSI.reset}` : "\u2610";
743
+ const description = option.description ? ` ${ANSI.dim}${option.description}${ANSI.reset}` : "";
744
+ lines.push(`${cursor} ${checked} ${option.label}${description}`);
745
+ }
746
+ if (options.length > windowSize) {
747
+ lines.push(`${ANSI.dim}\uD45C\uC2DC \uBC94\uC704: ${start + 1}-${end} / ${options.length}${ANSI.reset}`);
748
+ }
749
+ return lines;
750
+ }
751
+ async function showMultiSelect(question, options, windowSize = COMMIT_SELECTION_WINDOW) {
752
+ ensureInteractiveSelectionAvailable("showMultiSelect", "\u274C \uCEE4\uBC0B \uC120\uD0DD \uBAA8\uB2EC\uC740 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
753
+ let selectedIndex = 0;
754
+ let renderedLineCount = 0;
755
+ const toggled = /* @__PURE__ */ new Set();
756
+ const rl = readline.createInterface({
757
+ input: process.stdin,
758
+ output: process.stdout,
759
+ terminal: true
760
+ });
761
+ process.stdout.write("\x1B[?25l");
762
+ const cleanup = () => {
763
+ if (renderedLineCount > 0) {
764
+ readline.moveCursor(process.stdout, 0, -renderedLineCount);
765
+ readline.clearScreenDown(process.stdout);
766
+ renderedLineCount = 0;
332
767
  }
333
- helperTrace("diff-args:commit-mode", `${commitHash}~${n + 1} ${commitHash}`);
334
- console.log(`\u2139\uFE0F \uCEE4\uBC0B '${commitHash}' ${n > 0 ? ` \uD3EC\uD568 \uCD1D ${n + 1}\uAC1C\uC758 \uCEE4\uBC0B` : ""}\uC744 \uB9AC\uBDF0\uD569\uB2C8\uB2E4...`);
335
- diffArgs = `${commitHash}~${n + 1} ${commitHash}`;
336
- } else {
337
- try {
338
- helperTrace("diff-args:unstaged-check:start");
339
- const check = execSync(`git diff --name-only -- ${includeParams} ${excludeParams}`).toString();
340
- if (!check.trim()) {
341
- helperTrace("diff-args:unstaged-check:empty", "use HEAD~1 HEAD");
342
- 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...");
343
- diffArgs = "HEAD~1 HEAD";
344
- } else {
345
- helperTrace("diff-args:unstaged-check:has-changes", `length=${check.length}`);
768
+ process.stdin.removeListener("data", onData);
769
+ process.stdin.setRawMode(false);
770
+ process.stdin.pause();
771
+ rl.close();
772
+ process.stdout.write("\x1B[?25h");
773
+ };
774
+ const render = () => {
775
+ const lines = buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize);
776
+ renderedLineCount = renderSelectionBlock(lines, renderedLineCount);
777
+ };
778
+ const confirmSelection = (resolve) => {
779
+ const values = [...toggled].sort((left, right) => left - right).map((index) => options[index]?.value).filter((value) => value !== void 0);
780
+ cleanup();
781
+ resolve(values);
782
+ };
783
+ const cancelSelection = (resolve) => {
784
+ cleanup();
785
+ resolve([]);
786
+ };
787
+ let onData = (_data) => {
788
+ };
789
+ render();
790
+ return new Promise((resolve) => {
791
+ onData = (data) => {
792
+ const key = data.toString();
793
+ if (key === "") {
794
+ cleanup();
795
+ process.exit(0);
346
796
  }
347
- } catch (error) {
348
- helperTrace("diff-args:unstaged-check:failed", getErrorSummary(error));
349
- }
797
+ if (key === "\x1B") {
798
+ cancelSelection(resolve);
799
+ return;
800
+ }
801
+ if (key === "\x1B[A") {
802
+ selectedIndex = (selectedIndex - 1 + options.length) % options.length;
803
+ render();
804
+ return;
805
+ }
806
+ if (key === "\x1B[B") {
807
+ selectedIndex = (selectedIndex + 1) % options.length;
808
+ render();
809
+ return;
810
+ }
811
+ if (key === " ") {
812
+ if (toggled.has(selectedIndex)) {
813
+ toggled.delete(selectedIndex);
814
+ } else {
815
+ toggled.add(selectedIndex);
816
+ }
817
+ render();
818
+ return;
819
+ }
820
+ if (key === "\r" || key === "\n") {
821
+ confirmSelection(resolve);
822
+ }
823
+ };
824
+ process.stdin.setRawMode(true);
825
+ process.stdin.resume();
826
+ process.stdin.on("data", onData);
827
+ });
828
+ }
829
+ async function selectReviewCommits() {
830
+ const commits = getRecentCommitOptions();
831
+ if (commits.length === 0) {
832
+ console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uCD5C\uADFC \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
833
+ return [];
350
834
  }
351
- helperTrace("diff-args:resolve:done", diffArgs || "(default)");
352
- return diffArgs;
835
+ return showMultiSelect(
836
+ "\uB9AC\uBDF0\uD560 \uCEE4\uBC0B\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.",
837
+ commits.map((commit) => ({
838
+ description: commit.description,
839
+ label: commit.label,
840
+ value: commit
841
+ })),
842
+ COMMIT_SELECTION_WINDOW
843
+ );
353
844
  }
354
845
  async function showSelectionAIService() {
355
846
  const selectedServiceFromArgs = parseServiceFromArgs();
356
847
  if (selectedServiceFromArgs) {
357
848
  helperTrace("show-selection:from-args", selectedServiceFromArgs);
358
849
  console.log(`
359
- \u2705 \x1B[32m${selectedServiceFromArgs}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
850
+ \u2705 ${ANSI.green}${selectedServiceFromArgs}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
360
851
  `);
361
852
  return selectedServiceFromArgs;
362
853
  }
854
+ 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.");
363
855
  helperTrace("show-selection:interactive:start");
364
856
  let selectedIndex = 0;
365
857
  const rl = readline.createInterface({
@@ -377,11 +869,12 @@ async function showSelectionAIService() {
377
869
  helperTrace("show-selection:interactive:render", AIServices[selectedIndex] || "unknown");
378
870
  readline.clearScreenDown(process.stdout);
379
871
  process.stdout.write(
380
- "\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"
872
+ `\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):
873
+ `
381
874
  );
382
875
  AIServices.forEach((service, index) => {
383
876
  if (index === selectedIndex) {
384
- process.stdout.write(` \x1B[36m>\x1B[0m \x1B[36m\u25C9\x1B[0m \x1B[1m${service}\x1B[0m
877
+ process.stdout.write(` ${ANSI.cyan}>${ANSI.reset} ${ANSI.cyan}\u25C9${ANSI.reset} ${ANSI.bold}${service}${ANSI.reset}
385
878
  `);
386
879
  } else {
387
880
  process.stdout.write(` \u25EF ${service}
@@ -411,7 +904,7 @@ async function showSelectionAIService() {
411
904
  rl.close();
412
905
  process.stdout.write("\x1B[?25h");
413
906
  console.log(`
414
- \u2705 \x1B[32m${AIServices[selectedIndex]}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
907
+ \u2705 ${ANSI.green}${AIServices[selectedIndex]}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
415
908
  `);
416
909
  const result = AIServices[selectedIndex];
417
910
  if (result) {
@@ -431,6 +924,13 @@ var ALLOWED_REASONING_EFFORTS = ["minimal", "low", "medium", "high"];
431
924
  function shellQuote(value) {
432
925
  return `'${value.replace(/'/g, `'\\''`)}'`;
433
926
  }
927
+ function toShellOptionToken(value) {
928
+ const SIMPLE_SHELL_TOKEN_PATTERN = /^[A-Za-z0-9._:/=-]+$/;
929
+ if (SIMPLE_SHELL_TOKEN_PATTERN.test(value)) {
930
+ return value;
931
+ }
932
+ return shellQuote(value);
933
+ }
434
934
  function getArgValue(flag) {
435
935
  const index = args.indexOf(flag);
436
936
  if (index === -1 || !args[index + 1]) {
@@ -438,6 +938,11 @@ function getArgValue(flag) {
438
938
  }
439
939
  return args[index + 1];
440
940
  }
941
+ function printNotice(message) {
942
+ if (args.includes("--test")) {
943
+ console.warn(message);
944
+ }
945
+ }
441
946
  function toUnique(values) {
442
947
  const seen = /* @__PURE__ */ new Set();
443
948
  return values.filter((value) => {
@@ -461,11 +966,11 @@ function resolveReasoningEffort() {
461
966
  const normalized = normalizeEffort(customReasoningEffort);
462
967
  trace("reasoning:custom", `${customReasoningEffort} -> ${normalized}`);
463
968
  if (customReasoningEffort === "minimal") {
464
- console.warn("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
969
+ printNotice("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
465
970
  }
466
971
  return normalized;
467
972
  }
468
- console.warn(
973
+ printNotice(
469
974
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS.join(
470
975
  ", "
471
976
  )}`
@@ -505,10 +1010,10 @@ function getAliasFallbacks(primaryAlias) {
505
1010
  }
506
1011
  function buildClaudeExecCommand(options) {
507
1012
  const { tempDiffPath: tempDiffPath2, prompt, systemPromptFiles, effort, model, fallbackModel } = options;
508
- const modelOption = model ? `--model ${shellQuote(model)}` : "";
509
- const fallbackOption = model && fallbackModel ? `--fallback-model ${shellQuote(fallbackModel)}` : "";
510
- const effortOption = `--effort ${shellQuote(effort)}`;
511
- const appendedPromptFiles = systemPromptFiles.map((path2) => `--append-system-prompt-file ${shellQuote(path2)}`).join(" ");
1013
+ const modelOption = model ? `--model ${toShellOptionToken(model)}` : "";
1014
+ const fallbackOption = model && fallbackModel ? `--fallback-model ${toShellOptionToken(fallbackModel)}` : "";
1015
+ const effortOption = `--effort ${toShellOptionToken(effort)}`;
1016
+ const appendedPromptFiles = systemPromptFiles.map((path3) => `--append-system-prompt-file ${shellQuote(path3)}`).join(" ");
512
1017
  return `cat ${shellQuote(tempDiffPath2)} | claude ${[
513
1018
  modelOption,
514
1019
  fallbackOption,
@@ -541,13 +1046,13 @@ var createClaudeCommand = (tempDiffPath2, reviewFormPath2) => {
541
1046
  trace("model:candidates", modelCandidates.join(", "));
542
1047
  trace("command:candidates:count", String(modelCandidates.length + 1));
543
1048
  if (customModel) {
544
- console.warn(
1049
+ printNotice(
545
1050
  `\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
546
1051
  " -> "
547
1052
  )}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
548
1053
  );
549
1054
  } else {
550
- console.warn(
1055
+ printNotice(
551
1056
  `\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.`
552
1057
  );
553
1058
  }
@@ -627,22 +1132,37 @@ function getArgValue2(flag) {
627
1132
  }
628
1133
  return args2[index + 1];
629
1134
  }
1135
+ function printNotice2(message) {
1136
+ if (args2.includes("--test")) {
1137
+ console.warn(message);
1138
+ }
1139
+ }
1140
+ function normalizeEffort2(level) {
1141
+ if (level === "minimal") {
1142
+ return "low";
1143
+ }
1144
+ return level;
1145
+ }
630
1146
  function resolveReasoningEffort2() {
631
1147
  const customReasoningEffort = getArgValue2("--reasoning-effort");
632
1148
  if (customReasoningEffort) {
633
1149
  if (ALLOWED_REASONING_EFFORTS2.includes(customReasoningEffort)) {
634
- trace3("reasoning:custom", customReasoningEffort);
635
- return customReasoningEffort;
1150
+ const normalized = normalizeEffort2(customReasoningEffort);
1151
+ trace3("reasoning:custom", `${customReasoningEffort} -> ${normalized}`);
1152
+ if (customReasoningEffort === "minimal") {
1153
+ 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.");
1154
+ }
1155
+ return normalized;
636
1156
  }
637
- console.warn(
1157
+ printNotice2(
638
1158
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS2.join(
639
1159
  ", "
640
1160
  )}`
641
1161
  );
642
1162
  }
643
1163
  if (args2.includes("--flash")) {
644
- trace3("reasoning:flash-default", "minimal");
645
- return "minimal";
1164
+ trace3("reasoning:flash-default", "low");
1165
+ return "low";
646
1166
  }
647
1167
  if (args2.includes("--review")) {
648
1168
  trace3("reasoning:review-default", "high");
@@ -678,14 +1198,14 @@ ${reviewFormLine || "- (\uC5C6\uC74C)"}
678
1198
  trace3("prompt:prepared", `length=${prompt.length}`);
679
1199
  let command = "";
680
1200
  if (customModel) {
681
- 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.");
1201
+ 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.");
682
1202
  trace3("model:custom", customModel);
683
1203
  command = buildCodexExecCommand(prompt, reasoningEffort, customModel);
684
1204
  } else {
685
1205
  const preferredModelAlias = "gpt-5";
686
1206
  const aliasCommand = buildCodexExecCommand(prompt, reasoningEffort, preferredModelAlias);
687
1207
  const fallbackCommand = buildCodexExecCommand(prompt, reasoningEffort);
688
- console.warn(
1208
+ printNotice2(
689
1209
  `\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.`
690
1210
  );
691
1211
  trace3("model:alias-first", preferredModelAlias);
@@ -746,6 +1266,11 @@ function getArgValue3(flag) {
746
1266
  }
747
1267
  return args3[index + 1];
748
1268
  }
1269
+ function printNotice3(message) {
1270
+ if (args3.includes("--test")) {
1271
+ console.warn(message);
1272
+ }
1273
+ }
749
1274
  function toUnique2(values) {
750
1275
  const seen = /* @__PURE__ */ new Set();
751
1276
  return values.filter((value) => {
@@ -763,7 +1288,7 @@ function resolveReasoningEffort3() {
763
1288
  trace5("reasoning:custom", customReasoningEffort);
764
1289
  return customReasoningEffort;
765
1290
  }
766
- console.warn(
1291
+ printNotice3(
767
1292
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS3.join(
768
1293
  ", "
769
1294
  )}`
@@ -825,38 +1350,57 @@ function buildGeminiExecCommand(prompt, model) {
825
1350
  const modelOption = model ? `--model ${shellQuote3(model)}` : "";
826
1351
  return `gemini ${[modelOption, "-p", shellQuote3(prompt)].filter(Boolean).join(" ")}`;
827
1352
  }
1353
+ function toGeminiFileReference(filePath) {
1354
+ return `@${filePath}`;
1355
+ }
1356
+ function buildGeminiFileReferenceSection(files) {
1357
+ const existingFiles = files.filter((file) => fs.existsSync(file.path));
1358
+ return {
1359
+ count: existingFiles.length,
1360
+ items: existingFiles.map((file) => `${file.display} ${toGeminiFileReference(file.path)}`)
1361
+ };
1362
+ }
828
1363
  var createGeminiCommand = (tempDiffPath2, reviewFormPath2) => {
829
1364
  trace5("createGeminiCommand:start", `tempDiffPath=${tempDiffPath2}, reviewFormPath=${reviewFormPath2}`);
830
1365
  const customModel = getArgValue3("--model");
831
1366
  const reasoningEffort = resolveReasoningEffort3();
832
1367
  const primaryAlias = resolvePrimaryAlias2(reasoningEffort);
833
1368
  const aliasFallbacks = toUnique2(getAliasFallbacks2(primaryAlias));
1369
+ const resolvedTempDiffPath = path.resolve(tempDiffPath2);
1370
+ const resolvedReviewFormPath = path.resolve(reviewFormPath2);
834
1371
  const rules = [
835
1372
  { path: rulesPath, display: "\uB8F0\uC14B" },
836
1373
  { path: namingRulesPath, display: "\uB124\uC774\uBC0D \uADDC\uCE59" },
837
1374
  { path: codingConventionRulesPath, display: "\uCF54\uB529 \uCEE8\uBCA4\uC158" }
838
1375
  ];
839
- const validRules = rules.filter((rule) => fs.existsSync(rule.path)).map((rule) => `@${rule.path}`).join(", ");
840
- const rulesCount = validRules ? validRules.split(",").length : 0;
841
- trace5("rules:loaded", `count=${rulesCount}`);
842
- const reviewFormRef = fs.existsSync(reviewFormPath2) ? `@${reviewFormPath2}` : "(\uC5C6\uC74C)";
843
- trace5("reviewForm:status", reviewFormRef === "(\uC5C6\uC74C)" ? "missing" : "exists");
1376
+ const ruleSection = buildGeminiFileReferenceSection(rules);
1377
+ trace5("rules:loaded", `count=${ruleSection.count}`);
1378
+ const reviewFormExists = fs.existsSync(resolvedReviewFormPath);
1379
+ const reviewFormText = reviewFormExists ? `\uB9AC\uBDF0 \uC591\uC2DD ${toGeminiFileReference(resolvedReviewFormPath)}` : "\uB9AC\uBDF0 \uC591\uC2DD (\uC5C6\uC74C)";
1380
+ trace5("reviewForm:status", reviewFormExists ? "exists" : "missing");
844
1381
  const reasoningInstruction = getReasoningInstruction(reasoningEffort);
845
- const prompt = `\uB2E4\uC74C \uADDC\uCE59\uB4E4\uC744 \uCC38\uACE0\uD574\uC11C(${validRules || "(\uC5C6\uC74C)"}) \uC774 diff(@${tempDiffPath2})\uB97C \uB9AC\uBDF0\uD574\uC918.
846
- \uB9AC\uBDF0 \uC591\uC2DD\uC740 ${reviewFormRef} \uC5D0 \uB9DE\uCDB0\uC11C \uC791\uC131\uD574\uC918.
847
- \uCD94\uB860 \uAC15\uB3C4 \uC9C0\uCE68: ${reasoningInstruction}`;
1382
+ const ruleText = ruleSection.items.join(" ") || "(\uC5C6\uC74C)";
1383
+ const diffText = `\uB9AC\uBDF0 \uB300\uC0C1 diff ${toGeminiFileReference(resolvedTempDiffPath)}`;
1384
+ const prompt = [
1385
+ "\uC544\uB798 \uD30C\uC77C\uB4E4\uC744 \uC21C\uC11C\uB300\uB85C \uC77D\uACE0 \uCF54\uB4DC \uB9AC\uBDF0\uB97C \uC9C4\uD589\uD574\uC918.",
1386
+ `\uADDC\uCE59 \uD30C\uC77C: ${ruleText}`,
1387
+ `\uB9AC\uBDF0 \uC591\uC2DD \uD30C\uC77C: ${reviewFormText}`,
1388
+ `\uB9AC\uBDF0 \uB300\uC0C1 diff \uD30C\uC77C: ${diffText}`,
1389
+ "\uBC18\uB4DC\uC2DC \uB9AC\uBDF0 \uC591\uC2DD\uC5D0 \uB9DE\uCDB0 \uC791\uC131\uD574\uC918.",
1390
+ `\uCD94\uB860 \uAC15\uB3C4 \uC9C0\uCE68: ${reasoningInstruction}`
1391
+ ].join(" ");
848
1392
  trace5("prompt:prepared", `length=${prompt.length}`);
849
1393
  const modelCandidates = toUnique2(customModel ? [customModel, ...aliasFallbacks] : aliasFallbacks);
850
1394
  trace5("model:candidates", modelCandidates.join(", "));
851
1395
  trace5("command:candidates:count", String(modelCandidates.length + 1));
852
1396
  if (customModel) {
853
- console.warn(
1397
+ printNotice3(
854
1398
  `\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
855
1399
  " -> "
856
1400
  )}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
857
1401
  );
858
1402
  } else {
859
- console.warn(
1403
+ printNotice3(
860
1404
  `\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.`
861
1405
  );
862
1406
  }
@@ -907,14 +1451,24 @@ function checkGeminiCliInstalled() {
907
1451
  // src/pr-review/review.ts
908
1452
  async function main() {
909
1453
  const args4 = process.argv.slice(2);
1454
+ const startedAt = /* @__PURE__ */ new Date();
910
1455
  clearTraceMessages();
911
1456
  const isTest = isTestMode(args4);
1457
+ const shouldStreamOutput = shouldStreamAIOutput(args4);
912
1458
  const trace7 = createTraceLogger("review", args4);
913
1459
  trace7("main:start", `args=${JSON.stringify(args4)}`);
914
1460
  let command = "";
915
1461
  let savedDiffPath = "";
916
1462
  let savedReportPath = "";
917
1463
  let service = "";
1464
+ let selectedCommitSummary = "";
1465
+ let reviewTargetFiles = [];
1466
+ let executionLogPath = "";
1467
+ let executionStatus = "cancelled";
1468
+ 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.";
1469
+ let executionError = null;
1470
+ let resultLength = 0;
1471
+ let exitCode = 0;
918
1472
  try {
919
1473
  trace7("service-selection:start");
920
1474
  service = await showSelectionAIService();
@@ -938,30 +1492,39 @@ async function main() {
938
1492
  }
939
1493
  trace7("review-flow:start");
940
1494
  console.log("\u{1F680} AI Code Review\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4...");
941
- const nowStr = getNowString();
942
- trace7("timestamp:created", nowStr);
943
- trace7("report-dir:create:start");
944
- createReportDirectory();
945
- trace7("report-dir:create:done");
946
- trace7("diff-args:build:start");
947
- const diffArgs = getDiffArgs();
948
- trace7("diff-args:build:done", `diffArgs=${diffArgs || "(default)"}`);
949
- let diff = "";
950
- const { includeParams, excludeParams } = getGitDiffFilter();
951
- trace7("diff-filter:loaded", `include=${includeParams} | exclude=${excludeParams}`);
952
- try {
953
- trace7("git-diff:run");
954
- diff = execSync(`git diff ${diffArgs} -- ${includeParams} ${excludeParams}`).toString();
955
- trace7("git-diff:done", `length=${diff.length}`);
956
- } catch (error) {
957
- trace7("git-diff:error", getErrorSummary(error));
1495
+ if (shouldStreamOutput) {
1496
+ console.log("\u2139\uFE0F AI \uC2E4\uC2DC\uAC04 \uC751\uB2F5 \uD45C\uC2DC \uBAA8\uB4DC\uAC00 \uD65C\uC131\uD654\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
958
1497
  }
1498
+ trace7("commit-selection:start");
1499
+ const selectedCommits = await selectReviewCommits();
1500
+ trace7("commit-selection:done", `count=${selectedCommits.length}`);
1501
+ if (selectedCommits.length === 0) {
1502
+ trace7("commit-selection:empty");
1503
+ executionTitle = "\uC120\uD0DD\uB41C \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.";
1504
+ console.log("\u2139\uFE0F \uC120\uD0DD\uB41C \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1505
+ deleteTempDiff();
1506
+ return;
1507
+ }
1508
+ selectedCommitSummary = buildSelectedCommitSummary(selectedCommits);
1509
+ trace7("commit-summary:prepared", selectedCommitSummary);
1510
+ reviewTargetFiles = getSelectedCommitFiles(selectedCommits);
1511
+ console.log(`\u{1F4C2} \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C(${reviewTargetFiles.length}\uAC1C): ${formatReviewTargetFiles(reviewTargetFiles)}`);
1512
+ console.log("\u23F3 \uC120\uD0DD\uD55C \uCEE4\uBC0B diff\uB97C \uC815\uB9AC\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...");
1513
+ trace7("git-diff:build:start");
1514
+ const diff = buildSelectedCommitDiff(selectedCommits);
1515
+ trace7("git-diff:build:done", `length=${diff.length}`);
959
1516
  if (!diff.trim() && !isTest) {
960
1517
  trace7("empty-diff:exit");
961
- console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (Unstaged Empty & HEAD Empty).");
1518
+ executionTitle = "\uC120\uD0DD\uD55C \uCEE4\uBC0B\uC5D0\uC11C \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C \uBCC0\uACBD\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.";
1519
+ 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.");
962
1520
  deleteTempDiff();
963
- process.exit(0);
1521
+ return;
964
1522
  }
1523
+ const nowStr = getNowString();
1524
+ trace7("timestamp:created", nowStr);
1525
+ trace7("report-dir:create:start");
1526
+ createReportDirectory();
1527
+ trace7("report-dir:create:done");
965
1528
  trace7("temp-diff:write:start", tempDiffPath);
966
1529
  fs.writeFileSync(tempDiffPath, diff);
967
1530
  trace7("temp-diff:write:done");
@@ -983,9 +1546,12 @@ async function main() {
983
1546
  }
984
1547
  trace7("command:create:done");
985
1548
  trace7("command:exec:start");
986
- const result = execSync(command).toString();
1549
+ const result = (await executeShellCommandWithProgress(command, {
1550
+ progressMessage: `\u23F3 [\uB9AC\uBDF0 \uC9C4\uD589] ${service} | \uB300\uC0C1 ${reviewTargetFiles.length}\uAC1C \uD30C\uC77C`,
1551
+ streamOutput: isTest || shouldStreamOutput
1552
+ })).stdout;
1553
+ resultLength = result.length;
987
1554
  trace7("command:exec:done", `resultLength=${result.length}`);
988
- console.log(result);
989
1555
  trace7("report:write:start");
990
1556
  savedReportPath = getNextFilePath(REPORT_DIR, nowStr, ".md");
991
1557
  fs.writeFileSync(savedReportPath, result);
@@ -1010,9 +1576,15 @@ ${command}`);
1010
1576
  deleteTempDiff();
1011
1577
  trace7("cleanup-temp-diff:done");
1012
1578
  trace7("review-flow:end");
1579
+ executionStatus = "success";
1580
+ executionTitle = "\uB9AC\uBDF0\uAC00 \uC131\uACF5\uC801\uC73C\uB85C \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
1013
1581
  } catch (error) {
1014
1582
  trace7("review-flow:catch", getErrorSummary(error));
1015
1583
  let errorReportPath = "";
1584
+ executionStatus = "failed";
1585
+ executionTitle = "\uB9AC\uBDF0 \uC2E4\uD589 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
1586
+ executionError = error;
1587
+ exitCode = 1;
1016
1588
  trace7("cleanup-temp-diff:start(catch)");
1017
1589
  try {
1018
1590
  deleteTempDiff();
@@ -1033,6 +1605,8 @@ ${command}`);
1033
1605
  ${JSON.stringify(
1034
1606
  {
1035
1607
  service,
1608
+ selectedCommitSummary: selectedCommitSummary || null,
1609
+ reviewTargetFiles,
1036
1610
  command: command || null,
1037
1611
  tempDiffPath,
1038
1612
  savedDiffPath: savedDiffPath || null,
@@ -1050,7 +1624,50 @@ ${JSON.stringify(
1050
1624
  if (errorReportPath) {
1051
1625
  console.error(`\u{1F4C4} \uC5D0\uB7EC \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${errorReportPath}`);
1052
1626
  }
1053
- process.exit(1);
1627
+ } finally {
1628
+ executionLogPath = writeExecutionLog({
1629
+ scope: "review",
1630
+ status: executionStatus,
1631
+ title: executionTitle,
1632
+ args: args4,
1633
+ startedAt,
1634
+ error: executionError,
1635
+ extraSections: [
1636
+ {
1637
+ heading: "Execution Context",
1638
+ markdown: `\`\`\`json
1639
+ ${JSON.stringify(
1640
+ {
1641
+ service: service || null,
1642
+ selectedCommitSummary: selectedCommitSummary || null,
1643
+ reviewTargetFiles,
1644
+ command: command || null,
1645
+ tempDiffPath,
1646
+ savedDiffPath: savedDiffPath || null,
1647
+ savedReportPath: savedReportPath || null,
1648
+ shouldStreamOutput,
1649
+ resultLength
1650
+ },
1651
+ null,
1652
+ 2
1653
+ )}
1654
+ \`\`\``
1655
+ },
1656
+ {
1657
+ heading: "Generated Command",
1658
+ markdown: command ? `\`\`\`sh
1659
+ ${command}
1660
+ \`\`\`` : "(\uC5C6\uC74C)"
1661
+ }
1662
+ ]
1663
+ });
1664
+ if (executionLogPath) {
1665
+ const writeLog = executionStatus === "failed" ? console.error : console.log;
1666
+ writeLog(`\u{1F4DD} \uC2E4\uD589 \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${executionLogPath}`);
1667
+ }
1668
+ }
1669
+ if (exitCode !== 0) {
1670
+ process.exit(exitCode);
1054
1671
  }
1055
1672
  }
1056
1673
  main();