musubi-sdd 3.10.0 → 5.1.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.
- package/README.md +24 -19
- package/package.json +1 -1
- package/src/agents/agent-loop.js +532 -0
- package/src/agents/agentic/code-generator.js +767 -0
- package/src/agents/agentic/code-reviewer.js +698 -0
- package/src/agents/agentic/index.js +43 -0
- package/src/agents/function-tool.js +432 -0
- package/src/agents/index.js +45 -0
- package/src/agents/schema-generator.js +514 -0
- package/src/analyzers/ast-extractor.js +870 -0
- package/src/analyzers/context-optimizer.js +681 -0
- package/src/analyzers/repository-map.js +692 -0
- package/src/integrations/index.js +7 -1
- package/src/integrations/mcp/index.js +175 -0
- package/src/integrations/mcp/mcp-context-provider.js +472 -0
- package/src/integrations/mcp/mcp-discovery.js +436 -0
- package/src/integrations/mcp/mcp-tool-registry.js +467 -0
- package/src/integrations/mcp-connector.js +818 -0
- package/src/integrations/tool-discovery.js +589 -0
- package/src/managers/index.js +7 -0
- package/src/managers/skill-tools.js +565 -0
- package/src/monitoring/cost-tracker.js +7 -0
- package/src/monitoring/incident-manager.js +10 -0
- package/src/monitoring/observability.js +10 -0
- package/src/monitoring/quality-dashboard.js +491 -0
- package/src/monitoring/release-manager.js +10 -0
- package/src/orchestration/agent-skill-binding.js +655 -0
- package/src/orchestration/error-handler.js +827 -0
- package/src/orchestration/index.js +235 -1
- package/src/orchestration/mcp-tool-adapters.js +896 -0
- package/src/orchestration/reasoning/index.js +58 -0
- package/src/orchestration/reasoning/planning-engine.js +831 -0
- package/src/orchestration/reasoning/reasoning-engine.js +710 -0
- package/src/orchestration/reasoning/self-correction.js +751 -0
- package/src/orchestration/skill-executor.js +665 -0
- package/src/orchestration/skill-registry.js +650 -0
- package/src/orchestration/workflow-examples.js +1072 -0
- package/src/orchestration/workflow-executor.js +779 -0
- package/src/phase4-integration.js +248 -0
- package/src/phase5-integration.js +402 -0
- package/src/steering/steering-auto-update.js +572 -0
- package/src/steering/steering-validator.js +547 -0
- package/src/templates/template-constraints.js +646 -0
- package/src/validators/advanced-validation.js +580 -0
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file code-reviewer.js
|
|
3
|
+
* @description Autonomous code review engine for agentic coding
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { EventEmitter } = require('events');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Review severity levels
|
|
14
|
+
* @enum {string}
|
|
15
|
+
*/
|
|
16
|
+
const SEVERITY = {
|
|
17
|
+
INFO: 'info',
|
|
18
|
+
WARNING: 'warning',
|
|
19
|
+
ERROR: 'error',
|
|
20
|
+
CRITICAL: 'critical'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Issue category types
|
|
25
|
+
* @enum {string}
|
|
26
|
+
*/
|
|
27
|
+
const CATEGORY = {
|
|
28
|
+
STYLE: 'style',
|
|
29
|
+
LOGIC: 'logic',
|
|
30
|
+
SECURITY: 'security',
|
|
31
|
+
PERFORMANCE: 'performance',
|
|
32
|
+
MAINTAINABILITY: 'maintainability',
|
|
33
|
+
BEST_PRACTICE: 'best-practice',
|
|
34
|
+
ERROR_HANDLING: 'error-handling',
|
|
35
|
+
DOCUMENTATION: 'documentation'
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {Object} ReviewIssue
|
|
40
|
+
* @property {string} id - Issue identifier
|
|
41
|
+
* @property {string} severity - Issue severity
|
|
42
|
+
* @property {string} category - Issue category
|
|
43
|
+
* @property {string} message - Issue message
|
|
44
|
+
* @property {number} [line] - Line number
|
|
45
|
+
* @property {number} [column] - Column number
|
|
46
|
+
* @property {string} [code] - Problematic code snippet
|
|
47
|
+
* @property {string} [suggestion] - Fix suggestion
|
|
48
|
+
* @property {string} [rule] - Rule that was violated
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @typedef {Object} ReviewResult
|
|
53
|
+
* @property {string} id - Review identifier
|
|
54
|
+
* @property {string} filePath - Reviewed file path
|
|
55
|
+
* @property {string} language - Code language
|
|
56
|
+
* @property {ReviewIssue[]} issues - Found issues
|
|
57
|
+
* @property {number} score - Overall score (0-100)
|
|
58
|
+
* @property {Object} summary - Review summary
|
|
59
|
+
* @property {number} timestamp - Review timestamp
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @typedef {Object} CodeReviewerOptions
|
|
64
|
+
* @property {Object} [rules={}] - Custom review rules
|
|
65
|
+
* @property {string[]} [enabledCategories] - Categories to check
|
|
66
|
+
* @property {number} [minScore=70] - Minimum acceptable score
|
|
67
|
+
* @property {boolean} [strictMode=false] - Enable strict checking
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Default review rules
|
|
72
|
+
*/
|
|
73
|
+
const DEFAULT_RULES = {
|
|
74
|
+
javascript: [
|
|
75
|
+
{
|
|
76
|
+
id: 'js-no-var',
|
|
77
|
+
pattern: /\bvar\s+/g,
|
|
78
|
+
category: CATEGORY.BEST_PRACTICE,
|
|
79
|
+
severity: SEVERITY.WARNING,
|
|
80
|
+
message: 'Use const or let instead of var',
|
|
81
|
+
suggestion: 'Replace var with const or let'
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'js-console-log',
|
|
85
|
+
pattern: /console\.(log|debug|info)\(/g,
|
|
86
|
+
category: CATEGORY.BEST_PRACTICE,
|
|
87
|
+
severity: SEVERITY.INFO,
|
|
88
|
+
message: 'Console statements should be removed in production',
|
|
89
|
+
suggestion: 'Remove or replace with proper logging'
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'js-eval',
|
|
93
|
+
pattern: /\beval\s*\(/g,
|
|
94
|
+
category: CATEGORY.SECURITY,
|
|
95
|
+
severity: SEVERITY.CRITICAL,
|
|
96
|
+
message: 'eval() is dangerous and should be avoided',
|
|
97
|
+
suggestion: 'Use safer alternatives to eval()'
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'js-todo',
|
|
101
|
+
pattern: /\/\/\s*TODO:/gi,
|
|
102
|
+
category: CATEGORY.MAINTAINABILITY,
|
|
103
|
+
severity: SEVERITY.INFO,
|
|
104
|
+
message: 'TODO comment found',
|
|
105
|
+
suggestion: 'Complete or track the TODO item'
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'js-empty-catch',
|
|
109
|
+
pattern: /catch\s*\([^)]*\)\s*\{\s*\}/g,
|
|
110
|
+
category: CATEGORY.ERROR_HANDLING,
|
|
111
|
+
severity: SEVERITY.ERROR,
|
|
112
|
+
message: 'Empty catch block swallows errors',
|
|
113
|
+
suggestion: 'Handle or log the caught error'
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'js-magic-number',
|
|
117
|
+
pattern: /(?<![\w.])(?<!\.)\b\d{2,}\b(?![\w.])/g,
|
|
118
|
+
category: CATEGORY.MAINTAINABILITY,
|
|
119
|
+
severity: SEVERITY.INFO,
|
|
120
|
+
message: 'Magic number should be extracted to a named constant',
|
|
121
|
+
suggestion: 'Define a constant with a descriptive name'
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'js-long-function',
|
|
125
|
+
pattern: /function\s+\w+\s*\([^)]*\)\s*\{/g,
|
|
126
|
+
category: CATEGORY.MAINTAINABILITY,
|
|
127
|
+
severity: SEVERITY.WARNING,
|
|
128
|
+
message: 'Function may be too long',
|
|
129
|
+
checkLength: true,
|
|
130
|
+
maxLength: 50
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'js-no-strict',
|
|
134
|
+
checkGlobal: true,
|
|
135
|
+
pattern: null,
|
|
136
|
+
category: CATEGORY.BEST_PRACTICE,
|
|
137
|
+
severity: SEVERITY.WARNING,
|
|
138
|
+
message: 'Consider adding "use strict"',
|
|
139
|
+
condition: (code) => !code.includes("'use strict'") && !code.includes('"use strict"')
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
|
|
143
|
+
python: [
|
|
144
|
+
{
|
|
145
|
+
id: 'py-except-bare',
|
|
146
|
+
pattern: /except\s*:/g,
|
|
147
|
+
category: CATEGORY.ERROR_HANDLING,
|
|
148
|
+
severity: SEVERITY.WARNING,
|
|
149
|
+
message: 'Bare except catches all exceptions',
|
|
150
|
+
suggestion: 'Specify the exception type to catch'
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 'py-print',
|
|
154
|
+
pattern: /\bprint\s*\(/g,
|
|
155
|
+
category: CATEGORY.BEST_PRACTICE,
|
|
156
|
+
severity: SEVERITY.INFO,
|
|
157
|
+
message: 'Print statements should use logging in production',
|
|
158
|
+
suggestion: 'Use logging module instead'
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: 'py-todo',
|
|
162
|
+
pattern: /#\s*TODO:/gi,
|
|
163
|
+
category: CATEGORY.MAINTAINABILITY,
|
|
164
|
+
severity: SEVERITY.INFO,
|
|
165
|
+
message: 'TODO comment found',
|
|
166
|
+
suggestion: 'Complete or track the TODO item'
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: 'py-global',
|
|
170
|
+
pattern: /\bglobal\s+\w+/g,
|
|
171
|
+
category: CATEGORY.BEST_PRACTICE,
|
|
172
|
+
severity: SEVERITY.WARNING,
|
|
173
|
+
message: 'Global variables should be avoided',
|
|
174
|
+
suggestion: 'Consider using class or function parameters'
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
|
|
178
|
+
common: [
|
|
179
|
+
{
|
|
180
|
+
id: 'common-fixme',
|
|
181
|
+
pattern: /FIXME/gi,
|
|
182
|
+
category: CATEGORY.MAINTAINABILITY,
|
|
183
|
+
severity: SEVERITY.WARNING,
|
|
184
|
+
message: 'FIXME comment found - needs attention',
|
|
185
|
+
suggestion: 'Address the FIXME issue'
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: 'common-hack',
|
|
189
|
+
pattern: /HACK/gi,
|
|
190
|
+
category: CATEGORY.MAINTAINABILITY,
|
|
191
|
+
severity: SEVERITY.WARNING,
|
|
192
|
+
message: 'HACK comment found - technical debt',
|
|
193
|
+
suggestion: 'Refactor the hack'
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: 'common-password',
|
|
197
|
+
pattern: /password\s*=\s*['"][^'"]+['"]/gi,
|
|
198
|
+
category: CATEGORY.SECURITY,
|
|
199
|
+
severity: SEVERITY.CRITICAL,
|
|
200
|
+
message: 'Hardcoded password detected',
|
|
201
|
+
suggestion: 'Use environment variables or secure vault'
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: 'common-api-key',
|
|
205
|
+
pattern: /api[_-]?key\s*=\s*['"][^'"]+['"]/gi,
|
|
206
|
+
category: CATEGORY.SECURITY,
|
|
207
|
+
severity: SEVERITY.CRITICAL,
|
|
208
|
+
message: 'Hardcoded API key detected',
|
|
209
|
+
suggestion: 'Use environment variables'
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Code Reviewer class for autonomous code review
|
|
216
|
+
* @extends EventEmitter
|
|
217
|
+
*/
|
|
218
|
+
class CodeReviewer extends EventEmitter {
|
|
219
|
+
/**
|
|
220
|
+
* Create code reviewer
|
|
221
|
+
* @param {CodeReviewerOptions} [options={}] - Reviewer options
|
|
222
|
+
*/
|
|
223
|
+
constructor(options = {}) {
|
|
224
|
+
super();
|
|
225
|
+
|
|
226
|
+
this.rules = this.mergeRules(DEFAULT_RULES, options.rules || {});
|
|
227
|
+
this.enabledCategories = options.enabledCategories || Object.values(CATEGORY);
|
|
228
|
+
this.minScore = options.minScore ?? 70;
|
|
229
|
+
this.strictMode = options.strictMode ?? false;
|
|
230
|
+
|
|
231
|
+
// State
|
|
232
|
+
this.reviews = new Map();
|
|
233
|
+
this.reviewCounter = 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Merge custom rules with defaults
|
|
238
|
+
* @private
|
|
239
|
+
*/
|
|
240
|
+
mergeRules(defaults, custom) {
|
|
241
|
+
const merged = { ...defaults };
|
|
242
|
+
|
|
243
|
+
for (const [lang, rules] of Object.entries(custom)) {
|
|
244
|
+
if (merged[lang]) {
|
|
245
|
+
merged[lang] = [...merged[lang], ...rules];
|
|
246
|
+
} else {
|
|
247
|
+
merged[lang] = rules;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return merged;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Review code
|
|
256
|
+
* @param {string} code - Code to review
|
|
257
|
+
* @param {Object} [options={}] - Review options
|
|
258
|
+
* @returns {ReviewResult}
|
|
259
|
+
*/
|
|
260
|
+
review(code, options = {}) {
|
|
261
|
+
const id = this.generateId();
|
|
262
|
+
const language = options.language || this.detectLanguage(code, options.filePath);
|
|
263
|
+
const filePath = options.filePath || 'unknown';
|
|
264
|
+
|
|
265
|
+
this.emit('review:start', { id, filePath, language });
|
|
266
|
+
|
|
267
|
+
const issues = [];
|
|
268
|
+
|
|
269
|
+
// Get applicable rules
|
|
270
|
+
const rules = [
|
|
271
|
+
...(this.rules[language] || []),
|
|
272
|
+
...(this.rules.common || [])
|
|
273
|
+
];
|
|
274
|
+
|
|
275
|
+
// Apply each rule
|
|
276
|
+
for (const rule of rules) {
|
|
277
|
+
// Check if category is enabled
|
|
278
|
+
if (!this.enabledCategories.includes(rule.category)) continue;
|
|
279
|
+
|
|
280
|
+
// Apply rule
|
|
281
|
+
const ruleIssues = this.applyRule(rule, code, language);
|
|
282
|
+
issues.push(...ruleIssues);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Run additional checks
|
|
286
|
+
const structuralIssues = this.checkStructure(code, language);
|
|
287
|
+
issues.push(...structuralIssues);
|
|
288
|
+
|
|
289
|
+
// Calculate score
|
|
290
|
+
const score = this.calculateScore(issues);
|
|
291
|
+
|
|
292
|
+
// Create result
|
|
293
|
+
const result = {
|
|
294
|
+
id,
|
|
295
|
+
filePath,
|
|
296
|
+
language,
|
|
297
|
+
issues,
|
|
298
|
+
score,
|
|
299
|
+
summary: this.createSummary(issues, score),
|
|
300
|
+
timestamp: Date.now()
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// Store review
|
|
304
|
+
this.reviews.set(id, result);
|
|
305
|
+
|
|
306
|
+
this.emit('review:complete', { result });
|
|
307
|
+
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Apply a single rule
|
|
313
|
+
* @private
|
|
314
|
+
*/
|
|
315
|
+
applyRule(rule, code, language) {
|
|
316
|
+
const issues = [];
|
|
317
|
+
|
|
318
|
+
// Handle condition-based rules
|
|
319
|
+
if (rule.condition) {
|
|
320
|
+
if (rule.condition(code)) {
|
|
321
|
+
issues.push(this.createIssue(rule, null, null));
|
|
322
|
+
}
|
|
323
|
+
return issues;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Handle pattern-based rules
|
|
327
|
+
if (!rule.pattern) return issues;
|
|
328
|
+
|
|
329
|
+
const lines = code.split('\n');
|
|
330
|
+
let match;
|
|
331
|
+
|
|
332
|
+
// Reset regex
|
|
333
|
+
rule.pattern.lastIndex = 0;
|
|
334
|
+
|
|
335
|
+
while ((match = rule.pattern.exec(code)) !== null) {
|
|
336
|
+
const line = this.getLineNumber(code, match.index);
|
|
337
|
+
const column = match.index - this.getLineStart(code, line);
|
|
338
|
+
|
|
339
|
+
// Skip if checking length and within limit
|
|
340
|
+
if (rule.checkLength) {
|
|
341
|
+
const funcEnd = this.findFunctionEnd(code, match.index);
|
|
342
|
+
const funcLines = code.substring(match.index, funcEnd).split('\n').length;
|
|
343
|
+
if (funcLines <= rule.maxLength) continue;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
issues.push(this.createIssue(rule, line, column, match[0]));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return issues;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Create an issue object
|
|
354
|
+
* @private
|
|
355
|
+
*/
|
|
356
|
+
createIssue(rule, line, column, code = null) {
|
|
357
|
+
return {
|
|
358
|
+
id: `issue-${++this.reviewCounter}`,
|
|
359
|
+
severity: rule.severity,
|
|
360
|
+
category: rule.category,
|
|
361
|
+
message: rule.message,
|
|
362
|
+
line,
|
|
363
|
+
column,
|
|
364
|
+
code,
|
|
365
|
+
suggestion: rule.suggestion,
|
|
366
|
+
rule: rule.id
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Check code structure
|
|
372
|
+
* @private
|
|
373
|
+
*/
|
|
374
|
+
checkStructure(code, language) {
|
|
375
|
+
const issues = [];
|
|
376
|
+
const lines = code.split('\n');
|
|
377
|
+
|
|
378
|
+
// Check line length
|
|
379
|
+
lines.forEach((line, index) => {
|
|
380
|
+
if (line.length > 120) {
|
|
381
|
+
issues.push({
|
|
382
|
+
id: `issue-${++this.reviewCounter}`,
|
|
383
|
+
severity: SEVERITY.INFO,
|
|
384
|
+
category: CATEGORY.STYLE,
|
|
385
|
+
message: `Line exceeds 120 characters (${line.length})`,
|
|
386
|
+
line: index + 1,
|
|
387
|
+
suggestion: 'Break long lines for better readability'
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Check for missing documentation
|
|
393
|
+
if (language === 'javascript' || language === 'typescript') {
|
|
394
|
+
if (!code.includes('/**') && !code.includes('//')) {
|
|
395
|
+
issues.push({
|
|
396
|
+
id: `issue-${++this.reviewCounter}`,
|
|
397
|
+
severity: SEVERITY.INFO,
|
|
398
|
+
category: CATEGORY.DOCUMENTATION,
|
|
399
|
+
message: 'Code lacks documentation comments',
|
|
400
|
+
suggestion: 'Add JSDoc comments to functions and classes'
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Check nesting depth
|
|
406
|
+
const maxNesting = this.checkNestingDepth(code);
|
|
407
|
+
if (maxNesting > 4) {
|
|
408
|
+
issues.push({
|
|
409
|
+
id: `issue-${++this.reviewCounter}`,
|
|
410
|
+
severity: SEVERITY.WARNING,
|
|
411
|
+
category: CATEGORY.MAINTAINABILITY,
|
|
412
|
+
message: `Deep nesting detected (depth: ${maxNesting})`,
|
|
413
|
+
suggestion: 'Consider extracting nested code into separate functions'
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return issues;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Check nesting depth
|
|
422
|
+
* @private
|
|
423
|
+
*/
|
|
424
|
+
checkNestingDepth(code) {
|
|
425
|
+
let maxDepth = 0;
|
|
426
|
+
let currentDepth = 0;
|
|
427
|
+
|
|
428
|
+
for (const char of code) {
|
|
429
|
+
if (char === '{') {
|
|
430
|
+
currentDepth++;
|
|
431
|
+
maxDepth = Math.max(maxDepth, currentDepth);
|
|
432
|
+
} else if (char === '}') {
|
|
433
|
+
currentDepth = Math.max(0, currentDepth - 1);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return maxDepth;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Calculate review score
|
|
442
|
+
* @private
|
|
443
|
+
*/
|
|
444
|
+
calculateScore(issues) {
|
|
445
|
+
let score = 100;
|
|
446
|
+
|
|
447
|
+
const penalties = {
|
|
448
|
+
[SEVERITY.INFO]: 1,
|
|
449
|
+
[SEVERITY.WARNING]: 3,
|
|
450
|
+
[SEVERITY.ERROR]: 10,
|
|
451
|
+
[SEVERITY.CRITICAL]: 25
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
for (const issue of issues) {
|
|
455
|
+
score -= penalties[issue.severity] || 0;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return Math.max(0, score);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Create review summary
|
|
463
|
+
* @private
|
|
464
|
+
*/
|
|
465
|
+
createSummary(issues, score) {
|
|
466
|
+
const bySeverity = {};
|
|
467
|
+
const byCategory = {};
|
|
468
|
+
|
|
469
|
+
for (const issue of issues) {
|
|
470
|
+
bySeverity[issue.severity] = (bySeverity[issue.severity] || 0) + 1;
|
|
471
|
+
byCategory[issue.category] = (byCategory[issue.category] || 0) + 1;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
totalIssues: issues.length,
|
|
476
|
+
bySeverity,
|
|
477
|
+
byCategory,
|
|
478
|
+
score,
|
|
479
|
+
passed: score >= this.minScore,
|
|
480
|
+
recommendation: this.getRecommendation(score, issues)
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Get recommendation based on score
|
|
486
|
+
* @private
|
|
487
|
+
*/
|
|
488
|
+
getRecommendation(score, issues) {
|
|
489
|
+
if (score >= 90) return 'Excellent! Code quality is high.';
|
|
490
|
+
if (score >= 80) return 'Good. Minor improvements recommended.';
|
|
491
|
+
if (score >= 70) return 'Acceptable. Several issues should be addressed.';
|
|
492
|
+
if (score >= 50) return 'Needs work. Significant improvements required.';
|
|
493
|
+
return 'Critical issues detected. Immediate attention required.';
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Get line number from position
|
|
498
|
+
* @private
|
|
499
|
+
*/
|
|
500
|
+
getLineNumber(code, position) {
|
|
501
|
+
return code.substring(0, position).split('\n').length;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Get line start position
|
|
506
|
+
* @private
|
|
507
|
+
*/
|
|
508
|
+
getLineStart(code, lineNumber) {
|
|
509
|
+
const lines = code.split('\n');
|
|
510
|
+
let start = 0;
|
|
511
|
+
for (let i = 0; i < lineNumber - 1 && i < lines.length; i++) {
|
|
512
|
+
start += lines[i].length + 1;
|
|
513
|
+
}
|
|
514
|
+
return start;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Find function end position
|
|
519
|
+
* @private
|
|
520
|
+
*/
|
|
521
|
+
findFunctionEnd(code, start) {
|
|
522
|
+
let depth = 0;
|
|
523
|
+
let inFunction = false;
|
|
524
|
+
|
|
525
|
+
for (let i = start; i < code.length; i++) {
|
|
526
|
+
if (code[i] === '{') {
|
|
527
|
+
depth++;
|
|
528
|
+
inFunction = true;
|
|
529
|
+
} else if (code[i] === '}') {
|
|
530
|
+
depth--;
|
|
531
|
+
if (inFunction && depth === 0) {
|
|
532
|
+
return i + 1;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return code.length;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Detect language
|
|
542
|
+
* @private
|
|
543
|
+
*/
|
|
544
|
+
detectLanguage(code, filePath) {
|
|
545
|
+
if (filePath) {
|
|
546
|
+
const ext = path.extname(filePath);
|
|
547
|
+
const langMap = {
|
|
548
|
+
'.js': 'javascript',
|
|
549
|
+
'.ts': 'typescript',
|
|
550
|
+
'.py': 'python',
|
|
551
|
+
'.jsx': 'javascript',
|
|
552
|
+
'.tsx': 'typescript'
|
|
553
|
+
};
|
|
554
|
+
return langMap[ext] || 'javascript';
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Detect from content
|
|
558
|
+
if (code.includes('def ') || code.includes('import ') && !code.includes('from \'')) {
|
|
559
|
+
return 'python';
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return 'javascript';
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Generate unique ID
|
|
567
|
+
* @private
|
|
568
|
+
*/
|
|
569
|
+
generateId() {
|
|
570
|
+
return `review-${Date.now().toString(36)}-${Math.random().toString(36).substr(2, 6)}`;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Get review by ID
|
|
575
|
+
* @param {string} reviewId - Review identifier
|
|
576
|
+
* @returns {ReviewResult|null}
|
|
577
|
+
*/
|
|
578
|
+
getReview(reviewId) {
|
|
579
|
+
return this.reviews.get(reviewId) || null;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Get all reviews
|
|
584
|
+
* @returns {ReviewResult[]}
|
|
585
|
+
*/
|
|
586
|
+
getAllReviews() {
|
|
587
|
+
return Array.from(this.reviews.values());
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Get statistics
|
|
592
|
+
* @returns {Object}
|
|
593
|
+
*/
|
|
594
|
+
getStats() {
|
|
595
|
+
const reviews = this.getAllReviews();
|
|
596
|
+
const scores = reviews.map(r => r.score);
|
|
597
|
+
|
|
598
|
+
return {
|
|
599
|
+
totalReviews: reviews.length,
|
|
600
|
+
averageScore: scores.length > 0 ? scores.reduce((a, b) => a + b, 0) / scores.length : 0,
|
|
601
|
+
passed: reviews.filter(r => r.summary.passed).length,
|
|
602
|
+
failed: reviews.filter(r => !r.summary.passed).length,
|
|
603
|
+
totalIssues: reviews.reduce((sum, r) => sum + r.issues.length, 0)
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Add custom rule
|
|
609
|
+
* @param {string} language - Language
|
|
610
|
+
* @param {Object} rule - Rule definition
|
|
611
|
+
*/
|
|
612
|
+
addRule(language, rule) {
|
|
613
|
+
if (!this.rules[language]) {
|
|
614
|
+
this.rules[language] = [];
|
|
615
|
+
}
|
|
616
|
+
this.rules[language].push(rule);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Clear reviews
|
|
621
|
+
*/
|
|
622
|
+
clearReviews() {
|
|
623
|
+
this.reviews.clear();
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Export review to markdown
|
|
628
|
+
* @param {string} reviewId - Review identifier
|
|
629
|
+
* @returns {string}
|
|
630
|
+
*/
|
|
631
|
+
exportToMarkdown(reviewId) {
|
|
632
|
+
const review = this.getReview(reviewId);
|
|
633
|
+
if (!review) return '';
|
|
634
|
+
|
|
635
|
+
let md = `# Code Review Report\n\n`;
|
|
636
|
+
md += `**File:** ${review.filePath}\n`;
|
|
637
|
+
md += `**Language:** ${review.language}\n`;
|
|
638
|
+
md += `**Score:** ${review.score}/100 ${review.summary.passed ? '✅' : '❌'}\n\n`;
|
|
639
|
+
|
|
640
|
+
md += `## Summary\n\n`;
|
|
641
|
+
md += `${review.summary.recommendation}\n\n`;
|
|
642
|
+
md += `- Total Issues: ${review.issues.length}\n`;
|
|
643
|
+
for (const [severity, count] of Object.entries(review.summary.bySeverity)) {
|
|
644
|
+
md += `- ${severity}: ${count}\n`;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (review.issues.length > 0) {
|
|
648
|
+
md += `\n## Issues\n\n`;
|
|
649
|
+
|
|
650
|
+
for (const issue of review.issues) {
|
|
651
|
+
const icon = {
|
|
652
|
+
[SEVERITY.INFO]: 'ℹ️',
|
|
653
|
+
[SEVERITY.WARNING]: '⚠️',
|
|
654
|
+
[SEVERITY.ERROR]: '❌',
|
|
655
|
+
[SEVERITY.CRITICAL]: '🚨'
|
|
656
|
+
}[issue.severity];
|
|
657
|
+
|
|
658
|
+
md += `### ${icon} ${issue.message}\n\n`;
|
|
659
|
+
md += `- **Severity:** ${issue.severity}\n`;
|
|
660
|
+
md += `- **Category:** ${issue.category}\n`;
|
|
661
|
+
if (issue.line) md += `- **Line:** ${issue.line}\n`;
|
|
662
|
+
if (issue.suggestion) md += `- **Suggestion:** ${issue.suggestion}\n`;
|
|
663
|
+
md += `\n`;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
return md;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Create code reviewer
|
|
673
|
+
* @param {CodeReviewerOptions} [options={}] - Reviewer options
|
|
674
|
+
* @returns {CodeReviewer}
|
|
675
|
+
*/
|
|
676
|
+
function createCodeReviewer(options = {}) {
|
|
677
|
+
return new CodeReviewer(options);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Review code
|
|
682
|
+
* @param {string} code - Code to review
|
|
683
|
+
* @param {Object} [options={}] - Review options
|
|
684
|
+
* @returns {ReviewResult}
|
|
685
|
+
*/
|
|
686
|
+
function reviewCode(code, options = {}) {
|
|
687
|
+
const reviewer = createCodeReviewer(options);
|
|
688
|
+
return reviewer.review(code, options);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
module.exports = {
|
|
692
|
+
CodeReviewer,
|
|
693
|
+
createCodeReviewer,
|
|
694
|
+
reviewCode,
|
|
695
|
+
SEVERITY,
|
|
696
|
+
CATEGORY,
|
|
697
|
+
DEFAULT_RULES
|
|
698
|
+
};
|