mr-sliy 1.0.0

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 (50) hide show
  1. package/.env.example +145 -0
  2. package/database/schema.sql +187 -0
  3. package/package.json +74 -0
  4. package/scripts/download-tree-sitter.js +171 -0
  5. package/scripts/postinstall.js +134 -0
  6. package/src/agent/agent.js +563 -0
  7. package/src/agent.js +87 -0
  8. package/src/cli/index.js +1643 -0
  9. package/src/config/index.js +232 -0
  10. package/src/engine/dualModeEngine.js +486 -0
  11. package/src/index.js +165 -0
  12. package/src/middlewares/errorHandler.js +166 -0
  13. package/src/middlewares/index.js +23 -0
  14. package/src/routes/aiRoutes.js +117 -0
  15. package/src/routes/configRoutes.js +31 -0
  16. package/src/routes/index.js +75 -0
  17. package/src/routes/issueRoutes.js +195 -0
  18. package/src/routes/projectRoutes.js +46 -0
  19. package/src/routes/reportRoutes.js +40 -0
  20. package/src/routes/scanRoutes.js +245 -0
  21. package/src/routes/userRoutes.js +47 -0
  22. package/src/services/ast/parser.js +503 -0
  23. package/src/services/detection/detector.js +934 -0
  24. package/src/services/llm/providers.js +1107 -0
  25. package/src/services/rag/agent.js +375 -0
  26. package/src/services/vector/knowledgeBase.js +863 -0
  27. package/src/skills/Skill.js +38 -0
  28. package/src/skills/code-analysis/index.js +272 -0
  29. package/src/skills/code-detection/index.js +166 -0
  30. package/src/skills/code-detection/rules/console-log.js +45 -0
  31. package/src/skills/code-detection/rules/deep-nesting.js +76 -0
  32. package/src/skills/code-detection/rules/duplicate-code.js +57 -0
  33. package/src/skills/code-detection/rules/high-complexity.js +109 -0
  34. package/src/skills/code-detection/rules/index.js +59 -0
  35. package/src/skills/code-detection/rules/long-functions.js +54 -0
  36. package/src/skills/code-detection/rules/magic-numbers.js +48 -0
  37. package/src/skills/code-detection/rules/missing-comment.js +64 -0
  38. package/src/skills/code-detection/rules/null-check.js +71 -0
  39. package/src/skills/code-detection/rules/unnecessary-else.js +46 -0
  40. package/src/skills/code-detection/rules/unused-functions.js +57 -0
  41. package/src/skills/code-detection/rules/unused-imports.js +57 -0
  42. package/src/skills/code-detection/rules/unused-variables.js +54 -0
  43. package/src/skills/code-optimization/index.js +319 -0
  44. package/src/skills/index.js +152 -0
  45. package/src/utils/crypto.js +212 -0
  46. package/src/utils/database.js +125 -0
  47. package/src/utils/helpers.js +226 -0
  48. package/src/utils/logger.js +202 -0
  49. package/src/utils/mysql.js +198 -0
  50. package/src/utils/response.js +124 -0
@@ -0,0 +1,38 @@
1
+ /**
2
+ * 技能基类
3
+ * 所有智能体技能都继承自此类
4
+ */
5
+
6
+ class Skill {
7
+ constructor(name, description, version = '1.0.0') {
8
+ this.name = name;
9
+ this.description = description;
10
+ this.version = version;
11
+ this.enabled = true;
12
+ this.dependencies = [];
13
+ }
14
+
15
+ async init() {
16
+ return true;
17
+ }
18
+
19
+ async execute(context = {}) {
20
+ throw new Error(`技能 ${this.name} 必须实现 execute 方法`);
21
+ }
22
+
23
+ canExecute(context = {}) {
24
+ return this.enabled;
25
+ }
26
+
27
+ getInfo() {
28
+ return {
29
+ name: this.name,
30
+ description: this.description,
31
+ version: this.version,
32
+ enabled: this.enabled,
33
+ dependencies: this.dependencies
34
+ };
35
+ }
36
+ }
37
+
38
+ module.exports = Skill;
@@ -0,0 +1,272 @@
1
+ /**
2
+ * 代码分析技能
3
+ * 提供代码统计、结构分析、复杂度评估等功能
4
+ */
5
+
6
+ const Skill = require('../Skill');
7
+ const { logger } = require('../../utils/logger');
8
+ const { getFileLanguage, countLines } = require('../../utils/helpers');
9
+ const { parseCode, extractFunctions, extractVariables, extractImports } = require('../../services/ast/parser');
10
+
11
+ class CodeAnalysisSkill extends Skill {
12
+ constructor() {
13
+ super(
14
+ 'code-analysis',
15
+ '代码分析技能 - 代码统计、结构分析和复杂度评估',
16
+ '1.0.0'
17
+ );
18
+ this.dependencies = ['ast-parser'];
19
+ }
20
+
21
+ async init() {
22
+ logger.info('代码分析技能初始化完成');
23
+ return true;
24
+ }
25
+
26
+ canExecute(context = {}) {
27
+ return this.enabled && (context.sourceCode || context.filePath);
28
+ }
29
+
30
+ async execute(context = {}) {
31
+ const { sourceCode, filePath, options = {} } = context;
32
+
33
+ if (!sourceCode && !filePath) {
34
+ throw new Error('请提供源代码或文件路径');
35
+ }
36
+
37
+ const code = sourceCode || this._readFile(filePath);
38
+ const language = options.language || getFileLanguage(filePath || '');
39
+
40
+ return this._analyzeCode(code, filePath || 'unknown', language, options);
41
+ }
42
+
43
+ async analyzeFile(filePath, options = {}) {
44
+ const fs = require('fs');
45
+ const sourceCode = fs.readFileSync(filePath, 'utf-8');
46
+ return this.execute({ sourceCode, filePath, options });
47
+ }
48
+
49
+ async analyzeProject(dirPath, options = {}) {
50
+ const fs = require('fs');
51
+ const path = require('path');
52
+ const { scanExtensions, excludeDirs, excludeFiles } = require('../../config').config.scan;
53
+
54
+ const files = this._collectFiles(dirPath, scanExtensions, excludeDirs, excludeFiles);
55
+ const results = [];
56
+ const summary = {
57
+ totalFiles: files.length,
58
+ totalLines: 0,
59
+ totalFunctions: 0,
60
+ totalClasses: 0,
61
+ languages: {},
62
+ fileTypes: {}
63
+ };
64
+
65
+ for (const file of files) {
66
+ try {
67
+ const sourceCode = fs.readFileSync(file, 'utf-8');
68
+ const relPath = path.relative(dirPath, file);
69
+ const result = await this.execute({
70
+ sourceCode,
71
+ filePath: relPath,
72
+ options
73
+ });
74
+
75
+ if (result.success) {
76
+ results.push(result);
77
+ summary.totalLines += result.metrics.totalLines;
78
+ summary.totalFunctions += result.metrics.functionCount;
79
+
80
+ const lang = result.language;
81
+ summary.languages[lang] = (summary.languages[lang] || 0) + 1;
82
+
83
+ const ext = path.extname(file);
84
+ summary.fileTypes[ext] = (summary.fileTypes[ext] || 0) + 1;
85
+ }
86
+ } catch (error) {
87
+ logger.debug(`分析文件失败: ${file}`, error.message);
88
+ }
89
+ }
90
+
91
+ return {
92
+ success: true,
93
+ projectPath: dirPath,
94
+ summary,
95
+ files: results,
96
+ totalFiles: files.length,
97
+ analyzedFiles: results.length
98
+ };
99
+ }
100
+
101
+ async getMetrics(sourceCode, language = 'javascript') {
102
+ const lines = countLines(sourceCode);
103
+ const parseResult = await parseCode(sourceCode, language);
104
+
105
+ if (!parseResult.success) {
106
+ return { totalLines: lines };
107
+ }
108
+
109
+ const tree = parseResult.tree;
110
+ const functions = extractFunctions(tree, sourceCode);
111
+ const variables = extractVariables(tree, sourceCode);
112
+ const imports = extractImports(tree, sourceCode);
113
+
114
+ const avgFunctionLength = functions.length > 0
115
+ ? Math.round(functions.reduce((sum, f) => sum + (f.endLine - f.startLine + 1), 0) / functions.length)
116
+ : 0;
117
+
118
+ const maxFunctionLength = functions.length > 0
119
+ ? Math.max(...functions.map(f => f.endLine - f.startLine + 1))
120
+ : 0;
121
+
122
+ const commentLines = this._countCommentLines(sourceCode, language);
123
+ const commentRatio = lines > 0 ? (commentLines / lines * 100).toFixed(1) : 0;
124
+
125
+ return {
126
+ totalLines: lines,
127
+ codeLines: lines - commentLines,
128
+ commentLines,
129
+ commentRatio: `${commentRatio}%`,
130
+ functionCount: functions.length,
131
+ variableCount: variables.length,
132
+ importCount: imports.length,
133
+ avgFunctionLength,
134
+ maxFunctionLength,
135
+ functions: functions.map(f => ({
136
+ name: f.name,
137
+ startLine: f.startLine,
138
+ endLine: f.endLine,
139
+ lineCount: f.endLine - f.startLine + 1
140
+ }))
141
+ };
142
+ }
143
+
144
+ async getStructure(sourceCode, language = 'javascript') {
145
+ const parseResult = await parseCode(sourceCode, language);
146
+
147
+ if (!parseResult.success) {
148
+ return { success: false, error: parseResult.error };
149
+ }
150
+
151
+ const tree = parseResult.tree;
152
+ const functions = extractFunctions(tree, sourceCode);
153
+ const imports = extractImports(tree, sourceCode);
154
+ const variables = extractVariables(tree, sourceCode);
155
+
156
+ return {
157
+ success: true,
158
+ language,
159
+ structure: {
160
+ imports: imports.map(i => i.name),
161
+ functions: functions.map(f => ({
162
+ name: f.name,
163
+ line: f.startLine,
164
+ params: f.params || [],
165
+ isAsync: f.isAsync || false
166
+ })),
167
+ variables: variables.map(v => ({
168
+ name: v.name,
169
+ line: v.line,
170
+ type: v.kind || 'var'
171
+ }))
172
+ }
173
+ };
174
+ }
175
+
176
+ _readFile(filePath) {
177
+ const fs = require('fs');
178
+ return fs.readFileSync(filePath, 'utf-8');
179
+ }
180
+
181
+ async _analyzeCode(sourceCode, filePath, language, options) {
182
+ const startTime = Date.now();
183
+
184
+ try {
185
+ logger.debug(`开始分析代码: ${filePath}`);
186
+
187
+ const metrics = await this.getMetrics(sourceCode, language);
188
+ const structure = await this.getStructure(sourceCode, language);
189
+
190
+ return {
191
+ success: true,
192
+ filePath,
193
+ fileName: filePath.split(/[/\\]/).pop(),
194
+ language,
195
+ metrics,
196
+ structure: structure.structure || {},
197
+ durationMs: Date.now() - startTime
198
+ };
199
+ } catch (error) {
200
+ logger.error(`代码分析失败: ${filePath}`, error);
201
+ return {
202
+ success: false,
203
+ message: error.message,
204
+ filePath,
205
+ durationMs: Date.now() - startTime
206
+ };
207
+ }
208
+ }
209
+
210
+ _countCommentLines(sourceCode, language) {
211
+ const lines = sourceCode.split('\n');
212
+ let commentLines = 0;
213
+ let inBlockComment = false;
214
+
215
+ for (const line of lines) {
216
+ const trimmed = line.trim();
217
+
218
+ if (inBlockComment) {
219
+ commentLines++;
220
+ if (trimmed.includes('*/')) {
221
+ inBlockComment = false;
222
+ }
223
+ continue;
224
+ }
225
+
226
+ if (trimmed.startsWith('//')) {
227
+ commentLines++;
228
+ } else if (trimmed.startsWith('/*')) {
229
+ commentLines++;
230
+ if (!trimmed.includes('*/')) {
231
+ inBlockComment = true;
232
+ }
233
+ } else if (trimmed.startsWith('#') && ['python', 'ruby'].includes(language)) {
234
+ commentLines++;
235
+ }
236
+ }
237
+
238
+ return commentLines;
239
+ }
240
+
241
+ _collectFiles(dirPath, extensions, excludeDirs, excludeFiles) {
242
+ const fs = require('fs');
243
+ const path = require('path');
244
+ const files = [];
245
+
246
+ function scanDirectory(currentPath) {
247
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true });
248
+
249
+ for (const entry of entries) {
250
+ const fullPath = path.join(currentPath, entry.name);
251
+
252
+ if (entry.isDirectory()) {
253
+ if (!excludeDirs.includes(entry.name)) {
254
+ scanDirectory(fullPath);
255
+ }
256
+ } else if (entry.isFile()) {
257
+ const ext = path.extname(entry.name);
258
+ if (extensions.includes(ext) && !excludeFiles.some(ef => entry.name.endsWith(ef))) {
259
+ files.push(fullPath);
260
+ }
261
+ }
262
+ }
263
+ }
264
+
265
+ scanDirectory(dirPath);
266
+ return files;
267
+ }
268
+ }
269
+
270
+ const codeAnalysisSkill = new CodeAnalysisSkill();
271
+
272
+ module.exports = codeAnalysisSkill;
@@ -0,0 +1,166 @@
1
+ /**
2
+ * 代码检测技能
3
+ * 基于 AST 静态分析检测代码中的各种问题和不规范之处
4
+ */
5
+
6
+ const Skill = require('../Skill');
7
+ const { logger } = require('../../utils/logger');
8
+ const { parseCode } = require('../../services/ast/parser');
9
+ const { getFileLanguage } = require('../../utils/helpers');
10
+ const { getEnabledRules, getRuleConfig } = require('./rules');
11
+
12
+ class CodeDetectionSkill extends Skill {
13
+ constructor() {
14
+ super(
15
+ 'code-detection',
16
+ '代码缺陷检测技能 - 基于AST静态分析检测代码问题',
17
+ '1.0.0'
18
+ );
19
+ this.dependencies = ['ast-parser'];
20
+ this.enabledRules = [];
21
+ }
22
+
23
+ async init() {
24
+ this.enabledRules = getEnabledRules();
25
+ logger.info(`代码检测技能初始化完成,启用 ${this.enabledRules.length} 条检测规则`);
26
+ return true;
27
+ }
28
+
29
+ canExecute(context = {}) {
30
+ return this.enabled && (context.sourceCode || context.filePath);
31
+ }
32
+
33
+ async execute(context = {}) {
34
+ const { sourceCode, filePath, options = {} } = context;
35
+
36
+ if (!sourceCode && !filePath) {
37
+ throw new Error('请提供源代码或文件路径');
38
+ }
39
+
40
+ const code = sourceCode || this._readFile(filePath);
41
+ const language = options.language || getFileLanguage(filePath || '');
42
+
43
+ return this._detectIssues(code, filePath || 'unknown', language, options);
44
+ }
45
+
46
+ async detectFile(filePath, options = {}) {
47
+ const fs = require('fs');
48
+ const sourceCode = fs.readFileSync(filePath, 'utf-8');
49
+ return this.execute({ sourceCode, filePath, options });
50
+ }
51
+
52
+ async detectSource(sourceCode, language = 'javascript', options = {}) {
53
+ return this.execute({ sourceCode, language, options });
54
+ }
55
+
56
+ async batchDetect(filePaths, options = {}) {
57
+ const fs = require('fs');
58
+ const results = [];
59
+ const startTime = Date.now();
60
+
61
+ for (const filePath of filePaths) {
62
+ try {
63
+ const sourceCode = fs.readFileSync(filePath, 'utf-8');
64
+ const result = await this.execute({ sourceCode, filePath, options });
65
+ results.push(result);
66
+ } catch (error) {
67
+ logger.error(`批量检测文件失败: ${filePath}`, error);
68
+ results.push({
69
+ success: false,
70
+ message: error.message,
71
+ filePath
72
+ });
73
+ }
74
+ }
75
+
76
+ return {
77
+ success: true,
78
+ totalFiles: filePaths.length,
79
+ scannedFiles: results.filter(r => r.success).length,
80
+ failedFiles: results.filter(r => !r.success).length,
81
+ totalIssues: results.reduce((sum, r) => sum + (r.totalIssues || 0), 0),
82
+ results,
83
+ durationMs: Date.now() - startTime
84
+ };
85
+ }
86
+
87
+ getRules() {
88
+ return this.enabledRules;
89
+ }
90
+
91
+ _readFile(filePath) {
92
+ const fs = require('fs');
93
+ return fs.readFileSync(filePath, 'utf-8');
94
+ }
95
+
96
+ async _detectIssues(sourceCode, filePath, language, options = {}) {
97
+ const startTime = Date.now();
98
+
99
+ try {
100
+ logger.debug(`开始检测: ${filePath} [${language}]`);
101
+
102
+ const parseResult = await parseCode(sourceCode, language);
103
+
104
+ if (!parseResult.success) {
105
+ return {
106
+ success: false,
107
+ message: parseResult.error,
108
+ filePath,
109
+ language
110
+ };
111
+ }
112
+
113
+ const tree = parseResult.tree;
114
+ const issues = [];
115
+
116
+ for (const rule of this.enabledRules) {
117
+ try {
118
+ const ruleConfig = getRuleConfig(rule.id);
119
+ if (!ruleConfig.enabled) continue;
120
+
121
+ const ruleModule = require(`./rules/${this._ruleIdToFileName(rule.id)}`);
122
+ const ruleIssues = ruleModule.detect(tree, sourceCode, filePath, ruleConfig);
123
+ issues.push(...ruleIssues);
124
+ } catch (ruleError) {
125
+ logger.warn(`规则 ${rule.id} 执行失败: ${ruleError.message}`);
126
+ }
127
+ }
128
+
129
+ const result = {
130
+ success: true,
131
+ filePath,
132
+ language,
133
+ totalIssues: issues.length,
134
+ issueCounts: {
135
+ critical: issues.filter(i => i.severity === 'critical').length,
136
+ high: issues.filter(i => i.severity === 'high').length,
137
+ medium: issues.filter(i => i.severity === 'medium').length,
138
+ low: issues.filter(i => i.severity === 'low').length
139
+ },
140
+ issues,
141
+ rulesApplied: this.enabledRules.map(r => r.id),
142
+ durationMs: Date.now() - startTime
143
+ };
144
+
145
+ logger.debug(`检测完成: ${filePath}, 发现 ${issues.length} 个问题`);
146
+ return result;
147
+ } catch (error) {
148
+ logger.error(`检测失败: ${filePath}`, error);
149
+ return {
150
+ success: false,
151
+ message: error.message,
152
+ filePath,
153
+ language,
154
+ durationMs: Date.now() - startTime
155
+ };
156
+ }
157
+ }
158
+
159
+ _ruleIdToFileName(ruleId) {
160
+ return ruleId.replace(/_/g, '-');
161
+ }
162
+ }
163
+
164
+ const codeDetectionSkill = new CodeDetectionSkill();
165
+
166
+ module.exports = codeDetectionSkill;
@@ -0,0 +1,45 @@
1
+ const { getNodesByType } = require('../../../services/ast/parser');
2
+ const { generateUUID, getFileLanguage } = require('../../../utils/helpers');
3
+
4
+ const meta = {
5
+ id: 'console_log',
6
+ name: 'console.log检测',
7
+ description: '检测调试用的console.log语句',
8
+ category: 'code-smell',
9
+ defaultSeverity: 'low'
10
+ };
11
+
12
+ function detect(tree, sourceCode, filePath, ruleConfig) {
13
+ const issues = [];
14
+
15
+ try {
16
+ const callExpressions = getNodesByType(tree.rootNode, 'call_expression');
17
+
18
+ callExpressions.forEach(callExpr => {
19
+ if (callExpr.text.startsWith('console.log(')) {
20
+ issues.push({
21
+ id: generateUUID(),
22
+ filePath,
23
+ fileName: filePath.split(/[/\\]/).pop(),
24
+ language: getFileLanguage(filePath),
25
+ issueType: 'console_log',
26
+ severity: ruleConfig.severity || meta.defaultSeverity,
27
+ message: '存在调试用的console.log',
28
+ suggestion: '删除或替换为正式的日志记录',
29
+ lineStart: callExpr.startPosition.row + 1,
30
+ lineEnd: callExpr.endPosition.row + 1,
31
+ columnStart: callExpr.startPosition.column,
32
+ columnEnd: callExpr.endPosition.column,
33
+ codeSnippet: callExpr.text.substring(0, 80),
34
+ astNodeType: 'call_expression'
35
+ });
36
+ }
37
+ });
38
+ } catch (error) {
39
+ console.error('检测console.log失败:', error);
40
+ }
41
+
42
+ return issues;
43
+ }
44
+
45
+ module.exports = { meta, detect };
@@ -0,0 +1,76 @@
1
+ const { generateUUID, getFileLanguage } = require('../../../utils/helpers');
2
+
3
+ const meta = {
4
+ id: 'deep_nesting',
5
+ name: '嵌套过深检测',
6
+ description: '检测代码中嵌套层级过深的情况',
7
+ category: 'code-smell',
8
+ defaultSeverity: 'medium'
9
+ };
10
+
11
+ const DEFAULT_THRESHOLD = 4;
12
+
13
+ function detect(tree, sourceCode, filePath, ruleConfig) {
14
+ const issues = [];
15
+ const threshold = ruleConfig.threshold || DEFAULT_THRESHOLD;
16
+
17
+ try {
18
+ const lines = sourceCode.split('\n');
19
+ let maxDepth = 0;
20
+ let deepLines = [];
21
+
22
+ lines.forEach((line, index) => {
23
+ const trimmed = line.trim();
24
+ if (!trimmed) return;
25
+
26
+ const indentMatch = line.match(/^(\s+)/);
27
+ const indentSpaces = indentMatch ? indentMatch[1].length : 0;
28
+ const indentLevel = Math.floor(indentSpaces / 2);
29
+
30
+ const currentDepth = indentLevel;
31
+
32
+ if (currentDepth > maxDepth) {
33
+ maxDepth = currentDepth;
34
+ }
35
+
36
+ if (currentDepth > threshold) {
37
+ deepLines.push({
38
+ line: index + 1,
39
+ depth: currentDepth,
40
+ text: trimmed.substring(0, 50)
41
+ });
42
+ }
43
+ });
44
+
45
+ if (maxDepth > threshold && deepLines.length > 0) {
46
+ const firstDeepLine = deepLines[0];
47
+ issues.push({
48
+ id: generateUUID(),
49
+ filePath,
50
+ fileName: filePath.split(/[/\\]/).pop(),
51
+ language: getFileLanguage(filePath),
52
+ issueType: 'deep_nesting',
53
+ severity: ruleConfig.severity || meta.defaultSeverity,
54
+ message: `嵌套层级过深(最大${maxDepth}层,阈值${threshold}层)`,
55
+ suggestion: '减少嵌套层级,提取内层逻辑为独立函数',
56
+ lineStart: firstDeepLine.line,
57
+ lineEnd: firstDeepLine.line,
58
+ columnStart: 0,
59
+ columnEnd: firstDeepLine.text.length,
60
+ codeSnippet: firstDeepLine.text,
61
+ astNodeType: 'deep_nesting',
62
+ metadata: {
63
+ maxDepth,
64
+ threshold,
65
+ deepLineCount: deepLines.length
66
+ }
67
+ });
68
+ }
69
+ } catch (error) {
70
+ console.error('检测嵌套过深失败:', error);
71
+ }
72
+
73
+ return issues;
74
+ }
75
+
76
+ module.exports = { meta, detect };
@@ -0,0 +1,57 @@
1
+ const { generateUUID, getFileLanguage } = require('../../../utils/helpers');
2
+
3
+ const meta = {
4
+ id: 'duplicate_code',
5
+ name: '重复代码检测',
6
+ description: '检测代码中重复的代码片段',
7
+ category: 'code-smell',
8
+ defaultSeverity: 'medium'
9
+ };
10
+
11
+ const DEFAULT_MIN_LINES = 3;
12
+
13
+ function detect(tree, sourceCode, filePath, ruleConfig) {
14
+ const issues = [];
15
+ const minDuplicateLines = ruleConfig.threshold || DEFAULT_MIN_LINES;
16
+
17
+ try {
18
+ const lines = sourceCode.split('\n');
19
+ const seenBlocks = new Map();
20
+
21
+ for (let i = 0; i < lines.length - minDuplicateLines; i++) {
22
+ const block = lines.slice(i, i + minDuplicateLines).join('\n').trim();
23
+ if (block.length < 20) continue;
24
+
25
+ const trimmed = block.replace(/\s+/g, '');
26
+ if (seenBlocks.has(trimmed)) {
27
+ const previousLine = seenBlocks.get(trimmed);
28
+ if (i > previousLine + minDuplicateLines) {
29
+ issues.push({
30
+ id: generateUUID(),
31
+ filePath,
32
+ fileName: filePath.split(/[/\\]/).pop(),
33
+ language: getFileLanguage(filePath),
34
+ issueType: 'duplicate_code',
35
+ severity: ruleConfig.severity || meta.defaultSeverity,
36
+ message: `发现重复代码片段(第${previousLine + 1}行和第${i + 1}行)`,
37
+ suggestion: '提取重复代码为独立函数或模块',
38
+ lineStart: i + 1,
39
+ lineEnd: i + minDuplicateLines,
40
+ columnStart: 0,
41
+ columnEnd: block.length,
42
+ codeSnippet: block.substring(0, 100),
43
+ astNodeType: 'duplicate_block'
44
+ });
45
+ }
46
+ } else {
47
+ seenBlocks.set(trimmed, i);
48
+ }
49
+ }
50
+ } catch (error) {
51
+ console.error('检测重复代码失败:', error);
52
+ }
53
+
54
+ return issues;
55
+ }
56
+
57
+ module.exports = { meta, detect };