vibecheck-mcp-server 2.0.1
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 +191 -0
- package/agent-checkpoint.js +364 -0
- package/architect-tools.js +707 -0
- package/audit-mcp.js +206 -0
- package/codebase-architect-tools.js +838 -0
- package/guardrail-2.0-tools.js +748 -0
- package/guardrail-tools.js +1075 -0
- package/hygiene-tools.js +428 -0
- package/index-v1.js +698 -0
- package/index.js +1409 -0
- package/index.old.js +4137 -0
- package/intelligence-tools.js +664 -0
- package/intent-drift-tools.js +873 -0
- package/mdc-generator.js +298 -0
- package/package.json +47 -0
- package/premium-tools.js +1275 -0
- package/test-mcp.js +108 -0
- package/test-tools.js +36 -0
- package/tier-auth.js +147 -0
|
@@ -0,0 +1,1075 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guardrail AI Guardrails MCP Tools
|
|
3
|
+
*
|
|
4
|
+
* Prompt Firewall + Output Verification Layer for AI agents:
|
|
5
|
+
* - guardrail.verify - Verify AI agent output before applying
|
|
6
|
+
* - guardrail.quality - Code quality analysis
|
|
7
|
+
* - guardrail.smells - Detect code smells
|
|
8
|
+
* - guardrail.hallucination - Check for AI hallucination risks
|
|
9
|
+
* - guardrail.breaking - Detect breaking changes
|
|
10
|
+
* - guardrail.mdc - Generate MDC specifications
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import path from "path";
|
|
14
|
+
import fs from "fs/promises";
|
|
15
|
+
import { execSync } from "child_process";
|
|
16
|
+
import { withTierCheck, checkFeatureAccess } from "./tier-auth.js";
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// TOOL DEFINITIONS
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
export const GUARDRAIL_TOOLS = [
|
|
23
|
+
// 1. VERIFY - Verify AI agent output
|
|
24
|
+
{
|
|
25
|
+
name: "guardrail.verify",
|
|
26
|
+
description:
|
|
27
|
+
"🛡️ Verify AI Output — Validates AI-generated code/diffs before applying. Checks for secrets, dangerous commands, path traversal, and stubs.",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {
|
|
31
|
+
input: {
|
|
32
|
+
type: "string",
|
|
33
|
+
description: "AI agent output (guardrail-v1 JSON format) to verify",
|
|
34
|
+
},
|
|
35
|
+
file: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "Path to file containing AI output to verify",
|
|
38
|
+
},
|
|
39
|
+
mode: {
|
|
40
|
+
type: "string",
|
|
41
|
+
enum: ["explore", "build", "ship"],
|
|
42
|
+
description: "Verification mode: explore (lenient), build (normal), ship (strict)",
|
|
43
|
+
default: "build",
|
|
44
|
+
},
|
|
45
|
+
strict: {
|
|
46
|
+
type: "boolean",
|
|
47
|
+
description: "Enable strict mode (fail on warnings)",
|
|
48
|
+
default: false,
|
|
49
|
+
},
|
|
50
|
+
projectPath: {
|
|
51
|
+
type: "string",
|
|
52
|
+
description: "Project root for context",
|
|
53
|
+
default: ".",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
// 2. QUALITY - Code quality analysis
|
|
60
|
+
{
|
|
61
|
+
name: "guardrail.quality",
|
|
62
|
+
description:
|
|
63
|
+
"📊 Code Quality — Analyze complexity, maintainability, technical debt. Returns actionable metrics.",
|
|
64
|
+
inputSchema: {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
projectPath: {
|
|
68
|
+
type: "string",
|
|
69
|
+
description: "Path to project root",
|
|
70
|
+
default: ".",
|
|
71
|
+
},
|
|
72
|
+
file: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "Specific file to analyze (optional)",
|
|
75
|
+
},
|
|
76
|
+
threshold: {
|
|
77
|
+
type: "number",
|
|
78
|
+
description: "Minimum quality score (0-100) to pass",
|
|
79
|
+
default: 70,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
// 3. SMELLS - Code smell detection
|
|
86
|
+
{
|
|
87
|
+
name: "guardrail.smells",
|
|
88
|
+
description:
|
|
89
|
+
"👃 Code Smells — Detect anti-patterns, complexity issues, naming problems, and structural issues. PRO features include AI-powered technical debt calculation, trend analysis, and recommendations.",
|
|
90
|
+
inputSchema: {
|
|
91
|
+
type: "object",
|
|
92
|
+
properties: {
|
|
93
|
+
projectPath: {
|
|
94
|
+
type: "string",
|
|
95
|
+
description: "Path to project root",
|
|
96
|
+
default: ".",
|
|
97
|
+
},
|
|
98
|
+
pro: {
|
|
99
|
+
type: "boolean",
|
|
100
|
+
description: "Enable PRO features (advanced predictor, technical debt calculation, trend analysis)",
|
|
101
|
+
default: false,
|
|
102
|
+
},
|
|
103
|
+
premium: {
|
|
104
|
+
type: "boolean",
|
|
105
|
+
description: "Alias for pro - Enable premium features",
|
|
106
|
+
default: false,
|
|
107
|
+
},
|
|
108
|
+
file: {
|
|
109
|
+
type: "string",
|
|
110
|
+
description: "Specific file to analyze (optional)",
|
|
111
|
+
},
|
|
112
|
+
severity: {
|
|
113
|
+
type: "string",
|
|
114
|
+
enum: ["all", "critical", "high", "medium"],
|
|
115
|
+
description: "Minimum severity to report",
|
|
116
|
+
default: "medium",
|
|
117
|
+
},
|
|
118
|
+
limit: {
|
|
119
|
+
type: "number",
|
|
120
|
+
description: "Maximum number of smells to return (PRO only, default 50)",
|
|
121
|
+
default: 50,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
// 4. HALLUCINATION - AI hallucination detection
|
|
128
|
+
{
|
|
129
|
+
name: "guardrail.hallucination",
|
|
130
|
+
description:
|
|
131
|
+
"🔍 Hallucination Check — Verify claims against actual source code. Detects contradictions and missing evidence.",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: {
|
|
135
|
+
projectPath: {
|
|
136
|
+
type: "string",
|
|
137
|
+
description: "Path to project root",
|
|
138
|
+
default: ".",
|
|
139
|
+
},
|
|
140
|
+
claims: {
|
|
141
|
+
type: "array",
|
|
142
|
+
items: { type: "string" },
|
|
143
|
+
description: "Claims to verify against source code",
|
|
144
|
+
},
|
|
145
|
+
spec: {
|
|
146
|
+
type: "string",
|
|
147
|
+
description: "MDC specification file to verify",
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// 5. BREAKING - Breaking change detection
|
|
154
|
+
{
|
|
155
|
+
name: "guardrail.breaking",
|
|
156
|
+
description:
|
|
157
|
+
"⚠️ Breaking Changes — Detect API changes, removed methods, type changes between versions.",
|
|
158
|
+
inputSchema: {
|
|
159
|
+
type: "object",
|
|
160
|
+
properties: {
|
|
161
|
+
projectPath: {
|
|
162
|
+
type: "string",
|
|
163
|
+
description: "Path to project root",
|
|
164
|
+
default: ".",
|
|
165
|
+
},
|
|
166
|
+
previousVersion: {
|
|
167
|
+
type: "string",
|
|
168
|
+
description: "Git ref or path to previous version",
|
|
169
|
+
},
|
|
170
|
+
output: {
|
|
171
|
+
type: "string",
|
|
172
|
+
enum: ["text", "json", "markdown"],
|
|
173
|
+
description: "Output format",
|
|
174
|
+
default: "text",
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
// 6. MDC - Generate MDC specifications
|
|
181
|
+
{
|
|
182
|
+
name: "guardrail.mdc",
|
|
183
|
+
description:
|
|
184
|
+
"📝 MDC Generator — Generate Markdown Context files with verified, source-anchored documentation.",
|
|
185
|
+
inputSchema: {
|
|
186
|
+
type: "object",
|
|
187
|
+
properties: {
|
|
188
|
+
projectPath: {
|
|
189
|
+
type: "string",
|
|
190
|
+
description: "Path to project root",
|
|
191
|
+
default: ".",
|
|
192
|
+
},
|
|
193
|
+
outputDir: {
|
|
194
|
+
type: "string",
|
|
195
|
+
description: "Output directory for MDC files",
|
|
196
|
+
default: ".specs",
|
|
197
|
+
},
|
|
198
|
+
categories: {
|
|
199
|
+
type: "array",
|
|
200
|
+
items: { type: "string" },
|
|
201
|
+
description: "Categories to generate: architecture, algorithm, data-flow, design-system, integration, security, utility",
|
|
202
|
+
},
|
|
203
|
+
depth: {
|
|
204
|
+
type: "string",
|
|
205
|
+
enum: ["shallow", "medium", "deep"],
|
|
206
|
+
description: "Analysis depth",
|
|
207
|
+
default: "medium",
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
// 7. COVERAGE - Test coverage mapping
|
|
214
|
+
{
|
|
215
|
+
name: "guardrail.coverage",
|
|
216
|
+
description:
|
|
217
|
+
"🧪 Test Coverage — Map test coverage to components, identify untested code.",
|
|
218
|
+
inputSchema: {
|
|
219
|
+
type: "object",
|
|
220
|
+
properties: {
|
|
221
|
+
projectPath: {
|
|
222
|
+
type: "string",
|
|
223
|
+
description: "Path to project root",
|
|
224
|
+
default: ".",
|
|
225
|
+
},
|
|
226
|
+
format: {
|
|
227
|
+
type: "string",
|
|
228
|
+
enum: ["text", "json", "markdown"],
|
|
229
|
+
description: "Output format",
|
|
230
|
+
default: "text",
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
// 8. AUTOFIX - AI-powered verified autofix (PRO+)
|
|
237
|
+
{
|
|
238
|
+
name: "guardrail.autofix",
|
|
239
|
+
description:
|
|
240
|
+
"🔧 Verified Autofix — AI-powered code fixes with verification. Supports route-integrity, placeholders, type-errors, build-blockers, test-failures fix packs. PRO+ feature.",
|
|
241
|
+
inputSchema: {
|
|
242
|
+
type: "object",
|
|
243
|
+
properties: {
|
|
244
|
+
projectPath: {
|
|
245
|
+
type: "string",
|
|
246
|
+
description: "Path to project root",
|
|
247
|
+
default: ".",
|
|
248
|
+
},
|
|
249
|
+
fixPack: {
|
|
250
|
+
type: "string",
|
|
251
|
+
enum: ["route-integrity", "placeholders", "type-errors", "build-blockers", "test-failures"],
|
|
252
|
+
description: "Fix pack to apply",
|
|
253
|
+
},
|
|
254
|
+
dryRun: {
|
|
255
|
+
type: "boolean",
|
|
256
|
+
description: "Preview changes without applying",
|
|
257
|
+
default: true,
|
|
258
|
+
},
|
|
259
|
+
model: {
|
|
260
|
+
type: "string",
|
|
261
|
+
description: "AI model to use (e.g., gpt-4o, gpt-4o-mini, claude-sonnet-4-20250514)",
|
|
262
|
+
},
|
|
263
|
+
maxAttempts: {
|
|
264
|
+
type: "number",
|
|
265
|
+
description: "Maximum fix attempts",
|
|
266
|
+
default: 3,
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
required: ["fixPack"],
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
// ============================================================================
|
|
275
|
+
// TOOL HANDLERS
|
|
276
|
+
// ============================================================================
|
|
277
|
+
|
|
278
|
+
export async function handleGuardrailTool(toolName, args) {
|
|
279
|
+
const projectPath = path.resolve(args.projectPath || ".");
|
|
280
|
+
|
|
281
|
+
// Map tools to required features
|
|
282
|
+
const featureMap = {
|
|
283
|
+
"guardrail.verify": "verify",
|
|
284
|
+
"guardrail.quality": "quality",
|
|
285
|
+
"guardrail.smells": "smells",
|
|
286
|
+
"guardrail.hallucination": "hallucination",
|
|
287
|
+
"guardrail.breaking": "breaking",
|
|
288
|
+
"guardrail.mdc": "mdc",
|
|
289
|
+
"guardrail.coverage": "quality", // map to quality tier
|
|
290
|
+
"guardrail.autofix": "smells" // map to smells tier (fix requires starter+)
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const requiredFeature = featureMap[toolName];
|
|
294
|
+
if (requiredFeature) {
|
|
295
|
+
const access = await checkFeatureAccess(requiredFeature, args?.apiKey);
|
|
296
|
+
if (!access.hasAccess) {
|
|
297
|
+
return {
|
|
298
|
+
content: [{
|
|
299
|
+
type: "text",
|
|
300
|
+
text: `🚫 UPGRADE REQUIRED\n\n${access.reason}\n\nCurrent tier: ${access.tier}\nUpgrade at: ${access.upgradeUrl}`
|
|
301
|
+
}],
|
|
302
|
+
isError: true
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
switch (toolName) {
|
|
308
|
+
case "guardrail.verify":
|
|
309
|
+
return handleVerify(args, projectPath);
|
|
310
|
+
|
|
311
|
+
case "guardrail.quality":
|
|
312
|
+
return handleQuality(args, projectPath);
|
|
313
|
+
|
|
314
|
+
case "guardrail.smells":
|
|
315
|
+
return handleSmells(args, projectPath);
|
|
316
|
+
|
|
317
|
+
case "guardrail.hallucination":
|
|
318
|
+
return handleHallucination(args, projectPath);
|
|
319
|
+
|
|
320
|
+
case "guardrail.breaking":
|
|
321
|
+
return handleBreaking(args, projectPath);
|
|
322
|
+
|
|
323
|
+
case "guardrail.mdc":
|
|
324
|
+
return handleMDC(args, projectPath);
|
|
325
|
+
|
|
326
|
+
case "guardrail.coverage":
|
|
327
|
+
return handleCoverage(args, projectPath);
|
|
328
|
+
|
|
329
|
+
case "guardrail.autofix":
|
|
330
|
+
return handleAutofix(args, projectPath);
|
|
331
|
+
|
|
332
|
+
default:
|
|
333
|
+
return { error: `Unknown tool: ${toolName}` };
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ============================================================================
|
|
338
|
+
// INDIVIDUAL HANDLERS
|
|
339
|
+
// ============================================================================
|
|
340
|
+
|
|
341
|
+
async function handleVerify(args, projectPath) {
|
|
342
|
+
try {
|
|
343
|
+
let input = args.input;
|
|
344
|
+
|
|
345
|
+
// Read from file if specified
|
|
346
|
+
if (args.file && !input) {
|
|
347
|
+
const filePath = path.resolve(projectPath, args.file);
|
|
348
|
+
input = await fs.readFile(filePath, "utf8");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!input) {
|
|
352
|
+
return {
|
|
353
|
+
success: false,
|
|
354
|
+
error: "No input provided. Specify 'input' or 'file' parameter.",
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Use the verification module
|
|
359
|
+
const { verifyAgentOutput } = await import("../bin/runners/lib/verification.js");
|
|
360
|
+
|
|
361
|
+
const context = {
|
|
362
|
+
projectRoot: projectPath,
|
|
363
|
+
mode: args.mode || "build",
|
|
364
|
+
strict: args.strict || false,
|
|
365
|
+
runTests: false,
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const result = await verifyAgentOutput(input, context);
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
success: result.success,
|
|
372
|
+
checks: result.checks,
|
|
373
|
+
blockers: result.blockers,
|
|
374
|
+
warnings: result.warnings,
|
|
375
|
+
failureContext: result.failureContext,
|
|
376
|
+
summary: result.success
|
|
377
|
+
? "✅ Verification PASSED - Safe to apply"
|
|
378
|
+
: `❌ Verification FAILED - ${result.blockers.length} blocker(s)`,
|
|
379
|
+
};
|
|
380
|
+
} catch (error) {
|
|
381
|
+
return {
|
|
382
|
+
success: false,
|
|
383
|
+
error: `Verification error: ${error.message}`,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function handleQuality(args, projectPath) {
|
|
389
|
+
try {
|
|
390
|
+
const results = {
|
|
391
|
+
summary: {
|
|
392
|
+
overallScore: 0,
|
|
393
|
+
components: 0,
|
|
394
|
+
issues: [],
|
|
395
|
+
},
|
|
396
|
+
metrics: [],
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// Find source files
|
|
400
|
+
const sourceFiles = await findSourceFiles(projectPath, args.file);
|
|
401
|
+
|
|
402
|
+
for (const file of sourceFiles.slice(0, 20)) {
|
|
403
|
+
const content = await fs.readFile(file, "utf8");
|
|
404
|
+
const metrics = analyzeFileQuality(content, file);
|
|
405
|
+
results.metrics.push(metrics);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Calculate overall score
|
|
409
|
+
if (results.metrics.length > 0) {
|
|
410
|
+
results.summary.overallScore = Math.round(
|
|
411
|
+
results.metrics.reduce((sum, m) => sum + m.maintainability, 0) /
|
|
412
|
+
results.metrics.length
|
|
413
|
+
);
|
|
414
|
+
results.summary.components = results.metrics.length;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Collect issues
|
|
418
|
+
for (const m of results.metrics) {
|
|
419
|
+
if (m.maintainability < (args.threshold || 70)) {
|
|
420
|
+
results.summary.issues.push({
|
|
421
|
+
file: m.file,
|
|
422
|
+
score: m.maintainability,
|
|
423
|
+
suggestion: "Consider refactoring to improve maintainability",
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const passed = results.summary.overallScore >= (args.threshold || 70);
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
success: passed,
|
|
432
|
+
score: results.summary.overallScore,
|
|
433
|
+
threshold: args.threshold || 70,
|
|
434
|
+
components: results.summary.components,
|
|
435
|
+
issues: results.summary.issues,
|
|
436
|
+
summary: passed
|
|
437
|
+
? `✅ Quality PASSED (${results.summary.overallScore}/100)`
|
|
438
|
+
: `❌ Quality FAILED (${results.summary.overallScore}/100, threshold: ${args.threshold || 70})`,
|
|
439
|
+
};
|
|
440
|
+
} catch (error) {
|
|
441
|
+
return { success: false, error: `Quality analysis error: ${error.message}` };
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async function handleSmells(args, projectPath) {
|
|
446
|
+
try {
|
|
447
|
+
// Check for pro features
|
|
448
|
+
const isPro = args.pro === true || args.premium === true;
|
|
449
|
+
|
|
450
|
+
if (isPro) {
|
|
451
|
+
// Use advanced CodeSmellPredictor for pro users
|
|
452
|
+
const { codeSmellPredictor } = require('../src/lib/code-smell-predictor');
|
|
453
|
+
const report = await codeSmellPredictor.predict(projectPath);
|
|
454
|
+
|
|
455
|
+
// Filter by severity if specified
|
|
456
|
+
let filteredSmells = report.smells;
|
|
457
|
+
if (args.severity) {
|
|
458
|
+
const severityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
459
|
+
const minSeverity = severityOrder[args.severity];
|
|
460
|
+
filteredSmells = report.smells.filter(
|
|
461
|
+
(s) => severityOrder[s.severity] >= minSeverity
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
success: filteredSmells.filter((s) => s.severity === "critical").length === 0,
|
|
467
|
+
total: filteredSmells.length,
|
|
468
|
+
critical: filteredSmells.filter((s) => s.severity === "critical").length,
|
|
469
|
+
estimatedDebt: report.estimatedDebt,
|
|
470
|
+
estimatedDebtAI: report.estimatedDebt, // AI-adjusted debt hours
|
|
471
|
+
bySeverity: {
|
|
472
|
+
critical: filteredSmells.filter((s) => s.severity === "critical").length,
|
|
473
|
+
high: filteredSmells.filter((s) => s.severity === "high").length,
|
|
474
|
+
medium: filteredSmells.filter((s) => s.severity === "medium").length,
|
|
475
|
+
low: filteredSmells.filter((s) => s.severity === "low").length,
|
|
476
|
+
},
|
|
477
|
+
smells: filteredSmells.slice(0, args.limit || 50),
|
|
478
|
+
trends: report.trends,
|
|
479
|
+
recommendations: filteredSmells.flatMap(s => s.recommendation).slice(0, 10),
|
|
480
|
+
summary:
|
|
481
|
+
filteredSmells.length === 0
|
|
482
|
+
? "✅ No significant code smells detected (PRO Analysis)"
|
|
483
|
+
: `⚠️ Found ${filteredSmells.length} code smell(s) - ${report.estimatedDebt}h AI-assisted debt (PRO Analysis)`,
|
|
484
|
+
proFeatures: {
|
|
485
|
+
advancedPredictor: true,
|
|
486
|
+
technicalDebtCalculation: true,
|
|
487
|
+
trendAnalysis: true,
|
|
488
|
+
recommendations: true,
|
|
489
|
+
aiAdjustedTimelines: true
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
} else {
|
|
493
|
+
// Basic smell detection for free users
|
|
494
|
+
const smells = [];
|
|
495
|
+
const sourceFiles = await findSourceFiles(projectPath, args.file);
|
|
496
|
+
|
|
497
|
+
for (const file of sourceFiles.slice(0, 10)) { // Limited to 10 files for free tier
|
|
498
|
+
const content = await fs.readFile(file, "utf8");
|
|
499
|
+
const fileSmells = detectCodeSmells(content, file, args.severity || "medium");
|
|
500
|
+
smells.push(...fileSmells);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Filter by severity
|
|
504
|
+
const severityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
505
|
+
const minSeverity = severityOrder[args.severity || "medium"];
|
|
506
|
+
const filteredSmells = smells.filter(
|
|
507
|
+
(s) => severityOrder[s.severity] >= minSeverity
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
success: filteredSmells.filter((s) => s.severity === "critical").length === 0,
|
|
512
|
+
total: filteredSmells.length,
|
|
513
|
+
bySeverity: {
|
|
514
|
+
critical: filteredSmells.filter((s) => s.severity === "critical").length,
|
|
515
|
+
high: filteredSmells.filter((s) => s.severity === "high").length,
|
|
516
|
+
medium: filteredSmells.filter((s) => s.severity === "medium").length,
|
|
517
|
+
},
|
|
518
|
+
smells: filteredSmells.slice(0, 10), // Limited results for free tier
|
|
519
|
+
summary:
|
|
520
|
+
filteredSmells.length === 0
|
|
521
|
+
? "✅ No significant code smells detected (Basic Analysis)"
|
|
522
|
+
: `⚠️ Found ${filteredSmells.length} code smell(s) (Basic Analysis - Upgrade to PRO for advanced features)`,
|
|
523
|
+
upgradePrompt: "🚀 Upgrade to PRO for technical debt calculation, trend analysis, and AI-powered recommendations"
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
} catch (error) {
|
|
527
|
+
return { success: false, error: `Smell detection error: ${error.message}` };
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async function handleHallucination(args, projectPath) {
|
|
532
|
+
try {
|
|
533
|
+
const risks = [];
|
|
534
|
+
|
|
535
|
+
if (args.claims) {
|
|
536
|
+
// Verify specific claims
|
|
537
|
+
for (const claim of args.claims) {
|
|
538
|
+
const verified = await verifyClaim(claim, projectPath);
|
|
539
|
+
if (!verified.found) {
|
|
540
|
+
risks.push({
|
|
541
|
+
type: "unverified-claim",
|
|
542
|
+
claim,
|
|
543
|
+
confidence: verified.confidence,
|
|
544
|
+
suggestion: "Verify claim exists in source code",
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (args.spec) {
|
|
551
|
+
// Verify MDC specification
|
|
552
|
+
const specPath = path.resolve(projectPath, args.spec);
|
|
553
|
+
const specContent = await fs.readFile(specPath, "utf8");
|
|
554
|
+
const specRisks = await verifySpecification(specContent, projectPath);
|
|
555
|
+
risks.push(...specRisks);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const riskScore = risks.length > 0 ? Math.min(100, risks.length * 15) : 0;
|
|
559
|
+
|
|
560
|
+
return {
|
|
561
|
+
success: riskScore < 30,
|
|
562
|
+
riskScore,
|
|
563
|
+
risks: risks.slice(0, 10),
|
|
564
|
+
verified: args.claims ? args.claims.length - risks.length : 0,
|
|
565
|
+
unverified: risks.length,
|
|
566
|
+
summary:
|
|
567
|
+
riskScore < 30
|
|
568
|
+
? "✅ Low hallucination risk"
|
|
569
|
+
: riskScore < 60
|
|
570
|
+
? "⚠️ Medium hallucination risk - review claims"
|
|
571
|
+
: "🚨 High hallucination risk - verify all claims",
|
|
572
|
+
};
|
|
573
|
+
} catch (error) {
|
|
574
|
+
return { success: false, error: `Hallucination check error: ${error.message}` };
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async function handleBreaking(args, projectPath) {
|
|
579
|
+
try {
|
|
580
|
+
const changes = [];
|
|
581
|
+
|
|
582
|
+
// Compare with previous version if specified
|
|
583
|
+
if (args.previousVersion) {
|
|
584
|
+
// Get current exports
|
|
585
|
+
const currentExports = await extractExports(projectPath);
|
|
586
|
+
|
|
587
|
+
// This would compare with previous version
|
|
588
|
+
// For now, return structure
|
|
589
|
+
return {
|
|
590
|
+
success: true,
|
|
591
|
+
changes: [],
|
|
592
|
+
summary: "✅ No breaking changes detected",
|
|
593
|
+
note: "Full comparison requires git history or previous version path",
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return {
|
|
598
|
+
success: true,
|
|
599
|
+
changes: [],
|
|
600
|
+
summary: "Specify previousVersion to compare",
|
|
601
|
+
};
|
|
602
|
+
} catch (error) {
|
|
603
|
+
return { success: false, error: `Breaking change error: ${error.message}` };
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async function handleMDC(args, projectPath) {
|
|
608
|
+
try {
|
|
609
|
+
const outputDir = path.resolve(projectPath, args.outputDir || ".specs");
|
|
610
|
+
|
|
611
|
+
// Create output directory
|
|
612
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
613
|
+
|
|
614
|
+
// Find source files and generate basic MDC
|
|
615
|
+
const sourceFiles = await findSourceFiles(projectPath);
|
|
616
|
+
const components = [];
|
|
617
|
+
|
|
618
|
+
for (const file of sourceFiles.slice(0, 50)) {
|
|
619
|
+
const content = await fs.readFile(file, "utf8");
|
|
620
|
+
const extracted = extractComponents(content, file, projectPath);
|
|
621
|
+
components.push(...extracted);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Group by category
|
|
625
|
+
const categories = {};
|
|
626
|
+
for (const comp of components) {
|
|
627
|
+
const cat = categorizeComponent(comp);
|
|
628
|
+
if (!categories[cat]) categories[cat] = [];
|
|
629
|
+
categories[cat].push(comp);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Generate MDC files
|
|
633
|
+
const generated = [];
|
|
634
|
+
for (const [category, comps] of Object.entries(categories)) {
|
|
635
|
+
if (args.categories && !args.categories.includes(category)) continue;
|
|
636
|
+
|
|
637
|
+
const mdcContent = generateMDCContent(category, comps);
|
|
638
|
+
const fileName = `${category.replace("-", "_")}.mdc`;
|
|
639
|
+
await fs.writeFile(path.join(outputDir, fileName), mdcContent);
|
|
640
|
+
generated.push(fileName);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return {
|
|
644
|
+
success: true,
|
|
645
|
+
outputDir,
|
|
646
|
+
generated,
|
|
647
|
+
componentCount: components.length,
|
|
648
|
+
summary: `✅ Generated ${generated.length} MDC file(s) with ${components.length} components`,
|
|
649
|
+
};
|
|
650
|
+
} catch (error) {
|
|
651
|
+
return { success: false, error: `MDC generation error: ${error.message}` };
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async function handleCoverage(args, projectPath) {
|
|
656
|
+
try {
|
|
657
|
+
const coverage = {
|
|
658
|
+
total: 0,
|
|
659
|
+
tested: 0,
|
|
660
|
+
untested: [],
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
const sourceFiles = await findSourceFiles(projectPath);
|
|
664
|
+
const testFiles = await findTestFiles(projectPath);
|
|
665
|
+
|
|
666
|
+
for (const file of sourceFiles) {
|
|
667
|
+
coverage.total++;
|
|
668
|
+
const baseName = path.basename(file, path.extname(file));
|
|
669
|
+
const hasTest = testFiles.some(
|
|
670
|
+
(t) => t.includes(baseName) && (t.includes(".test.") || t.includes(".spec."))
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
if (hasTest) {
|
|
674
|
+
coverage.tested++;
|
|
675
|
+
} else {
|
|
676
|
+
coverage.untested.push(path.relative(projectPath, file));
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const percentage = coverage.total > 0
|
|
681
|
+
? Math.round((coverage.tested / coverage.total) * 100)
|
|
682
|
+
: 0;
|
|
683
|
+
|
|
684
|
+
return {
|
|
685
|
+
success: percentage >= 50,
|
|
686
|
+
coverage: percentage,
|
|
687
|
+
total: coverage.total,
|
|
688
|
+
tested: coverage.tested,
|
|
689
|
+
untested: coverage.untested.slice(0, 10),
|
|
690
|
+
summary:
|
|
691
|
+
percentage >= 80
|
|
692
|
+
? `✅ Good coverage (${percentage}%)`
|
|
693
|
+
: percentage >= 50
|
|
694
|
+
? `⚠️ Fair coverage (${percentage}%)`
|
|
695
|
+
: `❌ Low coverage (${percentage}%)`,
|
|
696
|
+
};
|
|
697
|
+
} catch (error) {
|
|
698
|
+
return { success: false, error: `Coverage error: ${error.message}` };
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// ============================================================================
|
|
703
|
+
// HELPER FUNCTIONS
|
|
704
|
+
// ============================================================================
|
|
705
|
+
|
|
706
|
+
async function findSourceFiles(projectPath, specificFile) {
|
|
707
|
+
if (specificFile) {
|
|
708
|
+
return [path.resolve(projectPath, specificFile)];
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const files = [];
|
|
712
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx"];
|
|
713
|
+
const excludeDirs = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
|
|
714
|
+
|
|
715
|
+
async function walk(dir) {
|
|
716
|
+
try {
|
|
717
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
718
|
+
for (const entry of entries) {
|
|
719
|
+
const fullPath = path.join(dir, entry.name);
|
|
720
|
+
const relativePath = path.relative(projectPath, fullPath);
|
|
721
|
+
|
|
722
|
+
if (entry.isDirectory()) {
|
|
723
|
+
if (!excludeDirs.some((d) => relativePath.includes(d))) {
|
|
724
|
+
await walk(fullPath);
|
|
725
|
+
}
|
|
726
|
+
} else if (entry.isFile()) {
|
|
727
|
+
const ext = path.extname(entry.name);
|
|
728
|
+
if (extensions.includes(ext) && !relativePath.includes("node_modules")) {
|
|
729
|
+
files.push(fullPath);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
} catch {}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
await walk(projectPath);
|
|
737
|
+
return files;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
async function findTestFiles(projectPath) {
|
|
741
|
+
const files = [];
|
|
742
|
+
|
|
743
|
+
async function walk(dir) {
|
|
744
|
+
try {
|
|
745
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
746
|
+
for (const entry of entries) {
|
|
747
|
+
const fullPath = path.join(dir, entry.name);
|
|
748
|
+
if (entry.isDirectory() && !entry.name.includes("node_modules")) {
|
|
749
|
+
await walk(fullPath);
|
|
750
|
+
} else if (
|
|
751
|
+
entry.isFile() &&
|
|
752
|
+
(entry.name.includes(".test.") || entry.name.includes(".spec."))
|
|
753
|
+
) {
|
|
754
|
+
files.push(fullPath);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
} catch {}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
await walk(projectPath);
|
|
761
|
+
return files;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function analyzeFileQuality(content, filePath) {
|
|
765
|
+
const lines = content.split("\n");
|
|
766
|
+
const linesOfCode = lines.filter((l) => l.trim() && !l.trim().startsWith("//")).length;
|
|
767
|
+
|
|
768
|
+
let complexity = 1;
|
|
769
|
+
for (const line of lines) {
|
|
770
|
+
if (/\bif\s*\(/.test(line)) complexity++;
|
|
771
|
+
if (/\bfor\s*\(/.test(line)) complexity++;
|
|
772
|
+
if (/\bwhile\s*\(/.test(line)) complexity++;
|
|
773
|
+
if (/\bswitch\s*\(/.test(line)) complexity++;
|
|
774
|
+
if (/&&|\|\|/.test(line)) complexity++;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const maintainability = Math.max(
|
|
778
|
+
0,
|
|
779
|
+
Math.min(100, 100 - complexity * 2 - Math.max(0, linesOfCode - 200) / 10)
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
return {
|
|
783
|
+
file: filePath,
|
|
784
|
+
linesOfCode,
|
|
785
|
+
complexity,
|
|
786
|
+
maintainability: Math.round(maintainability),
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function detectCodeSmells(content, filePath, minSeverity) {
|
|
791
|
+
const smells = [];
|
|
792
|
+
const lines = content.split("\n");
|
|
793
|
+
|
|
794
|
+
// Long function
|
|
795
|
+
let functionLength = 0;
|
|
796
|
+
let inFunction = false;
|
|
797
|
+
for (let i = 0; i < lines.length; i++) {
|
|
798
|
+
if (/function\s+\w+|=>\s*{/.test(lines[i])) {
|
|
799
|
+
inFunction = true;
|
|
800
|
+
functionLength = 0;
|
|
801
|
+
}
|
|
802
|
+
if (inFunction) functionLength++;
|
|
803
|
+
if (lines[i].includes("}") && inFunction && functionLength > 50) {
|
|
804
|
+
smells.push({
|
|
805
|
+
type: "long-function",
|
|
806
|
+
severity: "medium",
|
|
807
|
+
line: i + 1,
|
|
808
|
+
file: filePath,
|
|
809
|
+
message: `Function is ${functionLength} lines (>50)`,
|
|
810
|
+
});
|
|
811
|
+
inFunction = false;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Deep nesting
|
|
816
|
+
let maxNesting = 0;
|
|
817
|
+
let currentNesting = 0;
|
|
818
|
+
for (let i = 0; i < lines.length; i++) {
|
|
819
|
+
currentNesting += (lines[i].match(/{/g) || []).length;
|
|
820
|
+
currentNesting -= (lines[i].match(/}/g) || []).length;
|
|
821
|
+
if (currentNesting > 5) {
|
|
822
|
+
smells.push({
|
|
823
|
+
type: "deep-nesting",
|
|
824
|
+
severity: "high",
|
|
825
|
+
line: i + 1,
|
|
826
|
+
file: filePath,
|
|
827
|
+
message: `Nesting depth of ${currentNesting} (>5)`,
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
maxNesting = Math.max(maxNesting, currentNesting);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Magic numbers
|
|
834
|
+
for (let i = 0; i < lines.length; i++) {
|
|
835
|
+
const magicMatch = lines[i].match(/[^a-zA-Z_](\d{2,})[^a-zA-Z_]/);
|
|
836
|
+
if (magicMatch && !lines[i].includes("const") && !lines[i].includes("//")) {
|
|
837
|
+
smells.push({
|
|
838
|
+
type: "magic-number",
|
|
839
|
+
severity: "low",
|
|
840
|
+
line: i + 1,
|
|
841
|
+
file: filePath,
|
|
842
|
+
message: `Magic number ${magicMatch[1]} should be a named constant`,
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Console.log in production
|
|
848
|
+
for (let i = 0; i < lines.length; i++) {
|
|
849
|
+
if (/console\.(log|debug)\(/.test(lines[i]) && !filePath.includes("test")) {
|
|
850
|
+
smells.push({
|
|
851
|
+
type: "console-log",
|
|
852
|
+
severity: "low",
|
|
853
|
+
line: i + 1,
|
|
854
|
+
file: filePath,
|
|
855
|
+
message: "console.log in production code",
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// Empty catch
|
|
861
|
+
for (let i = 0; i < lines.length; i++) {
|
|
862
|
+
if (/catch\s*\([^)]*\)\s*{\s*}/.test(lines[i])) {
|
|
863
|
+
smells.push({
|
|
864
|
+
type: "empty-catch",
|
|
865
|
+
severity: "critical",
|
|
866
|
+
line: i + 1,
|
|
867
|
+
file: filePath,
|
|
868
|
+
message: "Empty catch block swallows errors",
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
return smells;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
async function verifyClaim(claim, projectPath) {
|
|
877
|
+
// Simple claim verification - search for keywords
|
|
878
|
+
const keywords = claim.split(/\s+/).filter((w) => w.length > 3);
|
|
879
|
+
const files = await findSourceFiles(projectPath);
|
|
880
|
+
|
|
881
|
+
for (const file of files.slice(0, 50)) {
|
|
882
|
+
try {
|
|
883
|
+
const content = await fs.readFile(file, "utf8");
|
|
884
|
+
const found = keywords.filter((k) => content.includes(k)).length;
|
|
885
|
+
if (found >= keywords.length * 0.5) {
|
|
886
|
+
return { found: true, confidence: found / keywords.length };
|
|
887
|
+
}
|
|
888
|
+
} catch {}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
return { found: false, confidence: 0 };
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
async function verifySpecification(content, projectPath) {
|
|
895
|
+
const risks = [];
|
|
896
|
+
// Parse spec and verify claims
|
|
897
|
+
const componentMatches = content.match(/Component:\s*`?(\w+)`?/g) || [];
|
|
898
|
+
|
|
899
|
+
for (const match of componentMatches) {
|
|
900
|
+
const name = match.replace(/Component:\s*`?(\w+)`?/, "$1");
|
|
901
|
+
const verified = await verifyClaim(name, projectPath);
|
|
902
|
+
if (!verified.found) {
|
|
903
|
+
risks.push({
|
|
904
|
+
type: "unverified-component",
|
|
905
|
+
component: name,
|
|
906
|
+
suggestion: "Verify component exists in source",
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
return risks;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
async function extractExports(projectPath) {
|
|
915
|
+
const exports = [];
|
|
916
|
+
const files = await findSourceFiles(projectPath);
|
|
917
|
+
|
|
918
|
+
for (const file of files.slice(0, 30)) {
|
|
919
|
+
try {
|
|
920
|
+
const content = await fs.readFile(file, "utf8");
|
|
921
|
+
const exportMatches = content.match(/export\s+(class|function|const|interface|type)\s+(\w+)/g) || [];
|
|
922
|
+
for (const match of exportMatches) {
|
|
923
|
+
const parts = match.split(/\s+/);
|
|
924
|
+
exports.push({ type: parts[1], name: parts[2], file });
|
|
925
|
+
}
|
|
926
|
+
} catch {}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
return exports;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function extractComponents(content, filePath, projectPath) {
|
|
933
|
+
const components = [];
|
|
934
|
+
const relativePath = path.relative(projectPath, filePath);
|
|
935
|
+
|
|
936
|
+
// Extract classes
|
|
937
|
+
const classMatches = content.match(/(?:export\s+)?class\s+(\w+)/g) || [];
|
|
938
|
+
for (const match of classMatches) {
|
|
939
|
+
const name = match.replace(/(?:export\s+)?class\s+/, "");
|
|
940
|
+
components.push({ name, type: "class", path: relativePath });
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// Extract functions
|
|
944
|
+
const funcMatches = content.match(/(?:export\s+)?(?:async\s+)?function\s+(\w+)/g) || [];
|
|
945
|
+
for (const match of funcMatches) {
|
|
946
|
+
const name = match.replace(/(?:export\s+)?(?:async\s+)?function\s+/, "");
|
|
947
|
+
components.push({ name, type: "function", path: relativePath });
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
return components;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function categorizeComponent(comp) {
|
|
954
|
+
const pathLower = comp.path.toLowerCase();
|
|
955
|
+
|
|
956
|
+
if (pathLower.includes("auth") || pathLower.includes("security")) return "security";
|
|
957
|
+
if (pathLower.includes("api") || pathLower.includes("route")) return "integration";
|
|
958
|
+
if (pathLower.includes("design") || pathLower.includes("theme")) return "design-system";
|
|
959
|
+
if (pathLower.includes("util") || pathLower.includes("helper")) return "utility";
|
|
960
|
+
return "architecture";
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function generateMDCContent(category, components) {
|
|
964
|
+
const titles = {
|
|
965
|
+
architecture: "Architecture Overview",
|
|
966
|
+
security: "Security Architecture",
|
|
967
|
+
integration: "Integration Specifications",
|
|
968
|
+
"design-system": "Design System",
|
|
969
|
+
utility: "Utility Functions",
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
let content = `---
|
|
973
|
+
description: ${titles[category] || category} documentation
|
|
974
|
+
category: ${category}
|
|
975
|
+
generatedAt: ${new Date().toISOString()}
|
|
976
|
+
---
|
|
977
|
+
|
|
978
|
+
# ${titles[category] || category}
|
|
979
|
+
|
|
980
|
+
## Components
|
|
981
|
+
|
|
982
|
+
`;
|
|
983
|
+
|
|
984
|
+
for (const comp of components.slice(0, 20)) {
|
|
985
|
+
content += `### ${comp.name}\n`;
|
|
986
|
+
content += `- **Type:** ${comp.type}\n`;
|
|
987
|
+
content += `- **Path:** \`${comp.path}\`\n\n`;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
return content;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// ============================================================================
|
|
994
|
+
// AUTOFIX HANDLER
|
|
995
|
+
// ============================================================================
|
|
996
|
+
|
|
997
|
+
async function handleAutofix(args, projectPath) {
|
|
998
|
+
try {
|
|
999
|
+
const { fixPack, dryRun = true, model, maxAttempts = 3 } = args;
|
|
1000
|
+
|
|
1001
|
+
if (!fixPack) {
|
|
1002
|
+
return {
|
|
1003
|
+
success: false,
|
|
1004
|
+
error: "fixPack is required. Choose from: route-integrity, placeholders, type-errors, build-blockers, test-failures",
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// Set model environment variable if provided
|
|
1009
|
+
if (model) {
|
|
1010
|
+
if (model.startsWith('gpt') || model.startsWith('o1')) {
|
|
1011
|
+
process.env.OPENAI_MODEL = model;
|
|
1012
|
+
} else if (model.startsWith('claude')) {
|
|
1013
|
+
process.env.ANTHROPIC_MODEL = model;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// Check for API key
|
|
1018
|
+
if (!process.env.OPENAI_API_KEY && !process.env.ANTHROPIC_API_KEY) {
|
|
1019
|
+
return {
|
|
1020
|
+
success: false,
|
|
1021
|
+
error: "No AI API key configured. Set OPENAI_API_KEY or ANTHROPIC_API_KEY environment variable.",
|
|
1022
|
+
hint: "Export your API key before starting the MCP server.",
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// Try to load verified autofix module
|
|
1027
|
+
const modulePath = path.resolve(__dirname, "../packages/core/dist/verified-autofix.js");
|
|
1028
|
+
|
|
1029
|
+
let verifiedAutofix;
|
|
1030
|
+
try {
|
|
1031
|
+
verifiedAutofix = await import(modulePath);
|
|
1032
|
+
} catch (e) {
|
|
1033
|
+
// Try alternative path
|
|
1034
|
+
try {
|
|
1035
|
+
const altPath = path.resolve(projectPath, "packages/core/dist/verified-autofix.js");
|
|
1036
|
+
verifiedAutofix = await import(altPath);
|
|
1037
|
+
} catch {
|
|
1038
|
+
return {
|
|
1039
|
+
success: false,
|
|
1040
|
+
error: "Verified autofix module not found. Run 'pnpm build' in packages/core first.",
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Run autofix
|
|
1046
|
+
const result = await verifiedAutofix.runVerifiedAutofix({
|
|
1047
|
+
projectPath,
|
|
1048
|
+
fixPack,
|
|
1049
|
+
dryRun,
|
|
1050
|
+
maxAttempts,
|
|
1051
|
+
verbose: true,
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
return {
|
|
1055
|
+
success: result.success,
|
|
1056
|
+
fixPack: result.fixPack,
|
|
1057
|
+
attempts: result.attempts,
|
|
1058
|
+
maxAttempts: result.maxAttempts,
|
|
1059
|
+
filesModified: result.filesModified,
|
|
1060
|
+
generatedDiffs: result.generatedDiffs,
|
|
1061
|
+
aiExplanation: result.aiExplanation,
|
|
1062
|
+
errors: result.errors,
|
|
1063
|
+
dryRun,
|
|
1064
|
+
duration: result.duration,
|
|
1065
|
+
message: result.success
|
|
1066
|
+
? (dryRun ? "Preview generated. Run with dryRun=false to apply." : "Fixes applied successfully.")
|
|
1067
|
+
: "Autofix could not complete. Check errors for details.",
|
|
1068
|
+
};
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
return {
|
|
1071
|
+
success: false,
|
|
1072
|
+
error: `Autofix failed: ${error.message}`,
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
}
|