sales-frontend-gemini-cli 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/common/helper.cjs +674 -68
  2. package/dist/common/helper.cjs.map +1 -1
  3. package/dist/common/helper.d.cts +106 -4
  4. package/dist/common/helper.d.ts +106 -4
  5. package/dist/common/helper.js +659 -70
  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 +58 -10
  10. package/dist/pr-review/claude/claude-commander.cjs.map +1 -1
  11. package/dist/pr-review/claude/claude-commander.js +58 -10
  12. package/dist/pr-review/claude/claude-commander.js.map +1 -1
  13. package/dist/pr-review/claude/installation-claude.cjs +219 -13
  14. package/dist/pr-review/claude/installation-claude.cjs.map +1 -1
  15. package/dist/pr-review/claude/installation-claude.js +218 -13
  16. package/dist/pr-review/claude/installation-claude.js.map +1 -1
  17. package/dist/pr-review/codex/codex-commander.cjs +55 -9
  18. package/dist/pr-review/codex/codex-commander.cjs.map +1 -1
  19. package/dist/pr-review/codex/codex-commander.js +55 -9
  20. package/dist/pr-review/codex/codex-commander.js.map +1 -1
  21. package/dist/pr-review/codex/installation-codex.cjs +219 -13
  22. package/dist/pr-review/codex/installation-codex.cjs.map +1 -1
  23. package/dist/pr-review/codex/installation-codex.js +218 -13
  24. package/dist/pr-review/codex/installation-codex.js.map +1 -1
  25. package/dist/pr-review/gemini/gemini-commander.cjs +82 -16
  26. package/dist/pr-review/gemini/gemini-commander.cjs.map +1 -1
  27. package/dist/pr-review/gemini/gemini-commander.js +82 -16
  28. package/dist/pr-review/gemini/gemini-commander.js.map +1 -1
  29. package/dist/pr-review/gemini/installation-gemini.cjs +219 -13
  30. package/dist/pr-review/gemini/installation-gemini.cjs.map +1 -1
  31. package/dist/pr-review/gemini/installation-gemini.js +218 -13
  32. package/dist/pr-review/gemini/installation-gemini.js.map +1 -1
  33. package/dist/pr-review/review-one-by-one.cjs +838 -184
  34. package/dist/pr-review/review-one-by-one.cjs.map +1 -1
  35. package/dist/pr-review/review-one-by-one.js +839 -185
  36. package/dist/pr-review/review-one-by-one.js.map +1 -1
  37. package/dist/pr-review/review.cjs +815 -156
  38. package/dist/pr-review/review.cjs.map +1 -1
  39. package/dist/pr-review/review.js +815 -156
  40. package/dist/pr-review/review.js.map +1 -1
  41. package/package.json +4 -7
  42. package/src/common/rules/coding-convention.md +393 -0
  43. package/src/common/rules/coding-convention.pdf +0 -0
  44. package/src/common/rules/naming-rule.md +347 -0
  45. package/src/common/rules/naming-rule.pdf +0 -0
@@ -1,19 +1,59 @@
1
- import { execSync } from 'child_process';
1
+ import { execSync, execFileSync } from 'child_process';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
  import readline from 'readline';
5
5
  import { fileURLToPath } from 'url';
6
+ import { inspect } from 'util';
6
7
 
7
8
  // src/common/helper.ts
8
9
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
9
- var rulesPath = path.resolve(__dirname, "../../src/common/rules/review-rules.md");
10
- var namingRulesPath = path.resolve(__dirname, "../../src/common/rules/naming-rule.md");
11
- var codingConventionRulesPath = path.resolve(__dirname, "../../src/common/rules/coding-convention.md");
12
- var reviewFormPath = path.resolve(__dirname, "../../src/common/form/review-form.md");
13
- var reviewFormOneByOnePath = path.resolve(__dirname, "../../src/common/form/review-form-one-by-one.md");
10
+ var traceMessages = [];
11
+ var GEMINI_CLI_PACKAGE_NAME = "sales-frontend-gemini-cli";
12
+ var cachedPackageRootPath = "";
13
+ function isGeminiCliPackageRoot(directory) {
14
+ const packageJsonPath = path.join(directory, "package.json");
15
+ if (!fs.existsSync(packageJsonPath)) {
16
+ return false;
17
+ }
18
+ try {
19
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
20
+ return packageJson.name === GEMINI_CLI_PACKAGE_NAME;
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+ function resolveGeminiCliPackageRoot(startDirectory = __dirname) {
26
+ if (cachedPackageRootPath) {
27
+ return cachedPackageRootPath;
28
+ }
29
+ let currentDirectory = startDirectory;
30
+ while (true) {
31
+ if (isGeminiCliPackageRoot(currentDirectory)) {
32
+ cachedPackageRootPath = currentDirectory;
33
+ return cachedPackageRootPath;
34
+ }
35
+ const parentDirectory = path.dirname(currentDirectory);
36
+ if (parentDirectory === currentDirectory) {
37
+ break;
38
+ }
39
+ currentDirectory = parentDirectory;
40
+ }
41
+ cachedPackageRootPath = path.resolve(startDirectory, "../..");
42
+ return cachedPackageRootPath;
43
+ }
44
+ function resolvePackageAssetPath(relativeFilePath) {
45
+ return path.resolve(resolveGeminiCliPackageRoot(), relativeFilePath);
46
+ }
47
+ var rulesPath = resolvePackageAssetPath("src/common/rules/review-rules.md");
48
+ var namingRulesPath = resolvePackageAssetPath("src/common/rules/naming-rule.md");
49
+ var codingConventionRulesPath = resolvePackageAssetPath("src/common/rules/coding-convention.md");
50
+ var reviewFormPath = resolvePackageAssetPath("src/common/form/review-form.md");
51
+ var reviewFormOneByOnePath = resolvePackageAssetPath("src/common/form/review-form-one-by-one.md");
14
52
  var REPORT_DIR = ".review-report";
15
53
  var tempDiffPath = "temp_diff.txt";
16
54
  var AIServices = ["gemini", "claude", "codex"];
55
+ var COMMIT_FETCH_LIMIT = 20;
56
+ var COMMIT_SELECTION_WINDOW = 8;
17
57
  var ignoreList = [
18
58
  "package.json",
19
59
  "*.yml",
@@ -28,32 +68,275 @@ var ignoreList = [
28
68
  ".review-report/"
29
69
  // 생성되는 리포트 폴더도 제외
30
70
  ];
31
- function parseServiceFromArgs(args = process.argv.slice(2)) {
32
- const serviceIndex = args.indexOf("--service");
33
- const rawService = serviceIndex !== -1 ? args[serviceIndex + 1] : "";
34
- if (!rawService) {
71
+ function isTestMode(args = process.argv.slice(2)) {
72
+ return args.includes("--test");
73
+ }
74
+ function clearTraceMessages() {
75
+ traceMessages.length = 0;
76
+ }
77
+ function getTraceMessages() {
78
+ return [...traceMessages];
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) {
35
154
  return "";
36
155
  }
37
- const normalizedService = rawService.toLowerCase();
38
- if (AIServices.includes(normalizedService)) {
39
- return normalizedService;
156
+ const tokens = tokenizeVisibleText(value);
157
+ const totalWidth = tokens.reduce((sum, token) => sum + token.visibleWidth, 0);
158
+ if (totalWidth <= maxWidth) {
159
+ return value;
40
160
  }
41
- console.error(
42
- `\u274C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC11C\uBE44\uC2A4\uC785\uB2C8\uB2E4: ${rawService}. \uC0AC\uC6A9 \uAC00\uB2A5 \uAC12: ${AIServices.join(", ")} (\uC608: --service codex)`
43
- );
44
- process.exit(1);
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}`;
45
178
  }
46
- function isTestMode(args = process.argv.slice(2)) {
47
- return args.includes("--test");
179
+ function fitLinesToTerminal(lines) {
180
+ const maxWidth = Math.max(20, (process.stdout.columns || 120) - 1);
181
+ return lines.map((line) => truncateLineForTerminal(line, maxWidth));
182
+ }
183
+ function runGitCommand(args, options = {}) {
184
+ const { allowFailure = false, trimOutput = true } = options;
185
+ try {
186
+ const output = execFileSync("git", args, {
187
+ encoding: "utf8",
188
+ maxBuffer: 1024 * 1024 * 20,
189
+ stdio: ["ignore", "pipe", "pipe"]
190
+ });
191
+ return trimOutput ? output.trim() : output;
192
+ } catch (error) {
193
+ helperTrace("git-command:failed", `${args.join(" ")} | ${getErrorSummary(error)}`);
194
+ if (allowFailure) {
195
+ return "";
196
+ }
197
+ throw error;
198
+ }
199
+ }
200
+ async function executeShellCommandWithProgress(command, options = {}) {
201
+ const { progressIntervalMs = 1e4, progressMessage = "\u23F3 \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uB294 \uC911\uC785\uB2C8\uB2E4...", streamOutput = false } = options;
202
+ const { spawn } = await import('child_process');
203
+ return new Promise((resolve, reject) => {
204
+ let stdout = "";
205
+ let stderr = "";
206
+ const startedAt = Date.now();
207
+ console.log(progressMessage);
208
+ const child = spawn("/bin/zsh", ["-lc", command], {
209
+ stdio: ["ignore", "pipe", "pipe"]
210
+ });
211
+ const progressTimer = setInterval(() => {
212
+ const elapsedSeconds = Math.max(1, Math.floor((Date.now() - startedAt) / 1e3));
213
+ console.log(`${progressMessage} (${elapsedSeconds}s \uACBD\uACFC)`);
214
+ }, progressIntervalMs);
215
+ child.stdout.on("data", (chunk) => {
216
+ const text = chunk.toString();
217
+ stdout += text;
218
+ if (streamOutput) {
219
+ process.stdout.write(text);
220
+ }
221
+ });
222
+ child.stderr.on("data", (chunk) => {
223
+ const text = chunk.toString();
224
+ stderr += text;
225
+ if (streamOutput) {
226
+ process.stderr.write(text);
227
+ }
228
+ });
229
+ child.on("error", (error) => {
230
+ clearInterval(progressTimer);
231
+ reject(error);
232
+ });
233
+ child.on("close", (code, signal) => {
234
+ clearInterval(progressTimer);
235
+ if (code === 0) {
236
+ resolve({
237
+ stderr,
238
+ stdout
239
+ });
240
+ return;
241
+ }
242
+ const exitSummary = signal ? `signal=${signal}` : `code=${String(code ?? "unknown")}`;
243
+ reject(new Error(`\uC258 \uBA85\uB839 \uC2E4\uD589 \uC2E4\uD328 (${exitSummary})${stderr.trim() ? `
244
+ ${stderr.trim()}` : ""}`));
245
+ });
246
+ });
247
+ }
248
+ function formatReviewTargetFiles(files, visibleCount = 5) {
249
+ if (files.length === 0) {
250
+ return "(\uC5C6\uC74C)";
251
+ }
252
+ const visibleFiles = files.slice(0, visibleCount);
253
+ const hiddenCount = Math.max(0, files.length - visibleFiles.length);
254
+ if (hiddenCount === 0) {
255
+ return visibleFiles.join(", ");
256
+ }
257
+ return `${visibleFiles.join(", ")} \uC678 ${hiddenCount}\uAC1C`;
48
258
  }
49
259
  function createTraceLogger(scope, args = process.argv.slice(2)) {
50
260
  const enabled = isTestMode(args);
51
261
  return (step, detail) => {
262
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
263
+ const message = `[${timestamp}][TRACE][${scope}] ${step}${detail ? ` | ${detail}` : ""}`;
264
+ traceMessages.push(message);
52
265
  if (!enabled) {
53
266
  return;
54
267
  }
55
- console.log(`[TRACE][${scope}] ${step}${detail ? ` | ${detail}` : ""}`);
268
+ console.log(message);
269
+ };
270
+ }
271
+ var helperTrace = createTraceLogger("helper");
272
+ function getTimestampParts(now = /* @__PURE__ */ new Date()) {
273
+ return {
274
+ YYYY: now.getFullYear(),
275
+ MM: String(now.getMonth() + 1).padStart(2, "0"),
276
+ DD: String(now.getDate()).padStart(2, "0"),
277
+ HH: String(now.getHours()).padStart(2, "0"),
278
+ mm: String(now.getMinutes()).padStart(2, "0"),
279
+ ss: String(now.getSeconds()).padStart(2, "0")
280
+ };
281
+ }
282
+ function getHumanReadableNowString(now = /* @__PURE__ */ new Date()) {
283
+ const { YYYY, MM, DD, HH, mm, ss } = getTimestampParts(now);
284
+ return `${YYYY}-${MM}-${DD} ${HH}:${mm}:${ss}`;
285
+ }
286
+ function stringifyUnknown(value) {
287
+ if (value === void 0 || value === null) {
288
+ return "";
289
+ }
290
+ if (typeof value === "string") {
291
+ return value;
292
+ }
293
+ if (Buffer.isBuffer(value)) {
294
+ return value.toString();
295
+ }
296
+ if (value instanceof Error) {
297
+ return value.stack || value.message;
298
+ }
299
+ return inspect(value, { depth: 5, breakLength: 120 });
300
+ }
301
+ function getErrorSummary(error) {
302
+ if (error instanceof Error) {
303
+ return `${error.name}: ${error.message}`;
304
+ }
305
+ return stringifyUnknown(error) || "Unknown error";
306
+ }
307
+ function serializeError(error) {
308
+ const serialized = {
309
+ summary: getErrorSummary(error)
56
310
  };
311
+ if (error instanceof Error) {
312
+ serialized.name = error.name;
313
+ serialized.message = error.message;
314
+ serialized.stack = error.stack;
315
+ } else {
316
+ serialized.value = stringifyUnknown(error);
317
+ }
318
+ if (error && typeof error === "object") {
319
+ const errorLike = error;
320
+ const extraKeys = ["code", "errno", "syscall", "path", "cmd", "status", "signal", "spawnargs"];
321
+ extraKeys.forEach((key) => {
322
+ if (errorLike[key] !== void 0) {
323
+ serialized[key] = errorLike[key];
324
+ }
325
+ });
326
+ const stdout = stringifyUnknown(errorLike.stdout);
327
+ if (stdout) {
328
+ serialized.stdout = stdout;
329
+ }
330
+ const stderr = stringifyUnknown(errorLike.stderr);
331
+ if (stderr) {
332
+ serialized.stderr = stderr;
333
+ }
334
+ const cause = stringifyUnknown(errorLike.cause);
335
+ if (cause) {
336
+ serialized.cause = cause;
337
+ }
338
+ }
339
+ return serialized;
57
340
  }
58
341
  function getNextFilePath(dir, baseName, extension) {
59
342
  let counter = 1;
@@ -65,6 +348,13 @@ function getNextFilePath(dir, baseName, extension) {
65
348
  counter++;
66
349
  }
67
350
  }
351
+ function getAvailableFilePath(dir, baseName, extension) {
352
+ const firstFilePath = path.join(dir, `${baseName}${extension}`);
353
+ if (!fs.existsSync(firstFilePath)) {
354
+ return firstFilePath;
355
+ }
356
+ return getNextFilePath(dir, baseName, extension);
357
+ }
68
358
  function deleteFile(filePath) {
69
359
  if (fs.existsSync(filePath)) {
70
360
  fs.unlinkSync(filePath);
@@ -78,27 +368,194 @@ function createReportDirectory() {
78
368
  fs.mkdirSync(REPORT_DIR, { recursive: true });
79
369
  }
80
370
  }
81
- function getNowString() {
82
- const now = /* @__PURE__ */ new Date();
83
- const YYYY = now.getFullYear();
84
- const MM = String(now.getMonth() + 1).padStart(2, "0");
85
- const DD = String(now.getDate()).padStart(2, "0");
86
- const HH = String(now.getHours()).padStart(2, "0");
87
- const mm = String(now.getMinutes()).padStart(2, "0");
88
- const ss = String(now.getSeconds()).padStart(2, "0");
371
+ function getNowString(now = /* @__PURE__ */ new Date()) {
372
+ const { YYYY, MM, DD, HH, mm, ss } = getTimestampParts(now);
89
373
  return `${YYYY}-${MM}-${DD}_${HH}-${mm}-${ss}`;
90
374
  }
375
+ function getErrorLogTimestamp(now = /* @__PURE__ */ new Date()) {
376
+ const { YYYY, MM, DD, HH, mm, ss } = getTimestampParts(now);
377
+ return `${YYYY}-${MM}-${DD}-${HH}\uC2DC-${mm}\uBD84-${ss}\uCD08`;
378
+ }
379
+ function writeErrorReport(error, options = {}) {
380
+ try {
381
+ const now = /* @__PURE__ */ new Date();
382
+ helperTrace("error-report:write:start", options.scope || "unknown");
383
+ createReportDirectory();
384
+ const reportPath = getAvailableFilePath(REPORT_DIR, `error-log-${getErrorLogTimestamp(now)}`, ".md");
385
+ const serializedError = serializeError(error);
386
+ const traceSnapshot = options.traceMessages ?? getTraceMessages();
387
+ const extraSections = options.extraSections || [];
388
+ const report = `# Error Log
389
+
390
+ - \uBC1C\uC0DD \uC2DC\uAC01: ${getHumanReadableNowString(now)}
391
+ - Scope: \`${options.scope || "unknown"}\`
392
+ - \uC791\uC5C5 \uACBD\uB85C: \`${process.cwd()}\`
393
+ - \uC2E4\uD589 \uC778\uC790: \`${JSON.stringify(options.args ?? process.argv.slice(2))}\`
394
+ - \uC2E4\uD589 \uD658\uACBD: \`${process.platform} ${process.arch} / Node ${process.version}\`
395
+
396
+ ## Summary
397
+
398
+ ${options.title || serializedError.summary || "Unknown error"}
399
+
400
+ ## Error
401
+
402
+ \`\`\`json
403
+ ${JSON.stringify(serializedError, null, 2)}
404
+ \`\`\`
405
+
406
+ ## Trace
407
+
408
+ \`\`\`json
409
+ ${JSON.stringify(traceSnapshot, null, 2)}
410
+ \`\`\`${extraSections.length ? `
411
+ ${extraSections.map((section) => `
412
+ ## ${section.heading}
413
+
414
+ ${section.markdown}`).join("\n")}
415
+ ` : "\n"}
416
+ `;
417
+ fs.writeFileSync(reportPath, report);
418
+ helperTrace("error-report:write:done", reportPath);
419
+ return reportPath;
420
+ } catch (writeError) {
421
+ console.error("\u26A0\uFE0F \uC5D0\uB7EC \uB85C\uADF8 \uD30C\uC77C \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
422
+ console.error(writeError);
423
+ return "";
424
+ }
425
+ }
426
+ function exitWithError(message, options = {}) {
427
+ const reportPath = writeErrorReport(options.error || new Error(message), {
428
+ ...options,
429
+ title: message
430
+ });
431
+ console.error(message);
432
+ if (options.error) {
433
+ console.error(options.error);
434
+ }
435
+ if (reportPath) {
436
+ console.error(`\u{1F4C4} \uC5D0\uB7EC \uB85C\uADF8 \uC800\uC7A5 \uC704\uCE58: ${reportPath}`);
437
+ }
438
+ process.exit(1);
439
+ }
440
+ function parseServiceFromArgs(args = process.argv.slice(2)) {
441
+ helperTrace("parse-service:start", `args=${JSON.stringify(args)}`);
442
+ const serviceIndex = args.indexOf("--service");
443
+ const rawService = serviceIndex !== -1 ? args[serviceIndex + 1] : "";
444
+ if (!rawService) {
445
+ helperTrace("parse-service:empty");
446
+ return "";
447
+ }
448
+ const normalizedService = rawService.toLowerCase();
449
+ if (AIServices.includes(normalizedService)) {
450
+ helperTrace("parse-service:resolved", normalizedService);
451
+ return normalizedService;
452
+ }
453
+ helperTrace("parse-service:invalid", rawService);
454
+ exitWithError(
455
+ `\u274C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC11C\uBE44\uC2A4\uC785\uB2C8\uB2E4: ${rawService}. \uC0AC\uC6A9 \uAC00\uB2A5 \uAC12: ${AIServices.join(", ")} (\uC608: --service codex)`,
456
+ {
457
+ scope: "helper:parseServiceFromArgs",
458
+ args,
459
+ extraSections: [
460
+ {
461
+ heading: "Allowed Services",
462
+ markdown: `\`\`\`json
463
+ ${JSON.stringify(AIServices, null, 2)}
464
+ \`\`\``
465
+ }
466
+ ]
467
+ }
468
+ );
469
+ }
91
470
  function getGitDiffFilter() {
92
- const includeExtensions = ["*.ts", "*.tsx", "*.js", "*.jsx"];
93
- const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);
471
+ const { includePatterns, excludePatterns } = getGitDiffPathspecs();
94
472
  const quote = (pattern) => `"${pattern}"`;
95
- const includeParams = includeExtensions.map(quote).join(" ");
473
+ const includeParams = includePatterns.map(quote).join(" ");
96
474
  const excludeParams = excludePatterns.map(quote).join(" ");
97
475
  return { includeParams, excludeParams };
98
476
  }
477
+ function truncateCommitSubject(subject) {
478
+ if (subject.length <= 72) {
479
+ return subject;
480
+ }
481
+ return `${subject.slice(0, 69)}...`;
482
+ }
483
+ function getRecentCommitOptions() {
484
+ const output = runGitCommand(
485
+ ["log", `-${COMMIT_FETCH_LIMIT}`, "--date=relative", "--pretty=format:%h%x09%an%x09%ar%x09%s"],
486
+ { allowFailure: true }
487
+ );
488
+ if (!output) {
489
+ return [];
490
+ }
491
+ return output.split("\n").map((line) => {
492
+ const [hash = "", author = "", relativeDate = "", ...subjectParts] = line.split(" ");
493
+ const subject = subjectParts.join(" ").trim();
494
+ return {
495
+ author,
496
+ description: `${author} | ${relativeDate}`,
497
+ hash,
498
+ label: `${hash} | ${truncateCommitSubject(subject)}`,
499
+ relativeDate,
500
+ subject
501
+ };
502
+ });
503
+ }
504
+ function buildSelectedCommitSummary(commits) {
505
+ return commits.map((commit) => `- ${commit.hash} | ${commit.subject} | ${commit.author} | ${commit.relativeDate}`).join("\n");
506
+ }
507
+ function getReviewPathspecArgs() {
508
+ const { includePatterns, excludePatterns } = getGitDiffPathspecs();
509
+ return [...includePatterns, ...excludePatterns];
510
+ }
511
+ function buildSelectedCommitDiff(commits) {
512
+ const reviewPathspecArgs = getReviewPathspecArgs();
513
+ const sections = commits.map((commit) => {
514
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", ...reviewPathspecArgs], {
515
+ allowFailure: true,
516
+ trimOutput: false
517
+ }).trim();
518
+ if (!diff) {
519
+ return "";
520
+ }
521
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
522
+ }).filter(Boolean).join("\n\n");
523
+ if (!sections) {
524
+ return "";
525
+ }
526
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", "# \uB9AC\uBDF0 \uB300\uC0C1 diff", sections].join("\n");
527
+ }
528
+ function getSelectedCommitFiles(commits) {
529
+ const reviewPathspecArgs = getReviewPathspecArgs();
530
+ const files = /* @__PURE__ */ new Set();
531
+ commits.forEach((commit) => {
532
+ const output = runGitCommand(["show", "--pretty=format:", "--name-only", commit.hash, "--", ...reviewPathspecArgs], {
533
+ allowFailure: true
534
+ });
535
+ output.split("\n").map((line) => line.trim()).filter(Boolean).forEach((filePath) => files.add(filePath));
536
+ });
537
+ return [...files];
538
+ }
539
+ function buildSelectedFileDiff(commits, filePath) {
540
+ const sections = commits.map((commit) => {
541
+ const diff = runGitCommand(["show", "--stat", "--patch", "--format=", commit.hash, "--", filePath], {
542
+ allowFailure: true,
543
+ trimOutput: false
544
+ }).trim();
545
+ if (!diff) {
546
+ return "";
547
+ }
548
+ return [`## ${commit.hash} ${commit.subject}`, diff].join("\n\n");
549
+ }).filter(Boolean).join("\n\n");
550
+ if (!sections) {
551
+ return "";
552
+ }
553
+ return ["# \uC120\uD0DD\uD55C \uCEE4\uBC0B", buildSelectedCommitSummary(commits), "", `# \uD30C\uC77C: ${filePath}`, sections].join("\n\n");
554
+ }
99
555
  function openReport(reportPath) {
100
556
  const resolvedPath = path.resolve(reportPath);
101
557
  const { platform } = process;
558
+ helperTrace("open-report:start", resolvedPath);
102
559
  const openWithChrome = () => {
103
560
  if (platform === "darwin") {
104
561
  execSync(`open -a "Google Chrome" "${resolvedPath}"`, { stdio: "ignore" });
@@ -123,71 +580,199 @@ function openReport(reportPath) {
123
580
  };
124
581
  try {
125
582
  if (openWithChrome()) {
583
+ helperTrace("open-report:chrome:success", platform);
126
584
  console.log("\u{1F680} Google Chrome\uC5D0\uC11C \uB9AC\uD3EC\uD2B8\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4.");
127
585
  return;
128
586
  }
129
- } catch {
587
+ } catch (error) {
588
+ helperTrace("open-report:chrome:failed", getErrorSummary(error));
130
589
  }
131
590
  try {
132
591
  if (openWithDefaultBrowser()) {
592
+ helperTrace("open-report:default-browser:success", platform);
133
593
  console.log("\u{1F680} \uAE30\uBCF8 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB9AC\uD3EC\uD2B8\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4.");
134
594
  return;
135
595
  }
136
- } catch (e) {
137
- console.error("\u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800 \uC5F4\uAE30 \uC2E4\uD328:", e);
596
+ } catch (error) {
597
+ helperTrace("open-report:default-browser:failed", getErrorSummary(error));
598
+ console.error("\u26A0\uFE0F \uBE0C\uB77C\uC6B0\uC800 \uC5F4\uAE30 \uC2E4\uD328:", error);
138
599
  return;
139
600
  }
601
+ helperTrace("open-report:unsupported-platform", platform);
140
602
  console.error(`\u26A0\uFE0F \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD50C\uB7AB\uD3FC\uC785\uB2C8\uB2E4: ${platform}`);
141
603
  }
142
- function getDiffArgs() {
143
- const args = process.argv.slice(2);
144
- const commitIndex = args.indexOf("--commit");
145
- const { includeParams, excludeParams } = getGitDiffFilter();
146
- let diffArgs = "";
147
- if (commitIndex !== -1) {
148
- const commitHash = args[commitIndex + 1];
149
- if (!commitHash) {
150
- console.error("\u274C \uCEE4\uBC0B \uD574\uC2DC\uAC00 \uC81C\uACF5\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
151
- process.exit(1);
152
- }
153
- const nextArg = args[commitIndex + 2];
154
- let n = 0;
155
- if (nextArg && !nextArg.startsWith("--")) {
156
- n = parseInt(nextArg, 10);
157
- if (isNaN(n)) {
158
- n = 0;
159
- }
604
+ function ensureInteractiveSelectionAvailable(scope, message) {
605
+ if (process.stdin.isTTY && process.stdout.isTTY && typeof process.stdin.setRawMode === "function") {
606
+ return;
607
+ }
608
+ helperTrace(`${scope}:tty-missing`);
609
+ exitWithError(message, {
610
+ scope: `helper:${scope}`
611
+ });
612
+ }
613
+ function renderSelectionBlock(lines, previousLineCount) {
614
+ if (previousLineCount > 0) {
615
+ readline.moveCursor(process.stdout, 0, -previousLineCount);
616
+ readline.clearScreenDown(process.stdout);
617
+ }
618
+ const fittedLines = fitLinesToTerminal(lines);
619
+ process.stdout.write(`${fittedLines.join("\n")}
620
+ `);
621
+ return fittedLines.length;
622
+ }
623
+ function getSelectionWindowRange(optionCount, selectedIndex, windowSize) {
624
+ if (optionCount <= windowSize) {
625
+ return {
626
+ end: optionCount,
627
+ start: 0
628
+ };
629
+ }
630
+ const halfWindow = Math.floor(windowSize / 2);
631
+ const maxStart = optionCount - windowSize;
632
+ const start = Math.max(0, Math.min(selectedIndex - halfWindow, maxStart));
633
+ return {
634
+ end: Math.min(optionCount, start + windowSize),
635
+ start
636
+ };
637
+ }
638
+ function buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize) {
639
+ const { start, end } = getSelectionWindowRange(options.length, selectedIndex, windowSize);
640
+ const lines = [
641
+ `${ANSI.bold}${question}${ANSI.reset}`,
642
+ `${ANSI.dim}\u2191\u2193 \uC774\uB3D9 | Space \uC120\uD0DD/\uD574\uC81C | Enter \uC644\uB8CC | Esc \uCDE8\uC18C${ANSI.reset}`,
643
+ `${ANSI.dim}\uC120\uD0DD\uB428: ${toggled.size}\uAC1C / \uC804\uCCB4: ${options.length}\uAC1C${ANSI.reset}`
644
+ ];
645
+ for (let index = start; index < end; index += 1) {
646
+ const option = options[index];
647
+ if (!option) {
648
+ continue;
160
649
  }
161
- console.log(`\u2139\uFE0F \uCEE4\uBC0B '${commitHash}' ${n > 0 ? ` \uD3EC\uD568 \uCD1D ${n + 1}\uAC1C\uC758 \uCEE4\uBC0B` : ""}\uC744 \uB9AC\uBDF0\uD569\uB2C8\uB2E4...`);
162
- diffArgs = `${commitHash}~${n + 1} ${commitHash}`;
163
- } else {
164
- try {
165
- const check = execSync(`git diff --name-only -- ${includeParams} ${excludeParams}`).toString();
166
- if (!check.trim()) {
167
- 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...");
168
- diffArgs = "HEAD~1 HEAD";
169
- }
170
- } catch {
650
+ const cursor = index === selectedIndex ? `${ANSI.cyan}>${ANSI.reset}` : " ";
651
+ const checked = toggled.has(index) ? `${ANSI.green}\u2611${ANSI.reset}` : "\u2610";
652
+ const description = option.description ? ` ${ANSI.dim}${option.description}${ANSI.reset}` : "";
653
+ lines.push(`${cursor} ${checked} ${option.label}${description}`);
654
+ }
655
+ if (options.length > windowSize) {
656
+ lines.push(`${ANSI.dim}\uD45C\uC2DC \uBC94\uC704: ${start + 1}-${end} / ${options.length}${ANSI.reset}`);
657
+ }
658
+ return lines;
659
+ }
660
+ async function showMultiSelect(question, options, windowSize = COMMIT_SELECTION_WINDOW) {
661
+ ensureInteractiveSelectionAvailable("showMultiSelect", "\u274C \uCEE4\uBC0B \uC120\uD0DD \uBAA8\uB2EC\uC740 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
662
+ let selectedIndex = 0;
663
+ let renderedLineCount = 0;
664
+ const toggled = /* @__PURE__ */ new Set();
665
+ const rl = readline.createInterface({
666
+ input: process.stdin,
667
+ output: process.stdout,
668
+ terminal: true
669
+ });
670
+ process.stdout.write("\x1B[?25l");
671
+ const cleanup = () => {
672
+ if (renderedLineCount > 0) {
673
+ readline.moveCursor(process.stdout, 0, -renderedLineCount);
674
+ readline.clearScreenDown(process.stdout);
675
+ renderedLineCount = 0;
171
676
  }
677
+ process.stdin.removeListener("data", onData);
678
+ process.stdin.setRawMode(false);
679
+ process.stdin.pause();
680
+ rl.close();
681
+ process.stdout.write("\x1B[?25h");
682
+ };
683
+ const render = () => {
684
+ const lines = buildMultiSelectLines(question, options, selectedIndex, toggled, windowSize);
685
+ renderedLineCount = renderSelectionBlock(lines, renderedLineCount);
686
+ };
687
+ const confirmSelection = (resolve) => {
688
+ const values = [...toggled].sort((left, right) => left - right).map((index) => options[index]?.value).filter((value) => value !== void 0);
689
+ cleanup();
690
+ resolve(values);
691
+ };
692
+ const cancelSelection = (resolve) => {
693
+ cleanup();
694
+ resolve([]);
695
+ };
696
+ let onData = (_data) => {
697
+ };
698
+ render();
699
+ return new Promise((resolve) => {
700
+ onData = (data) => {
701
+ const key = data.toString();
702
+ if (key === "") {
703
+ cleanup();
704
+ process.exit(0);
705
+ }
706
+ if (key === "\x1B") {
707
+ cancelSelection(resolve);
708
+ return;
709
+ }
710
+ if (key === "\x1B[A") {
711
+ selectedIndex = (selectedIndex - 1 + options.length) % options.length;
712
+ render();
713
+ return;
714
+ }
715
+ if (key === "\x1B[B") {
716
+ selectedIndex = (selectedIndex + 1) % options.length;
717
+ render();
718
+ return;
719
+ }
720
+ if (key === " ") {
721
+ if (toggled.has(selectedIndex)) {
722
+ toggled.delete(selectedIndex);
723
+ } else {
724
+ toggled.add(selectedIndex);
725
+ }
726
+ render();
727
+ return;
728
+ }
729
+ if (key === "\r" || key === "\n") {
730
+ confirmSelection(resolve);
731
+ }
732
+ };
733
+ process.stdin.setRawMode(true);
734
+ process.stdin.resume();
735
+ process.stdin.on("data", onData);
736
+ });
737
+ }
738
+ async function selectReviewCommits() {
739
+ const commits = getRecentCommitOptions();
740
+ if (commits.length === 0) {
741
+ console.log("\u2139\uFE0F \uB9AC\uBDF0\uD560 \uCD5C\uADFC \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
742
+ return [];
172
743
  }
173
- return diffArgs;
744
+ return showMultiSelect(
745
+ "\uB9AC\uBDF0\uD560 \uCEE4\uBC0B\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.",
746
+ commits.map((commit) => ({
747
+ description: commit.description,
748
+ label: commit.label,
749
+ value: commit
750
+ })),
751
+ COMMIT_SELECTION_WINDOW
752
+ );
174
753
  }
175
754
  function selectAIService() {
176
755
  const service = parseServiceFromArgs();
177
756
  if (!service) {
178
- console.error("\u274C \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
179
- process.exit(1);
757
+ helperTrace("select-service:missing");
758
+ exitWithError("\u274C \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.", {
759
+ scope: "helper:selectAIService"
760
+ });
180
761
  }
762
+ helperTrace("select-service:done", service);
181
763
  return service;
182
764
  }
183
765
  async function showSelectionAIService() {
184
766
  const selectedServiceFromArgs = parseServiceFromArgs();
185
767
  if (selectedServiceFromArgs) {
768
+ helperTrace("show-selection:from-args", selectedServiceFromArgs);
186
769
  console.log(`
187
- \u2705 \x1B[32m${selectedServiceFromArgs}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
770
+ \u2705 ${ANSI.green}${selectedServiceFromArgs}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
188
771
  `);
189
772
  return selectedServiceFromArgs;
190
773
  }
774
+ ensureInteractiveSelectionAvailable("showSelectionAIService", "\u274C AI \uC11C\uBE44\uC2A4 \uC120\uD0DD UI\uB294 TTY \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
775
+ helperTrace("show-selection:interactive:start");
191
776
  let selectedIndex = 0;
192
777
  const rl = readline.createInterface({
193
778
  input: process.stdin,
@@ -201,13 +786,15 @@ async function showSelectionAIService() {
201
786
  readline.moveCursor(process.stdout, 0, -(AIServices.length + 1));
202
787
  }
203
788
  firstRender = false;
789
+ helperTrace("show-selection:interactive:render", AIServices[selectedIndex] || "unknown");
204
790
  readline.clearScreenDown(process.stdout);
205
791
  process.stdout.write(
206
- "\u{1F916} AI \uC11C\uBE44\uC2A4\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694 (\x1B[33m\u2191\u2193 \uBC29\uD5A5\uD0A4\x1B[0m \uC774\uB3D9, \x1B[33mEnter\x1B[0m \uC120\uD0DD):\n"
792
+ `\u{1F916} AI \uC11C\uBE44\uC2A4\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694 (${ANSI.yellow}\u2191\u2193 \uBC29\uD5A5\uD0A4${ANSI.reset} \uC774\uB3D9, ${ANSI.yellow}Enter${ANSI.reset} \uC120\uD0DD):
793
+ `
207
794
  );
208
795
  AIServices.forEach((service, index) => {
209
796
  if (index === selectedIndex) {
210
- process.stdout.write(` \x1B[36m>\x1B[0m \x1B[36m\u25C9\x1B[0m \x1B[1m${service}\x1B[0m
797
+ process.stdout.write(` ${ANSI.cyan}>${ANSI.reset} ${ANSI.cyan}\u25C9${ANSI.reset} ${ANSI.bold}${service}${ANSI.reset}
211
798
  `);
212
799
  } else {
213
800
  process.stdout.write(` \u25EF ${service}
@@ -220,6 +807,7 @@ async function showSelectionAIService() {
220
807
  const onData = (data) => {
221
808
  const key = data.toString();
222
809
  if (key === "") {
810
+ helperTrace("show-selection:interactive:ctrl-c");
223
811
  process.stdout.write("\x1B[?25h");
224
812
  process.exit(0);
225
813
  }
@@ -236,10 +824,11 @@ async function showSelectionAIService() {
236
824
  rl.close();
237
825
  process.stdout.write("\x1B[?25h");
238
826
  console.log(`
239
- \u2705 \x1B[32m${AIServices[selectedIndex]}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
827
+ \u2705 ${ANSI.green}${AIServices[selectedIndex]}${ANSI.reset} \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
240
828
  `);
241
829
  const result = AIServices[selectedIndex];
242
830
  if (result) {
831
+ helperTrace("show-selection:interactive:confirmed", result);
243
832
  resolve(result);
244
833
  }
245
834
  }
@@ -250,6 +839,6 @@ async function showSelectionAIService() {
250
839
  });
251
840
  }
252
841
 
253
- export { AIServices, REPORT_DIR, codingConventionRulesPath, createReportDirectory, createTraceLogger, deleteFile, deleteTempDiff, getDiffArgs, getGitDiffFilter, getNextFilePath, getNowString, ignoreList, isTestMode, namingRulesPath, openReport, reviewFormOneByOnePath, reviewFormPath, rulesPath, selectAIService, showSelectionAIService, tempDiffPath };
842
+ export { AIServices, COMMIT_FETCH_LIMIT, COMMIT_SELECTION_WINDOW, REPORT_DIR, buildSelectedCommitDiff, buildSelectedCommitSummary, buildSelectedFileDiff, clearTraceMessages, codingConventionRulesPath, createReportDirectory, createTraceLogger, deleteFile, deleteTempDiff, executeShellCommandWithProgress, exitWithError, formatReviewTargetFiles, getAvailableFilePath, getErrorLogTimestamp, getErrorSummary, getGitDiffFilter, getNextFilePath, getNowString, getRecentCommitOptions, getSelectedCommitFiles, getTraceMessages, ignoreList, isTestMode, namingRulesPath, openReport, reviewFormOneByOnePath, reviewFormPath, rulesPath, selectAIService, selectReviewCommits, showMultiSelect, showSelectionAIService, tempDiffPath, writeErrorReport };
254
843
  //# sourceMappingURL=helper.js.map
255
844
  //# sourceMappingURL=helper.js.map