coderev-cli 1.0.11 → 1.0.12

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +110 -107
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coderev-cli",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "Multi-agent AI code review for git -- parallel agents with confidence scoring",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  const { program } = require('commander');
4
4
  const chalk = require('chalk');
@@ -71,7 +71,7 @@ program
71
71
  return;
72
72
  }
73
73
 
74
- console.error(chalk.bold(`\n馃搵 Found ${prList.length} open PRs in ${prRef.owner}/${prRef.repo}:`));
74
+ console.error(chalk.bold(`\n📋 Found ${prList.length} open PRs in ${prRef.owner}/${prRef.repo}:`));
75
75
  for (const pr of prList) {
76
76
  console.error(` #${pr.number} ${pr.title} (${pr.draft ? 'draft' : 'open'})`);
77
77
  }
@@ -79,7 +79,7 @@ program
79
79
 
80
80
  const results = [];
81
81
  for (const pr of prList) {
82
- console.error(chalk.blue(`鈫?Reviewing PR #${pr.number}...`));
82
+ console.error(chalk.blue(`↻ Reviewing PR #${pr.number}...`));
83
83
  const fullRef = { owner: prRef.owner, repo: prRef.repo, pr: pr.number };
84
84
  try {
85
85
  const prDiff = await fetchPrDiff(fullRef, token);
@@ -89,7 +89,7 @@ program
89
89
  if (options.post) {
90
90
  const md = formatMarkdown(result);
91
91
  await postPrComment(fullRef, md, token);
92
- console.error(chalk.green(` 鉁?#${pr.number} reviewed & posted`));
92
+ console.error(chalk.green(` ✔ #${pr.number} reviewed & posted`));
93
93
  } else {
94
94
  const scoreColor = result.score >= 80 ? chalk.green : result.score >= 50 ? chalk.yellow : chalk.red;
95
95
  const scoreStr = scoreColor(`${result.score}/100`);
@@ -97,7 +97,7 @@ program
97
97
  console.error(` ${scoreStr} (${issueCount} issues) - ${result.summary || ''}`);
98
98
  }
99
99
  } catch (err) {
100
- console.error(chalk.red(` 鉁?#${pr.number}: ${err.message}`));
100
+ console.error(chalk.red(` ✖ #${pr.number}: ${err.message}`));
101
101
  }
102
102
  }
103
103
 
@@ -105,7 +105,7 @@ program
105
105
  const scores = results.filter(r => r.result).map(r => r.result.score);
106
106
  if (scores.length > 0) {
107
107
  const avg = (scores.reduce((a, b) => a + b, 0) / scores.length).toFixed(1);
108
- console.error(chalk.bold(`\n馃搳 Batch Summary: ${results.length}/${prList.length} reviewed, avg score: ${avg}`));
108
+ console.error(chalk.bold(`\n📊 Batch Summary: ${results.length}/${prList.length} reviewed, avg score: ${avg}`));
109
109
  }
110
110
 
111
111
  if (options.output === 'json') {
@@ -124,35 +124,35 @@ program
124
124
  const { resolveMrRef, fetchMrDiff } = require('./gitlab');
125
125
  const glRef = resolveMrRef(options.gl, options.repo);
126
126
  const glToken = options.gitlabToken || process.env.GITLAB_TOKEN;
127
- console.error(chalk.blue(`鈫?Fetching GitLab MR ${glRef.owner}/${glRef.repo}!${glRef.mr}...`));
127
+ console.error(chalk.blue(`↻ Fetching GitLab MR ${glRef.owner}/${glRef.repo}!${glRef.mr}...`));
128
128
  diff = await fetchMrDiff(glRef, glToken);
129
- console.error(chalk.green(`鉁?Diff fetched (${diff.length} chars)`));
129
+ console.error(chalk.green(`✔ Diff fetched (${diff.length} chars)`));
130
130
  } else if (options.gee) {
131
131
  const { resolvePrRef: resolveGiteeRef, fetchPrDiff: fetchGiteeDiff } = require('./gitee');
132
132
  const geeRef = resolveGiteeRef(options.gee, options.repo);
133
133
  const geeToken = options.geeToken || process.env.GITEE_TOKEN;
134
- console.error(chalk.blue(`鈫?Fetching Gitee PR ${geeRef.owner}/${geeRef.repo}!${geeRef.pr}...`));
134
+ console.error(chalk.blue(`↻ Fetching Gitee PR ${geeRef.owner}/${geeRef.repo}!${geeRef.pr}...`));
135
135
  diff = await fetchGiteeDiff(geeRef, geeToken);
136
- console.error(chalk.green(`鉁?Diff fetched (${diff.length} chars)`));
136
+ console.error(chalk.green(`✔ Diff fetched (${diff.length} chars)`));
137
137
  } else if (options.gc) {
138
138
  const { resolveMrRef: resolveGcRef, fetchMrDiff: fetchGcDiff } = require('./gitcode');
139
139
  const gcRef = resolveGcRef(options.gc, options.repo);
140
140
  const gcToken = options.gcToken || process.env.GITCODE_TOKEN;
141
- console.error(chalk.blue(`鈫?Fetching GitCode MR ${gcRef.owner}/${gcRef.repo}!${gcRef.mr}...`));
141
+ console.error(chalk.blue(`↻ Fetching GitCode MR ${gcRef.owner}/${gcRef.repo}!${gcRef.mr}...`));
142
142
  diff = await fetchGcDiff(gcRef, gcToken);
143
- console.error(chalk.green(`鉁?Diff fetched (${diff.length} chars)`));
143
+ console.error(chalk.green(`✔ Diff fetched (${diff.length} chars)`));
144
144
  } else if (options.bb) {
145
145
  const { resolvePrRef: resolveBbRef, fetchPrDiff: fetchBbDiff } = require('./bitbucket');
146
146
  const bbRef = resolveBbRef(options.bb, options.repo);
147
147
  if (options.bbToken) process.env.BITBUCKET_USERNAME = options.bbToken.split(':')[0] || '';
148
148
  const bbToken = options.bbToken || process.env.BITBUCKET_APP_PASSWORD;
149
- console.error(chalk.blue(`鈫?Fetching Bitbucket PR ${bbRef.owner}/${bbRef.repo}#${bbRef.pr}...`));
149
+ console.error(chalk.blue(`↻ Fetching Bitbucket PR ${bbRef.owner}/${bbRef.repo}#${bbRef.pr}...`));
150
150
  diff = await fetchBbDiff(bbRef, bbToken);
151
- console.error(chalk.green(`鉁?Diff fetched (${diff.length} chars)`));
151
+ console.error(chalk.green(`✔ Diff fetched (${diff.length} chars)`));
152
152
  } else if (options.pr) {
153
153
  prRef = resolvePrRef(options.pr, options.repo);
154
154
  const token = resolveToken(options.githubToken, config);
155
- console.error(chalk.blue(`鈫?Fetching PR ${prRef.owner}/${prRef.repo}#${prRef.pr}...`));
155
+ console.error(chalk.blue(`↻ Fetching PR ${prRef.owner}/${prRef.repo}#${prRef.pr}...`));
156
156
  diff = await fetchPrDiff(prRef, token);
157
157
  } else if (options.file) {
158
158
  const fs = require('fs');
@@ -166,7 +166,7 @@ program
166
166
  if (stdinBuffer.trim()) {
167
167
  diff = stdinBuffer;
168
168
  } else {
169
- console.error(chalk.red('鉁?No diff input provided. Pipe a diff, use --file, use --repo, or use --pr.'));
169
+ console.error(chalk.red('No diff input provided. Pipe a diff, use --file, use --repo, or use --pr.'));
170
170
  process.exit(1);
171
171
  }
172
172
  }
@@ -191,22 +191,22 @@ program
191
191
  if (options.post && prRef) {
192
192
  const token = resolveToken(options.githubToken, config);
193
193
  if (!token) {
194
- console.error(chalk.red('鉁?--post requires --github-token or GITHUB_TOKEN env var'));
194
+ console.error(chalk.red('✖ --post requires --github-token or GITHUB_TOKEN env var'));
195
195
  process.exit(1);
196
196
  }
197
197
  const mdReport = formatMarkdown(result);
198
- console.error(chalk.blue(`鈫?Posting review to PR ${prRef.owner}/${prRef.repo}#${prRef.pr}...`));
198
+ console.error(chalk.blue(`↻ Posting review to PR ${prRef.owner}/${prRef.repo}#${prRef.pr}...`));
199
199
  await postPrComment(prRef, mdReport, token);
200
- console.error(chalk.green('鉁?Review posted as PR comment!'));
200
+ console.error(chalk.green('Review posted as PR comment!'));
201
201
  }
202
202
 
203
203
  if (options.inline && prRef) {
204
204
  const token = resolveToken(options.githubToken, config);
205
205
  if (!token) {
206
- console.error(chalk.red('鉁?--inline requires --github-token or GITHUB_TOKEN env var'));
206
+ console.error(chalk.red('✖ --inline requires --github-token or GITHUB_TOKEN env var'));
207
207
  process.exit(1);
208
208
  }
209
- console.error(chalk.blue(`鈫?Posting inline review to PR ${prRef.owner}/${prRef.repo}#${prRef.pr}...`));
209
+ console.error(chalk.blue(`↻ Posting inline review to PR ${prRef.owner}/${prRef.repo}#${prRef.pr}...`));
210
210
 
211
211
  // Get PR files for commit SHA and file mapping
212
212
  const prFiles = await fetchPrFiles(prRef, token);
@@ -214,7 +214,7 @@ program
214
214
  const https = require('https');
215
215
  const prInfo = await new Promise((resolve, reject) => {
216
216
  https.get('https://api.github.com/repos/' + prRef.owner + '/' + prRef.repo + '/pulls/' + prRef.pr, {
217
- headers: { 'User-Agent': 'coderev', 'Accept': 'application/vnd.github.v3+json', 'Authorization': '***' + token },
217
+ headers: { 'User-Agent': 'coderev', 'Accept': 'application/vnd.github.v3+json', 'Authorization': 'token ' + token },
218
218
  }, (r) => { let b=''; r.on('data',c=>b+=c); r.on('end',()=>{ try{resolve(JSON.parse(b))}catch{reject()}}); }).on('error', reject);
219
219
  });
220
220
 
@@ -246,23 +246,23 @@ program
246
246
  const headSha = prInfo?.head?.sha;
247
247
  if (headSha) {
248
248
  await postInlineComments(prRef, headSha, inlineComments, token);
249
- console.error(chalk.green(`鉁?${inlineComments.length} inline comments posted!`));
249
+ console.error(chalk.green(`✔ ${inlineComments.length} inline comments posted!`));
250
250
  } else {
251
- console.error(chalk.red('鉁?Could not resolve PR head commit SHA'));
251
+ console.error(chalk.red('Could not resolve PR head commit SHA'));
252
252
  }
253
253
  } else {
254
- console.error(chalk.yellow('鈿?No line-level issues to post inline'));
254
+ console.error(chalk.yellow('No line-level issues to post inline'));
255
255
  }
256
256
  }
257
257
 
258
258
  console.log(output);
259
259
  } catch (err) {
260
- console.error(chalk.red(`鉁?${err.message}`));
260
+ console.error(chalk.red(`✖ ${err.message}`));
261
261
  process.exit(1);
262
262
  }
263
263
  });
264
264
 
265
- // 鈹€鈹€ Cache Management 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
265
+ // ── Cache Management ──────────────────────────────────────────
266
266
  program
267
267
  .command('cache')
268
268
  .description('Manage review cache')
@@ -274,7 +274,7 @@ program
274
274
 
275
275
  if (action === 'clear') {
276
276
  const count = cleanCache();
277
- console.log(chalk.green(`鉁?Cache cleared (${count} entries removed)`));
277
+ console.log(chalk.green(`✔ Cache cleared (${count} entries removed)`));
278
278
  } else if (action === 'status') {
279
279
  if (!fs.existsSync(cacheDir)) {
280
280
  console.log(chalk.blue(' Cache is empty'));
@@ -282,11 +282,11 @@ program
282
282
  }
283
283
  const files = fs.readdirSync(cacheDir).filter(f => f.endsWith('.json'));
284
284
  const totalSize = files.reduce((sum, f) => sum + fs.statSync(path.join(cacheDir, f)).size, 0);
285
- console.log(chalk.bold(`\n馃摝 Cache: ${files.length} entries, ${(totalSize / 1024).toFixed(1)} KB`));
285
+ console.log(chalk.bold(`\n📦 Cache: ${files.length} entries, ${(totalSize / 1024).toFixed(1)} KB`));
286
286
  }
287
287
  });
288
288
 
289
- // 鈹€鈹€ Fix 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
289
+ // ── Fix ──────────────────────────────────────────────────────
290
290
  program
291
291
  .command('fix')
292
292
  .description('Generate a fix patch for issues found in a diff')
@@ -305,7 +305,7 @@ program
305
305
  const { resolvePrRef, fetchPrDiff } = require('./github');
306
306
  prRef = resolvePrRef(options.pr, options.repo);
307
307
  const token = resolveToken(options.githubToken, config);
308
- console.error(chalk.blue(`鈫?Fetching PR ${prRef.owner}/${prRef.repo}#${prRef.pr}...`));
308
+ console.error(chalk.blue(`↻ Fetching PR ${prRef.owner}/${prRef.repo}#${prRef.pr}...`));
309
309
  diff = await fetchPrDiff(prRef, token);
310
310
  } else if (options.file) {
311
311
  const fs = require('fs');
@@ -314,13 +314,13 @@ program
314
314
  const fs = require('fs');
315
315
  const stdinBuffer = fs.readFileSync(0, 'utf-8');
316
316
  if (!stdinBuffer.trim()) {
317
- console.error(chalk.red('鉁?No diff input provided.'));
317
+ console.error(chalk.red('No diff input provided.'));
318
318
  process.exit(1);
319
319
  }
320
320
  diff = stdinBuffer;
321
321
  }
322
322
 
323
- console.error(chalk.blue('鈫?Generating fix patch...'));
323
+ console.error(chalk.blue('Generating fix patch...'));
324
324
  const { reviewDiff } = require('./reviewer');
325
325
  const result = await reviewDiff(diff, config, { noCache: true, single: true });
326
326
 
@@ -340,34 +340,34 @@ program
340
340
  const aiResponse = await callAI(apiKey, fixPrompt, config);
341
341
 
342
342
  // Extract patch from response
343
- const patchMatch = aiResponse.match(/\`\`\`diff\n([\s\S]*?)\n\`\`\`/);
343
+ const patchMatch = aiResponse.match(/```diff\n([\s\S]*?)\n```/);
344
344
  const patch = patchMatch ? patchMatch[1] : aiResponse;
345
345
 
346
- console.log('\n' + chalk.bold('馃┕ Fix Patch:'));
347
- console.log('鈹?.repeat(50));
346
+ console.log('\n' + chalk.bold('🩹 Fix Patch / 修复补丁:'));
347
+ console.log('━'.repeat(50));
348
348
  console.log(patch);
349
349
 
350
350
  if (options.apply) {
351
351
  const fs = require('fs');
352
352
  const tmpFile = path.join(require('os').tmpdir(), 'coderev-fix.patch');
353
353
  fs.writeFileSync(tmpFile, patch);
354
- console.error(chalk.blue(`鈫?Applying patch from ${tmpFile}...`));
354
+ console.error(chalk.blue(`↻ Applying patch from ${tmpFile}...`));
355
355
  try {
356
356
  const { execSync } = require('child_process');
357
357
  const cwd = prRef ? undefined : process.cwd();
358
358
  execSync(`git apply "${tmpFile}"`, { cwd, stdio: 'pipe' });
359
- console.log(chalk.green('鉁?Patch applied successfully!'));
359
+ console.log(chalk.green('Patch applied successfully!'));
360
360
  } catch (applyErr) {
361
- console.error(chalk.red(`鉁?Failed to apply patch: ${applyErr.stderr || applyErr.message}`));
361
+ console.error(chalk.red(`✖ Failed to apply patch: ${applyErr.stderr || applyErr.message}`));
362
362
  }
363
363
  }
364
364
  } catch (err) {
365
- console.error(chalk.red(`鉁?${err.message}`));
365
+ console.error(chalk.red(`✖ ${err.message}`));
366
366
  process.exit(1);
367
367
  }
368
368
  });
369
369
 
370
- // 鈹€鈹€ Config 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
370
+ // ── Config ─────────────────────────────────────────────────────
371
371
  program
372
372
  .command('config')
373
373
  .description('Manage configuration')
@@ -381,8 +381,8 @@ program
381
381
  const masked = JSON.parse(JSON.stringify(config));
382
382
  if (masked.ai?.apiKey) masked.ai.apiKey = masked.ai.apiKey.slice(0, 8) + '...' + masked.ai.apiKey.slice(-4);
383
383
  if (masked.github?.token) masked.github.token = masked.github.token.slice(0, 8) + '...' + masked.github.token.slice(-4);
384
- console.log(chalk.bold('\n鈿?Active Configuration:'));
385
- console.log('鈹?.repeat(50));
384
+ console.log(chalk.bold('\nActive Configuration / 当前配置:'));
385
+ console.log('━'.repeat(50));
386
386
  console.log(JSON.stringify(masked, null, 2));
387
387
  } else if (action === 'validate') {
388
388
  const fs = require('fs');
@@ -407,13 +407,13 @@ program
407
407
  if (!parsed.ai?.provider) errors.push('Missing "ai.provider"');
408
408
  if (!parsed.ai?.model) errors.push('Missing "ai.model"');
409
409
  if (errors.length === 0) {
410
- console.log(chalk.green(`鉁?Config valid: ${found}`));
410
+ console.log(chalk.green(`✔ Config valid / 配置有效: ${found}`));
411
411
  } else {
412
- console.log(chalk.yellow(`鈿?Config found but has issues:`));
412
+ console.log(chalk.yellow(`⚠ Config found but has issues / 配置存在但有问题:`));
413
413
  for (const e of errors) console.log(chalk.yellow(` ${e}`));
414
414
  }
415
415
  } catch (parseErr) {
416
- console.error(chalk.red(`鉁?Invalid JSON in ${found}: ${parseErr.message}`));
416
+ console.error(chalk.red(`✖ Invalid JSON in ${found}: ${parseErr.message}`));
417
417
  }
418
418
  } else {
419
419
  console.log(chalk.blue(' No config file found in current or parent directories.'));
@@ -437,7 +437,7 @@ program
437
437
  }
438
438
  });
439
439
 
440
- // 鈹€鈹€ Stats 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
440
+ // ── Stats ─────────────────────────────────────────────────────
441
441
  program
442
442
  .command('stats')
443
443
  .description('Review statistics and trends')
@@ -448,9 +448,9 @@ program
448
448
 
449
449
  if (options.clear) {
450
450
  if (clearHistory()) {
451
- console.log(chalk.green('鉁?Review history cleared'));
451
+ console.log(chalk.green('Review history cleared'));
452
452
  } else {
453
- console.error(chalk.red('鉁?Failed to clear history'));
453
+ console.error(chalk.red('Failed to clear history'));
454
454
  }
455
455
  return;
456
456
  }
@@ -463,38 +463,39 @@ program
463
463
  return;
464
464
  }
465
465
 
466
- console.log(chalk.bold('\n馃搳 Review Statistics'));
467
- console.log('鈹?.repeat(50));
468
- console.log(` Period: ${chalk.bold(period)}`);
469
- console.log(` Total reviews: ${stats.total}`);
466
+ console.log(chalk.bold('\n📊 Review Statistics / 审查统计'));
467
+ console.log('━'.repeat(50));
468
+ console.log(` Period / 周期: ${chalk.bold(period)}`);
469
+ console.log(` Total reviews / 总数: ${stats.total}`);
470
470
  if (stats.totalAllTime > stats.total) {
471
- console.log(` All time: ${stats.totalAllTime}`);
471
+ console.log(` All time / 累计: ${stats.totalAllTime}`);
472
472
  }
473
- console.log(` Avg score: ${chalk.cyan(stats.averageScore)}`);
474
- console.log(` Highest: ${chalk.green(stats.highestScore)}`);
475
- console.log(` Lowest: ${chalk.red(stats.lowestScore)}`);
476
- console.log(` Total issues: ${chalk.yellow(stats.totalIssues)}`);
473
+ console.log(` Avg score / 平均分: ${chalk.cyan(stats.averageScore)}`);
474
+ console.log(` Highest / 最高: ${chalk.green(stats.highestScore)}`);
475
+ console.log(` Lowest / 最低: ${chalk.red(stats.lowestScore)}`);
476
+ console.log(` Total issues / 问题数: ${chalk.yellow(stats.totalIssues)}`);
477
477
 
478
478
  if (Object.keys(stats.issueTypes).length > 0) {
479
- console.log(chalk.bold('\n Issue Types:'));
479
+ console.log(chalk.bold('\n Issue Types / 问题类型:'));
480
480
  for (const [type, count] of Object.entries(stats.issueTypes)) {
481
- const icon = type === 'error' ? chalk.red('鉁?) : type === 'warning' ? chalk.yellow('鈿?) : chalk.blue('鈩?);
481
+ const icon = type === 'error' ? chalk.red('✖') : type === 'warning' ? chalk.yellow('⚠') : chalk.blue('ℹ');
482
482
  console.log(` ${icon} ${type}: ${count}`);
483
483
  }
484
484
  }
485
485
 
486
486
  if (Object.keys(stats.severityBreakdown).length > 0) {
487
- console.log(chalk.bold('\n Severity:'));
487
+ console.log(chalk.bold('\n Severity / 严重程度:'));
488
488
  for (const [sev, count] of Object.entries(stats.severityBreakdown)) {
489
489
  const color = sev === 'high' ? chalk.red : sev === 'medium' ? chalk.yellow : chalk.blue;
490
- console.log(` ${color('鈼?)} ${sev}: ${count}`);
490
+ const sevLabel = sev === 'high' ? '严重' : sev === 'medium' ? '中等' : sev === 'low' ? '轻微' : sev;
491
+ console.log(` ${color('●')} ${sevLabel}: ${count}`);
491
492
  }
492
493
  }
493
494
 
494
495
  if (stats.trend.length > 0) {
495
496
  console.log(chalk.bold('\n Trend (last ' + stats.trend.length + ' reviews):'));
496
497
  for (const t of stats.trend) {
497
- const bar = '鈻?.repeat(Math.max(1, Math.round(t.score / 10)));
498
+ const bar = '█'.repeat(Math.max(1, Math.round(t.score / 10)));
498
499
  const color = t.score >= 80 ? chalk.green : t.score >= 50 ? chalk.yellow : chalk.red;
499
500
  console.log(` ${t.date} ${color(bar)} ${t.score} (${t.issues} issues)`);
500
501
  }
@@ -502,7 +503,7 @@ program
502
503
  console.log('');
503
504
  });
504
505
 
505
- // 鈹€鈹€ Hook 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
506
+ // ── Hook ──────────────────────────────────────────────────────
506
507
  program
507
508
  .command('hook')
508
509
  .description('Install or remove a git hook (pre-commit / pre-push)')
@@ -517,20 +518,20 @@ program
517
518
 
518
519
  if (action === 'install') {
519
520
  if (!fs.existsSync(gitDir)) {
520
- console.error(chalk.red('鉁?Not a git repository: ' + process.cwd()));
521
+ console.error(chalk.red('Not a git repository: ' + process.cwd()));
521
522
  process.exit(1);
522
523
  }
523
524
 
524
525
  const hookScript = `#!/bin/sh
525
526
  # coderev ${hookType} hook
526
527
  export PATH="$PATH:$(npm root -g)/../.bin"
527
- echo "鈫?Running coderev ${hookType} hook..."
528
+ echo "Running coderev ${hookType} hook..."
528
529
  coderev review --repo . --output markdown > /tmp/coderev-hook-report.md 2>/dev/null
529
530
  SCORE=$(grep -oP 'Score: \\K\\d+' /tmp/coderev-hook-report.md || echo 0)
530
531
  echo "Score: $SCORE/100"
531
532
  MIN_SCORE=${minScore}
532
533
  if [ "$SCORE" -lt "$MIN_SCORE" ]; then
533
- echo "鉁?Score below threshold ($MIN_SCORE). Aborting ${hookType}."
534
+ echo "Score below threshold ($MIN_SCORE). Aborting ${hookType}."
534
535
  cat /tmp/coderev-hook-report.md
535
536
  exit 1
536
537
  fi
@@ -540,21 +541,21 @@ fi
540
541
  try {
541
542
  fs.chmodSync(hookPath, '755');
542
543
  } catch {}
543
- console.log(chalk.green(`鉁?${hookType} hook installed at ${hookPath}`));
544
+ console.log(chalk.green(`✔ ${hookType} hook installed at ${hookPath}`));
544
545
  } else if (action === 'remove') {
545
546
  if (fs.existsSync(hookPath)) {
546
547
  fs.unlinkSync(hookPath);
547
- console.log(chalk.green(`鉁?${hookType} hook removed`));
548
+ console.log(chalk.green(`✔ ${hookType} hook removed`));
548
549
  } else {
549
550
  console.log(chalk.blue(' No hook to remove'));
550
551
  }
551
552
  } else {
552
- console.error(chalk.red('鉁?Unknown action. Use "install" or "remove".'));
553
+ console.error(chalk.red('Unknown action. Use "install" or "remove".'));
553
554
  process.exit(1);
554
555
  }
555
556
  });
556
557
 
557
- // 鈹€鈹€ Init / Setup 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
558
+ // ── Init / Setup ──────────────────────────────────────────────
558
559
  program
559
560
  .command('init')
560
561
  .description('Create a default coderev config file')
@@ -567,7 +568,7 @@ program
567
568
  model: 'deepseek-chat',
568
569
  temperature: 0.3,
569
570
  maxTokens: 4096,
570
- // 濉叆浣犵殑 API Key 鎴栭€氳繃鐜鍙橀噺璁剧疆
571
+ // 填入你的 API Key 或通过环境变量设置
571
572
  // apiKey: "sk-xxx",
572
573
  // apiKeyEnv: "DEEPSEEK_API_KEY",
573
574
  },
@@ -583,7 +584,7 @@ program
583
584
  };
584
585
  const configPath = path.join(process.cwd(), '.coderevrc.json');
585
586
  fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
586
- console.log(chalk.green(`鉁?Default config created at ${configPath}`));
587
+ console.log(chalk.green(`✔ Default config created at ${configPath}`));
587
588
 
588
589
  // Also create .coderevignore if it doesn't exist
589
590
  const ignorePath = path.join(process.cwd(), '.coderevignore');
@@ -600,7 +601,7 @@ dist/
600
601
  build/
601
602
  `;
602
603
  fs.writeFileSync(ignorePath, ignoreContent);
603
- console.log(chalk.green(`鉁?Default .coderevignore created at ${ignorePath}`));
604
+ console.log(chalk.green(`✔ Default .coderevignore created at ${ignorePath}`));
604
605
  }
605
606
 
606
607
  // Also create .coderevhint if it doesn't exist
@@ -619,13 +620,13 @@ build/
619
620
  - Avoid:
620
621
  `;
621
622
  fs.writeFileSync(hintPath, hintContent);
622
- console.log(chalk.green(`鉁?Default .coderevhint created at ${hintPath}`));
623
+ console.log(chalk.green(`✔ Default .coderevhint created at ${hintPath}`));
623
624
  }
624
625
  });
625
626
 
626
627
  program.parse(process.argv);
627
628
 
628
- // 鈹€鈹€ Helpers 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
629
+ // ── Helpers ───────────────────────────────────────────────────
629
630
  async function getGitDiff(repoPath, base = 'main', head) {
630
631
  const { execSync } = require('child_process');
631
632
  const args = ['git', 'diff'];
@@ -640,76 +641,78 @@ async function getGitDiff(repoPath, base = 'main', head) {
640
641
 
641
642
  function formatTerminal(result) {
642
643
  const lines = [];
643
- lines.push(chalk.bold('\n馃搵 Code Review Report'));
644
- lines.push('鈹?.repeat(50));
644
+ lines.push(chalk.bold('\n📋 Code Review Report / 代码审查报告'));
645
+ lines.push('━'.repeat(50));
645
646
 
646
647
  if (result.summary) {
647
- lines.push(`\n${chalk.bold('Summary:')} ${result.summary}`);
648
+ lines.push('\n' + chalk.bold('Summary / 摘要:') + ' ' + result.summary);
648
649
  }
649
650
 
650
651
  if (result.score !== undefined && result.score !== null) {
651
652
  const color = result.score >= 80 ? chalk.green : result.score >= 50 ? chalk.yellow : chalk.red;
652
- lines.push(`\n${chalk.bold('Score:')} ${color(`${result.score}/100`)}`);
653
+ lines.push('\n' + chalk.bold('Score / 评分:') + ' ' + color(result.score + '/100'));
653
654
  }
654
655
 
655
656
  if (result.issues && result.issues.length > 0) {
656
- lines.push(`\n${chalk.bold(`Issues (${result.issues.length}):`)}`);
657
+ lines.push('\n' + chalk.bold('Issues / 问题 (' + result.issues.length + '):'));
657
658
  for (const issue of result.issues) {
658
659
  const typeLabel =
659
- issue.type === 'error' ? chalk.red('鉁?) :
660
- issue.type === 'warning' ? chalk.yellow('鈿?) : chalk.blue('鈩?);
661
- const severity = issue.severity ? ` [${issue.severity}]` : '';
662
- lines.push(` ${typeLabel}${severity} ${issue.message}`);
663
- if (issue.file) lines.push(` File: ${issue.file}`);
664
- if (issue.line) lines.push(` Line: ${issue.line}`);
665
- if (issue.suggestion) lines.push(` Suggestion: ${issue.suggestion}`);
660
+ issue.type === 'error' ? chalk.red('✖') :
661
+ issue.type === 'warning' ? chalk.yellow('⚠') : chalk.blue('ℹ');
662
+ const sevLabel = issue.severity === 'high' ? '严重' : issue.severity === 'medium' ? '中等' : issue.severity === 'low' ? '轻微' : '';
663
+ const severity = issue.severity ? ' [' + sevLabel + ']' : '';
664
+ lines.push(' ' + typeLabel + severity + ' ' + issue.message);
665
+ if (issue.file) lines.push(' ' + chalk.gray('File / 文件:') + ' ' + issue.file);
666
+ if (issue.line) lines.push(' ' + chalk.gray('Line / 行:') + ' ' + issue.line);
667
+ if (issue.suggestion) lines.push(' ' + chalk.gray('Suggestion / 建议:') + ' ' + issue.suggestion);
666
668
  }
667
669
  }
668
670
 
669
671
  if (result.suggestions && result.suggestions.length > 0) {
670
- lines.push(`\n${chalk.bold('Suggestions:')}`);
672
+ lines.push('\n' + chalk.bold('Suggestions / 改进建议:'));
671
673
  for (const s of result.suggestions) {
672
- lines.push(` 馃挕 ${s}`);
674
+ lines.push(' 💡 ' + s);
673
675
  }
674
676
  }
675
677
 
676
678
  if (result.praise && result.praise.length > 0) {
677
- lines.push(`\n${chalk.bold('馃憤 Good Practices:')}`);
679
+ lines.push('\n' + chalk.bold('👍 Good Practices / 好的实践:'));
678
680
  for (const p of result.praise) {
679
- lines.push(` 鉁?${p}`);
681
+ lines.push(' ✅ ' + p);
680
682
  }
681
683
  }
682
684
 
683
- lines.push('\n' + '鈹?.repeat(50));
685
+ lines.push('\n' + '━'.repeat(50));
684
686
  return lines.join('\n');
685
687
  }
686
688
 
687
689
  function formatMarkdown(result) {
688
- let md = '# 馃搵 Code Review Report\n\n';
690
+ let md = '# 📋 Code Review Report / 代码审查报告\n\n';
689
691
 
690
- if (result.summary) md += `**Summary:** ${result.summary}\n\n`;
691
- if (result.score !== undefined) md += `**Score:** ${result.score}/100\n\n`;
692
+ if (result.summary) md += '**Summary / 摘要:** ' + result.summary + '\n\n';
693
+ if (result.score !== undefined) md += '**Score / 评分:** ' + result.score + '/100\n\n';
692
694
 
693
695
  if (result.issues?.length) {
694
- md += `## Issues (${result.issues.length})\n\n`;
696
+ md += '## Issues / 问题 (' + result.issues.length + ')\n\n';
695
697
  for (const issue of result.issues) {
696
- md += `- **${issue.type.toUpperCase()}**`;
697
- if (issue.severity) md += ` [${issue.severity}]`;
698
- md += `: ${issue.message}\n`;
699
- if (issue.file) md += ` - File: \`${issue.file}\`\n`;
700
- if (issue.line) md += ` - Line: ${issue.line}\n`;
701
- if (issue.suggestion) md += ` - Suggestion: ${issue.suggestion}\n`;
698
+ const sevLabel = issue.severity === 'high' ? '严重' : issue.severity === 'medium' ? '中等' : issue.severity === 'low' ? '轻微' : '';
699
+ md += '- **' + issue.type.toUpperCase() + '**';
700
+ if (sevLabel) md += ' [' + sevLabel + ']';
701
+ md += ': ' + issue.message + '\n';
702
+ if (issue.file) md += ' - File / 文件: `' + issue.file + '`\n';
703
+ if (issue.line) md += ' - Line / 行: ' + issue.line + '\n';
704
+ if (issue.suggestion) md += ' - Suggestion / 建议: ' + issue.suggestion + '\n';
702
705
  }
703
706
  }
704
707
 
705
708
  if (result.suggestions?.length) {
706
- md += `\n## Suggestions\n\n`;
707
- for (const s of result.suggestions) md += `- 馃挕 ${s}\n`;
709
+ md += '\n## Suggestions / 改进建议\n\n';
710
+ for (const s of result.suggestions) md += '- 💡 ' + s + '\n';
708
711
  }
709
712
 
710
713
  if (result.praise?.length) {
711
- md += `\n## 馃憤 Good Practices\n\n`;
712
- for (const p of result.praise) md += `- 鉁?${p}\n`;
714
+ md += '\n## 👍 Good Practices / 好的实践\n\n';
715
+ for (const p of result.praise) md += '- ✅ ' + p + '\n';
713
716
  }
714
717
 
715
718
  return md;