sales-frontend-gemini-cli 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/common/helper.cjs +581 -53
  2. package/dist/common/helper.cjs.map +1 -1
  3. package/dist/common/helper.d.cts +114 -3
  4. package/dist/common/helper.d.ts +114 -3
  5. package/dist/common/helper.js +570 -54
  6. package/dist/common/helper.js.map +1 -1
  7. package/dist/common/types.d.cts +24 -1
  8. package/dist/common/types.d.ts +24 -1
  9. package/dist/pr-review/claude/claude-commander.cjs +60 -12
  10. package/dist/pr-review/claude/claude-commander.cjs.map +1 -1
  11. package/dist/pr-review/claude/claude-commander.js +60 -12
  12. package/dist/pr-review/claude/claude-commander.js.map +1 -1
  13. package/dist/pr-review/claude/installation-claude.cjs +42 -6
  14. package/dist/pr-review/claude/installation-claude.cjs.map +1 -1
  15. package/dist/pr-review/claude/installation-claude.js +42 -6
  16. package/dist/pr-review/claude/installation-claude.js.map +1 -1
  17. package/dist/pr-review/codex/codex-commander.cjs +63 -12
  18. package/dist/pr-review/codex/codex-commander.cjs.map +1 -1
  19. package/dist/pr-review/codex/codex-commander.d.cts +1 -1
  20. package/dist/pr-review/codex/codex-commander.d.ts +1 -1
  21. package/dist/pr-review/codex/codex-commander.js +63 -12
  22. package/dist/pr-review/codex/codex-commander.js.map +1 -1
  23. package/dist/pr-review/codex/installation-codex.cjs +42 -6
  24. package/dist/pr-review/codex/installation-codex.cjs.map +1 -1
  25. package/dist/pr-review/codex/installation-codex.js +42 -6
  26. package/dist/pr-review/codex/installation-codex.js.map +1 -1
  27. package/dist/pr-review/gemini/gemini-commander.cjs +76 -16
  28. package/dist/pr-review/gemini/gemini-commander.cjs.map +1 -1
  29. package/dist/pr-review/gemini/gemini-commander.js +76 -16
  30. package/dist/pr-review/gemini/gemini-commander.js.map +1 -1
  31. package/dist/pr-review/gemini/installation-gemini.cjs +42 -6
  32. package/dist/pr-review/gemini/installation-gemini.cjs.map +1 -1
  33. package/dist/pr-review/gemini/installation-gemini.js +42 -6
  34. package/dist/pr-review/gemini/installation-gemini.js.map +1 -1
  35. package/dist/pr-review/review-one-by-one.cjs +699 -106
  36. package/dist/pr-review/review-one-by-one.cjs.map +1 -1
  37. package/dist/pr-review/review-one-by-one.js +700 -107
  38. package/dist/pr-review/review-one-by-one.js.map +1 -1
  39. package/dist/pr-review/review.cjs +722 -105
  40. package/dist/pr-review/review.cjs.map +1 -1
  41. package/dist/pr-review/review.js +722 -105
  42. package/dist/pr-review/review.js.map +1 -1
  43. package/package.json +4 -7
  44. package/src/common/rules/coding-convention.md +393 -0
  45. package/src/common/rules/coding-convention.pdf +0 -0
  46. package/src/common/rules/naming-rule.md +347 -0
  47. package/src/common/rules/naming-rule.pdf +0 -0
@@ -1,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",
@@ -33,12 +71,224 @@ var ignoreList = [
33
71
  function isTestMode(args = process.argv.slice(2)) {
34
72
  return args.includes("--test");
35
73
  }
74
+ function shouldStreamAIOutput(args = process.argv.slice(2)) {
75
+ return args.includes("--stream-output");
76
+ }
36
77
  function clearTraceMessages() {
37
78
  traceMessages.length = 0;
38
79
  }
39
80
  function getTraceMessages() {
40
81
  return [...traceMessages];
41
82
  }
83
+ var ANSI = {
84
+ bold: "\x1B[1m",
85
+ cyan: "\x1B[36m",
86
+ dim: "\x1B[2m",
87
+ green: "\x1B[32m",
88
+ reset: "\x1B[0m",
89
+ yellow: "\x1B[33m"
90
+ };
91
+ var ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
92
+ var COMBINING_MARK_PATTERN = /\p{Mark}/u;
93
+ var GRAPHEME_SEGMENTER = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter("ko", { granularity: "grapheme" }) : null;
94
+ function getGitDiffPathspecs() {
95
+ return {
96
+ excludePatterns: ignoreList.map((item) => `:(exclude)${item}`),
97
+ includePatterns: ["*.ts", "*.tsx", "*.js", "*.jsx"]
98
+ };
99
+ }
100
+ function segmentGraphemes(value) {
101
+ if (!GRAPHEME_SEGMENTER) {
102
+ return [...value];
103
+ }
104
+ return [...GRAPHEME_SEGMENTER.segment(value)].map(({ segment }) => segment);
105
+ }
106
+ function isWideCodePoint(codePoint) {
107
+ return codePoint >= 4352 && (codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 12871 && codePoint !== 12351 || codePoint >= 12880 && codePoint <= 19903 || codePoint >= 19968 && codePoint <= 42182 || codePoint >= 43360 && codePoint <= 43388 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65131 || codePoint >= 65281 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 127488 && codePoint <= 127569 || codePoint >= 131072 && codePoint <= 262141);
108
+ }
109
+ function isEmojiCodePoint(codePoint) {
110
+ return codePoint >= 127462 && codePoint <= 127487 || codePoint >= 127744 && codePoint <= 129791 || codePoint >= 9728 && codePoint <= 10175;
111
+ }
112
+ function getGraphemeWidth(grapheme) {
113
+ let width = 0;
114
+ for (const character of grapheme) {
115
+ const codePoint = character.codePointAt(0);
116
+ if (!codePoint || COMBINING_MARK_PATTERN.test(character) || codePoint === 8205) {
117
+ continue;
118
+ }
119
+ if (codePoint >= 65024 && codePoint <= 65039 || codePoint >= 917760 && codePoint <= 917999) {
120
+ continue;
121
+ }
122
+ if (isWideCodePoint(codePoint) || isEmojiCodePoint(codePoint)) {
123
+ width = Math.max(width, 2);
124
+ continue;
125
+ }
126
+ width = Math.max(width, 1);
127
+ }
128
+ return width;
129
+ }
130
+ function tokenizePlainText(value) {
131
+ return segmentGraphemes(value).map((segment) => ({
132
+ value: segment,
133
+ visibleWidth: getGraphemeWidth(segment)
134
+ }));
135
+ }
136
+ function tokenizeVisibleText(value) {
137
+ const tokens = [];
138
+ let lastIndex = 0;
139
+ for (const match of value.matchAll(ANSI_PATTERN)) {
140
+ const index = match.index ?? 0;
141
+ if (index > lastIndex) {
142
+ tokens.push(...tokenizePlainText(value.slice(lastIndex, index)));
143
+ }
144
+ tokens.push({
145
+ value: match[0],
146
+ visibleWidth: 0
147
+ });
148
+ lastIndex = index + match[0].length;
149
+ }
150
+ if (lastIndex < value.length) {
151
+ tokens.push(...tokenizePlainText(value.slice(lastIndex)));
152
+ }
153
+ return tokens;
154
+ }
155
+ function truncateLineForTerminal(value, maxWidth) {
156
+ if (maxWidth <= 0) {
157
+ return "";
158
+ }
159
+ const tokens = tokenizeVisibleText(value);
160
+ const totalWidth = tokens.reduce((sum, token) => sum + token.visibleWidth, 0);
161
+ if (totalWidth <= maxWidth) {
162
+ return value;
163
+ }
164
+ const ellipsis = "...";
165
+ const ellipsisWidth = 3;
166
+ const targetWidth = Math.max(0, maxWidth - ellipsisWidth);
167
+ let usedWidth = 0;
168
+ let result = "";
169
+ for (const token of tokens) {
170
+ if (token.visibleWidth === 0) {
171
+ result += token.value;
172
+ continue;
173
+ }
174
+ if (usedWidth + token.visibleWidth > targetWidth) {
175
+ break;
176
+ }
177
+ result += token.value;
178
+ usedWidth += token.visibleWidth;
179
+ }
180
+ return `${result}${ellipsis}${ANSI.reset}`;
181
+ }
182
+ function fitLinesToTerminal(lines) {
183
+ const maxWidth = Math.max(20, (process.stdout.columns || 120) - 1);
184
+ return lines.map((line) => truncateLineForTerminal(line, maxWidth));
185
+ }
186
+ function runGitCommand(args, options = {}) {
187
+ const { allowFailure = false, trimOutput = true } = options;
188
+ try {
189
+ const output = execFileSync("git", args, {
190
+ encoding: "utf8",
191
+ maxBuffer: 1024 * 1024 * 20,
192
+ stdio: ["ignore", "pipe", "pipe"]
193
+ });
194
+ return trimOutput ? output.trim() : output;
195
+ } catch (error) {
196
+ helperTrace("git-command:failed", `${args.join(" ")} | ${getErrorSummary(error)}`);
197
+ if (allowFailure) {
198
+ return "";
199
+ }
200
+ throw error;
201
+ }
202
+ }
203
+ async function executeShellCommandWithProgress(command, options = {}) {
204
+ const { progressIntervalMs = 1e4, progressMessage = "\u23F3 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...", streamOutput = false } = options;
205
+ const { spawn } = await import('child_process');
206
+ return new Promise((resolve, reject) => {
207
+ let stdout = "";
208
+ let stderr = "";
209
+ const startedAt = Date.now();
210
+ console.log(progressMessage);
211
+ const child = spawn("/bin/zsh", ["-lc", command], {
212
+ stdio: ["ignore", "pipe", "pipe"]
213
+ });
214
+ const progressTimer = setInterval(() => {
215
+ const elapsedSeconds = Math.max(1, Math.floor((Date.now() - startedAt) / 1e3));
216
+ console.log(`${progressMessage} (${elapsedSeconds}s \uACBD\uACFC)`);
217
+ }, progressIntervalMs);
218
+ child.stdout.on("data", (chunk) => {
219
+ const text = chunk.toString();
220
+ stdout += text;
221
+ if (streamOutput) {
222
+ process.stdout.write(text);
223
+ }
224
+ });
225
+ child.stderr.on("data", (chunk) => {
226
+ const text = chunk.toString();
227
+ stderr += text;
228
+ if (streamOutput) {
229
+ process.stderr.write(text);
230
+ }
231
+ });
232
+ child.on("error", (error) => {
233
+ clearInterval(progressTimer);
234
+ reject(error);
235
+ });
236
+ child.on("close", (code, signal) => {
237
+ clearInterval(progressTimer);
238
+ if (code === 0) {
239
+ resolve({
240
+ stderr,
241
+ stdout
242
+ });
243
+ return;
244
+ }
245
+ const exitSummary = signal ? `signal=${signal}` : `code=${String(code ?? "unknown")}`;
246
+ const failureDetails = {
247
+ code,
248
+ command,
249
+ signal,
250
+ stderr,
251
+ stdout
252
+ };
253
+ reject(createShellCommandExecutionError(failureDetails, exitSummary));
254
+ });
255
+ });
256
+ }
257
+ function getShellCommandFailurePreview(failureDetails) {
258
+ const stderrText = failureDetails.stderr.trim();
259
+ const stdoutText = failureDetails.stdout.trim();
260
+ const combinedOutput = stderrText || stdoutText;
261
+ if (!combinedOutput) {
262
+ return "";
263
+ }
264
+ const MAX_PREVIEW_LENGTH = 4e3;
265
+ if (combinedOutput.length <= MAX_PREVIEW_LENGTH) {
266
+ return combinedOutput;
267
+ }
268
+ return combinedOutput.slice(-4e3);
269
+ }
270
+ function createShellCommandExecutionError(failureDetails, exitSummary) {
271
+ const failurePreview = getShellCommandFailurePreview(failureDetails);
272
+ const error = new Error(`\uC258 \uBA85\uB839 \uC2E4\uD589 \uC2E4\uD328 (${exitSummary})${failurePreview ? `
273
+ ${failurePreview}` : ""}`);
274
+ error.code = failureDetails.code;
275
+ error.signal = failureDetails.signal;
276
+ error.stdout = failureDetails.stdout;
277
+ error.stderr = failureDetails.stderr;
278
+ error.command = failureDetails.command;
279
+ return error;
280
+ }
281
+ function formatReviewTargetFiles(files, visibleCount = 5) {
282
+ if (files.length === 0) {
283
+ return "(\uC5C6\uC74C)";
284
+ }
285
+ const visibleFiles = files.slice(0, visibleCount);
286
+ const hiddenCount = Math.max(0, files.length - visibleFiles.length);
287
+ if (hiddenCount === 0) {
288
+ return visibleFiles.join(", ");
289
+ }
290
+ return `${visibleFiles.join(", ")} \uC678 ${hiddenCount}\uAC1C`;
291
+ }
42
292
  function createTraceLogger(scope, args = process.argv.slice(2)) {
43
293
  const enabled = isTestMode(args);
44
294
  return (step, detail) => {
@@ -100,7 +350,7 @@ function serializeError(error) {
100
350
  }
101
351
  if (error && typeof error === "object") {
102
352
  const errorLike = error;
103
- const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs"];
353
+ const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs", "command"];
104
354
  extraKeys.forEach((key) => {
105
355
  if (errorLike[key] !== void 0) {
106
356
  serialized[key] = errorLike[key];
@@ -206,6 +456,87 @@ ${section.markdown}`).join("\n")}
206
456
  return "";
207
457
  }
208
458
  }
459
+ function getExecutionLogSummary(status, title) {
460
+ if (title) {
461
+ return title;
462
+ }
463
+ switch (status) {
464
+ case "success":
465
+ return "\uB9AC\uBDF0 \uC2E4\uD589\uC774 \uC131\uACF5\uC801\uC73C\uB85C \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
466
+ case "failed":
467
+ return "\uB9AC\uBDF0 \uC2E4\uD589 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
468
+ case "partial_failure":
469
+ return "\uB9AC\uBDF0 \uC2E4\uD589\uC740 \uC644\uB8CC\uB418\uC5C8\uC9C0\uB9CC \uC77C\uBD80 \uB2E8\uACC4\uC5D0\uC11C \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
470
+ default:
471
+ return "\uB9AC\uBDF0 \uC2E4\uD589\uC774 \uCDE8\uC18C\uB418\uC5C8\uAC70\uB098 \uB9AC\uBDF0 \uB300\uC0C1\uC774 \uC5C6\uC5B4 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
472
+ }
473
+ }
474
+ function formatExecutionDuration(startedAt, finishedAt) {
475
+ const durationMs = Math.max(0, finishedAt.getTime() - startedAt.getTime());
476
+ if (durationMs < 1e3) {
477
+ return `${durationMs}ms`;
478
+ }
479
+ const durationSeconds = durationMs / 1e3;
480
+ if (durationSeconds < 60) {
481
+ return `${durationSeconds.toFixed(1)}s`;
482
+ }
483
+ const minutes = Math.floor(durationSeconds / 60);
484
+ const seconds = Math.round(durationSeconds % 60);
485
+ return `${minutes}m ${seconds}s`;
486
+ }
487
+ function writeExecutionLog(options = {}) {
488
+ try {
489
+ const startedAt = options.startedAt ?? /* @__PURE__ */ new Date();
490
+ const finishedAt = options.finishedAt ?? /* @__PURE__ */ new Date();
491
+ const status = options.status ?? "success";
492
+ helperTrace("execution-log:write:start", options.scope || "unknown");
493
+ createReportDirectory();
494
+ const reportPath = getAvailableFilePath(REPORT_DIR, `${getNowString(finishedAt)}-execution-log`, ".md");
495
+ const traceSnapshot = options.traceMessages ?? getTraceMessages();
496
+ const extraSections = options.extraSections || [];
497
+ const serializedError = options.error ? serializeError(options.error) : null;
498
+ const report = `# Execution Log
499
+
500
+ - \uC2DC\uC791 \uC2DC\uAC01: ${getHumanReadableNowString(startedAt)}
501
+ - \uC885\uB8CC \uC2DC\uAC01: ${getHumanReadableNowString(finishedAt)}
502
+ - \uC2E4\uD589 \uC2DC\uAC04: ${formatExecutionDuration(startedAt, finishedAt)}
503
+ - \uC0C1\uD0DC: \`${status}\`
504
+ - Scope: \`${options.scope || "unknown"}\`
505
+ - \uC791\uC5C5 \uACBD\uB85C: \`${process.cwd()}\`
506
+ - \uC2E4\uD589 \uC778\uC790: \`${JSON.stringify(options.args ?? process.argv.slice(2))}\`
507
+ - \uC2E4\uD589 \uD658\uACBD: \`${process.platform} ${process.arch} / Node ${process.version}\`
508
+
509
+ ## Summary
510
+
511
+ ${getExecutionLogSummary(status, options.title)}
512
+ ${serializedError ? `
513
+
514
+ ## Error
515
+
516
+ \`\`\`json
517
+ ${JSON.stringify(serializedError, null, 2)}
518
+ \`\`\`` : ""}
519
+
520
+ ## Trace
521
+
522
+ \`\`\`json
523
+ ${JSON.stringify(traceSnapshot, null, 2)}
524
+ \`\`\`${extraSections.length ? `
525
+ ${extraSections.map((section) => `
526
+ ## ${section.heading}
527
+
528
+ ${section.markdown}`).join("\n")}
529
+ ` : "\n"}
530
+ `;
531
+ fs.writeFileSync(reportPath, report);
532
+ helperTrace("execution-log:write:done", reportPath);
533
+ return reportPath;
534
+ } catch (writeError) {
535
+ console.error("\u26A0\uFE0F \uC2E4\uD589 \uB85C\uADF8 \uD30C\uC77C \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
536
+ console.error(writeError);
537
+ return "";
538
+ }
539
+ }
209
540
  function exitWithError(message, options = {}) {
210
541
  const reportPath = writeErrorReport(options.error || new Error(message), {
211
542
  ...options,
@@ -251,13 +582,90 @@ ${JSON.stringify(AIServices, null, 2)}
251
582
  );
252
583
  }
253
584
  function getGitDiffFilter() {
254
- const includeExtensions = ["*.ts", "*.tsx", "*.js", "*.jsx"];
255
- const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);
585
+ const { includePatterns, excludePatterns } = getGitDiffPathspecs();
256
586
  const quote = (pattern) => `"${pattern}"`;
257
- const includeParams = includeExtensions.map(quote).join(" ");
587
+ const includeParams = includePatterns.map(quote).join(" ");
258
588
  const excludeParams = excludePatterns.map(quote).join(" ");
259
589
  return { includeParams, excludeParams };
260
590
  }
591
+ function truncateCommitSubject(subject) {
592
+ if (subject.length <= 72) {
593
+ return subject;
594
+ }
595
+ return `${subject.slice(0, 69)}...`;
596
+ }
597
+ function getRecentCommitOptions() {
598
+ const output = runGitCommand(
599
+ ["log", `-${COMMIT_FETCH_LIMIT}`, "--date=relative", "--pretty=format:%h%x09%an%x09%ar%x09%s"],
600
+ { allowFailure: true }
601
+ );
602
+ if (!output) {
603
+ return [];
604
+ }
605
+ return output.split("\n").map((line) => {
606
+ const [hash = "", author = "", relativeDate = "", ...subjectParts] = line.split(" ");
607
+ const subject = subjectParts.join(" ").trim();
608
+ return {
609
+ author,
610
+ description: `${author} | ${relativeDate}`,
611
+ hash,
612
+ label: `${hash} | ${truncateCommitSubject(subject)}`,
613
+ relativeDate,
614
+ subject
615
+ };
616
+ });
617
+ }
618
+ function buildSelectedCommitSummary(commits) {
619
+ return commits.map((commit) => `- ${commit.hash} | ${commit.subject} | ${commit.author} | ${commit.relativeDate}`).join("\n");
620
+ }
621
+ function getReviewPathspecArgs() {
622
+ const { includePatterns, excludePatterns } = getGitDiffPathspecs();
623
+ return [...includePatterns, ...excludePatterns];
624
+ }
625
+ function buildSelectedCommitDiff(commits) {
626
+ const reviewPathspecArgs = getReviewPathspecArgs();
627
+ const sections = commits.map((commit) => {
628
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", ...reviewPathspecArgs], {
629
+ allowFailure: true,
630
+ trimOutput: false
631
+ }).trim();
632
+ if (!diff) {
633
+ return "";
634
+ }
635
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
636
+ }).filter(Boolean).join("\n\n");
637
+ if (!sections) {
638
+ return "";
639
+ }
640
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", "# \uB9AC\uBDF0 \uB300\uC0C1 diff", sections].join("\n");
641
+ }
642
+ function getSelectedCommitFiles(commits) {
643
+ const reviewPathspecArgs = getReviewPathspecArgs();
644
+ const files = /* @__PURE__ */ new Set();
645
+ commits.forEach((commit) => {
646
+ const output = runGitCommand(["show", "--pretty=format:", "--name-only", commit.hash, "--", ...reviewPathspecArgs], {
647
+ allowFailure: true
648
+ });
649
+ output.split("\n").map((line) => line.trim()).filter(Boolean).forEach((filePath) => files.add(filePath));
650
+ });
651
+ return [...files];
652
+ }
653
+ function buildSelectedFileDiff(commits, filePath) {
654
+ const sections = commits.map((commit) => {
655
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", filePath], {
656
+ allowFailure: true,
657
+ trimOutput: false
658
+ }).trim();
659
+ if (!diff) {
660
+ return "";
661
+ }
662
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
663
+ }).filter(Boolean).join("\n\n");
664
+ if (!sections) {
665
+ return "";
666
+ }
667
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", `# \uD30C\uC77C: ${filePath}`, sections].join("\n\n");
668
+ }
261
669
  function openReport(reportPath) {
262
670
  const resolvedPath = path.resolve(reportPath);
263
671
  const { platform } = process;
@@ -307,49 +715,155 @@ function openReport(reportPath) {
307
715
  helperTrace("open-report:unsupported-platform", platform);
308
716
  console.error(`\u26A0\uFE0F \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD50C\uB7AB\uD3FC\uC785\uB2C8\uB2E4: ${platform}`);
309
717
  }
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
- });
718
+ function ensureInteractiveSelectionAvailable(scope, message) {
719
+ if (process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function") {
720
+ return;
721
+ }
722
+ helperTrace(`${scope}:tty-missing`);
723
+ exitWithError(message, {
724
+ scope: `helper:${scope}`
725
+ });
726
+ }
727
+ function renderSelectionBlock(lines, previousLineCount) {
728
+ if (previousLineCount > 0) {
729
+ readline.moveCursor(process.stdout, 0, -previousLineCount);
730
+ readline.clearScreenDown(process.stdout);
731
+ }
732
+ const fittedLines = fitLinesToTerminal(lines);
733
+ process.stdout.write(`${fittedLines.join("\n")}
734
+ `);
735
+ return fittedLines.length;
736
+ }
737
+ function getSelectionWindowRange(optionCount, selectedIndex, windowSize) {
738
+ if (optionCount <= windowSize) {
739
+ return {
740
+ end: optionCount,
741
+ start: 0
742
+ };
743
+ }
744
+ const halfWindow = Math.floor(windowSize / 2);
745
+ const maxStart = optionCount - windowSize;
746
+ const start = Math.max(0, Math.min(selectedIndex - halfWindow, maxStart));
747
+ return {
748
+ end: Math.min(optionCount, start + windowSize),
749
+ start
750
+ };
751
+ }
752
+ function buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize) {
753
+ const { start, end } = getSelectionWindowRange(options.length, selectedIndex, windowSize);
754
+ const lines = [
755
+ `${ANSI.bold}${question}${ANSI.reset}`,
756
+ `${ANSI.dim}\u2191\u2193 \uC774\uB3D9 | Space \uC120\uD0DD/\uD574\uC81C | Enter \uC644\uB8CC | Esc \uCDE8\uC18C${ANSI.reset}`,
757
+ `${ANSI.dim}\uC120\uD0DD\uB428: ${toggled.size}\uAC1C / \uC804\uCCB4: ${options.length}\uAC1C${ANSI.reset}`
758
+ ];
759
+ for (let index = start; index < end; index += 1) {
760
+ const option = options[index];
761
+ if (!option) {
762
+ continue;
324
763
  }
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
- }
764
+ const cursor = index === selectedIndex ? `${ANSI.cyan}>${ANSI.reset}` : " ";
765
+ const checked = toggled.has(index) ? `${ANSI.green}\u2611${ANSI.reset}` : "\u2610";
766
+ const description = option.description ? ` ${ANSI.dim}${option.description}${ANSI.reset}` : "";
767
+ lines.push(`${cursor} ${checked} ${option.label}${description}`);
768
+ }
769
+ if (options.length > windowSize) {
770
+ lines.push(`${ANSI.dim}\uD45C\uC2DC \uBC94\uC704: ${start + 1}-${end} / ${options.length}${ANSI.reset}`);
771
+ }
772
+ return lines;
773
+ }
774
+ async function showMultiSelect(question, options, windowSize = COMMIT_SELECTION_WINDOW) {
775
+ ensureInteractiveSelectionAvailable("showMultiSelect", "\u274C \uCEE4\uBC0B \uC120\uD0DD \uBAA8\uB2EC\uC740 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
776
+ let selectedIndex = 0;
777
+ let renderedLineCount = 0;
778
+ const toggled = /* @__PURE__ */ new Set();
779
+ const rl = readline.createInterface({
780
+ input: process.stdin,
781
+ output: process.stdout,
782
+ terminal: true
783
+ });
784
+ process.stdout.write("\x1B[?25l");
785
+ const cleanup = () => {
786
+ if (renderedLineCount > 0) {
787
+ readline.moveCursor(process.stdout, 0, -renderedLineCount);
788
+ readline.clearScreenDown(process.stdout);
789
+ renderedLineCount = 0;
332
790
  }
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}`);
791
+ process.stdin.removeListener("data", onData);
792
+ process.stdin.setRawMode(false);
793
+ process.stdin.pause();
794
+ rl.close();
795
+ process.stdout.write("\x1B[?25h");
796
+ };
797
+ const render = () => {
798
+ const lines = buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize);
799
+ renderedLineCount = renderSelectionBlock(lines, renderedLineCount);
800
+ };
801
+ const confirmSelection = (resolve) => {
802
+ const values = [...toggled].sort((left, right) => left - right).map((index) => options[index]?.value).filter((value) => value !== void 0);
803
+ cleanup();
804
+ resolve(values);
805
+ };
806
+ const cancelSelection = (resolve) => {
807
+ cleanup();
808
+ resolve([]);
809
+ };
810
+ let onData = (_data) => {
811
+ };
812
+ render();
813
+ return new Promise((resolve) => {
814
+ onData = (data) => {
815
+ const key = data.toString();
816
+ if (key === "") {
817
+ cleanup();
818
+ process.exit(0);
346
819
  }
347
- } catch (error) {
348
- helperTrace("diff-args:unstaged-check:failed", getErrorSummary(error));
349
- }
820
+ if (key === "\x1B") {
821
+ cancelSelection(resolve);
822
+ return;
823
+ }
824
+ if (key === "\x1B[A") {
825
+ selectedIndex = (selectedIndex - 1 + options.length) % options.length;
826
+ render();
827
+ return;
828
+ }
829
+ if (key === "\x1B[B") {
830
+ selectedIndex = (selectedIndex + 1) % options.length;
831
+ render();
832
+ return;
833
+ }
834
+ if (key === " ") {
835
+ if (toggled.has(selectedIndex)) {
836
+ toggled.delete(selectedIndex);
837
+ } else {
838
+ toggled.add(selectedIndex);
839
+ }
840
+ render();
841
+ return;
842
+ }
843
+ if (key === "\r" || key === "\n") {
844
+ confirmSelection(resolve);
845
+ }
846
+ };
847
+ process.stdin.setRawMode(true);
848
+ process.stdin.resume();
849
+ process.stdin.on("data", onData);
850
+ });
851
+ }
852
+ async function selectReviewCommits() {
853
+ const commits = getRecentCommitOptions();
854
+ if (commits.length === 0) {
855
+ console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uCD5C\uADFC \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
856
+ return [];
350
857
  }
351
- helperTrace("diff-args:resolve:done", diffArgs || "(default)");
352
- return diffArgs;
858
+ return showMultiSelect(
859
+ "\uB9AC\uBDF0\uD560 \uCEE4\uBC0B\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.",
860
+ commits.map((commit) => ({
861
+ description: commit.description,
862
+ label: commit.label,
863
+ value: commit
864
+ })),
865
+ COMMIT_SELECTION_WINDOW
866
+ );
353
867
  }
354
868
  function selectAIService() {
355
869
  const service = parseServiceFromArgs();
@@ -367,10 +881,11 @@ async function showSelectionAIService() {
367
881
  if (selectedServiceFromArgs) {
368
882
  helperTrace("show-selection:from-args", selectedServiceFromArgs);
369
883
  console.log(`
370
- \u2705 \x1B[32m${selectedServiceFromArgs}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
884
+ \u2705 ${ANSI.green}${selectedServiceFromArgs}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
371
885
  `);
372
886
  return selectedServiceFromArgs;
373
887
  }
888
+ 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
889
  helperTrace("show-selection:interactive:start");
375
890
  let selectedIndex = 0;
376
891
  const rl = readline.createInterface({
@@ -388,11 +903,12 @@ async function showSelectionAIService() {
388
903
  helperTrace("show-selection:interactive:render", AIServices[selectedIndex] || "unknown");
389
904
  readline.clearScreenDown(process.stdout);
390
905
  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"
906
+ `\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):
907
+ `
392
908
  );
393
909
  AIServices.forEach((service, index) => {
394
910
  if (index === selectedIndex) {
395
- process.stdout.write(` \x1B[36m>\x1B[0m \x1B[36m\u25C9\x1B[0m \x1B[1m${service}\x1B[0m
911
+ process.stdout.write(` ${ANSI.cyan}>${ANSI.reset} ${ANSI.cyan}\u25C9${ANSI.reset} ${ANSI.bold}${service}${ANSI.reset}
396
912
  `);
397
913
  } else {
398
914
  process.stdout.write(` \u25EF ${service}
@@ -422,7 +938,7 @@ async function showSelectionAIService() {
422
938
  rl.close();
423
939
  process.stdout.write("\x1B[?25h");
424
940
  console.log(`
425
- \u2705 \x1B[32m${AIServices[selectedIndex]}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
941
+ \u2705 ${ANSI.green}${AIServices[selectedIndex]}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
426
942
  `);
427
943
  const result = AIServices[selectedIndex];
428
944
  if (result) {
@@ -437,6 +953,6 @@ async function showSelectionAIService() {
437
953
  });
438
954
  }
439
955
 
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 };
956
+ 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, shouldStreamAIOutput, showMultiSelect, showSelectionAIService, tempDiffPath, writeErrorReport, writeExecutionLog };
441
957
  //# sourceMappingURL=helper.js.map
442
958
  //# sourceMappingURL=helper.js.map