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,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { exec, execSync } from 'child_process';
2
+ import { exec, execSync, execFileSync } from 'child_process';
3
3
  import fs from 'fs';
4
4
  import util, { inspect } from 'util';
5
5
  import path from 'path';
@@ -8,14 +8,52 @@ import { fileURLToPath } from 'url';
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
- path.resolve(__dirname, "../../src/common/form/review-form.md");
15
- var reviewFormOneByOnePath = 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
+ resolvePackageAssetPath("src/common/form/review-form.md");
51
+ var reviewFormOneByOnePath = 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",
@@ -39,6 +77,137 @@ function clearTraceMessages() {
39
77
  function getTraceMessages() {
40
78
  return [...traceMessages];
41
79
  }
80
+ var ANSI = {
81
+ bold: "\x1B[1m",
82
+ cyan: "\x1B[36m",
83
+ dim: "\x1B[2m",
84
+ green: "\x1B[32m",
85
+ reset: "\x1B[0m",
86
+ yellow: "\x1B[33m"
87
+ };
88
+ var ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
89
+ var COMBINING_MARK_PATTERN = /\p{Mark}/u;
90
+ var GRAPHEME_SEGMENTER = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter("ko", { granularity: "grapheme" }) : null;
91
+ function getGitDiffPathspecs() {
92
+ return {
93
+ excludePatterns: ignoreList.map((item) => `:(exclude)${item}`),
94
+ includePatterns: ["*.ts", "*.tsx", "*.js", "*.jsx"]
95
+ };
96
+ }
97
+ function segmentGraphemes(value) {
98
+ if (!GRAPHEME_SEGMENTER) {
99
+ return [...value];
100
+ }
101
+ return [...GRAPHEME_SEGMENTER.segment(value)].map(({ segment }) => segment);
102
+ }
103
+ function isWideCodePoint(codePoint) {
104
+ 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);
105
+ }
106
+ function isEmojiCodePoint(codePoint) {
107
+ return codePoint >= 127462 && codePoint <= 127487 || codePoint >= 127744 && codePoint <= 129791 || codePoint >= 9728 && codePoint <= 10175;
108
+ }
109
+ function getGraphemeWidth(grapheme) {
110
+ let width = 0;
111
+ for (const character of grapheme) {
112
+ const codePoint = character.codePointAt(0);
113
+ if (!codePoint || COMBINING_MARK_PATTERN.test(character) || codePoint === 8205) {
114
+ continue;
115
+ }
116
+ if (codePoint >= 65024 && codePoint <= 65039 || codePoint >= 917760 && codePoint <= 917999) {
117
+ continue;
118
+ }
119
+ if (isWideCodePoint(codePoint) || isEmojiCodePoint(codePoint)) {
120
+ width = Math.max(width, 2);
121
+ continue;
122
+ }
123
+ width = Math.max(width, 1);
124
+ }
125
+ return width;
126
+ }
127
+ function tokenizePlainText(value) {
128
+ return segmentGraphemes(value).map((segment) => ({
129
+ value: segment,
130
+ visibleWidth: getGraphemeWidth(segment)
131
+ }));
132
+ }
133
+ function tokenizeVisibleText(value) {
134
+ const tokens = [];
135
+ let lastIndex = 0;
136
+ for (const match of value.matchAll(ANSI_PATTERN)) {
137
+ const index = match.index ?? 0;
138
+ if (index > lastIndex) {
139
+ tokens.push(...tokenizePlainText(value.slice(lastIndex, index)));
140
+ }
141
+ tokens.push({
142
+ value: match[0],
143
+ visibleWidth: 0
144
+ });
145
+ lastIndex = index + match[0].length;
146
+ }
147
+ if (lastIndex < value.length) {
148
+ tokens.push(...tokenizePlainText(value.slice(lastIndex)));
149
+ }
150
+ return tokens;
151
+ }
152
+ function truncateLineForTerminal(value, maxWidth) {
153
+ if (maxWidth <= 0) {
154
+ return "";
155
+ }
156
+ const tokens = tokenizeVisibleText(value);
157
+ const totalWidth = tokens.reduce((sum, token) => sum + token.visibleWidth, 0);
158
+ if (totalWidth <= maxWidth) {
159
+ return value;
160
+ }
161
+ const ellipsis = "...";
162
+ const ellipsisWidth = 3;
163
+ const targetWidth = Math.max(0, maxWidth - ellipsisWidth);
164
+ let usedWidth = 0;
165
+ let result = "";
166
+ for (const token of tokens) {
167
+ if (token.visibleWidth === 0) {
168
+ result += token.value;
169
+ continue;
170
+ }
171
+ if (usedWidth + token.visibleWidth > targetWidth) {
172
+ break;
173
+ }
174
+ result += token.value;
175
+ usedWidth += token.visibleWidth;
176
+ }
177
+ return `${result}${ellipsis}${ANSI.reset}`;
178
+ }
179
+ function fitLinesToTerminal(lines) {
180
+ const maxWidth = Math.max(20, (process.stdout.columns || 120) - 1);
181
+ return lines.map((line) => truncateLineForTerminal(line, maxWidth));
182
+ }
183
+ function runGitCommand(args4, options = {}) {
184
+ const { allowFailure = false, trimOutput = true } = options;
185
+ try {
186
+ const output = execFileSync("git", args4, {
187
+ encoding: "utf8",
188
+ maxBuffer: 1024 * 1024 * 20,
189
+ stdio: ["ignore", "pipe", "pipe"]
190
+ });
191
+ return trimOutput ? output.trim() : output;
192
+ } catch (error) {
193
+ helperTrace("git-command:failed", `${args4.join(" ")} | ${getErrorSummary(error)}`);
194
+ if (allowFailure) {
195
+ return "";
196
+ }
197
+ throw error;
198
+ }
199
+ }
200
+ function formatReviewTargetFiles(files, visibleCount = 5) {
201
+ if (files.length === 0) {
202
+ return "(\uC5C6\uC74C)";
203
+ }
204
+ const visibleFiles = files.slice(0, visibleCount);
205
+ const hiddenCount = Math.max(0, files.length - visibleFiles.length);
206
+ if (hiddenCount === 0) {
207
+ return visibleFiles.join(", ");
208
+ }
209
+ return `${visibleFiles.join(", ")} \uC678 ${hiddenCount}\uAC1C`;
210
+ }
42
211
  function createTraceLogger(scope, args4 = process.argv.slice(2)) {
43
212
  const enabled = isTestMode(args4);
44
213
  return (step, detail) => {
@@ -250,13 +419,83 @@ ${JSON.stringify(AIServices, null, 2)}
250
419
  }
251
420
  );
252
421
  }
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 };
422
+ function truncateCommitSubject(subject) {
423
+ if (subject.length <= 72) {
424
+ return subject;
425
+ }
426
+ return `${subject.slice(0, 69)}...`;
427
+ }
428
+ function getRecentCommitOptions() {
429
+ const output = runGitCommand(
430
+ ["log", `-${COMMIT_FETCH_LIMIT}`, "--date=relative", "--pretty=format:%h%x09%an%x09%ar%x09%s"],
431
+ { allowFailure: true }
432
+ );
433
+ if (!output) {
434
+ return [];
435
+ }
436
+ return output.split("\n").map((line) => {
437
+ const [hash = "", author = "", relativeDate = "", ...subjectParts] = line.split(" ");
438
+ const subject = subjectParts.join(" ").trim();
439
+ return {
440
+ author,
441
+ description: `${author} | ${relativeDate}`,
442
+ hash,
443
+ label: `${hash} | ${truncateCommitSubject(subject)}`,
444
+ relativeDate,
445
+ subject
446
+ };
447
+ });
448
+ }
449
+ function buildSelectedCommitSummary(commits) {
450
+ return commits.map((commit) => `- ${commit.hash} | ${commit.subject} | ${commit.author} | ${commit.relativeDate}`).join("\n");
451
+ }
452
+ function getReviewPathspecArgs() {
453
+ const { includePatterns, excludePatterns } = getGitDiffPathspecs();
454
+ return [...includePatterns, ...excludePatterns];
455
+ }
456
+ function buildSelectedCommitDiff(commits) {
457
+ const reviewPathspecArgs = getReviewPathspecArgs();
458
+ const sections = commits.map((commit) => {
459
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", ...reviewPathspecArgs], {
460
+ allowFailure: true,
461
+ trimOutput: false
462
+ }).trim();
463
+ if (!diff) {
464
+ return "";
465
+ }
466
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
467
+ }).filter(Boolean).join("\n\n");
468
+ if (!sections) {
469
+ return "";
470
+ }
471
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", "# \uB9AC\uBDF0 \uB300\uC0C1 diff", sections].join("\n");
472
+ }
473
+ function getSelectedCommitFiles(commits) {
474
+ const reviewPathspecArgs = getReviewPathspecArgs();
475
+ const files = /* @__PURE__ */ new Set();
476
+ commits.forEach((commit) => {
477
+ const output = runGitCommand(["show", "--pretty=format:", "--name-only", commit.hash, "--", ...reviewPathspecArgs], {
478
+ allowFailure: true
479
+ });
480
+ output.split("\n").map((line) => line.trim()).filter(Boolean).forEach((filePath) => files.add(filePath));
481
+ });
482
+ return [...files];
483
+ }
484
+ function buildSelectedFileDiff(commits, filePath) {
485
+ const sections = commits.map((commit) => {
486
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", filePath], {
487
+ allowFailure: true,
488
+ trimOutput: false
489
+ }).trim();
490
+ if (!diff) {
491
+ return "";
492
+ }
493
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
494
+ }).filter(Boolean).join("\n\n");
495
+ if (!sections) {
496
+ return "";
497
+ }
498
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", `# \uD30C\uC77C: ${filePath}`, sections].join("\n\n");
260
499
  }
261
500
  function openReport(reportPath) {
262
501
  const resolvedPath = path.resolve(reportPath);
@@ -307,59 +546,166 @@ function openReport(reportPath) {
307
546
  helperTrace("open-report:unsupported-platform", platform);
308
547
  console.error(`\u26A0\uFE0F \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD50C\uB7AB\uD3FC\uC785\uB2C8\uB2E4: ${platform}`);
309
548
  }
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
- });
549
+ function ensureInteractiveSelectionAvailable(scope, message) {
550
+ if (process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function") {
551
+ return;
552
+ }
553
+ helperTrace(`${scope}:tty-missing`);
554
+ exitWithError(message, {
555
+ scope: `helper:${scope}`
556
+ });
557
+ }
558
+ function renderSelectionBlock(lines, previousLineCount) {
559
+ if (previousLineCount > 0) {
560
+ readline.moveCursor(process.stdout, 0, -previousLineCount);
561
+ readline.clearScreenDown(process.stdout);
562
+ }
563
+ const fittedLines = fitLinesToTerminal(lines);
564
+ process.stdout.write(`${fittedLines.join("\n")}
565
+ `);
566
+ return fittedLines.length;
567
+ }
568
+ function getSelectionWindowRange(optionCount, selectedIndex, windowSize) {
569
+ if (optionCount <= windowSize) {
570
+ return {
571
+ end: optionCount,
572
+ start: 0
573
+ };
574
+ }
575
+ const halfWindow = Math.floor(windowSize / 2);
576
+ const maxStart = optionCount - windowSize;
577
+ const start = Math.max(0, Math.min(selectedIndex - halfWindow, maxStart));
578
+ return {
579
+ end: Math.min(optionCount, start + windowSize),
580
+ start
581
+ };
582
+ }
583
+ function buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize) {
584
+ const { start, end } = getSelectionWindowRange(options.length, selectedIndex, windowSize);
585
+ const lines = [
586
+ `${ANSI.bold}${question}${ANSI.reset}`,
587
+ `${ANSI.dim}\u2191\u2193 \uC774\uB3D9 | Space \uC120\uD0DD/\uD574\uC81C | Enter \uC644\uB8CC | Esc \uCDE8\uC18C${ANSI.reset}`,
588
+ `${ANSI.dim}\uC120\uD0DD\uB428: ${toggled.size}\uAC1C / \uC804\uCCB4: ${options.length}\uAC1C${ANSI.reset}`
589
+ ];
590
+ for (let index = start; index < end; index += 1) {
591
+ const option = options[index];
592
+ if (!option) {
593
+ continue;
324
594
  }
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
- }
595
+ const cursor = index === selectedIndex ? `${ANSI.cyan}>${ANSI.reset}` : " ";
596
+ const checked = toggled.has(index) ? `${ANSI.green}\u2611${ANSI.reset}` : "\u2610";
597
+ const description = option.description ? ` ${ANSI.dim}${option.description}${ANSI.reset}` : "";
598
+ lines.push(`${cursor} ${checked} ${option.label}${description}`);
599
+ }
600
+ if (options.length > windowSize) {
601
+ lines.push(`${ANSI.dim}\uD45C\uC2DC \uBC94\uC704: ${start + 1}-${end} / ${options.length}${ANSI.reset}`);
602
+ }
603
+ return lines;
604
+ }
605
+ async function showMultiSelect(question, options, windowSize = COMMIT_SELECTION_WINDOW) {
606
+ ensureInteractiveSelectionAvailable("showMultiSelect", "\u274C \uCEE4\uBC0B \uC120\uD0DD \uBAA8\uB2EC\uC740 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
607
+ let selectedIndex = 0;
608
+ let renderedLineCount = 0;
609
+ const toggled = /* @__PURE__ */ new Set();
610
+ const rl = readline.createInterface({
611
+ input: process.stdin,
612
+ output: process.stdout,
613
+ terminal: true
614
+ });
615
+ process.stdout.write("\x1B[?25l");
616
+ const cleanup = () => {
617
+ if (renderedLineCount > 0) {
618
+ readline.moveCursor(process.stdout, 0, -renderedLineCount);
619
+ readline.clearScreenDown(process.stdout);
620
+ renderedLineCount = 0;
332
621
  }
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}`);
622
+ process.stdin.removeListener("data", onData);
623
+ process.stdin.setRawMode(false);
624
+ process.stdin.pause();
625
+ rl.close();
626
+ process.stdout.write("\x1B[?25h");
627
+ };
628
+ const render = () => {
629
+ const lines = buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize);
630
+ renderedLineCount = renderSelectionBlock(lines, renderedLineCount);
631
+ };
632
+ const confirmSelection = (resolve) => {
633
+ const values = [...toggled].sort((left, right) => left - right).map((index) => options[index]?.value).filter((value) => value !== void 0);
634
+ cleanup();
635
+ resolve(values);
636
+ };
637
+ const cancelSelection = (resolve) => {
638
+ cleanup();
639
+ resolve([]);
640
+ };
641
+ let onData = (_data) => {
642
+ };
643
+ render();
644
+ return new Promise((resolve) => {
645
+ onData = (data) => {
646
+ const key = data.toString();
647
+ if (key === "") {
648
+ cleanup();
649
+ process.exit(0);
346
650
  }
347
- } catch (error) {
348
- helperTrace("diff-args:unstaged-check:failed", getErrorSummary(error));
349
- }
651
+ if (key === "\x1B") {
652
+ cancelSelection(resolve);
653
+ return;
654
+ }
655
+ if (key === "\x1B[A") {
656
+ selectedIndex = (selectedIndex - 1 + options.length) % options.length;
657
+ render();
658
+ return;
659
+ }
660
+ if (key === "\x1B[B") {
661
+ selectedIndex = (selectedIndex + 1) % options.length;
662
+ render();
663
+ return;
664
+ }
665
+ if (key === " ") {
666
+ if (toggled.has(selectedIndex)) {
667
+ toggled.delete(selectedIndex);
668
+ } else {
669
+ toggled.add(selectedIndex);
670
+ }
671
+ render();
672
+ return;
673
+ }
674
+ if (key === "\r" || key === "\n") {
675
+ confirmSelection(resolve);
676
+ }
677
+ };
678
+ process.stdin.setRawMode(true);
679
+ process.stdin.resume();
680
+ process.stdin.on("data", onData);
681
+ });
682
+ }
683
+ async function selectReviewCommits() {
684
+ const commits = getRecentCommitOptions();
685
+ if (commits.length === 0) {
686
+ console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uCD5C\uADFC \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
687
+ return [];
350
688
  }
351
- helperTrace("diff-args:resolve:done", diffArgs || "(default)");
352
- return diffArgs;
689
+ return showMultiSelect(
690
+ "\uB9AC\uBDF0\uD560 \uCEE4\uBC0B\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.",
691
+ commits.map((commit) => ({
692
+ description: commit.description,
693
+ label: commit.label,
694
+ value: commit
695
+ })),
696
+ COMMIT_SELECTION_WINDOW
697
+ );
353
698
  }
354
699
  async function showSelectionAIService() {
355
700
  const selectedServiceFromArgs = parseServiceFromArgs();
356
701
  if (selectedServiceFromArgs) {
357
702
  helperTrace("show-selection:from-args", selectedServiceFromArgs);
358
703
  console.log(`
359
- \u2705 \x1B[32m${selectedServiceFromArgs}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
704
+ \u2705 ${ANSI.green}${selectedServiceFromArgs}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
360
705
  `);
361
706
  return selectedServiceFromArgs;
362
707
  }
708
+ 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
709
  helperTrace("show-selection:interactive:start");
364
710
  let selectedIndex = 0;
365
711
  const rl = readline.createInterface({
@@ -377,11 +723,12 @@ async function showSelectionAIService() {
377
723
  helperTrace("show-selection:interactive:render", AIServices[selectedIndex] || "unknown");
378
724
  readline.clearScreenDown(process.stdout);
379
725
  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"
726
+ `\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):
727
+ `
381
728
  );
382
729
  AIServices.forEach((service, index) => {
383
730
  if (index === selectedIndex) {
384
- process.stdout.write(` \x1B[36m>\x1B[0m \x1B[36m\u25C9\x1B[0m \x1B[1m${service}\x1B[0m
731
+ process.stdout.write(` ${ANSI.cyan}>${ANSI.reset} ${ANSI.cyan}\u25C9${ANSI.reset} ${ANSI.bold}${service}${ANSI.reset}
385
732
  `);
386
733
  } else {
387
734
  process.stdout.write(` \u25EF ${service}
@@ -411,7 +758,7 @@ async function showSelectionAIService() {
411
758
  rl.close();
412
759
  process.stdout.write("\x1B[?25h");
413
760
  console.log(`
414
- \u2705 \x1B[32m${AIServices[selectedIndex]}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
761
+ \u2705 ${ANSI.green}${AIServices[selectedIndex]}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
415
762
  `);
416
763
  const result = AIServices[selectedIndex];
417
764
  if (result) {
@@ -438,6 +785,11 @@ function getArgValue(flag) {
438
785
  }
439
786
  return args[index + 1];
440
787
  }
788
+ function printNotice(message) {
789
+ if (args.includes("--test")) {
790
+ console.warn(message);
791
+ }
792
+ }
441
793
  function toUnique(values) {
442
794
  const seen = /* @__PURE__ */ new Set();
443
795
  return values.filter((value) => {
@@ -461,11 +813,11 @@ function resolveReasoningEffort() {
461
813
  const normalized = normalizeEffort(customReasoningEffort);
462
814
  trace("reasoning:custom", `${customReasoningEffort} -> ${normalized}`);
463
815
  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.");
816
+ printNotice("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
465
817
  }
466
818
  return normalized;
467
819
  }
468
- console.warn(
820
+ printNotice(
469
821
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS.join(
470
822
  ", "
471
823
  )}`
@@ -508,7 +860,7 @@ function buildClaudeExecCommand(options) {
508
860
  const modelOption = model ? `--model ${shellQuote(model)}` : "";
509
861
  const fallbackOption = model && fallbackModel ? `--fallback-model ${shellQuote(fallbackModel)}` : "";
510
862
  const effortOption = `--effort ${shellQuote(effort)}`;
511
- const appendedPromptFiles = systemPromptFiles.map((path2) => `--append-system-prompt-file ${shellQuote(path2)}`).join(" ");
863
+ const appendedPromptFiles = systemPromptFiles.map((path3) => `--append-system-prompt-file ${shellQuote(path3)}`).join(" ");
512
864
  return `cat ${shellQuote(tempDiffPath2)} | claude ${[
513
865
  modelOption,
514
866
  fallbackOption,
@@ -541,13 +893,13 @@ var createClaudeCommand = (tempDiffPath2, reviewFormPath2) => {
541
893
  trace("model:candidates", modelCandidates.join(", "));
542
894
  trace("command:candidates:count", String(modelCandidates.length + 1));
543
895
  if (customModel) {
544
- console.warn(
896
+ printNotice(
545
897
  `\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
546
898
  " -> "
547
899
  )}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
548
900
  );
549
901
  } else {
550
- console.warn(
902
+ printNotice(
551
903
  `\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
904
  );
553
905
  }
@@ -627,6 +979,11 @@ function getArgValue2(flag) {
627
979
  }
628
980
  return args2[index + 1];
629
981
  }
982
+ function printNotice2(message) {
983
+ if (args2.includes("--test")) {
984
+ console.warn(message);
985
+ }
986
+ }
630
987
  function resolveReasoningEffort2() {
631
988
  const customReasoningEffort = getArgValue2("--reasoning-effort");
632
989
  if (customReasoningEffort) {
@@ -634,7 +991,7 @@ function resolveReasoningEffort2() {
634
991
  trace3("reasoning:custom", customReasoningEffort);
635
992
  return customReasoningEffort;
636
993
  }
637
- console.warn(
994
+ printNotice2(
638
995
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS2.join(
639
996
  ", "
640
997
  )}`
@@ -678,14 +1035,14 @@ ${reviewFormLine || "- (\uC5C6\uC74C)"}
678
1035
  trace3("prompt:prepared", `length=${prompt.length}`);
679
1036
  let command = "";
680
1037
  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.");
1038
+ 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
1039
  trace3("model:custom", customModel);
683
1040
  command = buildCodexExecCommand(prompt, reasoningEffort, customModel);
684
1041
  } else {
685
1042
  const preferredModelAlias = "gpt-5";
686
1043
  const aliasCommand = buildCodexExecCommand(prompt, reasoningEffort, preferredModelAlias);
687
1044
  const fallbackCommand = buildCodexExecCommand(prompt, reasoningEffort);
688
- console.warn(
1045
+ printNotice2(
689
1046
  `\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
1047
  );
691
1048
  trace3("model:alias-first", preferredModelAlias);
@@ -746,6 +1103,11 @@ function getArgValue3(flag) {
746
1103
  }
747
1104
  return args3[index + 1];
748
1105
  }
1106
+ function printNotice3(message) {
1107
+ if (args3.includes("--test")) {
1108
+ console.warn(message);
1109
+ }
1110
+ }
749
1111
  function toUnique2(values) {
750
1112
  const seen = /* @__PURE__ */ new Set();
751
1113
  return values.filter((value) => {
@@ -763,7 +1125,7 @@ function resolveReasoningEffort3() {
763
1125
  trace5("reasoning:custom", customReasoningEffort);
764
1126
  return customReasoningEffort;
765
1127
  }
766
- console.warn(
1128
+ printNotice3(
767
1129
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS3.join(
768
1130
  ", "
769
1131
  )}`
@@ -825,38 +1187,57 @@ function buildGeminiExecCommand(prompt, model) {
825
1187
  const modelOption = model ? `--model ${shellQuote3(model)}` : "";
826
1188
  return `gemini ${[modelOption, "-p", shellQuote3(prompt)].filter(Boolean).join(" ")}`;
827
1189
  }
1190
+ function toGeminiFileReference(filePath) {
1191
+ return `@${filePath}`;
1192
+ }
1193
+ function buildGeminiFileReferenceSection(files) {
1194
+ const existingFiles = files.filter((file) => fs.existsSync(file.path));
1195
+ return {
1196
+ count: existingFiles.length,
1197
+ lines: existingFiles.map((file) => `- ${file.display}: ${toGeminiFileReference(file.path)}`)
1198
+ };
1199
+ }
828
1200
  var createGeminiCommand = (tempDiffPath2, reviewFormPath2) => {
829
1201
  trace5("createGeminiCommand:start", `tempDiffPath=${tempDiffPath2}, reviewFormPath=${reviewFormPath2}`);
830
1202
  const customModel = getArgValue3("--model");
831
1203
  const reasoningEffort = resolveReasoningEffort3();
832
1204
  const primaryAlias = resolvePrimaryAlias2(reasoningEffort);
833
1205
  const aliasFallbacks = toUnique2(getAliasFallbacks2(primaryAlias));
1206
+ const resolvedTempDiffPath = path.resolve(tempDiffPath2);
1207
+ const resolvedReviewFormPath = path.resolve(reviewFormPath2);
834
1208
  const rules = [
835
1209
  { path: rulesPath, display: "\uB8F0\uC14B" },
836
1210
  { path: namingRulesPath, display: "\uB124\uC774\uBC0D \uADDC\uCE59" },
837
1211
  { path: codingConventionRulesPath, display: "\uCF54\uB529 \uCEE8\uBCA4\uC158" }
838
1212
  ];
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");
1213
+ const ruleSection = buildGeminiFileReferenceSection(rules);
1214
+ trace5("rules:loaded", `count=${ruleSection.count}`);
1215
+ const reviewFormExists = fs.existsSync(resolvedReviewFormPath);
1216
+ const reviewFormLine = reviewFormExists ? `- \uB9AC\uBDF0 \uC591\uC2DD: ${toGeminiFileReference(resolvedReviewFormPath)}` : "- \uB9AC\uBDF0 \uC591\uC2DD: (\uC5C6\uC74C)";
1217
+ trace5("reviewForm:status", reviewFormExists ? "exists" : "missing");
844
1218
  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.
1219
+ const prompt = `\uC544\uB798 \uD30C\uC77C\uB4E4\uC744 \uC21C\uC11C\uB300\uB85C \uC77D\uACE0 \uCF54\uB4DC \uB9AC\uBDF0\uB97C \uC9C4\uD589\uD574\uC918.
1220
+ \uADDC\uCE59 \uD30C\uC77C:
1221
+ ${ruleSection.lines.join("\n") || "- (\uC5C6\uC74C)"}
1222
+ \uB9AC\uBDF0 \uC591\uC2DD \uD30C\uC77C:
1223
+ ${reviewFormLine}
1224
+ \uB9AC\uBDF0 \uB300\uC0C1 diff \uD30C\uC77C:
1225
+ - \uB9AC\uBDF0 \uB300\uC0C1 diff: ${toGeminiFileReference(resolvedTempDiffPath)}
1226
+
1227
+ \uBC18\uB4DC\uC2DC \uB9AC\uBDF0 \uC591\uC2DD\uC5D0 \uB9DE\uCDB0 \uC791\uC131\uD574\uC918.
847
1228
  \uCD94\uB860 \uAC15\uB3C4 \uC9C0\uCE68: ${reasoningInstruction}`;
848
1229
  trace5("prompt:prepared", `length=${prompt.length}`);
849
1230
  const modelCandidates = toUnique2(customModel ? [customModel, ...aliasFallbacks] : aliasFallbacks);
850
1231
  trace5("model:candidates", modelCandidates.join(", "));
851
1232
  trace5("command:candidates:count", String(modelCandidates.length + 1));
852
1233
  if (customModel) {
853
- console.warn(
1234
+ printNotice3(
854
1235
  `\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
855
1236
  " -> "
856
1237
  )}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
857
1238
  );
858
1239
  } else {
859
- console.warn(
1240
+ printNotice3(
860
1241
  `\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
1242
  );
862
1243
  }
@@ -915,6 +1296,7 @@ async function main() {
915
1296
  let service = "";
916
1297
  let savedDiffPath = "";
917
1298
  let savedReportPath = "";
1299
+ let selectedCommitSummary = "";
918
1300
  try {
919
1301
  trace7("service-selection:start");
920
1302
  service = await showSelectionAIService();
@@ -938,29 +1320,35 @@ async function main() {
938
1320
  }
939
1321
  trace7("review-flow:start");
940
1322
  console.log("\u{1F680} AI Code Review\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4...");
941
- trace7("report-dir:create:start");
942
- createReportDirectory();
943
- trace7("report-dir:create:done");
944
- const { includeParams, excludeParams } = getGitDiffFilter();
945
- trace7("diff-filter:loaded", `include=${includeParams} | exclude=${excludeParams}`);
946
- trace7("diff-args:build:start");
947
- const diffArgs = getDiffArgs();
948
- trace7("diff-args:build:done", `diffArgs=${diffArgs || "(default)"}`);
949
- const filesCommand = `git diff --name-only ${diffArgs} -- ${includeParams} ${excludeParams}`;
950
- trace7("files-command:run", filesCommand);
951
- const fileList = execSync(filesCommand).toString().split("\n").filter(Boolean);
1323
+ trace7("commit-selection:start");
1324
+ const selectedCommits = await selectReviewCommits();
1325
+ trace7("commit-selection:done", `count=${selectedCommits.length}`);
1326
+ if (selectedCommits.length === 0) {
1327
+ trace7("commit-selection:empty");
1328
+ console.log("\u2139\uFE0F \uC120\uD0DD\uB41C \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1329
+ deleteTempDiff();
1330
+ process.exit(0);
1331
+ }
1332
+ selectedCommitSummary = buildSelectedCommitSummary(selectedCommits);
1333
+ trace7("commit-summary:prepared", selectedCommitSummary);
1334
+ console.log("\u23F3 \uC120\uD0DD\uD55C \uCEE4\uBC0B\uC758 \uD30C\uC77C \uBAA9\uB85D\uACFC diff\uB97C \uC815\uB9AC\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...");
1335
+ trace7("files-command:run");
1336
+ const fileList = getSelectedCommitFiles(selectedCommits);
952
1337
  trace7("files-command:done", `fileCount=${fileList.length}`);
1338
+ console.log(`\u{1F4C2} \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C(${fileList.length}\uAC1C): ${formatReviewTargetFiles(fileList)}`);
953
1339
  if (fileList.length === 0) {
954
1340
  trace7("empty-file-list:exit");
955
- console.log("\u2139\uFE0F \uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1341
+ 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.");
956
1342
  deleteTempDiff();
957
1343
  process.exit(0);
958
1344
  }
959
- const fullDiffCommand = `git diff ${diffArgs} -- ${includeParams} ${excludeParams}`;
960
- trace7("full-diff:run");
961
- const fullDiff = execSync(fullDiffCommand).toString();
1345
+ trace7("report-dir:create:start");
1346
+ createReportDirectory();
1347
+ trace7("report-dir:create:done");
1348
+ trace7("full-diff:build:start");
1349
+ const fullDiff = buildSelectedCommitDiff(selectedCommits);
962
1350
  const nowStr = getNowString();
963
- trace7("full-diff:done", `length=${fullDiff.length}`);
1351
+ trace7("full-diff:build:done", `length=${fullDiff.length}`);
964
1352
  trace7("timestamp:created", nowStr);
965
1353
  trace7("temp-diff:write:start", tempDiffPath);
966
1354
  fs.writeFileSync(tempDiffPath, fullDiff);
@@ -971,15 +1359,19 @@ async function main() {
971
1359
  trace7("saved-diff:copy:done", savedDiffPath);
972
1360
  savedReportPath = getNextFilePath(REPORT_DIR, nowStr, ".md");
973
1361
  trace7("saved-report:path", savedReportPath);
974
- const promises = fileList.map(async (file) => {
1362
+ const promises = fileList.map(async (file, index) => {
975
1363
  const tempOneFileDiffPath = `temp_diff_${file.replace(/\//g, "_")}.txt`;
976
1364
  let command = "";
977
1365
  try {
978
1366
  trace7("file-review:start", file);
979
- console.log(`\u{1F50D} Reviewing: ${file}...`);
980
- trace7("file-diff:run", file);
981
- const fileDiff = execSync(`git diff ${diffArgs} -- "${file}"`).toString();
982
- trace7("file-diff:done", `${file} | length=${fileDiff.length}`);
1367
+ console.log(`\u{1F50D} [${index + 1}/${fileList.length}] \uB9AC\uBDF0 \uC900\uBE44: ${file}`);
1368
+ trace7("file-diff:build:start", file);
1369
+ const fileDiff = buildSelectedFileDiff(selectedCommits, file);
1370
+ trace7("file-diff:build:done", `${file} | length=${fileDiff.length}`);
1371
+ if (!fileDiff.trim()) {
1372
+ trace7("file-diff:empty", file);
1373
+ return;
1374
+ }
983
1375
  trace7("file-temp-diff:write:start", tempOneFileDiffPath);
984
1376
  fs.writeFileSync(tempOneFileDiffPath, fileDiff);
985
1377
  trace7("file-temp-diff:write:done", tempOneFileDiffPath);
@@ -996,6 +1388,7 @@ async function main() {
996
1388
  break;
997
1389
  }
998
1390
  trace7("file-command:create:done", `${file} | commandLength=${command.length}`);
1391
+ console.log(`\u23F3 [${index + 1}/${fileList.length}] \uB9AC\uBDF0 \uC9C4\uD589: ${file}`);
999
1392
  trace7("file-command:exec:start", file);
1000
1393
  const { stdout } = await execAsync(command, { maxBuffer: 1024 * 1024 * 20 });
1001
1394
  const result = stdout.toString();
@@ -1007,7 +1400,6 @@ async function main() {
1007
1400
  ${result}
1008
1401
 
1009
1402
  `;
1010
- console.log(tempReport);
1011
1403
  trace7("file-report:append:start", file);
1012
1404
  fs.appendFileSync(savedReportPath, tempReport);
1013
1405
  trace7("file-report:append:done", file);
@@ -1033,6 +1425,7 @@ ${command}`);
1033
1425
  ${JSON.stringify(
1034
1426
  {
1035
1427
  service,
1428
+ selectedCommitSummary: selectedCommitSummary || null,
1036
1429
  file,
1037
1430
  command: command || null,
1038
1431
  tempOneFileDiffPath,
@@ -1107,6 +1500,7 @@ ${getErrorSummary(err)}
1107
1500
  ${JSON.stringify(
1108
1501
  {
1109
1502
  service: service || null,
1503
+ selectedCommitSummary: selectedCommitSummary || null,
1110
1504
  tempDiffPath,
1111
1505
  savedDiffPath: savedDiffPath || null,
1112
1506
  savedReportPath: savedReportPath || null