smart-review 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en-US.md +580 -0
- package/README.md +93 -54
- package/bin/install.js +419 -280
- package/bin/review.js +42 -47
- package/index.js +0 -1
- package/lib/ai-client-pool.js +63 -25
- package/lib/ai-client.js +262 -415
- package/lib/config-loader.js +35 -7
- package/lib/default-config.js +42 -32
- package/lib/reviewer.js +289 -97
- package/lib/segmented-analyzer.js +102 -126
- package/lib/utils/git-diff-parser.js +9 -8
- package/lib/utils/i18n.js +980 -0
- package/package.json +2 -10
- package/templates/rules/en-US/best-practices.js +123 -0
- package/templates/rules/en-US/performance.js +136 -0
- package/templates/rules/en-US/security.js +345 -0
- package/templates/rules/zh-CN/best-practices.js +123 -0
- package/templates/rules/zh-CN/performance.js +136 -0
- package/templates/rules/zh-CN/security.js +345 -0
- package/templates/smart-review.json +5 -2
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { stripCommentsForAI, stripNoReviewForAI } from './utils/strip.js';
|
|
7
7
|
import { logger } from './utils/logger.js';
|
|
8
8
|
import { SEGMENTATION_CONSTANTS, AI_CONSTANTS } from './utils/constants.js';
|
|
9
|
+
import { t, getLocale, FIELD_LABELS, buildPrompts } from './utils/i18n.js';
|
|
9
10
|
|
|
10
11
|
export class SegmentedAnalyzer {
|
|
11
12
|
constructor(config = {}) {
|
|
@@ -37,11 +38,9 @@ export class SegmentedAnalyzer {
|
|
|
37
38
|
*/
|
|
38
39
|
async analyzeFileSegmented(filePath, content, aiClient, customPrompt = '', staticIssues = []) {
|
|
39
40
|
try {
|
|
40
|
-
logger.progress(`开始分段分析文件: ${filePath}`);
|
|
41
|
-
|
|
42
41
|
// 第一步:智能分段
|
|
43
42
|
const segments = this.createIntelligentSegments(content, filePath);
|
|
44
|
-
logger.
|
|
43
|
+
logger.progress(t(this.config, 'segment_overall_start', { file: filePath, total: segments.length, concurrency: '', totalNote: '' }));
|
|
45
44
|
|
|
46
45
|
const allIssues = [];
|
|
47
46
|
const segmentSummaries = []; // 存储每段的摘要
|
|
@@ -50,8 +49,8 @@ export class SegmentedAnalyzer {
|
|
|
50
49
|
// 第二步:逐段分析
|
|
51
50
|
for (let i = 0; i < segments.length; i++) {
|
|
52
51
|
const segment = segments[i];
|
|
53
|
-
logger.info(
|
|
54
|
-
logger.
|
|
52
|
+
logger.info(t(this.config, 'segment_start_label', { file: filePath, index: i + 1, total: segments.length, start: segment.startLine, end: segment.endLine }));
|
|
53
|
+
logger.debug(t(this.config, 'segment_size_estimate_dbg', { tokens: segment.tokens, lines: (segment.endLine - segment.startLine + 1) }));
|
|
55
54
|
|
|
56
55
|
try {
|
|
57
56
|
// 分析当前段
|
|
@@ -69,9 +68,9 @@ export class SegmentedAnalyzer {
|
|
|
69
68
|
// 收集问题
|
|
70
69
|
if (segmentResult.issues && segmentResult.issues.length > 0) {
|
|
71
70
|
allIssues.push(...segmentResult.issues);
|
|
72
|
-
logger.success(
|
|
71
|
+
logger.success(t(this.config, 'segment_analysis_done_n_issues', { batch: '', file: filePath, index: i + 1, count: segmentResult.issues.length }));
|
|
73
72
|
} else {
|
|
74
|
-
logger.success(
|
|
73
|
+
logger.success(t(this.config, 'segment_analysis_done_zero', { batch: '', file: filePath, index: i + 1 }));
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
// 收集摘要(如果启用)
|
|
@@ -84,13 +83,14 @@ export class SegmentedAnalyzer {
|
|
|
84
83
|
|
|
85
84
|
// 更新上下文(保留最近几段的摘要)
|
|
86
85
|
const recentSummaries = segmentSummaries.slice(-3); // 保留最近3段摘要
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
const loc = getLocale(this.config);
|
|
87
|
+
previousContext = [t(this.config, 'segment_prev_context_header'),
|
|
88
|
+
...recentSummaries.map(s => t(this.config, 'segment_prev_context_item', { index: s.segmentIndex, range: s.lineRange, summary: s.summary }))
|
|
89
|
+
].join('\n');
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
} catch (error) {
|
|
93
|
-
logger.error(
|
|
93
|
+
logger.error(t(this.config, 'segment_analysis_failed', { index: i + 1, error: error?.message || String(error) }));
|
|
94
94
|
// 继续分析下一段,不中断整个流程
|
|
95
95
|
}
|
|
96
96
|
}
|
|
@@ -106,7 +106,7 @@ export class SegmentedAnalyzer {
|
|
|
106
106
|
};
|
|
107
107
|
|
|
108
108
|
} catch (error) {
|
|
109
|
-
logger.error(
|
|
109
|
+
logger.error(t(this.config, 'segment_file_failed', { error: error?.message || String(error) }));
|
|
110
110
|
throw error;
|
|
111
111
|
}
|
|
112
112
|
}
|
|
@@ -227,13 +227,13 @@ export class SegmentedAnalyzer {
|
|
|
227
227
|
*/
|
|
228
228
|
async analyzeSegment(segment, filePath, aiClient, customPrompt, staticIssues, previousContext, currentSegmentNum, totalSegments) {
|
|
229
229
|
try {
|
|
230
|
-
logger.debug(
|
|
230
|
+
logger.debug(t(this.config, 'segment_prepare_content_dbg', { index: currentSegmentNum }));
|
|
231
231
|
|
|
232
232
|
// 准备代码内容
|
|
233
233
|
const prepared = await stripNoReviewForAI(segment.content, filePath);
|
|
234
234
|
const clean = await stripCommentsForAI(prepared, filePath);
|
|
235
235
|
|
|
236
|
-
logger.debug(
|
|
236
|
+
logger.debug(t(this.config, 'segment_build_prompt_dbg', { index: currentSegmentNum }));
|
|
237
237
|
|
|
238
238
|
// 构建分段分析提示词
|
|
239
239
|
const segmentPrompt = this.buildSegmentPrompt(
|
|
@@ -267,12 +267,12 @@ export class SegmentedAnalyzer {
|
|
|
267
267
|
// 解析AI响应
|
|
268
268
|
const result = this.parseSegmentResponse(responseContent, filePath, segment);
|
|
269
269
|
|
|
270
|
-
logger.info(
|
|
270
|
+
logger.info(t(this.config, 'segment_response_parsed_info', { index: currentSegmentNum, count: (result.issues ? result.issues.length : 0) }));
|
|
271
271
|
|
|
272
272
|
return result;
|
|
273
273
|
|
|
274
274
|
} catch (error) {
|
|
275
|
-
logger.error(
|
|
275
|
+
logger.error(t(this.config, 'segment_analysis_failed', { index: currentSegmentNum, error: error?.message || String(error) }));
|
|
276
276
|
throw error;
|
|
277
277
|
}
|
|
278
278
|
}
|
|
@@ -281,61 +281,48 @@ export class SegmentedAnalyzer {
|
|
|
281
281
|
* 构建分段分析提示词
|
|
282
282
|
*/
|
|
283
283
|
buildSegmentPrompt(segment, filePath, cleanContent, customPrompt, staticIssues, previousContext, currentSegmentNum, totalSegments) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
prompt += `前 ${segment.contextInfo.preContextLines} 行为上文上下文`;
|
|
295
|
-
}
|
|
296
|
-
if (segment.contextInfo.hasPostContext) {
|
|
297
|
-
if (segment.contextInfo.hasPreContext) prompt += `,`;
|
|
298
|
-
prompt += `后 ${segment.contextInfo.postContextLines} 行为下文上下文`;
|
|
299
|
-
}
|
|
300
|
-
prompt += `,这些上下文行仅用于帮助理解代码逻辑,请避免对重叠部分重复报告问题。`;
|
|
301
|
-
}
|
|
284
|
+
const loc = getLocale(this.config);
|
|
285
|
+
const L = FIELD_LABELS[loc];
|
|
286
|
+
let prompt = t(this.config, 'segment_prompt_template', {
|
|
287
|
+
index: currentSegmentNum,
|
|
288
|
+
total: totalSegments,
|
|
289
|
+
Lfile: L.file,
|
|
290
|
+
Lcontent: L.content,
|
|
291
|
+
file: filePath,
|
|
292
|
+
content: cleanContent
|
|
293
|
+
});
|
|
302
294
|
|
|
303
|
-
//
|
|
295
|
+
// 追加前面段落摘要上下文
|
|
304
296
|
if (previousContext && this.config.enableSummaryContext) {
|
|
305
|
-
prompt += `\n\n
|
|
297
|
+
prompt += `\n\n${previousContext}`;
|
|
306
298
|
}
|
|
307
299
|
|
|
308
|
-
//
|
|
309
|
-
prompt += `\n\n**代码内容**:\n\`\`\`\n${cleanContent}\n\`\`\``;
|
|
310
|
-
|
|
311
|
-
// 添加静态问题提示
|
|
300
|
+
// 添加静态问题提示(仅与当前段相关)
|
|
312
301
|
if (staticIssues && staticIssues.length > 0) {
|
|
313
|
-
const relevantStaticIssues = staticIssues.filter(issue =>
|
|
314
|
-
// 简单的行号匹配,实际可以更精确
|
|
315
|
-
return issue.line >= segment.startLine && issue.line <= segment.endLine;
|
|
316
|
-
});
|
|
317
|
-
|
|
302
|
+
const relevantStaticIssues = staticIssues.filter(issue => issue.line >= segment.startLine && issue.line <= segment.endLine);
|
|
318
303
|
if (relevantStaticIssues.length > 0) {
|
|
319
|
-
prompt += `\n\n
|
|
304
|
+
prompt += `\n\n${t(this.config, 'segment_static_issues_header', { index: currentSegmentNum })}\n`;
|
|
320
305
|
relevantStaticIssues.forEach((issue, idx) => {
|
|
321
|
-
|
|
306
|
+
const riskDisp = issue.risk;
|
|
307
|
+
const snippetLabel = L.snippet;
|
|
308
|
+
const suggest = issue.suggestion ? ` ${issue.suggestion}` : '';
|
|
309
|
+
prompt += t(this.config, 'segment_static_issue_line', {
|
|
310
|
+
index: idx + 1,
|
|
311
|
+
risk: riskDisp,
|
|
312
|
+
message: issue.message,
|
|
313
|
+
suggest,
|
|
314
|
+
snippetLabel,
|
|
315
|
+
snippet: issue.snippet || ''
|
|
316
|
+
}) + '\n';
|
|
322
317
|
});
|
|
323
318
|
}
|
|
324
319
|
}
|
|
325
320
|
|
|
326
|
-
//
|
|
321
|
+
// 追加自定义提示词
|
|
327
322
|
if (customPrompt) {
|
|
328
|
-
prompt += `\n\n
|
|
323
|
+
prompt += `\n\n${customPrompt}`;
|
|
329
324
|
}
|
|
330
325
|
|
|
331
|
-
// 添加分段分析要求
|
|
332
|
-
prompt += `\n\n**分段分析要求**:
|
|
333
|
-
1. 请仅分析当前分段的代码,不要分析上下文行中的问题
|
|
334
|
-
2. 如果启用摘要功能,请在分析结果后提供一个简短的代码摘要
|
|
335
|
-
3. 严格按照指定格式返回结果
|
|
336
|
-
4. 对于跨段的问题,请在当前段中标注,后续段会参考前面的摘要进行综合判断
|
|
337
|
-
5. 分段上下文限制:不要评估“导入是否被使用”,严格忽略关于“未使用的导入/模块/依赖”的任何提示或建议(包括建议删除未使用的导入)`;
|
|
338
|
-
|
|
339
326
|
return prompt;
|
|
340
327
|
}
|
|
341
328
|
|
|
@@ -343,52 +330,7 @@ export class SegmentedAnalyzer {
|
|
|
343
330
|
* 获取分段分析的系统提示词
|
|
344
331
|
*/
|
|
345
332
|
getSegmentSystemPrompt() {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
**分段分析特点**:
|
|
349
|
-
- 你收到的是文件的一个片段,可能包含上下文行
|
|
350
|
-
- 上下文行仅用于理解代码逻辑,不应对其报告问题
|
|
351
|
-
- 需要考虑代码的连续性和上下文关系
|
|
352
|
-
- 避免对重叠部分重复报告问题
|
|
353
|
-
|
|
354
|
-
**输出格式要求**:
|
|
355
|
-
请严格按照以下格式返回分析结果:
|
|
356
|
-
|
|
357
|
-
**-----代码分析结果-----**
|
|
358
|
-
文件路径:{文件路径}
|
|
359
|
-
代码片段:{具体的问题代码片段}
|
|
360
|
-
风险等级:{致命/高危/中危/低危/建议}
|
|
361
|
-
风险原因:{详细原因}
|
|
362
|
-
修改建议:{具体建议}
|
|
363
|
-
|
|
364
|
-
[如果有多个问题,用空行分隔]`;
|
|
365
|
-
|
|
366
|
-
// 如果启用摘要功能,添加摘要要求
|
|
367
|
-
if (this.config.enableSummaryContext) {
|
|
368
|
-
systemPrompt += `
|
|
369
|
-
|
|
370
|
-
**摘要要求**:
|
|
371
|
-
在所有问题分析完成后,请提供一个简短摘要:
|
|
372
|
-
|
|
373
|
-
**-----段落摘要-----**
|
|
374
|
-
{简要描述这段代码的主要功能、关键逻辑和重要特征,控制在${this.config.maxSummaryLength}字符以内}`;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
systemPrompt += `
|
|
378
|
-
|
|
379
|
-
**风险等级定义**:
|
|
380
|
-
- 致命:可能导致系统崩溃、数据丢失、严重安全漏洞
|
|
381
|
-
- 高危:可能导致安全漏洞、数据泄露、业务逻辑错误
|
|
382
|
-
- 中危:可能影响系统稳定性、性能问题、用户体验
|
|
383
|
-
- 低危:代码质量问题、不符合最佳实践
|
|
384
|
-
- 建议:改进建议,提升代码质量`;
|
|
385
|
-
|
|
386
|
-
systemPrompt += `
|
|
387
|
-
|
|
388
|
-
**分段场景的忽略规则(必须遵守)**:
|
|
389
|
-
- 由于其它段可能引用当前段的导入,请不要评估或报告“未使用的导入/模块/依赖”相关问题;即使当前片段内未见引用,也不要建议移除该导入。
|
|
390
|
-
- 仅在本段内可以明确识别的实际错误或风险时再输出问题。`;
|
|
391
|
-
|
|
333
|
+
const { systemPrompt } = buildPrompts(this.config);
|
|
392
334
|
return systemPrompt;
|
|
393
335
|
}
|
|
394
336
|
|
|
@@ -400,19 +342,51 @@ export class SegmentedAnalyzer {
|
|
|
400
342
|
let summary = '';
|
|
401
343
|
|
|
402
344
|
try {
|
|
403
|
-
//
|
|
404
|
-
const
|
|
405
|
-
const
|
|
406
|
-
|
|
345
|
+
// 分离问题分析和摘要(支持中英文摘要标记)
|
|
346
|
+
const summaryMarkerZh = t(this.config, 'segment_summary_marker');
|
|
347
|
+
const summaryMarkerEn = '**-----Segment Summary-----**';
|
|
348
|
+
let analysisContent = responseContent;
|
|
349
|
+
let summaryContent = '';
|
|
350
|
+
if (responseContent.includes(summaryMarkerZh)) {
|
|
351
|
+
const parts = responseContent.split(summaryMarkerZh);
|
|
352
|
+
analysisContent = parts[0];
|
|
353
|
+
summaryContent = parts[1];
|
|
354
|
+
} else if (responseContent.includes(summaryMarkerEn)) {
|
|
355
|
+
const parts = responseContent.split(summaryMarkerEn);
|
|
356
|
+
analysisContent = parts[0];
|
|
357
|
+
summaryContent = parts[1];
|
|
358
|
+
}
|
|
407
359
|
|
|
408
|
-
//
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
360
|
+
// 解析问题(支持多种标记:开始/结束 或 单标记 中英文)
|
|
361
|
+
const startEndMarkers = [
|
|
362
|
+
{ start: '**-----代码分析结果开始-----**', end: '**-----代码分析结果结束-----**' },
|
|
363
|
+
{ start: '**-----Code Analysis Result Start-----**', end: '**-----Code Analysis Result End-----**' },
|
|
364
|
+
];
|
|
365
|
+
let parsedByStartEnd = false;
|
|
366
|
+
for (const m of startEndMarkers) {
|
|
367
|
+
if (analysisContent.includes(m.start) && analysisContent.includes(m.end)) {
|
|
368
|
+
parsedByStartEnd = true;
|
|
369
|
+
const regex = new RegExp(`${m.start}([\s\S]*?)${m.end}`, 'g');
|
|
370
|
+
const matches = Array.from(analysisContent.matchAll(regex));
|
|
371
|
+
for (const match of matches) {
|
|
372
|
+
const section = match[1].trim();
|
|
373
|
+
const issue = this.parseIssueSection(section, filePath, segment);
|
|
374
|
+
if (issue) issues.push(issue);
|
|
375
|
+
}
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (!parsedByStartEnd) {
|
|
381
|
+
const singleMarkers = ['**-----代码分析结果-----**', '**-----Code Review Result-----**'];
|
|
382
|
+
for (const marker of singleMarkers) {
|
|
383
|
+
if (analysisContent.includes(marker)) {
|
|
384
|
+
const problemSections = analysisContent.split(marker).slice(1);
|
|
385
|
+
for (const section of problemSections) {
|
|
386
|
+
const issue = this.parseIssueSection(section.trim(), filePath, segment);
|
|
387
|
+
if (issue) issues.push(issue);
|
|
388
|
+
}
|
|
389
|
+
break;
|
|
416
390
|
}
|
|
417
391
|
}
|
|
418
392
|
}
|
|
@@ -423,7 +397,7 @@ export class SegmentedAnalyzer {
|
|
|
423
397
|
}
|
|
424
398
|
|
|
425
399
|
} catch (error) {
|
|
426
|
-
logger.warn(
|
|
400
|
+
logger.warn(t(this.config, 'parse_seg_response_failed_warn', { error: error?.message || String(error) }));
|
|
427
401
|
}
|
|
428
402
|
|
|
429
403
|
return {
|
|
@@ -438,6 +412,8 @@ export class SegmentedAnalyzer {
|
|
|
438
412
|
parseIssueSection(section, filePath, segment) {
|
|
439
413
|
try {
|
|
440
414
|
const lines = section.split('\n').filter(line => line.trim());
|
|
415
|
+
const loc = getLocale(this.config);
|
|
416
|
+
const L = FIELD_LABELS[loc];
|
|
441
417
|
|
|
442
418
|
let issue = {
|
|
443
419
|
file: filePath,
|
|
@@ -447,16 +423,16 @@ export class SegmentedAnalyzer {
|
|
|
447
423
|
};
|
|
448
424
|
|
|
449
425
|
for (const line of lines) {
|
|
450
|
-
if (line.startsWith(
|
|
426
|
+
if (line.startsWith(L.file)) {
|
|
451
427
|
// 文件路径已知,跳过
|
|
452
|
-
} else if (line.startsWith(
|
|
453
|
-
issue.snippet = line.substring(
|
|
454
|
-
} else if (line.startsWith(
|
|
455
|
-
issue.risk = line.substring(
|
|
456
|
-
} else if (line.startsWith(
|
|
457
|
-
issue.message = line.substring(
|
|
458
|
-
} else if (line.startsWith(
|
|
459
|
-
issue.suggestion = line.substring(
|
|
428
|
+
} else if (line.startsWith(L.snippet)) {
|
|
429
|
+
issue.snippet = line.substring(L.snippet.length).trim();
|
|
430
|
+
} else if (line.startsWith(L.risk)) {
|
|
431
|
+
issue.risk = line.substring(L.risk.length).trim();
|
|
432
|
+
} else if (line.startsWith(L.reason)) {
|
|
433
|
+
issue.message = line.substring(L.reason.length).trim();
|
|
434
|
+
} else if (line.startsWith(L.suggestion)) {
|
|
435
|
+
issue.suggestion = line.substring(L.suggestion.length).trim();
|
|
460
436
|
}
|
|
461
437
|
}
|
|
462
438
|
|
|
@@ -466,7 +442,7 @@ export class SegmentedAnalyzer {
|
|
|
466
442
|
}
|
|
467
443
|
|
|
468
444
|
} catch (error) {
|
|
469
|
-
logger.warn(
|
|
445
|
+
logger.warn(t(this.config, 'parse_issue_section_failed_warn', { error: error?.message || String(error) }));
|
|
470
446
|
}
|
|
471
447
|
|
|
472
448
|
return null;
|
|
@@ -2,6 +2,7 @@ import { exec } from 'child_process';
|
|
|
2
2
|
import { promisify } from 'util';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { logger } from './logger.js';
|
|
5
|
+
import { t } from './i18n.js';
|
|
5
6
|
|
|
6
7
|
const execAsync = promisify(exec);
|
|
7
8
|
|
|
@@ -28,7 +29,7 @@ export class GitDiffParser {
|
|
|
28
29
|
});
|
|
29
30
|
return stdout;
|
|
30
31
|
} catch (error) {
|
|
31
|
-
logger.error(
|
|
32
|
+
logger.error(t(this.config, 'git_diff_parse_failed', { error: error?.message || String(error) }));
|
|
32
33
|
return '';
|
|
33
34
|
}
|
|
34
35
|
}
|
|
@@ -81,7 +82,7 @@ export class GitDiffParser {
|
|
|
81
82
|
|
|
82
83
|
// 检查文件是否应该被忽略
|
|
83
84
|
if (!this.isReviewableFile(filePath)) {
|
|
84
|
-
logger.debug(
|
|
85
|
+
logger.debug(t(this.config, 'file_skipped_by_ignore_rule_dbg', { file: filePath }));
|
|
85
86
|
return null;
|
|
86
87
|
}
|
|
87
88
|
|
|
@@ -259,7 +260,7 @@ export class GitDiffParser {
|
|
|
259
260
|
smartSegments.push(this.finalizeSegment(currentSegment));
|
|
260
261
|
}
|
|
261
262
|
|
|
262
|
-
logger.debug(
|
|
263
|
+
logger.debug(t(this.config, 'smart_segmentation_done_dbg', { file: filePath, raw: sections.length, smart: smartSegments.length }));
|
|
263
264
|
return smartSegments;
|
|
264
265
|
}
|
|
265
266
|
|
|
@@ -309,7 +310,7 @@ export class GitDiffParser {
|
|
|
309
310
|
const reviewLineStrings = reviewItems.map(it => (it.type === '+' ? '+' : ' ') + it.content);
|
|
310
311
|
const filteredLines = this.filterDisabledLines(reviewLineStrings, addedLineNumbers);
|
|
311
312
|
if (filteredLines.addedLineNumbers.length === 0) {
|
|
312
|
-
logger.debug(
|
|
313
|
+
logger.debug(t(this.config, 'all_added_lines_ignored_dbg', { file: filePath }));
|
|
313
314
|
return null;
|
|
314
315
|
}
|
|
315
316
|
|
|
@@ -363,7 +364,7 @@ export class GitDiffParser {
|
|
|
363
364
|
});
|
|
364
365
|
return stdout;
|
|
365
366
|
} catch (error) {
|
|
366
|
-
logger.debug(
|
|
367
|
+
logger.debug(t(this.config, 'get_staged_file_failed_dbg', { file: filePath, error: error?.message || String(error) }));
|
|
367
368
|
return '';
|
|
368
369
|
}
|
|
369
370
|
}
|
|
@@ -373,11 +374,11 @@ export class GitDiffParser {
|
|
|
373
374
|
* @returns {Promise<Array>} 审查数据数组
|
|
374
375
|
*/
|
|
375
376
|
async getStagedDiffReviewData() {
|
|
376
|
-
logger.progress('
|
|
377
|
+
logger.progress(t(this.config, 'analyze_staged_changes_progress'));
|
|
377
378
|
|
|
378
379
|
const diffOutput = await this.getStagedDiff();
|
|
379
380
|
if (!diffOutput.trim()) {
|
|
380
|
-
logger.info('
|
|
381
|
+
logger.info(t(this.config, 'no_changes_skip'));
|
|
381
382
|
return [];
|
|
382
383
|
}
|
|
383
384
|
|
|
@@ -398,7 +399,7 @@ export class GitDiffParser {
|
|
|
398
399
|
}
|
|
399
400
|
}
|
|
400
401
|
|
|
401
|
-
logger.info(
|
|
402
|
+
logger.info(t(this.config, 'found_changed_files_n', { count: reviewData.length }));
|
|
402
403
|
return reviewData;
|
|
403
404
|
}
|
|
404
405
|
|