sales-frontend-gemini-cli 0.4.0 → 0.4.1

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 (39) hide show
  1. package/dist/common/helper.cjs +23 -3
  2. package/dist/common/helper.cjs.map +1 -1
  3. package/dist/common/helper.d.cts +1 -1
  4. package/dist/common/helper.d.ts +1 -1
  5. package/dist/common/helper.js +23 -3
  6. package/dist/common/helper.js.map +1 -1
  7. package/dist/pr-review/claude/claude-commander.cjs +134 -27
  8. package/dist/pr-review/claude/claude-commander.cjs.map +1 -1
  9. package/dist/pr-review/claude/claude-commander.d.cts +7 -8
  10. package/dist/pr-review/claude/claude-commander.d.ts +7 -8
  11. package/dist/pr-review/claude/claude-commander.js +134 -27
  12. package/dist/pr-review/claude/claude-commander.js.map +1 -1
  13. package/dist/pr-review/claude/installation-claude.cjs.map +1 -1
  14. package/dist/pr-review/claude/installation-claude.js.map +1 -1
  15. package/dist/pr-review/codex/codex-commander.cjs +58 -20
  16. package/dist/pr-review/codex/codex-commander.cjs.map +1 -1
  17. package/dist/pr-review/codex/codex-commander.d.cts +8 -3
  18. package/dist/pr-review/codex/codex-commander.d.ts +8 -3
  19. package/dist/pr-review/codex/codex-commander.js +58 -20
  20. package/dist/pr-review/codex/codex-commander.js.map +1 -1
  21. package/dist/pr-review/codex/installation-codex.cjs.map +1 -1
  22. package/dist/pr-review/codex/installation-codex.js.map +1 -1
  23. package/dist/pr-review/gemini/gemini-commander.cjs +115 -20
  24. package/dist/pr-review/gemini/gemini-commander.cjs.map +1 -1
  25. package/dist/pr-review/gemini/gemini-commander.d.cts +10 -13
  26. package/dist/pr-review/gemini/gemini-commander.d.ts +10 -13
  27. package/dist/pr-review/gemini/gemini-commander.js +115 -20
  28. package/dist/pr-review/gemini/gemini-commander.js.map +1 -1
  29. package/dist/pr-review/gemini/installation-gemini.cjs.map +1 -1
  30. package/dist/pr-review/gemini/installation-gemini.js.map +1 -1
  31. package/dist/pr-review/review-one-by-one.cjs +329 -67
  32. package/dist/pr-review/review-one-by-one.cjs.map +1 -1
  33. package/dist/pr-review/review-one-by-one.js +329 -67
  34. package/dist/pr-review/review-one-by-one.js.map +1 -1
  35. package/dist/pr-review/review.cjs +329 -67
  36. package/dist/pr-review/review.cjs.map +1 -1
  37. package/dist/pr-review/review.js +329 -67
  38. package/dist/pr-review/review.js.map +1 -1
  39. package/package.json +1 -1
@@ -37,6 +37,21 @@ var ignoreList = [
37
37
  ".review-report/"
38
38
  // 생성되는 리포트 폴더도 제외
39
39
  ];
40
+ function parseServiceFromArgs(args = process.argv.slice(2)) {
41
+ const serviceIndex = args.indexOf("--service");
42
+ const rawService = serviceIndex !== -1 ? args[serviceIndex + 1] : "";
43
+ if (!rawService) {
44
+ return "";
45
+ }
46
+ const normalizedService = rawService.toLowerCase();
47
+ if (AIServices.includes(normalizedService)) {
48
+ return normalizedService;
49
+ }
50
+ console.error(
51
+ `\u274C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC11C\uBE44\uC2A4\uC785\uB2C8\uB2E4: ${rawService}. \uC0AC\uC6A9 \uAC00\uB2A5 \uAC12: ${AIServices.join(", ")} (\uC608: --service codex)`
52
+ );
53
+ process.exit(1);
54
+ }
40
55
  function isTestMode(args = process.argv.slice(2)) {
41
56
  return args.includes("--test");
42
57
  }
@@ -167,9 +182,7 @@ function getDiffArgs() {
167
182
  return diffArgs;
168
183
  }
169
184
  function selectAIService() {
170
- const args = process.argv.slice(2);
171
- const serviceIndex = args.indexOf("--service");
172
- const service = args[serviceIndex + 1];
185
+ const service = parseServiceFromArgs();
173
186
  if (!service) {
174
187
  console.error("\u274C \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
175
188
  process.exit(1);
@@ -177,6 +190,13 @@ function selectAIService() {
177
190
  return service;
178
191
  }
179
192
  async function showSelectionAIService() {
193
+ const selectedServiceFromArgs = parseServiceFromArgs();
194
+ if (selectedServiceFromArgs) {
195
+ console.log(`
196
+ \u2705 \x1B[32m${selectedServiceFromArgs}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
197
+ `);
198
+ return selectedServiceFromArgs;
199
+ }
180
200
  let selectedIndex = 0;
181
201
  const rl = readline__default.default.createInterface({
182
202
  input: process.stdin,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/common/helper.ts"],"names":["__dirname","path","fileURLToPath","fs","execSync","readline"],"mappings":"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","file":"helper.cjs","sourcesContent":["import { execSync } from 'child_process';\nimport fs from 'fs';\nimport path from 'path';\nimport readline from 'readline';\nimport { fileURLToPath } from 'url';\n\nimport { AIServiceType } from './types';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n// 설치된 위치에 맞게 규칙/양식 파일 경로를 계산 (dist에서 src로 이동 등 고려)\n// dist/common/helper.js 가 실행되므로 __dirname은 .../dist/common 입니다.\n// 따라서 ../../src/common/rules 로 이동해야 원본 소스의 규칙 파일을 찾을 수 있습니다.\nexport const rulesPath = path.resolve(__dirname, '../../src/common/rules/review-rules.md');\nexport const namingRulesPath = path.resolve(__dirname, '../../src/common/rules/naming-rule.md');\nexport const codingConventionRulesPath = path.resolve(__dirname, '../../src/common/rules/coding-convention.md');\nexport const reviewFormPath = path.resolve(__dirname, '../../src/common/form/review-form.md');\nexport const reviewFormOneByOnePath = path.resolve(__dirname, '../../src/common/form/review-form-one-by-one.md');\nexport const REPORT_DIR = '.review-report';\nexport const tempDiffPath = 'temp_diff.txt';\nexport const AIServices: AIServiceType[] = ['gemini', 'claude', 'codex'];\nexport const ignoreList = [\n 'package.json',\n '*.yml',\n '*.md',\n '*.lock',\n 'dist/',\n 'node_modules/',\n 'assets/',\n 'public/',\n '*.json',\n '*.yaml',\n '.review-report/' // 생성되는 리포트 폴더도 제외\n];\n\nexport function isTestMode(args: string[] = process.argv.slice(2)) {\n return args.includes('--test');\n}\n\nexport function createTraceLogger(scope: string, args: string[] = process.argv.slice(2)) {\n const enabled = isTestMode(args);\n\n return (step: string, detail?: string) => {\n if (!enabled) {\n return;\n }\n\n console.log(`[TRACE][${scope}] ${step}${detail ? ` | ${detail}` : ''}`);\n };\n}\n\nexport function getNextFilePath(dir: string, baseName: string, extension: string) {\n let counter = 1;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const filePath = path.join(dir, `${baseName}-${counter}${extension}`);\n if (!fs.existsSync(filePath)) {\n return filePath;\n }\n counter++;\n }\n}\n\nexport function deleteFile(filePath: string) {\n if (fs.existsSync(filePath)) {\n fs.unlinkSync(filePath);\n }\n}\n\n/**\n * 임시파일 삭제\n */\nexport function deleteTempDiff() {\n deleteFile(tempDiffPath);\n}\n\n/**\n * 리뷰 결과 폴더 생성\n */\nexport function createReportDirectory() {\n if (!fs.existsSync(REPORT_DIR)) {\n fs.mkdirSync(REPORT_DIR, { recursive: true });\n }\n}\n\n/**\n * 현재 시간 문자열 생성\n */\nexport function getNowString() {\n const now = new Date();\n const YYYY = now.getFullYear();\n const MM = String(now.getMonth() + 1).padStart(2, '0');\n const DD = String(now.getDate()).padStart(2, '0');\n const HH = String(now.getHours()).padStart(2, '0');\n const mm = String(now.getMinutes()).padStart(2, '0');\n const ss = String(now.getSeconds()).padStart(2, '0');\n\n return `${YYYY}-${MM}-${DD}_${HH}-${mm}-${ss}`;\n}\n\nexport function getGitDiffFilter() {\n // 1. 리뷰 대상 파일 확장자 정의\n const includeExtensions = ['*.ts', '*.tsx', '*.js', '*.jsx'];\n\n // ignoreList 를 import 하여 재사용하여 작성한다.\n const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);\n\n // const excludePatterns = [':(exclude)*.lock', ':(exclude)dist/', ':(exclude)*.md'];\n\n // 2. 변경된 파일 목록 가져오기 (각 패턴을 따옴표로 감싸서 쉘 에러 방지)\n const quote = (pattern: string) => `\"${pattern}\"`;\n const includeParams = includeExtensions.map(quote).join(' ');\n const excludeParams = excludePatterns.map(quote).join(' ');\n\n return { includeParams, excludeParams };\n}\n\n/**\n * openReport를 OS별로 동작하도록 변경\n * 우선순위:\n * 1. Chrome 시도\n * - macOS: open -a \"Google Chrome\" \"<path>\"\n * - Ubuntu/Linux: google-chrome \"<path>\"\n * 2. 실패 시 기본 브라우저로 폴백\n * - macOS: open \"<path>\"\n * - Ubuntu/Linux: xdg-open \"<path>\"\n * 3. 둘 다 실패하면 에러 출력\n * 4. 미지원 플랫폼이면 플랫폼 경고 출력\n */\nexport function openReport(reportPath: string) {\n const resolvedPath = path.resolve(reportPath);\n const { platform } = process;\n\n const openWithChrome = () => {\n if (platform === 'darwin') {\n execSync(`open -a \"Google Chrome\" \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n if (platform === 'linux') {\n execSync(`google-chrome \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n return false;\n };\n\n const openWithDefaultBrowser = () => {\n if (platform === 'darwin') {\n execSync(`open \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n if (platform === 'linux') {\n execSync(`xdg-open \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n return false;\n };\n\n try {\n if (openWithChrome()) {\n console.log('🚀 Google Chrome에서 리포트를 열었습니다.');\n\n return;\n }\n } catch {\n // Chrome 실행 실패 시 기본 브라우저로 폴백\n }\n\n try {\n if (openWithDefaultBrowser()) {\n console.log('🚀 기본 브라우저에서 리포트를 열었습니다.');\n\n return;\n }\n } catch (e) {\n console.error('⚠️ 브라우저 열기 실패:', e);\n\n return;\n }\n\n console.error(`⚠️ 지원하지 않는 플랫폼입니다: ${platform}`);\n}\n\nexport function getDiffArgs() {\n const args = process.argv.slice(2);\n const commitIndex = args.indexOf('--commit');\n const { includeParams, excludeParams } = getGitDiffFilter();\n\n let diffArgs = '';\n\n if (commitIndex !== -1) {\n // 특정 커밋 (및 이전 n개) 리뷰\n const commitHash = args[commitIndex + 1];\n if (!commitHash) {\n console.error('❌ 커밋 해시가 제공되지 않았습니다.');\n process.exit(1);\n }\n\n // n값 확인 (optional)\n const nextArg = args[commitIndex + 2];\n let n = 0;\n if (nextArg && !nextArg.startsWith('--')) {\n n = parseInt(nextArg, 10);\n if (isNaN(n)) {\n n = 0;\n }\n }\n\n console.log(`ℹ️ 커밋 '${commitHash}' ${n > 0 ? ` 포함 총 ${n + 1}개의 커밋` : ''}을 리뷰합니다...`);\n diffArgs = `${commitHash}~${n + 1} ${commitHash}`;\n } else {\n // 기본 모드:\n // 1. Unstaged 변경사항 확인\n try {\n const check = execSync(`git diff --name-only -- ${includeParams} ${excludeParams}`).toString();\n if (!check.trim()) {\n console.log('ℹ️ Unstaged 변경사항이 없습니다. 마지막 커밋(HEAD)을 리뷰합니다...');\n diffArgs = 'HEAD~1 HEAD';\n }\n } catch {\n // git diff 실패시 무시\n }\n }\n\n return diffArgs;\n}\n\n// export const ServiceType = {\n// GEMINI: 'gemini',\n// CLAUDE: 'claude',\n// CODEX: 'codex'\n\n// }\n\n/**\n * AI 서비스 선택\n */\nexport function selectAIService() {\n const args = process.argv.slice(2);\n const serviceIndex = args.indexOf('--service');\n const service = args[serviceIndex + 1];\n if (!service) {\n console.error('❌ 서비스가 선택되지 않았습니다.');\n process.exit(1);\n }\n\n return service;\n}\n\n/**\n * 터미널에서 라디오 버튼 형태로 AI 서비스를 선택합니다.\n */\nexport async function showSelectionAIService(): Promise<AIServiceType> {\n let selectedIndex = 0;\n\n // Use readline to handle keypresses\n // 키 입력을 처리하기 위해 readline 인터페이스 사용\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n terminal: true\n });\n\n let firstRender = true;\n\n // Hide cursor\n process.stdout.write('\\u001b[?25l');\n\n const render = () => {\n if (!firstRender) {\n // Move cursor back to the starting line of the selection UI\n // We print (1 question line + services.length lines)\n // 선택 UI의 시작 라인으로 커서 이동 (질문 1줄 + 서비스 목록 N줄)\n readline.moveCursor(process.stdout, 0, -(AIServices.length + 1));\n }\n firstRender = false;\n\n // Clear everything from cursor down to avoid ghosting/overlaps\n // 잔상이나 겹침을 방지하기 위해 커서 위치부터 아래쪽 모두 지움\n readline.clearScreenDown(process.stdout);\n\n process.stdout.write(\n '🤖 AI 서비스를 선택해주세요 (\\u001b[33m↑↓ 방향키\\u001b[0m 이동, \\u001b[33mEnter\\u001b[0m 선택):\\n'\n );\n AIServices.forEach((service, index) => {\n if (index === selectedIndex) {\n process.stdout.write(` \\u001b[36m>\\u001b[0m \\u001b[36m◉\\u001b[0m \\u001b[1m${service}\\u001b[0m\\n`);\n } else {\n process.stdout.write(` ◯ ${service}\\n`);\n }\n });\n };\n\n render();\n\n return new Promise((resolve) => {\n const onData = (data: Buffer) => {\n const key = data.toString();\n if (key === '\\u0003') {\n // Ctrl+C\n process.stdout.write('\\u001b[?25h'); // Show cursor\n process.exit(0);\n }\n if (key === '\\x1b[A') {\n // Up arrow\n selectedIndex = (selectedIndex - 1 + AIServices.length) % AIServices.length;\n render();\n } else if (key === '\\x1b[B') {\n // Down arrow\n selectedIndex = (selectedIndex + 1) % AIServices.length;\n render();\n } else if (key === '\\r' || key === '\\n') {\n // Enter\n process.stdin.removeListener('data', onData);\n process.stdin.setRawMode(false);\n process.stdin.pause();\n rl.close();\n\n // Show cursor\n process.stdout.write('\\u001b[?25h');\n\n console.log(`\\n✅ \\u001b[32m${AIServices[selectedIndex]}\\u001b[0m 서비스가 선택되었습니다.\\n`);\n const result = AIServices[selectedIndex];\n if (result) {\n resolve(result);\n }\n }\n };\n\n process.stdin.setRawMode(true);\n process.stdin.resume();\n process.stdin.on('data', onData);\n });\n}\n"]}
1
+ {"version":3,"sources":["../../src/common/helper.ts"],"names":["__dirname","path","fileURLToPath","fs","execSync","readline"],"mappings":"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","file":"helper.cjs","sourcesContent":["import { execSync } from 'child_process';\nimport fs from 'fs';\nimport path from 'path';\nimport readline from 'readline';\nimport { fileURLToPath } from 'url';\n\nimport { AIServiceType } from './types';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n// 설치된 위치에 맞게 규칙/양식 파일 경로를 계산 (dist에서 src로 이동 등 고려)\n// dist/common/helper.js 가 실행되므로 __dirname은 .../dist/common 입니다.\n// 따라서 ../../src/common/rules 로 이동해야 원본 소스의 규칙 파일을 찾을 수 있습니다.\nexport const rulesPath = path.resolve(__dirname, '../../src/common/rules/review-rules.md');\nexport const namingRulesPath = path.resolve(__dirname, '../../src/common/rules/naming-rule.md');\nexport const codingConventionRulesPath = path.resolve(__dirname, '../../src/common/rules/coding-convention.md');\nexport const reviewFormPath = path.resolve(__dirname, '../../src/common/form/review-form.md');\nexport const reviewFormOneByOnePath = path.resolve(__dirname, '../../src/common/form/review-form-one-by-one.md');\nexport const REPORT_DIR = '.review-report';\nexport const tempDiffPath = 'temp_diff.txt';\nexport const AIServices: AIServiceType[] = ['gemini', 'claude', 'codex'];\nexport const ignoreList = [\n 'package.json',\n '*.yml',\n '*.md',\n '*.lock',\n 'dist/',\n 'node_modules/',\n 'assets/',\n 'public/',\n '*.json',\n '*.yaml',\n '.review-report/' // 생성되는 리포트 폴더도 제외\n];\n\nfunction parseServiceFromArgs(args: string[] = process.argv.slice(2)): AIServiceType | '' {\n const serviceIndex = args.indexOf('--service');\n const rawService = serviceIndex !== -1 ? args[serviceIndex + 1] : '';\n\n if (!rawService) {\n return '';\n }\n\n const normalizedService = rawService.toLowerCase();\n\n if (AIServices.includes(normalizedService as AIServiceType)) {\n return normalizedService as AIServiceType;\n }\n\n console.error(\n `❌ 지원하지 않는 서비스입니다: ${rawService}. 사용 가능 값: ${AIServices.join(', ')} (예: --service codex)`\n );\n process.exit(1);\n}\n\nexport function isTestMode(args: string[] = process.argv.slice(2)) {\n return args.includes('--test');\n}\n\nexport function createTraceLogger(scope: string, args: string[] = process.argv.slice(2)) {\n const enabled = isTestMode(args);\n\n return (step: string, detail?: string) => {\n if (!enabled) {\n return;\n }\n\n console.log(`[TRACE][${scope}] ${step}${detail ? ` | ${detail}` : ''}`);\n };\n}\n\nexport function getNextFilePath(dir: string, baseName: string, extension: string) {\n let counter = 1;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const filePath = path.join(dir, `${baseName}-${counter}${extension}`);\n if (!fs.existsSync(filePath)) {\n return filePath;\n }\n counter++;\n }\n}\n\nexport function deleteFile(filePath: string) {\n if (fs.existsSync(filePath)) {\n fs.unlinkSync(filePath);\n }\n}\n\n/**\n * 임시파일 삭제\n */\nexport function deleteTempDiff() {\n deleteFile(tempDiffPath);\n}\n\n/**\n * 리뷰 결과 폴더 생성\n */\nexport function createReportDirectory() {\n if (!fs.existsSync(REPORT_DIR)) {\n fs.mkdirSync(REPORT_DIR, { recursive: true });\n }\n}\n\n/**\n * 현재 시간 문자열 생성\n */\nexport function getNowString() {\n const now = new Date();\n const YYYY = now.getFullYear();\n const MM = String(now.getMonth() + 1).padStart(2, '0');\n const DD = String(now.getDate()).padStart(2, '0');\n const HH = String(now.getHours()).padStart(2, '0');\n const mm = String(now.getMinutes()).padStart(2, '0');\n const ss = String(now.getSeconds()).padStart(2, '0');\n\n return `${YYYY}-${MM}-${DD}_${HH}-${mm}-${ss}`;\n}\n\nexport function getGitDiffFilter() {\n // 1. 리뷰 대상 파일 확장자 정의\n const includeExtensions = ['*.ts', '*.tsx', '*.js', '*.jsx'];\n\n // ignoreList 를 import 하여 재사용하여 작성한다.\n const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);\n\n // const excludePatterns = [':(exclude)*.lock', ':(exclude)dist/', ':(exclude)*.md'];\n\n // 2. 변경된 파일 목록 가져오기 (각 패턴을 따옴표로 감싸서 쉘 에러 방지)\n const quote = (pattern: string) => `\"${pattern}\"`;\n const includeParams = includeExtensions.map(quote).join(' ');\n const excludeParams = excludePatterns.map(quote).join(' ');\n\n return { includeParams, excludeParams };\n}\n\n/**\n * openReport를 OS별로 동작하도록 변경\n * 우선순위:\n * 1. Chrome 시도\n * - macOS: open -a \"Google Chrome\" \"<path>\"\n * - Ubuntu/Linux: google-chrome \"<path>\"\n * 2. 실패 시 기본 브라우저로 폴백\n * - macOS: open \"<path>\"\n * - Ubuntu/Linux: xdg-open \"<path>\"\n * 3. 둘 다 실패하면 에러 출력\n * 4. 미지원 플랫폼이면 플랫폼 경고 출력\n */\nexport function openReport(reportPath: string) {\n const resolvedPath = path.resolve(reportPath);\n const { platform } = process;\n\n const openWithChrome = () => {\n if (platform === 'darwin') {\n execSync(`open -a \"Google Chrome\" \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n if (platform === 'linux') {\n execSync(`google-chrome \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n return false;\n };\n\n const openWithDefaultBrowser = () => {\n if (platform === 'darwin') {\n execSync(`open \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n if (platform === 'linux') {\n execSync(`xdg-open \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n return false;\n };\n\n try {\n if (openWithChrome()) {\n console.log('🚀 Google Chrome에서 리포트를 열었습니다.');\n\n return;\n }\n } catch {\n // Chrome 실행 실패 시 기본 브라우저로 폴백\n }\n\n try {\n if (openWithDefaultBrowser()) {\n console.log('🚀 기본 브라우저에서 리포트를 열었습니다.');\n\n return;\n }\n } catch (e) {\n console.error('⚠️ 브라우저 열기 실패:', e);\n\n return;\n }\n\n console.error(`⚠️ 지원하지 않는 플랫폼입니다: ${platform}`);\n}\n\nexport function getDiffArgs() {\n const args = process.argv.slice(2);\n const commitIndex = args.indexOf('--commit');\n const { includeParams, excludeParams } = getGitDiffFilter();\n\n let diffArgs = '';\n\n if (commitIndex !== -1) {\n // 특정 커밋 (및 이전 n개) 리뷰\n const commitHash = args[commitIndex + 1];\n if (!commitHash) {\n console.error('❌ 커밋 해시가 제공되지 않았습니다.');\n process.exit(1);\n }\n\n // n값 확인 (optional)\n const nextArg = args[commitIndex + 2];\n let n = 0;\n if (nextArg && !nextArg.startsWith('--')) {\n n = parseInt(nextArg, 10);\n if (isNaN(n)) {\n n = 0;\n }\n }\n\n console.log(`ℹ️ 커밋 '${commitHash}' ${n > 0 ? ` 포함 총 ${n + 1}개의 커밋` : ''}을 리뷰합니다...`);\n diffArgs = `${commitHash}~${n + 1} ${commitHash}`;\n } else {\n // 기본 모드:\n // 1. Unstaged 변경사항 확인\n try {\n const check = execSync(`git diff --name-only -- ${includeParams} ${excludeParams}`).toString();\n if (!check.trim()) {\n console.log('ℹ️ Unstaged 변경사항이 없습니다. 마지막 커밋(HEAD)을 리뷰합니다...');\n diffArgs = 'HEAD~1 HEAD';\n }\n } catch {\n // git diff 실패시 무시\n }\n }\n\n return diffArgs;\n}\n\n// export const ServiceType = {\n// GEMINI: 'gemini',\n// CLAUDE: 'claude',\n// CODEX: 'codex'\n\n// }\n\n/**\n * AI 서비스 선택\n */\nexport function selectAIService() {\n const service = parseServiceFromArgs();\n\n if (!service) {\n console.error('❌ 서비스가 선택되지 않았습니다.');\n process.exit(1);\n }\n\n return service;\n}\n\n/**\n * 터미널에서 라디오 버튼 형태로 AI 서비스를 선택합니다.\n */\nexport async function showSelectionAIService(): Promise<AIServiceType> {\n const selectedServiceFromArgs = parseServiceFromArgs();\n\n if (selectedServiceFromArgs) {\n console.log(`\\n✅ \\u001b[32m${selectedServiceFromArgs}\\u001b[0m 서비스가 선택되었습니다. (--service)\\n`);\n\n return selectedServiceFromArgs;\n }\n\n let selectedIndex = 0;\n\n // Use readline to handle keypresses\n // 키 입력을 처리하기 위해 readline 인터페이스 사용\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n terminal: true\n });\n\n let firstRender = true;\n\n // Hide cursor\n process.stdout.write('\\u001b[?25l');\n\n const render = () => {\n if (!firstRender) {\n // Move cursor back to the starting line of the selection UI\n // We print (1 question line + services.length lines)\n // 선택 UI의 시작 라인으로 커서 이동 (질문 1줄 + 서비스 목록 N줄)\n readline.moveCursor(process.stdout, 0, -(AIServices.length + 1));\n }\n firstRender = false;\n\n // Clear everything from cursor down to avoid ghosting/overlaps\n // 잔상이나 겹침을 방지하기 위해 커서 위치부터 아래쪽 모두 지움\n readline.clearScreenDown(process.stdout);\n\n process.stdout.write(\n '🤖 AI 서비스를 선택해주세요 (\\u001b[33m↑↓ 방향키\\u001b[0m 이동, \\u001b[33mEnter\\u001b[0m 선택):\\n'\n );\n AIServices.forEach((service, index) => {\n if (index === selectedIndex) {\n process.stdout.write(` \\u001b[36m>\\u001b[0m \\u001b[36m◉\\u001b[0m \\u001b[1m${service}\\u001b[0m\\n`);\n } else {\n process.stdout.write(` ◯ ${service}\\n`);\n }\n });\n };\n\n render();\n\n return new Promise((resolve) => {\n const onData = (data: Buffer) => {\n const key = data.toString();\n if (key === '\\u0003') {\n // Ctrl+C\n process.stdout.write('\\u001b[?25h'); // Show cursor\n process.exit(0);\n }\n if (key === '\\x1b[A') {\n // Up arrow\n selectedIndex = (selectedIndex - 1 + AIServices.length) % AIServices.length;\n render();\n } else if (key === '\\x1b[B') {\n // Down arrow\n selectedIndex = (selectedIndex + 1) % AIServices.length;\n render();\n } else if (key === '\\r' || key === '\\n') {\n // Enter\n process.stdin.removeListener('data', onData);\n process.stdin.setRawMode(false);\n process.stdin.pause();\n rl.close();\n\n // Show cursor\n process.stdout.write('\\u001b[?25h');\n\n console.log(`\\n✅ \\u001b[32m${AIServices[selectedIndex]}\\u001b[0m 서비스가 선택되었습니다.\\n`);\n const result = AIServices[selectedIndex];\n if (result) {\n resolve(result);\n }\n }\n };\n\n process.stdin.setRawMode(true);\n process.stdin.resume();\n process.stdin.on('data', onData);\n });\n}\n"]}
@@ -46,7 +46,7 @@ declare function getDiffArgs(): string;
46
46
  /**
47
47
  * AI 서비스 선택
48
48
  */
49
- declare function selectAIService(): string;
49
+ declare function selectAIService(): AIServiceType;
50
50
  /**
51
51
  * 터미널에서 라디오 버튼 형태로 AI 서비스를 선택합니다.
52
52
  */
@@ -46,7 +46,7 @@ declare function getDiffArgs(): string;
46
46
  /**
47
47
  * AI 서비스 선택
48
48
  */
49
- declare function selectAIService(): string;
49
+ declare function selectAIService(): AIServiceType;
50
50
  /**
51
51
  * 터미널에서 라디오 버튼 형태로 AI 서비스를 선택합니다.
52
52
  */
@@ -28,6 +28,21 @@ var ignoreList = [
28
28
  ".review-report/"
29
29
  // 생성되는 리포트 폴더도 제외
30
30
  ];
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) {
35
+ return "";
36
+ }
37
+ const normalizedService = rawService.toLowerCase();
38
+ if (AIServices.includes(normalizedService)) {
39
+ return normalizedService;
40
+ }
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);
45
+ }
31
46
  function isTestMode(args = process.argv.slice(2)) {
32
47
  return args.includes("--test");
33
48
  }
@@ -158,9 +173,7 @@ function getDiffArgs() {
158
173
  return diffArgs;
159
174
  }
160
175
  function selectAIService() {
161
- const args = process.argv.slice(2);
162
- const serviceIndex = args.indexOf("--service");
163
- const service = args[serviceIndex + 1];
176
+ const service = parseServiceFromArgs();
164
177
  if (!service) {
165
178
  console.error("\u274C \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
166
179
  process.exit(1);
@@ -168,6 +181,13 @@ function selectAIService() {
168
181
  return service;
169
182
  }
170
183
  async function showSelectionAIService() {
184
+ const selectedServiceFromArgs = parseServiceFromArgs();
185
+ if (selectedServiceFromArgs) {
186
+ console.log(`
187
+ \u2705 \x1B[32m${selectedServiceFromArgs}\x1B[0m \uC11C\uBE44\uC2A4\uAC00 \uC120\uD0DD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (--service)
188
+ `);
189
+ return selectedServiceFromArgs;
190
+ }
171
191
  let selectedIndex = 0;
172
192
  const rl = readline.createInterface({
173
193
  input: process.stdin,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/common/helper.ts"],"names":[],"mappings":"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","file":"helper.js","sourcesContent":["import { execSync } from 'child_process';\nimport fs from 'fs';\nimport path from 'path';\nimport readline from 'readline';\nimport { fileURLToPath } from 'url';\n\nimport { AIServiceType } from './types';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n// 설치된 위치에 맞게 규칙/양식 파일 경로를 계산 (dist에서 src로 이동 등 고려)\n// dist/common/helper.js 가 실행되므로 __dirname은 .../dist/common 입니다.\n// 따라서 ../../src/common/rules 로 이동해야 원본 소스의 규칙 파일을 찾을 수 있습니다.\nexport const rulesPath = path.resolve(__dirname, '../../src/common/rules/review-rules.md');\nexport const namingRulesPath = path.resolve(__dirname, '../../src/common/rules/naming-rule.md');\nexport const codingConventionRulesPath = path.resolve(__dirname, '../../src/common/rules/coding-convention.md');\nexport const reviewFormPath = path.resolve(__dirname, '../../src/common/form/review-form.md');\nexport const reviewFormOneByOnePath = path.resolve(__dirname, '../../src/common/form/review-form-one-by-one.md');\nexport const REPORT_DIR = '.review-report';\nexport const tempDiffPath = 'temp_diff.txt';\nexport const AIServices: AIServiceType[] = ['gemini', 'claude', 'codex'];\nexport const ignoreList = [\n 'package.json',\n '*.yml',\n '*.md',\n '*.lock',\n 'dist/',\n 'node_modules/',\n 'assets/',\n 'public/',\n '*.json',\n '*.yaml',\n '.review-report/' // 생성되는 리포트 폴더도 제외\n];\n\nexport function isTestMode(args: string[] = process.argv.slice(2)) {\n return args.includes('--test');\n}\n\nexport function createTraceLogger(scope: string, args: string[] = process.argv.slice(2)) {\n const enabled = isTestMode(args);\n\n return (step: string, detail?: string) => {\n if (!enabled) {\n return;\n }\n\n console.log(`[TRACE][${scope}] ${step}${detail ? ` | ${detail}` : ''}`);\n };\n}\n\nexport function getNextFilePath(dir: string, baseName: string, extension: string) {\n let counter = 1;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const filePath = path.join(dir, `${baseName}-${counter}${extension}`);\n if (!fs.existsSync(filePath)) {\n return filePath;\n }\n counter++;\n }\n}\n\nexport function deleteFile(filePath: string) {\n if (fs.existsSync(filePath)) {\n fs.unlinkSync(filePath);\n }\n}\n\n/**\n * 임시파일 삭제\n */\nexport function deleteTempDiff() {\n deleteFile(tempDiffPath);\n}\n\n/**\n * 리뷰 결과 폴더 생성\n */\nexport function createReportDirectory() {\n if (!fs.existsSync(REPORT_DIR)) {\n fs.mkdirSync(REPORT_DIR, { recursive: true });\n }\n}\n\n/**\n * 현재 시간 문자열 생성\n */\nexport function getNowString() {\n const now = new Date();\n const YYYY = now.getFullYear();\n const MM = String(now.getMonth() + 1).padStart(2, '0');\n const DD = String(now.getDate()).padStart(2, '0');\n const HH = String(now.getHours()).padStart(2, '0');\n const mm = String(now.getMinutes()).padStart(2, '0');\n const ss = String(now.getSeconds()).padStart(2, '0');\n\n return `${YYYY}-${MM}-${DD}_${HH}-${mm}-${ss}`;\n}\n\nexport function getGitDiffFilter() {\n // 1. 리뷰 대상 파일 확장자 정의\n const includeExtensions = ['*.ts', '*.tsx', '*.js', '*.jsx'];\n\n // ignoreList 를 import 하여 재사용하여 작성한다.\n const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);\n\n // const excludePatterns = [':(exclude)*.lock', ':(exclude)dist/', ':(exclude)*.md'];\n\n // 2. 변경된 파일 목록 가져오기 (각 패턴을 따옴표로 감싸서 쉘 에러 방지)\n const quote = (pattern: string) => `\"${pattern}\"`;\n const includeParams = includeExtensions.map(quote).join(' ');\n const excludeParams = excludePatterns.map(quote).join(' ');\n\n return { includeParams, excludeParams };\n}\n\n/**\n * openReport를 OS별로 동작하도록 변경\n * 우선순위:\n * 1. Chrome 시도\n * - macOS: open -a \"Google Chrome\" \"<path>\"\n * - Ubuntu/Linux: google-chrome \"<path>\"\n * 2. 실패 시 기본 브라우저로 폴백\n * - macOS: open \"<path>\"\n * - Ubuntu/Linux: xdg-open \"<path>\"\n * 3. 둘 다 실패하면 에러 출력\n * 4. 미지원 플랫폼이면 플랫폼 경고 출력\n */\nexport function openReport(reportPath: string) {\n const resolvedPath = path.resolve(reportPath);\n const { platform } = process;\n\n const openWithChrome = () => {\n if (platform === 'darwin') {\n execSync(`open -a \"Google Chrome\" \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n if (platform === 'linux') {\n execSync(`google-chrome \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n return false;\n };\n\n const openWithDefaultBrowser = () => {\n if (platform === 'darwin') {\n execSync(`open \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n if (platform === 'linux') {\n execSync(`xdg-open \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n return false;\n };\n\n try {\n if (openWithChrome()) {\n console.log('🚀 Google Chrome에서 리포트를 열었습니다.');\n\n return;\n }\n } catch {\n // Chrome 실행 실패 시 기본 브라우저로 폴백\n }\n\n try {\n if (openWithDefaultBrowser()) {\n console.log('🚀 기본 브라우저에서 리포트를 열었습니다.');\n\n return;\n }\n } catch (e) {\n console.error('⚠️ 브라우저 열기 실패:', e);\n\n return;\n }\n\n console.error(`⚠️ 지원하지 않는 플랫폼입니다: ${platform}`);\n}\n\nexport function getDiffArgs() {\n const args = process.argv.slice(2);\n const commitIndex = args.indexOf('--commit');\n const { includeParams, excludeParams } = getGitDiffFilter();\n\n let diffArgs = '';\n\n if (commitIndex !== -1) {\n // 특정 커밋 (및 이전 n개) 리뷰\n const commitHash = args[commitIndex + 1];\n if (!commitHash) {\n console.error('❌ 커밋 해시가 제공되지 않았습니다.');\n process.exit(1);\n }\n\n // n값 확인 (optional)\n const nextArg = args[commitIndex + 2];\n let n = 0;\n if (nextArg && !nextArg.startsWith('--')) {\n n = parseInt(nextArg, 10);\n if (isNaN(n)) {\n n = 0;\n }\n }\n\n console.log(`ℹ️ 커밋 '${commitHash}' ${n > 0 ? ` 포함 총 ${n + 1}개의 커밋` : ''}을 리뷰합니다...`);\n diffArgs = `${commitHash}~${n + 1} ${commitHash}`;\n } else {\n // 기본 모드:\n // 1. Unstaged 변경사항 확인\n try {\n const check = execSync(`git diff --name-only -- ${includeParams} ${excludeParams}`).toString();\n if (!check.trim()) {\n console.log('ℹ️ Unstaged 변경사항이 없습니다. 마지막 커밋(HEAD)을 리뷰합니다...');\n diffArgs = 'HEAD~1 HEAD';\n }\n } catch {\n // git diff 실패시 무시\n }\n }\n\n return diffArgs;\n}\n\n// export const ServiceType = {\n// GEMINI: 'gemini',\n// CLAUDE: 'claude',\n// CODEX: 'codex'\n\n// }\n\n/**\n * AI 서비스 선택\n */\nexport function selectAIService() {\n const args = process.argv.slice(2);\n const serviceIndex = args.indexOf('--service');\n const service = args[serviceIndex + 1];\n if (!service) {\n console.error('❌ 서비스가 선택되지 않았습니다.');\n process.exit(1);\n }\n\n return service;\n}\n\n/**\n * 터미널에서 라디오 버튼 형태로 AI 서비스를 선택합니다.\n */\nexport async function showSelectionAIService(): Promise<AIServiceType> {\n let selectedIndex = 0;\n\n // Use readline to handle keypresses\n // 키 입력을 처리하기 위해 readline 인터페이스 사용\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n terminal: true\n });\n\n let firstRender = true;\n\n // Hide cursor\n process.stdout.write('\\u001b[?25l');\n\n const render = () => {\n if (!firstRender) {\n // Move cursor back to the starting line of the selection UI\n // We print (1 question line + services.length lines)\n // 선택 UI의 시작 라인으로 커서 이동 (질문 1줄 + 서비스 목록 N줄)\n readline.moveCursor(process.stdout, 0, -(AIServices.length + 1));\n }\n firstRender = false;\n\n // Clear everything from cursor down to avoid ghosting/overlaps\n // 잔상이나 겹침을 방지하기 위해 커서 위치부터 아래쪽 모두 지움\n readline.clearScreenDown(process.stdout);\n\n process.stdout.write(\n '🤖 AI 서비스를 선택해주세요 (\\u001b[33m↑↓ 방향키\\u001b[0m 이동, \\u001b[33mEnter\\u001b[0m 선택):\\n'\n );\n AIServices.forEach((service, index) => {\n if (index === selectedIndex) {\n process.stdout.write(` \\u001b[36m>\\u001b[0m \\u001b[36m◉\\u001b[0m \\u001b[1m${service}\\u001b[0m\\n`);\n } else {\n process.stdout.write(` ◯ ${service}\\n`);\n }\n });\n };\n\n render();\n\n return new Promise((resolve) => {\n const onData = (data: Buffer) => {\n const key = data.toString();\n if (key === '\\u0003') {\n // Ctrl+C\n process.stdout.write('\\u001b[?25h'); // Show cursor\n process.exit(0);\n }\n if (key === '\\x1b[A') {\n // Up arrow\n selectedIndex = (selectedIndex - 1 + AIServices.length) % AIServices.length;\n render();\n } else if (key === '\\x1b[B') {\n // Down arrow\n selectedIndex = (selectedIndex + 1) % AIServices.length;\n render();\n } else if (key === '\\r' || key === '\\n') {\n // Enter\n process.stdin.removeListener('data', onData);\n process.stdin.setRawMode(false);\n process.stdin.pause();\n rl.close();\n\n // Show cursor\n process.stdout.write('\\u001b[?25h');\n\n console.log(`\\n✅ \\u001b[32m${AIServices[selectedIndex]}\\u001b[0m 서비스가 선택되었습니다.\\n`);\n const result = AIServices[selectedIndex];\n if (result) {\n resolve(result);\n }\n }\n };\n\n process.stdin.setRawMode(true);\n process.stdin.resume();\n process.stdin.on('data', onData);\n });\n}\n"]}
1
+ {"version":3,"sources":["../../src/common/helper.ts"],"names":[],"mappings":"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","file":"helper.js","sourcesContent":["import { execSync } from 'child_process';\nimport fs from 'fs';\nimport path from 'path';\nimport readline from 'readline';\nimport { fileURLToPath } from 'url';\n\nimport { AIServiceType } from './types';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n// 설치된 위치에 맞게 규칙/양식 파일 경로를 계산 (dist에서 src로 이동 등 고려)\n// dist/common/helper.js 가 실행되므로 __dirname은 .../dist/common 입니다.\n// 따라서 ../../src/common/rules 로 이동해야 원본 소스의 규칙 파일을 찾을 수 있습니다.\nexport const rulesPath = path.resolve(__dirname, '../../src/common/rules/review-rules.md');\nexport const namingRulesPath = path.resolve(__dirname, '../../src/common/rules/naming-rule.md');\nexport const codingConventionRulesPath = path.resolve(__dirname, '../../src/common/rules/coding-convention.md');\nexport const reviewFormPath = path.resolve(__dirname, '../../src/common/form/review-form.md');\nexport const reviewFormOneByOnePath = path.resolve(__dirname, '../../src/common/form/review-form-one-by-one.md');\nexport const REPORT_DIR = '.review-report';\nexport const tempDiffPath = 'temp_diff.txt';\nexport const AIServices: AIServiceType[] = ['gemini', 'claude', 'codex'];\nexport const ignoreList = [\n 'package.json',\n '*.yml',\n '*.md',\n '*.lock',\n 'dist/',\n 'node_modules/',\n 'assets/',\n 'public/',\n '*.json',\n '*.yaml',\n '.review-report/' // 생성되는 리포트 폴더도 제외\n];\n\nfunction parseServiceFromArgs(args: string[] = process.argv.slice(2)): AIServiceType | '' {\n const serviceIndex = args.indexOf('--service');\n const rawService = serviceIndex !== -1 ? args[serviceIndex + 1] : '';\n\n if (!rawService) {\n return '';\n }\n\n const normalizedService = rawService.toLowerCase();\n\n if (AIServices.includes(normalizedService as AIServiceType)) {\n return normalizedService as AIServiceType;\n }\n\n console.error(\n `❌ 지원하지 않는 서비스입니다: ${rawService}. 사용 가능 값: ${AIServices.join(', ')} (예: --service codex)`\n );\n process.exit(1);\n}\n\nexport function isTestMode(args: string[] = process.argv.slice(2)) {\n return args.includes('--test');\n}\n\nexport function createTraceLogger(scope: string, args: string[] = process.argv.slice(2)) {\n const enabled = isTestMode(args);\n\n return (step: string, detail?: string) => {\n if (!enabled) {\n return;\n }\n\n console.log(`[TRACE][${scope}] ${step}${detail ? ` | ${detail}` : ''}`);\n };\n}\n\nexport function getNextFilePath(dir: string, baseName: string, extension: string) {\n let counter = 1;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const filePath = path.join(dir, `${baseName}-${counter}${extension}`);\n if (!fs.existsSync(filePath)) {\n return filePath;\n }\n counter++;\n }\n}\n\nexport function deleteFile(filePath: string) {\n if (fs.existsSync(filePath)) {\n fs.unlinkSync(filePath);\n }\n}\n\n/**\n * 임시파일 삭제\n */\nexport function deleteTempDiff() {\n deleteFile(tempDiffPath);\n}\n\n/**\n * 리뷰 결과 폴더 생성\n */\nexport function createReportDirectory() {\n if (!fs.existsSync(REPORT_DIR)) {\n fs.mkdirSync(REPORT_DIR, { recursive: true });\n }\n}\n\n/**\n * 현재 시간 문자열 생성\n */\nexport function getNowString() {\n const now = new Date();\n const YYYY = now.getFullYear();\n const MM = String(now.getMonth() + 1).padStart(2, '0');\n const DD = String(now.getDate()).padStart(2, '0');\n const HH = String(now.getHours()).padStart(2, '0');\n const mm = String(now.getMinutes()).padStart(2, '0');\n const ss = String(now.getSeconds()).padStart(2, '0');\n\n return `${YYYY}-${MM}-${DD}_${HH}-${mm}-${ss}`;\n}\n\nexport function getGitDiffFilter() {\n // 1. 리뷰 대상 파일 확장자 정의\n const includeExtensions = ['*.ts', '*.tsx', '*.js', '*.jsx'];\n\n // ignoreList 를 import 하여 재사용하여 작성한다.\n const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);\n\n // const excludePatterns = [':(exclude)*.lock', ':(exclude)dist/', ':(exclude)*.md'];\n\n // 2. 변경된 파일 목록 가져오기 (각 패턴을 따옴표로 감싸서 쉘 에러 방지)\n const quote = (pattern: string) => `\"${pattern}\"`;\n const includeParams = includeExtensions.map(quote).join(' ');\n const excludeParams = excludePatterns.map(quote).join(' ');\n\n return { includeParams, excludeParams };\n}\n\n/**\n * openReport를 OS별로 동작하도록 변경\n * 우선순위:\n * 1. Chrome 시도\n * - macOS: open -a \"Google Chrome\" \"<path>\"\n * - Ubuntu/Linux: google-chrome \"<path>\"\n * 2. 실패 시 기본 브라우저로 폴백\n * - macOS: open \"<path>\"\n * - Ubuntu/Linux: xdg-open \"<path>\"\n * 3. 둘 다 실패하면 에러 출력\n * 4. 미지원 플랫폼이면 플랫폼 경고 출력\n */\nexport function openReport(reportPath: string) {\n const resolvedPath = path.resolve(reportPath);\n const { platform } = process;\n\n const openWithChrome = () => {\n if (platform === 'darwin') {\n execSync(`open -a \"Google Chrome\" \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n if (platform === 'linux') {\n execSync(`google-chrome \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n return false;\n };\n\n const openWithDefaultBrowser = () => {\n if (platform === 'darwin') {\n execSync(`open \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n if (platform === 'linux') {\n execSync(`xdg-open \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n return false;\n };\n\n try {\n if (openWithChrome()) {\n console.log('🚀 Google Chrome에서 리포트를 열었습니다.');\n\n return;\n }\n } catch {\n // Chrome 실행 실패 시 기본 브라우저로 폴백\n }\n\n try {\n if (openWithDefaultBrowser()) {\n console.log('🚀 기본 브라우저에서 리포트를 열었습니다.');\n\n return;\n }\n } catch (e) {\n console.error('⚠️ 브라우저 열기 실패:', e);\n\n return;\n }\n\n console.error(`⚠️ 지원하지 않는 플랫폼입니다: ${platform}`);\n}\n\nexport function getDiffArgs() {\n const args = process.argv.slice(2);\n const commitIndex = args.indexOf('--commit');\n const { includeParams, excludeParams } = getGitDiffFilter();\n\n let diffArgs = '';\n\n if (commitIndex !== -1) {\n // 특정 커밋 (및 이전 n개) 리뷰\n const commitHash = args[commitIndex + 1];\n if (!commitHash) {\n console.error('❌ 커밋 해시가 제공되지 않았습니다.');\n process.exit(1);\n }\n\n // n값 확인 (optional)\n const nextArg = args[commitIndex + 2];\n let n = 0;\n if (nextArg && !nextArg.startsWith('--')) {\n n = parseInt(nextArg, 10);\n if (isNaN(n)) {\n n = 0;\n }\n }\n\n console.log(`ℹ️ 커밋 '${commitHash}' ${n > 0 ? ` 포함 총 ${n + 1}개의 커밋` : ''}을 리뷰합니다...`);\n diffArgs = `${commitHash}~${n + 1} ${commitHash}`;\n } else {\n // 기본 모드:\n // 1. Unstaged 변경사항 확인\n try {\n const check = execSync(`git diff --name-only -- ${includeParams} ${excludeParams}`).toString();\n if (!check.trim()) {\n console.log('ℹ️ Unstaged 변경사항이 없습니다. 마지막 커밋(HEAD)을 리뷰합니다...');\n diffArgs = 'HEAD~1 HEAD';\n }\n } catch {\n // git diff 실패시 무시\n }\n }\n\n return diffArgs;\n}\n\n// export const ServiceType = {\n// GEMINI: 'gemini',\n// CLAUDE: 'claude',\n// CODEX: 'codex'\n\n// }\n\n/**\n * AI 서비스 선택\n */\nexport function selectAIService() {\n const service = parseServiceFromArgs();\n\n if (!service) {\n console.error('❌ 서비스가 선택되지 않았습니다.');\n process.exit(1);\n }\n\n return service;\n}\n\n/**\n * 터미널에서 라디오 버튼 형태로 AI 서비스를 선택합니다.\n */\nexport async function showSelectionAIService(): Promise<AIServiceType> {\n const selectedServiceFromArgs = parseServiceFromArgs();\n\n if (selectedServiceFromArgs) {\n console.log(`\\n✅ \\u001b[32m${selectedServiceFromArgs}\\u001b[0m 서비스가 선택되었습니다. (--service)\\n`);\n\n return selectedServiceFromArgs;\n }\n\n let selectedIndex = 0;\n\n // Use readline to handle keypresses\n // 키 입력을 처리하기 위해 readline 인터페이스 사용\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n terminal: true\n });\n\n let firstRender = true;\n\n // Hide cursor\n process.stdout.write('\\u001b[?25l');\n\n const render = () => {\n if (!firstRender) {\n // Move cursor back to the starting line of the selection UI\n // We print (1 question line + services.length lines)\n // 선택 UI의 시작 라인으로 커서 이동 (질문 1줄 + 서비스 목록 N줄)\n readline.moveCursor(process.stdout, 0, -(AIServices.length + 1));\n }\n firstRender = false;\n\n // Clear everything from cursor down to avoid ghosting/overlaps\n // 잔상이나 겹침을 방지하기 위해 커서 위치부터 아래쪽 모두 지움\n readline.clearScreenDown(process.stdout);\n\n process.stdout.write(\n '🤖 AI 서비스를 선택해주세요 (\\u001b[33m↑↓ 방향키\\u001b[0m 이동, \\u001b[33mEnter\\u001b[0m 선택):\\n'\n );\n AIServices.forEach((service, index) => {\n if (index === selectedIndex) {\n process.stdout.write(` \\u001b[36m>\\u001b[0m \\u001b[36m◉\\u001b[0m \\u001b[1m${service}\\u001b[0m\\n`);\n } else {\n process.stdout.write(` ◯ ${service}\\n`);\n }\n });\n };\n\n render();\n\n return new Promise((resolve) => {\n const onData = (data: Buffer) => {\n const key = data.toString();\n if (key === '\\u0003') {\n // Ctrl+C\n process.stdout.write('\\u001b[?25h'); // Show cursor\n process.exit(0);\n }\n if (key === '\\x1b[A') {\n // Up arrow\n selectedIndex = (selectedIndex - 1 + AIServices.length) % AIServices.length;\n render();\n } else if (key === '\\x1b[B') {\n // Down arrow\n selectedIndex = (selectedIndex + 1) % AIServices.length;\n render();\n } else if (key === '\\r' || key === '\\n') {\n // Enter\n process.stdin.removeListener('data', onData);\n process.stdin.setRawMode(false);\n process.stdin.pause();\n rl.close();\n\n // Show cursor\n process.stdout.write('\\u001b[?25h');\n\n console.log(`\\n✅ \\u001b[32m${AIServices[selectedIndex]}\\u001b[0m 서비스가 선택되었습니다.\\n`);\n const result = AIServices[selectedIndex];\n if (result) {\n resolve(result);\n }\n }\n };\n\n process.stdin.setRawMode(true);\n process.stdin.resume();\n process.stdin.on('data', onData);\n });\n}\n"]}
@@ -33,40 +33,147 @@ function createTraceLogger(scope, args2 = process.argv.slice(2)) {
33
33
  // src/pr-review/claude/claude-commander.ts
34
34
  var args = process.argv.slice(2);
35
35
  var trace = createTraceLogger("claude-commander", args);
36
- var createClaudeCommand = (tempDiffPath, reviewFormPath2) => {
37
- trace("createClaudeCommand:start", `tempDiffPath=${tempDiffPath}, reviewFormPath=${reviewFormPath2}`);
38
- let modelOption = "";
39
- if (args.includes("--review")) {
40
- modelOption = "--model opus";
41
- trace("model:review", modelOption);
42
- } else if (args.includes("--flash")) {
43
- modelOption = "--model haiku";
44
- trace("model:flash", modelOption);
45
- } else {
46
- const modelIndex = args.indexOf("--model");
47
- if (modelIndex !== -1 && args[modelIndex + 1]) {
48
- 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.");
49
- modelOption = `--model ${args[modelIndex + 1]}`;
50
- trace("model:custom", modelOption);
51
- } else {
52
- console.warn("\u26A0\uFE0F \uBAA8\uB378\uC774 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uAE30\uBCF8 \uBAA8\uB378\uC778 sonnet\uC744 \uC0AC\uC6A9\uD569\uB2C8\uB2E4.");
53
- modelOption = "--model sonnet";
54
- trace("model:default", modelOption);
36
+ var ALLOWED_REASONING_EFFORTS = ["minimal", "low", "medium", "high"];
37
+ function shellQuote(value) {
38
+ return `'${value.replace(/'/g, `'\\''`)}'`;
39
+ }
40
+ function getArgValue(flag) {
41
+ const index = args.indexOf(flag);
42
+ if (index === -1 || !args[index + 1]) {
43
+ return "";
44
+ }
45
+ return args[index + 1];
46
+ }
47
+ function toUnique(values) {
48
+ const seen = /* @__PURE__ */ new Set();
49
+ return values.filter((value) => {
50
+ if (!value || seen.has(value)) {
51
+ return false;
55
52
  }
53
+ seen.add(value);
54
+ return true;
55
+ });
56
+ }
57
+ function normalizeEffort(level) {
58
+ if (level === "minimal") {
59
+ return "low";
60
+ }
61
+ return level;
62
+ }
63
+ function resolveReasoningEffort() {
64
+ const customReasoningEffort = getArgValue("--reasoning-effort") || getArgValue("--effort");
65
+ if (customReasoningEffort) {
66
+ if (ALLOWED_REASONING_EFFORTS.includes(customReasoningEffort)) {
67
+ const normalized = normalizeEffort(customReasoningEffort);
68
+ trace("reasoning:custom", `${customReasoningEffort} -> ${normalized}`);
69
+ if (customReasoningEffort === "minimal") {
70
+ console.warn("\u26A0\uFE0F Claude\uB294 minimal\uC744 \uC9C1\uC811 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 low\uB85C \uB9E4\uD551\uD569\uB2C8\uB2E4.");
71
+ }
72
+ return normalized;
73
+ }
74
+ console.warn(
75
+ `\u26A0\uFE0F \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uB294 reasoning effort(${customReasoningEffort})\uC785\uB2C8\uB2E4. allowed: ${ALLOWED_REASONING_EFFORTS.join(
76
+ ", "
77
+ )}`
78
+ );
79
+ }
80
+ if (args.includes("--flash")) {
81
+ trace("reasoning:flash-default", "low");
82
+ return "low";
83
+ }
84
+ if (args.includes("--review")) {
85
+ trace("reasoning:review-default", "high");
86
+ return "high";
56
87
  }
88
+ trace("reasoning:default", "medium");
89
+ return "medium";
90
+ }
91
+ function resolvePrimaryAlias() {
92
+ if (args.includes("--review")) {
93
+ trace("model:mode-alias", "opus");
94
+ return "opus";
95
+ }
96
+ if (args.includes("--flash")) {
97
+ trace("model:mode-alias", "haiku");
98
+ return "haiku";
99
+ }
100
+ trace("model:default-alias", "sonnet");
101
+ return "sonnet";
102
+ }
103
+ function getAliasFallbacks(primaryAlias) {
104
+ if (primaryAlias === "opus") {
105
+ return ["opus", "sonnet", "haiku"];
106
+ }
107
+ if (primaryAlias === "haiku") {
108
+ return ["haiku", "sonnet"];
109
+ }
110
+ return [primaryAlias, "sonnet", "haiku"];
111
+ }
112
+ function buildClaudeExecCommand(options) {
113
+ const { tempDiffPath, prompt, systemPromptFiles, effort, model, fallbackModel } = options;
114
+ const modelOption = model ? `--model ${shellQuote(model)}` : "";
115
+ const fallbackOption = model && fallbackModel ? `--fallback-model ${shellQuote(fallbackModel)}` : "";
116
+ const effortOption = `--effort ${shellQuote(effort)}`;
117
+ const appendedPromptFiles = systemPromptFiles.map((path2) => `--append-system-prompt-file ${shellQuote(path2)}`).join(" ");
118
+ return `cat ${shellQuote(tempDiffPath)} | claude ${[
119
+ modelOption,
120
+ fallbackOption,
121
+ effortOption,
122
+ appendedPromptFiles,
123
+ "-p",
124
+ shellQuote(prompt)
125
+ ].filter(Boolean).join(" ")}`;
126
+ }
127
+ var createClaudeCommand = (tempDiffPath, reviewFormPath2) => {
128
+ trace("createClaudeCommand:start", `tempDiffPath=${tempDiffPath}, reviewFormPath=${reviewFormPath2}`);
129
+ const customModel = getArgValue("--model");
130
+ const effort = resolveReasoningEffort();
131
+ const primaryAlias = resolvePrimaryAlias();
132
+ const aliasFallbacks = toUnique(getAliasFallbacks(primaryAlias));
57
133
  const rules = [
58
134
  { path: rulesPath, display: "\uB8F0\uC14B" },
59
135
  { path: namingRulesPath, display: "\uB124\uC774\uBC0D \uADDC\uCE59" },
60
136
  { path: codingConventionRulesPath, display: "\uCF54\uB529 \uCEE8\uBCA4\uC158" }
61
137
  ];
62
- const systemPromptFiles = rules.filter((rule) => fs__default.default.existsSync(rule.path)).map((rule) => `--append-system-prompt-file ${rule.path}`).join(" ");
63
- trace(
64
- "rules:loaded",
65
- `count=${systemPromptFiles ? systemPromptFiles.split("--append-system-prompt-file").length - 1 : 0}`
66
- );
67
- const reviewFormOption = fs__default.default.existsSync(reviewFormPath2) ? `--append-system-prompt-file ${reviewFormPath2}` : "";
68
- trace("reviewForm:status", reviewFormOption ? "exists" : "missing");
69
- const command = `cat ${tempDiffPath} | claude ${modelOption} ${systemPromptFiles} ${reviewFormOption} -p "\uC704 \uADDC\uCE59\uB4E4\uC744 \uCC38\uACE0\uD558\uC5EC \uC774 diff\uB97C \uCF54\uB4DC\uB9AC\uBDF0\uD574\uC918. \uB9AC\uBDF0\uC591\uC2DD\uC5D0 \uB9DE\uCDB0\uC11C \uC791\uC131\uD574\uC918."`;
138
+ const existingRuleFiles = rules.filter((rule) => fs__default.default.existsSync(rule.path)).map((rule) => rule.path);
139
+ trace("rules:loaded", `count=${existingRuleFiles.length}`);
140
+ const reviewFormExists = fs__default.default.existsSync(reviewFormPath2);
141
+ trace("reviewForm:status", reviewFormExists ? "exists" : "missing");
142
+ const systemPromptFiles = reviewFormExists ? [...existingRuleFiles, reviewFormPath2] : existingRuleFiles;
143
+ const prompt = "\uC704 \uADDC\uCE59\uB4E4\uC744 \uCC38\uACE0\uD558\uC5EC \uC774 diff\uB97C \uCF54\uB4DC\uB9AC\uBDF0\uD574\uC918. \uB9AC\uBDF0\uC591\uC2DD\uC5D0 \uB9DE\uCDB0\uC11C \uC791\uC131\uD574\uC918.";
144
+ const modelCandidates = toUnique(customModel ? [customModel, ...aliasFallbacks] : aliasFallbacks);
145
+ trace("model:candidates", modelCandidates.join(", "));
146
+ if (customModel) {
147
+ console.warn(
148
+ `\u26A0\uFE0F \uCEE4\uC2A4\uD140 \uBAA8\uB378(${customModel})\uC744 \uC6B0\uC120 \uC2DC\uB3C4\uD558\uACE0 \uC2E4\uD328\uD558\uBA74 alias(${aliasFallbacks.join(
149
+ " -> "
150
+ )}) \uBC0F \uAE30\uBCF8 \uBAA8\uB378 \uC21C\uC73C\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.`
151
+ );
152
+ } else {
153
+ console.warn(
154
+ `\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.`
155
+ );
156
+ }
157
+ const commandCandidates = modelCandidates.map((model, index) => {
158
+ const fallbackModel = modelCandidates[index + 1];
159
+ return buildClaudeExecCommand({
160
+ tempDiffPath,
161
+ prompt,
162
+ systemPromptFiles,
163
+ effort,
164
+ model,
165
+ fallbackModel
166
+ });
167
+ });
168
+ const command = [
169
+ ...commandCandidates,
170
+ buildClaudeExecCommand({
171
+ tempDiffPath,
172
+ prompt,
173
+ systemPromptFiles,
174
+ effort
175
+ })
176
+ ].join(" || ");
70
177
  trace("command:created");
71
178
  if (args.includes("--test")) {
72
179
  const safeCommand = command.replace(/"/g, '\\"');
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/common/helper.ts","../../../src/pr-review/claude/claude-commander.ts"],"names":["__dirname","path","fileURLToPath","args","reviewFormPath","fs"],"mappings":"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","file":"claude-commander.cjs","sourcesContent":["import { execSync } from 'child_process';\nimport fs from 'fs';\nimport path from 'path';\nimport readline from 'readline';\nimport { fileURLToPath } from 'url';\n\nimport { AIServiceType } from './types';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n// 설치된 위치에 맞게 규칙/양식 파일 경로를 계산 (dist에서 src로 이동 등 고려)\n// dist/common/helper.js 가 실행되므로 __dirname은 .../dist/common 입니다.\n// 따라서 ../../src/common/rules 로 이동해야 원본 소스의 규칙 파일을 찾을 수 있습니다.\nexport const rulesPath = path.resolve(__dirname, '../../src/common/rules/review-rules.md');\nexport const namingRulesPath = path.resolve(__dirname, '../../src/common/rules/naming-rule.md');\nexport const codingConventionRulesPath = path.resolve(__dirname, '../../src/common/rules/coding-convention.md');\nexport const reviewFormPath = path.resolve(__dirname, '../../src/common/form/review-form.md');\nexport const reviewFormOneByOnePath = path.resolve(__dirname, '../../src/common/form/review-form-one-by-one.md');\nexport const REPORT_DIR = '.review-report';\nexport const tempDiffPath = 'temp_diff.txt';\nexport const AIServices: AIServiceType[] = ['gemini', 'claude', 'codex'];\nexport const ignoreList = [\n 'package.json',\n '*.yml',\n '*.md',\n '*.lock',\n 'dist/',\n 'node_modules/',\n 'assets/',\n 'public/',\n '*.json',\n '*.yaml',\n '.review-report/' // 생성되는 리포트 폴더도 제외\n];\n\nexport function isTestMode(args: string[] = process.argv.slice(2)) {\n return args.includes('--test');\n}\n\nexport function createTraceLogger(scope: string, args: string[] = process.argv.slice(2)) {\n const enabled = isTestMode(args);\n\n return (step: string, detail?: string) => {\n if (!enabled) {\n return;\n }\n\n console.log(`[TRACE][${scope}] ${step}${detail ? ` | ${detail}` : ''}`);\n };\n}\n\nexport function getNextFilePath(dir: string, baseName: string, extension: string) {\n let counter = 1;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const filePath = path.join(dir, `${baseName}-${counter}${extension}`);\n if (!fs.existsSync(filePath)) {\n return filePath;\n }\n counter++;\n }\n}\n\nexport function deleteFile(filePath: string) {\n if (fs.existsSync(filePath)) {\n fs.unlinkSync(filePath);\n }\n}\n\n/**\n * 임시파일 삭제\n */\nexport function deleteTempDiff() {\n deleteFile(tempDiffPath);\n}\n\n/**\n * 리뷰 결과 폴더 생성\n */\nexport function createReportDirectory() {\n if (!fs.existsSync(REPORT_DIR)) {\n fs.mkdirSync(REPORT_DIR, { recursive: true });\n }\n}\n\n/**\n * 현재 시간 문자열 생성\n */\nexport function getNowString() {\n const now = new Date();\n const YYYY = now.getFullYear();\n const MM = String(now.getMonth() + 1).padStart(2, '0');\n const DD = String(now.getDate()).padStart(2, '0');\n const HH = String(now.getHours()).padStart(2, '0');\n const mm = String(now.getMinutes()).padStart(2, '0');\n const ss = String(now.getSeconds()).padStart(2, '0');\n\n return `${YYYY}-${MM}-${DD}_${HH}-${mm}-${ss}`;\n}\n\nexport function getGitDiffFilter() {\n // 1. 리뷰 대상 파일 확장자 정의\n const includeExtensions = ['*.ts', '*.tsx', '*.js', '*.jsx'];\n\n // ignoreList 를 import 하여 재사용하여 작성한다.\n const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);\n\n // const excludePatterns = [':(exclude)*.lock', ':(exclude)dist/', ':(exclude)*.md'];\n\n // 2. 변경된 파일 목록 가져오기 (각 패턴을 따옴표로 감싸서 쉘 에러 방지)\n const quote = (pattern: string) => `\"${pattern}\"`;\n const includeParams = includeExtensions.map(quote).join(' ');\n const excludeParams = excludePatterns.map(quote).join(' ');\n\n return { includeParams, excludeParams };\n}\n\n/**\n * openReport를 OS별로 동작하도록 변경\n * 우선순위:\n * 1. Chrome 시도\n * - macOS: open -a \"Google Chrome\" \"<path>\"\n * - Ubuntu/Linux: google-chrome \"<path>\"\n * 2. 실패 시 기본 브라우저로 폴백\n * - macOS: open \"<path>\"\n * - Ubuntu/Linux: xdg-open \"<path>\"\n * 3. 둘 다 실패하면 에러 출력\n * 4. 미지원 플랫폼이면 플랫폼 경고 출력\n */\nexport function openReport(reportPath: string) {\n const resolvedPath = path.resolve(reportPath);\n const { platform } = process;\n\n const openWithChrome = () => {\n if (platform === 'darwin') {\n execSync(`open -a \"Google Chrome\" \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n if (platform === 'linux') {\n execSync(`google-chrome \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n return false;\n };\n\n const openWithDefaultBrowser = () => {\n if (platform === 'darwin') {\n execSync(`open \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n if (platform === 'linux') {\n execSync(`xdg-open \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n return false;\n };\n\n try {\n if (openWithChrome()) {\n console.log('🚀 Google Chrome에서 리포트를 열었습니다.');\n\n return;\n }\n } catch {\n // Chrome 실행 실패 시 기본 브라우저로 폴백\n }\n\n try {\n if (openWithDefaultBrowser()) {\n console.log('🚀 기본 브라우저에서 리포트를 열었습니다.');\n\n return;\n }\n } catch (e) {\n console.error('⚠️ 브라우저 열기 실패:', e);\n\n return;\n }\n\n console.error(`⚠️ 지원하지 않는 플랫폼입니다: ${platform}`);\n}\n\nexport function getDiffArgs() {\n const args = process.argv.slice(2);\n const commitIndex = args.indexOf('--commit');\n const { includeParams, excludeParams } = getGitDiffFilter();\n\n let diffArgs = '';\n\n if (commitIndex !== -1) {\n // 특정 커밋 (및 이전 n개) 리뷰\n const commitHash = args[commitIndex + 1];\n if (!commitHash) {\n console.error('❌ 커밋 해시가 제공되지 않았습니다.');\n process.exit(1);\n }\n\n // n값 확인 (optional)\n const nextArg = args[commitIndex + 2];\n let n = 0;\n if (nextArg && !nextArg.startsWith('--')) {\n n = parseInt(nextArg, 10);\n if (isNaN(n)) {\n n = 0;\n }\n }\n\n console.log(`ℹ️ 커밋 '${commitHash}' ${n > 0 ? ` 포함 총 ${n + 1}개의 커밋` : ''}을 리뷰합니다...`);\n diffArgs = `${commitHash}~${n + 1} ${commitHash}`;\n } else {\n // 기본 모드:\n // 1. Unstaged 변경사항 확인\n try {\n const check = execSync(`git diff --name-only -- ${includeParams} ${excludeParams}`).toString();\n if (!check.trim()) {\n console.log('ℹ️ Unstaged 변경사항이 없습니다. 마지막 커밋(HEAD)을 리뷰합니다...');\n diffArgs = 'HEAD~1 HEAD';\n }\n } catch {\n // git diff 실패시 무시\n }\n }\n\n return diffArgs;\n}\n\n// export const ServiceType = {\n// GEMINI: 'gemini',\n// CLAUDE: 'claude',\n// CODEX: 'codex'\n\n// }\n\n/**\n * AI 서비스 선택\n */\nexport function selectAIService() {\n const args = process.argv.slice(2);\n const serviceIndex = args.indexOf('--service');\n const service = args[serviceIndex + 1];\n if (!service) {\n console.error('❌ 서비스가 선택되지 않았습니다.');\n process.exit(1);\n }\n\n return service;\n}\n\n/**\n * 터미널에서 라디오 버튼 형태로 AI 서비스를 선택합니다.\n */\nexport async function showSelectionAIService(): Promise<AIServiceType> {\n let selectedIndex = 0;\n\n // Use readline to handle keypresses\n // 키 입력을 처리하기 위해 readline 인터페이스 사용\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n terminal: true\n });\n\n let firstRender = true;\n\n // Hide cursor\n process.stdout.write('\\u001b[?25l');\n\n const render = () => {\n if (!firstRender) {\n // Move cursor back to the starting line of the selection UI\n // We print (1 question line + services.length lines)\n // 선택 UI의 시작 라인으로 커서 이동 (질문 1줄 + 서비스 목록 N줄)\n readline.moveCursor(process.stdout, 0, -(AIServices.length + 1));\n }\n firstRender = false;\n\n // Clear everything from cursor down to avoid ghosting/overlaps\n // 잔상이나 겹침을 방지하기 위해 커서 위치부터 아래쪽 모두 지움\n readline.clearScreenDown(process.stdout);\n\n process.stdout.write(\n '🤖 AI 서비스를 선택해주세요 (\\u001b[33m↑↓ 방향키\\u001b[0m 이동, \\u001b[33mEnter\\u001b[0m 선택):\\n'\n );\n AIServices.forEach((service, index) => {\n if (index === selectedIndex) {\n process.stdout.write(` \\u001b[36m>\\u001b[0m \\u001b[36m◉\\u001b[0m \\u001b[1m${service}\\u001b[0m\\n`);\n } else {\n process.stdout.write(` ◯ ${service}\\n`);\n }\n });\n };\n\n render();\n\n return new Promise((resolve) => {\n const onData = (data: Buffer) => {\n const key = data.toString();\n if (key === '\\u0003') {\n // Ctrl+C\n process.stdout.write('\\u001b[?25h'); // Show cursor\n process.exit(0);\n }\n if (key === '\\x1b[A') {\n // Up arrow\n selectedIndex = (selectedIndex - 1 + AIServices.length) % AIServices.length;\n render();\n } else if (key === '\\x1b[B') {\n // Down arrow\n selectedIndex = (selectedIndex + 1) % AIServices.length;\n render();\n } else if (key === '\\r' || key === '\\n') {\n // Enter\n process.stdin.removeListener('data', onData);\n process.stdin.setRawMode(false);\n process.stdin.pause();\n rl.close();\n\n // Show cursor\n process.stdout.write('\\u001b[?25h');\n\n console.log(`\\n✅ \\u001b[32m${AIServices[selectedIndex]}\\u001b[0m 서비스가 선택되었습니다.\\n`);\n const result = AIServices[selectedIndex];\n if (result) {\n resolve(result);\n }\n }\n };\n\n process.stdin.setRawMode(true);\n process.stdin.resume();\n process.stdin.on('data', onData);\n });\n}\n","import fs from 'fs';\n\nimport { codingConventionRulesPath, createTraceLogger, namingRulesPath, rulesPath } from '../../common/helper';\n\nconst args = process.argv.slice(2);\nconst trace = createTraceLogger('claude-commander', args);\n\n/**\n * @description\n * Claude Code CLI를 활용한 코드 리뷰 명령어를 생성합니다.\n *\n * 모델 별칭(Alias):\n * 1. sonnet (기본값, 균형 잡힌 성능) - 일반적인 코드 리뷰에 적합\n * 2. opus (최고 성능) - 복잡한 분석, 긴 컨텍스트 처리에 강점\n * 3. haiku (빠른 속도) - 오타 수정, 간단한 리뷰에 적합\n *\n * Claude CLI 기능 활용:\n * - `-p`: 비대화형 모드(print mode)로 실행하여 결과를 stdout으로 출력\n * - `--append-system-prompt-file`: 룰셋/리뷰양식 파일을 시스템 프롬프트에 추가 (Claude가 \"지침\"으로 인식)\n * - `cat diff | claude`: diff 내용을 stdin으로 파이프하여 \"리뷰 대상\"으로 전달\n */\nexport const createClaudeCommand = (tempDiffPath: string, reviewFormPath: string) => {\n trace('createClaudeCommand:start', `tempDiffPath=${tempDiffPath}, reviewFormPath=${reviewFormPath}`);\n\n let modelOption = '';\n\n if (args.includes('--review')) {\n // 최고 성능 모델 (opus)\n // 복잡한 코드 분석, 긴 컨텍스트 처리에 강점\n modelOption = '--model opus';\n trace('model:review', modelOption);\n } else if (args.includes('--flash')) {\n // 속도 우선 모델 (haiku)\n // 오타 수정, 간단한 리뷰에 적합\n modelOption = '--model haiku';\n trace('model:flash', modelOption);\n } else {\n // 사용자 직접 지정\n const modelIndex = args.indexOf('--model');\n // args[modelIndex + 1] 에는 사용자가 지정한 모델명이 들어갑니다.\n // 예: sonnet, opus, haiku, claude-sonnet-4-5-20250929 등\n if (modelIndex !== -1 && args[modelIndex + 1]) {\n console.warn('⚠️ 지정한 모델이 없는 경우, 에러가 발생하니 주의하세요.');\n modelOption = `--model ${args[modelIndex + 1]}`;\n trace('model:custom', modelOption);\n } else {\n console.warn('⚠️ 모델이 지정되지 않았습니다. 기본 모델인 sonnet을 사용합니다.');\n modelOption = '--model sonnet';\n trace('model:default', modelOption);\n }\n }\n\n // 룰셋 파일들을 --append-system-prompt-file 로 시스템 프롬프트에 추가\n // Claude가 \"지침/규칙\"으로 인식하도록 시스템 프롬프트 레벨에 주입\n const rules = [\n { path: rulesPath, display: '룰셋' },\n { path: namingRulesPath, display: '네이밍 규칙' },\n { path: codingConventionRulesPath, display: '코딩 컨벤션' }\n ];\n\n const systemPromptFiles = rules\n .filter((rule) => fs.existsSync(rule.path))\n .map((rule) => `--append-system-prompt-file ${rule.path}`)\n .join(' ');\n trace(\n 'rules:loaded',\n `count=${systemPromptFiles ? systemPromptFiles.split('--append-system-prompt-file').length - 1 : 0}`\n );\n\n // 리뷰 양식도 시스템 프롬프트에 추가 (출력 포맷 지침)\n const reviewFormOption = fs.existsSync(reviewFormPath) ? `--append-system-prompt-file ${reviewFormPath}` : '';\n trace('reviewForm:status', reviewFormOption ? 'exists' : 'missing');\n\n // Claude CLI 명령어 생성\n // cat으로 diff 내용을 stdin 파이프 → Claude가 \"리뷰 대상 데이터\"로 인식\n // -p: 비대화형 모드로 실행\n const command = `cat ${tempDiffPath} | claude ${modelOption} ${systemPromptFiles} ${reviewFormOption} -p \"위 규칙들을 참고하여 이 diff를 코드리뷰해줘. 리뷰양식에 맞춰서 작성해줘.\"`;\n trace('command:created');\n\n // test mode\n if (args.includes('--test')) {\n const safeCommand = command.replace(/\"/g, '\\\\\"');\n trace('test-mode:return-preview');\n\n return `echo \"[TEST MODE] Claude 명령어가 실행되지 않았습니다.\\n\\n생성될 명령어 미리보기:\\n${safeCommand}\"`;\n }\n\n trace('createClaudeCommand:end');\n\n return command;\n};\n"]}
1
+ {"version":3,"sources":["../../../src/common/helper.ts","../../../src/pr-review/claude/claude-commander.ts"],"names":["__dirname","path","fileURLToPath","args","reviewFormPath","fs"],"mappings":"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","file":"claude-commander.cjs","sourcesContent":["import { execSync } from 'child_process';\nimport fs from 'fs';\nimport path from 'path';\nimport readline from 'readline';\nimport { fileURLToPath } from 'url';\n\nimport { AIServiceType } from './types';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n// 설치된 위치에 맞게 규칙/양식 파일 경로를 계산 (dist에서 src로 이동 등 고려)\n// dist/common/helper.js 가 실행되므로 __dirname은 .../dist/common 입니다.\n// 따라서 ../../src/common/rules 로 이동해야 원본 소스의 규칙 파일을 찾을 수 있습니다.\nexport const rulesPath = path.resolve(__dirname, '../../src/common/rules/review-rules.md');\nexport const namingRulesPath = path.resolve(__dirname, '../../src/common/rules/naming-rule.md');\nexport const codingConventionRulesPath = path.resolve(__dirname, '../../src/common/rules/coding-convention.md');\nexport const reviewFormPath = path.resolve(__dirname, '../../src/common/form/review-form.md');\nexport const reviewFormOneByOnePath = path.resolve(__dirname, '../../src/common/form/review-form-one-by-one.md');\nexport const REPORT_DIR = '.review-report';\nexport const tempDiffPath = 'temp_diff.txt';\nexport const AIServices: AIServiceType[] = ['gemini', 'claude', 'codex'];\nexport const ignoreList = [\n 'package.json',\n '*.yml',\n '*.md',\n '*.lock',\n 'dist/',\n 'node_modules/',\n 'assets/',\n 'public/',\n '*.json',\n '*.yaml',\n '.review-report/' // 생성되는 리포트 폴더도 제외\n];\n\nfunction parseServiceFromArgs(args: string[] = process.argv.slice(2)): AIServiceType | '' {\n const serviceIndex = args.indexOf('--service');\n const rawService = serviceIndex !== -1 ? args[serviceIndex + 1] : '';\n\n if (!rawService) {\n return '';\n }\n\n const normalizedService = rawService.toLowerCase();\n\n if (AIServices.includes(normalizedService as AIServiceType)) {\n return normalizedService as AIServiceType;\n }\n\n console.error(\n `❌ 지원하지 않는 서비스입니다: ${rawService}. 사용 가능 값: ${AIServices.join(', ')} (예: --service codex)`\n );\n process.exit(1);\n}\n\nexport function isTestMode(args: string[] = process.argv.slice(2)) {\n return args.includes('--test');\n}\n\nexport function createTraceLogger(scope: string, args: string[] = process.argv.slice(2)) {\n const enabled = isTestMode(args);\n\n return (step: string, detail?: string) => {\n if (!enabled) {\n return;\n }\n\n console.log(`[TRACE][${scope}] ${step}${detail ? ` | ${detail}` : ''}`);\n };\n}\n\nexport function getNextFilePath(dir: string, baseName: string, extension: string) {\n let counter = 1;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const filePath = path.join(dir, `${baseName}-${counter}${extension}`);\n if (!fs.existsSync(filePath)) {\n return filePath;\n }\n counter++;\n }\n}\n\nexport function deleteFile(filePath: string) {\n if (fs.existsSync(filePath)) {\n fs.unlinkSync(filePath);\n }\n}\n\n/**\n * 임시파일 삭제\n */\nexport function deleteTempDiff() {\n deleteFile(tempDiffPath);\n}\n\n/**\n * 리뷰 결과 폴더 생성\n */\nexport function createReportDirectory() {\n if (!fs.existsSync(REPORT_DIR)) {\n fs.mkdirSync(REPORT_DIR, { recursive: true });\n }\n}\n\n/**\n * 현재 시간 문자열 생성\n */\nexport function getNowString() {\n const now = new Date();\n const YYYY = now.getFullYear();\n const MM = String(now.getMonth() + 1).padStart(2, '0');\n const DD = String(now.getDate()).padStart(2, '0');\n const HH = String(now.getHours()).padStart(2, '0');\n const mm = String(now.getMinutes()).padStart(2, '0');\n const ss = String(now.getSeconds()).padStart(2, '0');\n\n return `${YYYY}-${MM}-${DD}_${HH}-${mm}-${ss}`;\n}\n\nexport function getGitDiffFilter() {\n // 1. 리뷰 대상 파일 확장자 정의\n const includeExtensions = ['*.ts', '*.tsx', '*.js', '*.jsx'];\n\n // ignoreList 를 import 하여 재사용하여 작성한다.\n const excludePatterns = ignoreList.map((item) => `:(exclude)${item}`);\n\n // const excludePatterns = [':(exclude)*.lock', ':(exclude)dist/', ':(exclude)*.md'];\n\n // 2. 변경된 파일 목록 가져오기 (각 패턴을 따옴표로 감싸서 쉘 에러 방지)\n const quote = (pattern: string) => `\"${pattern}\"`;\n const includeParams = includeExtensions.map(quote).join(' ');\n const excludeParams = excludePatterns.map(quote).join(' ');\n\n return { includeParams, excludeParams };\n}\n\n/**\n * openReport를 OS별로 동작하도록 변경\n * 우선순위:\n * 1. Chrome 시도\n * - macOS: open -a \"Google Chrome\" \"<path>\"\n * - Ubuntu/Linux: google-chrome \"<path>\"\n * 2. 실패 시 기본 브라우저로 폴백\n * - macOS: open \"<path>\"\n * - Ubuntu/Linux: xdg-open \"<path>\"\n * 3. 둘 다 실패하면 에러 출력\n * 4. 미지원 플랫폼이면 플랫폼 경고 출력\n */\nexport function openReport(reportPath: string) {\n const resolvedPath = path.resolve(reportPath);\n const { platform } = process;\n\n const openWithChrome = () => {\n if (platform === 'darwin') {\n execSync(`open -a \"Google Chrome\" \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n if (platform === 'linux') {\n execSync(`google-chrome \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n return false;\n };\n\n const openWithDefaultBrowser = () => {\n if (platform === 'darwin') {\n execSync(`open \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n if (platform === 'linux') {\n execSync(`xdg-open \"${resolvedPath}\"`, { stdio: 'ignore' });\n\n return true;\n }\n\n return false;\n };\n\n try {\n if (openWithChrome()) {\n console.log('🚀 Google Chrome에서 리포트를 열었습니다.');\n\n return;\n }\n } catch {\n // Chrome 실행 실패 시 기본 브라우저로 폴백\n }\n\n try {\n if (openWithDefaultBrowser()) {\n console.log('🚀 기본 브라우저에서 리포트를 열었습니다.');\n\n return;\n }\n } catch (e) {\n console.error('⚠️ 브라우저 열기 실패:', e);\n\n return;\n }\n\n console.error(`⚠️ 지원하지 않는 플랫폼입니다: ${platform}`);\n}\n\nexport function getDiffArgs() {\n const args = process.argv.slice(2);\n const commitIndex = args.indexOf('--commit');\n const { includeParams, excludeParams } = getGitDiffFilter();\n\n let diffArgs = '';\n\n if (commitIndex !== -1) {\n // 특정 커밋 (및 이전 n개) 리뷰\n const commitHash = args[commitIndex + 1];\n if (!commitHash) {\n console.error('❌ 커밋 해시가 제공되지 않았습니다.');\n process.exit(1);\n }\n\n // n값 확인 (optional)\n const nextArg = args[commitIndex + 2];\n let n = 0;\n if (nextArg && !nextArg.startsWith('--')) {\n n = parseInt(nextArg, 10);\n if (isNaN(n)) {\n n = 0;\n }\n }\n\n console.log(`ℹ️ 커밋 '${commitHash}' ${n > 0 ? ` 포함 총 ${n + 1}개의 커밋` : ''}을 리뷰합니다...`);\n diffArgs = `${commitHash}~${n + 1} ${commitHash}`;\n } else {\n // 기본 모드:\n // 1. Unstaged 변경사항 확인\n try {\n const check = execSync(`git diff --name-only -- ${includeParams} ${excludeParams}`).toString();\n if (!check.trim()) {\n console.log('ℹ️ Unstaged 변경사항이 없습니다. 마지막 커밋(HEAD)을 리뷰합니다...');\n diffArgs = 'HEAD~1 HEAD';\n }\n } catch {\n // git diff 실패시 무시\n }\n }\n\n return diffArgs;\n}\n\n// export const ServiceType = {\n// GEMINI: 'gemini',\n// CLAUDE: 'claude',\n// CODEX: 'codex'\n\n// }\n\n/**\n * AI 서비스 선택\n */\nexport function selectAIService() {\n const service = parseServiceFromArgs();\n\n if (!service) {\n console.error('❌ 서비스가 선택되지 않았습니다.');\n process.exit(1);\n }\n\n return service;\n}\n\n/**\n * 터미널에서 라디오 버튼 형태로 AI 서비스를 선택합니다.\n */\nexport async function showSelectionAIService(): Promise<AIServiceType> {\n const selectedServiceFromArgs = parseServiceFromArgs();\n\n if (selectedServiceFromArgs) {\n console.log(`\\n✅ \\u001b[32m${selectedServiceFromArgs}\\u001b[0m 서비스가 선택되었습니다. (--service)\\n`);\n\n return selectedServiceFromArgs;\n }\n\n let selectedIndex = 0;\n\n // Use readline to handle keypresses\n // 키 입력을 처리하기 위해 readline 인터페이스 사용\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n terminal: true\n });\n\n let firstRender = true;\n\n // Hide cursor\n process.stdout.write('\\u001b[?25l');\n\n const render = () => {\n if (!firstRender) {\n // Move cursor back to the starting line of the selection UI\n // We print (1 question line + services.length lines)\n // 선택 UI의 시작 라인으로 커서 이동 (질문 1줄 + 서비스 목록 N줄)\n readline.moveCursor(process.stdout, 0, -(AIServices.length + 1));\n }\n firstRender = false;\n\n // Clear everything from cursor down to avoid ghosting/overlaps\n // 잔상이나 겹침을 방지하기 위해 커서 위치부터 아래쪽 모두 지움\n readline.clearScreenDown(process.stdout);\n\n process.stdout.write(\n '🤖 AI 서비스를 선택해주세요 (\\u001b[33m↑↓ 방향키\\u001b[0m 이동, \\u001b[33mEnter\\u001b[0m 선택):\\n'\n );\n AIServices.forEach((service, index) => {\n if (index === selectedIndex) {\n process.stdout.write(` \\u001b[36m>\\u001b[0m \\u001b[36m◉\\u001b[0m \\u001b[1m${service}\\u001b[0m\\n`);\n } else {\n process.stdout.write(` ◯ ${service}\\n`);\n }\n });\n };\n\n render();\n\n return new Promise((resolve) => {\n const onData = (data: Buffer) => {\n const key = data.toString();\n if (key === '\\u0003') {\n // Ctrl+C\n process.stdout.write('\\u001b[?25h'); // Show cursor\n process.exit(0);\n }\n if (key === '\\x1b[A') {\n // Up arrow\n selectedIndex = (selectedIndex - 1 + AIServices.length) % AIServices.length;\n render();\n } else if (key === '\\x1b[B') {\n // Down arrow\n selectedIndex = (selectedIndex + 1) % AIServices.length;\n render();\n } else if (key === '\\r' || key === '\\n') {\n // Enter\n process.stdin.removeListener('data', onData);\n process.stdin.setRawMode(false);\n process.stdin.pause();\n rl.close();\n\n // Show cursor\n process.stdout.write('\\u001b[?25h');\n\n console.log(`\\n✅ \\u001b[32m${AIServices[selectedIndex]}\\u001b[0m 서비스가 선택되었습니다.\\n`);\n const result = AIServices[selectedIndex];\n if (result) {\n resolve(result);\n }\n }\n };\n\n process.stdin.setRawMode(true);\n process.stdin.resume();\n process.stdin.on('data', onData);\n });\n}\n","import fs from 'fs';\n\nimport { codingConventionRulesPath, createTraceLogger, namingRulesPath, rulesPath } from '../../common/helper';\n\nconst args = process.argv.slice(2);\nconst trace = createTraceLogger('claude-commander', args);\nconst ALLOWED_REASONING_EFFORTS = ['minimal', 'low', 'medium', 'high'] as const;\n\ntype ReasoningEffort = (typeof ALLOWED_REASONING_EFFORTS)[number];\ntype ClaudeEffort = 'low' | 'medium' | 'high';\n\nfunction shellQuote(value: string) {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction getArgValue(flag: string) {\n const index = args.indexOf(flag);\n\n if (index === -1 || !args[index + 1]) {\n return '';\n }\n\n return args[index + 1];\n}\n\nfunction toUnique(values: string[]) {\n const seen = new Set<string>();\n\n return values.filter((value) => {\n if (!value || seen.has(value)) {\n return false;\n }\n seen.add(value);\n\n return true;\n });\n}\n\nfunction normalizeEffort(level: ReasoningEffort): ClaudeEffort {\n if (level === 'minimal') {\n return 'low';\n }\n\n return level;\n}\n\nfunction resolveReasoningEffort(): ClaudeEffort {\n const customReasoningEffort = getArgValue('--reasoning-effort') || getArgValue('--effort');\n\n if (customReasoningEffort) {\n if (ALLOWED_REASONING_EFFORTS.includes(customReasoningEffort as ReasoningEffort)) {\n const normalized = normalizeEffort(customReasoningEffort as ReasoningEffort);\n trace('reasoning:custom', `${customReasoningEffort} -> ${normalized}`);\n\n if (customReasoningEffort === 'minimal') {\n console.warn('⚠️ Claude는 minimal을 직접 지원하지 않아 low로 매핑합니다.');\n }\n\n return normalized;\n }\n\n console.warn(\n `⚠️ 지원되지 않는 reasoning effort(${customReasoningEffort})입니다. allowed: ${ALLOWED_REASONING_EFFORTS.join(\n ', '\n )}`\n );\n }\n\n if (args.includes('--flash')) {\n trace('reasoning:flash-default', 'low');\n\n return 'low';\n }\n\n if (args.includes('--review')) {\n trace('reasoning:review-default', 'high');\n\n return 'high';\n }\n\n trace('reasoning:default', 'medium');\n\n return 'medium';\n}\n\nfunction resolvePrimaryAlias() {\n if (args.includes('--review')) {\n trace('model:mode-alias', 'opus');\n\n return 'opus';\n }\n\n if (args.includes('--flash')) {\n trace('model:mode-alias', 'haiku');\n\n return 'haiku';\n }\n\n trace('model:default-alias', 'sonnet');\n\n return 'sonnet';\n}\n\nfunction getAliasFallbacks(primaryAlias: string) {\n if (primaryAlias === 'opus') {\n return ['opus', 'sonnet', 'haiku'];\n }\n\n if (primaryAlias === 'haiku') {\n return ['haiku', 'sonnet'];\n }\n\n return [primaryAlias, 'sonnet', 'haiku'];\n}\n\nfunction buildClaudeExecCommand(options: {\n tempDiffPath: string;\n prompt: string;\n systemPromptFiles: string[];\n effort: ClaudeEffort;\n model?: string;\n fallbackModel?: string;\n}) {\n const { tempDiffPath, prompt, systemPromptFiles, effort, model, fallbackModel } = options;\n const modelOption = model ? `--model ${shellQuote(model)}` : '';\n const fallbackOption = model && fallbackModel ? `--fallback-model ${shellQuote(fallbackModel)}` : '';\n const effortOption = `--effort ${shellQuote(effort)}`;\n\n const appendedPromptFiles = systemPromptFiles\n .map((path) => `--append-system-prompt-file ${shellQuote(path)}`)\n .join(' ');\n\n return `cat ${shellQuote(tempDiffPath)} | claude ${[\n modelOption,\n fallbackOption,\n effortOption,\n appendedPromptFiles,\n '-p',\n shellQuote(prompt)\n ]\n .filter(Boolean)\n .join(' ')}`;\n}\n\n/**\n * @description\n * Claude Code CLI를 활용한 코드 리뷰 명령어를 생성합니다.\n *\n * 모델 전략:\n * 1) --model 커스텀 모델 우선 시도\n * 2) 서비스 alias 순차 폴백 (opus/sonnet/haiku 조합)\n * 3) 마지막에 --model 없이 계정/CLI 기본 모델로 폴백\n *\n * reasoning 전략:\n * - --effort(low|medium|high)를 사용\n * - --reasoning-effort(minimal|low|medium|high) 입력 시 Claude 호환 값으로 매핑\n */\nexport const createClaudeCommand = (tempDiffPath: string, reviewFormPath: string) => {\n trace('createClaudeCommand:start', `tempDiffPath=${tempDiffPath}, reviewFormPath=${reviewFormPath}`);\n const customModel = getArgValue('--model');\n const effort = resolveReasoningEffort();\n const primaryAlias = resolvePrimaryAlias();\n const aliasFallbacks = toUnique(getAliasFallbacks(primaryAlias));\n\n // 룰셋 파일들을 --append-system-prompt-file 로 시스템 프롬프트에 추가\n // Claude가 \"지침/규칙\"으로 인식하도록 시스템 프롬프트 레벨에 주입\n const rules = [\n { path: rulesPath, display: '룰셋' },\n { path: namingRulesPath, display: '네이밍 규칙' },\n { path: codingConventionRulesPath, display: '코딩 컨벤션' }\n ];\n const existingRuleFiles = rules.filter((rule) => fs.existsSync(rule.path)).map((rule) => rule.path);\n\n trace('rules:loaded', `count=${existingRuleFiles.length}`);\n\n // 리뷰 양식도 시스템 프롬프트에 추가 (출력 포맷 지침)\n const reviewFormExists = fs.existsSync(reviewFormPath);\n trace('reviewForm:status', reviewFormExists ? 'exists' : 'missing');\n const systemPromptFiles = reviewFormExists ? [...existingRuleFiles, reviewFormPath] : existingRuleFiles;\n\n const prompt = '위 규칙들을 참고하여 이 diff를 코드리뷰해줘. 리뷰양식에 맞춰서 작성해줘.';\n const modelCandidates = toUnique(customModel ? [customModel, ...aliasFallbacks] : aliasFallbacks);\n trace('model:candidates', modelCandidates.join(', '));\n\n if (customModel) {\n console.warn(\n `⚠️ 커스텀 모델(${customModel})을 우선 시도하고 실패하면 alias(${aliasFallbacks.join(\n ' -> '\n )}) 및 기본 모델 순으로 폴백합니다.`\n );\n } else {\n console.warn(\n `⚠️ 모델이 지정되지 않았습니다. alias(${aliasFallbacks.join(' -> ')})를 순차 시도하고 마지막에 기본 모델로 폴백합니다.`\n );\n }\n\n const commandCandidates = modelCandidates.map((model, index) => {\n const fallbackModel = modelCandidates[index + 1];\n\n return buildClaudeExecCommand({\n tempDiffPath,\n prompt,\n systemPromptFiles,\n effort,\n model,\n fallbackModel\n });\n });\n\n const command = [\n ...commandCandidates,\n buildClaudeExecCommand({\n tempDiffPath,\n prompt,\n systemPromptFiles,\n effort\n })\n ].join(' || ');\n\n trace('command:created');\n\n // test mode\n if (args.includes('--test')) {\n const safeCommand = command.replace(/\"/g, '\\\\\"');\n trace('test-mode:return-preview');\n\n return `echo \"[TEST MODE] Claude 명령어가 실행되지 않았습니다.\\n\\n생성될 명령어 미리보기:\\n${safeCommand}\"`;\n }\n\n trace('createClaudeCommand:end');\n\n return command;\n};\n"]}
@@ -2,15 +2,14 @@
2
2
  * @description
3
3
  * Claude Code CLI를 활용한 코드 리뷰 명령어를 생성합니다.
4
4
  *
5
- * 모델 별칭(Alias):
6
- * 1. sonnet (기본값, 균형 잡힌 성능) - 일반적인 코드 리뷰에 적합
7
- * 2. opus (최고 성능) - 복잡한 분석, 컨텍스트 처리에 강점
8
- * 3. haiku (빠른 속도) - 오타 수정, 간단한 리뷰에 적합
5
+ * 모델 전략:
6
+ * 1) --model 커스텀 모델 우선 시도
7
+ * 2) 서비스 alias 순차 폴백 (opus/sonnet/haiku 조합)
8
+ * 3) 마지막에 --model 없이 계정/CLI 기본 모델로 폴백
9
9
  *
10
- * Claude CLI 기능 활용:
11
- * - `-p`: 비대화형 모드(print mode) 실행하여 결과를 stdout으로 출력
12
- * - `--append-system-prompt-file`: 룰셋/리뷰양식 파일을 시스템 프롬프트에 추가 (Claude가 "지침"으로 인식)
13
- * - `cat diff | claude`: diff 내용을 stdin으로 파이프하여 "리뷰 대상"으로 전달
10
+ * reasoning 전략:
11
+ * - --effort(low|medium|high) 사용
12
+ * - --reasoning-effort(minimal|low|medium|high) 입력 Claude 호환 값으로 매핑
14
13
  */
15
14
  declare const createClaudeCommand: (tempDiffPath: string, reviewFormPath: string) => string;
16
15