sales-frontend-gemini-cli 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/common/helper.cjs +464 -52
  2. package/dist/common/helper.cjs.map +1 -1
  3. package/dist/common/helper.d.cts +85 -3
  4. package/dist/common/helper.d.ts +85 -3
  5. package/dist/common/helper.js +455 -53
  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 +50 -9
  10. package/dist/pr-review/claude/claude-commander.cjs.map +1 -1
  11. package/dist/pr-review/claude/claude-commander.js +50 -9
  12. package/dist/pr-review/claude/claude-commander.js.map +1 -1
  13. package/dist/pr-review/claude/installation-claude.cjs +41 -5
  14. package/dist/pr-review/claude/installation-claude.cjs.map +1 -1
  15. package/dist/pr-review/claude/installation-claude.js +41 -5
  16. package/dist/pr-review/claude/installation-claude.js.map +1 -1
  17. package/dist/pr-review/codex/codex-commander.cjs +49 -8
  18. package/dist/pr-review/codex/codex-commander.cjs.map +1 -1
  19. package/dist/pr-review/codex/codex-commander.js +49 -8
  20. package/dist/pr-review/codex/codex-commander.js.map +1 -1
  21. package/dist/pr-review/codex/installation-codex.cjs +41 -5
  22. package/dist/pr-review/codex/installation-codex.cjs.map +1 -1
  23. package/dist/pr-review/codex/installation-codex.js +41 -5
  24. package/dist/pr-review/codex/installation-codex.js.map +1 -1
  25. package/dist/pr-review/gemini/gemini-commander.cjs +75 -15
  26. package/dist/pr-review/gemini/gemini-commander.cjs.map +1 -1
  27. package/dist/pr-review/gemini/gemini-commander.js +75 -15
  28. package/dist/pr-review/gemini/gemini-commander.js.map +1 -1
  29. package/dist/pr-review/gemini/installation-gemini.cjs +41 -5
  30. package/dist/pr-review/gemini/installation-gemini.cjs.map +1 -1
  31. package/dist/pr-review/gemini/installation-gemini.js +41 -5
  32. package/dist/pr-review/gemini/installation-gemini.js.map +1 -1
  33. package/dist/pr-review/review-one-by-one.cjs +489 -95
  34. package/dist/pr-review/review-one-by-one.cjs.map +1 -1
  35. package/dist/pr-review/review-one-by-one.js +490 -96
  36. package/dist/pr-review/review-one-by-one.js.map +1 -1
  37. package/dist/pr-review/review.cjs +517 -94
  38. package/dist/pr-review/review.cjs.map +1 -1
  39. package/dist/pr-review/review.js +517 -94
  40. package/dist/pr-review/review.js.map +1 -1
  41. package/package.json +4 -7
  42. package/src/common/rules/coding-convention.md +393 -0
  43. package/src/common/rules/coding-convention.pdf +0 -0
  44. package/src/common/rules/naming-rule.md +347 -0
  45. package/src/common/rules/naming-rule.pdf +0 -0
@@ -1,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",
@@ -48,6 +86,185 @@ function clearTraceMessages() {
48
86
  function getTraceMessages() {
49
87
  return [...traceMessages];
50
88
  }
89
+ var ANSI = {
90
+ bold: "\x1B[1m",
91
+ cyan: "\x1B[36m",
92
+ dim: "\x1B[2m",
93
+ green: "\x1B[32m",
94
+ reset: "\x1B[0m",
95
+ yellow: "\x1B[33m"
96
+ };
97
+ var ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
98
+ var COMBINING_MARK_PATTERN = /\p{Mark}/u;
99
+ var GRAPHEME_SEGMENTER = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter("ko", { granularity: "grapheme" }) : null;
100
+ function getGitDiffPathspecs() {
101
+ return {
102
+ excludePatterns: ignoreList.map((item) => `:(exclude)${item}`),
103
+ includePatterns: ["*.ts", "*.tsx", "*.js", "*.jsx"]
104
+ };
105
+ }
106
+ function segmentGraphemes(value) {
107
+ if (!GRAPHEME_SEGMENTER) {
108
+ return [...value];
109
+ }
110
+ return [...GRAPHEME_SEGMENTER.segment(value)].map(({ segment }) => segment);
111
+ }
112
+ function isWideCodePoint(codePoint) {
113
+ return codePoint >= 4352 && (codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 12871 && codePoint !== 12351 || codePoint >= 12880 && codePoint <= 19903 || codePoint >= 19968 && codePoint <= 42182 || codePoint >= 43360 && codePoint <= 43388 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65131 || codePoint >= 65281 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 127488 && codePoint <= 127569 || codePoint >= 131072 && codePoint <= 262141);
114
+ }
115
+ function isEmojiCodePoint(codePoint) {
116
+ return codePoint >= 127462 && codePoint <= 127487 || codePoint >= 127744 && codePoint <= 129791 || codePoint >= 9728 && codePoint <= 10175;
117
+ }
118
+ function getGraphemeWidth(grapheme) {
119
+ let width = 0;
120
+ for (const character of grapheme) {
121
+ const codePoint = character.codePointAt(0);
122
+ if (!codePoint || COMBINING_MARK_PATTERN.test(character) || codePoint === 8205) {
123
+ continue;
124
+ }
125
+ if (codePoint >= 65024 && codePoint <= 65039 || codePoint >= 917760 && codePoint <= 917999) {
126
+ continue;
127
+ }
128
+ if (isWideCodePoint(codePoint) || isEmojiCodePoint(codePoint)) {
129
+ width = Math.max(width, 2);
130
+ continue;
131
+ }
132
+ width = Math.max(width, 1);
133
+ }
134
+ return width;
135
+ }
136
+ function tokenizePlainText(value) {
137
+ return segmentGraphemes(value).map((segment) => ({
138
+ value: segment,
139
+ visibleWidth: getGraphemeWidth(segment)
140
+ }));
141
+ }
142
+ function tokenizeVisibleText(value) {
143
+ const tokens = [];
144
+ let lastIndex = 0;
145
+ for (const match of value.matchAll(ANSI_PATTERN)) {
146
+ const index = match.index ?? 0;
147
+ if (index > lastIndex) {
148
+ tokens.push(...tokenizePlainText(value.slice(lastIndex, index)));
149
+ }
150
+ tokens.push({
151
+ value: match[0],
152
+ visibleWidth: 0
153
+ });
154
+ lastIndex = index + match[0].length;
155
+ }
156
+ if (lastIndex < value.length) {
157
+ tokens.push(...tokenizePlainText(value.slice(lastIndex)));
158
+ }
159
+ return tokens;
160
+ }
161
+ function truncateLineForTerminal(value, maxWidth) {
162
+ if (maxWidth <= 0) {
163
+ return "";
164
+ }
165
+ const tokens = tokenizeVisibleText(value);
166
+ const totalWidth = tokens.reduce((sum, token) => sum + token.visibleWidth, 0);
167
+ if (totalWidth <= maxWidth) {
168
+ return value;
169
+ }
170
+ const ellipsis = "...";
171
+ const ellipsisWidth = 3;
172
+ const targetWidth = Math.max(0, maxWidth - ellipsisWidth);
173
+ let usedWidth = 0;
174
+ let result = "";
175
+ for (const token of tokens) {
176
+ if (token.visibleWidth === 0) {
177
+ result += token.value;
178
+ continue;
179
+ }
180
+ if (usedWidth + token.visibleWidth > targetWidth) {
181
+ break;
182
+ }
183
+ result += token.value;
184
+ usedWidth += token.visibleWidth;
185
+ }
186
+ return `${result}${ellipsis}${ANSI.reset}`;
187
+ }
188
+ function fitLinesToTerminal(lines) {
189
+ const maxWidth = Math.max(20, (process.stdout.columns || 120) - 1);
190
+ return lines.map((line) => truncateLineForTerminal(line, maxWidth));
191
+ }
192
+ function runGitCommand(args4, options = {}) {
193
+ const { allowFailure = false, trimOutput = true } = options;
194
+ try {
195
+ const output = child_process.execFileSync("git", args4, {
196
+ encoding: "utf8",
197
+ maxBuffer: 1024 * 1024 * 20,
198
+ stdio: ["ignore", "pipe", "pipe"]
199
+ });
200
+ return trimOutput ? output.trim() : output;
201
+ } catch (error) {
202
+ helperTrace("git-command:failed", `${args4.join(" ")} | ${getErrorSummary(error)}`);
203
+ if (allowFailure) {
204
+ return "";
205
+ }
206
+ throw error;
207
+ }
208
+ }
209
+ async function executeShellCommandWithProgress(command, options = {}) {
210
+ const { progressIntervalMs = 1e4, progressMessage = "\u23F3 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...", streamOutput = false } = options;
211
+ const { spawn } = await import('child_process');
212
+ return new Promise((resolve, reject) => {
213
+ let stdout = "";
214
+ let stderr = "";
215
+ const startedAt = Date.now();
216
+ console.log(progressMessage);
217
+ const child = spawn("/bin/zsh", ["-lc", command], {
218
+ stdio: ["ignore", "pipe", "pipe"]
219
+ });
220
+ const progressTimer = setInterval(() => {
221
+ const elapsedSeconds = Math.max(1, Math.floor((Date.now() - startedAt) / 1e3));
222
+ console.log(`${progressMessage} (${elapsedSeconds}s \uACBD\uACFC)`);
223
+ }, progressIntervalMs);
224
+ child.stdout.on("data", (chunk) => {
225
+ const text = chunk.toString();
226
+ stdout += text;
227
+ if (streamOutput) {
228
+ process.stdout.write(text);
229
+ }
230
+ });
231
+ child.stderr.on("data", (chunk) => {
232
+ const text = chunk.toString();
233
+ stderr += text;
234
+ if (streamOutput) {
235
+ process.stderr.write(text);
236
+ }
237
+ });
238
+ child.on("error", (error) => {
239
+ clearInterval(progressTimer);
240
+ reject(error);
241
+ });
242
+ child.on("close", (code, signal) => {
243
+ clearInterval(progressTimer);
244
+ if (code === 0) {
245
+ resolve({
246
+ stderr,
247
+ stdout
248
+ });
249
+ return;
250
+ }
251
+ const exitSummary = signal ? `signal=${signal}` : `code=${String(code ?? "unknown")}`;
252
+ reject(new Error(`\uC258 \uBA85\uB839 \uC2E4\uD589 \uC2E4\uD328 (${exitSummary})${stderr.trim() ? `
253
+ ${stderr.trim()}` : ""}`));
254
+ });
255
+ });
256
+ }
257
+ function formatReviewTargetFiles(files, visibleCount = 5) {
258
+ if (files.length === 0) {
259
+ return "(\uC5C6\uC74C)";
260
+ }
261
+ const visibleFiles = files.slice(0, visibleCount);
262
+ const hiddenCount = Math.max(0, files.length - visibleFiles.length);
263
+ if (hiddenCount === 0) {
264
+ return visibleFiles.join(", ");
265
+ }
266
+ return `${visibleFiles.join(", ")} \uC678 ${hiddenCount}\uAC1C`;
267
+ }
51
268
  function createTraceLogger(scope, args4 = process.argv.slice(2)) {
52
269
  const enabled = isTestMode(args4);
53
270
  return (step, detail) => {
@@ -259,13 +476,67 @@ ${JSON.stringify(AIServices, null, 2)}
259
476
  }
260
477
  );
261
478
  }
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 };
479
+ function truncateCommitSubject(subject) {
480
+ if (subject.length <= 72) {
481
+ return subject;
482
+ }
483
+ return `${subject.slice(0, 69)}...`;
484
+ }
485
+ function getRecentCommitOptions() {
486
+ const output = runGitCommand(
487
+ ["log", `-${COMMIT_FETCH_LIMIT}`, "--date=relative", "--pretty=format:%h%x09%an%x09%ar%x09%s"],
488
+ { allowFailure: true }
489
+ );
490
+ if (!output) {
491
+ return [];
492
+ }
493
+ return output.split("\n").map((line) => {
494
+ const [hash = "", author = "", relativeDate = "", ...subjectParts] = line.split(" ");
495
+ const subject = subjectParts.join(" ").trim();
496
+ return {
497
+ author,
498
+ description: `${author} | ${relativeDate}`,
499
+ hash,
500
+ label: `${hash} | ${truncateCommitSubject(subject)}`,
501
+ relativeDate,
502
+ subject
503
+ };
504
+ });
505
+ }
506
+ function buildSelectedCommitSummary(commits) {
507
+ return commits.map((commit) => `- ${commit.hash} | ${commit.subject} | ${commit.author} | ${commit.relativeDate}`).join("\n");
508
+ }
509
+ function getReviewPathspecArgs() {
510
+ const { includePatterns, excludePatterns } = getGitDiffPathspecs();
511
+ return [...includePatterns, ...excludePatterns];
512
+ }
513
+ function buildSelectedCommitDiff(commits) {
514
+ const reviewPathspecArgs = getReviewPathspecArgs();
515
+ const sections = commits.map((commit) => {
516
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", ...reviewPathspecArgs], {
517
+ allowFailure: true,
518
+ trimOutput: false
519
+ }).trim();
520
+ if (!diff) {
521
+ return "";
522
+ }
523
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
524
+ }).filter(Boolean).join("\n\n");
525
+ if (!sections) {
526
+ return "";
527
+ }
528
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", "# \uB9AC\uBDF0 \uB300\uC0C1 diff", sections].join("\n");
529
+ }
530
+ function getSelectedCommitFiles(commits) {
531
+ const reviewPathspecArgs = getReviewPathspecArgs();
532
+ const files = /* @__PURE__ */ new Set();
533
+ commits.forEach((commit) => {
534
+ const output = runGitCommand(["show", "--pretty=format:", "--name-only", commit.hash, "--", ...reviewPathspecArgs], {
535
+ allowFailure: true
536
+ });
537
+ output.split("\n").map((line) => line.trim()).filter(Boolean).forEach((filePath) => files.add(filePath));
538
+ });
539
+ return [...files];
269
540
  }
270
541
  function openReport(reportPath) {
271
542
  const resolvedPath = path__default.default.resolve(reportPath);
@@ -316,59 +587,166 @@ function openReport(reportPath) {
316
587
  helperTrace("open-report:unsupported-platform", platform);
317
588
  console.error(`\u26A0\uFE0F \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD50C\uB7AB\uD3FC\uC785\uB2C8\uB2E4: ${platform}`);
318
589
  }
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
- });
590
+ function ensureInteractiveSelectionAvailable(scope, message) {
591
+ if (process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function") {
592
+ return;
593
+ }
594
+ helperTrace(`${scope}:tty-missing`);
595
+ exitWithError(message, {
596
+ scope: `helper:${scope}`
597
+ });
598
+ }
599
+ function renderSelectionBlock(lines, previousLineCount) {
600
+ if (previousLineCount > 0) {
601
+ readline__default.default.moveCursor(process.stdout, 0, -previousLineCount);
602
+ readline__default.default.clearScreenDown(process.stdout);
603
+ }
604
+ const fittedLines = fitLinesToTerminal(lines);
605
+ process.stdout.write(`${fittedLines.join("\n")}
606
+ `);
607
+ return fittedLines.length;
608
+ }
609
+ function getSelectionWindowRange(optionCount, selectedIndex, windowSize) {
610
+ if (optionCount <= windowSize) {
611
+ return {
612
+ end: optionCount,
613
+ start: 0
614
+ };
615
+ }
616
+ const halfWindow = Math.floor(windowSize / 2);
617
+ const maxStart = optionCount - windowSize;
618
+ const start = Math.max(0, Math.min(selectedIndex - halfWindow, maxStart));
619
+ return {
620
+ end: Math.min(optionCount, start + windowSize),
621
+ start
622
+ };
623
+ }
624
+ function buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize) {
625
+ const { start, end } = getSelectionWindowRange(options.length, selectedIndex, windowSize);
626
+ const lines = [
627
+ `${ANSI.bold}${question}${ANSI.reset}`,
628
+ `${ANSI.dim}\u2191\u2193 \uC774\uB3D9 | Space \uC120\uD0DD/\uD574\uC81C | Enter \uC644\uB8CC | Esc \uCDE8\uC18C${ANSI.reset}`,
629
+ `${ANSI.dim}\uC120\uD0DD\uB428: ${toggled.size}\uAC1C / \uC804\uCCB4: ${options.length}\uAC1C${ANSI.reset}`
630
+ ];
631
+ for (let index = start; index < end; index += 1) {
632
+ const option = options[index];
633
+ if (!option) {
634
+ continue;
333
635
  }
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
- }
636
+ const cursor = index === selectedIndex ? `${ANSI.cyan}>${ANSI.reset}` : " ";
637
+ const checked = toggled.has(index) ? `${ANSI.green}\u2611${ANSI.reset}` : "\u2610";
638
+ const description = option.description ? ` ${ANSI.dim}${option.description}${ANSI.reset}` : "";
639
+ lines.push(`${cursor} ${checked} ${option.label}${description}`);
640
+ }
641
+ if (options.length > windowSize) {
642
+ lines.push(`${ANSI.dim}\uD45C\uC2DC \uBC94\uC704: ${start + 1}-${end} / ${options.length}${ANSI.reset}`);
643
+ }
644
+ return lines;
645
+ }
646
+ async function showMultiSelect(question, options, windowSize = COMMIT_SELECTION_WINDOW) {
647
+ ensureInteractiveSelectionAvailable("showMultiSelect", "\u274C \uCEE4\uBC0B \uC120\uD0DD \uBAA8\uB2EC\uC740 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
648
+ let selectedIndex = 0;
649
+ let renderedLineCount = 0;
650
+ const toggled = /* @__PURE__ */ new Set();
651
+ const rl = readline__default.default.createInterface({
652
+ input: process.stdin,
653
+ output: process.stdout,
654
+ terminal: true
655
+ });
656
+ process.stdout.write("\x1B[?25l");
657
+ const cleanup = () => {
658
+ if (renderedLineCount > 0) {
659
+ readline__default.default.moveCursor(process.stdout, 0, -renderedLineCount);
660
+ readline__default.default.clearScreenDown(process.stdout);
661
+ renderedLineCount = 0;
341
662
  }
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}`);
663
+ process.stdin.removeListener("data", onData);
664
+ process.stdin.setRawMode(false);
665
+ process.stdin.pause();
666
+ rl.close();
667
+ process.stdout.write("\x1B[?25h");
668
+ };
669
+ const render = () => {
670
+ const lines = buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize);
671
+ renderedLineCount = renderSelectionBlock(lines, renderedLineCount);
672
+ };
673
+ const confirmSelection = (resolve) => {
674
+ const values = [...toggled].sort((left, right) => left - right).map((index) => options[index]?.value).filter((value) => value !== void 0);
675
+ cleanup();
676
+ resolve(values);
677
+ };
678
+ const cancelSelection = (resolve) => {
679
+ cleanup();
680
+ resolve([]);
681
+ };
682
+ let onData = (_data) => {
683
+ };
684
+ render();
685
+ return new Promise((resolve) => {
686
+ onData = (data) => {
687
+ const key = data.toString();
688
+ if (key === "") {
689
+ cleanup();
690
+ process.exit(0);
355
691
  }
356
- } catch (error) {
357
- helperTrace("diff-args:unstaged-check:failed", getErrorSummary(error));
358
- }
692
+ if (key === "\x1B") {
693
+ cancelSelection(resolve);
694
+ return;
695
+ }
696
+ if (key === "\x1B[A") {
697
+ selectedIndex = (selectedIndex - 1 + options.length) % options.length;
698
+ render();
699
+ return;
700
+ }
701
+ if (key === "\x1B[B") {
702
+ selectedIndex = (selectedIndex + 1) % options.length;
703
+ render();
704
+ return;
705
+ }
706
+ if (key === " ") {
707
+ if (toggled.has(selectedIndex)) {
708
+ toggled.delete(selectedIndex);
709
+ } else {
710
+ toggled.add(selectedIndex);
711
+ }
712
+ render();
713
+ return;
714
+ }
715
+ if (key === "\r" || key === "\n") {
716
+ confirmSelection(resolve);
717
+ }
718
+ };
719
+ process.stdin.setRawMode(true);
720
+ process.stdin.resume();
721
+ process.stdin.on("data", onData);
722
+ });
723
+ }
724
+ async function selectReviewCommits() {
725
+ const commits = getRecentCommitOptions();
726
+ if (commits.length === 0) {
727
+ console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uCD5C\uADFC \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
728
+ return [];
359
729
  }
360
- helperTrace("diff-args:resolve:done", diffArgs || "(default)");
361
- return diffArgs;
730
+ return showMultiSelect(
731
+ "\uB9AC\uBDF0\uD560 \uCEE4\uBC0B\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.",
732
+ commits.map((commit) => ({
733
+ description: commit.description,
734
+ label: commit.label,
735
+ value: commit
736
+ })),
737
+ COMMIT_SELECTION_WINDOW
738
+ );
362
739
  }
363
740
  async function showSelectionAIService() {
364
741
  const selectedServiceFromArgs = parseServiceFromArgs();
365
742
  if (selectedServiceFromArgs) {
366
743
  helperTrace("show-selection:from-args", selectedServiceFromArgs);
367
744
  console.log(`
368
- \u2705 \x1B[32m${selectedServiceFromArgs}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
745
+ \u2705 ${ANSI.green}${selectedServiceFromArgs}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
369
746
  `);
370
747
  return selectedServiceFromArgs;
371
748
  }
749
+ ensureInteractiveSelectionAvailable("showSelectionAIService", "\u274C AI \uC11C\uBE44\uC2A4 \uC120\uD0DD UI\uB294 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
372
750
  helperTrace("show-selection:interactive:start");
373
751
  let selectedIndex = 0;
374
752
  const rl = readline__default.default.createInterface({
@@ -386,11 +764,12 @@ async function showSelectionAIService() {
386
764
  helperTrace("show-selection:interactive:render", AIServices[selectedIndex] || "unknown");
387
765
  readline__default.default.clearScreenDown(process.stdout);
388
766
  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"
767
+ `\u{1F916} AI \uC11C\uBE44\uC2A4\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694 (${ANSI.yellow}\u2191\u2193 \uBC29\uD5A5\uD0A4${ANSI.reset} \uC774\uB3D9, ${ANSI.yellow}Enter${ANSI.reset} \uC120\uD0DD):
768
+ `
390
769
  );
391
770
  AIServices.forEach((service, index) => {
392
771
  if (index === selectedIndex) {
393
- process.stdout.write(` \x1B[36m>\x1B[0m \x1B[36m\u25C9\x1B[0m \x1B[1m${service}\x1B[0m
772
+ process.stdout.write(` ${ANSI.cyan}>${ANSI.reset} ${ANSI.cyan}\u25C9${ANSI.reset} ${ANSI.bold}${service}${ANSI.reset}
394
773
  `);
395
774
  } else {
396
775
  process.stdout.write(` \u25EF ${service}
@@ -420,7 +799,7 @@ async function showSelectionAIService() {
420
799
  rl.close();
421
800
  process.stdout.write("\x1B[?25h");
422
801
  console.log(`
423
- \u2705 \x1B[32m${AIServices[selectedIndex]}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
802
+ \u2705 ${ANSI.green}${AIServices[selectedIndex]}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
424
803
  `);
425
804
  const result = AIServices[selectedIndex];
426
805
  if (result) {
@@ -447,6 +826,11 @@ function getArgValue(flag) {
447
826
  }
448
827
  return args[index + 1];
449
828
  }
829
+ function printNotice(message) {
830
+ if (args.includes("--test")) {
831
+ console.warn(message);
832
+ }
833
+ }
450
834
  function toUnique(values) {
451
835
  const seen = /* @__PURE__ */ new Set();
452
836
  return values.filter((value) => {
@@ -470,11 +854,11 @@ function resolveReasoningEffort() {
470
854
  const normalized = normalizeEffort(customReasoningEffort);
471
855
  trace("reasoning:custom", `${customReasoningEffort} -> ${normalized}`);
472
856
  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.");
857
+ printNotice("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
474
858
  }
475
859
  return normalized;
476
860
  }
477
- console.warn(
861
+ printNotice(
478
862
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS.join(
479
863
  ", "
480
864
  )}`
@@ -517,7 +901,7 @@ function buildClaudeExecCommand(options) {
517
901
  const modelOption = model ? `--model ${shellQuote(model)}` : "";
518
902
  const fallbackOption = model && fallbackModel ? `--fallback-model ${shellQuote(fallbackModel)}` : "";
519
903
  const effortOption = `--effort ${shellQuote(effort)}`;
520
- const appendedPromptFiles = systemPromptFiles.map((path2) => `--append-system-prompt-file ${shellQuote(path2)}`).join(" ");
904
+ const appendedPromptFiles = systemPromptFiles.map((path3) => `--append-system-prompt-file ${shellQuote(path3)}`).join(" ");
521
905
  return `cat ${shellQuote(tempDiffPath2)} | claude ${[
522
906
  modelOption,
523
907
  fallbackOption,
@@ -550,13 +934,13 @@ var createClaudeCommand = (tempDiffPath2, reviewFormPath2) => {
550
934
  trace("model:candidates", modelCandidates.join(", "));
551
935
  trace("command:candidates:count", String(modelCandidates.length + 1));
552
936
  if (customModel) {
553
- console.warn(
937
+ printNotice(
554
938
  `\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
555
939
  " -> "
556
940
  )}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
557
941
  );
558
942
  } else {
559
- console.warn(
943
+ printNotice(
560
944
  `\u26A0\uFE0F \uBAA8\uB378\uC774 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. alias(${aliasFallbacks.join(" -> ")})\uB97C \uC21C\uCC28 \uC2DC\uB3C4\uD558\uACE0 \uB9C8\uC9C0\uB9C9\uC5D0 \uAE30\uBCF8 \uBAA8\uB378\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
561
945
  );
562
946
  }
@@ -636,6 +1020,11 @@ function getArgValue2(flag) {
636
1020
  }
637
1021
  return args2[index + 1];
638
1022
  }
1023
+ function printNotice2(message) {
1024
+ if (args2.includes("--test")) {
1025
+ console.warn(message);
1026
+ }
1027
+ }
639
1028
  function resolveReasoningEffort2() {
640
1029
  const customReasoningEffort = getArgValue2("--reasoning-effort");
641
1030
  if (customReasoningEffort) {
@@ -643,7 +1032,7 @@ function resolveReasoningEffort2() {
643
1032
  trace3("reasoning:custom", customReasoningEffort);
644
1033
  return customReasoningEffort;
645
1034
  }
646
- console.warn(
1035
+ printNotice2(
647
1036
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS2.join(
648
1037
  ", "
649
1038
  )}`
@@ -687,14 +1076,14 @@ ${reviewFormLine || "- (\uC5C6\uC74C)"}
687
1076
  trace3("prompt:prepared", `length=${prompt.length}`);
688
1077
  let command = "";
689
1078
  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.");
1079
+ printNotice2("\u26A0\uFE0F \uC9C0\uC815\uD55C \uBAA8\uB378\uC774 \uC5C6\uB294 \uACBD\uC6B0, \uC5D0\uB7EC\uAC00 \uBC1C\uC0DD\uD558\uB2C8 \uC8FC\uC758\uD558\uC138\uC694.");
691
1080
  trace3("model:custom", customModel);
692
1081
  command = buildCodexExecCommand(prompt, reasoningEffort, customModel);
693
1082
  } else {
694
1083
  const preferredModelAlias = "gpt-5";
695
1084
  const aliasCommand = buildCodexExecCommand(prompt, reasoningEffort, preferredModelAlias);
696
1085
  const fallbackCommand = buildCodexExecCommand(prompt, reasoningEffort);
697
- console.warn(
1086
+ printNotice2(
698
1087
  `\u26A0\uFE0F \uBAA8\uB378\uC774 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. alias(${preferredModelAlias})\uB97C \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 \uACC4\uC815 \uAE30\uBCF8 \uBAA8\uB378\uB85C \uC790\uB3D9 \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
699
1088
  );
700
1089
  trace3("model:alias-first", preferredModelAlias);
@@ -755,6 +1144,11 @@ function getArgValue3(flag) {
755
1144
  }
756
1145
  return args3[index + 1];
757
1146
  }
1147
+ function printNotice3(message) {
1148
+ if (args3.includes("--test")) {
1149
+ console.warn(message);
1150
+ }
1151
+ }
758
1152
  function toUnique2(values) {
759
1153
  const seen = /* @__PURE__ */ new Set();
760
1154
  return values.filter((value) => {
@@ -772,7 +1166,7 @@ function resolveReasoningEffort3() {
772
1166
  trace5("reasoning:custom", customReasoningEffort);
773
1167
  return customReasoningEffort;
774
1168
  }
775
- console.warn(
1169
+ printNotice3(
776
1170
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS3.join(
777
1171
  ", "
778
1172
  )}`
@@ -834,38 +1228,57 @@ function buildGeminiExecCommand(prompt, model) {
834
1228
  const modelOption = model ? `--model ${shellQuote3(model)}` : "";
835
1229
  return `gemini ${[modelOption, "-p", shellQuote3(prompt)].filter(Boolean).join(" ")}`;
836
1230
  }
1231
+ function toGeminiFileReference(filePath) {
1232
+ return `@${filePath}`;
1233
+ }
1234
+ function buildGeminiFileReferenceSection(files) {
1235
+ const existingFiles = files.filter((file) => fs__default.default.existsSync(file.path));
1236
+ return {
1237
+ count: existingFiles.length,
1238
+ lines: existingFiles.map((file) => `- ${file.display}: ${toGeminiFileReference(file.path)}`)
1239
+ };
1240
+ }
837
1241
  var createGeminiCommand = (tempDiffPath2, reviewFormPath2) => {
838
1242
  trace5("createGeminiCommand:start", `tempDiffPath=${tempDiffPath2}, reviewFormPath=${reviewFormPath2}`);
839
1243
  const customModel = getArgValue3("--model");
840
1244
  const reasoningEffort = resolveReasoningEffort3();
841
1245
  const primaryAlias = resolvePrimaryAlias2(reasoningEffort);
842
1246
  const aliasFallbacks = toUnique2(getAliasFallbacks2(primaryAlias));
1247
+ const resolvedTempDiffPath = path__default.default.resolve(tempDiffPath2);
1248
+ const resolvedReviewFormPath = path__default.default.resolve(reviewFormPath2);
843
1249
  const rules = [
844
1250
  { path: rulesPath, display: "\uB8F0\uC14B" },
845
1251
  { path: namingRulesPath, display: "\uB124\uC774\uBC0D \uADDC\uCE59" },
846
1252
  { path: codingConventionRulesPath, display: "\uCF54\uB529 \uCEE8\uBCA4\uC158" }
847
1253
  ];
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");
1254
+ const ruleSection = buildGeminiFileReferenceSection(rules);
1255
+ trace5("rules:loaded", `count=${ruleSection.count}`);
1256
+ const reviewFormExists = fs__default.default.existsSync(resolvedReviewFormPath);
1257
+ const reviewFormLine = reviewFormExists ? `- \uB9AC\uBDF0 \uC591\uC2DD: ${toGeminiFileReference(resolvedReviewFormPath)}` : "- \uB9AC\uBDF0 \uC591\uC2DD: (\uC5C6\uC74C)";
1258
+ trace5("reviewForm:status", reviewFormExists ? "exists" : "missing");
853
1259
  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.
1260
+ const prompt = `\uC544\uB798 \uD30C\uC77C\uB4E4\uC744 \uC21C\uC11C\uB300\uB85C \uC77D\uACE0 \uCF54\uB4DC \uB9AC\uBDF0\uB97C \uC9C4\uD589\uD574\uC918.
1261
+ \uADDC\uCE59 \uD30C\uC77C:
1262
+ ${ruleSection.lines.join("\n") || "- (\uC5C6\uC74C)"}
1263
+ \uB9AC\uBDF0 \uC591\uC2DD \uD30C\uC77C:
1264
+ ${reviewFormLine}
1265
+ \uB9AC\uBDF0 \uB300\uC0C1 diff \uD30C\uC77C:
1266
+ - \uB9AC\uBDF0 \uB300\uC0C1 diff: ${toGeminiFileReference(resolvedTempDiffPath)}
1267
+
1268
+ \uBC18\uB4DC\uC2DC \uB9AC\uBDF0 \uC591\uC2DD\uC5D0 \uB9DE\uCDB0 \uC791\uC131\uD574\uC918.
856
1269
  \uCD94\uB860 \uAC15\uB3C4 \uC9C0\uCE68: ${reasoningInstruction}`;
857
1270
  trace5("prompt:prepared", `length=${prompt.length}`);
858
1271
  const modelCandidates = toUnique2(customModel ? [customModel, ...aliasFallbacks] : aliasFallbacks);
859
1272
  trace5("model:candidates", modelCandidates.join(", "));
860
1273
  trace5("command:candidates:count", String(modelCandidates.length + 1));
861
1274
  if (customModel) {
862
- console.warn(
1275
+ printNotice3(
863
1276
  `\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
864
1277
  " -> "
865
1278
  )}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
866
1279
  );
867
1280
  } else {
868
- console.warn(
1281
+ printNotice3(
869
1282
  `\u26A0\uFE0F \uBAA8\uB378\uC774 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. alias(${aliasFallbacks.join(" -> ")})\uB97C \uC21C\uCC28 \uC2DC\uB3C4\uD558\uACE0 \uB9C8\uC9C0\uB9C9\uC5D0 \uAE30\uBCF8 \uBAA8\uB378\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
870
1283
  );
871
1284
  }
@@ -924,6 +1337,8 @@ async function main() {
924
1337
  let savedDiffPath = "";
925
1338
  let savedReportPath = "";
926
1339
  let service = "";
1340
+ let selectedCommitSummary = "";
1341
+ let reviewTargetFiles = [];
927
1342
  try {
928
1343
  trace7("service-selection:start");
929
1344
  service = await showSelectionAIService();
@@ -947,30 +1362,34 @@ async function main() {
947
1362
  }
948
1363
  trace7("review-flow:start");
949
1364
  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));
1365
+ trace7("commit-selection:start");
1366
+ const selectedCommits = await selectReviewCommits();
1367
+ trace7("commit-selection:done", `count=${selectedCommits.length}`);
1368
+ if (selectedCommits.length === 0) {
1369
+ trace7("commit-selection:empty");
1370
+ console.log("\u2139\uFE0F \uC120\uD0DD\uB41C \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1371
+ deleteTempDiff();
1372
+ process.exit(0);
967
1373
  }
1374
+ selectedCommitSummary = buildSelectedCommitSummary(selectedCommits);
1375
+ trace7("commit-summary:prepared", selectedCommitSummary);
1376
+ reviewTargetFiles = getSelectedCommitFiles(selectedCommits);
1377
+ console.log(`\u{1F4C2} \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C(${reviewTargetFiles.length}\uAC1C): ${formatReviewTargetFiles(reviewTargetFiles)}`);
1378
+ console.log("\u23F3 \uC120\uD0DD\uD55C \uCEE4\uBC0B diff\uB97C \uC815\uB9AC\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...");
1379
+ trace7("git-diff:build:start");
1380
+ const diff = buildSelectedCommitDiff(selectedCommits);
1381
+ trace7("git-diff:build:done", `length=${diff.length}`);
968
1382
  if (!diff.trim() && !isTest) {
969
1383
  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).");
1384
+ console.log("\u2139\uFE0F \uC120\uD0DD\uD55C \uCEE4\uBC0B\uC5D0\uC11C \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C \uBCC0\uACBD\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
971
1385
  deleteTempDiff();
972
1386
  process.exit(0);
973
1387
  }
1388
+ const nowStr = getNowString();
1389
+ trace7("timestamp:created", nowStr);
1390
+ trace7("report-dir:create:start");
1391
+ createReportDirectory();
1392
+ trace7("report-dir:create:done");
974
1393
  trace7("temp-diff:write:start", tempDiffPath);
975
1394
  fs__default.default.writeFileSync(tempDiffPath, diff);
976
1395
  trace7("temp-diff:write:done");
@@ -992,9 +1411,11 @@ async function main() {
992
1411
  }
993
1412
  trace7("command:create:done");
994
1413
  trace7("command:exec:start");
995
- const result = child_process.execSync(command).toString();
1414
+ const result = (await executeShellCommandWithProgress(command, {
1415
+ progressMessage: `\u23F3 [\uB9AC\uBDF0 \uC9C4\uD589] ${service} | \uB300\uC0C1 ${reviewTargetFiles.length}\uAC1C \uD30C\uC77C`,
1416
+ streamOutput: isTest
1417
+ })).stdout;
996
1418
  trace7("command:exec:done", `resultLength=${result.length}`);
997
- console.log(result);
998
1419
  trace7("report:write:start");
999
1420
  savedReportPath = getNextFilePath(REPORT_DIR, nowStr, ".md");
1000
1421
  fs__default.default.writeFileSync(savedReportPath, result);
@@ -1042,6 +1463,8 @@ ${command}`);
1042
1463
  ${JSON.stringify(
1043
1464
  {
1044
1465
  service,
1466
+ selectedCommitSummary: selectedCommitSummary || null,
1467
+ reviewTargetFiles,
1045
1468
  command: command || null,
1046
1469
  tempDiffPath,
1047
1470
  savedDiffPath: savedDiffPath || null,