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
@@ -17,14 +17,52 @@ var readline__default = /*#__PURE__*/_interopDefault(readline);
17
17
  // src/common/helper.ts
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('helper.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
- var reviewFormOneByOnePath = 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
+ var reviewFormOneByOnePath = 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(args, options = {}) {
193
+ const { allowFailure = false, trimOutput = true } = options;
194
+ try {
195
+ const output = child_process.execFileSync("git", args, {
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", `${args.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, args = process.argv.slice(2)) {
52
269
  const enabled = isTestMode(args);
53
270
  return (step, detail) => {
@@ -260,13 +477,90 @@ ${JSON.stringify(AIServices, null, 2)}
260
477
  );
261
478
  }
262
479
  function getGitDiffFilter() {
263
- const includeExtensions = ["*.ts", "*.tsx", "*.js", "*.jsx"];
264
- const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);
480
+ const { includePatterns, excludePatterns } = getGitDiffPathspecs();
265
481
  const quote = (pattern) => `"${pattern}"`;
266
- const includeParams = includeExtensions.map(quote).join(" ");
482
+ const includeParams = includePatterns.map(quote).join(" ");
267
483
  const excludeParams = excludePatterns.map(quote).join(" ");
268
484
  return { includeParams, excludeParams };
269
485
  }
486
+ function truncateCommitSubject(subject) {
487
+ if (subject.length <= 72) {
488
+ return subject;
489
+ }
490
+ return `${subject.slice(0, 69)}...`;
491
+ }
492
+ function getRecentCommitOptions() {
493
+ const output = runGitCommand(
494
+ ["log", `-${COMMIT_FETCH_LIMIT}`, "--date=relative", "--pretty=format:%h%x09%an%x09%ar%x09%s"],
495
+ { allowFailure: true }
496
+ );
497
+ if (!output) {
498
+ return [];
499
+ }
500
+ return output.split("\n").map((line) => {
501
+ const [hash = "", author = "", relativeDate = "", ...subjectParts] = line.split(" ");
502
+ const subject = subjectParts.join(" ").trim();
503
+ return {
504
+ author,
505
+ description: `${author} | ${relativeDate}`,
506
+ hash,
507
+ label: `${hash} | ${truncateCommitSubject(subject)}`,
508
+ relativeDate,
509
+ subject
510
+ };
511
+ });
512
+ }
513
+ function buildSelectedCommitSummary(commits) {
514
+ return commits.map((commit) => `- ${commit.hash} | ${commit.subject} | ${commit.author} | ${commit.relativeDate}`).join("\n");
515
+ }
516
+ function getReviewPathspecArgs() {
517
+ const { includePatterns, excludePatterns } = getGitDiffPathspecs();
518
+ return [...includePatterns, ...excludePatterns];
519
+ }
520
+ function buildSelectedCommitDiff(commits) {
521
+ const reviewPathspecArgs = getReviewPathspecArgs();
522
+ const sections = commits.map((commit) => {
523
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", ...reviewPathspecArgs], {
524
+ allowFailure: true,
525
+ trimOutput: false
526
+ }).trim();
527
+ if (!diff) {
528
+ return "";
529
+ }
530
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
531
+ }).filter(Boolean).join("\n\n");
532
+ if (!sections) {
533
+ return "";
534
+ }
535
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", "# \uB9AC\uBDF0 \uB300\uC0C1 diff", sections].join("\n");
536
+ }
537
+ function getSelectedCommitFiles(commits) {
538
+ const reviewPathspecArgs = getReviewPathspecArgs();
539
+ const files = /* @__PURE__ */ new Set();
540
+ commits.forEach((commit) => {
541
+ const output = runGitCommand(["show", "--pretty=format:", "--name-only", commit.hash, "--", ...reviewPathspecArgs], {
542
+ allowFailure: true
543
+ });
544
+ output.split("\n").map((line) => line.trim()).filter(Boolean).forEach((filePath) => files.add(filePath));
545
+ });
546
+ return [...files];
547
+ }
548
+ function buildSelectedFileDiff(commits, filePath) {
549
+ const sections = commits.map((commit) => {
550
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", filePath], {
551
+ allowFailure: true,
552
+ trimOutput: false
553
+ }).trim();
554
+ if (!diff) {
555
+ return "";
556
+ }
557
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
558
+ }).filter(Boolean).join("\n\n");
559
+ if (!sections) {
560
+ return "";
561
+ }
562
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", `# \uD30C\uC77C: ${filePath}`, sections].join("\n\n");
563
+ }
270
564
  function openReport(reportPath) {
271
565
  const resolvedPath = path__default.default.resolve(reportPath);
272
566
  const { platform } = process;
@@ -316,49 +610,155 @@ function openReport(reportPath) {
316
610
  helperTrace("open-report:unsupported-platform", platform);
317
611
  console.error(`\u26A0\uFE0F \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD50C\uB7AB\uD3FC\uC785\uB2C8\uB2E4: ${platform}`);
318
612
  }
319
- function getDiffArgs() {
320
- const args = process.argv.slice(2);
321
- const commitIndex = args.indexOf("--commit");
322
- const { includeParams, excludeParams } = getGitDiffFilter();
323
- helperTrace("diff-args:resolve:start", `args=${JSON.stringify(args)}`);
324
- let diffArgs = "";
325
- if (commitIndex !== -1) {
326
- const commitHash = args[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
332
- });
613
+ function ensureInteractiveSelectionAvailable(scope, message) {
614
+ if (process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function") {
615
+ return;
616
+ }
617
+ helperTrace(`${scope}:tty-missing`);
618
+ exitWithError(message, {
619
+ scope: `helper:${scope}`
620
+ });
621
+ }
622
+ function renderSelectionBlock(lines, previousLineCount) {
623
+ if (previousLineCount > 0) {
624
+ readline__default.default.moveCursor(process.stdout, 0, -previousLineCount);
625
+ readline__default.default.clearScreenDown(process.stdout);
626
+ }
627
+ const fittedLines = fitLinesToTerminal(lines);
628
+ process.stdout.write(`${fittedLines.join("\n")}
629
+ `);
630
+ return fittedLines.length;
631
+ }
632
+ function getSelectionWindowRange(optionCount, selectedIndex, windowSize) {
633
+ if (optionCount <= windowSize) {
634
+ return {
635
+ end: optionCount,
636
+ start: 0
637
+ };
638
+ }
639
+ const halfWindow = Math.floor(windowSize / 2);
640
+ const maxStart = optionCount - windowSize;
641
+ const start = Math.max(0, Math.min(selectedIndex - halfWindow, maxStart));
642
+ return {
643
+ end: Math.min(optionCount, start + windowSize),
644
+ start
645
+ };
646
+ }
647
+ function buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize) {
648
+ const { start, end } = getSelectionWindowRange(options.length, selectedIndex, windowSize);
649
+ const lines = [
650
+ `${ANSI.bold}${question}${ANSI.reset}`,
651
+ `${ANSI.dim}\u2191\u2193 \uC774\uB3D9 | Space \uC120\uD0DD/\uD574\uC81C | Enter \uC644\uB8CC | Esc \uCDE8\uC18C${ANSI.reset}`,
652
+ `${ANSI.dim}\uC120\uD0DD\uB428: ${toggled.size}\uAC1C / \uC804\uCCB4: ${options.length}\uAC1C${ANSI.reset}`
653
+ ];
654
+ for (let index = start; index < end; index += 1) {
655
+ const option = options[index];
656
+ if (!option) {
657
+ continue;
333
658
  }
334
- const nextArg = args[commitIndex + 2];
335
- let n = 0;
336
- if (nextArg && !nextArg.startsWith("--")) {
337
- n = parseInt(nextArg, 10);
338
- if (isNaN(n)) {
339
- n = 0;
340
- }
659
+ const cursor = index === selectedIndex ? `${ANSI.cyan}>${ANSI.reset}` : " ";
660
+ const checked = toggled.has(index) ? `${ANSI.green}\u2611${ANSI.reset}` : "\u2610";
661
+ const description = option.description ? ` ${ANSI.dim}${option.description}${ANSI.reset}` : "";
662
+ lines.push(`${cursor} ${checked} ${option.label}${description}`);
663
+ }
664
+ if (options.length > windowSize) {
665
+ lines.push(`${ANSI.dim}\uD45C\uC2DC \uBC94\uC704: ${start + 1}-${end} / ${options.length}${ANSI.reset}`);
666
+ }
667
+ return lines;
668
+ }
669
+ async function showMultiSelect(question, options, windowSize = COMMIT_SELECTION_WINDOW) {
670
+ ensureInteractiveSelectionAvailable("showMultiSelect", "\u274C \uCEE4\uBC0B \uC120\uD0DD \uBAA8\uB2EC\uC740 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
671
+ let selectedIndex = 0;
672
+ let renderedLineCount = 0;
673
+ const toggled = /* @__PURE__ */ new Set();
674
+ const rl = readline__default.default.createInterface({
675
+ input: process.stdin,
676
+ output: process.stdout,
677
+ terminal: true
678
+ });
679
+ process.stdout.write("\x1B[?25l");
680
+ const cleanup = () => {
681
+ if (renderedLineCount > 0) {
682
+ readline__default.default.moveCursor(process.stdout, 0, -renderedLineCount);
683
+ readline__default.default.clearScreenDown(process.stdout);
684
+ renderedLineCount = 0;
341
685
  }
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}`);
686
+ process.stdin.removeListener("data", onData);
687
+ process.stdin.setRawMode(false);
688
+ process.stdin.pause();
689
+ rl.close();
690
+ process.stdout.write("\x1B[?25h");
691
+ };
692
+ const render = () => {
693
+ const lines = buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize);
694
+ renderedLineCount = renderSelectionBlock(lines, renderedLineCount);
695
+ };
696
+ const confirmSelection = (resolve) => {
697
+ const values = [...toggled].sort((left, right) => left - right).map((index) => options[index]?.value).filter((value) => value !== void 0);
698
+ cleanup();
699
+ resolve(values);
700
+ };
701
+ const cancelSelection = (resolve) => {
702
+ cleanup();
703
+ resolve([]);
704
+ };
705
+ let onData = (_data) => {
706
+ };
707
+ render();
708
+ return new Promise((resolve) => {
709
+ onData = (data) => {
710
+ const key = data.toString();
711
+ if (key === "") {
712
+ cleanup();
713
+ process.exit(0);
355
714
  }
356
- } catch (error) {
357
- helperTrace("diff-args:unstaged-check:failed", getErrorSummary(error));
358
- }
715
+ if (key === "\x1B") {
716
+ cancelSelection(resolve);
717
+ return;
718
+ }
719
+ if (key === "\x1B[A") {
720
+ selectedIndex = (selectedIndex - 1 + options.length) % options.length;
721
+ render();
722
+ return;
723
+ }
724
+ if (key === "\x1B[B") {
725
+ selectedIndex = (selectedIndex + 1) % options.length;
726
+ render();
727
+ return;
728
+ }
729
+ if (key === " ") {
730
+ if (toggled.has(selectedIndex)) {
731
+ toggled.delete(selectedIndex);
732
+ } else {
733
+ toggled.add(selectedIndex);
734
+ }
735
+ render();
736
+ return;
737
+ }
738
+ if (key === "\r" || key === "\n") {
739
+ confirmSelection(resolve);
740
+ }
741
+ };
742
+ process.stdin.setRawMode(true);
743
+ process.stdin.resume();
744
+ process.stdin.on("data", onData);
745
+ });
746
+ }
747
+ async function selectReviewCommits() {
748
+ const commits = getRecentCommitOptions();
749
+ if (commits.length === 0) {
750
+ console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uCD5C\uADFC \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
751
+ return [];
359
752
  }
360
- helperTrace("diff-args:resolve:done", diffArgs || "(default)");
361
- return diffArgs;
753
+ return showMultiSelect(
754
+ "\uB9AC\uBDF0\uD560 \uCEE4\uBC0B\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.",
755
+ commits.map((commit) => ({
756
+ description: commit.description,
757
+ label: commit.label,
758
+ value: commit
759
+ })),
760
+ COMMIT_SELECTION_WINDOW
761
+ );
362
762
  }
363
763
  function selectAIService() {
364
764
  const service = parseServiceFromArgs();
@@ -376,10 +776,11 @@ async function showSelectionAIService() {
376
776
  if (selectedServiceFromArgs) {
377
777
  helperTrace("show-selection:from-args", selectedServiceFromArgs);
378
778
  console.log(`
379
- \u2705 \x1B[32m${selectedServiceFromArgs}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
779
+ \u2705 ${ANSI.green}${selectedServiceFromArgs}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
380
780
  `);
381
781
  return selectedServiceFromArgs;
382
782
  }
783
+ 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.");
383
784
  helperTrace("show-selection:interactive:start");
384
785
  let selectedIndex = 0;
385
786
  const rl = readline__default.default.createInterface({
@@ -397,11 +798,12 @@ async function showSelectionAIService() {
397
798
  helperTrace("show-selection:interactive:render", AIServices[selectedIndex] || "unknown");
398
799
  readline__default.default.clearScreenDown(process.stdout);
399
800
  process.stdout.write(
400
- "\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"
801
+ `\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):
802
+ `
401
803
  );
402
804
  AIServices.forEach((service, index) => {
403
805
  if (index === selectedIndex) {
404
- process.stdout.write(` \x1B[36m>\x1B[0m \x1B[36m\u25C9\x1B[0m \x1B[1m${service}\x1B[0m
806
+ process.stdout.write(` ${ANSI.cyan}>${ANSI.reset} ${ANSI.cyan}\u25C9${ANSI.reset} ${ANSI.bold}${service}${ANSI.reset}
405
807
  `);
406
808
  } else {
407
809
  process.stdout.write(` \u25EF ${service}
@@ -431,7 +833,7 @@ async function showSelectionAIService() {
431
833
  rl.close();
432
834
  process.stdout.write("\x1B[?25h");
433
835
  console.log(`
434
- \u2705 \x1B[32m${AIServices[selectedIndex]}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
836
+ \u2705 ${ANSI.green}${AIServices[selectedIndex]}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
435
837
  `);
436
838
  const result = AIServices[selectedIndex];
437
839
  if (result) {
@@ -447,21 +849,29 @@ async function showSelectionAIService() {
447
849
  }
448
850
 
449
851
  exports.AIServices = AIServices;
852
+ exports.COMMIT_FETCH_LIMIT = COMMIT_FETCH_LIMIT;
853
+ exports.COMMIT_SELECTION_WINDOW = COMMIT_SELECTION_WINDOW;
450
854
  exports.REPORT_DIR = REPORT_DIR;
855
+ exports.buildSelectedCommitDiff = buildSelectedCommitDiff;
856
+ exports.buildSelectedCommitSummary = buildSelectedCommitSummary;
857
+ exports.buildSelectedFileDiff = buildSelectedFileDiff;
451
858
  exports.clearTraceMessages = clearTraceMessages;
452
859
  exports.codingConventionRulesPath = codingConventionRulesPath;
453
860
  exports.createReportDirectory = createReportDirectory;
454
861
  exports.createTraceLogger = createTraceLogger;
455
862
  exports.deleteFile = deleteFile;
456
863
  exports.deleteTempDiff = deleteTempDiff;
864
+ exports.executeShellCommandWithProgress = executeShellCommandWithProgress;
457
865
  exports.exitWithError = exitWithError;
866
+ exports.formatReviewTargetFiles = formatReviewTargetFiles;
458
867
  exports.getAvailableFilePath = getAvailableFilePath;
459
- exports.getDiffArgs = getDiffArgs;
460
868
  exports.getErrorLogTimestamp = getErrorLogTimestamp;
461
869
  exports.getErrorSummary = getErrorSummary;
462
870
  exports.getGitDiffFilter = getGitDiffFilter;
463
871
  exports.getNextFilePath = getNextFilePath;
464
872
  exports.getNowString = getNowString;
873
+ exports.getRecentCommitOptions = getRecentCommitOptions;
874
+ exports.getSelectedCommitFiles = getSelectedCommitFiles;
465
875
  exports.getTraceMessages = getTraceMessages;
466
876
  exports.ignoreList = ignoreList;
467
877
  exports.isTestMode = isTestMode;
@@ -471,6 +881,8 @@ exports.reviewFormOneByOnePath = reviewFormOneByOnePath;
471
881
  exports.reviewFormPath = reviewFormPath;
472
882
  exports.rulesPath = rulesPath;
473
883
  exports.selectAIService = selectAIService;
884
+ exports.selectReviewCommits = selectReviewCommits;
885
+ exports.showMultiSelect = showMultiSelect;
474
886
  exports.showSelectionAIService = showSelectionAIService;
475
887
  exports.tempDiffPath = tempDiffPath;
476
888
  exports.writeErrorReport = writeErrorReport;