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,4 +1,4 @@
1
- import { execSync } from 'child_process';
1
+ import { execSync, execFileSync } from 'child_process';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
  import readline from 'readline';
@@ -8,14 +8,52 @@ import { inspect } from 'util';
8
8
  // src/common/helper.ts
9
9
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
10
10
  var traceMessages = [];
11
- var rulesPath = path.resolve(__dirname, "../../src/common/rules/review-rules.md");
12
- var namingRulesPath = path.resolve(__dirname, "../../src/common/rules/naming-rule.md");
13
- var codingConventionRulesPath = path.resolve(__dirname, "../../src/common/rules/coding-convention.md");
14
- var reviewFormPath = path.resolve(__dirname, "../../src/common/form/review-form.md");
15
- 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
+ var reviewFormPath = 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,185 @@ 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(args, options = {}) {
184
+ const { allowFailure = false, trimOutput = true } = options;
185
+ try {
186
+ const output = execFileSync("git", args, {
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", `${args.join(" ")} | ${getErrorSummary(error)}`);
194
+ if (allowFailure) {
195
+ return "";
196
+ }
197
+ throw error;
198
+ }
199
+ }
200
+ async function executeShellCommandWithProgress(command, options = {}) {
201
+ const { progressIntervalMs = 1e4, progressMessage = "\u23F3 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...", streamOutput = false } = options;
202
+ const { spawn } = await import('child_process');
203
+ return new Promise((resolve, reject) => {
204
+ let stdout = "";
205
+ let stderr = "";
206
+ const startedAt = Date.now();
207
+ console.log(progressMessage);
208
+ const child = spawn("/bin/zsh", ["-lc", command], {
209
+ stdio: ["ignore", "pipe", "pipe"]
210
+ });
211
+ const progressTimer = setInterval(() => {
212
+ const elapsedSeconds = Math.max(1, Math.floor((Date.now() - startedAt) / 1e3));
213
+ console.log(`${progressMessage} (${elapsedSeconds}s \uACBD\uACFC)`);
214
+ }, progressIntervalMs);
215
+ child.stdout.on("data", (chunk) => {
216
+ const text = chunk.toString();
217
+ stdout += text;
218
+ if (streamOutput) {
219
+ process.stdout.write(text);
220
+ }
221
+ });
222
+ child.stderr.on("data", (chunk) => {
223
+ const text = chunk.toString();
224
+ stderr += text;
225
+ if (streamOutput) {
226
+ process.stderr.write(text);
227
+ }
228
+ });
229
+ child.on("error", (error) => {
230
+ clearInterval(progressTimer);
231
+ reject(error);
232
+ });
233
+ child.on("close", (code, signal) => {
234
+ clearInterval(progressTimer);
235
+ if (code === 0) {
236
+ resolve({
237
+ stderr,
238
+ stdout
239
+ });
240
+ return;
241
+ }
242
+ const exitSummary = signal ? `signal=${signal}` : `code=${String(code ?? "unknown")}`;
243
+ reject(new Error(`\uC258 \uBA85\uB839 \uC2E4\uD589 \uC2E4\uD328 (${exitSummary})${stderr.trim() ? `
244
+ ${stderr.trim()}` : ""}`));
245
+ });
246
+ });
247
+ }
248
+ function formatReviewTargetFiles(files, visibleCount = 5) {
249
+ if (files.length === 0) {
250
+ return "(\uC5C6\uC74C)";
251
+ }
252
+ const visibleFiles = files.slice(0, visibleCount);
253
+ const hiddenCount = Math.max(0, files.length - visibleFiles.length);
254
+ if (hiddenCount === 0) {
255
+ return visibleFiles.join(", ");
256
+ }
257
+ return `${visibleFiles.join(", ")} \uC678 ${hiddenCount}\uAC1C`;
258
+ }
42
259
  function createTraceLogger(scope, args = process.argv.slice(2)) {
43
260
  const enabled = isTestMode(args);
44
261
  return (step, detail) => {
@@ -251,13 +468,90 @@ ${JSON.stringify(AIServices, null, 2)}
251
468
  );
252
469
  }
253
470
  function getGitDiffFilter() {
254
- const includeExtensions = ["*.ts", "*.tsx", "*.js", "*.jsx"];
255
- const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);
471
+ const { includePatterns, excludePatterns } = getGitDiffPathspecs();
256
472
  const quote = (pattern) => `"${pattern}"`;
257
- const includeParams = includeExtensions.map(quote).join(" ");
473
+ const includeParams = includePatterns.map(quote).join(" ");
258
474
  const excludeParams = excludePatterns.map(quote).join(" ");
259
475
  return { includeParams, excludeParams };
260
476
  }
477
+ function truncateCommitSubject(subject) {
478
+ if (subject.length <= 72) {
479
+ return subject;
480
+ }
481
+ return `${subject.slice(0, 69)}...`;
482
+ }
483
+ function getRecentCommitOptions() {
484
+ const output = runGitCommand(
485
+ ["log", `-${COMMIT_FETCH_LIMIT}`, "--date=relative", "--pretty=format:%h%x09%an%x09%ar%x09%s"],
486
+ { allowFailure: true }
487
+ );
488
+ if (!output) {
489
+ return [];
490
+ }
491
+ return output.split("\n").map((line) => {
492
+ const [hash = "", author = "", relativeDate = "", ...subjectParts] = line.split(" ");
493
+ const subject = subjectParts.join(" ").trim();
494
+ return {
495
+ author,
496
+ description: `${author} | ${relativeDate}`,
497
+ hash,
498
+ label: `${hash} | ${truncateCommitSubject(subject)}`,
499
+ relativeDate,
500
+ subject
501
+ };
502
+ });
503
+ }
504
+ function buildSelectedCommitSummary(commits) {
505
+ return commits.map((commit) => `- ${commit.hash} | ${commit.subject} | ${commit.author} | ${commit.relativeDate}`).join("\n");
506
+ }
507
+ function getReviewPathspecArgs() {
508
+ const { includePatterns, excludePatterns } = getGitDiffPathspecs();
509
+ return [...includePatterns, ...excludePatterns];
510
+ }
511
+ function buildSelectedCommitDiff(commits) {
512
+ const reviewPathspecArgs = getReviewPathspecArgs();
513
+ const sections = commits.map((commit) => {
514
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", ...reviewPathspecArgs], {
515
+ allowFailure: true,
516
+ trimOutput: false
517
+ }).trim();
518
+ if (!diff) {
519
+ return "";
520
+ }
521
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
522
+ }).filter(Boolean).join("\n\n");
523
+ if (!sections) {
524
+ return "";
525
+ }
526
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", "# \uB9AC\uBDF0 \uB300\uC0C1 diff", sections].join("\n");
527
+ }
528
+ function getSelectedCommitFiles(commits) {
529
+ const reviewPathspecArgs = getReviewPathspecArgs();
530
+ const files = /* @__PURE__ */ new Set();
531
+ commits.forEach((commit) => {
532
+ const output = runGitCommand(["show", "--pretty=format:", "--name-only", commit.hash, "--", ...reviewPathspecArgs], {
533
+ allowFailure: true
534
+ });
535
+ output.split("\n").map((line) => line.trim()).filter(Boolean).forEach((filePath) => files.add(filePath));
536
+ });
537
+ return [...files];
538
+ }
539
+ function buildSelectedFileDiff(commits, filePath) {
540
+ const sections = commits.map((commit) => {
541
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", filePath], {
542
+ allowFailure: true,
543
+ trimOutput: false
544
+ }).trim();
545
+ if (!diff) {
546
+ return "";
547
+ }
548
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
549
+ }).filter(Boolean).join("\n\n");
550
+ if (!sections) {
551
+ return "";
552
+ }
553
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", `# \uD30C\uC77C: ${filePath}`, sections].join("\n\n");
554
+ }
261
555
  function openReport(reportPath) {
262
556
  const resolvedPath = path.resolve(reportPath);
263
557
  const { platform } = process;
@@ -307,49 +601,155 @@ function openReport(reportPath) {
307
601
  helperTrace("open-report:unsupported-platform", platform);
308
602
  console.error(`\u26A0\uFE0F \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD50C\uB7AB\uD3FC\uC785\uB2C8\uB2E4: ${platform}`);
309
603
  }
310
- function getDiffArgs() {
311
- const args = process.argv.slice(2);
312
- const commitIndex = args.indexOf("--commit");
313
- const { includeParams, excludeParams } = getGitDiffFilter();
314
- helperTrace("diff-args:resolve:start", `args=${JSON.stringify(args)}`);
315
- let diffArgs = "";
316
- if (commitIndex !== -1) {
317
- const commitHash = args[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
323
- });
604
+ function ensureInteractiveSelectionAvailable(scope, message) {
605
+ if (process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function") {
606
+ return;
607
+ }
608
+ helperTrace(`${scope}:tty-missing`);
609
+ exitWithError(message, {
610
+ scope: `helper:${scope}`
611
+ });
612
+ }
613
+ function renderSelectionBlock(lines, previousLineCount) {
614
+ if (previousLineCount > 0) {
615
+ readline.moveCursor(process.stdout, 0, -previousLineCount);
616
+ readline.clearScreenDown(process.stdout);
617
+ }
618
+ const fittedLines = fitLinesToTerminal(lines);
619
+ process.stdout.write(`${fittedLines.join("\n")}
620
+ `);
621
+ return fittedLines.length;
622
+ }
623
+ function getSelectionWindowRange(optionCount, selectedIndex, windowSize) {
624
+ if (optionCount <= windowSize) {
625
+ return {
626
+ end: optionCount,
627
+ start: 0
628
+ };
629
+ }
630
+ const halfWindow = Math.floor(windowSize / 2);
631
+ const maxStart = optionCount - windowSize;
632
+ const start = Math.max(0, Math.min(selectedIndex - halfWindow, maxStart));
633
+ return {
634
+ end: Math.min(optionCount, start + windowSize),
635
+ start
636
+ };
637
+ }
638
+ function buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize) {
639
+ const { start, end } = getSelectionWindowRange(options.length, selectedIndex, windowSize);
640
+ const lines = [
641
+ `${ANSI.bold}${question}${ANSI.reset}`,
642
+ `${ANSI.dim}\u2191\u2193 \uC774\uB3D9 | Space \uC120\uD0DD/\uD574\uC81C | Enter \uC644\uB8CC | Esc \uCDE8\uC18C${ANSI.reset}`,
643
+ `${ANSI.dim}\uC120\uD0DD\uB428: ${toggled.size}\uAC1C / \uC804\uCCB4: ${options.length}\uAC1C${ANSI.reset}`
644
+ ];
645
+ for (let index = start; index < end; index += 1) {
646
+ const option = options[index];
647
+ if (!option) {
648
+ continue;
324
649
  }
325
- const nextArg = args[commitIndex + 2];
326
- let n = 0;
327
- if (nextArg && !nextArg.startsWith("--")) {
328
- n = parseInt(nextArg, 10);
329
- if (isNaN(n)) {
330
- n = 0;
331
- }
650
+ const cursor = index === selectedIndex ? `${ANSI.cyan}>${ANSI.reset}` : " ";
651
+ const checked = toggled.has(index) ? `${ANSI.green}\u2611${ANSI.reset}` : "\u2610";
652
+ const description = option.description ? ` ${ANSI.dim}${option.description}${ANSI.reset}` : "";
653
+ lines.push(`${cursor} ${checked} ${option.label}${description}`);
654
+ }
655
+ if (options.length > windowSize) {
656
+ lines.push(`${ANSI.dim}\uD45C\uC2DC \uBC94\uC704: ${start + 1}-${end} / ${options.length}${ANSI.reset}`);
657
+ }
658
+ return lines;
659
+ }
660
+ async function showMultiSelect(question, options, windowSize = COMMIT_SELECTION_WINDOW) {
661
+ ensureInteractiveSelectionAvailable("showMultiSelect", "\u274C \uCEE4\uBC0B \uC120\uD0DD \uBAA8\uB2EC\uC740 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
662
+ let selectedIndex = 0;
663
+ let renderedLineCount = 0;
664
+ const toggled = /* @__PURE__ */ new Set();
665
+ const rl = readline.createInterface({
666
+ input: process.stdin,
667
+ output: process.stdout,
668
+ terminal: true
669
+ });
670
+ process.stdout.write("\x1B[?25l");
671
+ const cleanup = () => {
672
+ if (renderedLineCount > 0) {
673
+ readline.moveCursor(process.stdout, 0, -renderedLineCount);
674
+ readline.clearScreenDown(process.stdout);
675
+ renderedLineCount = 0;
332
676
  }
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}`);
677
+ process.stdin.removeListener("data", onData);
678
+ process.stdin.setRawMode(false);
679
+ process.stdin.pause();
680
+ rl.close();
681
+ process.stdout.write("\x1B[?25h");
682
+ };
683
+ const render = () => {
684
+ const lines = buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize);
685
+ renderedLineCount = renderSelectionBlock(lines, renderedLineCount);
686
+ };
687
+ const confirmSelection = (resolve) => {
688
+ const values = [...toggled].sort((left, right) => left - right).map((index) => options[index]?.value).filter((value) => value !== void 0);
689
+ cleanup();
690
+ resolve(values);
691
+ };
692
+ const cancelSelection = (resolve) => {
693
+ cleanup();
694
+ resolve([]);
695
+ };
696
+ let onData = (_data) => {
697
+ };
698
+ render();
699
+ return new Promise((resolve) => {
700
+ onData = (data) => {
701
+ const key = data.toString();
702
+ if (key === "") {
703
+ cleanup();
704
+ process.exit(0);
346
705
  }
347
- } catch (error) {
348
- helperTrace("diff-args:unstaged-check:failed", getErrorSummary(error));
349
- }
706
+ if (key === "\x1B") {
707
+ cancelSelection(resolve);
708
+ return;
709
+ }
710
+ if (key === "\x1B[A") {
711
+ selectedIndex = (selectedIndex - 1 + options.length) % options.length;
712
+ render();
713
+ return;
714
+ }
715
+ if (key === "\x1B[B") {
716
+ selectedIndex = (selectedIndex + 1) % options.length;
717
+ render();
718
+ return;
719
+ }
720
+ if (key === " ") {
721
+ if (toggled.has(selectedIndex)) {
722
+ toggled.delete(selectedIndex);
723
+ } else {
724
+ toggled.add(selectedIndex);
725
+ }
726
+ render();
727
+ return;
728
+ }
729
+ if (key === "\r" || key === "\n") {
730
+ confirmSelection(resolve);
731
+ }
732
+ };
733
+ process.stdin.setRawMode(true);
734
+ process.stdin.resume();
735
+ process.stdin.on("data", onData);
736
+ });
737
+ }
738
+ async function selectReviewCommits() {
739
+ const commits = getRecentCommitOptions();
740
+ if (commits.length === 0) {
741
+ console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uCD5C\uADFC \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
742
+ return [];
350
743
  }
351
- helperTrace("diff-args:resolve:done", diffArgs || "(default)");
352
- return diffArgs;
744
+ return showMultiSelect(
745
+ "\uB9AC\uBDF0\uD560 \uCEE4\uBC0B\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.",
746
+ commits.map((commit) => ({
747
+ description: commit.description,
748
+ label: commit.label,
749
+ value: commit
750
+ })),
751
+ COMMIT_SELECTION_WINDOW
752
+ );
353
753
  }
354
754
  function selectAIService() {
355
755
  const service = parseServiceFromArgs();
@@ -367,10 +767,11 @@ async function showSelectionAIService() {
367
767
  if (selectedServiceFromArgs) {
368
768
  helperTrace("show-selection:from-args", selectedServiceFromArgs);
369
769
  console.log(`
370
- \u2705 \x1B[32m${selectedServiceFromArgs}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
770
+ \u2705 ${ANSI.green}${selectedServiceFromArgs}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
371
771
  `);
372
772
  return selectedServiceFromArgs;
373
773
  }
774
+ 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.");
374
775
  helperTrace("show-selection:interactive:start");
375
776
  let selectedIndex = 0;
376
777
  const rl = readline.createInterface({
@@ -388,11 +789,12 @@ async function showSelectionAIService() {
388
789
  helperTrace("show-selection:interactive:render", AIServices[selectedIndex] || "unknown");
389
790
  readline.clearScreenDown(process.stdout);
390
791
  process.stdout.write(
391
- "\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"
792
+ `\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):
793
+ `
392
794
  );
393
795
  AIServices.forEach((service, index) => {
394
796
  if (index === selectedIndex) {
395
- process.stdout.write(` \x1B[36m>\x1B[0m \x1B[36m\u25C9\x1B[0m \x1B[1m${service}\x1B[0m
797
+ process.stdout.write(` ${ANSI.cyan}>${ANSI.reset} ${ANSI.cyan}\u25C9${ANSI.reset} ${ANSI.bold}${service}${ANSI.reset}
396
798
  `);
397
799
  } else {
398
800
  process.stdout.write(` \u25EF ${service}
@@ -422,7 +824,7 @@ async function showSelectionAIService() {
422
824
  rl.close();
423
825
  process.stdout.write("\x1B[?25h");
424
826
  console.log(`
425
- \u2705 \x1B[32m${AIServices[selectedIndex]}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
827
+ \u2705 ${ANSI.green}${AIServices[selectedIndex]}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
426
828
  `);
427
829
  const result = AIServices[selectedIndex];
428
830
  if (result) {
@@ -437,6 +839,6 @@ async function showSelectionAIService() {
437
839
  });
438
840
  }
439
841
 
440
- export { AIServices, REPORT_DIR, clearTraceMessages, codingConventionRulesPath, createReportDirectory, createTraceLogger, deleteFile, deleteTempDiff, exitWithError, getAvailableFilePath, getDiffArgs, getErrorLogTimestamp, getErrorSummary, getGitDiffFilter, getNextFilePath, getNowString, getTraceMessages, ignoreList, isTestMode, namingRulesPath, openReport, reviewFormOneByOnePath, reviewFormPath, rulesPath, selectAIService, showSelectionAIService, tempDiffPath, writeErrorReport };
842
+ export { AIServices, COMMIT_FETCH_LIMIT, COMMIT_SELECTION_WINDOW, REPORT_DIR, buildSelectedCommitDiff, buildSelectedCommitSummary, buildSelectedFileDiff, clearTraceMessages, codingConventionRulesPath, createReportDirectory, createTraceLogger, deleteFile, deleteTempDiff, executeShellCommandWithProgress, exitWithError, formatReviewTargetFiles, getAvailableFilePath, getErrorLogTimestamp, getErrorSummary, getGitDiffFilter, getNextFilePath, getNowString, getRecentCommitOptions, getSelectedCommitFiles, getTraceMessages, ignoreList, isTestMode, namingRulesPath, openReport, reviewFormOneByOnePath, reviewFormPath, rulesPath, selectAIService, selectReviewCommits, showMultiSelect, showSelectionAIService, tempDiffPath, writeErrorReport };
441
843
  //# sourceMappingURL=helper.js.map
442
844
  //# sourceMappingURL=helper.js.map