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,503 @@
1
+ /**
2
+ * Tree-sitter AST解析服务
3
+ * 支持多语言源代码解析为AST语法树
4
+ * 优先使用npm包中的语言解析器,降级策略:无解析器时使用基础正则解析
5
+ */
6
+
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const { logger } = require('../../utils/logger');
10
+ const { getFileLanguage } = require('../../utils/helpers');
11
+
12
+ const languageParsers = new Map();
13
+ let Parser = null;
14
+ let parserInitialized = false;
15
+
16
+ const WASM_DIR = path.join(__dirname, '..', '..', '..', 'wasm');
17
+
18
+ const languageMap = {
19
+ javascript: 'javascript',
20
+ typescript: 'typescript',
21
+ python: 'python',
22
+ java: 'java',
23
+ go: 'go',
24
+ rust: 'rust',
25
+ c: 'c',
26
+ cpp: 'cpp',
27
+ csharp: 'c_sharp',
28
+ ruby: 'ruby',
29
+ php: 'php',
30
+ swift: 'swift',
31
+ kotlin: 'kotlin',
32
+ scala: 'scala'
33
+ };
34
+
35
+ const npmPackageMap = {
36
+ javascript: 'tree-sitter-javascript',
37
+ typescript: 'tree-sitter-typescript',
38
+ python: 'tree-sitter-python',
39
+ java: 'tree-sitter-java',
40
+ go: 'tree-sitter-go',
41
+ rust: 'tree-sitter-rust',
42
+ c: 'tree-sitter-c',
43
+ cpp: 'tree-sitter-cpp',
44
+ csharp: 'tree-sitter-c-sharp',
45
+ ruby: 'tree-sitter-ruby',
46
+ php: 'tree-sitter-php'
47
+ };
48
+
49
+ async function initParser() {
50
+ if (parserInitialized) return;
51
+ try {
52
+ Parser = require('web-tree-sitter');
53
+ await Parser.init();
54
+ parserInitialized = true;
55
+ logger.info('Tree-sitter Parser初始化成功');
56
+ } catch (error) {
57
+ logger.warn('Tree-sitter Parser初始化失败,将使用基础解析模式:', error.message);
58
+ Parser = null;
59
+ parserInitialized = true;
60
+ }
61
+ }
62
+
63
+ async function loadLanguage(languageName) {
64
+ if (languageParsers.has(languageName)) {
65
+ return languageParsers.get(languageName);
66
+ }
67
+
68
+ if (!Parser) {
69
+ return null;
70
+ }
71
+
72
+ try {
73
+ const npmPackage = npmPackageMap[languageName];
74
+ if (npmPackage) {
75
+ try {
76
+ const languageModule = require(npmPackage);
77
+ const language = Parser.Language.load(languageModule.wasm || path.join(require.resolve(npmPackage), '../', 'tree-sitter-' + languageMap[languageName] + '.wasm'));
78
+ const parser = new Parser();
79
+ parser.setLanguage(language);
80
+ languageParsers.set(languageName, { parser, language });
81
+ logger.info(`加载语言解析器成功(npm): ${languageName}`);
82
+ return { parser, language };
83
+ } catch (npmError) {
84
+ logger.debug(`npm包加载失败,尝试WASM文件: ${npmError.message}`);
85
+ }
86
+ }
87
+
88
+ const wasmFileName = languageMap[languageName] || languageName;
89
+ const wasmPath = path.join(WASM_DIR, `tree-sitter-${wasmFileName}.wasm`);
90
+
91
+ if (!fs.existsSync(wasmPath)) {
92
+ logger.warn(`语言WASM文件不存在: ${wasmPath}`);
93
+ return null;
94
+ }
95
+
96
+ const language = await Parser.Language.load(wasmPath);
97
+ const parser = new Parser();
98
+ parser.setLanguage(language);
99
+
100
+ languageParsers.set(languageName, { parser, language });
101
+ logger.info(`加载语言解析器成功(wasm): ${languageName}`);
102
+
103
+ return { parser, language };
104
+ } catch (error) {
105
+ logger.error(`加载语言解析器失败: ${languageName}`, error);
106
+ return null;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * 基础解析器(降级模式)
112
+ * 当Tree-sitter不可用时使用正则进行简单解析
113
+ */
114
+ class FallbackParser {
115
+ parse(sourceCode) {
116
+ const lines = sourceCode.split('\n');
117
+ const nodes = [];
118
+
119
+ lines.forEach((line, index) => {
120
+ // 检测变量声明
121
+ const varMatch = line.match(/(?:const|let|var)\s+(\w+)/);
122
+ if (varMatch) {
123
+ nodes.push({
124
+ type: 'variable_declarator',
125
+ text: line.trim(),
126
+ startPosition: { row: index, column: line.search(/\b(?:const|let|var)\b/) },
127
+ endPosition: { row: index, column: line.length }
128
+ });
129
+ }
130
+
131
+ // 检测函数声明
132
+ const funcMatch = line.match(/function\s+(\w+)/);
133
+ if (funcMatch) {
134
+ nodes.push({
135
+ type: 'function_declaration',
136
+ text: line.trim(),
137
+ startPosition: { row: index, column: line.search(/\bfunction\b/) },
138
+ endPosition: { row: index, column: line.length }
139
+ });
140
+ }
141
+
142
+ // 检测导入语句
143
+ const importMatch = line.match(/import\s+/);
144
+ if (importMatch) {
145
+ nodes.push({
146
+ type: 'import_statement',
147
+ text: line.trim(),
148
+ startPosition: { row: index, column: 0 },
149
+ endPosition: { row: index, column: line.length }
150
+ });
151
+ }
152
+
153
+ // 检测if语句
154
+ const ifMatch = line.match(/if\s*\(/);
155
+ if (ifMatch) {
156
+ nodes.push({
157
+ type: 'if_statement',
158
+ text: line.trim(),
159
+ startPosition: { row: index, column: line.search(/\bif\b/) },
160
+ endPosition: { row: index, column: line.length }
161
+ });
162
+ }
163
+
164
+ // 检测for循环
165
+ const forMatch = line.match(/for\s*\(/);
166
+ if (forMatch) {
167
+ nodes.push({
168
+ type: 'for_statement',
169
+ text: line.trim(),
170
+ startPosition: { row: index, column: line.search(/\bfor\b/) },
171
+ endPosition: { row: index, column: line.length }
172
+ });
173
+ }
174
+
175
+ // 检测while循环
176
+ const whileMatch = line.match(/while\s*\(/);
177
+ if (whileMatch) {
178
+ nodes.push({
179
+ type: 'while_statement',
180
+ text: line.trim(),
181
+ startPosition: { row: index, column: line.search(/\bwhile\b/) },
182
+ endPosition: { row: index, column: line.length }
183
+ });
184
+ }
185
+
186
+ // 检测console.log调用
187
+ const consoleMatch = line.match(/console\.log\(/);
188
+ if (consoleMatch) {
189
+ nodes.push({
190
+ type: 'call_expression',
191
+ text: line.trim(),
192
+ startPosition: { row: index, column: line.search('console.log') },
193
+ endPosition: { row: index, column: line.length }
194
+ });
195
+ }
196
+
197
+ // 检测其他函数调用
198
+ const callMatch = line.match(/(\w+)\s*\(/);
199
+ if (callMatch && !line.includes('function') && !line.includes('return')) {
200
+ nodes.push({
201
+ type: 'call_expression',
202
+ text: line.trim(),
203
+ startPosition: { row: index, column: callMatch.index },
204
+ endPosition: { row: index, column: line.length }
205
+ });
206
+ }
207
+
208
+ // 检测标识符
209
+ const idMatches = line.matchAll(/\b([a-zA-Z_]\w*)\b/g);
210
+ for (const match of idMatches) {
211
+ const keyword = ['const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'import', 'from', 'class', 'console'];
212
+ if (!keyword.includes(match[1])) {
213
+ nodes.push({
214
+ type: 'identifier',
215
+ text: match[1],
216
+ startPosition: { row: index, column: match.index },
217
+ endPosition: { row: index, column: match.index + match[1].length }
218
+ });
219
+ }
220
+ }
221
+
222
+ // 检测数字
223
+ const numMatches = line.matchAll(/\b(\d+(?:\.\d+)?)\b/g);
224
+ for (const match of numMatches) {
225
+ nodes.push({
226
+ type: 'number',
227
+ text: match[1],
228
+ startPosition: { row: index, column: match.index },
229
+ endPosition: { row: index, column: match.index + match[1].length }
230
+ });
231
+ }
232
+ });
233
+
234
+ return {
235
+ rootNode: {
236
+ type: 'program',
237
+ text: sourceCode,
238
+ startPosition: { row: 0, column: 0 },
239
+ endPosition: { row: lines.length - 1, column: lines[lines.length - 1]?.length || 0 },
240
+ childCount: nodes.length,
241
+ children: nodes
242
+ }
243
+ };
244
+ }
245
+ }
246
+
247
+ /**
248
+ * 解析源代码为AST
249
+ */
250
+ async function parseCode(sourceCode, languageName) {
251
+ try {
252
+ await initParser();
253
+ const langParser = await loadLanguage(languageName);
254
+
255
+ if (langParser) {
256
+ const tree = langParser.parser.parse(sourceCode);
257
+ return {
258
+ success: true,
259
+ tree,
260
+ language: languageName,
261
+ rootNode: tree.rootNode
262
+ };
263
+ }
264
+
265
+ // 降级到基础解析器
266
+ logger.warn(`Tree-sitter不可用,使用基础解析器: ${languageName}`);
267
+ const fallback = new FallbackParser();
268
+ const tree = fallback.parse(sourceCode);
269
+ return {
270
+ success: true,
271
+ tree,
272
+ language: languageName,
273
+ rootNode: tree.rootNode,
274
+ fallback: true
275
+ };
276
+ } catch (error) {
277
+ logger.error('解析代码失败:', error);
278
+ return {
279
+ success: false,
280
+ error: error.message
281
+ };
282
+ }
283
+ }
284
+
285
+ /**
286
+ * 解析文件
287
+ */
288
+ async function parseFile(filePath) {
289
+ try {
290
+ const sourceCode = fs.readFileSync(filePath, 'utf-8');
291
+ const languageName = getFileLanguage(filePath);
292
+
293
+ return await parseCode(sourceCode, languageName);
294
+ } catch (error) {
295
+ logger.error(`解析文件失败: ${filePath}`, error);
296
+ return {
297
+ success: false,
298
+ error: error.message,
299
+ filePath
300
+ };
301
+ }
302
+ }
303
+
304
+ /**
305
+ * 查询AST节点
306
+ */
307
+ async function queryNodes(tree, language, queryPattern) {
308
+ try {
309
+ const langParser = languageParsers.get(language);
310
+ if (!langParser) {
311
+ throw new Error(`语言解析器未加载: ${language}`);
312
+ }
313
+
314
+ const query = langParser.language.query(queryPattern);
315
+ const matches = query.matches(tree.rootNode);
316
+
317
+ return matches.map(match => ({
318
+ pattern: match.pattern,
319
+ captures: match.captures.map(capture => ({
320
+ name: capture.name,
321
+ node: capture.node,
322
+ text: capture.node.text,
323
+ startPosition: capture.node.startPosition,
324
+ endPosition: capture.node.endPosition,
325
+ startPosition: {
326
+ row: capture.node.startPosition.row,
327
+ column: capture.node.startPosition.column
328
+ },
329
+ endPosition: {
330
+ row: capture.node.endPosition.row,
331
+ column: capture.node.endPosition.column
332
+ }
333
+ }))
334
+ }));
335
+ } catch (error) {
336
+ logger.error('查询AST节点失败:', error);
337
+ return [];
338
+ }
339
+ }
340
+
341
+ /**
342
+ * 遍历AST树
343
+ */
344
+ function traverseAST(node, callback, depth = 0) {
345
+ callback(node, depth);
346
+
347
+ const children = node.children || (typeof node.childCount === 'number' ? [] : []);
348
+ for (let i = 0; i < children.length; i++) {
349
+ traverseAST(children[i], callback, depth + 1);
350
+ }
351
+
352
+ // Tree-sitter原生节点使用child()方法
353
+ if (node.childCount && typeof node.child === 'function') {
354
+ for (let i = 0; i < node.childCount; i++) {
355
+ const child = node.child(i);
356
+ if (child) traverseAST(child, callback, depth + 1);
357
+ }
358
+ }
359
+ }
360
+
361
+ /**
362
+ * 获取所有特定类型的节点
363
+ */
364
+ function getNodesByType(rootNode, nodeType) {
365
+ const nodes = [];
366
+
367
+ traverseAST(rootNode, (node) => {
368
+ if (node.type === nodeType) {
369
+ nodes.push({
370
+ type: node.type,
371
+ text: node.text,
372
+ startPosition: {
373
+ row: node.startPosition.row,
374
+ column: node.startPosition.column
375
+ },
376
+ endPosition: {
377
+ row: node.endPosition.row,
378
+ column: node.endPosition.column
379
+ },
380
+ children: node.childCount
381
+ });
382
+ }
383
+ });
384
+
385
+ return nodes;
386
+ }
387
+
388
+ /**
389
+ * 提取函数声明节点
390
+ */
391
+ async function extractFunctions(tree) {
392
+ return getNodesByType(tree.rootNode, 'function_declaration');
393
+ }
394
+
395
+ /**
396
+ * 提取变量声明节点
397
+ */
398
+ async function extractVariables(tree) {
399
+ return getNodesByType(tree.rootNode, 'variable_declarator');
400
+ }
401
+
402
+ /**
403
+ * 提取导入语句节点
404
+ */
405
+ async function extractImports(tree) {
406
+ return getNodesByType(tree.rootNode, 'import_statement');
407
+ }
408
+
409
+ /**
410
+ * 提取类声明节点
411
+ */
412
+ async function extractClasses(tree) {
413
+ return getNodesByType(tree.rootNode, 'class_declaration');
414
+ }
415
+
416
+ /**
417
+ * 提取标识符节点
418
+ */
419
+ async function extractIdentifiers(tree) {
420
+ return getNodesByType(tree.rootNode, 'identifier');
421
+ }
422
+
423
+ /**
424
+ * 获取AST树结构摘要
425
+ */
426
+ function getASTSummary(tree) {
427
+ const summary = {
428
+ totalNodes: 0,
429
+ nodeTypes: {},
430
+ maxDepth: 0
431
+ };
432
+
433
+ traverseAST(tree.rootNode, (node, depth) => {
434
+ summary.totalNodes++;
435
+
436
+ if (!summary.nodeTypes[node.type]) {
437
+ summary.nodeTypes[node.type] = 0;
438
+ }
439
+ summary.nodeTypes[node.type]++;
440
+
441
+ if (depth > summary.maxDepth) {
442
+ summary.maxDepth = depth;
443
+ }
444
+ });
445
+
446
+ return summary;
447
+ }
448
+
449
+ /**
450
+ * 将AST转换为JSON格式(用于存储和传输)
451
+ */
452
+ function astToJson(node) {
453
+ return {
454
+ type: node.type,
455
+ text: node.text,
456
+ startPosition: {
457
+ row: node.startPosition.row,
458
+ column: node.startPosition.column
459
+ },
460
+ endPosition: {
461
+ row: node.endPosition.row,
462
+ column: node.endPosition.column
463
+ },
464
+ children: node.children ? node.children.map(child => astToJson(child)) : []
465
+ };
466
+ }
467
+
468
+ /**
469
+ * 释放Parser资源
470
+ */
471
+ function disposeParser() {
472
+ languageParsers.forEach((langParser, languageName) => {
473
+ if (langParser.parser) {
474
+ langParser.parser.dispose();
475
+ }
476
+ if (langParser.language) {
477
+ langParser.language.dispose();
478
+ }
479
+ logger.info(`释放语言解析器: ${languageName}`);
480
+ });
481
+
482
+ languageParsers.clear();
483
+ }
484
+
485
+ // 不再在模块加载时自动初始化,改为按需初始化(在parseCode中调用)
486
+
487
+ module.exports = {
488
+ initParser,
489
+ loadLanguage,
490
+ parseCode,
491
+ parseFile,
492
+ queryNodes,
493
+ traverseAST,
494
+ getNodesByType,
495
+ extractFunctions,
496
+ extractVariables,
497
+ extractImports,
498
+ extractClasses,
499
+ extractIdentifiers,
500
+ getASTSummary,
501
+ astToJson,
502
+ disposeParser
503
+ };