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
@@ -18,14 +18,52 @@ var readline__default = /*#__PURE__*/_interopDefault(readline);
18
18
 
19
19
  var __dirname$1 = path__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('review-one-by-one.cjs', document.baseURI).href))));
20
20
  var traceMessages = [];
21
- var rulesPath = path__default.default.resolve(__dirname$1, "../../src/common/rules/review-rules.md");
22
- var namingRulesPath = path__default.default.resolve(__dirname$1, "../../src/common/rules/naming-rule.md");
23
- var codingConventionRulesPath = path__default.default.resolve(__dirname$1, "../../src/common/rules/coding-convention.md");
24
- path__default.default.resolve(__dirname$1, "../../src/common/form/review-form.md");
25
- var reviewFormOneByOnePath = path__default.default.resolve(__dirname$1, "../../src/common/form/review-form-one-by-one.md");
21
+ var GEMINI_CLI_PACKAGE_NAME = "sales-frontend-gemini-cli";
22
+ var cachedPackageRootPath = "";
23
+ function isGeminiCliPackageRoot(directory) {
24
+ const packageJsonPath = path__default.default.join(directory, "package.json");
25
+ if (!fs__default.default.existsSync(packageJsonPath)) {
26
+ return false;
27
+ }
28
+ try {
29
+ const packageJson = JSON.parse(fs__default.default.readFileSync(packageJsonPath, "utf8"));
30
+ return packageJson.name === GEMINI_CLI_PACKAGE_NAME;
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+ function resolveGeminiCliPackageRoot(startDirectory = __dirname$1) {
36
+ if (cachedPackageRootPath) {
37
+ return cachedPackageRootPath;
38
+ }
39
+ let currentDirectory = startDirectory;
40
+ while (true) {
41
+ if (isGeminiCliPackageRoot(currentDirectory)) {
42
+ cachedPackageRootPath = currentDirectory;
43
+ return cachedPackageRootPath;
44
+ }
45
+ const parentDirectory = path__default.default.dirname(currentDirectory);
46
+ if (parentDirectory === currentDirectory) {
47
+ break;
48
+ }
49
+ currentDirectory = parentDirectory;
50
+ }
51
+ cachedPackageRootPath = path__default.default.resolve(startDirectory, "../..");
52
+ return cachedPackageRootPath;
53
+ }
54
+ function resolvePackageAssetPath(relativeFilePath) {
55
+ return path__default.default.resolve(resolveGeminiCliPackageRoot(), relativeFilePath);
56
+ }
57
+ var rulesPath = resolvePackageAssetPath("src/common/rules/review-rules.md");
58
+ var namingRulesPath = resolvePackageAssetPath("src/common/rules/naming-rule.md");
59
+ var codingConventionRulesPath = resolvePackageAssetPath("src/common/rules/coding-convention.md");
60
+ resolvePackageAssetPath("src/common/form/review-form.md");
61
+ var reviewFormOneByOnePath = resolvePackageAssetPath("src/common/form/review-form-one-by-one.md");
26
62
  var REPORT_DIR = ".review-report";
27
63
  var tempDiffPath = "temp_diff.txt";
28
64
  var AIServices = ["gemini", "claude", "codex"];
65
+ var COMMIT_FETCH_LIMIT = 20;
66
+ var COMMIT_SELECTION_WINDOW = 8;
29
67
  var ignoreList = [
30
68
  "package.json",
31
69
  "*.yml",
@@ -49,6 +87,137 @@ function clearTraceMessages() {
49
87
  function getTraceMessages() {
50
88
  return [...traceMessages];
51
89
  }
90
+ var ANSI = {
91
+ bold: "\x1B[1m",
92
+ cyan: "\x1B[36m",
93
+ dim: "\x1B[2m",
94
+ green: "\x1B[32m",
95
+ reset: "\x1B[0m",
96
+ yellow: "\x1B[33m"
97
+ };
98
+ var ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
99
+ var COMBINING_MARK_PATTERN = /\p{Mark}/u;
100
+ var GRAPHEME_SEGMENTER = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter("ko", { granularity: "grapheme" }) : null;
101
+ function getGitDiffPathspecs() {
102
+ return {
103
+ excludePatterns: ignoreList.map((item) => `:(exclude)${item}`),
104
+ includePatterns: ["*.ts", "*.tsx", "*.js", "*.jsx"]
105
+ };
106
+ }
107
+ function segmentGraphemes(value) {
108
+ if (!GRAPHEME_SEGMENTER) {
109
+ return [...value];
110
+ }
111
+ return [...GRAPHEME_SEGMENTER.segment(value)].map(({ segment }) => segment);
112
+ }
113
+ function isWideCodePoint(codePoint) {
114
+ 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);
115
+ }
116
+ function isEmojiCodePoint(codePoint) {
117
+ return codePoint >= 127462 && codePoint <= 127487 || codePoint >= 127744 && codePoint <= 129791 || codePoint >= 9728 && codePoint <= 10175;
118
+ }
119
+ function getGraphemeWidth(grapheme) {
120
+ let width = 0;
121
+ for (const character of grapheme) {
122
+ const codePoint = character.codePointAt(0);
123
+ if (!codePoint || COMBINING_MARK_PATTERN.test(character) || codePoint === 8205) {
124
+ continue;
125
+ }
126
+ if (codePoint >= 65024 && codePoint <= 65039 || codePoint >= 917760 && codePoint <= 917999) {
127
+ continue;
128
+ }
129
+ if (isWideCodePoint(codePoint) || isEmojiCodePoint(codePoint)) {
130
+ width = Math.max(width, 2);
131
+ continue;
132
+ }
133
+ width = Math.max(width, 1);
134
+ }
135
+ return width;
136
+ }
137
+ function tokenizePlainText(value) {
138
+ return segmentGraphemes(value).map((segment) => ({
139
+ value: segment,
140
+ visibleWidth: getGraphemeWidth(segment)
141
+ }));
142
+ }
143
+ function tokenizeVisibleText(value) {
144
+ const tokens = [];
145
+ let lastIndex = 0;
146
+ for (const match of value.matchAll(ANSI_PATTERN)) {
147
+ const index = match.index ?? 0;
148
+ if (index > lastIndex) {
149
+ tokens.push(...tokenizePlainText(value.slice(lastIndex, index)));
150
+ }
151
+ tokens.push({
152
+ value: match[0],
153
+ visibleWidth: 0
154
+ });
155
+ lastIndex = index + match[0].length;
156
+ }
157
+ if (lastIndex < value.length) {
158
+ tokens.push(...tokenizePlainText(value.slice(lastIndex)));
159
+ }
160
+ return tokens;
161
+ }
162
+ function truncateLineForTerminal(value, maxWidth) {
163
+ if (maxWidth <= 0) {
164
+ return "";
165
+ }
166
+ const tokens = tokenizeVisibleText(value);
167
+ const totalWidth = tokens.reduce((sum, token) => sum + token.visibleWidth, 0);
168
+ if (totalWidth <= maxWidth) {
169
+ return value;
170
+ }
171
+ const ellipsis = "...";
172
+ const ellipsisWidth = 3;
173
+ const targetWidth = Math.max(0, maxWidth - ellipsisWidth);
174
+ let usedWidth = 0;
175
+ let result = "";
176
+ for (const token of tokens) {
177
+ if (token.visibleWidth === 0) {
178
+ result += token.value;
179
+ continue;
180
+ }
181
+ if (usedWidth + token.visibleWidth > targetWidth) {
182
+ break;
183
+ }
184
+ result += token.value;
185
+ usedWidth += token.visibleWidth;
186
+ }
187
+ return `${result}${ellipsis}${ANSI.reset}`;
188
+ }
189
+ function fitLinesToTerminal(lines) {
190
+ const maxWidth = Math.max(20, (process.stdout.columns || 120) - 1);
191
+ return lines.map((line) => truncateLineForTerminal(line, maxWidth));
192
+ }
193
+ function runGitCommand(args4, options = {}) {
194
+ const { allowFailure = false, trimOutput = true } = options;
195
+ try {
196
+ const output = child_process.execFileSync("git", args4, {
197
+ encoding: "utf8",
198
+ maxBuffer: 1024 * 1024 * 20,
199
+ stdio: ["ignore", "pipe", "pipe"]
200
+ });
201
+ return trimOutput ? output.trim() : output;
202
+ } catch (error) {
203
+ helperTrace("git-command:failed", `${args4.join(" ")} | ${getErrorSummary(error)}`);
204
+ if (allowFailure) {
205
+ return "";
206
+ }
207
+ throw error;
208
+ }
209
+ }
210
+ function formatReviewTargetFiles(files, visibleCount = 5) {
211
+ if (files.length === 0) {
212
+ return "(\uC5C6\uC74C)";
213
+ }
214
+ const visibleFiles = files.slice(0, visibleCount);
215
+ const hiddenCount = Math.max(0, files.length - visibleFiles.length);
216
+ if (hiddenCount === 0) {
217
+ return visibleFiles.join(", ");
218
+ }
219
+ return `${visibleFiles.join(", ")} \uC678 ${hiddenCount}\uAC1C`;
220
+ }
52
221
  function createTraceLogger(scope, args4 = process.argv.slice(2)) {
53
222
  const enabled = isTestMode(args4);
54
223
  return (step, detail) => {
@@ -110,7 +279,7 @@ function serializeError(error) {
110
279
  }
111
280
  if (error && typeof error === "object") {
112
281
  const errorLike = error;
113
- const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs"];
282
+ const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs", "command"];
114
283
  extraKeys.forEach((key) => {
115
284
  if (errorLike[key] !== void 0) {
116
285
  serialized[key] = errorLike[key];
@@ -216,6 +385,87 @@ ${section.markdown}`).join("\n")}
216
385
  return "";
217
386
  }
218
387
  }
388
+ function getExecutionLogSummary(status, title) {
389
+ if (title) {
390
+ return title;
391
+ }
392
+ switch (status) {
393
+ case "success":
394
+ return "\uB9AC\uBDF0 \uC2E4\uD589\uC774 \uC131\uACF5\uC801\uC73C\uB85C \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
395
+ case "failed":
396
+ return "\uB9AC\uBDF0 \uC2E4\uD589 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
397
+ case "partial_failure":
398
+ 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.";
399
+ default:
400
+ 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.";
401
+ }
402
+ }
403
+ function formatExecutionDuration(startedAt, finishedAt) {
404
+ const durationMs = Math.max(0, finishedAt.getTime() - startedAt.getTime());
405
+ if (durationMs < 1e3) {
406
+ return `${durationMs}ms`;
407
+ }
408
+ const durationSeconds = durationMs / 1e3;
409
+ if (durationSeconds < 60) {
410
+ return `${durationSeconds.toFixed(1)}s`;
411
+ }
412
+ const minutes = Math.floor(durationSeconds / 60);
413
+ const seconds = Math.round(durationSeconds % 60);
414
+ return `${minutes}m ${seconds}s`;
415
+ }
416
+ function writeExecutionLog(options = {}) {
417
+ try {
418
+ const startedAt = options.startedAt ?? /* @__PURE__ */ new Date();
419
+ const finishedAt = options.finishedAt ?? /* @__PURE__ */ new Date();
420
+ const status = options.status ?? "success";
421
+ helperTrace("execution-log:write:start", options.scope || "unknown");
422
+ createReportDirectory();
423
+ const reportPath = getAvailableFilePath(REPORT_DIR, `${getNowString(finishedAt)}-execution-log`, ".md");
424
+ const traceSnapshot = options.traceMessages ?? getTraceMessages();
425
+ const extraSections = options.extraSections || [];
426
+ const serializedError = options.error ? serializeError(options.error) : null;
427
+ const report = `# Execution Log
428
+
429
+ - \uC2DC\uC791 \uC2DC\uAC01: ${getHumanReadableNowString(startedAt)}
430
+ - \uC885\uB8CC \uC2DC\uAC01: ${getHumanReadableNowString(finishedAt)}
431
+ - \uC2E4\uD589 \uC2DC\uAC04: ${formatExecutionDuration(startedAt, finishedAt)}
432
+ - \uC0C1\uD0DC: \`${status}\`
433
+ - Scope: \`${options.scope || "unknown"}\`
434
+ - \uC791\uC5C5 \uACBD\uB85C: \`${process.cwd()}\`
435
+ - \uC2E4\uD589 \uC778\uC790: \`${JSON.stringify(options.args ?? process.argv.slice(2))}\`
436
+ - \uC2E4\uD589 \uD658\uACBD: \`${process.platform} ${process.arch} / Node ${process.version}\`
437
+
438
+ ## Summary
439
+
440
+ ${getExecutionLogSummary(status, options.title)}
441
+ ${serializedError ? `
442
+
443
+ ## Error
444
+
445
+ \`\`\`json
446
+ ${JSON.stringify(serializedError, null, 2)}
447
+ \`\`\`` : ""}
448
+
449
+ ## Trace
450
+
451
+ \`\`\`json
452
+ ${JSON.stringify(traceSnapshot, null, 2)}
453
+ \`\`\`${extraSections.length ? `
454
+ ${extraSections.map((section) => `
455
+ ## ${section.heading}
456
+
457
+ ${section.markdown}`).join("\n")}
458
+ ` : "\n"}
459
+ `;
460
+ fs__default.default.writeFileSync(reportPath, report);
461
+ helperTrace("execution-log:write:done", reportPath);
462
+ return reportPath;
463
+ } catch (writeError) {
464
+ console.error("\u26A0\uFE0F \uC2E4\uD589 \uB85C\uADF8 \uD30C\uC77C \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
465
+ console.error(writeError);
466
+ return "";
467
+ }
468
+ }
219
469
  function exitWithError(message, options = {}) {
220
470
  const reportPath = writeErrorReport(options.error || new Error(message), {
221
471
  ...options,
@@ -260,13 +510,83 @@ ${JSON.stringify(AIServices, null, 2)}
260
510
  }
261
511
  );
262
512
  }
263
- function getGitDiffFilter() {
264
- const includeExtensions = ["*.ts", "*.tsx", "*.js", "*.jsx"];
265
- const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);
266
- const quote = (pattern) => `"${pattern}"`;
267
- const includeParams = includeExtensions.map(quote).join(" ");
268
- const excludeParams = excludePatterns.map(quote).join(" ");
269
- return { includeParams, excludeParams };
513
+ function truncateCommitSubject(subject) {
514
+ if (subject.length <= 72) {
515
+ return subject;
516
+ }
517
+ return `${subject.slice(0, 69)}...`;
518
+ }
519
+ function getRecentCommitOptions() {
520
+ const output = runGitCommand(
521
+ ["log", `-${COMMIT_FETCH_LIMIT}`, "--date=relative", "--pretty=format:%h%x09%an%x09%ar%x09%s"],
522
+ { allowFailure: true }
523
+ );
524
+ if (!output) {
525
+ return [];
526
+ }
527
+ return output.split("\n").map((line) => {
528
+ const [hash = "", author = "", relativeDate = "", ...subjectParts] = line.split(" ");
529
+ const subject = subjectParts.join(" ").trim();
530
+ return {
531
+ author,
532
+ description: `${author} | ${relativeDate}`,
533
+ hash,
534
+ label: `${hash} | ${truncateCommitSubject(subject)}`,
535
+ relativeDate,
536
+ subject
537
+ };
538
+ });
539
+ }
540
+ function buildSelectedCommitSummary(commits) {
541
+ return commits.map((commit) => `- ${commit.hash} | ${commit.subject} | ${commit.author} | ${commit.relativeDate}`).join("\n");
542
+ }
543
+ function getReviewPathspecArgs() {
544
+ const { includePatterns, excludePatterns } = getGitDiffPathspecs();
545
+ return [...includePatterns, ...excludePatterns];
546
+ }
547
+ function buildSelectedCommitDiff(commits) {
548
+ const reviewPathspecArgs = getReviewPathspecArgs();
549
+ const sections = commits.map((commit) => {
550
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", ...reviewPathspecArgs], {
551
+ allowFailure: true,
552
+ trimOutput: false
553
+ }).trim();
554
+ if (!diff) {
555
+ return "";
556
+ }
557
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
558
+ }).filter(Boolean).join("\n\n");
559
+ if (!sections) {
560
+ return "";
561
+ }
562
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", "# \uB9AC\uBDF0 \uB300\uC0C1 diff", sections].join("\n");
563
+ }
564
+ function getSelectedCommitFiles(commits) {
565
+ const reviewPathspecArgs = getReviewPathspecArgs();
566
+ const files = /* @__PURE__ */ new Set();
567
+ commits.forEach((commit) => {
568
+ const output = runGitCommand(["show", "--pretty=format:", "--name-only", commit.hash, "--", ...reviewPathspecArgs], {
569
+ allowFailure: true
570
+ });
571
+ output.split("\n").map((line) => line.trim()).filter(Boolean).forEach((filePath) => files.add(filePath));
572
+ });
573
+ return [...files];
574
+ }
575
+ function buildSelectedFileDiff(commits, filePath) {
576
+ const sections = commits.map((commit) => {
577
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", filePath], {
578
+ allowFailure: true,
579
+ trimOutput: false
580
+ }).trim();
581
+ if (!diff) {
582
+ return "";
583
+ }
584
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
585
+ }).filter(Boolean).join("\n\n");
586
+ if (!sections) {
587
+ return "";
588
+ }
589
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", `# \uD30C\uC77C: ${filePath}`, sections].join("\n\n");
270
590
  }
271
591
  function openReport(reportPath) {
272
592
  const resolvedPath = path__default.default.resolve(reportPath);
@@ -317,59 +637,166 @@ function openReport(reportPath) {
317
637
  helperTrace("open-report:unsupported-platform", platform);
318
638
  console.error(`\u26A0\uFE0F \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD50C\uB7AB\uD3FC\uC785\uB2C8\uB2E4: ${platform}`);
319
639
  }
320
- function getDiffArgs() {
321
- const args4 = process.argv.slice(2);
322
- const commitIndex = args4.indexOf("--commit");
323
- const { includeParams, excludeParams } = getGitDiffFilter();
324
- helperTrace("diff-args:resolve:start", `args=${JSON.stringify(args4)}`);
325
- let diffArgs = "";
326
- if (commitIndex !== -1) {
327
- const commitHash = args4[commitIndex + 1];
328
- if (!commitHash) {
329
- helperTrace("diff-args:commit-hash-missing");
330
- exitWithError("\u274C \uCEE4\uBC0B \uD574\uC2DC\uAC00 \uC81C\uACF5\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.", {
331
- scope: "helper:getDiffArgs",
332
- args: args4
333
- });
640
+ function ensureInteractiveSelectionAvailable(scope, message) {
641
+ if (process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function") {
642
+ return;
643
+ }
644
+ helperTrace(`${scope}:tty-missing`);
645
+ exitWithError(message, {
646
+ scope: `helper:${scope}`
647
+ });
648
+ }
649
+ function renderSelectionBlock(lines, previousLineCount) {
650
+ if (previousLineCount > 0) {
651
+ readline__default.default.moveCursor(process.stdout, 0, -previousLineCount);
652
+ readline__default.default.clearScreenDown(process.stdout);
653
+ }
654
+ const fittedLines = fitLinesToTerminal(lines);
655
+ process.stdout.write(`${fittedLines.join("\n")}
656
+ `);
657
+ return fittedLines.length;
658
+ }
659
+ function getSelectionWindowRange(optionCount, selectedIndex, windowSize) {
660
+ if (optionCount <= windowSize) {
661
+ return {
662
+ end: optionCount,
663
+ start: 0
664
+ };
665
+ }
666
+ const halfWindow = Math.floor(windowSize / 2);
667
+ const maxStart = optionCount - windowSize;
668
+ const start = Math.max(0, Math.min(selectedIndex - halfWindow, maxStart));
669
+ return {
670
+ end: Math.min(optionCount, start + windowSize),
671
+ start
672
+ };
673
+ }
674
+ function buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize) {
675
+ const { start, end } = getSelectionWindowRange(options.length, selectedIndex, windowSize);
676
+ const lines = [
677
+ `${ANSI.bold}${question}${ANSI.reset}`,
678
+ `${ANSI.dim}\u2191\u2193 \uC774\uB3D9 | Space \uC120\uD0DD/\uD574\uC81C | Enter \uC644\uB8CC | Esc \uCDE8\uC18C${ANSI.reset}`,
679
+ `${ANSI.dim}\uC120\uD0DD\uB428: ${toggled.size}\uAC1C / \uC804\uCCB4: ${options.length}\uAC1C${ANSI.reset}`
680
+ ];
681
+ for (let index = start; index < end; index += 1) {
682
+ const option = options[index];
683
+ if (!option) {
684
+ continue;
334
685
  }
335
- const nextArg = args4[commitIndex + 2];
336
- let n = 0;
337
- if (nextArg && !nextArg.startsWith("--")) {
338
- n = parseInt(nextArg, 10);
339
- if (isNaN(n)) {
340
- n = 0;
341
- }
686
+ const cursor = index === selectedIndex ? `${ANSI.cyan}>${ANSI.reset}` : " ";
687
+ const checked = toggled.has(index) ? `${ANSI.green}\u2611${ANSI.reset}` : "\u2610";
688
+ const description = option.description ? ` ${ANSI.dim}${option.description}${ANSI.reset}` : "";
689
+ lines.push(`${cursor} ${checked} ${option.label}${description}`);
690
+ }
691
+ if (options.length > windowSize) {
692
+ lines.push(`${ANSI.dim}\uD45C\uC2DC \uBC94\uC704: ${start + 1}-${end} / ${options.length}${ANSI.reset}`);
693
+ }
694
+ return lines;
695
+ }
696
+ async function showMultiSelect(question, options, windowSize = COMMIT_SELECTION_WINDOW) {
697
+ ensureInteractiveSelectionAvailable("showMultiSelect", "\u274C \uCEE4\uBC0B \uC120\uD0DD \uBAA8\uB2EC\uC740 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
698
+ let selectedIndex = 0;
699
+ let renderedLineCount = 0;
700
+ const toggled = /* @__PURE__ */ new Set();
701
+ const rl = readline__default.default.createInterface({
702
+ input: process.stdin,
703
+ output: process.stdout,
704
+ terminal: true
705
+ });
706
+ process.stdout.write("\x1B[?25l");
707
+ const cleanup = () => {
708
+ if (renderedLineCount > 0) {
709
+ readline__default.default.moveCursor(process.stdout, 0, -renderedLineCount);
710
+ readline__default.default.clearScreenDown(process.stdout);
711
+ renderedLineCount = 0;
342
712
  }
343
- helperTrace("diff-args:commit-mode", `${commitHash}~${n + 1} ${commitHash}`);
344
- console.log(`\u2139\uFE0F \uCEE4\uBC0B '${commitHash}' ${n > 0 ? ` \uD3EC\uD568 \uCD1D ${n + 1}\uAC1C\uC758 \uCEE4\uBC0B` : ""}\uC744 \uB9AC\uBDF0\uD569\uB2C8\uB2E4...`);
345
- diffArgs = `${commitHash}~${n + 1} ${commitHash}`;
346
- } else {
347
- try {
348
- helperTrace("diff-args:unstaged-check:start");
349
- const check = child_process.execSync(`git diff --name-only -- ${includeParams} ${excludeParams}`).toString();
350
- if (!check.trim()) {
351
- helperTrace("diff-args:unstaged-check:empty", "use HEAD~1 HEAD");
352
- 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...");
353
- diffArgs = "HEAD~1 HEAD";
354
- } else {
355
- helperTrace("diff-args:unstaged-check:has-changes", `length=${check.length}`);
713
+ process.stdin.removeListener("data", onData);
714
+ process.stdin.setRawMode(false);
715
+ process.stdin.pause();
716
+ rl.close();
717
+ process.stdout.write("\x1B[?25h");
718
+ };
719
+ const render = () => {
720
+ const lines = buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize);
721
+ renderedLineCount = renderSelectionBlock(lines, renderedLineCount);
722
+ };
723
+ const confirmSelection = (resolve) => {
724
+ const values = [...toggled].sort((left, right) => left - right).map((index) => options[index]?.value).filter((value) => value !== void 0);
725
+ cleanup();
726
+ resolve(values);
727
+ };
728
+ const cancelSelection = (resolve) => {
729
+ cleanup();
730
+ resolve([]);
731
+ };
732
+ let onData = (_data) => {
733
+ };
734
+ render();
735
+ return new Promise((resolve) => {
736
+ onData = (data) => {
737
+ const key = data.toString();
738
+ if (key === "") {
739
+ cleanup();
740
+ process.exit(0);
356
741
  }
357
- } catch (error) {
358
- helperTrace("diff-args:unstaged-check:failed", getErrorSummary(error));
359
- }
742
+ if (key === "\x1B") {
743
+ cancelSelection(resolve);
744
+ return;
745
+ }
746
+ if (key === "\x1B[A") {
747
+ selectedIndex = (selectedIndex - 1 + options.length) % options.length;
748
+ render();
749
+ return;
750
+ }
751
+ if (key === "\x1B[B") {
752
+ selectedIndex = (selectedIndex + 1) % options.length;
753
+ render();
754
+ return;
755
+ }
756
+ if (key === " ") {
757
+ if (toggled.has(selectedIndex)) {
758
+ toggled.delete(selectedIndex);
759
+ } else {
760
+ toggled.add(selectedIndex);
761
+ }
762
+ render();
763
+ return;
764
+ }
765
+ if (key === "\r" || key === "\n") {
766
+ confirmSelection(resolve);
767
+ }
768
+ };
769
+ process.stdin.setRawMode(true);
770
+ process.stdin.resume();
771
+ process.stdin.on("data", onData);
772
+ });
773
+ }
774
+ async function selectReviewCommits() {
775
+ const commits = getRecentCommitOptions();
776
+ if (commits.length === 0) {
777
+ console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uCD5C\uADFC \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
778
+ return [];
360
779
  }
361
- helperTrace("diff-args:resolve:done", diffArgs || "(default)");
362
- return diffArgs;
780
+ return showMultiSelect(
781
+ "\uB9AC\uBDF0\uD560 \uCEE4\uBC0B\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.",
782
+ commits.map((commit) => ({
783
+ description: commit.description,
784
+ label: commit.label,
785
+ value: commit
786
+ })),
787
+ COMMIT_SELECTION_WINDOW
788
+ );
363
789
  }
364
790
  async function showSelectionAIService() {
365
791
  const selectedServiceFromArgs = parseServiceFromArgs();
366
792
  if (selectedServiceFromArgs) {
367
793
  helperTrace("show-selection:from-args", selectedServiceFromArgs);
368
794
  console.log(`
369
- \u2705 \x1B[32m${selectedServiceFromArgs}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
795
+ \u2705 ${ANSI.green}${selectedServiceFromArgs}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
370
796
  `);
371
797
  return selectedServiceFromArgs;
372
798
  }
799
+ 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.");
373
800
  helperTrace("show-selection:interactive:start");
374
801
  let selectedIndex = 0;
375
802
  const rl = readline__default.default.createInterface({
@@ -387,11 +814,12 @@ async function showSelectionAIService() {
387
814
  helperTrace("show-selection:interactive:render", AIServices[selectedIndex] || "unknown");
388
815
  readline__default.default.clearScreenDown(process.stdout);
389
816
  process.stdout.write(
390
- "\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"
817
+ `\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):
818
+ `
391
819
  );
392
820
  AIServices.forEach((service, index) => {
393
821
  if (index === selectedIndex) {
394
- process.stdout.write(` \x1B[36m>\x1B[0m \x1B[36m\u25C9\x1B[0m \x1B[1m${service}\x1B[0m
822
+ process.stdout.write(` ${ANSI.cyan}>${ANSI.reset} ${ANSI.cyan}\u25C9${ANSI.reset} ${ANSI.bold}${service}${ANSI.reset}
395
823
  `);
396
824
  } else {
397
825
  process.stdout.write(` \u25EF ${service}
@@ -421,7 +849,7 @@ async function showSelectionAIService() {
421
849
  rl.close();
422
850
  process.stdout.write("\x1B[?25h");
423
851
  console.log(`
424
- \u2705 \x1B[32m${AIServices[selectedIndex]}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
852
+ \u2705 ${ANSI.green}${AIServices[selectedIndex]}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
425
853
  `);
426
854
  const result = AIServices[selectedIndex];
427
855
  if (result) {
@@ -441,6 +869,13 @@ var ALLOWED_REASONING_EFFORTS = ["minimal", "low", "medium", "high"];
441
869
  function shellQuote(value) {
442
870
  return `'${value.replace(/'/g, `'\\''`)}'`;
443
871
  }
872
+ function toShellOptionToken(value) {
873
+ const SIMPLE_SHELL_TOKEN_PATTERN = /^[A-Za-z0-9._:/=-]+$/;
874
+ if (SIMPLE_SHELL_TOKEN_PATTERN.test(value)) {
875
+ return value;
876
+ }
877
+ return shellQuote(value);
878
+ }
444
879
  function getArgValue(flag) {
445
880
  const index = args.indexOf(flag);
446
881
  if (index === -1 || !args[index + 1]) {
@@ -448,6 +883,11 @@ function getArgValue(flag) {
448
883
  }
449
884
  return args[index + 1];
450
885
  }
886
+ function printNotice(message) {
887
+ if (args.includes("--test")) {
888
+ console.warn(message);
889
+ }
890
+ }
451
891
  function toUnique(values) {
452
892
  const seen = /* @__PURE__ */ new Set();
453
893
  return values.filter((value) => {
@@ -471,11 +911,11 @@ function resolveReasoningEffort() {
471
911
  const normalized = normalizeEffort(customReasoningEffort);
472
912
  trace("reasoning:custom", `${customReasoningEffort} -> ${normalized}`);
473
913
  if (customReasoningEffort === "minimal") {
474
- console.warn("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
914
+ printNotice("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
475
915
  }
476
916
  return normalized;
477
917
  }
478
- console.warn(
918
+ printNotice(
479
919
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS.join(
480
920
  ", "
481
921
  )}`
@@ -515,10 +955,10 @@ function getAliasFallbacks(primaryAlias) {
515
955
  }
516
956
  function buildClaudeExecCommand(options) {
517
957
  const { tempDiffPath: tempDiffPath2, prompt, systemPromptFiles, effort, model, fallbackModel } = options;
518
- const modelOption = model ? `--model ${shellQuote(model)}` : "";
519
- const fallbackOption = model && fallbackModel ? `--fallback-model ${shellQuote(fallbackModel)}` : "";
520
- const effortOption = `--effort ${shellQuote(effort)}`;
521
- const appendedPromptFiles = systemPromptFiles.map((path2) => `--append-system-prompt-file ${shellQuote(path2)}`).join(" ");
958
+ const modelOption = model ? `--model ${toShellOptionToken(model)}` : "";
959
+ const fallbackOption = model && fallbackModel ? `--fallback-model ${toShellOptionToken(fallbackModel)}` : "";
960
+ const effortOption = `--effort ${toShellOptionToken(effort)}`;
961
+ const appendedPromptFiles = systemPromptFiles.map((path3) => `--append-system-prompt-file ${shellQuote(path3)}`).join(" ");
522
962
  return `cat ${shellQuote(tempDiffPath2)} | claude ${[
523
963
  modelOption,
524
964
  fallbackOption,
@@ -551,13 +991,13 @@ var createClaudeCommand = (tempDiffPath2, reviewFormPath2) => {
551
991
  trace("model:candidates", modelCandidates.join(", "));
552
992
  trace("command:candidates:count", String(modelCandidates.length + 1));
553
993
  if (customModel) {
554
- console.warn(
994
+ printNotice(
555
995
  `\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
556
996
  " -> "
557
997
  )}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
558
998
  );
559
999
  } else {
560
- console.warn(
1000
+ printNotice(
561
1001
  `\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.`
562
1002
  );
563
1003
  }
@@ -637,22 +1077,37 @@ function getArgValue2(flag) {
637
1077
  }
638
1078
  return args2[index + 1];
639
1079
  }
1080
+ function printNotice2(message) {
1081
+ if (args2.includes("--test")) {
1082
+ console.warn(message);
1083
+ }
1084
+ }
1085
+ function normalizeEffort2(level) {
1086
+ if (level === "minimal") {
1087
+ return "low";
1088
+ }
1089
+ return level;
1090
+ }
640
1091
  function resolveReasoningEffort2() {
641
1092
  const customReasoningEffort = getArgValue2("--reasoning-effort");
642
1093
  if (customReasoningEffort) {
643
1094
  if (ALLOWED_REASONING_EFFORTS2.includes(customReasoningEffort)) {
644
- trace3("reasoning:custom", customReasoningEffort);
645
- return customReasoningEffort;
1095
+ const normalized = normalizeEffort2(customReasoningEffort);
1096
+ trace3("reasoning:custom", `${customReasoningEffort} -> ${normalized}`);
1097
+ if (customReasoningEffort === "minimal") {
1098
+ 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.");
1099
+ }
1100
+ return normalized;
646
1101
  }
647
- console.warn(
1102
+ printNotice2(
648
1103
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS2.join(
649
1104
  ", "
650
1105
  )}`
651
1106
  );
652
1107
  }
653
1108
  if (args2.includes("--flash")) {
654
- trace3("reasoning:flash-default", "minimal");
655
- return "minimal";
1109
+ trace3("reasoning:flash-default", "low");
1110
+ return "low";
656
1111
  }
657
1112
  if (args2.includes("--review")) {
658
1113
  trace3("reasoning:review-default", "high");
@@ -688,14 +1143,14 @@ ${reviewFormLine || "- (\uC5C6\uC74C)"}
688
1143
  trace3("prompt:prepared", `length=${prompt.length}`);
689
1144
  let command = "";
690
1145
  if (customModel) {
691
- 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.");
1146
+ 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.");
692
1147
  trace3("model:custom", customModel);
693
1148
  command = buildCodexExecCommand(prompt, reasoningEffort, customModel);
694
1149
  } else {
695
1150
  const preferredModelAlias = "gpt-5";
696
1151
  const aliasCommand = buildCodexExecCommand(prompt, reasoningEffort, preferredModelAlias);
697
1152
  const fallbackCommand = buildCodexExecCommand(prompt, reasoningEffort);
698
- console.warn(
1153
+ printNotice2(
699
1154
  `\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.`
700
1155
  );
701
1156
  trace3("model:alias-first", preferredModelAlias);
@@ -756,6 +1211,11 @@ function getArgValue3(flag) {
756
1211
  }
757
1212
  return args3[index + 1];
758
1213
  }
1214
+ function printNotice3(message) {
1215
+ if (args3.includes("--test")) {
1216
+ console.warn(message);
1217
+ }
1218
+ }
759
1219
  function toUnique2(values) {
760
1220
  const seen = /* @__PURE__ */ new Set();
761
1221
  return values.filter((value) => {
@@ -773,7 +1233,7 @@ function resolveReasoningEffort3() {
773
1233
  trace5("reasoning:custom", customReasoningEffort);
774
1234
  return customReasoningEffort;
775
1235
  }
776
- console.warn(
1236
+ printNotice3(
777
1237
  `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS3.join(
778
1238
  ", "
779
1239
  )}`
@@ -835,38 +1295,57 @@ function buildGeminiExecCommand(prompt, model) {
835
1295
  const modelOption = model ? `--model ${shellQuote3(model)}` : "";
836
1296
  return `gemini ${[modelOption, "-p", shellQuote3(prompt)].filter(Boolean).join(" ")}`;
837
1297
  }
1298
+ function toGeminiFileReference(filePath) {
1299
+ return `@${filePath}`;
1300
+ }
1301
+ function buildGeminiFileReferenceSection(files) {
1302
+ const existingFiles = files.filter((file) => fs__default.default.existsSync(file.path));
1303
+ return {
1304
+ count: existingFiles.length,
1305
+ items: existingFiles.map((file) => `${file.display} ${toGeminiFileReference(file.path)}`)
1306
+ };
1307
+ }
838
1308
  var createGeminiCommand = (tempDiffPath2, reviewFormPath2) => {
839
1309
  trace5("createGeminiCommand:start", `tempDiffPath=${tempDiffPath2}, reviewFormPath=${reviewFormPath2}`);
840
1310
  const customModel = getArgValue3("--model");
841
1311
  const reasoningEffort = resolveReasoningEffort3();
842
1312
  const primaryAlias = resolvePrimaryAlias2(reasoningEffort);
843
1313
  const aliasFallbacks = toUnique2(getAliasFallbacks2(primaryAlias));
1314
+ const resolvedTempDiffPath = path__default.default.resolve(tempDiffPath2);
1315
+ const resolvedReviewFormPath = path__default.default.resolve(reviewFormPath2);
844
1316
  const rules = [
845
1317
  { path: rulesPath, display: "\uB8F0\uC14B" },
846
1318
  { path: namingRulesPath, display: "\uB124\uC774\uBC0D \uADDC\uCE59" },
847
1319
  { path: codingConventionRulesPath, display: "\uCF54\uB529 \uCEE8\uBCA4\uC158" }
848
1320
  ];
849
- const validRules = rules.filter((rule) => fs__default.default.existsSync(rule.path)).map((rule) => `@${rule.path}`).join(", ");
850
- const rulesCount = validRules ? validRules.split(",").length : 0;
851
- trace5("rules:loaded", `count=${rulesCount}`);
852
- const reviewFormRef = fs__default.default.existsSync(reviewFormPath2) ? `@${reviewFormPath2}` : "(\uC5C6\uC74C)";
853
- trace5("reviewForm:status", reviewFormRef === "(\uC5C6\uC74C)" ? "missing" : "exists");
1321
+ const ruleSection = buildGeminiFileReferenceSection(rules);
1322
+ trace5("rules:loaded", `count=${ruleSection.count}`);
1323
+ const reviewFormExists = fs__default.default.existsSync(resolvedReviewFormPath);
1324
+ const reviewFormText = reviewFormExists ? `\uB9AC\uBDF0 \uC591\uC2DD ${toGeminiFileReference(resolvedReviewFormPath)}` : "\uB9AC\uBDF0 \uC591\uC2DD (\uC5C6\uC74C)";
1325
+ trace5("reviewForm:status", reviewFormExists ? "exists" : "missing");
854
1326
  const reasoningInstruction = getReasoningInstruction(reasoningEffort);
855
- const prompt = `\uB2E4\uC74C \uADDC\uCE59\uB4E4\uC744 \uCC38\uACE0\uD574\uC11C(${validRules || "(\uC5C6\uC74C)"}) \uC774 diff(@${tempDiffPath2})\uB97C \uB9AC\uBDF0\uD574\uC918.
856
- \uB9AC\uBDF0 \uC591\uC2DD\uC740 ${reviewFormRef} \uC5D0 \uB9DE\uCDB0\uC11C \uC791\uC131\uD574\uC918.
857
- \uCD94\uB860 \uAC15\uB3C4 \uC9C0\uCE68: ${reasoningInstruction}`;
1327
+ const ruleText = ruleSection.items.join(" ") || "(\uC5C6\uC74C)";
1328
+ const diffText = `\uB9AC\uBDF0 \uB300\uC0C1 diff ${toGeminiFileReference(resolvedTempDiffPath)}`;
1329
+ const prompt = [
1330
+ "\uC544\uB798 \uD30C\uC77C\uB4E4\uC744 \uC21C\uC11C\uB300\uB85C \uC77D\uACE0 \uCF54\uB4DC \uB9AC\uBDF0\uB97C \uC9C4\uD589\uD574\uC918.",
1331
+ `\uADDC\uCE59 \uD30C\uC77C: ${ruleText}`,
1332
+ `\uB9AC\uBDF0 \uC591\uC2DD \uD30C\uC77C: ${reviewFormText}`,
1333
+ `\uB9AC\uBDF0 \uB300\uC0C1 diff \uD30C\uC77C: ${diffText}`,
1334
+ "\uBC18\uB4DC\uC2DC \uB9AC\uBDF0 \uC591\uC2DD\uC5D0 \uB9DE\uCDB0 \uC791\uC131\uD574\uC918.",
1335
+ `\uCD94\uB860 \uAC15\uB3C4 \uC9C0\uCE68: ${reasoningInstruction}`
1336
+ ].join(" ");
858
1337
  trace5("prompt:prepared", `length=${prompt.length}`);
859
1338
  const modelCandidates = toUnique2(customModel ? [customModel, ...aliasFallbacks] : aliasFallbacks);
860
1339
  trace5("model:candidates", modelCandidates.join(", "));
861
1340
  trace5("command:candidates:count", String(modelCandidates.length + 1));
862
1341
  if (customModel) {
863
- console.warn(
1342
+ printNotice3(
864
1343
  `\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
865
1344
  " -> "
866
1345
  )}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
867
1346
  );
868
1347
  } else {
869
- console.warn(
1348
+ printNotice3(
870
1349
  `\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.`
871
1350
  );
872
1351
  }
@@ -918,6 +1397,7 @@ function checkGeminiCliInstalled() {
918
1397
  var execAsync = util__default.default.promisify(child_process.exec);
919
1398
  async function main() {
920
1399
  const args4 = process.argv.slice(2);
1400
+ const startedAt = /* @__PURE__ */ new Date();
921
1401
  clearTraceMessages();
922
1402
  const isTest = isTestMode(args4);
923
1403
  const trace7 = createTraceLogger("review-one-by-one", args4);
@@ -925,6 +1405,14 @@ async function main() {
925
1405
  let service = "";
926
1406
  let savedDiffPath = "";
927
1407
  let savedReportPath = "";
1408
+ let selectedCommitSummary = "";
1409
+ let fileList = [];
1410
+ let executionLogPath = "";
1411
+ let executionStatus = "cancelled";
1412
+ 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.";
1413
+ let executionError = null;
1414
+ let exitCode = 0;
1415
+ const fileExecutionLogs = [];
928
1416
  try {
929
1417
  trace7("service-selection:start");
930
1418
  service = await showSelectionAIService();
@@ -948,29 +1436,37 @@ async function main() {
948
1436
  }
949
1437
  trace7("review-flow:start");
950
1438
  console.log("\u{1F680} AI Code Review\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4...");
951
- trace7("report-dir:create:start");
952
- createReportDirectory();
953
- trace7("report-dir:create:done");
954
- const { includeParams, excludeParams } = getGitDiffFilter();
955
- trace7("diff-filter:loaded", `include=${includeParams} | exclude=${excludeParams}`);
956
- trace7("diff-args:build:start");
957
- const diffArgs = getDiffArgs();
958
- trace7("diff-args:build:done", `diffArgs=${diffArgs || "(default)"}`);
959
- const filesCommand = `git diff --name-only ${diffArgs} -- ${includeParams} ${excludeParams}`;
960
- trace7("files-command:run", filesCommand);
961
- const fileList = child_process.execSync(filesCommand).toString().split("\n").filter(Boolean);
1439
+ trace7("commit-selection:start");
1440
+ const selectedCommits = await selectReviewCommits();
1441
+ trace7("commit-selection:done", `count=${selectedCommits.length}`);
1442
+ if (selectedCommits.length === 0) {
1443
+ trace7("commit-selection:empty");
1444
+ executionTitle = "\uC120\uD0DD\uB41C \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.";
1445
+ console.log("\u2139\uFE0F \uC120\uD0DD\uB41C \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1446
+ deleteTempDiff();
1447
+ return;
1448
+ }
1449
+ selectedCommitSummary = buildSelectedCommitSummary(selectedCommits);
1450
+ trace7("commit-summary:prepared", selectedCommitSummary);
1451
+ console.log("\u23F3 \uC120\uD0DD\uD55C \uCEE4\uBC0B\uC758 \uD30C\uC77C \uBAA9\uB85D\uACFC diff\uB97C \uC815\uB9AC\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...");
1452
+ trace7("files-command:run");
1453
+ fileList = getSelectedCommitFiles(selectedCommits);
962
1454
  trace7("files-command:done", `fileCount=${fileList.length}`);
1455
+ console.log(`\u{1F4C2} \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C(${fileList.length}\uAC1C): ${formatReviewTargetFiles(fileList)}`);
963
1456
  if (fileList.length === 0) {
964
1457
  trace7("empty-file-list:exit");
965
- console.log("\u2139\uFE0F \uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
1458
+ executionTitle = "\uC120\uD0DD\uD55C \uCEE4\uBC0B\uC5D0\uC11C \uB9AC\uBDF0 \uB300\uC0C1 \uD30C\uC77C \uBCC0\uACBD\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.";
1459
+ 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.");
966
1460
  deleteTempDiff();
967
- process.exit(0);
1461
+ return;
968
1462
  }
969
- const fullDiffCommand = `git diff ${diffArgs} -- ${includeParams} ${excludeParams}`;
970
- trace7("full-diff:run");
971
- const fullDiff = child_process.execSync(fullDiffCommand).toString();
1463
+ trace7("report-dir:create:start");
1464
+ createReportDirectory();
1465
+ trace7("report-dir:create:done");
1466
+ trace7("full-diff:build:start");
1467
+ const fullDiff = buildSelectedCommitDiff(selectedCommits);
972
1468
  const nowStr = getNowString();
973
- trace7("full-diff:done", `length=${fullDiff.length}`);
1469
+ trace7("full-diff:build:done", `length=${fullDiff.length}`);
974
1470
  trace7("timestamp:created", nowStr);
975
1471
  trace7("temp-diff:write:start", tempDiffPath);
976
1472
  fs__default.default.writeFileSync(tempDiffPath, fullDiff);
@@ -981,15 +1477,28 @@ async function main() {
981
1477
  trace7("saved-diff:copy:done", savedDiffPath);
982
1478
  savedReportPath = getNextFilePath(REPORT_DIR, nowStr, ".md");
983
1479
  trace7("saved-report:path", savedReportPath);
984
- const promises = fileList.map(async (file) => {
1480
+ const promises = fileList.map(async (file, index) => {
985
1481
  const tempOneFileDiffPath = `temp_diff_${file.replace(/\//g, "_")}.txt`;
986
1482
  let command = "";
987
1483
  try {
988
1484
  trace7("file-review:start", file);
989
- console.log(`\u{1F50D} Reviewing: ${file}...`);
990
- trace7("file-diff:run", file);
991
- const fileDiff = child_process.execSync(`git diff ${diffArgs} -- "${file}"`).toString();
992
- trace7("file-diff:done", `${file} | length=${fileDiff.length}`);
1485
+ console.log(`\u{1F50D} [${index + 1}/${fileList.length}] \uB9AC\uBDF0 \uC900\uBE44: ${file}`);
1486
+ trace7("file-diff:build:start", file);
1487
+ const fileDiff = buildSelectedFileDiff(selectedCommits, file);
1488
+ trace7("file-diff:build:done", `${file} | length=${fileDiff.length}`);
1489
+ if (!fileDiff.trim()) {
1490
+ trace7("file-diff:empty", file);
1491
+ fileExecutionLogs.push({
1492
+ command: null,
1493
+ errorSummary: null,
1494
+ file,
1495
+ index,
1496
+ resultLength: 0,
1497
+ status: "skipped",
1498
+ tempOneFileDiffPath
1499
+ });
1500
+ return;
1501
+ }
993
1502
  trace7("file-temp-diff:write:start", tempOneFileDiffPath);
994
1503
  fs__default.default.writeFileSync(tempOneFileDiffPath, fileDiff);
995
1504
  trace7("file-temp-diff:write:done", tempOneFileDiffPath);
@@ -1006,6 +1515,7 @@ async function main() {
1006
1515
  break;
1007
1516
  }
1008
1517
  trace7("file-command:create:done", `${file} | commandLength=${command.length}`);
1518
+ console.log(`\u23F3 [${index + 1}/${fileList.length}] \uB9AC\uBDF0 \uC9C4\uD589: ${file}`);
1009
1519
  trace7("file-command:exec:start", file);
1010
1520
  const { stdout } = await execAsync(command, { maxBuffer: 1024 * 1024 * 20 });
1011
1521
  const result = stdout.toString();
@@ -1017,7 +1527,6 @@ async function main() {
1017
1527
  ${result}
1018
1528
 
1019
1529
  `;
1020
- console.log(tempReport);
1021
1530
  trace7("file-report:append:start", file);
1022
1531
  fs__default.default.appendFileSync(savedReportPath, tempReport);
1023
1532
  trace7("file-report:append:done", file);
@@ -1030,9 +1539,27 @@ ${result}
1030
1539
  ${command}`);
1031
1540
  trace7("file-test-command:append:done", file);
1032
1541
  }
1542
+ fileExecutionLogs.push({
1543
+ command,
1544
+ errorSummary: null,
1545
+ file,
1546
+ index,
1547
+ resultLength: result.length,
1548
+ status: "success",
1549
+ tempOneFileDiffPath
1550
+ });
1033
1551
  trace7("file-review:end", file);
1034
1552
  } catch (err) {
1035
1553
  trace7("file-review:catch", `${file} | ${getErrorSummary(err)}`);
1554
+ fileExecutionLogs.push({
1555
+ command: command || null,
1556
+ errorSummary: getErrorSummary(err),
1557
+ file,
1558
+ index,
1559
+ resultLength: 0,
1560
+ status: "failed",
1561
+ tempOneFileDiffPath
1562
+ });
1036
1563
  const errorLogPath = writeErrorReport(err, {
1037
1564
  scope: "review-one-by-one:file",
1038
1565
  args: args4,
@@ -1043,6 +1570,7 @@ ${command}`);
1043
1570
  ${JSON.stringify(
1044
1571
  {
1045
1572
  service,
1573
+ selectedCommitSummary: selectedCommitSummary || null,
1046
1574
  file,
1047
1575
  command: command || null,
1048
1576
  tempOneFileDiffPath,
@@ -1083,6 +1611,15 @@ ${getErrorSummary(err)}
1083
1611
  trace7("parallel-review:await-start");
1084
1612
  await Promise.all(promises);
1085
1613
  trace7("parallel-review:await-done");
1614
+ const failedFileCount = fileExecutionLogs.filter((log) => log.status === "failed").length;
1615
+ const successfulFileCount = fileExecutionLogs.filter((log) => log.status === "success").length;
1616
+ if (failedFileCount > 0) {
1617
+ executionStatus = "partial_failure";
1618
+ executionTitle = `\uD30C\uC77C\uBCC4 \uB9AC\uBDF0 \uC911 \uC77C\uBD80\uAC00 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. (\uC131\uACF5 ${successfulFileCount}\uAC74 / \uC2E4\uD328 ${failedFileCount}\uAC74)`;
1619
+ } else {
1620
+ executionStatus = "success";
1621
+ executionTitle = "\uD30C\uC77C\uBCC4 \uB9AC\uBDF0\uAC00 \uC131\uACF5\uC801\uC73C\uB85C \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
1622
+ }
1086
1623
  console.log(`
1087
1624
  \u2705 \uB9AC\uBDF0\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
1088
1625
  console.log(`\u{1F4C4} \uB9AC\uD3EC\uD2B8 \uC800\uC7A5 \uC704\uCE58: ${savedReportPath}`);
@@ -1097,6 +1634,10 @@ ${getErrorSummary(err)}
1097
1634
  } catch (error) {
1098
1635
  trace7("review-flow:catch", getErrorSummary(error));
1099
1636
  let errorReportPath = "";
1637
+ executionStatus = "failed";
1638
+ executionTitle = "\uD30C\uC77C\uBCC4 \uB9AC\uBDF0 \uC2E4\uD589 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
1639
+ executionError = error;
1640
+ exitCode = 1;
1100
1641
  trace7("cleanup-temp-diff:start(catch)");
1101
1642
  try {
1102
1643
  deleteTempDiff();
@@ -1117,6 +1658,7 @@ ${getErrorSummary(err)}
1117
1658
  ${JSON.stringify(
1118
1659
  {
1119
1660
  service: service || null,
1661
+ selectedCommitSummary: selectedCommitSummary || null,
1120
1662
  tempDiffPath,
1121
1663
  savedDiffPath: savedDiffPath || null,
1122
1664
  savedReportPath: savedReportPath || null
@@ -1133,7 +1675,58 @@ ${JSON.stringify(
1133
1675
  if (errorReportPath) {
1134
1676
  console.error(`\u{1F4C4} \uC5D0\uB7EC \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${errorReportPath}`);
1135
1677
  }
1136
- process.exit(1);
1678
+ } finally {
1679
+ executionLogPath = writeExecutionLog({
1680
+ scope: "review-one-by-one",
1681
+ status: executionStatus,
1682
+ title: executionTitle,
1683
+ args: args4,
1684
+ startedAt,
1685
+ error: executionError,
1686
+ extraSections: [
1687
+ {
1688
+ heading: "Execution Context",
1689
+ markdown: `\`\`\`json
1690
+ ${JSON.stringify(
1691
+ {
1692
+ service: service || null,
1693
+ selectedCommitSummary: selectedCommitSummary || null,
1694
+ fileList,
1695
+ tempDiffPath,
1696
+ savedDiffPath: savedDiffPath || null,
1697
+ savedReportPath: savedReportPath || null
1698
+ },
1699
+ null,
1700
+ 2
1701
+ )}
1702
+ \`\`\``
1703
+ },
1704
+ {
1705
+ heading: "Generated Commands",
1706
+ markdown: `\`\`\`json
1707
+ ${JSON.stringify(
1708
+ fileExecutionLogs.slice().sort((left, right) => left.index - right.index).map((log) => ({
1709
+ file: log.file,
1710
+ status: log.status,
1711
+ resultLength: log.resultLength,
1712
+ errorSummary: log.errorSummary,
1713
+ tempOneFileDiffPath: log.tempOneFileDiffPath,
1714
+ command: log.command
1715
+ })),
1716
+ null,
1717
+ 2
1718
+ )}
1719
+ \`\`\``
1720
+ }
1721
+ ]
1722
+ });
1723
+ if (executionLogPath) {
1724
+ const writeLog = executionStatus === "failed" ? console.error : console.log;
1725
+ writeLog(`\u{1F4DD} \uC2E4\uD589 \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${executionLogPath}`);
1726
+ }
1727
+ }
1728
+ if (exitCode !== 0) {
1729
+ process.exit(exitCode);
1137
1730
  }
1138
1731
  }
1139
1732
  main();