code-sentinel-mcp 0.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 +149 -0
- package/build/analyzers/deceptive.d.ts +2 -0
- package/build/analyzers/deceptive.js +200 -0
- package/build/analyzers/errors.d.ts +2 -0
- package/build/analyzers/errors.js +214 -0
- package/build/analyzers/patterns.d.ts +138 -0
- package/build/analyzers/patterns.js +801 -0
- package/build/analyzers/placeholders.d.ts +2 -0
- package/build/analyzers/placeholders.js +216 -0
- package/build/analyzers/security.d.ts +2 -0
- package/build/analyzers/security.js +238 -0
- package/build/analyzers/strengths.d.ts +2 -0
- package/build/analyzers/strengths.js +169 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +366 -0
- package/build/report.d.ts +2 -0
- package/build/report.js +228 -0
- package/build/types.d.ts +54 -0
- package/build/types.js +2 -0
- package/package.json +38 -0
package/build/index.js
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { analyzeSecurityIssues } from './analyzers/security.js';
|
|
6
|
+
import { analyzeDeceptivePatterns } from './analyzers/deceptive.js';
|
|
7
|
+
import { analyzePlaceholders } from './analyzers/placeholders.js';
|
|
8
|
+
import { analyzeErrors } from './analyzers/errors.js';
|
|
9
|
+
import { analyzeStrengths } from './analyzers/strengths.js';
|
|
10
|
+
import { analyzePatterns, inferLevelFromQuery } from './analyzers/patterns.js';
|
|
11
|
+
import { analyzeDesignPatterns, formatDesignAnalysis } from './analyzers/patterns.js';
|
|
12
|
+
import { generateHtmlReport } from './report.js';
|
|
13
|
+
// Detect language from filename
|
|
14
|
+
function detectLanguage(filename) {
|
|
15
|
+
const ext = filename.split('.').pop()?.toLowerCase() || '';
|
|
16
|
+
const langMap = {
|
|
17
|
+
'ts': 'TypeScript',
|
|
18
|
+
'tsx': 'TypeScript React',
|
|
19
|
+
'js': 'JavaScript',
|
|
20
|
+
'jsx': 'JavaScript React',
|
|
21
|
+
'py': 'Python',
|
|
22
|
+
'rb': 'Ruby',
|
|
23
|
+
'go': 'Go',
|
|
24
|
+
'rs': 'Rust',
|
|
25
|
+
'java': 'Java',
|
|
26
|
+
'kt': 'Kotlin',
|
|
27
|
+
'swift': 'Swift',
|
|
28
|
+
'cs': 'C#',
|
|
29
|
+
'cpp': 'C++',
|
|
30
|
+
'c': 'C',
|
|
31
|
+
'php': 'PHP',
|
|
32
|
+
'vue': 'Vue',
|
|
33
|
+
'svelte': 'Svelte'
|
|
34
|
+
};
|
|
35
|
+
return langMap[ext] || 'Unknown';
|
|
36
|
+
}
|
|
37
|
+
// Main analysis function
|
|
38
|
+
function analyzeCode(code, filename) {
|
|
39
|
+
const language = detectLanguage(filename);
|
|
40
|
+
// Run all analyzers
|
|
41
|
+
const securityIssues = analyzeSecurityIssues(code, filename);
|
|
42
|
+
const deceptiveIssues = analyzeDeceptivePatterns(code, filename);
|
|
43
|
+
const placeholderIssues = analyzePlaceholders(code, filename);
|
|
44
|
+
const errorIssues = analyzeErrors(code, filename);
|
|
45
|
+
const strengths = analyzeStrengths(code, filename);
|
|
46
|
+
// Combine all issues
|
|
47
|
+
const allIssues = [
|
|
48
|
+
...securityIssues,
|
|
49
|
+
...deceptiveIssues,
|
|
50
|
+
...placeholderIssues,
|
|
51
|
+
...errorIssues
|
|
52
|
+
];
|
|
53
|
+
// Sort by severity
|
|
54
|
+
const severityOrder = {
|
|
55
|
+
critical: 0,
|
|
56
|
+
high: 1,
|
|
57
|
+
medium: 2,
|
|
58
|
+
low: 3,
|
|
59
|
+
info: 4
|
|
60
|
+
};
|
|
61
|
+
allIssues.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
62
|
+
// Calculate summary
|
|
63
|
+
const summary = {
|
|
64
|
+
totalIssues: allIssues.length,
|
|
65
|
+
critical: allIssues.filter(i => i.severity === 'critical').length,
|
|
66
|
+
high: allIssues.filter(i => i.severity === 'high').length,
|
|
67
|
+
medium: allIssues.filter(i => i.severity === 'medium').length,
|
|
68
|
+
low: allIssues.filter(i => i.severity === 'low').length,
|
|
69
|
+
strengths: strengths.length
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
filename,
|
|
73
|
+
language,
|
|
74
|
+
timestamp: new Date().toISOString(),
|
|
75
|
+
summary,
|
|
76
|
+
issues: allIssues,
|
|
77
|
+
strengths
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Create the MCP server
|
|
81
|
+
const server = new Server({
|
|
82
|
+
name: "code-sentinel",
|
|
83
|
+
version: "0.1.0",
|
|
84
|
+
}, {
|
|
85
|
+
capabilities: {
|
|
86
|
+
tools: {},
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
// List available tools
|
|
90
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
91
|
+
return {
|
|
92
|
+
tools: [
|
|
93
|
+
{
|
|
94
|
+
name: "analyze_code",
|
|
95
|
+
description: "Analyze code for security issues, errors, deceptive patterns, and placeholders. Returns a structured analysis with issues and strengths.",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
type: "object",
|
|
98
|
+
properties: {
|
|
99
|
+
code: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "The source code to analyze"
|
|
102
|
+
},
|
|
103
|
+
filename: {
|
|
104
|
+
type: "string",
|
|
105
|
+
description: "The filename (used to detect language). Example: 'app.ts'"
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
required: ["code", "filename"]
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "generate_report",
|
|
113
|
+
description: "Analyze code and generate a detailed HTML report with visual indicators for issues and strengths.",
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {
|
|
117
|
+
code: {
|
|
118
|
+
type: "string",
|
|
119
|
+
description: "The source code to analyze"
|
|
120
|
+
},
|
|
121
|
+
filename: {
|
|
122
|
+
type: "string",
|
|
123
|
+
description: "The filename (used to detect language). Example: 'app.ts'"
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
required: ["code", "filename"]
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "check_security",
|
|
131
|
+
description: "Check code for security vulnerabilities only (hardcoded secrets, SQL injection, XSS, etc.)",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: {
|
|
135
|
+
code: {
|
|
136
|
+
type: "string",
|
|
137
|
+
description: "The source code to check"
|
|
138
|
+
},
|
|
139
|
+
filename: {
|
|
140
|
+
type: "string",
|
|
141
|
+
description: "The filename"
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
required: ["code", "filename"]
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "check_deceptive_patterns",
|
|
149
|
+
description: "Check for code patterns that hide errors or create false confidence (empty catches, silent failures, etc.)",
|
|
150
|
+
inputSchema: {
|
|
151
|
+
type: "object",
|
|
152
|
+
properties: {
|
|
153
|
+
code: {
|
|
154
|
+
type: "string",
|
|
155
|
+
description: "The source code to check"
|
|
156
|
+
},
|
|
157
|
+
filename: {
|
|
158
|
+
type: "string",
|
|
159
|
+
description: "The filename"
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
required: ["code", "filename"]
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "check_placeholders",
|
|
167
|
+
description: "Check for placeholder code, dummy data, TODO/FIXME comments, and incomplete implementations",
|
|
168
|
+
inputSchema: {
|
|
169
|
+
type: "object",
|
|
170
|
+
properties: {
|
|
171
|
+
code: {
|
|
172
|
+
type: "string",
|
|
173
|
+
description: "The source code to check"
|
|
174
|
+
},
|
|
175
|
+
filename: {
|
|
176
|
+
type: "string",
|
|
177
|
+
description: "The filename"
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
required: ["code", "filename"]
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
// Handle tool calls
|
|
187
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
188
|
+
const { name, arguments: args } = request.params;
|
|
189
|
+
if (!args || typeof args.code !== 'string' || typeof args.filename !== 'string') {
|
|
190
|
+
return {
|
|
191
|
+
content: [
|
|
192
|
+
{
|
|
193
|
+
type: "text",
|
|
194
|
+
text: "Error: 'code' and 'filename' are required string parameters"
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const { code, filename } = args;
|
|
200
|
+
try {
|
|
201
|
+
switch (name) {
|
|
202
|
+
case "analyze_code": {
|
|
203
|
+
const result = analyzeCode(code, filename);
|
|
204
|
+
return {
|
|
205
|
+
content: [
|
|
206
|
+
{
|
|
207
|
+
type: "text",
|
|
208
|
+
text: JSON.stringify(result, null, 2)
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
case "generate_report": {
|
|
214
|
+
const result = analyzeCode(code, filename);
|
|
215
|
+
const html = generateHtmlReport(result);
|
|
216
|
+
return {
|
|
217
|
+
content: [
|
|
218
|
+
{
|
|
219
|
+
type: "text",
|
|
220
|
+
text: `## Analysis Summary for ${filename}\n\n` +
|
|
221
|
+
`**Score:** ${Math.max(0, 100 - (result.summary.critical * 25 + result.summary.high * 15 + result.summary.medium * 5 + result.summary.low))}%\n\n` +
|
|
222
|
+
`- 🔴 Critical: ${result.summary.critical}\n` +
|
|
223
|
+
`- 🟠 High: ${result.summary.high}\n` +
|
|
224
|
+
`- 🟡 Medium: ${result.summary.medium}\n` +
|
|
225
|
+
`- 🔵 Low: ${result.summary.low}\n` +
|
|
226
|
+
`- 💪 Strengths: ${result.summary.strengths}\n\n` +
|
|
227
|
+
`---\n\n` +
|
|
228
|
+
`\`\`\`html\n${html}\n\`\`\``
|
|
229
|
+
}
|
|
230
|
+
]
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
case "check_security": {
|
|
234
|
+
const issues = analyzeSecurityIssues(code, filename);
|
|
235
|
+
return {
|
|
236
|
+
content: [
|
|
237
|
+
{
|
|
238
|
+
type: "text",
|
|
239
|
+
text: issues.length === 0
|
|
240
|
+
? "✅ No security issues detected."
|
|
241
|
+
: `Found ${issues.length} security issue(s):\n\n${JSON.stringify(issues, null, 2)}`
|
|
242
|
+
}
|
|
243
|
+
]
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
case "check_deceptive_patterns": {
|
|
247
|
+
const issues = analyzeDeceptivePatterns(code, filename);
|
|
248
|
+
return {
|
|
249
|
+
content: [
|
|
250
|
+
{
|
|
251
|
+
type: "text",
|
|
252
|
+
text: issues.length === 0
|
|
253
|
+
? "✅ No deceptive patterns detected."
|
|
254
|
+
: `Found ${issues.length} deceptive pattern(s):\n\n${JSON.stringify(issues, null, 2)}`
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
case "check_placeholders": {
|
|
260
|
+
const issues = analyzePlaceholders(code, filename);
|
|
261
|
+
return {
|
|
262
|
+
content: [
|
|
263
|
+
{
|
|
264
|
+
type: "text",
|
|
265
|
+
text: issues.length === 0
|
|
266
|
+
? "✅ No placeholders or incomplete code detected."
|
|
267
|
+
: `Found ${issues.length} placeholder(s):\n\n${JSON.stringify(issues, null, 2)}`
|
|
268
|
+
}
|
|
269
|
+
]
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
case "analyze_patterns": {
|
|
273
|
+
// Determine level from explicit param or infer from query
|
|
274
|
+
let level = 'all';
|
|
275
|
+
if (args.level && ['architectural', 'design', 'code', 'all'].includes(args.level)) {
|
|
276
|
+
level = args.level;
|
|
277
|
+
}
|
|
278
|
+
else if (args.query && typeof args.query === 'string') {
|
|
279
|
+
const inferred = inferLevelFromQuery(args.query);
|
|
280
|
+
if (inferred)
|
|
281
|
+
level = inferred;
|
|
282
|
+
}
|
|
283
|
+
const patternResult = analyzePatterns(code, filename, level);
|
|
284
|
+
// Format output optimized for LLM action
|
|
285
|
+
const output = {
|
|
286
|
+
// Summary for quick understanding
|
|
287
|
+
summary: patternResult.summary,
|
|
288
|
+
// What patterns were detected
|
|
289
|
+
detectedPatterns: patternResult.detectedPatterns.map(p => ({
|
|
290
|
+
pattern: p.name,
|
|
291
|
+
level: p.level,
|
|
292
|
+
confidence: p.confidence,
|
|
293
|
+
description: p.description,
|
|
294
|
+
foundAt: p.locations.map(l => `Line ${l.line}`)
|
|
295
|
+
})),
|
|
296
|
+
// Inconsistencies that should be fixed
|
|
297
|
+
inconsistencies: patternResult.inconsistencies.map(i => ({
|
|
298
|
+
issue: i.title,
|
|
299
|
+
severity: i.severity,
|
|
300
|
+
variants: i.variants.map(v => `${v.approach}: ${v.count} occurrences`),
|
|
301
|
+
recommendation: i.recommendation
|
|
302
|
+
})),
|
|
303
|
+
// Suggestions for improvement
|
|
304
|
+
suggestions: patternResult.suggestions.map(s => ({
|
|
305
|
+
title: s.title,
|
|
306
|
+
priority: s.priority,
|
|
307
|
+
currentApproach: s.currentApproach.name,
|
|
308
|
+
suggestedPattern: s.suggestedApproach.name,
|
|
309
|
+
why: s.suggestedApproach.why,
|
|
310
|
+
benefits: s.suggestedApproach.benefits,
|
|
311
|
+
tradeoffs: s.suggestedApproach.tradeoffs,
|
|
312
|
+
example: s.suggestedApproach.example
|
|
313
|
+
})),
|
|
314
|
+
// Ready-to-execute action items for LLM
|
|
315
|
+
actionItems: patternResult.actionItems
|
|
316
|
+
};
|
|
317
|
+
return {
|
|
318
|
+
content: [
|
|
319
|
+
{
|
|
320
|
+
type: "text",
|
|
321
|
+
text: JSON.stringify(output, null, 2)
|
|
322
|
+
}
|
|
323
|
+
]
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
case "analyze_design_patterns": {
|
|
327
|
+
const result = analyzeDesignPatterns(code, filename);
|
|
328
|
+
const formatted = formatDesignAnalysis(result);
|
|
329
|
+
return {
|
|
330
|
+
content: [
|
|
331
|
+
{
|
|
332
|
+
type: "text",
|
|
333
|
+
text: formatted
|
|
334
|
+
}
|
|
335
|
+
]
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
default:
|
|
339
|
+
return {
|
|
340
|
+
content: [
|
|
341
|
+
{
|
|
342
|
+
type: "text",
|
|
343
|
+
text: `Unknown tool: ${name}`
|
|
344
|
+
}
|
|
345
|
+
]
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
return {
|
|
351
|
+
content: [
|
|
352
|
+
{
|
|
353
|
+
type: "text",
|
|
354
|
+
text: `Error analyzing code: ${error instanceof Error ? error.message : String(error)}`
|
|
355
|
+
}
|
|
356
|
+
]
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
// Start the server
|
|
361
|
+
async function main() {
|
|
362
|
+
const transport = new StdioServerTransport();
|
|
363
|
+
await server.connect(transport);
|
|
364
|
+
console.error("CodeSentinel MCP Server running on stdio");
|
|
365
|
+
}
|
|
366
|
+
main().catch(console.error);
|
package/build/report.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
const severityColors = {
|
|
2
|
+
critical: '#dc2626',
|
|
3
|
+
high: '#ea580c',
|
|
4
|
+
medium: '#ca8a04',
|
|
5
|
+
low: '#2563eb',
|
|
6
|
+
info: '#6b7280'
|
|
7
|
+
};
|
|
8
|
+
const severityBg = {
|
|
9
|
+
critical: '#fef2f2',
|
|
10
|
+
high: '#fff7ed',
|
|
11
|
+
medium: '#fefce8',
|
|
12
|
+
low: '#eff6ff',
|
|
13
|
+
info: '#f9fafb'
|
|
14
|
+
};
|
|
15
|
+
function escapeHtml(text) {
|
|
16
|
+
return text
|
|
17
|
+
.replace(/&/g, '&')
|
|
18
|
+
.replace(/</g, '<')
|
|
19
|
+
.replace(/>/g, '>')
|
|
20
|
+
.replace(/"/g, '"')
|
|
21
|
+
.replace(/'/g, ''');
|
|
22
|
+
}
|
|
23
|
+
function renderIssue(issue) {
|
|
24
|
+
return `
|
|
25
|
+
<div class="issue" style="background: ${severityBg[issue.severity]}; border-left: 4px solid ${severityColors[issue.severity]}; padding: 16px; margin: 12px 0; border-radius: 8px;">
|
|
26
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
27
|
+
<span class="badge" style="background: ${severityColors[issue.severity]}; color: white; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: 600; text-transform: uppercase;">
|
|
28
|
+
${issue.severity}
|
|
29
|
+
</span>
|
|
30
|
+
<span style="color: #6b7280; font-size: 12px;">${issue.id}${issue.line ? ` • Line ${issue.line}` : ''}</span>
|
|
31
|
+
</div>
|
|
32
|
+
<h4 style="margin: 0 0 8px 0; color: #1f2937; font-size: 16px;">${escapeHtml(issue.title)}</h4>
|
|
33
|
+
<p style="margin: 0 0 8px 0; color: #4b5563; font-size: 14px;">${escapeHtml(issue.description)}</p>
|
|
34
|
+
${issue.code ? `
|
|
35
|
+
<pre style="background: #1f2937; color: #e5e7eb; padding: 12px; border-radius: 6px; overflow-x: auto; font-size: 13px; margin: 8px 0;"><code>${escapeHtml(issue.code)}</code></pre>
|
|
36
|
+
` : ''}
|
|
37
|
+
${issue.suggestion ? `
|
|
38
|
+
<div style="background: #f0fdf4; border: 1px solid #86efac; padding: 12px; border-radius: 6px; margin-top: 8px;">
|
|
39
|
+
<strong style="color: #166534; font-size: 12px;">💡 Suggestion:</strong>
|
|
40
|
+
<p style="margin: 4px 0 0 0; color: #166534; font-size: 13px;">${escapeHtml(issue.suggestion)}</p>
|
|
41
|
+
</div>
|
|
42
|
+
` : ''}
|
|
43
|
+
</div>
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
function renderStrength(strength) {
|
|
47
|
+
return `
|
|
48
|
+
<div class="strength" style="background: #f0fdf4; border-left: 4px solid #22c55e; padding: 16px; margin: 12px 0; border-radius: 8px;">
|
|
49
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
50
|
+
<span class="badge" style="background: #22c55e; color: white; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: 600;">
|
|
51
|
+
✓ GOOD PRACTICE
|
|
52
|
+
</span>
|
|
53
|
+
<span style="color: #6b7280; font-size: 12px;">${strength.id}</span>
|
|
54
|
+
</div>
|
|
55
|
+
<h4 style="margin: 0 0 8px 0; color: #166534; font-size: 16px;">${escapeHtml(strength.title)}</h4>
|
|
56
|
+
<p style="margin: 0; color: #15803d; font-size: 14px;">${escapeHtml(strength.description)}</p>
|
|
57
|
+
${strength.examples && strength.examples.length > 0 ? `
|
|
58
|
+
<div style="margin-top: 8px;">
|
|
59
|
+
<span style="color: #166534; font-size: 12px; font-weight: 600;">Found ${strength.examples.length}+ instances</span>
|
|
60
|
+
</div>
|
|
61
|
+
` : ''}
|
|
62
|
+
</div>
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
function renderSummaryCard(label, count, color, bgColor) {
|
|
66
|
+
return `
|
|
67
|
+
<div style="background: ${bgColor}; border: 2px solid ${color}; border-radius: 12px; padding: 20px; text-align: center; min-width: 120px;">
|
|
68
|
+
<div style="font-size: 32px; font-weight: 700; color: ${color};">${count}</div>
|
|
69
|
+
<div style="font-size: 14px; color: ${color}; text-transform: uppercase; letter-spacing: 0.5px;">${label}</div>
|
|
70
|
+
</div>
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
export function generateHtmlReport(result) {
|
|
74
|
+
const { summary, issues, strengths, filename, language, timestamp } = result;
|
|
75
|
+
// Group issues by category
|
|
76
|
+
const securityIssues = issues.filter(i => i.category === 'security');
|
|
77
|
+
const errorIssues = issues.filter(i => i.category === 'error');
|
|
78
|
+
const deceptiveIssues = issues.filter(i => i.category === 'deceptive');
|
|
79
|
+
const placeholderIssues = issues.filter(i => i.category === 'placeholder');
|
|
80
|
+
// Calculate score (0-100)
|
|
81
|
+
const maxPenalty = 100;
|
|
82
|
+
const penalty = summary.critical * 25 +
|
|
83
|
+
summary.high * 15 +
|
|
84
|
+
summary.medium * 5 +
|
|
85
|
+
summary.low * 1;
|
|
86
|
+
const score = Math.max(0, Math.min(100, maxPenalty - penalty + (summary.strengths * 2)));
|
|
87
|
+
const scoreColor = score >= 80 ? '#22c55e' : score >= 60 ? '#ca8a04' : score >= 40 ? '#ea580c' : '#dc2626';
|
|
88
|
+
return `
|
|
89
|
+
<!DOCTYPE html>
|
|
90
|
+
<html lang="en">
|
|
91
|
+
<head>
|
|
92
|
+
<meta charset="UTF-8">
|
|
93
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
94
|
+
<title>CodeSentinel Report - ${escapeHtml(filename)}</title>
|
|
95
|
+
<style>
|
|
96
|
+
* { box-sizing: border-box; }
|
|
97
|
+
body {
|
|
98
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
99
|
+
line-height: 1.6;
|
|
100
|
+
color: #1f2937;
|
|
101
|
+
background: #f9fafb;
|
|
102
|
+
margin: 0;
|
|
103
|
+
padding: 20px;
|
|
104
|
+
}
|
|
105
|
+
.container { max-width: 1000px; margin: 0 auto; }
|
|
106
|
+
.header {
|
|
107
|
+
background: linear-gradient(135deg, #1e3a5f 0%, #2d5a87 100%);
|
|
108
|
+
color: white;
|
|
109
|
+
padding: 32px;
|
|
110
|
+
border-radius: 16px;
|
|
111
|
+
margin-bottom: 24px;
|
|
112
|
+
}
|
|
113
|
+
.score-circle {
|
|
114
|
+
width: 100px;
|
|
115
|
+
height: 100px;
|
|
116
|
+
border-radius: 50%;
|
|
117
|
+
background: white;
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
justify-content: center;
|
|
121
|
+
font-size: 28px;
|
|
122
|
+
font-weight: 700;
|
|
123
|
+
}
|
|
124
|
+
.section {
|
|
125
|
+
background: white;
|
|
126
|
+
border-radius: 12px;
|
|
127
|
+
padding: 24px;
|
|
128
|
+
margin-bottom: 20px;
|
|
129
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
130
|
+
}
|
|
131
|
+
.section h2 {
|
|
132
|
+
margin: 0 0 16px 0;
|
|
133
|
+
padding-bottom: 12px;
|
|
134
|
+
border-bottom: 2px solid #e5e7eb;
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
gap: 8px;
|
|
138
|
+
}
|
|
139
|
+
.count-badge {
|
|
140
|
+
background: #e5e7eb;
|
|
141
|
+
color: #4b5563;
|
|
142
|
+
padding: 2px 10px;
|
|
143
|
+
border-radius: 12px;
|
|
144
|
+
font-size: 14px;
|
|
145
|
+
}
|
|
146
|
+
@media (max-width: 600px) {
|
|
147
|
+
.summary-grid { flex-direction: column; }
|
|
148
|
+
}
|
|
149
|
+
</style>
|
|
150
|
+
</head>
|
|
151
|
+
<body>
|
|
152
|
+
<div class="container">
|
|
153
|
+
<header class="header">
|
|
154
|
+
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 20px;">
|
|
155
|
+
<div>
|
|
156
|
+
<h1 style="margin: 0 0 8px 0; font-size: 28px;">🛡️ CodeSentinel Report</h1>
|
|
157
|
+
<p style="margin: 0; opacity: 0.9; font-size: 14px;">
|
|
158
|
+
<strong>${escapeHtml(filename)}</strong> • ${language} • ${timestamp}
|
|
159
|
+
</p>
|
|
160
|
+
</div>
|
|
161
|
+
<div class="score-circle" style="color: ${scoreColor}">
|
|
162
|
+
${score}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</header>
|
|
166
|
+
|
|
167
|
+
<div class="section">
|
|
168
|
+
<h2>📊 Summary</h2>
|
|
169
|
+
<div class="summary-grid" style="display: flex; gap: 16px; flex-wrap: wrap; justify-content: center;">
|
|
170
|
+
${renderSummaryCard('Critical', summary.critical, severityColors.critical, severityBg.critical)}
|
|
171
|
+
${renderSummaryCard('High', summary.high, severityColors.high, severityBg.high)}
|
|
172
|
+
${renderSummaryCard('Medium', summary.medium, severityColors.medium, severityBg.medium)}
|
|
173
|
+
${renderSummaryCard('Low', summary.low, severityColors.low, severityBg.low)}
|
|
174
|
+
${renderSummaryCard('Strengths', summary.strengths, '#22c55e', '#f0fdf4')}
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
${securityIssues.length > 0 ? `
|
|
179
|
+
<div class="section">
|
|
180
|
+
<h2>🔒 Security Issues <span class="count-badge">${securityIssues.length}</span></h2>
|
|
181
|
+
${securityIssues.map(renderIssue).join('')}
|
|
182
|
+
</div>
|
|
183
|
+
` : ''}
|
|
184
|
+
|
|
185
|
+
${deceptiveIssues.length > 0 ? `
|
|
186
|
+
<div class="section">
|
|
187
|
+
<h2>🎭 Deceptive Patterns <span class="count-badge">${deceptiveIssues.length}</span></h2>
|
|
188
|
+
<p style="color: #6b7280; margin-top: 0;">Code that hides errors or creates false confidence</p>
|
|
189
|
+
${deceptiveIssues.map(renderIssue).join('')}
|
|
190
|
+
</div>
|
|
191
|
+
` : ''}
|
|
192
|
+
|
|
193
|
+
${errorIssues.length > 0 ? `
|
|
194
|
+
<div class="section">
|
|
195
|
+
<h2>⚠️ Errors & Code Smells <span class="count-badge">${errorIssues.length}</span></h2>
|
|
196
|
+
${errorIssues.map(renderIssue).join('')}
|
|
197
|
+
</div>
|
|
198
|
+
` : ''}
|
|
199
|
+
|
|
200
|
+
${placeholderIssues.length > 0 ? `
|
|
201
|
+
<div class="section">
|
|
202
|
+
<h2>📝 Placeholders & Incomplete Code <span class="count-badge">${placeholderIssues.length}</span></h2>
|
|
203
|
+
${placeholderIssues.map(renderIssue).join('')}
|
|
204
|
+
</div>
|
|
205
|
+
` : ''}
|
|
206
|
+
|
|
207
|
+
${strengths.length > 0 ? `
|
|
208
|
+
<div class="section">
|
|
209
|
+
<h2>💪 Code Strengths <span class="count-badge">${strengths.length}</span></h2>
|
|
210
|
+
${strengths.map(renderStrength).join('')}
|
|
211
|
+
</div>
|
|
212
|
+
` : ''}
|
|
213
|
+
|
|
214
|
+
${summary.totalIssues === 0 && strengths.length > 0 ? `
|
|
215
|
+
<div class="section" style="background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%); border: 2px solid #22c55e;">
|
|
216
|
+
<h2 style="color: #166534; border: none;">🎉 Excellent Code Quality!</h2>
|
|
217
|
+
<p style="color: #15803d; margin: 0;">No issues detected. This code demonstrates good practices.</p>
|
|
218
|
+
</div>
|
|
219
|
+
` : ''}
|
|
220
|
+
|
|
221
|
+
<footer style="text-align: center; padding: 20px; color: #6b7280; font-size: 13px;">
|
|
222
|
+
Generated by CodeSentinel MCP Server • ${new Date().toISOString()}
|
|
223
|
+
</footer>
|
|
224
|
+
</div>
|
|
225
|
+
</body>
|
|
226
|
+
</html>
|
|
227
|
+
`.trim();
|
|
228
|
+
}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';
|
|
2
|
+
export type Category = 'security' | 'error' | 'deceptive' | 'placeholder' | 'strength';
|
|
3
|
+
export type VerificationStatus = 'confirmed' | 'needs_verification';
|
|
4
|
+
export interface Verification {
|
|
5
|
+
status: VerificationStatus;
|
|
6
|
+
commands?: string[];
|
|
7
|
+
assumption?: string;
|
|
8
|
+
instruction?: string;
|
|
9
|
+
confirmIf?: string;
|
|
10
|
+
falsePositiveIf?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface Issue {
|
|
13
|
+
id: string;
|
|
14
|
+
category: Category;
|
|
15
|
+
severity: Severity;
|
|
16
|
+
title: string;
|
|
17
|
+
description: string;
|
|
18
|
+
line?: number;
|
|
19
|
+
column?: number;
|
|
20
|
+
code?: string;
|
|
21
|
+
suggestion?: string;
|
|
22
|
+
verification?: Verification;
|
|
23
|
+
}
|
|
24
|
+
export interface Strength {
|
|
25
|
+
id: string;
|
|
26
|
+
title: string;
|
|
27
|
+
description: string;
|
|
28
|
+
examples?: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface AnalysisResult {
|
|
31
|
+
filename: string;
|
|
32
|
+
language: string;
|
|
33
|
+
timestamp: string;
|
|
34
|
+
summary: {
|
|
35
|
+
totalIssues: number;
|
|
36
|
+
critical: number;
|
|
37
|
+
high: number;
|
|
38
|
+
medium: number;
|
|
39
|
+
low: number;
|
|
40
|
+
strengths: number;
|
|
41
|
+
};
|
|
42
|
+
issues: Issue[];
|
|
43
|
+
strengths: Strength[];
|
|
44
|
+
}
|
|
45
|
+
export interface Pattern {
|
|
46
|
+
id: string;
|
|
47
|
+
pattern: RegExp;
|
|
48
|
+
title: string;
|
|
49
|
+
description: string;
|
|
50
|
+
severity: Severity;
|
|
51
|
+
category: Category;
|
|
52
|
+
suggestion?: string;
|
|
53
|
+
verification?: Verification;
|
|
54
|
+
}
|
package/build/types.js
ADDED