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,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { exec, execSync } from 'child_process';
2
+ import { exec, execSync, execFileSync } from 'child_process';
3
3
  import fs from 'fs';
4
4
  import util, { inspect } from 'util';
5
5
  import path from 'path';
@@ -8,14 +8,52 @@ import { fileURLToPath } from 'url';
8
8
 
9
9
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
10
10
  var traceMessages = [];
11
- var rulesPath = path.resolve(__dirname, "../../src/common/rules/review-rules.md");
12
- var namingRulesPath = path.resolve(__dirname, "../../src/common/rules/naming-rule.md");
13
- var codingConventionRulesPath = path.resolve(__dirname, "../../src/common/rules/coding-convention.md");
14
- path.resolve(__dirname, "../../src/common/form/review-form.md");
15
- var reviewFormOneByOnePath = path.resolve(__dirname, "../../src/common/form/review-form-one-by-one.md");
11
+ var GEMINI_CLI_PACKAGE_NAME = "sales-frontend-gemini-cli";
12
+ var cachedPackageRootPath = "";
13
+ function isGeminiCliPackageRoot(directory) {
14
+ const packageJsonPath = path.join(directory, "package.json");
15
+ if (!fs.existsSync(packageJsonPath)) {
16
+ return false;
17
+ }
18
+ try {
19
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
20
+ return packageJson.name === GEMINI_CLI_PACKAGE_NAME;
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+ function resolveGeminiCliPackageRoot(startDirectory = __dirname) {
26
+ if (cachedPackageRootPath) {
27
+ return cachedPackageRootPath;
28
+ }
29
+ let currentDirectory = startDirectory;
30
+ while (true) {
31
+ if (isGeminiCliPackageRoot(currentDirectory)) {
32
+ cachedPackageRootPath = currentDirectory;
33
+ return cachedPackageRootPath;
34
+ }
35
+ const parentDirectory = path.dirname(currentDirectory);
36
+ if (parentDirectory === currentDirectory) {
37
+ break;
38
+ }
39
+ currentDirectory = parentDirectory;
40
+ }
41
+ cachedPackageRootPath = path.resolve(startDirectory, "../..");
42
+ return cachedPackageRootPath;
43
+ }
44
+ function resolvePackageAssetPath(relativeFilePath) {
45
+ return path.resolve(resolveGeminiCliPackageRoot(), relativeFilePath);
46
+ }
47
+ var rulesPath = resolvePackageAssetPath("src/common/rules/review-rules.md");
48
+ var namingRulesPath = resolvePackageAssetPath("src/common/rules/naming-rule.md");
49
+ var codingConventionRulesPath = resolvePackageAssetPath("src/common/rules/coding-convention.md");
50
+ resolvePackageAssetPath("src/common/form/review-form.md");
51
+ var reviewFormOneByOnePath = resolvePackageAssetPath("src/common/form/review-form-one-by-one.md");
16
52
  var REPORT_DIR = ".review-report";
17
53
  var tempDiffPath = "temp_diff.txt";
18
54
  var AIServices = ["gemini", "claude", "codex"];
55
+ var COMMIT_FETCH_LIMIT = 20;
56
+ var COMMIT_SELECTION_WINDOW = 8;
19
57
  var ignoreList = [
20
58
  "package.json",
21
59
  "*.yml",
@@ -39,6 +77,137 @@ function clearTraceMessages() {
39
77
  function getTraceMessages() {
40
78
  return [...traceMessages];
41
79
  }
80
+ var ANSI = {
81
+ bold: "\x1B[1m",
82
+ cyan: "\x1B[36m",
83
+ dim: "\x1B[2m",
84
+ green: "\x1B[32m",
85
+ reset: "\x1B[0m",
86
+ yellow: "\x1B[33m"
87
+ };
88
+ var ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
89
+ var COMBINING_MARK_PATTERN = /\p{Mark}/u;
90
+ var GRAPHEME_SEGMENTER = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter("ko", { granularity: "grapheme" }) : null;
91
+ function getGitDiffPathspecs() {
92
+ return {
93
+ excludePatterns: ignoreList.map((item) => `:(exclude)${item}`),
94
+ includePatterns: ["*.ts", "*.tsx", "*.js", "*.jsx"]
95
+ };
96
+ }
97
+ function segmentGraphemes(value) {
98
+ if (!GRAPHEME_SEGMENTER) {
99
+ return [...value];
100
+ }
101
+ return [...GRAPHEME_SEGMENTER.segment(value)].map(({ segment }) => segment);
102
+ }
103
+ function isWideCodePoint(codePoint) {
104
+ return codePoint >= 4352 && (codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 12871 && codePoint !== 12351 || codePoint >= 12880 && codePoint <= 19903 || codePoint >= 19968 && codePoint <= 42182 || codePoint >= 43360 && codePoint <= 43388 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65131 || codePoint >= 65281 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 127488 && codePoint <= 127569 || codePoint >= 131072 && codePoint <= 262141);
105
+ }
106
+ function isEmojiCodePoint(codePoint) {
107
+ return codePoint >= 127462 && codePoint <= 127487 || codePoint >= 127744 && codePoint <= 129791 || codePoint >= 9728 && codePoint <= 10175;
108
+ }
109
+ function getGraphemeWidth(grapheme) {
110
+ let width = 0;
111
+ for (const character of grapheme) {
112
+ const codePoint = character.codePointAt(0);
113
+ if (!codePoint || COMBINING_MARK_PATTERN.test(character) || codePoint === 8205) {
114
+ continue;
115
+ }
116
+ if (codePoint >= 65024 && codePoint <= 65039 || codePoint >= 917760 && codePoint <= 917999) {
117
+ continue;
118
+ }
119
+ if (isWideCodePoint(codePoint) || isEmojiCodePoint(codePoint)) {
120
+ width = Math.max(width, 2);
121
+ continue;
122
+ }
123
+ width = Math.max(width, 1);
124
+ }
125
+ return width;
126
+ }
127
+ function tokenizePlainText(value) {
128
+ return segmentGraphemes(value).map((segment) => ({
129
+ value: segment,
130
+ visibleWidth: getGraphemeWidth(segment)
131
+ }));
132
+ }
133
+ function tokenizeVisibleText(value) {
134
+ const tokens = [];
135
+ let lastIndex = 0;
136
+ for (const match of value.matchAll(ANSI_PATTERN)) {
137
+ const index = match.index ?? 0;
138
+ if (index > lastIndex) {
139
+ tokens.push(...tokenizePlainText(value.slice(lastIndex, index)));
140
+ }
141
+ tokens.push({
142
+ value: match[0],
143
+ visibleWidth: 0
144
+ });
145
+ lastIndex = index + match[0].length;
146
+ }
147
+ if (lastIndex < value.length) {
148
+ tokens.push(...tokenizePlainText(value.slice(lastIndex)));
149
+ }
150
+ return tokens;
151
+ }
152
+ function truncateLineForTerminal(value, maxWidth) {
153
+ if (maxWidth <= 0) {
154
+ return "";
155
+ }
156
+ const tokens = tokenizeVisibleText(value);
157
+ const totalWidth = tokens.reduce((sum, token) => sum + token.visibleWidth, 0);
158
+ if (totalWidth <= maxWidth) {
159
+ return value;
160
+ }
161
+ const ellipsis = "...";
162
+ const ellipsisWidth = 3;
163
+ const targetWidth = Math.max(0, maxWidth - ellipsisWidth);
164
+ let usedWidth = 0;
165
+ let result = "";
166
+ for (const token of tokens) {
167
+ if (token.visibleWidth === 0) {
168
+ result += token.value;
169
+ continue;
170
+ }
171
+ if (usedWidth + token.visibleWidth > targetWidth) {
172
+ break;
173
+ }
174
+ result += token.value;
175
+ usedWidth += token.visibleWidth;
176
+ }
177
+ return `${result}${ellipsis}${ANSI.reset}`;
178
+ }
179
+ function fitLinesToTerminal(lines) {
180
+ const maxWidth = Math.max(20, (process.stdout.columns || 120) - 1);
181
+ return lines.map((line) => truncateLineForTerminal(line, maxWidth));
182
+ }
183
+ function runGitCommand(args4, options = {}) {
184
+ const { allowFailure = false, trimOutput = true } = options;
185
+ try {
186
+ const output = execFileSync("git", args4, {
187
+ encoding: "utf8",
188
+ maxBuffer: 1024 * 1024 * 20,
189
+ stdio: ["ignore", "pipe", "pipe"]
190
+ });
191
+ return trimOutput ? output.trim() : output;
192
+ } catch (error) {
193
+ helperTrace("git-command:failed", `${args4.join(" ")} | ${getErrorSummary(error)}`);
194
+ if (allowFailure) {
195
+ return "";
196
+ }
197
+ throw error;
198
+ }
199
+ }
200
+ function formatReviewTargetFiles(files, visibleCount = 5) {
201
+ if (files.length === 0) {
202
+ return "(\uC5C6\uC74C)";
203
+ }
204
+ const visibleFiles = files.slice(0, visibleCount);
205
+ const hiddenCount = Math.max(0, files.length - visibleFiles.length);
206
+ if (hiddenCount === 0) {
207
+ return visibleFiles.join(", ");
208
+ }
209
+ return `${visibleFiles.join(", ")} \uC678 ${hiddenCount}\uAC1C`;
210
+ }
42
211
  function createTraceLogger(scope, args4 = process.argv.slice(2)) {
43
212
  const enabled = isTestMode(args4);
44
213
  return (step, detail) => {
@@ -100,7 +269,7 @@ function serializeError(error) {
100
269
  }
101
270
  if (error && typeof error === "object") {
102
271
  const errorLike = error;
103
- const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs"];
272
+ const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs", "command"];
104
273
  extraKeys.forEach((key) => {
105
274
  if (errorLike[key] !== void 0) {
106
275
  serialized[key] = errorLike[key];
@@ -206,6 +375,87 @@ ${section.markdown}`).join("\n")}
206
375
  return "";
207
376
  }
208
377
  }
378
+ function getExecutionLogSummary(status, title) {
379
+ if (title) {
380
+ return title;
381
+ }
382
+ switch (status) {
383
+ case "success":
384
+ return "\uB9AC\uBDF0 \uC2E4\uD589\uC774 \uC131\uACF5\uC801\uC73C\uB85C \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
385
+ case "failed":
386
+ return "\uB9AC\uBDF0 \uC2E4\uD589 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
387
+ case "partial_failure":
388
+ 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.";
389
+ default:
390
+ 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.";
391
+ }
392
+ }
393
+ function formatExecutionDuration(startedAt, finishedAt) {
394
+ const durationMs = Math.max(0, finishedAt.getTime() - startedAt.getTime());
395
+ if (durationMs < 1e3) {
396
+ return `${durationMs}ms`;
397
+ }
398
+ const durationSeconds = durationMs / 1e3;
399
+ if (durationSeconds < 60) {
400
+ return `${durationSeconds.toFixed(1)}s`;
401
+ }
402
+ const minutes = Math.floor(durationSeconds / 60);
403
+ const seconds = Math.round(durationSeconds % 60);
404
+ return `${minutes}m ${seconds}s`;
405
+ }
406
+ function writeExecutionLog(options = {}) {
407
+ try {
408
+ const startedAt = options.startedAt ?? /* @__PURE__ */ new Date();
409
+ const finishedAt = options.finishedAt ?? /* @__PURE__ */ new Date();
410
+ const status = options.status ?? "success";
411
+ helperTrace("execution-log:write:start", options.scope || "unknown");
412
+ createReportDirectory();
413
+ const reportPath = getAvailableFilePath(REPORT_DIR, `${getNowString(finishedAt)}-execution-log`, ".md");
414
+ const traceSnapshot = options.traceMessages ?? getTraceMessages();
415
+ const extraSections = options.extraSections || [];
416
+ const serializedError = options.error ? serializeError(options.error) : null;
417
+ const report = `# Execution Log
418
+
419
+ - \uC2DC\uC791 \uC2DC\uAC01: ${getHumanReadableNowString(startedAt)}
420
+ - \uC885\uB8CC \uC2DC\uAC01: ${getHumanReadableNowString(finishedAt)}
421
+ - \uC2E4\uD589 \uC2DC\uAC04: ${formatExecutionDuration(startedAt, finishedAt)}
422
+ - \uC0C1\uD0DC: \`${status}\`
423
+ - Scope: \`${options.scope || "unknown"}\`
424
+ - \uC791\uC5C5 \uACBD\uB85C: \`${process.cwd()}\`
425
+ - \uC2E4\uD589 \uC778\uC790: \`${JSON.stringify(options.args ?? process.argv.slice(2))}\`
426
+ - \uC2E4\uD589 \uD658\uACBD: \`${process.platform} ${process.arch} / Node ${process.version}\`
427
+
428
+ ## Summary
429
+
430
+ ${getExecutionLogSummary(status, options.title)}
431
+ ${serializedError ? `
432
+
433
+ ## Error
434
+
435
+ \`\`\`json
436
+ ${JSON.stringify(serializedError, null, 2)}
437
+ \`\`\`` : ""}
438
+
439
+ ## Trace
440
+
441
+ \`\`\`json
442
+ ${JSON.stringify(traceSnapshot, null, 2)}
443
+ \`\`\`${extraSections.length ? `
444
+ ${extraSections.map((section) => `
445
+ ## ${section.heading}
446
+
447
+ ${section.markdown}`).join("\n")}
448
+ ` : "\n"}
449
+ `;
450
+ fs.writeFileSync(reportPath, report);
451
+ helperTrace("execution-log:write:done", reportPath);
452
+ return reportPath;
453
+ } catch (writeError) {
454
+ console.error("\u26A0\uFE0F \uC2E4\uD589 \uB85C\uADF8 \uD30C\uC77C \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
455
+ console.error(writeError);
456
+ return "";
457
+ }
458
+ }
209
459
  function exitWithError(message, options = {}) {
210
460
  const reportPath = writeErrorReport(options.error || new Error(message), {
211
461
  ...options,
@@ -250,13 +500,83 @@ ${JSON.stringify(AIServices, null, 2)}
250
500
  }
251
501
  );
252
502
  }
253
- function getGitDiffFilter() {
254
- const includeExtensions = ["*.ts", "*.tsx", "*.js", "*.jsx"];
255
- const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);
256
- const quote = (pattern) => `"${pattern}"`;
257
- const includeParams = includeExtensions.map(quote).join(" ");
258
- const excludeParams = excludePatterns.map(quote).join(" ");
259
- return { includeParams, excludeParams };
503
+ function truncateCommitSubject(subject) {
504
+ if (subject.length <= 72) {
505
+ return subject;
506
+ }
507
+ return `${subject.slice(0, 69)}...`;
508
+ }
509
+ function getRecentCommitOptions() {
510
+ const output = runGitCommand(
511
+ ["log", `-${COMMIT_FETCH_LIMIT}`, "--date=relative", "--pretty=format:%h%x09%an%x09%ar%x09%s"],
512
+ { allowFailure: true }
513
+ );
514
+ if (!output) {
515
+ return [];
516
+ }
517
+ return output.split("\n").map((line) => {
518
+ const [hash = "", author = "", relativeDate = "", ...subjectParts] = line.split(" ");
519
+ const subject = subjectParts.join(" ").trim();
520
+ return {
521
+ author,
522
+ description: `${author} | ${relativeDate}`,
523
+ hash,
524
+ label: `${hash} | ${truncateCommitSubject(subject)}`,
525
+ relativeDate,
526
+ subject
527
+ };
528
+ });
529
+ }
530
+ function buildSelectedCommitSummary(commits) {
531
+ return commits.map((commit) => `- ${commit.hash} | ${commit.subject} | ${commit.author} | ${commit.relativeDate}`).join("\n");
532
+ }
533
+ function getReviewPathspecArgs() {
534
+ const { includePatterns, excludePatterns } = getGitDiffPathspecs();
535
+ return [...includePatterns, ...excludePatterns];
536
+ }
537
+ function buildSelectedCommitDiff(commits) {
538
+ const reviewPathspecArgs = getReviewPathspecArgs();
539
+ const sections = commits.map((commit) => {
540
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", ...reviewPathspecArgs], {
541
+ allowFailure: true,
542
+ trimOutput: false
543
+ }).trim();
544
+ if (!diff) {
545
+ return "";
546
+ }
547
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
548
+ }).filter(Boolean).join("\n\n");
549
+ if (!sections) {
550
+ return "";
551
+ }
552
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", "# \uB9AC\uBDF0 \uB300\uC0C1 diff", sections].join("\n");
553
+ }
554
+ function getSelectedCommitFiles(commits) {
555
+ const reviewPathspecArgs = getReviewPathspecArgs();
556
+ const files = /* @__PURE__ */ new Set();
557
+ commits.forEach((commit) => {
558
+ const output = runGitCommand(["show", "--pretty=format:", "--name-only", commit.hash, "--", ...reviewPathspecArgs], {
559
+ allowFailure: true
560
+ });
561
+ output.split("\n").map((line) => line.trim()).filter(Boolean).forEach((filePath) => files.add(filePath));
562
+ });
563
+ return [...files];
564
+ }
565
+ function buildSelectedFileDiff(commits, filePath) {
566
+ const sections = commits.map((commit) => {
567
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", filePath], {
568
+ allowFailure: true,
569
+ trimOutput: false
570
+ }).trim();
571
+ if (!diff) {
572
+ return "";
573
+ }
574
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
575
+ }).filter(Boolean).join("\n\n");
576
+ if (!sections) {
577
+ return "";
578
+ }
579
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", `# \uD30C\uC77C: ${filePath}`, sections].join("\n\n");
260
580
  }
261
581
  function openReport(reportPath) {
262
582
  const resolvedPath = path.resolve(reportPath);
@@ -307,59 +627,166 @@ function openReport(reportPath) {
307
627
  helperTrace("open-report:unsupported-platform", platform);
308
628
  console.error(`\u26A0\uFE0F \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD50C\uB7AB\uD3FC\uC785\uB2C8\uB2E4: ${platform}`);
309
629
  }
310
- function getDiffArgs() {
311
- const args4 = process.argv.slice(2);
312
- const commitIndex = args4.indexOf("--commit");
313
- const { includeParams, excludeParams } = getGitDiffFilter();
314
- helperTrace("diff-args:resolve:start", `args=${JSON.stringify(args4)}`);
315
- let diffArgs = "";
316
- if (commitIndex !== -1) {
317
- const commitHash = args4[commitIndex + 1];
318
- if (!commitHash) {
319
- helperTrace("diff-args:commit-hash-missing");
320
- exitWithError("\u274C \uCEE4\uBC0B \uD574\uC2DC\uAC00 \uC81C\uACF5\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.", {
321
- scope: "helper:getDiffArgs",
322
- args: args4
323
- });
630
+ function ensureInteractiveSelectionAvailable(scope, message) {
631
+ if (process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function") {
632
+ return;
633
+ }
634
+ helperTrace(`${scope}:tty-missing`);
635
+ exitWithError(message, {
636
+ scope: `helper:${scope}`
637
+ });
638
+ }
639
+ function renderSelectionBlock(lines, previousLineCount) {
640
+ if (previousLineCount > 0) {
641
+ readline.moveCursor(process.stdout, 0, -previousLineCount);
642
+ readline.clearScreenDown(process.stdout);
643
+ }
644
+ const fittedLines = fitLinesToTerminal(lines);
645
+ process.stdout.write(`${fittedLines.join("\n")}
646
+ `);
647
+ return fittedLines.length;
648
+ }
649
+ function getSelectionWindowRange(optionCount, selectedIndex, windowSize) {
650
+ if (optionCount <= windowSize) {
651
+ return {
652
+ end: optionCount,
653
+ start: 0
654
+ };
655
+ }
656
+ const halfWindow = Math.floor(windowSize / 2);
657
+ const maxStart = optionCount - windowSize;
658
+ const start = Math.max(0, Math.min(selectedIndex - halfWindow, maxStart));
659
+ return {
660
+ end: Math.min(optionCount, start + windowSize),
661
+ start
662
+ };
663
+ }
664
+ function buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize) {
665
+ const { start, end } = getSelectionWindowRange(options.length, selectedIndex, windowSize);
666
+ const lines = [
667
+ `${ANSI.bold}${question}${ANSI.reset}`,
668
+ `${ANSI.dim}\u2191\u2193 \uC774\uB3D9 | Space \uC120\uD0DD/\uD574\uC81C | Enter \uC644\uB8CC | Esc \uCDE8\uC18C${ANSI.reset}`,
669
+ `${ANSI.dim}\uC120\uD0DD\uB428: ${toggled.size}\uAC1C / \uC804\uCCB4: ${options.length}\uAC1C${ANSI.reset}`
670
+ ];
671
+ for (let index = start; index < end; index += 1) {
672
+ const option = options[index];
673
+ if (!option) {
674
+ continue;
324
675
  }
325
- const nextArg = args4[commitIndex + 2];
326
- let n = 0;
327
- if (nextArg && !nextArg.startsWith("--")) {
328
- n = parseInt(nextArg, 10);
329
- if (isNaN(n)) {
330
- n = 0;
331
- }
676
+ const cursor = index === selectedIndex ? `${ANSI.cyan}>${ANSI.reset}` : " ";
677
+ const checked = toggled.has(index) ? `${ANSI.green}\u2611${ANSI.reset}` : "\u2610";
678
+ const description = option.description ? ` ${ANSI.dim}${option.description}${ANSI.reset}` : "";
679
+ lines.push(`${cursor} ${checked} ${option.label}${description}`);
680
+ }
681
+ if (options.length > windowSize) {
682
+ lines.push(`${ANSI.dim}\uD45C\uC2DC \uBC94\uC704: ${start + 1}-${end} / ${options.length}${ANSI.reset}`);
683
+ }
684
+ return lines;
685
+ }
686
+ async function showMultiSelect(question, options, windowSize = COMMIT_SELECTION_WINDOW) {
687
+ ensureInteractiveSelectionAvailable("showMultiSelect", "\u274C \uCEE4\uBC0B \uC120\uD0DD \uBAA8\uB2EC\uC740 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
688
+ let selectedIndex = 0;
689
+ let renderedLineCount = 0;
690
+ const toggled = /* @__PURE__ */ new Set();
691
+ const rl = readline.createInterface({
692
+ input: process.stdin,
693
+ output: process.stdout,
694
+ terminal: true
695
+ });
696
+ process.stdout.write("\x1B[?25l");
697
+ const cleanup = () => {
698
+ if (renderedLineCount > 0) {
699
+ readline.moveCursor(process.stdout, 0, -renderedLineCount);
700
+ readline.clearScreenDown(process.stdout);
701
+ renderedLineCount = 0;
332
702
  }
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}`);
703
+ process.stdin.removeListener("data", onData);
704
+ process.stdin.setRawMode(false);
705
+ process.stdin.pause();
706
+ rl.close();
707
+ process.stdout.write("\x1B[?25h");
708
+ };
709
+ const render = () => {
710
+ const lines = buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize);
711
+ renderedLineCount = renderSelectionBlock(lines, renderedLineCount);
712
+ };
713
+ const confirmSelection = (resolve) => {
714
+ const values = [...toggled].sort((left, right) => left - right).map((index) => options[index]?.value).filter((value) => value !== void 0);
715
+ cleanup();
716
+ resolve(values);
717
+ };
718
+ const cancelSelection = (resolve) => {
719
+ cleanup();
720
+ resolve([]);
721
+ };
722
+ let onData = (_data) => {
723
+ };
724
+ render();
725
+ return new Promise((resolve) => {
726
+ onData = (data) => {
727
+ const key = data.toString();
728
+ if (key === "") {
729
+ cleanup();
730
+ process.exit(0);
346
731
  }
347
- } catch (error) {
348
- helperTrace("diff-args:unstaged-check:failed", getErrorSummary(error));
349
- }
732
+ if (key === "\x1B") {
733
+ cancelSelection(resolve);
734
+ return;
735
+ }
736
+ if (key === "\x1B[A") {
737
+ selectedIndex = (selectedIndex - 1 + options.length) % options.length;
738
+ render();
739
+ return;
740
+ }
741
+ if (key === "\x1B[B") {
742
+ selectedIndex = (selectedIndex + 1) % options.length;
743
+ render();
744
+ return;
745
+ }
746
+ if (key === " ") {
747
+ if (toggled.has(selectedIndex)) {
748
+ toggled.delete(selectedIndex);
749
+ } else {
750
+ toggled.add(selectedIndex);
751
+ }
752
+ render();
753
+ return;
754
+ }
755
+ if (key === "\r" || key === "\n") {
756
+ confirmSelection(resolve);
757
+ }
758
+ };
759
+ process.stdin.setRawMode(true);
760
+ process.stdin.resume();
761
+ process.stdin.on("data", onData);
762
+ });
763
+ }
764
+ async function selectReviewCommits() {
765
+ const commits = getRecentCommitOptions();
766
+ if (commits.length === 0) {
767
+ console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uCD5C\uADFC \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
768
+ return [];
350
769
  }
351
- helperTrace("diff-args:resolve:done", diffArgs || "(default)");
352
- return diffArgs;
770
+ return showMultiSelect(
771
+ "\uB9AC\uBDF0\uD560 \uCEE4\uBC0B\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.",
772
+ commits.map((commit) => ({
773
+ description: commit.description,
774
+ label: commit.label,
775
+ value: commit
776
+ })),
777
+ COMMIT_SELECTION_WINDOW
778
+ );
353
779
  }
354
780
  async function showSelectionAIService() {
355
781
  const selectedServiceFromArgs = parseServiceFromArgs();
356
782
  if (selectedServiceFromArgs) {
357
783
  helperTrace("show-selection:from-args", selectedServiceFromArgs);
358
784
  console.log(`
359
- \u2705 \x1B[32m${selectedServiceFromArgs}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
785
+ \u2705 ${ANSI.green}${selectedServiceFromArgs}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
360
786
  `);
361
787
  return selectedServiceFromArgs;
362
788
  }
789
+ ensureInteractiveSelectionAvailable("showSelectionAIService", "\u274C AI \uC11C\uBE44\uC2A4 \uC120\uD0DD UI\uB294 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
363
790
  helperTrace("show-selection:interactive:start");
364
791
  let selectedIndex = 0;
365
792
  const rl = readline.createInterface({
@@ -377,11 +804,12 @@ async function showSelectionAIService() {
377
804
  helperTrace("show-selection:interactive:render", AIServices[selectedIndex] || "unknown");
378
805
  readline.clearScreenDown(process.stdout);
379
806
  process.stdout.write(
380
- "\u{1F916} AI \uC11C\uBE44\uC2A4\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694 (\x1B[33m\u2191\u2193 \uBC29\uD5A5\uD0A4\x1B[0m \uC774\uB3D9, \x1B[33mEnter\x1B[0m \uC120\uD0DD):\n"
807
+ `\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):
808
+ `
381
809
  );
382
810
  AIServices.forEach((service, index) => {
383
811
  if (index === selectedIndex) {
384
- process.stdout.write(` \x1B[36m>\x1B[0m \x1B[36m\u25C9\x1B[0m \x1B[1m${service}\x1B[0m
812
+ process.stdout.write(` ${ANSI.cyan}>${ANSI.reset} ${ANSI.cyan}\u25C9${ANSI.reset} ${ANSI.bold}${service}${ANSI.reset}
385
813
  `);
386
814
  } else {
387
815
  process.stdout.write(` \u25EF ${service}
@@ -411,7 +839,7 @@ async function showSelectionAIService() {
411
839
  rl.close();
412
840
  process.stdout.write("\x1B[?25h");
413
841
  console.log(`
414
- \u2705 \x1B[32m${AIServices[selectedIndex]}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
842
+ \u2705 ${ANSI.green}${AIServices[selectedIndex]}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
415
843
  `);
416
844
  const result = AIServices[selectedIndex];
417
845
  if (result) {
@@ -431,6 +859,13 @@ var ALLOWED_REASONING_EFFORTS = ["minimal", "low", "medium", "high"];
431
859
  function shellQuote(value) {
432
860
  return `'${value.replace(/'/g, `'\\''`)}'`;
433
861
  }
862
+ function toShellOptionToken(value) {
863
+ const SIMPLE_SHELL_TOKEN_PATTERN = /^[A-Za-z0-9._:/=-]+$/;
864
+ if (SIMPLE_SHELL_TOKEN_PATTERN.test(value)) {
865
+ return value;
866
+ }
867
+ return shellQuote(value);
868
+ }
434
869
  function getArgValue(flag) {
435
870
  const index = args.indexOf(flag);
436
871
  if (index === -1 || !args[index + 1]) {
@@ -438,6 +873,11 @@ function getArgValue(flag) {
438
873
  }
439
874
  return args[index + 1];
440
875
  }
876
+ function printNotice(message) {
877
+ if (args.includes("--test")) {
878
+ console.warn(message);
879
+ }
880
+ }
441
881
  function toUnique(values) {
442
882
  const seen = /* @__PURE__ */ new Set();
443
883
  return values.filter((value) => {
@@ -461,11 +901,11 @@ function resolveReasoningEffort() {
461
901
  const normalized = normalizeEffort(customReasoningEffort);
462
902
  trace("reasoning:custom", `${customReasoningEffort} -> ${normalized}`);
463
903
  if (customReasoningEffort === "minimal") {
464
- console.warn("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
904
+ printNotice("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
465
905
  }
466
906
  return normalized;
467
907
  }
468
- console.warn(
908
+ printNotice(
469
909
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS.join(
470
910
  ", "
471
911
  )}`
@@ -505,10 +945,10 @@ function getAliasFallbacks(primaryAlias) {
505
945
  }
506
946
  function buildClaudeExecCommand(options) {
507
947
  const { tempDiffPath: tempDiffPath2, prompt, systemPromptFiles, effort, model, fallbackModel } = options;
508
- const modelOption = model ? `--model ${shellQuote(model)}` : "";
509
- const fallbackOption = model && fallbackModel ? `--fallback-model ${shellQuote(fallbackModel)}` : "";
510
- const effortOption = `--effort ${shellQuote(effort)}`;
511
- const appendedPromptFiles = systemPromptFiles.map((path2) => `--append-system-prompt-file ${shellQuote(path2)}`).join(" ");
948
+ const modelOption = model ? `--model ${toShellOptionToken(model)}` : "";
949
+ const fallbackOption = model && fallbackModel ? `--fallback-model ${toShellOptionToken(fallbackModel)}` : "";
950
+ const effortOption = `--effort ${toShellOptionToken(effort)}`;
951
+ const appendedPromptFiles = systemPromptFiles.map((path3) => `--append-system-prompt-file ${shellQuote(path3)}`).join(" ");
512
952
  return `cat ${shellQuote(tempDiffPath2)} | claude ${[
513
953
  modelOption,
514
954
  fallbackOption,
@@ -541,13 +981,13 @@ var createClaudeCommand = (tempDiffPath2, reviewFormPath2) => {
541
981
  trace("model:candidates", modelCandidates.join(", "));
542
982
  trace("command:candidates:count", String(modelCandidates.length + 1));
543
983
  if (customModel) {
544
- console.warn(
984
+ printNotice(
545
985
  `\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
546
986
  " -> "
547
987
  )}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
548
988
  );
549
989
  } else {
550
- console.warn(
990
+ printNotice(
551
991
  `\u26A0\uFE0F \uBAA8\uB378\uC774 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. alias(${aliasFallbacks.join(" -> ")})\uB97C \uC21C\uCC28 \uC2DC\uB3C4\uD558\uACE0 \uB9C8\uC9C0\uB9C9\uC5D0 \uAE30\uBCF8 \uBAA8\uB378\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
552
992
  );
553
993
  }
@@ -627,22 +1067,37 @@ function getArgValue2(flag) {
627
1067
  }
628
1068
  return args2[index + 1];
629
1069
  }
1070
+ function printNotice2(message) {
1071
+ if (args2.includes("--test")) {
1072
+ console.warn(message);
1073
+ }
1074
+ }
1075
+ function normalizeEffort2(level) {
1076
+ if (level === "minimal") {
1077
+ return "low";
1078
+ }
1079
+ return level;
1080
+ }
630
1081
  function resolveReasoningEffort2() {
631
1082
  const customReasoningEffort = getArgValue2("--reasoning-effort");
632
1083
  if (customReasoningEffort) {
633
1084
  if (ALLOWED_REASONING_EFFORTS2.includes(customReasoningEffort)) {
634
- trace3("reasoning:custom", customReasoningEffort);
635
- return customReasoningEffort;
1085
+ const normalized = normalizeEffort2(customReasoningEffort);
1086
+ trace3("reasoning:custom", `${customReasoningEffort} -> ${normalized}`);
1087
+ if (customReasoningEffort === "minimal") {
1088
+ printNotice2("\u26A0\uFE0F Codex\uB294 minimal\uC774 web_search \uB3C4\uAD6C\uC640 \uCDA9\uB3CC\uD560 \uC218 \uC788\uC5B4 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
1089
+ }
1090
+ return normalized;
636
1091
  }
637
- console.warn(
1092
+ printNotice2(
638
1093
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS2.join(
639
1094
  ", "
640
1095
  )}`
641
1096
  );
642
1097
  }
643
1098
  if (args2.includes("--flash")) {
644
- trace3("reasoning:flash-default", "minimal");
645
- return "minimal";
1099
+ trace3("reasoning:flash-default", "low");
1100
+ return "low";
646
1101
  }
647
1102
  if (args2.includes("--review")) {
648
1103
  trace3("reasoning:review-default", "high");
@@ -678,14 +1133,14 @@ ${reviewFormLine || "- (\uC5C6\uC74C)"}
678
1133
  trace3("prompt:prepared", `length=${prompt.length}`);
679
1134
  let command = "";
680
1135
  if (customModel) {
681
- console.warn("\u26A0\uFE0F \uC9C0\uC815\uD55C \uBAA8\uB378\uC774 \uC5C6\uB294 \uACBD\uC6B0, \uC5D0\uB7EC\uAC00 \uBC1C\uC0DD\uD558\uB2C8 \uC8FC\uC758\uD558\uC138\uC694.");
1136
+ printNotice2("\u26A0\uFE0F \uC9C0\uC815\uD55C \uBAA8\uB378\uC774 \uC5C6\uB294 \uACBD\uC6B0, \uC5D0\uB7EC\uAC00 \uBC1C\uC0DD\uD558\uB2C8 \uC8FC\uC758\uD558\uC138\uC694.");
682
1137
  trace3("model:custom", customModel);
683
1138
  command = buildCodexExecCommand(prompt, reasoningEffort, customModel);
684
1139
  } else {
685
1140
  const preferredModelAlias = "gpt-5";
686
1141
  const aliasCommand = buildCodexExecCommand(prompt, reasoningEffort, preferredModelAlias);
687
1142
  const fallbackCommand = buildCodexExecCommand(prompt, reasoningEffort);
688
- console.warn(
1143
+ printNotice2(
689
1144
  `\u26A0\uFE0F \uBAA8\uB378\uC774 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. alias(${preferredModelAlias})\uB97C \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 \uACC4\uC815 \uAE30\uBCF8 \uBAA8\uB378\uB85C \uC790\uB3D9 \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
690
1145
  );
691
1146
  trace3("model:alias-first", preferredModelAlias);
@@ -746,6 +1201,11 @@ function getArgValue3(flag) {
746
1201
  }
747
1202
  return args3[index + 1];
748
1203
  }
1204
+ function printNotice3(message) {
1205
+ if (args3.includes("--test")) {
1206
+ console.warn(message);
1207
+ }
1208
+ }
749
1209
  function toUnique2(values) {
750
1210
  const seen = /* @__PURE__ */ new Set();
751
1211
  return values.filter((value) => {
@@ -763,7 +1223,7 @@ function resolveReasoningEffort3() {
763
1223
  trace5("reasoning:custom", customReasoningEffort);
764
1224
  return customReasoningEffort;
765
1225
  }
766
- console.warn(
1226
+ printNotice3(
767
1227
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS3.join(
768
1228
  ", "
769
1229
  )}`
@@ -825,38 +1285,57 @@ function buildGeminiExecCommand(prompt, model) {
825
1285
  const modelOption = model ? `--model ${shellQuote3(model)}` : "";
826
1286
  return `gemini ${[modelOption, "-p", shellQuote3(prompt)].filter(Boolean).join(" ")}`;
827
1287
  }
1288
+ function toGeminiFileReference(filePath) {
1289
+ return `@${filePath}`;
1290
+ }
1291
+ function buildGeminiFileReferenceSection(files) {
1292
+ const existingFiles = files.filter((file) => fs.existsSync(file.path));
1293
+ return {
1294
+ count: existingFiles.length,
1295
+ items: existingFiles.map((file) => `${file.display} ${toGeminiFileReference(file.path)}`)
1296
+ };
1297
+ }
828
1298
  var createGeminiCommand = (tempDiffPath2, reviewFormPath2) => {
829
1299
  trace5("createGeminiCommand:start", `tempDiffPath=${tempDiffPath2}, reviewFormPath=${reviewFormPath2}`);
830
1300
  const customModel = getArgValue3("--model");
831
1301
  const reasoningEffort = resolveReasoningEffort3();
832
1302
  const primaryAlias = resolvePrimaryAlias2(reasoningEffort);
833
1303
  const aliasFallbacks = toUnique2(getAliasFallbacks2(primaryAlias));
1304
+ const resolvedTempDiffPath = path.resolve(tempDiffPath2);
1305
+ const resolvedReviewFormPath = path.resolve(reviewFormPath2);
834
1306
  const rules = [
835
1307
  { path: rulesPath, display: "\uB8F0\uC14B" },
836
1308
  { path: namingRulesPath, display: "\uB124\uC774\uBC0D \uADDC\uCE59" },
837
1309
  { path: codingConventionRulesPath, display: "\uCF54\uB529 \uCEE8\uBCA4\uC158" }
838
1310
  ];
839
- const validRules = rules.filter((rule) => fs.existsSync(rule.path)).map((rule) => `@${rule.path}`).join(", ");
840
- const rulesCount = validRules ? validRules.split(",").length : 0;
841
- trace5("rules:loaded", `count=${rulesCount}`);
842
- const reviewFormRef = fs.existsSync(reviewFormPath2) ? `@${reviewFormPath2}` : "(\uC5C6\uC74C)";
843
- trace5("reviewForm:status", reviewFormRef === "(\uC5C6\uC74C)" ? "missing" : "exists");
1311
+ const ruleSection = buildGeminiFileReferenceSection(rules);
1312
+ trace5("rules:loaded", `count=${ruleSection.count}`);
1313
+ const reviewFormExists = fs.existsSync(resolvedReviewFormPath);
1314
+ const reviewFormText = reviewFormExists ? `\uB9AC\uBDF0 \uC591\uC2DD ${toGeminiFileReference(resolvedReviewFormPath)}` : "\uB9AC\uBDF0 \uC591\uC2DD (\uC5C6\uC74C)";
1315
+ trace5("reviewForm:status", reviewFormExists ? "exists" : "missing");
844
1316
  const reasoningInstruction = getReasoningInstruction(reasoningEffort);
845
- const prompt = `\uB2E4\uC74C \uADDC\uCE59\uB4E4\uC744 \uCC38\uACE0\uD574\uC11C(${validRules || "(\uC5C6\uC74C)"}) \uC774 diff(@${tempDiffPath2})\uB97C \uB9AC\uBDF0\uD574\uC918.
846
- \uB9AC\uBDF0 \uC591\uC2DD\uC740 ${reviewFormRef} \uC5D0 \uB9DE\uCDB0\uC11C \uC791\uC131\uD574\uC918.
847
- \uCD94\uB860 \uAC15\uB3C4 \uC9C0\uCE68: ${reasoningInstruction}`;
1317
+ const ruleText = ruleSection.items.join(" ") || "(\uC5C6\uC74C)";
1318
+ const diffText = `\uB9AC\uBDF0 \uB300\uC0C1 diff ${toGeminiFileReference(resolvedTempDiffPath)}`;
1319
+ const prompt = [
1320
+ "\uC544\uB798 \uD30C\uC77C\uB4E4\uC744 \uC21C\uC11C\uB300\uB85C \uC77D\uACE0 \uCF54\uB4DC \uB9AC\uBDF0\uB97C \uC9C4\uD589\uD574\uC918.",
1321
+ `\uADDC\uCE59 \uD30C\uC77C: ${ruleText}`,
1322
+ `\uB9AC\uBDF0 \uC591\uC2DD \uD30C\uC77C: ${reviewFormText}`,
1323
+ `\uB9AC\uBDF0 \uB300\uC0C1 diff \uD30C\uC77C: ${diffText}`,
1324
+ "\uBC18\uB4DC\uC2DC \uB9AC\uBDF0 \uC591\uC2DD\uC5D0 \uB9DE\uCDB0 \uC791\uC131\uD574\uC918.",
1325
+ `\uCD94\uB860 \uAC15\uB3C4 \uC9C0\uCE68: ${reasoningInstruction}`
1326
+ ].join(" ");
848
1327
  trace5("prompt:prepared", `length=${prompt.length}`);
849
1328
  const modelCandidates = toUnique2(customModel ? [customModel, ...aliasFallbacks] : aliasFallbacks);
850
1329
  trace5("model:candidates", modelCandidates.join(", "));
851
1330
  trace5("command:candidates:count", String(modelCandidates.length + 1));
852
1331
  if (customModel) {
853
- console.warn(
1332
+ printNotice3(
854
1333
  `\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
855
1334
  " -> "
856
1335
  )}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
857
1336
  );
858
1337
  } else {
859
- console.warn(
1338
+ printNotice3(
860
1339
  `\u26A0\uFE0F \uBAA8\uB378\uC774 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. alias(${aliasFallbacks.join(" -> ")})\uB97C \uC21C\uCC28 \uC2DC\uB3C4\uD558\uACE0 \uB9C8\uC9C0\uB9C9\uC5D0 \uAE30\uBCF8 \uBAA8\uB378\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
861
1340
  );
862
1341
  }
@@ -908,6 +1387,7 @@ function checkGeminiCliInstalled() {
908
1387
  var execAsync = util.promisify(exec);
909
1388
  async function main() {
910
1389
  const args4 = process.argv.slice(2);
1390
+ const startedAt = /* @__PURE__ */ new Date();
911
1391
  clearTraceMessages();
912
1392
  const isTest = isTestMode(args4);
913
1393
  const trace7 = createTraceLogger("review-one-by-one", args4);
@@ -915,6 +1395,14 @@ async function main() {
915
1395
  let service = "";
916
1396
  let savedDiffPath = "";
917
1397
  let savedReportPath = "";
1398
+ let selectedCommitSummary = "";
1399
+ let fileList = [];
1400
+ let executionLogPath = "";
1401
+ let executionStatus = "cancelled";
1402
+ let executionTitle = "\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.";
1403
+ let executionError = null;
1404
+ let exitCode = 0;
1405
+ const fileExecutionLogs = [];
918
1406
  try {
919
1407
  trace7("service-selection:start");
920
1408
  service = await showSelectionAIService();
@@ -938,29 +1426,37 @@ async function main() {
938
1426
  }
939
1427
  trace7("review-flow:start");
940
1428
  console.log("\u{1F680} AI Code Review\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4...");
941
- trace7("report-dir:create:start");
942
- createReportDirectory();
943
- trace7("report-dir:create:done");
944
- const { includeParams, excludeParams } = getGitDiffFilter();
945
- trace7("diff-filter:loaded", `include=${includeParams} | exclude=${excludeParams}`);
946
- trace7("diff-args:build:start");
947
- const diffArgs = getDiffArgs();
948
- trace7("diff-args:build:done", `diffArgs=${diffArgs || "(default)"}`);
949
- const filesCommand = `git diff --name-only ${diffArgs} -- ${includeParams} ${excludeParams}`;
950
- trace7("files-command:run", filesCommand);
951
- const fileList = execSync(filesCommand).toString().split("\n").filter(Boolean);
1429
+ trace7("commit-selection:start");
1430
+ const selectedCommits = await selectReviewCommits();
1431
+ trace7("commit-selection:done", `count=${selectedCommits.length}`);
1432
+ if (selectedCommits.length === 0) {
1433
+ trace7("commit-selection:empty");
1434
+ executionTitle = "\uC120\uD0DD\uB41C \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.";
1435
+ console.log("\u2139\uFE0F \uC120\uD0DD\uB41C \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1436
+ deleteTempDiff();
1437
+ return;
1438
+ }
1439
+ selectedCommitSummary = buildSelectedCommitSummary(selectedCommits);
1440
+ trace7("commit-summary:prepared", selectedCommitSummary);
1441
+ console.log("\u23F3 \uC120\uD0DD\uD55C \uCEE4\uBC0B\uC758 \uD30C\uC77C \uBAA9\uB85D\uACFC diff\uB97C \uC815\uB9AC\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...");
1442
+ trace7("files-command:run");
1443
+ fileList = getSelectedCommitFiles(selectedCommits);
952
1444
  trace7("files-command:done", `fileCount=${fileList.length}`);
1445
+ console.log(`\u{1F4C2} \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C(${fileList.length}\uAC1C): ${formatReviewTargetFiles(fileList)}`);
953
1446
  if (fileList.length === 0) {
954
1447
  trace7("empty-file-list:exit");
955
- console.log("\u2139\uFE0F \uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1448
+ executionTitle = "\uC120\uD0DD\uD55C \uCEE4\uBC0B\uC5D0\uC11C \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C \uBCC0\uACBD\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.";
1449
+ console.log("\u2139\uFE0F \uC120\uD0DD\uD55C \uCEE4\uBC0B\uC5D0\uC11C \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C \uBCC0\uACBD\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
956
1450
  deleteTempDiff();
957
- process.exit(0);
1451
+ return;
958
1452
  }
959
- const fullDiffCommand = `git diff ${diffArgs} -- ${includeParams} ${excludeParams}`;
960
- trace7("full-diff:run");
961
- const fullDiff = execSync(fullDiffCommand).toString();
1453
+ trace7("report-dir:create:start");
1454
+ createReportDirectory();
1455
+ trace7("report-dir:create:done");
1456
+ trace7("full-diff:build:start");
1457
+ const fullDiff = buildSelectedCommitDiff(selectedCommits);
962
1458
  const nowStr = getNowString();
963
- trace7("full-diff:done", `length=${fullDiff.length}`);
1459
+ trace7("full-diff:build:done", `length=${fullDiff.length}`);
964
1460
  trace7("timestamp:created", nowStr);
965
1461
  trace7("temp-diff:write:start", tempDiffPath);
966
1462
  fs.writeFileSync(tempDiffPath, fullDiff);
@@ -971,15 +1467,28 @@ async function main() {
971
1467
  trace7("saved-diff:copy:done", savedDiffPath);
972
1468
  savedReportPath = getNextFilePath(REPORT_DIR, nowStr, ".md");
973
1469
  trace7("saved-report:path", savedReportPath);
974
- const promises = fileList.map(async (file) => {
1470
+ const promises = fileList.map(async (file, index) => {
975
1471
  const tempOneFileDiffPath = `temp_diff_${file.replace(/\//g, "_")}.txt`;
976
1472
  let command = "";
977
1473
  try {
978
1474
  trace7("file-review:start", file);
979
- console.log(`\u{1F50D} Reviewing: ${file}...`);
980
- trace7("file-diff:run", file);
981
- const fileDiff = execSync(`git diff ${diffArgs} -- "${file}"`).toString();
982
- trace7("file-diff:done", `${file} | length=${fileDiff.length}`);
1475
+ console.log(`\u{1F50D} [${index + 1}/${fileList.length}] \uB9AC\uBDF0 \uC900\uBE44: ${file}`);
1476
+ trace7("file-diff:build:start", file);
1477
+ const fileDiff = buildSelectedFileDiff(selectedCommits, file);
1478
+ trace7("file-diff:build:done", `${file} | length=${fileDiff.length}`);
1479
+ if (!fileDiff.trim()) {
1480
+ trace7("file-diff:empty", file);
1481
+ fileExecutionLogs.push({
1482
+ command: null,
1483
+ errorSummary: null,
1484
+ file,
1485
+ index,
1486
+ resultLength: 0,
1487
+ status: "skipped",
1488
+ tempOneFileDiffPath
1489
+ });
1490
+ return;
1491
+ }
983
1492
  trace7("file-temp-diff:write:start", tempOneFileDiffPath);
984
1493
  fs.writeFileSync(tempOneFileDiffPath, fileDiff);
985
1494
  trace7("file-temp-diff:write:done", tempOneFileDiffPath);
@@ -996,6 +1505,7 @@ async function main() {
996
1505
  break;
997
1506
  }
998
1507
  trace7("file-command:create:done", `${file} | commandLength=${command.length}`);
1508
+ console.log(`\u23F3 [${index + 1}/${fileList.length}] \uB9AC\uBDF0 \uC9C4\uD589: ${file}`);
999
1509
  trace7("file-command:exec:start", file);
1000
1510
  const { stdout } = await execAsync(command, { maxBuffer: 1024 * 1024 * 20 });
1001
1511
  const result = stdout.toString();
@@ -1007,7 +1517,6 @@ async function main() {
1007
1517
  ${result}
1008
1518
 
1009
1519
  `;
1010
- console.log(tempReport);
1011
1520
  trace7("file-report:append:start", file);
1012
1521
  fs.appendFileSync(savedReportPath, tempReport);
1013
1522
  trace7("file-report:append:done", file);
@@ -1020,9 +1529,27 @@ ${result}
1020
1529
  ${command}`);
1021
1530
  trace7("file-test-command:append:done", file);
1022
1531
  }
1532
+ fileExecutionLogs.push({
1533
+ command,
1534
+ errorSummary: null,
1535
+ file,
1536
+ index,
1537
+ resultLength: result.length,
1538
+ status: "success",
1539
+ tempOneFileDiffPath
1540
+ });
1023
1541
  trace7("file-review:end", file);
1024
1542
  } catch (err) {
1025
1543
  trace7("file-review:catch", `${file} | ${getErrorSummary(err)}`);
1544
+ fileExecutionLogs.push({
1545
+ command: command || null,
1546
+ errorSummary: getErrorSummary(err),
1547
+ file,
1548
+ index,
1549
+ resultLength: 0,
1550
+ status: "failed",
1551
+ tempOneFileDiffPath
1552
+ });
1026
1553
  const errorLogPath = writeErrorReport(err, {
1027
1554
  scope: "review-one-by-one:file",
1028
1555
  args: args4,
@@ -1033,6 +1560,7 @@ ${command}`);
1033
1560
  ${JSON.stringify(
1034
1561
  {
1035
1562
  service,
1563
+ selectedCommitSummary: selectedCommitSummary || null,
1036
1564
  file,
1037
1565
  command: command || null,
1038
1566
  tempOneFileDiffPath,
@@ -1073,6 +1601,15 @@ ${getErrorSummary(err)}
1073
1601
  trace7("parallel-review:await-start");
1074
1602
  await Promise.all(promises);
1075
1603
  trace7("parallel-review:await-done");
1604
+ const failedFileCount = fileExecutionLogs.filter((log) => log.status === "failed").length;
1605
+ const successfulFileCount = fileExecutionLogs.filter((log) => log.status === "success").length;
1606
+ if (failedFileCount > 0) {
1607
+ executionStatus = "partial_failure";
1608
+ executionTitle = `\uD30C\uC77C\uBCC4 \uB9AC\uBDF0 \uC911 \uC77C\uBD80\uAC00 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. (\uC131\uACF5 ${successfulFileCount}\uAC74 / \uC2E4\uD328 ${failedFileCount}\uAC74)`;
1609
+ } else {
1610
+ executionStatus = "success";
1611
+ executionTitle = "\uD30C\uC77C\uBCC4 \uB9AC\uBDF0\uAC00 \uC131\uACF5\uC801\uC73C\uB85C \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
1612
+ }
1076
1613
  console.log(`
1077
1614
  \u2705 \uB9AC\uBDF0\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
1078
1615
  console.log(`\u{1F4C4} \uB9AC\uD3EC\uD2B8 \uC800\uC7A5 \uC704\uCE58: ${savedReportPath}`);
@@ -1087,6 +1624,10 @@ ${getErrorSummary(err)}
1087
1624
  } catch (error) {
1088
1625
  trace7("review-flow:catch", getErrorSummary(error));
1089
1626
  let errorReportPath = "";
1627
+ executionStatus = "failed";
1628
+ executionTitle = "\uD30C\uC77C\uBCC4 \uB9AC\uBDF0 \uC2E4\uD589 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
1629
+ executionError = error;
1630
+ exitCode = 1;
1090
1631
  trace7("cleanup-temp-diff:start(catch)");
1091
1632
  try {
1092
1633
  deleteTempDiff();
@@ -1107,6 +1648,7 @@ ${getErrorSummary(err)}
1107
1648
  ${JSON.stringify(
1108
1649
  {
1109
1650
  service: service || null,
1651
+ selectedCommitSummary: selectedCommitSummary || null,
1110
1652
  tempDiffPath,
1111
1653
  savedDiffPath: savedDiffPath || null,
1112
1654
  savedReportPath: savedReportPath || null
@@ -1123,7 +1665,58 @@ ${JSON.stringify(
1123
1665
  if (errorReportPath) {
1124
1666
  console.error(`\u{1F4C4} \uC5D0\uB7EC \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${errorReportPath}`);
1125
1667
  }
1126
- process.exit(1);
1668
+ } finally {
1669
+ executionLogPath = writeExecutionLog({
1670
+ scope: "review-one-by-one",
1671
+ status: executionStatus,
1672
+ title: executionTitle,
1673
+ args: args4,
1674
+ startedAt,
1675
+ error: executionError,
1676
+ extraSections: [
1677
+ {
1678
+ heading: "Execution Context",
1679
+ markdown: `\`\`\`json
1680
+ ${JSON.stringify(
1681
+ {
1682
+ service: service || null,
1683
+ selectedCommitSummary: selectedCommitSummary || null,
1684
+ fileList,
1685
+ tempDiffPath,
1686
+ savedDiffPath: savedDiffPath || null,
1687
+ savedReportPath: savedReportPath || null
1688
+ },
1689
+ null,
1690
+ 2
1691
+ )}
1692
+ \`\`\``
1693
+ },
1694
+ {
1695
+ heading: "Generated Commands",
1696
+ markdown: `\`\`\`json
1697
+ ${JSON.stringify(
1698
+ fileExecutionLogs.slice().sort((left, right) => left.index - right.index).map((log) => ({
1699
+ file: log.file,
1700
+ status: log.status,
1701
+ resultLength: log.resultLength,
1702
+ errorSummary: log.errorSummary,
1703
+ tempOneFileDiffPath: log.tempOneFileDiffPath,
1704
+ command: log.command
1705
+ })),
1706
+ null,
1707
+ 2
1708
+ )}
1709
+ \`\`\``
1710
+ }
1711
+ ]
1712
+ });
1713
+ if (executionLogPath) {
1714
+ const writeLog = executionStatus === "failed" ? console.error : console.log;
1715
+ writeLog(`\u{1F4DD} \uC2E4\uD589 \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${executionLogPath}`);
1716
+ }
1717
+ }
1718
+ if (exitCode !== 0) {
1719
+ process.exit(exitCode);
1127
1720
  }
1128
1721
  }
1129
1722
  main();